4.4 KiB
Extend api-plugin + api with host-seeded ServiceRegistry slots
Packages: @suaveplan/api-plugin, @suaveplan/api
Status: PROPOSED
Type: feature (additive — no breaking changes)
- Spec deltas: ./specs/api-plugin/spec.md, ./specs/api/spec.md
Why
The just-shipped 5-package api split exposed a foundation-layer gap: PluginContext provides getService<K extends keyof ServiceRegistry>(name) but nothing on the host side ever seeds app, openAPIRegistry, migrationRunner, or db into the registry. The first concrete consumer (the in-flight api-plugin-auth, blocked at scaffold time) cannot resolve those services without forcing operators to register them by hand — which breaks the plug-and-play promise.
Fix: add PluginRegistry.seedServices(map) to @suaveplan/api-plugin, augment ServiceRegistry from @suaveplan/api with the canonical host slots, and seed them in SuaveApp.initialize() BEFORE registry.initialize() runs so every plugin's onRegister / onInit / onReady can resolve them via typed ctx.getService("app"), etc.
What changes
@suaveplan/api-plugin (additive)
- ADDED:
PluginRegistry.seedServices<K extends keyof ServiceRegistry>(map: { [K in keyof ServiceRegistry]?: ServiceRegistry[K] }): void— bulk-registers entries in an internal seeded-services map. Idempotent; later seeds override earlier ones. ThrowsPluginError(PLUGIN_INVALID_STATE)if called afterinitialize()has started. - MODIFIED:
createPluginContext(and the per-plugin context factory) pre-populates the shared service map from the registry's seeded entries before pluginonRegister. Plugins callingctx.getService("X")see the seeded entry without needing a sibling plugin'sregisterServiceto have run first. - ADDED: 1 new error code:
PLUGIN_INVALID_STATE(already part of the spec's 10 → 11 codes total — used byseedServicespost-initialize). - No public-surface removals.
@suaveplan/api (additive)
-
ADDED: ServiceRegistry module augmentation declaring canonical host slots:
// src/services/registry-augmentation.ts (genesis-original) declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { app: OpenAPIHono; openAPIRegistry: OpenAPIRegistry; migrationRunner?: MigrationRunner; db?: SurrealAdapter; logger: LoggerLike; eventBus: EventBusLike; clock: Clock; } } -
MODIFIED:
SuaveAppOptionsgains optionaldb?: SurrealAdapterfield (for plugins that need direct DB access — wired into the seeded slot). Defaults toundefined; no behaviour change for callers that don't supply it. -
MODIFIED:
SuaveApp.initialize()callsthis._registry.seedServices({ app: this._hono, openAPIRegistry: this._openAPIRegistry, migrationRunner: this._migrations, db: this._db, logger: this._logger, eventBus: this._events, clock: this._clock })immediately beforethis._registry.initialize(). -
ADDED: subpath export
./services/registry-augmentationso consumers can side-effect-import the augmentation if they only consume sub-packages. -
No public-surface removals.
Out of scope
- Backwards-compatible runtime shim. This is additive — existing callers of
ctx.getService("X")for non-augmented keys continue to returnundefinedand silently work. No deprecation needed. - Per-request services (request-id, tenant, etc.). Those still flow via Hono
c.get(key)— they're per-request, not per-plugin. - Plugin manifest-side service declarations. Plugins continue to declare what they
registerServicevia the existing surface; this change only adds host-seeded slots.
Source mapping
Genesis-original — surfaced by add-api-plugin-auth's scaffold-time blocker (the subagent stopped because PluginContext.app doesn't exist on the shipped surface, then noticed SuaveApp never seeds app/openAPIRegistry/etc into the registry).
Impact
Affected dependents
@suaveplan/api-plugin-auth(in-flight) — unblocks. Resolvesapp/openAPIRegistry/migrationRunnervia typedctx.getServiceinstead of operator pre-registration.@suaveplan/api-plugin-user,@suaveplan/api-plugin-vault(in-flight) — inherit the same resolution path.- Every future
@suaveplan/api-plugin-*package — same.
Risk: low. Additive on both packages; no existing API surface changes. Existing api-plugin + api tests stay green (verified by post-patch dependent sweep).