# `schemas` — Zod schemas at every public boundary _(no submodules)_ ## 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.