# @suaveplan/api - Thin Hono-based orchestrator for the Suaveplan API stack — composes pre-built PluginRegistry, OpenAPIRegistry, MiddlewareChain, TenantResolver, and MigrationRunner into a SuaveApp runtime. Monotonic AppState FSM, configurable health routes, optional single-AsyncLocalStorage RequestContext, Zod-validated multi-source config loader (file → env-file → env-vars), name-addressable middleware chain with insert-target enforcement, full-status-passthrough error handler, conditional CORS rate-limit headers, fluent AppBuilder with real tenant() wiring. Server-tier (Node + Bun). The thin Hono-based application orchestrator for the Suaveplan API stack. Composes the four sibling packages — `@suaveplan/api-plugin`, `@suaveplan/api-tenant`, `@suaveplan/api-openapi`, and `@suaveplan/api-migrations` — into a runnable HTTP service with a monotonic state FSM, a name-addressable middleware chain, a Zod- validated multi-source config loader, configurable health routes, an optional single-`AsyncLocalStorage` `RequestContext`, and a typed lifecycle event bus. ## Documentation tree - [app](./src/app/app.md) — The `app` module owns the `SuaveApp` class — the Hono-based orchestrator that composes the four sibling packages into a… - [app](./src/app/app.md) — The `app` module owns the `SuaveApp` class — the Hono-based orchestrator that composes the four sibling packages into a… - [health-routes](./src/app/health-routes.md) — The `health-routes` module wires the configurable health-probe routes (`/health`, `/ready`, optional `/live`) onto a `O… - [migrations](./src/app/migrations.md) — The `migrations` module is the tiny helper that connects the `@suaveplan/api-migrations` `MigrationRunner` to the `Suav… - [request-context](./src/app/request-context.md) — The `request-context` module owns the single `AsyncLocalStorage` instance that the `SuaveApp` orchestrator uses to prop… - [timeout](./src/app/timeout.md) — The `timeout` module ships a single internal-facing helper used by `SuaveApp.stop` to enforce the shutdown budget. It r… - [builder](./src/builder/builder.md) — The `builder` module ships the fluent `AppBuilder` class, the `defineApp()` factory, and the `quickstart()` one-call he… - [assemble](./src/builder/assemble.md) — The `assemble` module owns the pure helpers that the fluent `AppBuilder` delegates to during `.build()`: configuration… - [builder](./src/builder/builder.md) — The `builder` module ships the fluent `AppBuilder` class, the `defineApp()` factory, and the `quickstart()` one-call he… - [cors-bridge](./src/builder/cors-bridge.md) — The `cors-bridge` module owns the two helpers `AppBuilder` uses to translate between `CorsOptions` (the runtime middlew… - [config](./src/config/config.md) — The `config` module defines every Zod schema that the `@suaveplan/api` orchestrator validates against. It is the single… - [config](./src/config/config.md) — The `config` module defines every Zod schema that the `@suaveplan/api` orchestrator validates against. It is the single… - [loader](./src/config/loader.md) — The `loader` module assembles an `AppConfigInput` from four sources — schema defaults, a base config file, an environme… - [validator](./src/config/validator.md) — The `validator` module wraps `AppConfigSchema.safeParse` with the package's typed error path. Every validation failure… - [error-codes](./src/error-codes/error-codes.md) — The `error-codes` module owns the stable string registry of `API.*` failure codes raised by the `@suaveplan/api` orches… - [error-codes](./src/error-codes/error-codes.md) — The `error-codes` module owns the stable string registry of `API.*` failure codes raised by the `@suaveplan/api` orches… - [event-integration](./src/event-integration/event-bridge.md) — The `event-integration` module exposes the typed lifecycle event surface that the `SuaveApp` orchestrator publishes to.… - [event-bridge](./src/event-integration/event-bridge.md) — The `event-integration` module exposes the typed lifecycle event surface that the `SuaveApp` orchestrator publishes to.… - [chain](./src/middleware/chain.md) — The `chain` module owns the order-aware, name-addressable list of Hono middlewares that the `SuaveApp` orchestrator han… - [composition](./src/middleware/composition.md) — The `composition` module ships four pure higher-order functions that compose Hono middlewares into one another. They ar… - [cors](./src/middleware/cors.md) — The `cors` module wraps `hono/cors` with the orchestrator's CORS configuration shape, registers the resulting handler u… - [error-handler](./src/middleware/error-handler.md) — The `error-handler` module ships the catch-all middleware that converts every throwable raised by downstream handlers i… - [logger](./src/middleware/logger.md) — The `logger` module ships a Hono middleware that emits one structured log line per request, plus three drop-in formatte… - [request-id](./src/middleware/request-id.md) — The `request-id` module assigns every inbound request a stable identifier that survives across middleware, route handle… - [services](./src/services/registry-augmentation.md) — The `registry-augmentation` module declares the canonical host services that `@suaveplan/api` seeds into the `PluginReg… - [registry-augmentation](./src/services/registry-augmentation.md) — The `registry-augmentation` module declares the canonical host services that `@suaveplan/api` seeds into the `PluginReg… - [types](./src/types/api.md) — The `types` module is the public type contract for the `@suaveplan/api` orchestrator. Every other module in the package… - [api](./src/types/api.md) — The `types` module is the public type contract for the `@suaveplan/api` orchestrator. Every other module in the package… ## Install ```bash bun add @suaveplan/api ``` ## Quick start ```ts import { defineApp } from "@suaveplan/api"; import { definePlugin } from "@suaveplan/api-plugin"; const billing = definePlugin({ name: "billing", version: "1.0.0" }); const app = await defineApp() .name("billing-api") .version("1.0.0") .port(8080) .skipConfigFile() .openapi({ title: "Billing", version: "1.0.0" }) .use(billing) .start(); ``` ## AppBuilder fluent example ```ts import { defineApp } from "@suaveplan/api"; import { headerResolver } from "@suaveplan/api-tenant"; import { createMigrationRunner, createSqliteAdapter } from "@suaveplan/api-migrations"; const runner = createMigrationRunner(createSqliteAdapter(sqliteClient)); const app = await defineApp() .name("orders") .version("2.0.0") .env("production") .cors({ origins: ["https://app.example.com"], withRateLimitHeaders: true }) .openapi({ title: "Orders", version: "2.0.0" }) .useAsyncLocalStorage() .tenant(headerResolver({ headerName: "x-tenant-id", lookupFn })) .migrations({ runner, dirPath: "./migrations" }) .use(plugin) .start(); ``` ## Config file format `suaveplan.config.{ts,js,json}` files are read from the working directory (override via `.configFile({ configDir })`). Layered with environment-specific overrides (`suaveplan.config.production.json`) and environment variables (`SUAVEPLAN_SERVER_PORT=4000`). YAML and TOML are NOT supported (the upstream package advertised them but threw at runtime — drops the union entries). ## ALS opt-in ```ts import { getCurrentRequestContext } from "@suaveplan/api"; const app = await defineApp() .name("trace") .openapi({ title: "Trace", version: "0.1.0" }) .skipConfigFile() .useAsyncLocalStorage() .start(); // Deep in a request handler: const ctx = getCurrentRequestContext(); // ctx.requestId, ctx.tenant?, ctx.user?, ctx.app ``` ## Modules - [`./src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — `API_CODES` + `ApiError`. - [`./src/types/api.md`](./src/types/api.md) — public type contract. - [`./src/config/config.md`](./src/config/config.md) — Zod schemas. - [`./src/config/loader.md`](./src/config/loader.md) — multi-source loader. - [`./src/config/validator.md`](./src/config/validator.md) — typed validation. - [`./src/middleware/chain.md`](./src/middleware/chain.md) — name-addressable chain. - [`./src/middleware/cors.md`](./src/middleware/cors.md) — `createCors` (conditional rate-limit headers). - [`./src/middleware/error-handler.md`](./src/middleware/error-handler.md) — global error handler (full status passthrough). - [`./src/middleware/logger.md`](./src/middleware/logger.md) — request logger + 3 formatters. - [`./src/middleware/request-id.md`](./src/middleware/request-id.md) — request id + 3 generators. - [`./src/middleware/composition.md`](./src/middleware/composition.md) — `combineMiddleware` & friends. - [`./src/event-integration/event-bridge.md`](./src/event-integration/event-bridge.md) — typed lifecycle bus. - [`./src/app/app.md`](./src/app/app.md) — `SuaveApp` orchestrator. - [`./src/app/health-routes.md`](./src/app/health-routes.md) — configurable health routes. - [`./src/app/request-context.md`](./src/app/request-context.md) — single ALS. - [`./src/app/migrations.md`](./src/app/migrations.md) — migration-runner integration helper. - [`./src/app/timeout.md`](./src/app/timeout.md) — `runWithTimeout` helper. - [`./src/builder/builder.md`](./src/builder/builder.md) — fluent `AppBuilder`. - [`./src/builder/assemble.md`](./src/builder/assemble.md) — pure assembly helpers. - [`./src/builder/cors-bridge.md`](./src/builder/cors-bridge.md) — config ↔ middleware bridge. ## Migration-runner integration When `MigrationsOptions` is supplied and `config.migrations.runBeforePluginInit` is `true` (the default), the runner executes before `PluginRegistry.initialize()` so plugin `onInit` hooks see a migrated database. Pass an explicit `files` array for deterministic in-memory tests, or `dirPath` to scan a real directory. ## Error codes | Code | When raised | | --- | --- | | `API.INVALID_CONFIG` | `validateConfig` / `validatePluginConfig` | | `API.INVALID_STATE` | `SuaveApp.initialize`/`start` from wrong state | | `API.MIDDLEWARE_INSERT_TARGET_NOT_FOUND` | `chain.insertBefore`/`insertAfter` | | `API.SHUTDOWN_TIMEOUT` | `SuaveApp.stop(timeoutMs)` exceeds budget | | `API.SERVER_START_FAILED` | `serverFactory` throws during `start` | ## Design notes vs upstream - `SuaveApp` is dependency-injected — no internal `new`. - `AppBuilder.tenant(resolver)` actually wires `createTenantMiddleware` into the chain (upstream silently dropped). - One opt-in `RequestContext` `AsyncLocalStorage` instead of three. - No global singletons (`globalApp`, `getGlobalApp`, etc.). - CORS only exposes `X-RateLimit-*` when `withRateLimitHeaders: true`. - `MiddlewareChain.insertBefore`/`insertAfter` raise on missing target. - Error handler passes the full `SuaveplanError` `statusCode` through (no closed-enum cast). - Configurable health routes. - YAML / TOML config formats removed (upstream advertised, never worked).