From 7c772415cb9319c949f47946396f196b0b8f1c8d Mon Sep 17 00:00:00 2001 From: Ricardo Devis Agullo Date: Tue, 7 Jul 2026 10:43:54 +0200 Subject: [PATCH 1/3] Add configurable HTTP server adapter (phase 5) --- http-server-adapter-status.md | 162 ++++++++++++++++++ .../domain/http-server/express-adapter.ts | 11 +- .../src/registry/domain/options-sanitiser.ts | 16 ++ .../oc/src/registry/domain/server-adapter.ts | 29 ++++ packages/oc/src/registry/index.ts | 7 +- packages/oc/src/types.ts | 8 + .../unit/registry-domain-options-sanitiser.js | 46 +++++ .../unit/registry-domain-server-adapter.js | 40 +++++ packages/oc/test/unit/registry.js | 26 ++- 9 files changed, 335 insertions(+), 10 deletions(-) create mode 100644 http-server-adapter-status.md create mode 100644 packages/oc/src/registry/domain/server-adapter.ts create mode 100644 packages/oc/test/unit/registry-domain-server-adapter.js diff --git a/http-server-adapter-status.md b/http-server-adapter-status.md new file mode 100644 index 000000000..35daa7478 --- /dev/null +++ b/http-server-adapter-status.md @@ -0,0 +1,162 @@ +# HTTP Server Adapter Status + +Tracks progress for the HTTP server adapter migration plan. + +## Summary + +| Phase | Status | Notes | +|---|---|---| +| 1. Express pre-work cleanups | Done | Implemented in PR #1507 / commit `b7d5f07b`. Removes `req.route.path`, replaces startup `app.get('port')`, normalizes response header writes. | +| 2. Add neutral facade and adapter interfaces | Done | Added in-core type-only module at `packages/oc/src/registry/domain/http-server/types.ts`; no runtime wiring changed. | +| 3. Implement and wire Express adapter | Done | Added in-core Express adapter and wired registry app creation, middleware/router registration, server lifecycle, errors, close, `native()`, and `httpServer()` through it. | +| 4. Finish core neutralization | Done | Switched core streaming call sites to `res.stream()`, neutralized route/middleware handler types to `OcHandler`/`OcRequest`/`OcResponse`, and kept Express-only compatibility at adapter/public boundaries. | +| 5. Add `server.adapter` config and normaliser | Done | Added `server.adapter` / `server.options`, defaulted to the in-core Express adapter, and routed registry construction through the normaliser. Express remains the only adapter at this stage. | +| 6. Add Fastify adapter and dual-adapter tests | Not started | Separate package, opt-in. Add parity coverage for ETag/304, OPTIONS, urlencoded/query parsing, multipart publish, streaming, and other gotchas. | + +## Done + +### Phase 1 — Express pre-work cleanups + +Completed in PR #1507. + +Changes: + +- Replaced `req.route.path` usage in `static-redirector` with explicit route identities supplied by `router.ts`. +- Registered separate static redirector handlers for: + - `client` + - `dev-client` + - `client-map` + - `local-static` +- Replaced startup logging dependency on Express app settings: + - from `app.get('port')` + - to `options.port` +- Normalized internal response header writes to `res.set(...)` in registry code. + +Verification: + +- `npm run build -w oc` +- `npm run test -w oc` — 927 passing + +### Phase 2 — Add neutral facade and adapter interfaces + +Completed in the current Phase 2 branch. + +Changes: + +- Added in-core type-only module at `packages/oc/src/registry/domain/http-server/types.ts`. +- Added neutral request/response facade types: + - `OcRequest` + - `OcResponse` + - `OcHandler` +- Added supporting adapter types: + - `Method` + - `UploadedFile` + - `ExpressMiddleware` + - `HttpServerAdapter` +- Kept current Express runtime wiring unchanged. +- Deferred external `oc-server-adapter-utils` extraction until an external adapter is published. + +Verification: + +- `npm run build -w oc` + +### Phase 3 — Implement and wire Express adapter + +Completed in the current Phase 3 branch. + +Changes: + +- Added in-core Express adapter at `packages/oc/src/registry/domain/http-server/express-adapter.ts`. +- Kept eager Express app creation behind `native()` so `Registry(opts).app` remains immediately available. +- Moved Express app settings, body parsing, cookies, multipart uploads, request timing, morgan logging, and local error handling behind adapter capabilities. +- Routed registry middleware and routes through `HttpServerAdapter.use(...)`, `route(...)`, and `fromConnect(...)` while preserving Express-compatible handlers for existing hooks/routes. +- Moved server creation, timeouts, keep-alive timeout, listen, server error events, close, listening checks, and `httpServer()` behind adapter lifecycle methods. +- Preserved the public start callback contract: + - `{ app: express.Express; server: http.Server }` +- Updated registry unit tests to mock the HTTP server adapter lifecycle instead of direct Express/http internals. + +Verification: + +- `npm run build -w oc` +- `npm run test -w oc` — 927 passing + +### Phase 4 — Finish core neutralization + +Completed in the current Phase 4 branch. + +Changes: + +- Replaced remaining core `stream.pipe(res)` route call sites with `res.stream(readable)`: + - streamed component responses + - static redirector file responses +- Converted registry domain route and middleware typings from Express `Request`/`Response`/`RequestHandler` to neutral HTTP facade types: + - `OcHandler` + - `OcRequest` + - `OcResponse` +- Converted portable registry middleware (`cors`, `base-url-handler`, `discovery-handler`, and `res.conf` injection) to register directly as neutral handlers instead of connect middleware. +- Kept Express/connect compatibility explicitly at adapter/public-extension boundaries: + - `beforePublish` + - configured user routes + - public `Registry(opts).app` / start callback Express app contract + - public Express global augmentation +- Normalized Express route params in the adapter facade, including wildcard `splat`, so core handlers consume string params through `OcRequest.params`. +- Adjusted Express adapter route-handler continuation semantics so neutral route handlers can complete asynchronously without falling through to later routes, while neutral `use()` middleware still continues the chain when it does not send a response. + +Verification: + +- `npm run build -w oc` +- `npm run test -w oc` — 927 passing + +### Phase 5 — Add server adapter config and normaliser + +Completed in the current Phase 5 branch. + +Changes: + +- Added `Config.server` with: + - `server.adapter` + - `server.options` +- Added server adapter defaulting in `options-sanitiser.ts`, mirroring the storage adapter defaulting pattern. +- Defaulted missing server config to the in-core Express adapter with `{ port: options.port }` options. +- Added `registry/domain/server-adapter.ts` normaliser that accepts either an adapter factory or an already-created adapter instance and validates the adapter shape. +- Updated registry construction to instantiate the HTTP adapter through `options.server.adapter` / `options.server.options` instead of directly importing the Express adapter. +- Kept Express-only behavior at this stage; the default adapter still returns the real Express app and real Node `http.Server`. +- Added unit coverage for server config defaulting, normaliser behavior, and registry adapter selection wiring. + +Verification: + +- `npm run build` +- `npm run test -w oc` — 937 passing + +## Remaining work + +### Phase 6 — Add Fastify adapter and parity tests + +Goal: add opt-in Fastify support against the stable adapter interface. + +Expected work: + +- Create separate Fastify adapter package. +- Implement adapter-native capabilities: + - body parsing + - cookies + - multipart uploads + - request timing + - logging + - local error handling + - ETag behavior + - OPTIONS/HEAD parity +- Parameterize acceptance tests over Express and Fastify adapters. +- Add targeted parity tests for known gotchas: + - ETag / conditional 304 + - CORS preflight OPTIONS + - urlencoded `qs` semantics + - query parsing differences + - multipart publish + - streamed responses + - route wildcard/splat normalization + - default 404/error body differences, if normalized + +## Suggested next step + +Start Phase 6 by adding the separate Fastify adapter package and parameterizing acceptance coverage over both adapters. diff --git a/packages/oc/src/registry/domain/http-server/express-adapter.ts b/packages/oc/src/registry/domain/http-server/express-adapter.ts index 27cb941c2..39fc3fbdc 100644 --- a/packages/oc/src/registry/domain/http-server/express-adapter.ts +++ b/packages/oc/src/registry/domain/http-server/express-adapter.ts @@ -41,9 +41,16 @@ function normaliseParams(raw: Record): Record { } export default function createExpressAdapter( - port?: number | string + options?: unknown ): HttpServerAdapter { - return new ExpressHttpServerAdapter(port); + const adapterOptions = options as + | { port?: number | string } + | number + | string + | undefined; + return new ExpressHttpServerAdapter( + typeof adapterOptions === 'object' ? adapterOptions.port : adapterOptions + ); } class ExpressHttpServerAdapter implements HttpServerAdapter { diff --git a/packages/oc/src/registry/domain/options-sanitiser.ts b/packages/oc/src/registry/domain/options-sanitiser.ts index 44a20f966..8dcedbf6c 100644 --- a/packages/oc/src/registry/domain/options-sanitiser.ts +++ b/packages/oc/src/registry/domain/options-sanitiser.ts @@ -3,6 +3,7 @@ import { compileSync } from 'oc-client-browser'; import settings from '../../resources/settings'; import type { Config } from '../../types'; import * as auth from './authentication'; +import createExpressAdapter from './http-server/express-adapter'; const DEFAULT_NODE_KEEPALIVE_MS = 5000; @@ -197,6 +198,21 @@ export default function optionsSanitiser(input: RegistryOptions): Config { options.keepAliveTimeout = options.keepAliveTimeout || DEFAULT_NODE_KEEPALIVE_MS; + if (!options.server) { + options.server = { + adapter: createExpressAdapter, + options: { port: options.port } + }; + } + + if (options.server && !options.server.adapter) { + options.server.adapter = createExpressAdapter; + } + + if (options.server && typeof options.server.options === 'undefined') { + options.server.options = { port: options.port }; + } + if (options.s3) { options.storage = { adapter: require('oc-s3-storage-adapter'), diff --git a/packages/oc/src/registry/domain/server-adapter.ts b/packages/oc/src/registry/domain/server-adapter.ts new file mode 100644 index 000000000..653e0b825 --- /dev/null +++ b/packages/oc/src/registry/domain/server-adapter.ts @@ -0,0 +1,29 @@ +import type { HttpServerAdapter } from './http-server/types'; + +type HttpServerAdapterFactory = (options?: T) => HttpServerAdapter; + +function isHttpServerAdapter(adapter: unknown): adapter is HttpServerAdapter { + return ( + !!adapter && + typeof adapter === 'object' && + typeof (adapter as HttpServerAdapter).native === 'function' && + typeof (adapter as HttpServerAdapter).listen === 'function' && + typeof (adapter as HttpServerAdapter).httpServer === 'function' + ); +} + +export default function getHttpServerAdapter( + adapter: HttpServerAdapter | HttpServerAdapterFactory, + options?: T +): HttpServerAdapter { + if (isHttpServerAdapter(adapter)) { + return adapter; + } + + const instance = adapter(options); + if (!isHttpServerAdapter(instance)) { + throw new Error('Invalid HTTP server adapter'); + } + + return instance; +} diff --git a/packages/oc/src/registry/index.ts b/packages/oc/src/registry/index.ts index 06466caab..2f04efe84 100644 --- a/packages/oc/src/registry/index.ts +++ b/packages/oc/src/registry/index.ts @@ -5,10 +5,10 @@ import type express from 'express'; import type { Plugin } from '../types'; import appStart from './app-start'; import eventsHandler from './domain/events-handler'; -import createExpressAdapter from './domain/http-server/express-adapter'; import sanitiseOptions, { RegistryOptions } from './domain/options-sanitiser'; import * as pluginsInitialiser from './domain/plugins-initialiser'; import Repository from './domain/repository'; +import getHttpServerAdapter from './domain/server-adapter'; import * as validator from './domain/validators'; import * as middleware from './middleware'; import { create as createRouter } from './router'; @@ -24,7 +24,10 @@ export default function registry(inputOptions: RegistryOptions) { const options = sanitiseOptions(inputOptions); const plugins: Plugin[] = []; - const adapter = middleware.bind(createExpressAdapter(options.port), options); + const adapter = middleware.bind( + getHttpServerAdapter(options.server.adapter, options.server.options), + options + ); const app = adapter.native() as express.Express; const repository = Repository(options); diff --git a/packages/oc/src/types.ts b/packages/oc/src/types.ts index 934a6103c..ab4a96ce6 100644 --- a/packages/oc/src/types.ts +++ b/packages/oc/src/types.ts @@ -2,6 +2,7 @@ import type { NextFunction, Request, Response } from 'express'; import type { MetadataStore as MetadataStoreType } from 'oc-metadata-adapters-utils'; import type { StorageAdapter } from 'oc-storage-adapters-utils'; import type { PackageJson } from 'type-fest'; +import type { HttpServerAdapter } from './registry/domain/http-server/types'; export type { ComponentRow, MetadataStore } from 'oc-metadata-adapters-utils'; @@ -426,6 +427,13 @@ export interface Config { adapter: (options: T) => StorageAdapter; options: T & { componentsDir: string }; }; + /** + * Low-level HTTP server adapter used by the registry. + */ + server: { + adapter: (options?: unknown) => HttpServerAdapter; + options?: unknown; + }; /** * Directory used by the registry for temporary files. */ diff --git a/packages/oc/test/unit/registry-domain-options-sanitiser.js b/packages/oc/test/unit/registry-domain-options-sanitiser.js index e6676ab07..fd692810d 100644 --- a/packages/oc/test/unit/registry-domain-options-sanitiser.js +++ b/packages/oc/test/unit/registry-domain-options-sanitiser.js @@ -20,6 +20,52 @@ describe('registry : domain : options-sanitiser', () => { expect(sanitise(options)[property]).to.eql(value); }); } + + it('should default to the Express HTTP server adapter', () => { + expect(sanitise(options).server.adapter).to.equal( + require('../../dist/registry/domain/http-server/express-adapter').default + ); + expect(sanitise(options).server.options).to.eql({ port: 3000 }); + }); + }); + + describe('server adapter configuration', () => { + describe('when no server adapter is provided', () => { + const options = { server: {}, port: 1234, baseUrl: 'dummy' }; + + it('should default to the Express HTTP server adapter', () => { + expect(sanitise(options).server.adapter).to.equal( + require('../../dist/registry/domain/http-server/express-adapter') + .default + ); + }); + }); + + describe('when no server options are provided', () => { + const serverAdapter = () => ({}); + const options = { + server: { adapter: serverAdapter }, + port: 1234, + baseUrl: 'dummy' + }; + + it('should pass the port to the server options', () => { + expect(sanitise(options).server.options).to.eql({ port: 1234 }); + }); + }); + + describe('when server options are provided', () => { + const serverAdapter = () => ({}); + const options = { + server: { adapter: serverAdapter, options: { custom: true } }, + port: 1234, + baseUrl: 'dummy' + }; + + it('should leave them untouched', () => { + expect(sanitise(options).server.options).to.eql({ custom: true }); + }); + }); }); describe('when verbosity is provided', () => { diff --git a/packages/oc/test/unit/registry-domain-server-adapter.js b/packages/oc/test/unit/registry-domain-server-adapter.js new file mode 100644 index 000000000..6f53e1943 --- /dev/null +++ b/packages/oc/test/unit/registry-domain-server-adapter.js @@ -0,0 +1,40 @@ +const expect = require('chai').expect; +const sinon = require('sinon'); + +describe('registry : domain : server-adapter', () => { + const getHttpServerAdapter = + require('../../dist/registry/domain/server-adapter').default; + + const createAdapter = () => ({ + native: sinon.stub(), + listen: sinon.stub(), + httpServer: sinon.stub() + }); + + describe('when given an adapter factory', () => { + it('should return the created adapter', () => { + const adapter = createAdapter(); + const factory = sinon.stub().returns(adapter); + const options = { custom: true }; + + expect(getHttpServerAdapter(factory, options)).to.equal(adapter); + expect(factory.calledWith(options)).to.be.true; + }); + }); + + describe('when given an adapter instance', () => { + it('should return the adapter untouched', () => { + const adapter = createAdapter(); + + expect(getHttpServerAdapter(adapter)).to.equal(adapter); + }); + }); + + describe('when the factory returns an invalid adapter', () => { + it('should throw', () => { + expect(() => getHttpServerAdapter(() => ({}))).to.throw( + 'Invalid HTTP server adapter' + ); + }); + }); +}); diff --git a/packages/oc/test/unit/registry.js b/packages/oc/test/unit/registry.js index 13396b1af..39e92bae6 100644 --- a/packages/oc/test/unit/registry.js +++ b/packages/oc/test/unit/registry.js @@ -15,10 +15,12 @@ describe('registry', () => { close: sinon.stub() }); + const serverAdapterFactory = sinon.stub(); + const deps = { './app-start': sinon.stub(), './domain/events-handler': { fire: sinon.stub() }, - './domain/http-server/express-adapter': sinon.stub().callsFake(() => { + './domain/server-adapter': sinon.stub().callsFake(() => { adapter = createAdapter(); return adapter; }), @@ -65,20 +67,29 @@ describe('registry', () => { deps['./domain/validators'].validateRegistryConfiguration.returns({ isValid: true }); - deps['./domain/options-sanitiser'].returns({ port: 3000 }); + deps['./domain/options-sanitiser'].returns({ + port: 3000, + server: { adapter: serverAdapterFactory, options: { port: 3000 } } + }); registry = Registry({}); }); it('should instantiate the HTTP server adapter', () => { - expect(deps['./domain/http-server/express-adapter'].calledWith(3000)).to - .be.true; + expect( + deps['./domain/server-adapter'].calledWith(serverAdapterFactory, { + port: 3000 + }) + ).to.be.true; }); it('should bind the middleware', () => { const bind = deps['./middleware'].bind; expect(bind.called).to.be.true; expect(bind.lastCall.args[0]).to.equal(adapter); - expect(bind.lastCall.args[1]).to.eql({ port: 3000 }); + expect(bind.lastCall.args[1]).to.eql({ + port: 3000, + server: { adapter: serverAdapterFactory, options: { port: 3000 } } + }); }); it('should instanciate the repository', () => { @@ -247,7 +258,10 @@ describe('registry', () => { deps['./domain/validators'].validateRegistryConfiguration.returns({ isValid: true }); - deps['./domain/options-sanitiser'].returns({ port: 3000 }); + deps['./domain/options-sanitiser'].returns({ + port: 3000, + server: { adapter: serverAdapterFactory, options: { port: 3000 } } + }); }); it('should close the repository when the server is not listening', (done) => { From 12e0cd4dca46b1a0c62247b56de1d443bfe3b59a Mon Sep 17 00:00:00 2001 From: Ricardo Devis Agullo Date: Tue, 7 Jul 2026 10:47:41 +0200 Subject: [PATCH 2/3] Remove HTTP server adapter status doc from branch --- http-server-adapter-status.md | 162 ---------------------------------- 1 file changed, 162 deletions(-) delete mode 100644 http-server-adapter-status.md diff --git a/http-server-adapter-status.md b/http-server-adapter-status.md deleted file mode 100644 index 35daa7478..000000000 --- a/http-server-adapter-status.md +++ /dev/null @@ -1,162 +0,0 @@ -# HTTP Server Adapter Status - -Tracks progress for the HTTP server adapter migration plan. - -## Summary - -| Phase | Status | Notes | -|---|---|---| -| 1. Express pre-work cleanups | Done | Implemented in PR #1507 / commit `b7d5f07b`. Removes `req.route.path`, replaces startup `app.get('port')`, normalizes response header writes. | -| 2. Add neutral facade and adapter interfaces | Done | Added in-core type-only module at `packages/oc/src/registry/domain/http-server/types.ts`; no runtime wiring changed. | -| 3. Implement and wire Express adapter | Done | Added in-core Express adapter and wired registry app creation, middleware/router registration, server lifecycle, errors, close, `native()`, and `httpServer()` through it. | -| 4. Finish core neutralization | Done | Switched core streaming call sites to `res.stream()`, neutralized route/middleware handler types to `OcHandler`/`OcRequest`/`OcResponse`, and kept Express-only compatibility at adapter/public boundaries. | -| 5. Add `server.adapter` config and normaliser | Done | Added `server.adapter` / `server.options`, defaulted to the in-core Express adapter, and routed registry construction through the normaliser. Express remains the only adapter at this stage. | -| 6. Add Fastify adapter and dual-adapter tests | Not started | Separate package, opt-in. Add parity coverage for ETag/304, OPTIONS, urlencoded/query parsing, multipart publish, streaming, and other gotchas. | - -## Done - -### Phase 1 — Express pre-work cleanups - -Completed in PR #1507. - -Changes: - -- Replaced `req.route.path` usage in `static-redirector` with explicit route identities supplied by `router.ts`. -- Registered separate static redirector handlers for: - - `client` - - `dev-client` - - `client-map` - - `local-static` -- Replaced startup logging dependency on Express app settings: - - from `app.get('port')` - - to `options.port` -- Normalized internal response header writes to `res.set(...)` in registry code. - -Verification: - -- `npm run build -w oc` -- `npm run test -w oc` — 927 passing - -### Phase 2 — Add neutral facade and adapter interfaces - -Completed in the current Phase 2 branch. - -Changes: - -- Added in-core type-only module at `packages/oc/src/registry/domain/http-server/types.ts`. -- Added neutral request/response facade types: - - `OcRequest` - - `OcResponse` - - `OcHandler` -- Added supporting adapter types: - - `Method` - - `UploadedFile` - - `ExpressMiddleware` - - `HttpServerAdapter` -- Kept current Express runtime wiring unchanged. -- Deferred external `oc-server-adapter-utils` extraction until an external adapter is published. - -Verification: - -- `npm run build -w oc` - -### Phase 3 — Implement and wire Express adapter - -Completed in the current Phase 3 branch. - -Changes: - -- Added in-core Express adapter at `packages/oc/src/registry/domain/http-server/express-adapter.ts`. -- Kept eager Express app creation behind `native()` so `Registry(opts).app` remains immediately available. -- Moved Express app settings, body parsing, cookies, multipart uploads, request timing, morgan logging, and local error handling behind adapter capabilities. -- Routed registry middleware and routes through `HttpServerAdapter.use(...)`, `route(...)`, and `fromConnect(...)` while preserving Express-compatible handlers for existing hooks/routes. -- Moved server creation, timeouts, keep-alive timeout, listen, server error events, close, listening checks, and `httpServer()` behind adapter lifecycle methods. -- Preserved the public start callback contract: - - `{ app: express.Express; server: http.Server }` -- Updated registry unit tests to mock the HTTP server adapter lifecycle instead of direct Express/http internals. - -Verification: - -- `npm run build -w oc` -- `npm run test -w oc` — 927 passing - -### Phase 4 — Finish core neutralization - -Completed in the current Phase 4 branch. - -Changes: - -- Replaced remaining core `stream.pipe(res)` route call sites with `res.stream(readable)`: - - streamed component responses - - static redirector file responses -- Converted registry domain route and middleware typings from Express `Request`/`Response`/`RequestHandler` to neutral HTTP facade types: - - `OcHandler` - - `OcRequest` - - `OcResponse` -- Converted portable registry middleware (`cors`, `base-url-handler`, `discovery-handler`, and `res.conf` injection) to register directly as neutral handlers instead of connect middleware. -- Kept Express/connect compatibility explicitly at adapter/public-extension boundaries: - - `beforePublish` - - configured user routes - - public `Registry(opts).app` / start callback Express app contract - - public Express global augmentation -- Normalized Express route params in the adapter facade, including wildcard `splat`, so core handlers consume string params through `OcRequest.params`. -- Adjusted Express adapter route-handler continuation semantics so neutral route handlers can complete asynchronously without falling through to later routes, while neutral `use()` middleware still continues the chain when it does not send a response. - -Verification: - -- `npm run build -w oc` -- `npm run test -w oc` — 927 passing - -### Phase 5 — Add server adapter config and normaliser - -Completed in the current Phase 5 branch. - -Changes: - -- Added `Config.server` with: - - `server.adapter` - - `server.options` -- Added server adapter defaulting in `options-sanitiser.ts`, mirroring the storage adapter defaulting pattern. -- Defaulted missing server config to the in-core Express adapter with `{ port: options.port }` options. -- Added `registry/domain/server-adapter.ts` normaliser that accepts either an adapter factory or an already-created adapter instance and validates the adapter shape. -- Updated registry construction to instantiate the HTTP adapter through `options.server.adapter` / `options.server.options` instead of directly importing the Express adapter. -- Kept Express-only behavior at this stage; the default adapter still returns the real Express app and real Node `http.Server`. -- Added unit coverage for server config defaulting, normaliser behavior, and registry adapter selection wiring. - -Verification: - -- `npm run build` -- `npm run test -w oc` — 937 passing - -## Remaining work - -### Phase 6 — Add Fastify adapter and parity tests - -Goal: add opt-in Fastify support against the stable adapter interface. - -Expected work: - -- Create separate Fastify adapter package. -- Implement adapter-native capabilities: - - body parsing - - cookies - - multipart uploads - - request timing - - logging - - local error handling - - ETag behavior - - OPTIONS/HEAD parity -- Parameterize acceptance tests over Express and Fastify adapters. -- Add targeted parity tests for known gotchas: - - ETag / conditional 304 - - CORS preflight OPTIONS - - urlencoded `qs` semantics - - query parsing differences - - multipart publish - - streamed responses - - route wildcard/splat normalization - - default 404/error body differences, if normalized - -## Suggested next step - -Start Phase 6 by adding the separate Fastify adapter package and parameterizing acceptance coverage over both adapters. From c7d248eefd15c8f550efcc811774aa065d98ceea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:48:51 +0000 Subject: [PATCH 3/3] Remove redundant options.server guard checks in options-sanitiser --- packages/oc/src/registry/domain/options-sanitiser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/oc/src/registry/domain/options-sanitiser.ts b/packages/oc/src/registry/domain/options-sanitiser.ts index 8dcedbf6c..f32b1a8ec 100644 --- a/packages/oc/src/registry/domain/options-sanitiser.ts +++ b/packages/oc/src/registry/domain/options-sanitiser.ts @@ -205,11 +205,11 @@ export default function optionsSanitiser(input: RegistryOptions): Config { }; } - if (options.server && !options.server.adapter) { + if (!options.server.adapter) { options.server.adapter = createExpressAdapter; } - if (options.server && typeof options.server.options === 'undefined') { + if (typeof options.server.options === 'undefined') { options.server.options = { port: options.port }; }