Initial Commit
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# `schemas` — Zod schemas at every public boundary
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`schemas.ts` exposes a Zod schema for every public option bundle in the package so callers can validate untrusted input before passing it into a constructor or public method. The `NotificationRouter` constructor calls `NotificationRouterConfigSchema.parse(...)` at the boundary; other classes leave validation as an opt-in for the caller because validating on every send is wasteful — the consumer chooses when to enforce.
|
||||
|
||||
## Features
|
||||
|
||||
- Schemas for every channel config, every public bundle, and every enum: `NotificationChannelSchema`, `NotificationPrioritySchema`, `NotificationStatusSchema`, `RateLimitRuleSchema`, `ChannelConfigSchema`, `RoutingRuleSchema`, `NotificationTemplateSchema`, `TemplateRegistrationSchema`, `NotificationInputSchema`, `NotificationRouterConfigSchema`, `EmailChannelConfigSchema`, `SmsChannelConfigSchema`, `VapidKeysSchema`, `PushChannelConfigSchema`, `WebhookChannelConfigSchema`, `DigestChannelConfigSchema`, `RetryConfigSchema`, `QuietHoursConfigSchema`, `StoredPreferencesSchema`, `PreferenceManagerOptionsSchema`.
|
||||
- Function-valued fields (`sendFn`, `getSubscriptions`, `fetchFn`, `condition`, `formatDigest`, `store`, `clock`) are typed as `z.unknown()` — Zod cannot introspect callables, and the TypeScript surface is the source of truth for shapes.
|
||||
- Boundary defaults are NOT injected — schemas validate shape only, defaults live in the consuming classes.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { NotificationInputSchema } from "@suaveplan/notification-channel";
|
||||
|
||||
const input = NotificationInputSchema.parse(req.body);
|
||||
await router.send(input);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
NotificationRouterConfigSchema,
|
||||
StoredPreferencesSchema,
|
||||
} from "@suaveplan/notification-channel";
|
||||
|
||||
// Validate config loaded from a YAML file:
|
||||
const cfg = NotificationRouterConfigSchema.parse(yaml.parse(text));
|
||||
const router = new NotificationRouter(cfg);
|
||||
|
||||
// Validate stored prefs at the persistence boundary:
|
||||
const safe = StoredPreferencesSchema.parse(row);
|
||||
await store.set(safe.userId, safe);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
Each named export is a `z.ZodType`. Use `.parse(value)` to validate-or-throw, `.safeParse(value)` to validate-or-result. The schemas describe shape only — runtime behaviour, defaults, and engine-pluggable callables remain owned by the consuming classes.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`exactOptionalPropertyTypes: true` in `tsconfig.base.json` makes Zod's `.optional()` behave correctly: an explicit `undefined` value is rejected when the field is absent in the input record, but is accepted on output because the consumer might spread defaults. We use `z.record(z.string(), z.unknown())` for `metadata`-style fields because notification metadata is genuinely free-form. The `RoutingRule.condition` and every `sendFn` / `fetchFn` are `z.unknown()` so YAML-loaded configs (which cannot carry callables) parse cleanly — TypeScript will still complain at the call site if the values are not callable.
|
||||
Reference in New Issue
Block a user