UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

596 lines 19.9 kB
import type { StandardSchemaV1 } from "@standard-schema/spec"; import type { ChannelDefinition } from "../broadcasting/index.js"; import type { BroadcastPort, BroadcastPublication } from "../broadcasting/server.js"; import { type JobDef, type JobDispatcher, type JobHook, type JobRetryOptions, type JobTimeoutDuration } from "../jobs/index.js"; import type { SendMailOptions } from "../mail/index.js"; import type { ProviderInstrumentationTarget } from "../providers/index.js"; /** * Any Standard Schema compatible validator. */ export type StandardSchema = StandardSchemaV1<unknown, unknown>; /** * Value or promise of that value. */ export type MaybePromise<T> = T | Promise<T>; /** * Infer the parsed output type from a Standard Schema. */ export type InferSchemaOutput<T extends StandardSchemaV1> = StandardSchemaV1.InferOutput<T>; /** * Minimal notification definition shape accepted by notification ports. */ export interface NotificationPayloadDef<Name extends string = string, Payload extends StandardSchema = StandardSchema> { /** * Stable notification name used by dispatchers, tests, and tooling. */ readonly name: Name; /** * Standard Schema payload validator. */ readonly payload: Payload; /** * Optional human-readable description for docs and tooling. */ readonly description?: string; } /** * Infer the parsed payload type for a notification definition. */ export type InferNotificationPayload<N extends NotificationPayloadDef> = N["payload"] extends StandardSchemaV1<unknown, infer Output> ? Output : never; /** * Result for one notification channel. */ export interface NotificationChannelResult { /** * Channel name, such as `email`, `sms`, `push`, or `inApp`. */ channel: string; /** * Delivery outcome for this channel. */ status: "queued" | "sent" | "skipped" | "failed"; /** * Provider delivery ID when available. */ id?: string; /** * Provider name when available. */ provider?: string; /** * Human-readable skip or failure reason. */ reason?: string; /** * Channel-specific metadata. Dispatchers should keep this safe to log. */ details?: Record<string, unknown>; } /** * Original error captured for one failed notification channel. */ export interface NotificationChannelError { channel: string; error: unknown; } /** * Arguments passed to a notification channel handler. */ export interface NotificationChannelHandleArgs<Payload extends StandardSchema, Ctx> { /** * Notification definition being delivered. */ notification: NotificationDef<string, Payload, Ctx>; /** * Parsed notification payload. */ payload: InferSchemaOutput<Payload>; /** * Handler context. */ ctx: Ctx; /** * Channel name being delivered. */ channel: string; } /** * Handler for one notification channel. */ export type NotificationChannelHandler<Payload extends StandardSchema, Ctx> = (args: NotificationChannelHandleArgs<Payload, Ctx>) => MaybePromise<NotificationChannelResult | undefined>; /** * Notification channel handlers keyed by channel name. */ export type NotificationChannels<Payload extends StandardSchema, Ctx> = Record<string, NotificationChannelHandler<Payload, Ctx>>; /** * Arguments passed to an app-owned notification preference evaluator. */ export interface NotificationPreferenceArgs<Payload extends StandardSchema = StandardSchema, Ctx = unknown> extends NotificationChannelHandleArgs<Payload, Ctx> { /** * Optional metadata supplied by the notification sender. */ metadata?: Record<string, unknown>; } /** * App-owned decision for one notification channel. */ export interface NotificationPreferenceDecision { /** * Whether this channel should deliver. */ deliver: boolean; /** * Optional reason recorded when delivery is skipped. */ reason?: string; } /** * Optional app-facing port for notification channel preferences and opt-outs. */ export interface NotificationPreferencesPort<Ctx = unknown> { /** * Evaluate the current preference immediately before channel delivery. */ evaluate(args: NotificationPreferenceArgs<StandardSchema, Ctx>): MaybePromise<NotificationPreferenceDecision>; } /** * Notification definition created by `defineNotification(...)`. */ export interface NotificationDef<Name extends string = string, Payload extends StandardSchema = StandardSchema, Ctx = unknown> extends NotificationPayloadDef<Name, Payload> { /** * Discriminator for notification definitions. */ readonly kind: "notification"; /** * Channel handlers that deliver the notification. */ readonly channels: NotificationChannels<Payload, Ctx>; } /** * Options for declaring a typed notification. */ export interface DefineNotificationOptions<Payload extends StandardSchema, Ctx> { /** * Standard Schema payload validator. */ payload: Payload; /** * Optional human-readable description for docs and tooling. */ description?: string; /** * Channel handlers that deliver the notification. */ channels: NotificationChannels<Payload, Ctx>; } /** * Options passed when sending a notification. */ export interface SendNotificationOptions { /** * Subset of channels to deliver. Defaults to all channels on the definition. */ channels?: readonly string[]; /** * Optional app metadata attached to memory deliveries and instrumentation. */ metadata?: Record<string, unknown>; /** * Request correlation ID for instrumentation. */ requestId?: string; /** * Trace identifier for instrumentation. */ traceId?: string; /** * Span identifier for instrumentation. */ spanId?: string; /** * Parent span identifier for instrumentation. */ parentSpanId?: string; /** * W3C traceparent header value for instrumentation. */ traceparent?: string; } /** * Result returned after a notification send attempt. */ export interface SendNotificationResult { /** * Notification name. */ notificationName: string; /** * Parsed notification payload. */ payload: unknown; /** * Channels selected for delivery. */ channels: readonly string[]; /** * Per-channel delivery results. */ results: readonly NotificationChannelResult[]; } /** * App-facing notification port. */ export interface NotificationPort { /** * Send a typed notification. */ send<N extends NotificationDef>(notification: N, payload: InferNotificationPayload<N>, options?: SendNotificationOptions): Promise<SendNotificationResult>; } /** * Options for the inline notification dispatcher. */ export interface InlineNotificationDispatcherOptions<Ctx> { /** * Static notification context or factory evaluated for each send. */ ctx?: Ctx | (() => MaybePromise<Ctx>); /** * Called when a channel handler or preference check fails. A returned result * replaces the default failed result. Observer failures are ignored so the * remaining channels still run. */ onError?: (error: unknown, args: NotificationChannelHandleArgs<StandardSchema, Ctx>) => MaybePromise<NotificationChannelResult | undefined>; /** * How completed channel failures are surfaced. Defaults to `"report"`. * `"throw"` still runs every selected channel before rejecting. */ failureMode?: "report" | "throw"; /** * Optional app-owned notification preference evaluator. */ preferences?: NotificationPreferencesPort<Ctx>; /** * Optional devtools/provider instrumentation target. */ instrumentation?: ProviderInstrumentationTarget; } /** * Delivery captured by the memory notification port. */ export interface MemoryNotificationDelivery { /** * Generated delivery ID. */ id: string; /** * Notification name. */ notificationName: string; /** * Parsed payload that would have been sent. */ payload: unknown; /** * Selected channels. */ channels: readonly string[]; /** * Optional app metadata supplied by the caller. */ metadata?: Record<string, unknown>; /** * Timestamp assigned by the memory port. */ sentAt: Date; } /** * In-memory notification port for tests and local examples. */ export interface MemoryNotificationPort extends NotificationPort { /** * Captured notification sends. */ readonly deliveries: readonly MemoryNotificationDelivery[]; /** * Clear captured notification sends. */ clear(): void; } /** * Options for `createMemoryNotificationPort(...)`. */ export interface CreateMemoryNotificationPortOptions { /** * Clock used for captured deliveries. */ now?: () => Date; /** * ID factory used for captured deliveries. */ id?: () => string; /** * Observer called after a delivery is captured. */ onSend?: (delivery: MemoryNotificationDelivery) => MaybePromise<void>; } /** * Context shape required by `defineMailNotificationChannel(...)`. */ export interface MailNotificationContext { ports: { mailer: { send(message: SendMailOptions): MaybePromise<{ id?: string; provider?: string; }>; }; }; } /** * Render a mail message for one notification payload. */ export type MailNotificationRenderer<Payload extends StandardSchema, Ctx extends MailNotificationContext> = (args: NotificationChannelHandleArgs<Payload, Ctx>) => MaybePromise<SendMailOptions | undefined>; /** * Context-bound notification helper factory. */ export interface Notifications<Ctx> { /** * Define a notification with the bound context type. */ defineNotification<Name extends string, Payload extends StandardSchema>(name: Name, options: DefineNotificationOptions<Payload, Ctx>): NotificationDef<Name, Payload, Ctx>; } /** * Notification definitions available to durable delivery workers. */ export interface NotificationRegistry<Ctx = unknown> { /** * Registered definitions in declaration order. */ readonly definitions: readonly NotificationDef<string, StandardSchema, Ctx>[]; /** * Resolve a notification definition by its stable name. */ get(name: string): NotificationDef<string, StandardSchema, Ctx> | undefined; } /** * Payload carried by the first-party notification delivery job. */ export interface NotificationDeliveryJobPayload { notificationName: string; channel: string; payload: unknown; options: Omit<SendNotificationOptions, "channels">; } type NotificationDeliveryPayloadSchema = StandardSchemaV1<unknown, NotificationDeliveryJobPayload>; /** * Job definition used by queued notification dispatchers and workers. */ export interface NotificationDeliveryJob<Name extends string = string, Ctx = unknown> extends JobDef<Name, NotificationDeliveryPayloadSchema, Ctx> { /** * Registry used by both enqueue-time checks and worker delivery. */ readonly registry: NotificationRegistry<Ctx>; } /** * Options for the first-party notification delivery job. */ export interface DefineNotificationDeliveryJobOptions<Name extends string, Ctx> { /** * Stable job name. Defaults to `"notifications.deliver"`. */ name?: Name; /** * Notification definitions available to the worker. */ registry: NotificationRegistry<Ctx>; /** * Optional app-owned preferences evaluated when the job runs. */ preferences?: NotificationPreferencesPort<Ctx>; /** * Retry policy. Defaults to exponential backoff with three attempts. */ retry?: JobRetryOptions; /** * Optional maximum duration for each channel delivery attempt. */ timeout?: JobTimeoutDuration; /** * Optional execution hooks applied to each delivery attempt. */ hooks?: readonly JobHook<JobDef<Name, NotificationDeliveryPayloadSchema, Ctx>, Ctx>[]; } /** * Options for a notification dispatcher backed by Beignet jobs. */ export interface QueuedNotificationDispatcherOptions<Name extends string, Ctx> { /** * Job dispatcher used to enqueue one delivery job per channel. */ jobs: JobDispatcher; /** * Registered notification delivery job. */ deliveryJob: NotificationDeliveryJob<Name, Ctx>; /** * Optional devtools/provider instrumentation target. */ instrumentation?: ProviderInstrumentationTarget; } /** * Error thrown when notification payload validation fails. */ export declare class NotificationValidationError extends Error { /** * Raw Standard Schema validation issues. */ readonly issues: readonly StandardSchemaV1.Issue[]; constructor(args: { name: string; issues: readonly StandardSchemaV1.Issue[]; }); } /** * Error thrown when notification delivery fails. */ export declare class NotificationDeliveryError extends Error { /** * Notification name. */ readonly notificationName: string; /** * First channel that failed, retained for concise error handling. */ readonly channel: string; /** * Original error for the first failed channel when available. */ readonly cause: unknown; /** * Complete notification result after every selected channel ran. */ readonly result: SendNotificationResult; /** * Failed channel results. */ readonly failures: readonly NotificationChannelResult[]; /** * Original channel errors in delivery order. */ readonly errors: readonly NotificationChannelError[]; constructor(args: { result: SendNotificationResult; errors?: readonly NotificationChannelError[]; }); } /** * Error thrown when a notification registry cannot safely resolve a delivery. */ export declare class NotificationRegistryError extends Error { constructor(message: string); } /** * Define the notification catalog available to durable delivery workers. * Duplicate names throw because queued delivery resolves definitions by name. */ export declare function defineNotificationRegistry<Ctx>(definitions: readonly NotificationDef<string, StandardSchema, Ctx>[]): NotificationRegistry<Ctx>; /** * Define the generic job that resolves and delivers one notification channel. * Register the returned job with every worker or outbox registry that can * receive queued notifications. */ export declare function defineNotificationDeliveryJob<Ctx, Name extends string = "notifications.deliver">(options: DefineNotificationDeliveryJobOptions<Name, Ctx>): NotificationDeliveryJob<Name, Ctx>; /** * Validate and parse a notification payload with the notification's Standard * Schema. */ export declare function parseNotificationPayload<N extends NotificationPayloadDef>(notification: N, payload: unknown): Promise<InferNotificationPayload<N>>; /** * Create an inline notification dispatcher. * * The dispatcher validates payloads and runs selected channel handlers * immediately. Channel failures are isolated and reported after every selected * channel runs. Use this directly in tests and local apps, or use * `createQueuedNotificationDispatcher(...)` for background execution. */ export declare function createInlineNotificationDispatcher<Ctx>(options?: InlineNotificationDispatcherOptions<Ctx>): NotificationPort; /** * Options for the inline notifications provider. */ export interface InlineNotificationsProviderOptions extends Omit<InlineNotificationDispatcherOptions<unknown>, "ctx" | "instrumentation"> { /** * Provider name. Defaults to "inline-notifications". */ name?: string; } /** * Ports contributed by the inline notifications provider. */ export interface InlineNotificationsProviderPorts { /** * Beignet notification port. */ notifications: NotificationPort; } /** * Create a provider that contributes an inline notification dispatcher. * * Use it as the dev-default `notifications` port in `server/providers.ts`. * Channel handlers run with an app service context built lazily through the * server context blueprint on each send, so the provider is safe to register * before all providers have started. Sends are recorded as devtools events * through the `notifications` watcher when an instrumentation port is * installed. */ export declare function createInlineNotificationsProvider(options?: InlineNotificationsProviderOptions): import("../providers/provider.js").ServiceProvider<unknown, StandardSchemaV1<void, void>, { notifications: NotificationPort; }, unknown, void>; /** * Create a notification dispatcher that enqueues one delivery job per channel. * Separate jobs keep provider retries from resending channels that already * completed successfully. */ export declare function createQueuedNotificationDispatcher<Name extends string, Ctx>(options: QueuedNotificationDispatcherOptions<Name, Ctx>): NotificationPort; /** * Options for the queued notifications provider. */ export interface QueuedNotificationsProviderOptions<Name extends string, Ctx> { /** * Registered notification delivery job. */ deliveryJob: NotificationDeliveryJob<Name, Ctx>; /** * Provider name. Defaults to `"queued-notifications"`. */ name?: string; } /** * Create a provider that contributes a job-backed notification dispatcher. */ export declare function createQueuedNotificationsProvider<Name extends string, Ctx>(options: QueuedNotificationsProviderOptions<Name, Ctx>): import("../providers/provider.js").ServiceProvider<{ jobs: JobDispatcher; }, StandardSchemaV1<void, void>, { notifications: NotificationPort; }, unknown, void>; /** * Define a mail-backed notification channel. * * Return `undefined` from the renderer when the channel should be skipped, for * example when a recipient does not have an email address. */ export declare function defineMailNotificationChannel<Payload extends StandardSchema, Ctx extends MailNotificationContext>(render: MailNotificationRenderer<Payload, Ctx>): NotificationChannelHandler<Payload, Ctx>; /** Deliver a typed browser hint through the existing notification delivery pipeline. * `sent` means provider acceptance, including when the recipient is offline. * Return undefined to skip. Persistent inbox writes and their outbox ordering belong to the app. */ export declare function defineBroadcastNotificationChannel<C extends ChannelDefinition, Payload extends StandardSchema, Ctx extends { ports: { broadcast: BroadcastPort; }; }>(options: { channel: C; render: (args: NotificationChannelHandleArgs<Payload, Ctx>) => MaybePromise<BroadcastPublication<NoInfer<C>> | undefined>; }): NotificationChannelHandler<Payload, Ctx>; /** * Create an in-memory notification port for tests and examples. * * The memory port validates payloads and records notification intent without * running channel handlers. */ export declare function createMemoryNotificationPort(options?: CreateMemoryNotificationPortOptions): MemoryNotificationPort; /** * Create notification helper methods bound to an application context type. * * Call it once in `lib/notifications.ts`: * * ```ts * export const { defineNotification } = createNotifications<AppContext>(); * ``` * * Notifications represent user-facing communication intent. Channel handlers * decide how that intent becomes mail, SMS, push, in-app delivery, or another * app-owned channel. */ export declare function createNotifications<Ctx>(): Notifications<Ctx>; export {}; //# sourceMappingURL=index.d.ts.map