Move docs/ package mirror into document-navigator plugin

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>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
# @suaveplan/api-plugin-webhook - Signed outbound webhook delivery plugin for @suaveplan/api. Wraps @suaveplan/webhook-client behind a definePlugin descriptor, registers a typed webhookService in ServiceRegistry, mounts tenant-scoped CRUD + test routes through @suaveplan/api-openapi, drives HMAC-SHA256 delivery with exponential-backoff retry + dead-letter sink, and dedups concurrent deliveries via @suaveplan/idempotency.
Signed outbound webhook delivery plugin for `@suaveplan/api`. Wraps `@suaveplan/webhook-client` behind a `definePlugin` descriptor, registers a typed `webhookService` in the host `ServiceRegistry`, mounts tenant-scoped CRUD + test routes through `@suaveplan/api-openapi`, drives HMAC-SHA256 delivery with exponential-backoff retry and a dead-letter sink, and dedups concurrent deliveries via `@suaveplan/idempotency`.
<!-- begin:auto-docs -->
## Documentation tree
- [dead-letter](./src/dead-letter/dead-letter.md) — Defines the `DeadLetterSink` interface and `DeadLetterEntry` shape used by `WebhookService.send` to persist delivery at…
- [dead-letter](./src/dead-letter/dead-letter.md) — Defines the `DeadLetterSink` interface and `DeadLetterEntry` shape used by `WebhookService.send` to persist delivery at…
- [memory](./src/dead-letter/memory.md) — Provides the default `DeadLetterSink` implementation shipped with the plugin: an unbounded `Array<DeadLetterEntry>` bac…
- [error-codes](./src/error-codes/error-codes.md) — Defines the ten stable `WEBHOOK_*` error codes raised by the api-plugin-webhook package and the concrete `WebhookError`…
- [error-codes](./src/error-codes/error-codes.md) — Defines the ten stable `WEBHOOK_*` error codes raised by the api-plugin-webhook package and the concrete `WebhookError`…
- [otel](./src/otel/instruments.md) — Defines the four OpenTelemetry instruments emitted by the webhook plugin: two counters and two histograms covering ever…
- [instruments](./src/otel/instruments.md) — Defines the four OpenTelemetry instruments emitted by the webhook plugin: two counters and two histograms covering ever…
- [plugin](./src/plugin/plugin.md) — Top-level `definePlugin` consumer for the webhook plugin. Returns a typed `PluginDescriptor<WebhookPluginConfig>` whose…
- [plugin](./src/plugin/plugin.md) — Top-level `definePlugin` consumer for the webhook plugin. Returns a typed `PluginDescriptor<WebhookPluginConfig>` whose…
- [routes](./src/routes/build-routes.md) — Builds the six CRUD + test route entries the webhook plugin mounts on the host's Hono app. Each entry pairs a Zod-typed…
- [build-routes](./src/routes/build-routes.md) — Builds the six CRUD + test route entries the webhook plugin mounts on the host's Hono app. Each entry pairs a Zod-typed…
- [routes](./src/schemas/routes/routes.md) — Bundles the Zod schemas for every CRUD + test route the webhook plugin mounts: list response, create body, create/read…
- [routes](./src/schemas/routes/routes.md) — Bundles the Zod schemas for every CRUD + test route the webhook plugin mounts: list response, create body, create/read…
- [endpoint](./src/schemas/endpoint.md) — Zod schemas for the webhook endpoint shapes — the persistent `WebhookEndpoint` row, the `POST /webhooks` create body, a…
- [options](./src/schemas/options.md) — Zod schema gating `createWebhookPlugin(options)`. Validates every primitive option (route prefix, retry caps, attempt c…
- [registry-augmentation](./src/services/registry-augmentation.md) — Declares the `webhookService` slot on `@suaveplan/api-plugin/service-registry`'s `ServiceRegistry` interface via TypeSc…
- [webhook-service](./src/services/webhook-service.md) — Wraps `@suaveplan/webhook-client`'s `deliver` function with the plugin's orchestration layer: endpoint lookup, tenant s…
- [endpoint-store](./src/store/endpoint-store.md) — Defines the pluggable persistence seam for webhook endpoints. Every method is async-only so downstream packages can plu…
- [memory](./src/store/memory.md) — Provides the default `EndpointStore` shipped with the plugin: a `Map`-backed implementation suitable for tests, single-…
- [types](./src/types/types.md) — Defines the cross-feature data shapes used by every module in `@suaveplan/api-plugin-webhook`: the persistent `WebhookE…
- [types](./src/types/types.md) — Defines the cross-feature data shapes used by every module in `@suaveplan/api-plugin-webhook`: the persistent `WebhookE…
<!-- end:auto-docs -->
## Install
```bash
bun add @suaveplan/api-plugin-webhook
```
## Quick start
```ts
import { defineApp } from "@suaveplan/api";
import { createAuthPlugin } from "@suaveplan/api-plugin-auth";
import { createWebhookPlugin } from "@suaveplan/api-plugin-webhook";
const app = defineApp()
.use(createAuthPlugin({ betterAuth: { /* ... */ } }))
.use(createWebhookPlugin());
// Register and fire from anywhere with access to the ServiceRegistry:
const webhookService = registry.getService("webhookService");
const endpoint = await webhookService.register({
tenantId: null,
url: "https://partner.example.com/hooks",
secret: crypto.randomUUID(),
events: ["order.created"],
});
await webhookService.send(endpoint.id, "order.created", { orderId: 42 });
```
## Surface
Six CRUD + test routes mounted under `routePrefix` (default `/webhooks`), every route gated by `requireAuth()` from `@suaveplan/api-plugin-auth` and scoped by the request principal's `tenantId` when present:
| Method | Path | Purpose |
|---|---|---|
| `GET` | `/webhooks` | List endpoints visible to the principal |
| `POST` | `/webhooks` | Create an endpoint (caller supplies the HMAC secret) |
| `GET` | `/webhooks/:id` | Read one endpoint |
| `PATCH` | `/webhooks/:id` | Partial update |
| `DELETE` | `/webhooks/:id` | Hard delete (idempotent) |
| `POST` | `/webhooks/:id/test` | Fire a synthetic `webhook.ping` |
The same surface is available programmatically via the `webhookService` slot in `ServiceRegistry`:
- `send(endpointId, event, data)`
- `sendAll(event, data, filter?)`
- `register(input)` / `update(id, patch)` / `unregister(id)`
- `listEndpoints(filter?)` / `getEndpoint(id)`
- `test(endpointId)`
## Delivery semantics
- HMAC-SHA256 default (SHA-512 opt-in per endpoint), header naming follows webhook-client (`X-Hub-Signature-256` / `X-Hub-Signature-512`).
- Standard outbound headers: `Content-Type: application/json`, `X-Webhook-Id`, `X-Webhook-Event`, `X-Webhook-Timestamp`.
- Exponential backoff with full jitter, doubling per attempt, capped at `maxRetryDelayMs`.
- `maxAttempts` defaults to 5; 4xx (except 429) terminates immediately; 429 and 5xx retry; network errors and timeouts retry.
- Exhausted deliveries land in the `DeadLetterSink` with the full attempt history before surfacing `WEBHOOK_DELIVERY_EXHAUSTED` to the caller.
- Per-delivery dedup via `(endpointId, payload.id)` — concurrent same-id sends collapse to a single delivery, either through the injected `DistributedIdempotencyGuard` or an in-process `Map`.
## Persistence
The plugin ships `MemoryEndpointStore` (default) and `MemoryDeadLetterSink` (default) so it boots zero-config. Production deployments configure durable siblings against the `EndpointStore` / `DeadLetterSink` interfaces — a SurrealDB-backed store lands as `@suaveplan/api-plugin-webhook-surreal`.
## Modules
- [`src/plugin/plugin.md`](./src/plugin/plugin.md) — `createWebhookPlugin` factory + onInit lifecycle.
- [`src/services/webhook-service.md`](./src/services/webhook-service.md) — `WebhookService` impl.
- [`src/routes/build-routes.md`](./src/routes/build-routes.md) — CRUD + test routes.
- [`src/store/endpoint-store.md`](./src/store/endpoint-store.md) — `EndpointStore` interface.
- [`src/store/memory.md`](./src/store/memory.md) — default in-memory store.
- [`src/dead-letter/dead-letter.md`](./src/dead-letter/dead-letter.md) — `DeadLetterSink` interface.
- [`src/dead-letter/memory.md`](./src/dead-letter/memory.md) — default in-memory sink.
- [`src/schemas/options.md`](./src/schemas/options.md) — options Zod schema.
- [`src/schemas/endpoint.md`](./src/schemas/endpoint.md) — endpoint Zod schemas.
- [`src/schemas/routes/routes.md`](./src/schemas/routes/routes.md) — per-route Zod schemas.
- [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — `WEBHOOK_*` error registry.
- [`src/otel/instruments.md`](./src/otel/instruments.md) — OpenTelemetry instruments.
- [`src/types/types.md`](./src/types/types.md) — shared types.
## OpenTelemetry
- Counter `webhook_plugin.deliveries_total{event,status,endpoint_id}`
- Counter `webhook_plugin.dead_letters_total{event,endpoint_id}`
- Histogram `webhook_plugin.delivery_attempts{event,status}`
- Histogram `webhook_plugin.send_duration_ms{status}`
Status label values: `delivered`, `exhausted`, `not_found`, `inactive`, `invalid`.
## License
MIT