Streams, events and uploads
Streams out, server-sent events, uploads in.
A handler may return a stream instead of a value: a Node Readable, a web ReadableStream
or any async iterable. It goes out piece by piece, as it comes.
Server-sent events have a contract like any other answer, one schema per event:
import { , } from "@vxnsin/inkan";
.(
"/clock",
{
: { 200: .({ : .({ : .() }) }) },
: [{ : "three ticks", : [{ : { : 1 } }, { : { : 2 } }, { : { : 3 } }] }],
},
() =>
(async function* () {
for (let = 1; !.; ++) {
yield { : "tick", : { } };
await sleep(1000);
}
}),
);The signal fires when the client goes away, so the loop stops. Quiet streams get a
keep-alive comment every 15 seconds. In development, an event that breaks its schema ends the
stream with an error event and a line on the console. inkan check and the docs page read
as many events as an example expects, so a stream that never ends can still be an example.
Uploads are a t.file() in the body. A body with a file is read as
multipart/form-data, and OpenAPI says so:
import { , } from "@vxnsin/inkan";
.(
"/avatars",
{
: .({ : .(), : .().(200_000).("image/png", "image/jpeg") }),
: [{ : "a tiny png", : { : 1, : ("me.png", "…", "image/png") } }],
},
({ }) => save(., ..), // name, type, size and data: a Buffer
);Fields next to the file are coerced like a query ("1" becomes 1). A file that is too big
or of the wrong type is a 400 that says which, and bodyLimit still caps the whole upload.
A file as the body itself, as fetch(url, { body: file }) sends it, is a t.binary():
the bytes, unparsed, with a limit and media types of their own. Its max is that route's
body limit, so it can be larger than the app's:
.("/pictures", { : .().(25_000_000).("image/*") }, ({ , }) =>
store(, ["content-type"]), // body: a Buffer
);Larger than memory, a t.stream() hands the body to the handler unread, chunk by chunk
as it arrives. Past its max the stream throws a 413 problem; an answer before the body is
read, a 401 say, still reaches the client.
import { } from "node:stream/promises";
.("/backups", { : .().(2 * 1024 ** 3) }, async ({ }) => {
await (, createWriteStream(path)); // never all in memory
return (201, { : true });
});Any route can also say bodyLimit: n for itself. The typed client sends a Blob, bytes or
a ReadableStream as they are, and the docs list the media types.