# Add @suaveplan/api — Hono orchestrator + middleware framework (5 of 5) **Package**: `@suaveplan/api` (NEW, server-tier) **Status**: PROPOSED **Type**: new-package (split from upstream `@suaveplan/api` — this is the thin orchestrator slice) - **Spec delta**: ./specs/api/spec.md - **Companions**: `add-api-plugin`, `add-api-tenant`, `add-api-openapi`, `add-api-migrations` - **Position in chain**: depends on all 4 companions (composes them into the runtime) ## Why This is the final piece of the `@suaveplan/api` split. Upstream's single 8.7k-LOC package combined plugin + tenant + openapi + migrations + middleware + config + app orchestration into one. The 4 sibling changes peel off the first four concerns. What remains is the **thin app orchestrator** (`SuaveApp`/`AppBuilder`, ~739 LOC upstream), the **Hono middleware framework** (909 LOC upstream — request-id / cors / logger / error-handler + chain manager), the **app config layer** (595 LOC upstream — Zod-typed multi-source loader), and the **events bus integration** (the `@suaveplan/events`-typed event surface the plugin lifecycle emits to). The genesis enterprise audit additionally fixes the upstream bugs found while reading: `AppBuilder.tenant(resolver)` plumbed but silently dropped (real bug); 3 AsyncLocalStorage instances (app + tenant + implicit globals); 3 global singletons (`globalApp`/`globalEventBus`/`globalSchemaRegistry`); error-handler statusCode closed-enum cast; hardcoded health-route paths; CORS exposing rate-limit headers without a rate-limiter wired; `insertBefore`/`insertAfter` silently fall back to push. ## What changes Net-new package at `0.1.0`. ADDED: - `SuaveApp` class — thin orchestrator that **composes** rather than imports siblings directly. Constructor accepts pre-built `PluginRegistry`, `OpenAPIRegistry`, `MiddlewareChain`, `TenantResolver?`, `MigrationRunner?` (all optional except registry). Removes upstream's deep coupling. - `AppState` literal union (`created`/`initializing`/`ready`/`running`/`stopping`/`stopped`) — the FSM. `state` getter exposes it; transitions are private. - `AppBuilder` fluent API — chainable `.name/.version/.env/.debug/.port/.host/.basePath/.database/.cors/.openapi/.use/.useAll/.middleware/.tenant/.skipConfigFile/.config/.migrations` → `.build()` / `.initialize()` / `.start()`. **Genesis fix:** `.tenant(resolver)` is actually wired into the middleware chain (upstream silently drops it). - `quickstart({name, port?, plugins?, debug?})` — one-call helper for trivial apps. - `AppConfigSchema` (Zod) — server / database / cors / logging / openapi / plugins / migrations sub-configs. Multi-source loader: file (json/ts/js — yaml/toml dropped from upstream's mis-advertised union) → env-specific file → env vars (`SUAVEPLAN_SERVER_PORT` → `server.port`). - `ConfigLoader` class + `loadConfig()` convenience + `defineConfig()` type helper + `defineEnvConfig()` for environment-keyed overrides. - `validateConfig(input): AppConfig` and `tryValidateConfig(input): ValidationResult` (both Zod-backed). - `MiddlewareChain` class — name-addressable Hono middleware list with `MiddlewareOrder` slot constants (`FIRST=-1000` → `LAST=1000`, with `SECURITY=-500`, `CORS=-400`, `REQUEST_ID=-300`, `LOGGING=-200`, `TIMING=-100`, `AUTH=0`, `TENANT=100`, `VALIDATION=200`, `BUSINESS=500`, `RESPONSE=800`, `ERROR=900`). **Genesis fix:** `insertBefore(name, m)` and `insertAfter(name, m)` SHALL raise `ApiError(API_MIDDLEWARE_INSERT_TARGET_NOT_FOUND)` when the named middleware doesn't exist (upstream silently falls back to push). - 5 shipped middleware factories: `createRequestId`, `createCors`, `createLogger`, `createErrorHandler`, `createNotFoundHandler`. Plus helpers `combineMiddleware`, `conditionalMiddleware`, `pathMiddleware`, `excludePathMiddleware`. - **Error handler widens status code**: `createErrorHandler` SHALL pass the SuaveplanError's full `statusCode` through to Hono's response (no closed-enum cast — upstream caps at 400/401/403/404/500). - **CORS conditional headers**: `createCors` SHALL only expose `X-RateLimit-*` headers when the caller passes `withRateLimitHeaders: true`. Default omits them (upstream exposes unconditionally). - **Configurable health routes**: `SuaveApp.setupHealthRoutes(options?: { healthPath?, readyPath?, livePath?, enabled? })`. Defaults match upstream but can be disabled or renamed. - Event-bus integration: SuaveApp emits `app:starting`, `app:started`, `app:stopping`, `app:stopped`, `app:error` to the registry's typed `EventBus` (from `@suaveplan/events`). - **No global singletons.** Upstream `globalApp`/`getGlobalApp`/`setGlobalApp`/`clearGlobalApp` SHALL NOT exist on this package. Same for `globalEventBus` — every consumer constructs its own. - **AsyncLocalStorage consolidated.** ONE optional `RequestContext` ALS instance (`{ app: SuaveApp; requestId: string; tenant?: Tenant; user?: User }`) replaces upstream's 3 separate ALS instances. Opt-in via `app.config.useAsyncLocalStorage: true`. Default is off; consumers thread `c` from Hono. - `ApiError extends SuaveplanError` with codes `API_INVALID_CONFIG`, `API_INVALID_STATE`, `API_MIDDLEWARE_INSERT_TARGET_NOT_FOUND`, `API_SHUTDOWN_TIMEOUT`, `API_SERVER_START_FAILED`. - Subpath exports per feature module. ## Out of scope - **All 4 sibling packages.** Plugin system (`add-api-plugin`), tenant (`add-api-tenant`), OpenAPI (`add-api-openapi`), and migrations (`add-api-migrations`) are separate dependencies — not bundled. - **Database adapter** (the upstream `DatabaseAdapter`/`ConnectionManager`/`QueryBuilder`). Owned by `@suaveplan/db` + dialect-specific adapters. The orchestrator wires a caller-supplied adapter into the plugin context via `registerService("db", ...)`. - **Auth middleware.** The `AUTH=0` order slot is reserved for `@suaveplan/api-plugin-auth` and friends — not shipped here. - **Validation middleware.** Plugin-level Zod validation is the `@hono/zod-openapi` integration via `api-openapi`; cross-cutting input validation belongs in middleware contributed by domain plugins. - **Rate limit middleware.** Tenant rate limit lives in `api-tenant`; global rate limit will be a separate `api-plugin-rate-limit` package. ## Source mapping | Capability | Upstream source | Notes | |---|---|---| | `SuaveApp` class + FSM + `start`/`stop`/`initialize` | `../suaveplan/packages/server/api/src/app/app.ts` L66-L367 | port shape; drop globalApp; consolidate ALS; fix tenant-resolver wiring; widen health-route configurability | | `AppBuilder` fluent API | `../suaveplan/packages/server/api/src/app/app-builder.ts` L14-L217 | port + actually wire `.tenant(resolver)` into the middleware chain | | `quickstart` helper | `../suaveplan/packages/server/api/src/app/app-builder.ts` L229-L260 | verbatim port | | `AppContext` ALS | `../suaveplan/packages/server/api/src/app/app-context.ts` L1-L106 | consolidate into single optional `RequestContext` ALS; drop globalApp | | `AppConfigSchema` + sub-configs | `../suaveplan/packages/server/api/src/config/config.interface.ts` L1-L163 | port; add `migrations` sub-config; drop yaml/toml from `ConfigFileFormat` union | | `ConfigLoader` + multi-source merge + env-var parsing | `../suaveplan/packages/server/api/src/config/config-loader.ts` L1-L236 | port; replace upstream `deepMerge` with `@suaveplan/utils`'s | | `validateConfig` + `tryValidateConfig` + plugin config validator + typed getter | `../suaveplan/packages/server/api/src/config/config-validator.ts` L1-L191 | port shape; switch error from upstream `ConfigValidationError` to `defineCodes`-typed `ApiError(API_INVALID_CONFIG)` | | `MiddlewareChain` + `MiddlewareOrder` constants | `../suaveplan/packages/server/api/src/middleware/middleware-chain.ts` L1-L178 | port + fix `insertBefore`/`insertAfter` silent push fallback to raise typed error | | 5 middleware factories | `../suaveplan/packages/server/api/src/middleware/{cors,logger,error-handler,request-id}.ts` | port; widen error-handler statusCode; conditional `X-RateLimit-*` in CORS; ID generator alternatives kept | | Middleware composition helpers | `../suaveplan/packages/server/api/src/middleware/middleware.interface.ts` L75-L148 | verbatim port | | Event-bus integration | `../suaveplan/packages/server/api/src/events/event-bus.ts` L1-L385 | NOT migrated whole — only the `EventBus` integration surface lives here. The genesis package consumes `@suaveplan/events`'s typed bus directly; the `Hooks` transform-pipeline alter-ego is dropped (use the plugin `HookExecutor` for behaviour contribution). | ## Impact ### Affected dependents None today — depends on all 4 sibling packages. Will be consumed by every `@suaveplan/api-plugin-*` package (40+ in `openspec/changes/`) plus application code. ### Risk - **Medium.** The thinnest of the 5 packages by responsibility but the most architectural impact — its tests cover every other layer's integration with Hono. The integration test suite is where most of the genesis bug fixes are pinned.