inkan

Problems

How inkan answers when something is wrong: RFC 9457 problem documents.

import {  } from "@vxnsin/inkan";

.("/teas", { : NewTea }, ({  }) => {
  if (store.has(.name)) throw (409, "taken", `There is a tea called ${.name}`, { : .name });
  return store.add();
});

Every error inkan answers with is a problem document, application/problem+json, as RFC 9457 describes it: a stable type a client can switch on, a title, the status, a detail for people, and the request's instance and requestId.

{
  "type": "taken",
  "title": "Conflict",
  "status": 409,
  "detail": "There is a tea called Sencha",
  "name": "Sencha",
  "instance": "/teas",
  "requestId": "0k3f9a2-1c8"
}

problem()

(, type, detail?, extra?)
(, type, { , ,  })

Makes one. Thrown anywhere on the way, in a handler, a hook or a middleware, it becomes the answer. Returned instead of thrown, it is answered the same way, with its own status and shape (before 0.7.0 a returned problem went out as a 200). detail is for people, extra adds fields of your own, headers go on the answer:

throw (429, "slow-down", { : "Try again in a minute", : { "retry-after": "60" } });
throw (402, "insufficient-funds", { : "Top up first", : { : 30 } });
throw (404, "tea-not-found", `There is no tea with id ${id}`); // the short form, detail only

The options form is new in 0.8.0; the positional one stays. Headers can still be set on p.headers of a problem made earlier.

The ones inkan sends by itself

statustypewhen
400validationthe input breaks the contract; errors lists every field, with in, path and message
400invalid-json, invalid-multiparta body that cannot be read
401unauthorizedthe route asks for credentials the request does not carry
404not-foundno route for the path, or ctx.notFound()
405method-not-allowedthe path exists, the method does not; allow names the ones it takes
413body-too-largepast the route's body limit
415unsupported-media-typea body of a type the route does not take
500internalanything else thrown; the detail only in development, and onError sees it
500response-contractin development: the handler answered with something its contract does not promise
504timeoutpast the route's timeout

onProblem()

.((, ) => {
  if (. === 404) return (404, "not-found", "Nothing here. Try /docs");
  if (. >= 500) report(, .);
});

Sees every problem before it becomes the answer, and may change it or return another. A route that lists 400: t.problem() or 404: t.problem() in its contract shows them in the docs and in OpenAPI; a route with input promises a 400 without saying so.

On this page