# @suaveplan/api-plugin-example - Reference plugin for the @suaveplan/api-host plugin system. Exercises every contract surface reachable through createTestHost: definePlugin spec, REST routes (public, RBAC-gated, Zod-validated, streaming, idempotent, rate-limited, schema-violation), custom PLUGIN.EXAMPLE.* error codes mapped to HTTP statuses, manifest-declared OTel instruments (counter + histogram + gauge), seeded secrets, tenant-scoped DB, event publish + subscribe handlers. Ships its own integration suite that runs end-to-end against api-host/testing.createTestHost so any host-side change that breaks the plugin contract fails in one place. Reference plugin for the `@suaveplan/api-host` plugin system. The package is a load-bearing integration target, not a tutorial: every contract surface the host exposes through `createTestHost` is wired through one route or one hook here, and the package's own integration test runs that surface end-to-end. When a host-side change breaks the plugin contract, this test fails in one place against the in-memory stack. The plugin doubles as the canonical onboarding doc. When a plugin author asks "how do I use capability X?", the answer is "look at how `api-plugin-example` uses it." ## Documentation tree - [error-codes](./src/error-codes/error-codes.md) — The reference plugin owns a small set of stable error codes that the integration suite asserts are surfaced through the… - [error-codes](./src/error-codes/error-codes.md) — The reference plugin owns a small set of stable error codes that the integration suite asserts are surfaced through the… - [events](./src/events/events.md) — The reference plugin both publishes and subscribes to events. This module exposes the topic names and the Zod payload s… - [events](./src/events/events.md) — The reference plugin both publishes and subscribes to events. This module exposes the topic names and the Zod payload s… - [instruments](./src/instruments/instruments.md) — The reference plugin declares three OTel instruments that mirror the `telemetry.metrics[]` block in `plugin.json`. The… - [instruments](./src/instruments/instruments.md) — The reference plugin declares three OTel instruments that mirror the `telemetry.metrics[]` block in `plugin.json`. The… - [plugin](./src/plugin/plugin.md) — The reference plugin's `definePlugin` invocation lives in its own module so the manifest, the integration test, and any… - [plugin](./src/plugin/plugin.md) — The reference plugin's `definePlugin` invocation lives in its own module so the manifest, the integration test, and any… - [routes](./src/routes/routes.md) — The route table is the heart of the reference plugin. Every route demonstrates one piece of the §6 11-step request pipe… - [routes](./src/routes/routes.md) — The route table is the heart of the reference plugin. Every route demonstrates one piece of the §6 11-step request pipe… - [schemas](./src/schemas/schemas.md) — Every route in the reference plugin is wired against a Zod schema so the §6 input-validation step always has a contract… - [schemas](./src/schemas/schemas.md) — Every route in the reference plugin is wired against a Zod schema so the §6 input-validation step always has a contract… ## Capability map (scope, against `createTestHost`) | Capability | Surface in this package | | --------------------------- | -------------------------------------------------------------------------- | | `definePlugin` lifecycle | `src/plugin/plugin.ts` — install / start / stop / uninstall / health all wired | | REST — public | `GET /example/public/ping`, `GET /example/health` | | REST — RBAC-gated | `GET /example/invoices`, `GET /example/invoices/:id`, `POST /example/invoices` | | REST — Zod-validated body | `POST /example/invoices` against `CreateInvoiceRequest` | | REST — idempotent flag | `POST /example/invoices` (declared in `plugin.json` — host gate) | | REST — rate-limited | `GET /example/rate-limited` (declared in `plugin.json` — host gate) | | REST — streaming | `GET /example/stream/large-report` (chunked NDJSON) | | REST — custom error codes | `PLUGIN.EXAMPLE.{SUBSCRIPTION_INACTIVE, INVOICE_NOT_FOUND, INVALID_INVOICE_STATE}` mapped in `errors.statusMap` | | REST — response validation | `GET /_/test/fail-response?failResponse=1` (intentional schema violation) | | Event publish | `example.invoice.created` on every `POST /example/invoices` happy path | | Event subscribe | `users.user.deleted` via `makeUserDeletedHandler` | | Secrets | `EXAMPLE_STRIPE_KEY`, `EXAMPLE_HMAC_SIGNING_KEY`, `EXAMPLE_WEBHOOK_TOKEN` | | Tenant resolution | Seeded `tenant-int` reached via `X-Tenant-Id` | | OTel instruments | `example.invoices_finalised_total`, `example.stripe.call_duration_ms`, `example.outstanding_invoice_count` | ## Capability map (declared in `plugin.json` for future Stages) These surfaces are present in `plugin.json` because the design doc §13.5 reference plugin SHALL declare every top-level manifest key. The runtime implementations land alongside the host substrates as those substrates ship in subsequent changes. | Capability | Declared in `plugin.json` | Host substrate status | | --------------------------- | ------------------------------------------------------------------ | ------------------------------------ | | GraphQL types + subscription | `graphql.queries` / `mutations` / `subscriptions` | `@suaveplan/api-graphql-yoga` lands later | | WebSocket fan-out (multi-host) | `websockets[]` with topic mapping | `@suaveplan/api-ws-bun` + broker shipped; cross-host federation later | | SSE with `Last-Event-Id` | `sse[]` with `resume: "last-event-id"` | `@suaveplan/api-sse-bun` shipped; resume wiring later | | Saga (3-step) + 2PC | `sagas[]` + `twoPhaseCommit` | `@suaveplan/api-saga` + `api-2pc` orchestrators land later | | Migrations (v1 → v2) | `database.schemaVersion: 2` + `migrationsDir` | `@suaveplan/api-migrations` rollback driver lands later | | Sandboxed (vm + wasm) | Future: rebuild against `api-sandbox-vm` / `api-sandbox-wasm` | Both shipped; isolation wiring through `createHost` lands later | | Per-tenant cron + queued jobs | `jobs.cron[]` (incl. `perTenant`) + `jobs.queued[]` | `@suaveplan/job-scheduler` already lives universal; host bridge later | ## Running ```bash bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-plugin-example ``` The integration suite at `tests/integration/full-flow.integration.test.ts` runs against `createTestHost` with the seeded principal, tenants, and secrets — no external services required. ## Files ```text plugin.json # Manifest declaring every top-level key src/index.ts # Package barrel src/plugin/plugin.ts # Frozen definePlugin record src/routes/routes.ts # REST surface (10 routes) src/schemas/schemas.ts # Zod request / response schemas src/events/events.ts # Topic constants + handler factories src/error-codes/error-codes.ts # PLUGIN.EXAMPLE.* registry + status map src/instruments/instruments.ts # OTel counter + histogram + gauge-as-counter tests/integration/full-flow.integration.test.ts # End-to-end against createTestHost tests/setup/polyfills.ts # In-memory MeterProvider install ``` ## Verification The acceptance gate runs: ```bash bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-plugin-example ``` 100% Istanbul coverage is enforced; the manifest `errors.statusMap` is verified end-to-end against the runtime by the integration suite.