diff --git a/reference/http/api.md b/reference/http/api.md index 7256fd88..2c787dfd 100644 --- a/reference/http/api.md +++ b/reference/http/api.md @@ -434,12 +434,23 @@ contentTypes.set('text/xml', { ### Handler Interface -| Property | Type | Description | -| ----------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `serialize(data)` | `(any) => Buffer \| Uint8Array \| string` | Serialize data for a response | -| `serializeStream(data)` | `(any) => Readable` | Serialize a streaming response body. Called once per response with the whole iterable (not once per chunk), and only when the response body is an iterable or async iterable. Returns a Node.js [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable). | -| `deserialize(buffer)` | `(Buffer \| string) => any` | Deserialize an incoming request body. String for `text/*` types, Buffer for binary types. | -| `q` | number (0–1) | Quality indicator for content negotiation. Defaults to `1`. | +| Property | Type | Description | +| ----------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `serialize(data)` | `(any) => Buffer \| Uint8Array \| string \| Readable` | Serialize a complete response body. Used when the response body is not iterable, or when the handler defines no `serializeStream`. Most handlers return a string or a `Buffer`; the built-in `text/csv` handler returns a [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable), which Harper streams to the response. | +| `serializeStream(data)` | `(any) => Readable \| Buffer \| string` | Serialize a streaming response body. Called once per response with the whole iterable (not once per chunk), and only when the response body is an object that is iterable or async iterable. A Node.js [`Readable`](https://nodejs.org/api/stream.html#class-streamreadable) is the usual return, but it is not the only one — see [Non-streaming returns from `serializeStream`](#non-streaming-returns-from-serializestream). | +| `deserialize(buffer)` | `(Buffer) => any` | Deserialize an incoming request body. Harper always passes a `Buffer`, including for `text/*` types — a text handler calls `buffer.toString()` itself. | +| `q` | number (0–1) | Quality indicator for content negotiation. Defaults to `1`. | + +#### Non-streaming returns from `serializeStream` + +`serializeStream` may return a `Readable`, a `Buffer`, a string, or any iterable or async iterable. Harper writes all of these to the response: a `Readable` is piped, an iterable is wrapped with `Readable.from()`, and a `Buffer` or string is sent as the whole body. + +The union is not theoretical. Harper picks `serializeStream` over `serialize` whenever the response body is iterable, and a plain array is iterable — so a resource that returns an array reaches `serializeStream`. A handler that has nothing to stream in that case can serialize the value in one shot instead: + +- The built-in `application/x-msgpack` handler streams only non-array iterables. For a plain array it returns a packed `Buffer`. +- The built-in NDJSON handler returns a string when it is handed a value that is not iterable. + +One caveat if you return something other than a stream: when Brotli compression applies to the response (the client sent `Accept-Encoding: br` and `http.compressionThreshold` is non-zero), Harper pipes the value returned by `serializeStream` into the compressor. A `Buffer` or string has no `pipe`, so a handler that must work with compression enabled should return a stream — wrap a buffered result with `Readable.from([buffer])` if there is nothing to stream incrementally. ---