3.7 KiB
Capability Spec Delta: api (extend-api-foundation-host-services)
Package: @suaveplan/api
Status: PROPOSED
ADDED Requirements
Requirement: SuaveApp seeds canonical host services before plugin init
SuaveApp.initialize() SHALL call this._registry.seedServices(...) immediately before this._registry.initialize(). The seeded map SHALL include app (the OpenAPIHono instance), openAPIRegistry, logger, eventBus, clock always; AND optionally migrationRunner (when supplied via SuaveAppOptions.migrationRunner) AND db (when supplied via the new SuaveAppOptions.db). After init, every plugin's ctx.getService("app") / ctx.getService("openAPIRegistry") / etc. SHALL resolve to the corresponding instance with typed inference via the ServiceRegistry module augmentation.
Scenario: plugin resolves seeded app via ctx.getService
- WHEN a SuaveApp is built with a plugin whose
onInit(ctx) { recordApp(ctx.getService("app")) }runs, andapp.initialize()is awaited - THEN
recordAppwas called with the OpenAPIHono instance the app holds atthis._hono(referential equality, not a wrapper)
Scenario: missing migrationRunner option leaves the slot undefined
- WHEN
new SuaveApp({ ...options /* no migrationRunner */ })is built and a plugin asksctx.getService("migrationRunner") - THEN the result is
undefined
Verified by: src/app/seed-services.test.ts::"plugin resolves seeded app via ctx.getService" AND src/app/seed-services.test.ts::"openAPIRegistry / logger / eventBus / clock are seeded" AND src/app/seed-services.test.ts::"missing migrationRunner leaves slot undefined"
Requirement: ServiceRegistry module augmentation declares canonical host slots
The package SHALL ship a side-effect-importable module at ./services/registry-augmentation that declares:
declare module "@suaveplan/api-plugin/service-registry" {
interface ServiceRegistry {
app: OpenAPIHono;
openAPIRegistry: OpenAPIRegistry;
migrationRunner?: MigrationRunner;
db?: SurrealAdapter;
logger: LoggerLike;
eventBus: EventBusLike;
clock: Clock;
}
}
Consumers SHALL get full type inference on ctx.getService("app") (returns OpenAPIHono | undefined), etc. The augmentation SHALL be auto-loaded by the package root barrel (src/index.ts side-effect imports it) so any consumer of @suaveplan/api picks it up without an explicit import. Subpath consumers of @suaveplan/api/services/registry-augmentation get it directly.
Scenario: ctx.getService("app") type-narrows to OpenAPIHono | undefined
- WHEN
expectTypeOf(ctx.getService("app")).toEqualTypeOf<OpenAPIHono | undefined>()is asserted in the conformance test - THEN the type check passes
Verified by: src/services/registry-augmentation.spec-conformance.test.ts::"every host slot type-narrows correctly via expectTypeOf" AND src/services/registry-augmentation.test.ts::"side-effect import auto-loads via root barrel"
Requirement: SuaveAppOptions.db is optional
SuaveAppOptions SHALL accept an optional db?: SurrealAdapter field. When supplied, it's seeded into ServiceRegistry["db"]; when omitted, the slot stays undefined. The constructor SHALL store it on this._db for later seeding. No change to existing options.
Scenario: db option seeded when provided
- WHEN
new SuaveApp({ ...config, db: surrealAdapter })is built and a plugin queriesctx.getService("db") - THEN the result is
surrealAdapter(referential)
Verified by: src/app/options.test.ts::"db option seeded when provided" AND src/app/options.test.ts::"omitting db leaves the slot undefined"