Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-plugin (extend-api-foundation-host-services)
**Package**: `@suaveplan/api-plugin`
**Status**: PROPOSED
## ADDED Requirements
### Requirement: PluginRegistry.seedServices bulk-registers entries before plugin init
The package SHALL add `PluginRegistry.seedServices<TMap extends Partial<{ [K in keyof ServiceRegistry]: ServiceRegistry[K] }>>(map: TMap): void`. Calling it stores every key/value pair in an internal seeded-services map. When `initialize()` later constructs per-plugin contexts, every seeded entry SHALL appear in `ctx.getService(name)` BEFORE any plugin's `onRegister` runs. Calling `seedServices` after `initialize()` has started SHALL raise `PluginError(PLUGIN_INVALID_STATE)`.
#### Scenario: seeded service is resolvable from the first plugin's onRegister
- **WHEN** `registry.seedServices({ app: stubApp })` is called, then a plugin with `onRegister(ctx) { ctx.getService("app") }` is registered, then `registry.initialize()` is awaited
- **THEN** the plugin's `onRegister` resolves `stubApp` (no `undefined`)
#### Scenario: later seed overrides earlier seed
- **WHEN** `registry.seedServices({ logger: l1 })` is followed by `registry.seedServices({ logger: l2 })` before `initialize()`
- **THEN** the next plugin's `ctx.getService("logger")` returns `l2`
#### Scenario: seedServices after initialize raises PLUGIN_INVALID_STATE
- **WHEN** `registry.initialize()` is awaited and then `registry.seedServices({ app: stubApp })` is called
- **THEN** `PluginError(PLUGIN_INVALID_STATE)` is thrown
**Verified by:** `src/registry/seed-services.test.ts::"seeded service resolvable from first plugin's onRegister"` AND `src/registry/seed-services.test.ts::"later seed overrides earlier"` AND `src/registry/seed-services.test.ts::"seedServices after initialize raises PLUGIN_INVALID_STATE"`
---
### Requirement: PluginRegistry exposes PLUGIN_INVALID_STATE in the code registry
`PLUGIN_CODES` SHALL include `PLUGIN_INVALID_STATE` (taking the registry to 11 codes total). The code SHALL be raised by `seedServices` post-`initialize()`; existing 10 codes are preserved.
#### Scenario: existing codes unchanged
- **WHEN** the spec-conformance test enumerates `PLUGIN_CODES`
- **THEN** the set is the union of the existing 10 codes plus `PLUGIN_INVALID_STATE` (no removals)
**Verified by:** `src/api-plugin.spec-conformance.test.ts::"PLUGIN_CODES contains PLUGIN_INVALID_STATE alongside the existing 10"`
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,68 @@
<!-- markdownlint-disable MD024 -->
# 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, and `app.initialize()` is awaited
- **THEN** `recordApp` was called with the OpenAPIHono instance the app holds at `this._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 asks `ctx.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:
```ts
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 queries `ctx.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"`
<!-- markdownlint-enable MD024 -->