Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@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 — The
appmodule owns theSuaveAppclass — the Hono-based orchestrator that composes the four sibling packages into a…- app — The
appmodule owns theSuaveAppclass — the Hono-based orchestrator that composes the four sibling packages into a… - health-routes — The
health-routesmodule wires the configurable health-probe routes (/health,/ready, optional/live) onto a `O… - migrations — The
migrationsmodule is the tiny helper that connects the@suaveplan/api-migrationsMigrationRunnerto the `Suav… - request-context — The
request-contextmodule owns the singleAsyncLocalStorageinstance that theSuaveApporchestrator uses to prop… - timeout — The
timeoutmodule ships a single internal-facing helper used bySuaveApp.stopto enforce the shutdown budget. It r…
- app — The
- builder — The
buildermodule ships the fluentAppBuilderclass, thedefineApp()factory, and thequickstart()one-call he…- assemble — The
assemblemodule owns the pure helpers that the fluentAppBuilderdelegates to during.build(): configuration… - builder — The
buildermodule ships the fluentAppBuilderclass, thedefineApp()factory, and thequickstart()one-call he… - cors-bridge — The
cors-bridgemodule owns the two helpersAppBuilderuses to translate betweenCorsOptions(the runtime middlew…
- assemble — The
- config — The
configmodule defines every Zod schema that the@suaveplan/apiorchestrator validates against. It is the single…- config — The
configmodule defines every Zod schema that the@suaveplan/apiorchestrator validates against. It is the single… - loader — The
loadermodule assembles anAppConfigInputfrom four sources — schema defaults, a base config file, an environme… - validator — The
validatormodule wrapsAppConfigSchema.safeParsewith the package's typed error path. Every validation failure…
- config — The
- error-codes — The
error-codesmodule owns the stable string registry ofAPI.*failure codes raised by the@suaveplan/apiorches…- error-codes — The
error-codesmodule owns the stable string registry ofAPI.*failure codes raised by the@suaveplan/apiorches…
- error-codes — The
- event-integration — The
event-integrationmodule exposes the typed lifecycle event surface that theSuaveApporchestrator publishes to.…- event-bridge — The
event-integrationmodule exposes the typed lifecycle event surface that theSuaveApporchestrator publishes to.…
- event-bridge — The
- chain — The
chainmodule owns the order-aware, name-addressable list of Hono middlewares that theSuaveApporchestrator han… - composition — The
compositionmodule ships four pure higher-order functions that compose Hono middlewares into one another. They ar… - cors — The
corsmodule wrapshono/corswith the orchestrator's CORS configuration shape, registers the resulting handler u… - error-handler — The
error-handlermodule ships the catch-all middleware that converts every throwable raised by downstream handlers i… - logger — The
loggermodule ships a Hono middleware that emits one structured log line per request, plus three drop-in formatte… - request-id — The
request-idmodule assigns every inbound request a stable identifier that survives across middleware, route handle… - services — The
registry-augmentationmodule declares the canonical host services that@suaveplan/apiseeds into the `PluginReg…- registry-augmentation — The
registry-augmentationmodule declares the canonical host services that@suaveplan/apiseeds into the `PluginReg…
- registry-augmentation — The
- types — The
typesmodule is the public type contract for the@suaveplan/apiorchestrator. Every other module in the package…- api — The
typesmodule is the public type contract for the@suaveplan/apiorchestrator. Every other module in the package…
- api — The
Install
bun add @suaveplan/api
Quick start
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
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
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—API_CODES+ApiError../src/types/api.md— public type contract../src/config/config.md— Zod schemas../src/config/loader.md— multi-source loader../src/config/validator.md— typed validation../src/middleware/chain.md— name-addressable chain../src/middleware/cors.md—createCors(conditional rate-limit headers)../src/middleware/error-handler.md— global error handler (full status passthrough)../src/middleware/logger.md— request logger + 3 formatters../src/middleware/request-id.md— request id + 3 generators../src/middleware/composition.md—combineMiddleware& friends../src/event-integration/event-bridge.md— typed lifecycle bus../src/app/app.md—SuaveApporchestrator../src/app/health-routes.md— configurable health routes../src/app/request-context.md— single ALS../src/app/migrations.md— migration-runner integration helper../src/app/timeout.md—runWithTimeouthelper../src/builder/builder.md— fluentAppBuilder../src/builder/assemble.md— pure assembly helpers../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
SuaveAppis dependency-injected — no internalnew.AppBuilder.tenant(resolver)actually wirescreateTenantMiddlewareinto the chain (upstream silently dropped).- One opt-in
RequestContextAsyncLocalStorageinstead of three. - No global singletons (
globalApp,getGlobalApp, etc.). - CORS only exposes
X-RateLimit-*whenwithRateLimitHeaders: true. MiddlewareChain.insertBefore/insertAfterraise on missing target.- Error handler passes the full
SuaveplanErrorstatusCodethrough (no closed-enum cast). - Configurable health routes.
- YAML / TOML config formats removed (upstream advertised, never worked).