106 lines
8.1 KiB
Markdown
106 lines
8.1 KiB
Markdown
# @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."
|
|
|
|
<!-- begin:auto-docs -->
|
|
## 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…
|
|
<!-- end:auto-docs -->
|
|
|
|
## 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.
|
|
|