Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/callback-api-deprecations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"oc": patch
"oc-fastify-server-adapter": patch
"oc-metadata-adapters-utils": patch
---

Add promise-first adapter boundaries and one-time deprecation warnings for legacy registry, plugin, storage, metadata, and HTTP server callback APIs while preserving their 0.x behavior.
29 changes: 14 additions & 15 deletions V1.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,15 @@ pages?" — **no**.

| # | Change | Rationale | Migration (registry operator) | Status |
|---|--------|-----------|-------------------------------|--------|
| R1 | **Promise-only registry API** | `registry.start/close/register`, plugin `register`, and adapter methods are callback-based behind a `universalify` shim. This is registry-boot code, not app code. | `await registry.start()` etc.; callback signatures removed. | 🚧 In progress — the 0.x additive `start/close/register` precursor is implemented in [#1539](https://github.com/opencomponents/oc/pull/1539) (tracked in [#1528](https://github.com/opencomponents/oc/issues/1528)); plugin and adapter promise APIs plus the v1 callback removal remain open. |
| R1 | **Promise-only registry API** | `registry.start/close/register`, plugin `register`, and adapter methods are callback-based behind a `universalify` shim. This is registry-boot code, not app code. | `await registry.start()` etc.; callback signatures removed. | 🚧 In progress — the 0.x additive registry lifecycle, plugin, and adapter promise paths now exist with callback warnings; v1 callback removal remains. |
| R2 | **ESM-only registry packages + `exports` map** | The registry runs as an app you deploy; ESM-only is an acceptable registry break and kills dual-build complexity. Locks down deep imports. | Registry deployment uses ESM (or dynamic `import()`); only documented entry points importable. | ⬜ Not started |
| R3 | **Adapter-native hook/config types** | `beforePublish`, `publishValidation`, `conf.routes` handlers, and `Authentication` leak Express `Request`/`Response`; a global `Express` augmentation ships in types. | Hooks receive `OcRequest`/`OcResponse`; Express becomes one adapter (Fastify, etc.). Express-compat shim provided. | ✅ Done — `HttpServerAdapter` interface + in-core Express adapter (default) and a first-class `oc-fastify-server-adapter` package are shipped ([#1507](https://github.com/opencomponents/oc/pull/1507), [#1508](https://github.com/opencomponents/oc/pull/1508), [#1509](https://github.com/opencomponents/oc/pull/1509), [#1510](https://github.com/opencomponents/oc/pull/1510), [#1511](https://github.com/opencomponents/oc/pull/1511), [#1512](https://github.com/opencomponents/oc/pull/1512), [#1513](https://github.com/opencomponents/oc/pull/1513), [#1518](https://github.com/opencomponents/oc/pull/1518)). Follow-up hardening (trailing-slash routing default) tracked in [#1515](https://github.com/opencomponents/oc/issues/1515), partially landed via [#1521](https://github.com/opencomponents/oc/pull/1521). |
| R4 | **Package split** | Monorepo already; a major is the moment. `oc-registry` (server), `oc-cli` (tooling), `oc-core` (shared types/runtime); `oc` = thin umbrella/CLI. | Update imports once (`import { Registry } from 'oc-registry'`). `oc` still installs the CLI. | 🚧 In progress — Turborepo/Changesets monorepo foundation ([#1476](https://github.com/opencomponents/oc/pull/1476)); storage adapters ([#1505](https://github.com/opencomponents/oc/pull/1505)) and metadata adapters ([#1503](https://github.com/opencomponents/oc/pull/1503)) already extracted into their own `packages/*`. The actual `oc-registry`/`oc-cli`/`oc-core` split of the core package has not started. |
| R5 | **Promise-only plugins + typed errors** | Plugin `register(options, deps, next)` is callback-style; code throws raw strings in places. | Plugins return promises; errors become typed `Error` subclasses. | ⬜ Not started |
| R5 | **Promise-only plugins + typed errors** | Plugin `register(options, deps, next)` is callback-style; code throws raw strings in places. | Plugins return promises; errors become typed `Error` subclasses. | 🚧 In progress — promise registration and one-time callback deprecation warnings are shipped on 0.x; typed errors and v1 removal remain. |
| R6 | **Remove global mutable state** | `events-handler` subscriptions and `plugins-initialiser` `deferredLoads` are process-global, so multiple registries in one process interfere. | Per-instance event/plugin state. Subtle behavior change; documented. | ⬜ Not started |
| R7 | **Metadata store = source of truth; bundle a default** | The metadata-adapter subsystem is newer and superior; flat-file is legacy. A bundled default keeps zero-config working. | No-config registries get the default store automatically. | 🚧 In progress — pluggable metadata store landed as **opt-in** ([#1503](https://github.com/opencomponents/oc/pull/1503)); storage-only remains the default. Bundling a default store + making it the source of truth is still open. |
| R8 | **Flat-file `components.json` = export-only legacy** | It's now a projection of the metadata store. | File is still *exported* for legacy consumers via `exportLegacyFiles`; no data loss. | ⬜ Not started (depends on R7) |
| R9 | **Promise-only storage/metadata adapters** | Drop the callback-adapter conversion shim. | Adapter authors return promises; operators use current promise-based adapter versions. | ⬜ Not started |
| R9 | **Promise-only storage/metadata adapters** | Drop the callback-adapter conversion shim. | Adapter authors return promises; operators use current promise-based adapter versions. | 🚧 In progress — storage and metadata callback shims now warn once and preserve 0.x behavior; v1 removal remains. |
| R10 | **Extract legacy jade/handlebars runtime to an opt-in compat package** | Keep core lean; old components must still render. | Operators still serving pre-`oc-template-*` components install/enable the compat package (registry-config action, not a component rewrite). | ⬜ Not started |
| R11 | **Configurable CORS / security headers; keep URLs + Accept contract** | CORS is hardcoded today; clients/components depend on route shapes so URLs stay. | New optional `cors` config; defaults preserve today's behavior. No URL versioning. | ⬜ Not started — `registry/middleware/cors.ts` still hardcodes headers |
| R12 | **Remove the `oc-cli-version` publish gate** | Dates to `fcf87069` ("Added preventing old oc versions to publish") when CLI + registry shipped lockstep. It rejects any CLI whose `major.minor` is behind the registry — a `1.0.0` registry would reject **all** v0.x CLIs on publish. `template.minOcVersion` + package-structure validation now cover the real need. | Publishing no longer fails on CLI semver skew. Keep `node-version` + `template.minOcVersion` checks. | ⬜ Not started — gate still present in `registry/routes/publish.ts` |
Expand All @@ -148,7 +148,7 @@ pages?" — **no**.
| # | Change | Rationale | Migration (web app owner) | Status |
|---|--------|-----------|---------------------------|--------|
| P1 | **Browser `oc-client.js` stays fully drop-in (guarantee, not a change)** | A web app embeds it and renders `<oc-component>`. Breaking it means editing every app. | **Nothing.** Same script URL, stable `window.oc` API, auto-injected importmap, unchanged `<oc-component>` markup (see §2b). | ✅ Holding — guarantee upheld; the browser client was extracted into its own `oc-client-browser` package ([#1478](https://github.com/opencomponents/oc/pull/1478)) with drop-in behavior preserved (e.g. [#1516](https://github.com/opencomponents/oc/pull/1516) fixed a DOM-move unmount regression, [#1521](https://github.com/opencomponents/oc/pull/1521) started removing trailing slashes from `oc.build` hrefs). |
| P2 | **Node SSR `oc-client` → promise API + TS types + ESM** | Separate repo, callback-based, untyped. This is the single intentional app-layer break, done once per SSR app. | `await client.renderComponent(...)`; adopt ESM/types. Smoothed by a 0.x callback shim + deprecation warnings. | ⬜ Not started (lives in the separate `opencomponents/oc-client` repo) |
| P2 | **Node SSR `oc-client` → promise API + TS types + ESM** | Separate repo, callback-based, untyped. This is the single intentional app-layer break, done once per SSR app. | `await client.renderComponent(...)`; adopt ESM/types. Smoothed by a 0.x callback shim + deprecation warnings. | 🚧 In progress — the 0.x promise/callback shim and registry integration warning are implemented; ESM/types remain for v1 (client lives in the separate `opencomponents/oc-client-node` repo). |
| P3 | **Node `oc-client` keeps a conservative engine floor (`>=20`)** | Don't force SSR apps to upgrade their Node runtime just to consume components. | SSR apps stay on Node 20+ even though the registry requires 22+. | ⬜ Not started |

### 4.3 Component layer `[COMPONENT-ADDITIVE]` — additive + opt-in only
Expand Down Expand Up @@ -203,11 +203,9 @@ the client and in the (opt-in) component authoring path, not in the host page.
### Track 1 — `0.x` (non-breaking, ships continuously, now)
Land everything that can be additive so v1 becomes mostly *removals*:

1. 🚧 Add promise-returning variants of `registry.start/close/register`, plugins, adapters,
and the node `oc-client` (keep callbacks working, emit deprecation warnings). — tracked in
[#1528](https://github.com/opencomponents/oc/issues/1528) (registry `start/close/register`),
implemented in [#1539](https://github.com/opencomponents/oc/pull/1539); plugin, adapter,
and node `oc-client` promise variants remain open.
1. ✅ Add promise-returning variants of `registry.start/close/register`, plugins, adapters,
and the node `oc-client` (keep callbacks working, emit one-time deprecation warnings).
Registry lifecycle is tracked in [#1528](https://github.com/opencomponents/oc/issues/1528).
2. ⬜ Migrate registry internals to ESM incrementally. — not started.
3. ⬜ Introduce the modern component runtime as **opt-in** and make it the `oc init` default,
while keeping legacy authoring fully supported. — not started.
Expand All @@ -220,10 +218,10 @@ Land everything that can be additive so v1 becomes mostly *removals*:
node `oc-client` API. — the central `deprecate()` utility plus warnings for `s3`,
`refreshInterval`, boolean `discovery`, and `oc.json` `mocks` are **merged** via
[#1532](https://github.com/opencomponents/oc/pull/1532) (tracked in
[#1529](https://github.com/opencomponents/oc/issues/1529)). The callback registry API
warning is implemented in [#1539](https://github.com/opencomponents/oc/pull/1539).
Still to cover: bare `jade`/`handlebars` on `oc init` and the callback node `oc-client`
API.
[#1529](https://github.com/opencomponents/oc/issues/1529)). Registry lifecycle callbacks
are implemented in [#1539](https://github.com/opencomponents/oc/pull/1539); this pass adds
plugin, storage, metadata, HTTP adapter, and node SSR client callback warnings. Still to
cover: bare `jade`/`handlebars` on `oc init`.

**Additional registry-layer groundwork landed ahead of/alongside the plan above** (see §4.1
for detail): the `HttpServerAdapter` abstraction with Express (default) and Fastify adapters
Expand Down Expand Up @@ -317,8 +315,9 @@ storage/metadata adapter package extraction (R4, 🚧 in progress — [#1476](ht
- [#1529](https://github.com/opencomponents/oc/issues/1529) — Central deprecation-warning pass
for v1 removals — Track 1 item 5. Core utility + config-option warnings merged via
[#1532](https://github.com/opencomponents/oc/pull/1532); callback registry warnings are
implemented in [#1539](https://github.com/opencomponents/oc/pull/1539), while bare
`jade`/`handlebars` and callback node `oc-client` warnings remain open.
implemented in [#1539](https://github.com/opencomponents/oc/pull/1539), and this pass covers
plugin, adapter, and node SSR client callbacks. Bare `jade`/`handlebars` warnings remain
open.
- [#1515](https://github.com/opencomponents/oc/issues/1515) — Make the Fastify adapter's
`ignoreTrailingSlash` configurable (default `false`) and stop OC from emitting trailing-slash
URLs — follow-on hardening for R3.
Expand Down
2 changes: 1 addition & 1 deletion packages/oc-fastify-server-adapter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Fastify HTTP server adapter for the OC registry. The adapter is opt-in and imple
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.56`).
Use this adapter with an OC version that supports the HTTP server adapter contract (`>=0.50.56`).

## Usage

Expand Down
18 changes: 14 additions & 4 deletions packages/oc-fastify-server-adapter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,25 @@ const ocResponseSym = Symbol('ocResponse');
const timingStartSym = Symbol('timingStart');
const multipartParsedSym = Symbol('multipartParsed');
const defaultBodyLimit = 100 * 1024;
const warnedDeprecations = new Set<string>();
const warningStoreKey = Symbol.for('opencomponents.deprecation-warnings');
const callbackWarningId = 'http-server-adapter-callbacks';

const warnAboutCallback = () => {
const id = 'http-server-adapter-callbacks';
if (warnedDeprecations.has(id)) {
const processWithWarningStore = process as typeof process & {
[key: symbol]: unknown;
};
let warned = processWithWarningStore[warningStoreKey] as
| Set<string>
| undefined;
if (!warned) {
warned = new Set<string>();
processWithWarningStore[warningStoreKey] = warned;
}
if (warned.has(callbackWarningId)) {
return;
}

warnedDeprecations.add(id);
warned.add(callbackWarningId);
process.emitWarning(
'The HTTP server adapter callback API is deprecated and will be removed in OpenComponents v1 - use the returned promises instead.',
'DeprecationWarning'
Expand Down
16 changes: 16 additions & 0 deletions packages/oc-metadata-adapters-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ export interface MetadataStore {
}>;
}

/** A promise- or callback-based metadata store accepted on the 0.x line. */
export type MetadataStoreLike = {
adapterType: string;
isValid(): boolean;
initialise: (...args: any[]) => unknown;
getAllComponents: (...args: any[]) => unknown;
addVersion: (...args: any[]) => unknown;
reserveVersion: (...args: any[]) => unknown;
commitVersion: (...args: any[]) => unknown;
abortVersion: (...args: any[]) => unknown;
getChangeToken?: (...args: any[]) => unknown;
close?: (...args: any[]) => unknown;
removeVersion?: (...args: any[]) => unknown;
changesSince?: (...args: any[]) => unknown;
};

export interface VersionAlreadyExistsError extends Error {
code: typeof VERSION_ALREADY_EXISTS | typeof VERSION_PUBLISH_IN_PROGRESS;
cause?: unknown;
Expand Down
5 changes: 3 additions & 2 deletions packages/oc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ metadata store. Publishing reserves the metadata row first, uploads package file
to storage only after the reservation succeeds, then commits the row. Duplicate
or in-progress metadata rows are treated as the existing "component version
already exists" publish error. When the registry is shut down via
`registry.close(callback)`, the metadata adapter's optional `close()` hook is
invoked so the adapter can release its connection pool.
`await registry.close()`, the metadata adapter's optional `close()` hook is
invoked so the adapter can release its connection pool. The callback form remains
available on 0.x but is deprecated.

Custom metadata adapters should implement the shared contract exported by
`oc-metadata-adapters-utils`:
Expand Down
5 changes: 3 additions & 2 deletions packages/oc/src/cli/facade/registry-migrate-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { fromPromise } from 'universalify';
import getPromiseBasedMetadataAdapter from '../../registry/domain/metadata-adapter';
import getMetadataAdapterOptions from '../../registry/domain/metadata-adapter-options';
import { backfillMetadataFromStorageDetails } from '../../registry/domain/metadata-migration';
import sanitiseOptions, {
Expand Down Expand Up @@ -69,8 +70,8 @@ const registryMigrateMetadata = ({ logger }: { logger: Logger }) =>
throw new Error('Registry config must include metadata options');
}

const metadataStore = conf.metadata.adapter(
getMetadataAdapterOptions(conf)
const metadataStore = getPromiseBasedMetadataAdapter(
conf.metadata.adapter(getMetadataAdapterOptions(conf))
);
const cdn = getPromiseBasedAdapter(
conf.storage.adapter(conf.storage.options)
Expand Down
2 changes: 2 additions & 0 deletions packages/oc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export type {
ExpressMiddleware,
HttpServerAdapter,
HttpServerAdapterFactory,
HttpServerAdapterLike,
HttpServerListenOptions,
LegacyHttpServerAdapter,
Method,
NativeApp,
OcHandler,
Expand Down
8 changes: 7 additions & 1 deletion packages/oc/src/registry/domain/http-server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ interface CallbackHttpServerAdapterLifecycle {
export type PromiseHttpServerAdapter<TNative = unknown> =
HttpServerAdapterBase<TNative> & PromiseHttpServerAdapterLifecycle;

export type LegacyHttpServerAdapter<TNative = unknown> =
HttpServerAdapterBase<TNative> & CallbackHttpServerAdapterLifecycle;

export type HttpServerAdapter<TNative = unknown> =
| PromiseHttpServerAdapter<TNative>
| (HttpServerAdapterBase<TNative> & CallbackHttpServerAdapterLifecycle);
| LegacyHttpServerAdapter<TNative>;

export type HttpServerAdapterLike<TNative = unknown> =
HttpServerAdapter<TNative>;
Loading
Loading