Files
skills/docs/api-stack/api/api-host/src/capability-registry/capability-registry.md
2026-05-29 08:20:56 +01:00

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, throws NOT_REGISTERED).
  • assertSatisfies(ranges) — per-plugin compat check used by the lifecycle driver; throws NOT_REGISTERED or VERSION_MISMATCH with the failing (id, range, actual) context.
  • Reverse-registration-order dispose() — collects every disposer failure and surfaces them via one aggregated DISPOSER_FAILED error 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 CapabilityRegistry
    • register(reg): void
    • lookup<Id>(id): CapabilityRegistration<Id> | undefined
    • require<Id>(id): CapabilityRegistration<Id>
    • has(id): boolean
    • ids(): readonly CapabilityId[]
    • size(): number
    • assertSatisfies(ranges: Record<string, string>): void
    • dispose(): Promise<void>
  • defineCapability<const Id>(reg): CapabilityRegistration<Id>
  • CapabilityScope: "host" | "plugin" | "request"
  • CapabilityRegistration<Id>: shape consumed by register.

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.