diff --git a/website/docs/registry/registry-configuration.md b/website/docs/registry/registry-configuration.md index 4f6641f..520cdbc 100644 --- a/website/docs/registry/registry-configuration.md +++ b/website/docs/registry/registry-configuration.md @@ -104,6 +104,7 @@ For unsuscribing to all [events](#registry-events). | verbosity | number | no | `0` | Verbosity level of the console logger (0 = silent) | | timeout | number (milliseconds) | no | `120000` | HTTP request timeout in milliseconds | | keepAliveTimeout | number (milliseconds) | no | `5000` | Milliseconds the HTTP server keeps idle connections alive | +| server | object | no | Express adapter | Opt-in [HTTP server adapter](/docs/registry/registry-server-adapters) configuration. Lets you swap the registry's underlying HTTP server (e.g. Fastify) instead of the default Express. Absent → Express (default) | ### Storage and Caching Options diff --git a/website/docs/registry/registry-server-adapters.md b/website/docs/registry/registry-server-adapters.md new file mode 100644 index 0000000..2c8e74b --- /dev/null +++ b/website/docs/registry/registry-server-adapters.md @@ -0,0 +1,134 @@ +--- +sidebar_position: 4 +--- + +# Registry Server Adapters + +:::info New feature +Swappable HTTP server adapters are available since **v0.50.58**. +::: + +## Overview + +The registry's HTTP layer sits behind a neutral `HttpServerAdapter` interface. +By default the registry uses **Express**, exactly as before — nothing changes +if you don't touch the `server` option. But the adapter is now pluggable, so +you can swap in a different HTTP server implementation without the registry +core taking a dependency on it. + +The first alternative adapter is [`oc-fastify-server-adapter`](#fastify-adapter---oc-fastify-server-adapter), +which lets a registry run on [Fastify](https://fastify.dev/) instead of +Express. + +## Configuration + +Add a `server` block to the registry configuration with an `adapter` factory +and its `options`: + +```js +const oc = require("oc"); + +const registry = oc.Registry({ + // regular OC registry options... + server: { + adapter: someServerAdapterFactory, + options: { + // adapter-specific options + }, + }, +}); +``` + +| Parameter | Type | Mandatory | Default | Description | +| ---------------- | -------- | --------- | ------------------------ | ----------------------------------------------------------------------------- | +| `server` | object | no | Express adapter | Presence swaps the HTTP server implementation. Absent → Express (default). | +| `server.adapter` | function | no | Express adapter factory | Server adapter factory returning an `HttpServerAdapter`. | +| `server.options` | object | no | `{ port: options.port }` | Options passed to the adapter factory. | + +If you omit `server` entirely, or omit `server.adapter`, the registry falls +back to the built-in Express adapter, so this is fully opt-in and non-breaking. + +## Fastify adapter - `oc-fastify-server-adapter` + +A Fastify implementation of the `HttpServerAdapter` interface. + +```sh +npm install oc-fastify-server-adapter fastify +``` + +`oc` is a peer dependency. Use this adapter with an OC version that exports +the HTTP server adapter types (`>=0.50.58`). + +```js +const oc = require("oc"); +const createFastifyAdapter = require("oc-fastify-server-adapter").default; + +const registry = oc.Registry({ + // regular OC registry options... + server: { + adapter: createFastifyAdapter, + options: { + host: "0.0.0.0", + port: 3030, + trustProxy: true, + }, + }, +}); +``` + +`options.host` defaults to `0.0.0.0`, matching Node/Express `server.listen(port)` +behavior. Set it to `127.0.0.1` or another interface to bind more narrowly. + +### Opt-in contract and differences from Express + +Because Fastify isn't a drop-in replacement for Express, there are a few +behavioral differences to be aware of before switching: + +- `registry.start()` returns a Fastify instance as `app`, not an Express + application. Code that reaches into `app` must use Fastify APIs. +- `server.httpServer()` and the `{ server }` value returned by + `registry.start()` are the underlying Node `http.Server`. +- User routes are registered through Fastify's router. OC route patterns such + as `:param` and `*splat` are normalized, but advanced Express-only route + syntax is not supported. +- `fromConnect()` supports common Connect middleware, including middleware + that ends the response without calling `next()`. Middleware that depends on + Express-specific request/response APIs may still need changes. +- JSON and urlencoded body limits follow the OC/Express configuration. Gzip, + deflate, and brotli encoded request bodies are inflated before Fastify + parses them. +- Urlencoded bodies use `qs` parsing to match Express `extended: true` + semantics. Query strings use Node's `querystring` parser to match Express + 5's simple parser. +- Cookies use OC/Express-style options. `maxAge` is accepted in milliseconds + and translated to Fastify's seconds-based serializer, with `Path=/` as the + default. Signed cookies are not supported unless the adapter grows a + Fastify cookie secret option. +- Multipart uploads are normalized to OC's uploaded file shape. Oversized + fields return a `LIMIT_FIELD_VALUE`-style 413 instead of silently + truncating. +- Strong ETags and 304 conditional responses are enabled to match the + Express registry. +- `OPTIONS` requests are handled by the adapter so OC CORS middleware can + answer preflight requests. The `Allow` header is scoped to matching routes + when possible; unmatched `OPTIONS` paths still receive the global adapter + method set for preflight compatibility. +- Handler errors are rendered by OC's configured error handler. The Fastify + adapter returns `text/html` error bodies when OC enables the error + handler, but default Fastify 404/error body shapes can differ from Express + for requests outside OC routes. +- Non-empty OC route handler chains must eventually send a response. This + matches existing OC callback-style handlers; empty handler arrays return + 404 defensively. +- Logging uses Fastify response timing, so formatting is not byte-for-byte + identical to Morgan's Express output. + +## Writing a custom server adapter + +The registry core has no dependency on Express or Fastify — it only calls the +`HttpServerAdapter` contract. To support another HTTP server, implement a +factory function that returns an object with the same shape used by the +built-in adapters (route registration, body parsing, cookies, file uploads, +request timing, logging, error handling, listen/close, etc.), and pass it as +`server.adapter`. Look at `oc-fastify-server-adapter`'s source for a complete +reference implementation. diff --git a/website/sidebars.js b/website/sidebars.js index 1e02a16..2964828 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -71,6 +71,11 @@ const sidebars = { id: "registry/registry-metadata-stores", className: "sidebar-item-new", }, + { + type: "doc", + id: "registry/registry-server-adapters", + className: "sidebar-item-new", + }, "registry/registry-using-google-storage", ], },