first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# Suppression route schemas
## Purpose
The suppression-route schemas describe the OpenAPI contract for the
`/suppression` route tree. The package's local
`CampaignSuppressionStore` interface is multi-channel — keyed on the
`(tenantId, channel, channelValue)` triple — so the schemas surface
`channel` (free-string with `"email"` default) and `channelValue`
rather than the email-only `channelType` shape used by the upstream
port. This keeps the same routes reusable for SMS, push, and future
delivery channels without a v2 endpoint family.
The schemas back four endpoints: list (paginated), create (single
entry, defends against duplicates at the store), delete (by `id`), and
check (point lookup by `channel + channelValue`). The check endpoint
is read-only and idempotent, and is intended to be hit on every
outgoing-message decision in a sender pipeline.
## Features
- `SuppressionReasonSchema` — 5-member enum mirroring
`CampaignSuppressionReason`.
- `CreateSuppressionSchema` — body validator with `channel: "email"`
default and optional `source` / `metadata`.
- `SuppressionQuerySchema` — list filters with coerced pagination.
- `SuppressionCheckQuerySchema` — required `channel` + `channelValue`.
- `SuppressionIdParamSchema` — `:id` path param.
- 3 response schemas: `SuppressionResponseSchema`,
`SuppressionListResponseSchema`, `SuppressionCheckResponseSchema`.
## Usage: Basic
```ts
import { CreateSuppressionSchema } from "@suaveplan/api-plugin-campaigns";
const parsed = CreateSuppressionSchema.parse(await c.req.json());
await store.add({ tenantId, ...parsed });
```
## Usage: Advanced
```ts
import {
SuppressionCheckQuerySchema,
SuppressionCheckResponseSchema,
} from "@suaveplan/api-plugin-campaigns";
const q = SuppressionCheckQuerySchema.parse({
channel: c.req.query("channel"),
channelValue: c.req.query("channelValue"),
});
const entry = await store.check(tenantId, q.channel, q.channelValue);
return c.json(
SuppressionCheckResponseSchema.parse({
suppressed: entry !== null,
entry,
}),
200
);
```
## API Reference
- `CreateSuppressionSchema`, `SuppressionQuerySchema`,
`SuppressionCheckQuerySchema`, `SuppressionIdParamSchema` — input
validators.
- `SuppressionResponseSchema`, `SuppressionListResponseSchema`,
`SuppressionCheckResponseSchema` — response shapes.
- `type CreateSuppressionBody`, `SuppressionQueryInput` — inferred TS
types.
## Implementation Notes
The `channel` field defaults to `"email"` rather than enumerating
known channels because the suppression store is intentionally
channel-agnostic (a tenant may opt into SMS or push channels without
schema changes here). The trade-off: the OpenAPI consumer sees a
free-string rather than a closed enum. The enforcement boundary moves
to the recipient delivery pipeline, which always knows its channel
identifier before consulting suppression.
`metadata` is `z.record(z.string(), z.unknown())` so adapters can
attach provider-specific context (bounce subcodes, complaint headers,
…) without a schema migration. The schema is **strict-on-input** only
for fields it owns; per-channel extensions live inside `metadata` and
are validated at the integration boundary, not here.