3.8 KiB
capability-registry — runtime registry of host-injected capabilities
(no submodules)
Purpose
The CapabilityRegistry is the live, single-instance store of every capability adapter wired into a host process. Stage-4 adapter packages (api-router-hono, api-auth-jwt, api-db-adapter, etc.) register against this registry at host boot. The host's lifecycle driver consults it on every plugin install to (a) verify every entry in manifest.requiredCapabilities resolves to a registered adapter and (b) verify the adapter's version satisfies the plugin's apiHost semver range.
The registry is also the disposal coordinator — stop() on the host invokes dispose() here, and disposers run in reverse-registration order so deeper consumers (e.g. db) shut down before their dependencies (e.g. logger).
Features
register(...)with semver-shape validation (INVALID_VERSION), scope-enum validation (INVALID_SCOPE), and duplicate detection (DUPLICATE_REGISTRATION).lookup(id)/require(id)(strict, throwsNOT_REGISTERED).assertSatisfies(ranges)— per-plugin compat check used by the lifecycle driver; throwsNOT_REGISTEREDorVERSION_MISMATCHwith the failing(id, range, actual)context.- Reverse-registration-order
dispose()— collects every disposer failure and surfaces them via one aggregatedDISPOSER_FAILEDerror so a single bad disposer does not block teardown. defineCapability(...)— author-facing helper (identity at runtime) used by adapter packages.
Usage: Basic
import {
CapabilityRegistry,
defineCapability,
} from "@suaveplan/api-host/capability-registry";
const registry = new CapabilityRegistry();
registry.register(
defineCapability({
id: "logger",
version: "1.0.0",
scope: "host",
provider: logger,
})
);
const entry = registry.require("logger"); // throws CAPABILITY.NOT_REGISTERED if missing.
Usage: Advanced
Plug into a manifest's requiredCapabilities resolution:
import { CapabilityRegistry } from "@suaveplan/api-host/capability-registry";
const registry = new CapabilityRegistry();
// ... register every adapter ...
function installPlugin(manifest: PluginManifest): void {
// The manifest pins ranges via `requiredCapabilities` + per-id range table.
registry.assertSatisfies({
db: "^1.0.0",
"auth.session": "~1.4",
});
// ... proceed with lifecycle install ...
}
// Host teardown — disposers run in REVERSE-registration order.
await registry.dispose();
API Reference
class CapabilityRegistryregister(reg): voidlookup<Id>(id): CapabilityRegistration<Id> | undefinedrequire<Id>(id): CapabilityRegistration<Id>has(id): booleanids(): readonly CapabilityId[]size(): numberassertSatisfies(ranges: Record<string, string>): voiddispose(): Promise<void>
defineCapability<const Id>(reg): CapabilityRegistration<Id>CapabilityScope:"host" | "plugin" | "request"CapabilityRegistration<Id>: shape consumed byregister.
Implementation Notes
Version compatibility uses @suaveplan/api-manifest/dep-graph's satisfies(version, range) — the same narrow semver dialect the manifest validator accepts (pinned, caret, tilde, major-wildcard, *). Unparseable ranges throw at register time inside satisfies; the registry catches and re-raises as CAPABILITY.VERSION_MISMATCH so the lifecycle driver only ever has to match on one code. Disposers are awaited sequentially in reverse-registration order — concurrent dispose would risk teardown races between adapters that share resources (e.g. events dispatching to logger). Idempotency: a second dispose() call is a no-op, so the host's stop path can call it freely even after a partial failure.