Files
skills/docs/api-stack/api/api
2026-05-29 08:20:56 +01:00
..
2026-05-29 08:20:56 +01:00
2026-05-29 08:20:56 +01:00

@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 app module owns the SuaveApp class — the Hono-based orchestrator that composes the four sibling packages into a…
    • app — The app module owns the SuaveApp class — the Hono-based orchestrator that composes the four sibling packages into a…
    • health-routes — The health-routes module wires the configurable health-probe routes (/health, /ready, optional /live) onto a `O…
    • migrations — The migrations module is the tiny helper that connects the @suaveplan/api-migrations MigrationRunner to the `Suav…
    • request-context — The request-context module owns the single AsyncLocalStorage instance that the SuaveApp orchestrator uses to prop…
    • timeout — The timeout module ships a single internal-facing helper used by SuaveApp.stop to enforce the shutdown budget. It r…
  • builder — The builder module ships the fluent AppBuilder class, the defineApp() factory, and the quickstart() one-call he…
    • assemble — The assemble module owns the pure helpers that the fluent AppBuilder delegates to during .build(): configuration…
    • builder — The builder module ships the fluent AppBuilder class, the defineApp() factory, and the quickstart() one-call he…
    • cors-bridge — The cors-bridge module owns the two helpers AppBuilder uses to translate between CorsOptions (the runtime middlew…
  • config — The config module defines every Zod schema that the @suaveplan/api orchestrator validates against. It is the single…
    • config — The config module defines every Zod schema that the @suaveplan/api orchestrator validates against. It is the single…
    • loader — The loader module assembles an AppConfigInput from four sources — schema defaults, a base config file, an environme…
    • validator — The validator module wraps AppConfigSchema.safeParse with the package's typed error path. Every validation failure…
  • error-codes — The error-codes module owns the stable string registry of API.* failure codes raised by the @suaveplan/api orches…
    • error-codes — The error-codes module owns the stable string registry of API.* failure codes raised by the @suaveplan/api orches…
  • event-integration — The event-integration module exposes the typed lifecycle event surface that the SuaveApp orchestrator publishes to.…
    • event-bridge — The event-integration module exposes the typed lifecycle event surface that the SuaveApp orchestrator publishes to.…
  • chain — The chain module owns the order-aware, name-addressable list of Hono middlewares that the SuaveApp orchestrator han…
  • composition — The composition module ships four pure higher-order functions that compose Hono middlewares into one another. They ar…
  • cors — The cors module wraps hono/cors with the orchestrator's CORS configuration shape, registers the resulting handler u…
  • error-handler — The error-handler module ships the catch-all middleware that converts every throwable raised by downstream handlers i…
  • logger — The logger module ships a Hono middleware that emits one structured log line per request, plus three drop-in formatte…
  • request-id — The request-id module assigns every inbound request a stable identifier that survives across middleware, route handle…
  • services — The registry-augmentation module declares the canonical host services that @suaveplan/api seeds into the `PluginReg…
    • registry-augmentation — The registry-augmentation module declares the canonical host services that @suaveplan/api seeds into the `PluginReg…
  • types — The types module is the public type contract for the @suaveplan/api orchestrator. Every other module in the package…
    • api — The types module is the public type contract for the @suaveplan/api orchestrator. Every other module in the package…

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

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).