@objectstack/service-messaging 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/messaging-service-plugin.ts","../src/recipient-resolver.ts","../src/preference-resolver.ts","../src/messaging-service.ts","../src/inbox-channel.ts","../src/sql-outbox.ts","../src/backoff.ts","../src/dispatcher.ts","../src/retention.ts","../src/template-renderer.ts","../src/email-channel.ts","../src/objects/inbox-message.object.ts","../src/objects/notification-receipt.object.ts","../src/objects/notification-delivery.object.ts","../src/objects/notification-preference.object.ts","../src/objects/notification-subscription.object.ts","../src/objects/notification-template.object.ts","../src/memory-outbox.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/service-messaging\n *\n * Outbound notification dispatch (ADR-0012, M1 minimal slice). Ships the\n * `MessagingChannel` seam, the `MessagingService` registry + `emit()` fan-out,\n * and the always-on `inbox` channel. The baseline `notify` flow node\n * (service-automation) dispatches through `kernel.getService('messaging')`.\n *\n * Deferred to follow-up milestones: durable outbox / retry / cluster-lock /\n * dead-letter, the topic catalog + per-user preference matrix, renderers, and\n * the email / webhook / push / IM channels. The seam is shaped so those land\n * without breaking callers.\n */\n\n// Plugin\nexport { MessagingServicePlugin } from './messaging-service-plugin.js';\nexport type { MessagingServicePluginOptions } from './messaging-service-plugin.js';\n\n// Service + types\nexport { MessagingService, NOTIFICATION_EVENT_OBJECT } from './messaging-service.js';\nexport type {\n DeliveryOutcome,\n EmitResult,\n EmitInput,\n Audience,\n AudienceSpec,\n MessagingServiceContext,\n} from './messaging-service.js';\n\n// Recipient resolution (ADR-0030 P1)\nexport {\n RecipientResolver,\n USER_OBJECT,\n MEMBER_OBJECT,\n TEAM_MEMBER_OBJECT,\n} from './recipient-resolver.js';\nexport type {\n RecipientResolverOptions,\n RecipientResolverLogger,\n ResolveContext,\n} from './recipient-resolver.js';\n\n// Preference filter (ADR-0030 P2) + quiet-hours deferral (P3b)\nexport { PreferenceResolver, PREFERENCE_OBJECT, quietHoursDeferral } from './preference-resolver.js';\nexport type {\n PreferenceResolverOptions,\n PreferenceResolverLogger,\n PreferenceContext,\n PreferenceTarget,\n QuietHours,\n} from './preference-resolver.js';\n\n// Channel seam\nexport { createInboxChannel, INBOX_OBJECT, RECEIPT_OBJECT } from './inbox-channel.js';\nexport type { InboxChannelOptions } from './inbox-channel.js';\nexport { createEmailChannel, USER_OBJECT as EMAIL_USER_OBJECT } from './email-channel.js';\nexport type { EmailChannelOptions, EmailSenderSurface } from './email-channel.js';\n\n// Templates + renderer (ADR-0030 P3)\nexport {\n NotificationTemplateStore,\n renderNotification,\n interpolate,\n TEMPLATE_OBJECT,\n DEFAULT_LOCALE,\n} from './template-renderer.js';\nexport type {\n NotificationTemplateRow,\n RenderedNotification,\n RenderInput,\n NotificationTemplateStoreOptions,\n} from './template-renderer.js';\nexport type {\n MessagingChannel,\n MessagingChannelContext,\n Notification,\n Delivery,\n SendResult,\n ErrorClass,\n} from './channel.js';\n\n// Reliable delivery — outbox + dispatcher (ADR-0030 P1)\nexport type {\n INotificationOutbox,\n NotificationDeliveryRecord,\n DeliveryStatus,\n DeliveryPayload,\n EnqueueDeliveryInput,\n ClaimOptions,\n AckResult,\n} from './outbox.js';\nexport { SqlNotificationOutbox, DELIVERY_OBJECT } from './sql-outbox.js';\nexport type { SqlNotificationOutboxOptions } from './sql-outbox.js';\nexport { MemoryNotificationOutbox } from './memory-outbox.js';\nexport { hashPartition, nextRetryDelayMs, classifyDeliveryAttempt } from './backoff.js';\nexport { NotificationDispatcher } from './dispatcher.js';\nexport type {\n NotificationDispatcherOptions,\n ChannelRegistry,\n DispatchCluster,\n DispatchLockHandle,\n} from './dispatcher.js';\nexport { NotificationRetention, DEFAULT_RETENTION_TARGETS } from './retention.js';\nexport type { NotificationRetentionOptions, RetentionTarget, PruneOutcome } from './retention.js';\n\n// Objects (metadata definitions)\nexport {\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n} from './objects/index.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { MessagingService } from './messaging-service.js';\nimport { createInboxChannel } from './inbox-channel.js';\nimport { SqlNotificationOutbox } from './sql-outbox.js';\nimport { NotificationDispatcher, type DispatchCluster } from './dispatcher.js';\nimport { NotificationRetention } from './retention.js';\nimport { createEmailChannel } from './email-channel.js';\nimport { NotificationTemplateStore } from './template-renderer.js';\nimport {\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n} from './objects/index.js';\n\nexport interface MessagingServicePluginOptions {\n /**\n * Register the always-on `inbox` channel during init (default `true`).\n * Set `false` only for tests that want an empty registry.\n */\n registerInbox?: boolean;\n /**\n * Run the durable delivery outbox + dispatcher (ADR-0030 P1) when a data\n * engine is available (default `true`). When off (or no engine), `emit()`\n * fans out inline best-effort (P0 behavior).\n */\n reliableDelivery?: boolean;\n /** Outbox/dispatcher partition count (default 8). */\n partitionCount?: number;\n /** Dispatcher tick interval in ms (default 500). */\n dispatchIntervalMs?: number;\n /**\n * Topics that bypass the per-user preference matrix (ADR-0030 P2) — e.g.\n * security/system alerts users must not be able to mute. Exact match, or a\n * `prefix.` entry for a prefix match (default none).\n */\n mandatoryTopics?: readonly string[];\n /**\n * Retention window in days for the notification pipeline (ADR-0030\n * hardening). When set (> 0), a periodic sweep prunes events, deliveries,\n * inbox materializations and receipts older than this — bounding the\n * event-log growth from high-frequency periodic flows. **Opt-in**: unset (or\n * ≤ 0) disables retention (default), so no notification data is ever deleted\n * without explicit operator policy.\n */\n retentionDays?: number;\n /** Retention sweep interval in ms (default 1 hour). Only used when `retentionDays` is set. */\n retentionSweepMs?: number;\n}\n\n/**\n * MessagingServicePlugin — registers the `messaging` service (ADR-0012 /\n * ADR-0030).\n *\n * After bootstrap, `kernel.getService('messaging')` is a {@link MessagingService}\n * with the always-on `inbox` channel registered. The baseline `notify` flow\n * node dispatches through it; flows therefore stop being no-ops once this\n * plugin is installed. Other channels (email/webhook/push/IM) register\n * themselves on this same service.\n *\n * At `kernel:ready` (engine available) the plugin wires the reliable-delivery\n * path: a `SqlNotificationOutbox` over `sys_notification_delivery` plus a\n * `NotificationDispatcher` that drains it with retry/backoff/dead-letter.\n * `emit()` then enqueues durable deliveries instead of fanning out inline.\n *\n * @example\n * ```ts\n * const kernel = new ObjectKernel();\n * kernel.use(new AutomationServicePlugin()); // ships the `notify` node\n * kernel.use(new MessagingServicePlugin()); // backs it with delivery\n * await kernel.bootstrap();\n * ```\n */\nexport class MessagingServicePlugin implements Plugin {\n name = 'com.objectstack.service.messaging';\n version = '1.0.0';\n type = 'standard' as const;\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: Required<MessagingServicePluginOptions>;\n private dispatcher?: NotificationDispatcher;\n private retentionTimer?: ReturnType<typeof setInterval>;\n\n constructor(options: MessagingServicePluginOptions = {}) {\n this.options = {\n registerInbox: true,\n reliableDelivery: true,\n partitionCount: 8,\n dispatchIntervalMs: 500,\n mandatoryTopics: [],\n retentionDays: 0,\n retentionSweepMs: 3_600_000,\n ...options,\n };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Shared lazy data-engine resolver — used to persist the L2\n // `sys_notification` event in `emit()`, by the inbox channel to\n // materialize rows, and (at kernel:ready) to back the outbox. Resolved\n // lazily so it works regardless of plugin init order.\n const getData = (): IDataEngine | undefined => {\n try {\n return ctx.getService<IDataEngine>('data') ?? ctx.getService<IDataEngine>('objectql');\n } catch {\n return undefined;\n }\n };\n\n const service = new MessagingService({\n logger: ctx.logger,\n getData,\n mandatoryTopics: this.options.mandatoryTopics,\n });\n\n if (this.options.registerInbox) {\n service.registerChannel(createInboxChannel({ getData }));\n }\n\n ctx.registerService('messaging', service);\n\n // Register the messaging objects so their rows can be written. The\n // preference/subscription objects (ADR-0030 P2) are Studio-configurable,\n // so contribute them to the Setup app's Configuration slot (ADR-0029 D7)\n // — they appear in nav only when this plugin is installed.\n ctx.getService<{ register(m: unknown): void }>('manifest').register({\n id: 'com.objectstack.service.messaging',\n name: 'Messaging Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'system',\n objects: [\n InboxMessage,\n NotificationReceipt,\n NotificationDelivery,\n NotificationPreference,\n NotificationSubscription,\n NotificationTemplate,\n ],\n navigationContributions: [\n {\n app: 'setup',\n group: 'group_configuration',\n priority: 120,\n items: [\n { id: 'nav_notification_preferences', type: 'object', label: 'Notification Preferences', objectName: 'sys_notification_preference', icon: 'bell-ring', requiresObject: 'sys_notification_preference' },\n { id: 'nav_notification_subscriptions', type: 'object', label: 'Notification Subscriptions', objectName: 'sys_notification_subscription', icon: 'rss', requiresObject: 'sys_notification_subscription' },\n { id: 'nav_notification_templates', type: 'object', label: 'Notification Templates', objectName: 'sys_notification_template', icon: 'file-text', requiresObject: 'sys_notification_template' },\n ],\n },\n ],\n });\n\n // Email channel (ADR-0030 P3): register when an `email` service is\n // present. Resolved at kernel:ready so init order with the email plugin\n // doesn't matter; absent email ⇒ no channel (a notify(channels:['email'])\n // then reports \"not registered\" rather than silently no-opping). The\n // dispatcher looks channels up dynamically, so registering after it is fine.\n if (typeof ctx.hook === 'function') {\n const templateStore = new NotificationTemplateStore({ getData });\n const getEmail = () => {\n try {\n return ctx.getService<import('./email-channel.js').EmailSenderSurface>('email');\n } catch {\n return undefined;\n }\n };\n ctx.hook('kernel:ready', async () => {\n if (getEmail()) {\n service.registerChannel(createEmailChannel({ getEmail, getData, store: templateStore }));\n ctx.logger.info('[messaging] email channel registered (renders sys_notification_template)');\n }\n });\n }\n\n // Reliable delivery (P1): wire the outbox + dispatcher once the engine\n // is resolvable. Until then `emit()` runs inline best-effort.\n if (this.options.reliableDelivery && typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const engine = getData();\n if (!engine) {\n ctx.logger.warn('[messaging] no data engine at kernel:ready — reliable delivery disabled (inline fan-out)');\n return;\n }\n const outbox = new SqlNotificationOutbox(engine, { partitionCount: this.options.partitionCount });\n service.setOutbox(outbox);\n\n let cluster: DispatchCluster | undefined;\n try {\n cluster = ctx.getService<DispatchCluster>('cluster');\n } catch {\n cluster = undefined; // single-node fallback in the dispatcher\n }\n\n this.dispatcher = new NotificationDispatcher({\n nodeId: `notify-${process.pid}-${randomUUID().slice(0, 8)}`,\n outbox,\n channels: service,\n channelContext: { logger: ctx.logger },\n cluster,\n partitionCount: this.options.partitionCount,\n intervalMs: this.options.dispatchIntervalMs,\n logger: ctx.logger,\n });\n this.dispatcher.start();\n ctx.logger.info(\n `[messaging] reliable delivery on (outbox + dispatcher, ${this.options.partitionCount} partitions${cluster ? ', clustered' : ', single-node'})`,\n );\n });\n }\n\n // Retention sweep (ADR-0030 hardening): opt-in pruning of the\n // notification pipeline so the event log can't grow unbounded. Runs once\n // at ready then on a low-frequency interval; the timer is unref'd so it\n // never keeps the process alive.\n if (this.options.retentionDays > 0 && typeof ctx.hook === 'function') {\n ctx.hook('kernel:ready', async () => {\n const retention = new NotificationRetention({ getData, logger: ctx.logger });\n const days = this.options.retentionDays;\n const sweep = () => {\n void retention.prune(days).catch((err) =>\n ctx.logger.warn(`[messaging] retention sweep failed: ${(err as Error)?.message ?? err}`),\n );\n };\n sweep();\n this.retentionTimer = setInterval(sweep, this.options.retentionSweepMs);\n this.retentionTimer.unref?.();\n ctx.logger.info(\n `[messaging] retention on (prune > ${days}d every ${Math.round(this.options.retentionSweepMs / 1000)}s)`,\n );\n });\n }\n\n ctx.logger.info(\n `[messaging] service registered with channels: ${service.getRegisteredChannels().join(', ') || '(none)'}`,\n );\n }\n\n /** Stop the dispatcher loop + retention sweep on shutdown. */\n async stop(): Promise<void> {\n await this.dispatcher?.stop();\n this.dispatcher = undefined;\n if (this.retentionTimer) {\n clearInterval(this.retentionTimer);\n this.retentionTimer = undefined;\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type { Audience, AudienceSpec } from './messaging-service.js';\n\n/**\n * Cheap \"looks like an email\" heuristic so we attempt id resolution. Hand-rolled\n * (no regex) to stay strictly linear — the obvious `^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$`\n * has overlapping quantifiers (the `.` is also in `[^\\s@]`) and backtracks\n * polynomially on adversarial input (ReDoS). This is O(n).\n */\nfunction looksLikeEmail(s: string): boolean {\n if (!s || /\\s/.test(s)) return false; // single char-class test — linear\n const at = s.indexOf('@');\n if (at <= 0 || at !== s.lastIndexOf('@') || at === s.length - 1) return false;\n const domain = s.slice(at + 1);\n const dot = domain.indexOf('.');\n // a dot in the domain, not first or last char\n return dot > 0 && dot < domain.length - 1;\n}\n\n/** The user identity object an email-shaped recipient is resolved against. */\nexport const USER_OBJECT = 'sys_user';\n/** Tenant-membership object backing `role:` expansion (`sys_member.role`). */\nexport const MEMBER_OBJECT = 'sys_member';\n/** Team-membership object backing `team:` expansion (`sys_team_member.team_id`). */\nexport const TEAM_MEMBER_OBJECT = 'sys_team_member';\n\n/**\n * Conventional owner/assignee field names tried, in order, for `owner_of:`\n * audience resolution. Mirrors the audit writer's `OWNER_FIELDS`.\n */\nconst DEFAULT_OWNER_FIELDS = ['owner_id', 'assigned_to', 'assignee_id', 'owner', 'assignee'];\n\nexport interface RecipientResolverLogger {\n warn(...args: unknown[]): void;\n info?(...args: unknown[]): void;\n}\n\nexport interface RecipientResolverOptions {\n /** Resolve the runtime data engine. `undefined` on a minimal/test stack. */\n getData(): IDataEngine | undefined;\n logger: RecipientResolverLogger;\n /** Identity object for email→id resolution (default {@link USER_OBJECT}). */\n userObject?: string;\n /** Membership object for `role:` (default {@link MEMBER_OBJECT}). */\n memberObject?: string;\n /** Membership object for `team:` (default {@link TEAM_MEMBER_OBJECT}). */\n teamMemberObject?: string;\n /** Owner field candidates for `owner_of:` (default {@link DEFAULT_OWNER_FIELDS}). */\n ownerFields?: string[];\n}\n\nexport interface ResolveContext {\n /** Tenant scope applied to `role:` expansion. */\n organizationId?: string;\n}\n\n/**\n * RecipientResolver (ADR-0030 P1) — expands an {@link Audience} into a flat,\n * de-duplicated list of recipient **user ids**.\n *\n * Reuses the platform's existing identity/membership object model (the same\n * `sys_member.role` / `sys_team_member.team_id` graph `plugin-sharing`'s\n * `TeamGraphService` reads) by querying it directly through the data engine —\n * so `service-messaging` gains no backward dependency on a plugin. This is the\n * single home for recipient resolution: the inbox channel no longer does its\n * own email→id fallback (that moved here per ADR-0030 P1).\n *\n * Supported audience specs:\n * - `'<userId>'` → the id verbatim\n * - `'user:<id>'` → the id (prefix stripped)\n * - `'<email>'` → `sys_user` lookup by email → id (verbatim on miss)\n * - `'role:<name>'` → `sys_member` where `role = name` (tenant-scoped)\n * - `'team:<id>'` → `sys_team_member` where `team_id = id`\n * - `'owner_of:<obj>:<id>'` → the owner field of that record\n * - `{ ownerOf: { object, id } }` → same, structured form\n *\n * Every lookup is best-effort: a failed query resolves to no recipients for\n * that spec (logged) rather than throwing — emit() must never fail because a\n * directory lookup hiccupped.\n */\nexport class RecipientResolver {\n private readonly userObject: string;\n private readonly memberObject: string;\n private readonly teamMemberObject: string;\n private readonly ownerFields: string[];\n\n constructor(private readonly opts: RecipientResolverOptions) {\n this.userObject = opts.userObject ?? USER_OBJECT;\n this.memberObject = opts.memberObject ?? MEMBER_OBJECT;\n this.teamMemberObject = opts.teamMemberObject ?? TEAM_MEMBER_OBJECT;\n this.ownerFields = opts.ownerFields ?? DEFAULT_OWNER_FIELDS;\n }\n\n /** Expand an audience to a de-duplicated list of recipient user ids. */\n async resolve(audience: Audience, ctx: ResolveContext = {}): Promise<string[]> {\n const specs = Array.isArray(audience) ? audience : [audience as AudienceSpec];\n const data = this.opts.getData();\n const out: string[] = [];\n\n for (const spec of specs) {\n for (const id of await this.resolveOne(spec, data, ctx)) {\n if (id) out.push(id);\n }\n }\n // De-dup while preserving order.\n return [...new Set(out)];\n }\n\n private async resolveOne(\n spec: AudienceSpec,\n data: IDataEngine | undefined,\n ctx: ResolveContext,\n ): Promise<string[]> {\n if (typeof spec !== 'string') {\n // Structured `{ ownerOf: { object, id } }`.\n if (spec && typeof spec === 'object' && 'ownerOf' in spec) {\n return this.resolveOwnerOf(spec.ownerOf.object, spec.ownerOf.id, data);\n }\n this.opts.logger.warn(`[recipients] unrecognized audience spec ${JSON.stringify(spec)}; skipped`);\n return [];\n }\n\n const value = spec.trim();\n if (!value) return [];\n\n if (value.startsWith('user:')) return [value.slice(5)].filter(Boolean);\n if (value.startsWith('role:')) return this.resolveRole(value.slice(5), data, ctx);\n if (value.startsWith('team:')) return this.resolveTeam(value.slice(5), data);\n if (value.startsWith('owner_of:')) {\n // `owner_of:<object>:<id>` — id may itself contain ':' (rare), so\n // split only on the first two segments.\n const rest = value.slice('owner_of:'.length);\n const sep = rest.indexOf(':');\n if (sep > 0) return this.resolveOwnerOf(rest.slice(0, sep), rest.slice(sep + 1), data);\n this.opts.logger.warn(`[recipients] malformed owner_of spec '${value}'; skipped`);\n return [];\n }\n if (looksLikeEmail(value)) return [await this.resolveEmail(value, data)];\n // Bare user id.\n return [value];\n }\n\n /** `role:` → `sys_member` rows with that role in the tenant. */\n private async resolveRole(role: string, data: IDataEngine | undefined, ctx: ResolveContext): Promise<string[]> {\n if (!role || !data) return [];\n const where: Record<string, unknown> = { role };\n if (ctx.organizationId) where.organization_id = ctx.organizationId;\n try {\n const rows = await data.find(this.memberObject, { where, fields: ['user_id'], limit: 10000 });\n return userIds(rows);\n } catch (err) {\n this.opts.logger.warn(`[recipients] role '${role}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /** `team:` → `sys_team_member` rows for that team. */\n private async resolveTeam(teamId: string, data: IDataEngine | undefined): Promise<string[]> {\n if (!teamId || !data) return [];\n try {\n const rows = await data.find(this.teamMemberObject, {\n where: { team_id: teamId },\n fields: ['user_id'],\n limit: 10000,\n });\n return userIds(rows);\n } catch (err) {\n this.opts.logger.warn(`[recipients] team '${teamId}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /** `owner_of:` → the owner/assignee field of the referenced record. */\n private async resolveOwnerOf(object: string, id: string, data: IDataEngine | undefined): Promise<string[]> {\n if (!object || !id || !data) return [];\n try {\n const rec = await data.findOne(object, { where: { id }, fields: ['id', ...this.ownerFields] });\n if (!rec) return [];\n for (const f of this.ownerFields) {\n const v = rec[f];\n if (typeof v === 'string' && v.length > 0) return [v];\n }\n return [];\n } catch (err) {\n this.opts.logger.warn(`[recipients] owner_of '${object}:${id}' lookup failed (${msg(err)}); 0 recipients`);\n return [];\n }\n }\n\n /**\n * Resolve an email-shaped recipient to its user id. Falls back to the email\n * verbatim on no match or lookup error (a downstream channel may still key\n * a row by it — never lose the recipient on a directory miss).\n */\n private async resolveEmail(email: string, data: IDataEngine | undefined): Promise<string> {\n if (!data) return email;\n try {\n const user = await data.findOne(this.userObject, { where: { email }, fields: ['id'] });\n const id = user?.id;\n if (id != null && String(id).length > 0) return String(id);\n this.opts.logger.warn(`[recipients] no '${this.userObject}' matched email '${email}'; keeping verbatim`);\n return email;\n } catch (err) {\n this.opts.logger.warn(`[recipients] email '${email}' lookup failed (${msg(err)}); keeping verbatim`);\n return email;\n }\n }\n}\n\n/** Pull distinct non-empty `user_id`s from a row set. */\nfunction userIds(rows: unknown): string[] {\n if (!Array.isArray(rows)) return [];\n return [...new Set(rows.map((r: any) => String(r?.user_id ?? '')).filter(Boolean))];\n}\n\nfunction msg(err: unknown): string {\n return (err as Error)?.message ?? String(err);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/** The object the preference matrix lives in. */\nexport const PREFERENCE_OBJECT = 'sys_notification_preference';\n\nexport interface PreferenceResolverLogger {\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n}\n\nexport interface PreferenceResolverOptions {\n /** Lazily resolve the data engine; `undefined` ⇒ fail-open (deliver all). */\n getData(): IDataEngine | undefined;\n logger: PreferenceResolverLogger;\n /**\n * Topics that bypass preferences entirely (security/system alerts users\n * must not be able to mute). An entry ending in `.` is a prefix match\n * (`security.` matches `security.breach`); otherwise it is an exact match.\n */\n mandatoryTopics?: readonly string[];\n /** Object name override (default {@link PREFERENCE_OBJECT}). */\n objectName?: string;\n}\n\nexport interface PreferenceContext {\n topic: string;\n organizationId?: string;\n /** Event severity — `critical` bypasses quiet-hours deferral (P3b). */\n severity?: string;\n /** \"Now\" reference (epoch ms) for quiet-hours math. Defaults to Date.now(). */\n now?: number;\n}\n\n/** A recipient with the channels they accept for this notification. */\nexport interface PreferenceTarget {\n recipient: string;\n channels: string[];\n /**\n * Earliest dispatch time (epoch ms) when the recipient is inside their\n * quiet-hours window; absent ⇒ send now. Applies to all of this recipient's\n * channels (quiet hours are a per-person setting). Honored only on the\n * durable outbox path; inline best-effort fan-out ignores it.\n */\n notBefore?: number;\n}\n\n/** Quiet-hours window declared on a preference row. */\nexport interface QuietHours {\n tz?: string;\n start?: string; // 'HH:MM'\n end?: string; // 'HH:MM'\n}\n\nconst WILDCARD = '*';\n\n/**\n * PreferenceResolver — the ADR-0030 Layer-3 preference filter (P2).\n *\n * Given the resolved recipients and the requested channels, returns, per\n * recipient, the channels they actually accept for `topic`. Resolution is\n * most-specific-wins over `sys_notification_preference` rows with `*` wildcards\n * for user / topic / channel; a real-user row overrides the `user_id='*'`\n * admin-global default; the built-in default is **on**.\n *\n * Two safety rules:\n * - **Mandatory topics bypass** the matrix (all channels kept).\n * - **Fail-open**: no data engine, or a lookup error, keeps all channels — a\n * preference outage must never silently swallow notifications.\n */\nexport class PreferenceResolver {\n private readonly objectName: string;\n private readonly mandatory: readonly string[];\n\n constructor(private readonly opts: PreferenceResolverOptions) {\n this.objectName = opts.objectName ?? PREFERENCE_OBJECT;\n this.mandatory = opts.mandatoryTopics ?? [];\n }\n\n /** Whether a topic bypasses preferences (exact or `prefix.` match). */\n isMandatory(topic: string): boolean {\n return this.mandatory.some((m) =>\n m.endsWith('.') ? topic.startsWith(m) : topic === m,\n );\n }\n\n /**\n * Filter `(recipient × channel)` by preference. Recipients left with no\n * accepted channel are dropped from the result.\n */\n async filter(\n recipients: string[],\n channels: string[],\n ctx: PreferenceContext,\n ): Promise<PreferenceTarget[]> {\n const all = (): PreferenceTarget[] => recipients.map((r) => ({ recipient: r, channels: [...channels] }));\n if (recipients.length === 0 || channels.length === 0) return [];\n if (this.isMandatory(ctx.topic)) return all();\n\n const data = this.opts.getData();\n if (!data) return all(); // fail-open\n\n let rows: Record<string, unknown>[];\n try {\n rows = await this.loadRows(data, ctx);\n } catch (err) {\n this.opts.logger.warn(\n `[preferences] lookup for topic '${ctx.topic}' failed (${msg(err)}); delivering all (fail-open)`,\n );\n return all();\n }\n\n // Index rows by `${user}|${topic}|${channel}` → { enabled, quietHours }.\n const recipientSet = new Set(recipients);\n const index = new Map<string, PrefRowLite>();\n for (const r of rows) {\n const user = String(r.user_id ?? '');\n if (user !== WILDCARD && !recipientSet.has(user)) continue; // ignore unrelated users\n const topic = String(r.topic ?? WILDCARD);\n const channel = String(r.channel ?? WILDCARD);\n index.set(`${user}|${topic}|${channel}`, {\n enabled: asBool(r.enabled),\n quietHours: parseQuietHours(r.quiet_hours),\n });\n }\n\n const nowMs = ctx.now ?? Date.now();\n const critical = ctx.severity === 'critical';\n const targets: PreferenceTarget[] = [];\n for (const recipient of recipients) {\n const accepted = channels.filter(\n (channel) => this.resolveRow(index, recipient, ctx.topic, channel)?.enabled ?? true,\n );\n if (accepted.length === 0) continue;\n // Quiet-hours deferral (per person; declared on a channel-wildcard\n // row). Critical events bypass it.\n let notBefore: number | undefined;\n if (!critical) {\n const qh = this.resolveQuietHours(index, recipient, ctx.topic);\n notBefore = qh ? quietHoursDeferral(qh, nowMs) : undefined;\n }\n targets.push(notBefore != null ? { recipient, channels: accepted, notBefore } : { recipient, channels: accepted });\n }\n return targets;\n }\n\n /** Load the candidate rows (topic-specific + wildcard-topic), org-scoped. */\n private async loadRows(data: IDataEngine, ctx: PreferenceContext): Promise<Record<string, unknown>[]> {\n // Two equality queries (topic and the '*' wildcard) avoid relying on\n // driver-specific IN support; user filtering is done in memory.\n const base: Record<string, unknown> = {};\n if (ctx.organizationId) base.organization_id = ctx.organizationId;\n const [specific, wildcard] = await Promise.all([\n data.find(this.objectName, { where: { ...base, topic: ctx.topic }, limit: 10000 }),\n data.find(this.objectName, { where: { ...base, topic: WILDCARD }, limit: 10000 }),\n ]);\n return [...(specific ?? []), ...(wildcard ?? [])];\n }\n\n /**\n * Most-specific-wins lookup for (user, topic, channel). User-specific beats\n * the `*` user; topic/channel specific beats their wildcards.\n */\n private resolveRow(index: Map<string, PrefRowLite>, user: string, topic: string, channel: string): PrefRowLite | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n for (const c of [channel, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${c}`);\n if (hit !== undefined) return hit;\n }\n }\n }\n return undefined; // built-in default handled by callers (opted in)\n }\n\n /**\n * Resolve a recipient's quiet-hours window. Declared on a channel-wildcard\n * row (`(user, *, *)` or `(user, topic, *)`) — quiet hours are a per-person,\n * channel-agnostic setting. Most-specific user/topic wins.\n */\n private resolveQuietHours(index: Map<string, PrefRowLite>, user: string, topic: string): QuietHours | undefined {\n for (const u of [user, WILDCARD]) {\n for (const t of [topic, WILDCARD]) {\n const hit = index.get(`${u}|${t}|${WILDCARD}`);\n if (hit?.quietHours) return hit.quietHours;\n }\n }\n return undefined;\n }\n}\n\ninterface PrefRowLite {\n enabled: boolean;\n quietHours?: QuietHours;\n}\n\nfunction asBool(v: unknown): boolean {\n return v === true || v === 1 || v === '1' || v === 'true';\n}\n\nfunction parseQuietHours(v: unknown): QuietHours | undefined {\n let o: any = v;\n if (typeof o === 'string') {\n try { o = JSON.parse(o); } catch { return undefined; }\n }\n if (!o || typeof o !== 'object') return undefined;\n if (o.start == null || o.end == null) return undefined;\n return { tz: o.tz, start: String(o.start), end: String(o.end) };\n}\n\n/**\n * Compute the deferral target (epoch ms) when `now` falls inside the quiet-hours\n * window, else `undefined`. Times are `HH:MM` in `quietHours.tz` (default UTC).\n * Supports overnight windows (start > end, e.g. 22:00–08:00). Uses `Intl` to read\n * the wall-clock minutes in the tz; returns `now + minutesUntilWindowEnd`.\n */\nexport function quietHoursDeferral(quietHours: QuietHours, nowMs: number): number | undefined {\n const start = parseHHMM(quietHours.start);\n const end = parseHHMM(quietHours.end);\n if (start == null || end == null || start === end) return undefined;\n\n const cur = minutesOfDayInTz(nowMs, quietHours.tz ?? 'UTC');\n let untilEnd: number | undefined;\n if (start < end) {\n if (cur >= start && cur < end) untilEnd = end - cur;\n } else {\n // Overnight window wrapping midnight.\n if (cur >= start) untilEnd = 1440 - cur + end;\n else if (cur < end) untilEnd = end - cur;\n }\n return untilEnd == null ? undefined : nowMs + untilEnd * 60_000;\n}\n\nfunction parseHHMM(s?: string): number | undefined {\n if (!s) return undefined;\n const m = /^(\\d{1,2}):(\\d{2})$/.exec(s.trim());\n if (!m) return undefined;\n const h = Number(m[1]);\n const min = Number(m[2]);\n if (h > 23 || min > 59) return undefined;\n return h * 60 + min;\n}\n\nfunction minutesOfDayInTz(nowMs: number, tz: string): number {\n try {\n const parts = new Intl.DateTimeFormat('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n timeZone: tz,\n }).formatToParts(new Date(nowMs));\n const hour = Number(parts.find((p) => p.type === 'hour')?.value ?? '0') % 24; // '24' → 0\n const minute = Number(parts.find((p) => p.type === 'minute')?.value ?? '0');\n return hour * 60 + minute;\n } catch {\n // Unknown tz → treat as UTC.\n const d = new Date(nowMs);\n return d.getUTCHours() * 60 + d.getUTCMinutes();\n }\n}\n\nfunction msg(err: unknown): string {\n return (err as Error)?.message ?? String(err);\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n MessagingChannel,\n MessagingChannelContext,\n Notification,\n} from './channel.js';\nimport { RecipientResolver } from './recipient-resolver.js';\nimport { PreferenceResolver, type PreferenceTarget } from './preference-resolver.js';\nimport type { INotificationOutbox } from './outbox.js';\n\n/** The L2 event object every `emit()` writes one row to (ADR-0030). */\nexport const NOTIFICATION_EVENT_OBJECT = 'sys_notification';\n\n/**\n * Audience selector for {@link EmitInput}. P0 resolves explicit user ids and\n * email-shaped recipients inline (email→id is finished at the inbox channel);\n * `role:` / `team:` / `owner_of:` selectors are forwarded but only fully\n * expanded by the `RecipientResolver` in P1.\n */\nexport type AudienceSpec =\n | string // user id | email | 'role:x' | 'team:x'\n | { ownerOf: { object: string; id: string } };\n\nexport type Audience = AudienceSpec | readonly AudienceSpec[];\n\n/**\n * The single notification ingress (ADR-0030 §3). Every producer — the flow\n * `notify` node, collaboration `@mention`, record assignment, system alerts —\n * calls `emit()` with this shape. No producer writes a per-user inbox row\n * directly; the in-app inbox is a *materialization of delivery*.\n */\nexport interface EmitInput {\n /** Topic id, e.g. `task.assigned`, `collab.mention`. */\n readonly topic: string;\n /** Who should receive it. See {@link Audience}. */\n readonly audience: Audience;\n /** Template inputs carried to channels (title/body/url/actor/source/…). */\n readonly payload?: Record<string, unknown>;\n /** Severity hint for rendering / filtering. */\n readonly severity?: 'info' | 'warning' | 'critical';\n /** Idempotency key within a topic window; a repeat `emit` is a no-op. */\n readonly dedupKey?: string;\n /** The record that triggered the event. */\n readonly source?: { object: string; id: string };\n /** User who caused the event (mentioner, assigner). */\n readonly actorId?: string;\n /** Tenant stamp for sudo/background paths so RLS matches recipients. */\n readonly organizationId?: string;\n /** Channels to fan out to. Defaults to `['inbox']` (always on). */\n readonly channels?: string[];\n}\n\n/** Per-delivery outcome record returned from {@link MessagingService.emit}. */\nexport interface DeliveryOutcome {\n readonly channel: string;\n readonly recipient: string;\n readonly ok: boolean;\n readonly externalId?: string;\n readonly error?: string;\n}\n\n/** Aggregate result of fanning one notification out to its channels. */\nexport interface EmitResult {\n /** Id of the L2 `sys_notification` event written (or matched, on dedup). */\n readonly notificationId: string;\n /** True when `dedupKey` matched an existing event and fan-out was skipped. */\n readonly deduped: boolean;\n readonly deliveries: DeliveryOutcome[];\n readonly delivered: number;\n readonly failed: number;\n}\n\n/** Context the service needs: a logger, plus data access for the L2 event. */\nexport interface MessagingServiceContext extends MessagingChannelContext {\n /**\n * Resolve the runtime data engine used to persist the L2 event. Returns\n * `undefined` on a minimal/test stack with no data layer — `emit()` then\n * uses a synthetic id and warns rather than throwing, matching the\n * platform's CRUD-node degradation.\n */\n getData?(): IDataEngine | undefined;\n /** Clock injection for deterministic tests. Defaults to `new Date()`. */\n now?(): string;\n /** Override the recipient resolver (tests). Defaults to a data-backed one. */\n recipientResolver?: RecipientResolver;\n /** Override the preference resolver (tests). Defaults to a data-backed one. */\n preferenceResolver?: PreferenceResolver;\n /**\n * Topics that bypass the per-user preference matrix (ADR-0030 P2) — e.g.\n * security/system alerts users must not be able to mute. Exact match, or a\n * `prefix.` entry for prefix match.\n */\n mandatoryTopics?: readonly string[];\n /**\n * Durable delivery outbox (ADR-0030 P1). When present, `emit()` enqueues a\n * `pending` delivery row per `(recipient × channel)` and the\n * `NotificationDispatcher` performs the send + retries. When absent, `emit()`\n * fans out inline (best-effort, no retry) — the P0 behavior.\n */\n outbox?: INotificationOutbox;\n}\n\n/**\n * MessagingService — the notification dispatcher (ADR-0012 / ADR-0030).\n *\n * Holds the `MessagingChannel` registry and implements the single ingress\n * `emit(EmitInput)`:\n * 1. write the L2 `sys_notification` event (idempotent on `dedupKey`);\n * 2. resolve the audience to recipient ids (inline in P0; `RecipientResolver`\n * owns role/team/owner_of expansion in P1);\n * 3. fan `(channel × recipient)` deliveries and call each channel's `send()`,\n * which materializes the artifact (inbox row + receipt, email, …).\n *\n * Deliberately *not* in this phase (ADR-0030 P1+): the durable outbox, retry\n * schedule, cluster-lock, dead-letter, the per-user preference matrix,\n * renderers/templates, and digest middleware. `emit()` is best-effort fan-out;\n * failures are reported in the result. The seams are shaped so those land\n * without breaking callers.\n */\nexport class MessagingService {\n private readonly channels = new Map<string, MessagingChannel>();\n private readonly now: () => string;\n private readonly resolver: RecipientResolver;\n private readonly preferences: PreferenceResolver;\n private outbox?: INotificationOutbox;\n\n constructor(private readonly ctx: MessagingServiceContext) {\n this.now = ctx.now ?? (() => new Date().toISOString());\n this.resolver =\n ctx.recipientResolver ??\n new RecipientResolver({ getData: () => ctx.getData?.(), logger: ctx.logger });\n this.preferences =\n ctx.preferenceResolver ??\n new PreferenceResolver({\n getData: () => ctx.getData?.(),\n logger: ctx.logger,\n mandatoryTopics: ctx.mandatoryTopics,\n });\n this.outbox = ctx.outbox;\n }\n\n /**\n * Attach the durable delivery outbox after construction. The plugin wires\n * this once the data engine is resolvable (kernel:ready), switching `emit()`\n * from inline fan-out to the reliable enqueue → dispatcher path.\n */\n setOutbox(outbox: INotificationOutbox): void {\n this.outbox = outbox;\n }\n\n /** Register a channel implementation. A duplicate id warns and replaces. */\n registerChannel(channel: MessagingChannel): void {\n if (this.channels.has(channel.id)) {\n this.ctx.logger.warn(`[messaging] channel '${channel.id}' already registered; replacing`);\n }\n this.channels.set(channel.id, channel);\n this.ctx.logger.info(`[messaging] channel registered: ${channel.id}`);\n }\n\n /** Remove a channel. No-op when absent. */\n unregisterChannel(id: string): void {\n this.channels.delete(id);\n }\n\n /** Look up a channel by id. */\n getChannel(id: string): MessagingChannel | undefined {\n return this.channels.get(id);\n }\n\n /** All registered channel ids. */\n getRegisteredChannels(): string[] {\n return [...this.channels.keys()];\n }\n\n /**\n * The single notification ingress. Writes the L2 event, resolves the\n * audience, and fans the result out to its channels. An unregistered\n * channel, or a channel that throws, is reported as a failed delivery — it\n * never aborts the rest of the fan-out. A `dedupKey` that matches an\n * existing event short-circuits: the event id is returned and no new\n * deliveries are produced.\n */\n async emit(input: EmitInput): Promise<EmitResult> {\n const data = this.ctx.getData?.();\n\n // 1) Idempotency — a prior event with the same dedup_key is a no-op.\n if (input.dedupKey && data) {\n const existing = await this.findEventByDedupKey(data, input.dedupKey);\n if (existing) {\n this.ctx.logger.info(\n `[messaging] emit: dedupKey '${input.dedupKey}' already emitted (${existing}); skipping`,\n );\n return { notificationId: existing, deduped: true, deliveries: [], delivered: 0, failed: 0 };\n }\n }\n\n // 2) Write the L2 event (or synthesize an id when there is no data layer).\n // The check at (1) is a fast-path. Where the driver materializes the\n // UNIQUE(dedup_key) index, it is the real guard: a concurrent emit\n // that raced past (1) and inserted first makes our insert hit the\n // unique violation — we catch it and converge to the winner's event\n // (treated as a dedup hit), so a record-change storm can't produce\n // duplicate notifications. Mirrors the delivery outbox's enqueue\n // convergence. (Drivers that don't enforce the index fall back to the\n // best-effort fast-path — the catch is then simply never taken.)\n let notificationId: string;\n try {\n notificationId = await this.writeEvent(data, input);\n } catch (err) {\n if (input.dedupKey && data) {\n const winner = await this.findEventByDedupKey(data, input.dedupKey);\n if (winner) {\n this.ctx.logger.info(\n `[messaging] emit: dedupKey '${input.dedupKey}' raced; converged to ${winner}`,\n );\n return { notificationId: winner, deduped: true, deliveries: [], delivered: 0, failed: 0 };\n }\n }\n throw err;\n }\n\n // 3) Resolve the audience to recipient user ids (RecipientResolver owns\n // role:/team:/owner_of:/email→id expansion).\n const recipients = await this.resolver.resolve(input.audience, {\n organizationId: input.organizationId,\n });\n if (recipients.length === 0) {\n this.ctx.logger.warn(`[messaging] emit: topic '${input.topic}' resolved to 0 recipients`);\n return { notificationId, deduped: false, deliveries: [], delivered: 0, failed: 0 };\n }\n\n // 3b) Preference filter (ADR-0030 P2): drop the (recipient × channel)\n // pairs the user muted. Mandatory topics bypass; fail-open on error.\n const payload = input.payload ?? {};\n const channels = input.channels?.length ? input.channels : ['inbox'];\n const targets = await this.preferences.filter(recipients, channels, {\n topic: input.topic,\n organizationId: input.organizationId,\n severity: input.severity,\n });\n if (targets.length === 0) {\n this.ctx.logger.info(`[messaging] emit: topic '${input.topic}' suppressed for all recipients by preference`);\n return { notificationId, deduped: false, deliveries: [], delivered: 0, failed: 0 };\n }\n\n // 4) Either enqueue durable deliveries (P1 outbox) or fan out inline (P0).\n if (this.outbox) {\n const deliveries = await this.enqueueDeliveries(this.outbox, notificationId, targets, input, payload);\n const delivered = deliveries.filter((d) => d.ok).length;\n return { notificationId, deduped: false, deliveries, delivered, failed: deliveries.length - delivered };\n }\n\n const notification: Notification = {\n notificationId,\n organizationId: input.organizationId,\n topic: input.topic,\n title: str(payload.title) ?? input.topic,\n body: str(payload.body) ?? '',\n severity: input.severity ?? 'info',\n recipients,\n channels: input.channels,\n actionUrl: actionUrlFor(input, payload),\n payload: input.payload,\n };\n\n const { deliveries, delivered, failed } = await this.fanOut(notification, targets);\n return { notificationId, deduped: false, deliveries, delivered, failed };\n }\n\n /**\n * Enqueue one `pending` delivery row per `(channel × recipient)`. The\n * dispatcher does the actual send + retry; here `ok` means \"accepted for\n * delivery\" (enqueued), not yet delivered — progress is observable on the\n * `sys_notification_delivery` row.\n */\n private async enqueueDeliveries(\n outbox: INotificationOutbox,\n notificationId: string,\n targets: PreferenceTarget[],\n input: EmitInput,\n payload: Record<string, unknown>,\n ): Promise<DeliveryOutcome[]> {\n // Snapshot the rendered content onto each delivery so a later event edit\n // can't rewrite an in-flight send.\n const deliveryPayload = {\n ...payload,\n title: str(payload.title) ?? input.topic,\n body: str(payload.body) ?? '',\n severity: input.severity ?? 'info',\n actionUrl: actionUrlFor(input, payload),\n };\n const deliveries: DeliveryOutcome[] = [];\n for (const { recipient, channels, notBefore } of targets) {\n for (const channel of channels) {\n try {\n const id = await outbox.enqueue({\n notificationId,\n recipientId: recipient,\n channel,\n topic: input.topic,\n payload: deliveryPayload,\n organizationId: input.organizationId,\n // Quiet-hours deferral (P3b): the dispatcher won't claim\n // this row until `notBefore`. Absent ⇒ immediate.\n notBefore,\n });\n deliveries.push({ channel, recipient, ok: true, externalId: id });\n } catch (err) {\n deliveries.push({ channel, recipient, ok: false, error: (err as Error)?.message ?? String(err) });\n }\n }\n }\n return deliveries;\n }\n\n /** Find an existing event id by its dedup key, tolerating lookup failure. */\n private async findEventByDedupKey(data: IDataEngine, dedupKey: string): Promise<string | undefined> {\n try {\n const row = await data.findOne(NOTIFICATION_EVENT_OBJECT, {\n where: { dedup_key: dedupKey },\n fields: ['id'],\n });\n const id = row?.id;\n return id != null && String(id).length > 0 ? String(id) : undefined;\n } catch (err) {\n this.ctx.logger.warn(`[messaging] dedup lookup failed (${(err as Error).message}); proceeding`);\n return undefined;\n }\n }\n\n /**\n * Persist the L2 event and return its id. With no data layer (minimal/test\n * stacks) we warn and synthesize an id so fan-out can still be exercised.\n */\n private async writeEvent(data: IDataEngine | undefined, input: EmitInput): Promise<string> {\n if (!data) {\n this.ctx.logger.warn('[messaging] no data engine registered; event not persisted');\n return `evt_${Math.random().toString(36).slice(2)}`;\n }\n const row: Record<string, unknown> = {\n topic: input.topic,\n payload: input.payload ?? null,\n severity: input.severity ?? 'info',\n dedup_key: input.dedupKey ?? null,\n // Normalize empty strings to null so the (source_object, source_id)\n // index keys on real ids, never '' (producers may pass a bare object\n // with no id — e.g. a comment thread_id with no record part).\n source_object: str(input.source?.object) ?? null,\n source_id: str(input.source?.id) ?? null,\n actor_id: input.actorId ?? null,\n organization_id: input.organizationId ?? null,\n created_at: this.now(),\n };\n const created = await data.insert(NOTIFICATION_EVENT_OBJECT, row);\n const id = Array.isArray(created) ? created[0]?.id : created?.id ?? created;\n return id != null ? String(id) : `evt_${Math.random().toString(36).slice(2)}`;\n }\n\n /**\n * Fan a notification out to each recipient's accepted channels. Each\n * `(recipient, channel)` pair becomes one `send()` call. An unregistered\n * channel, or a channel that throws, is reported as a failed delivery — it\n * never aborts the rest of the fan-out.\n */\n private async fanOut(\n notification: Notification,\n targets: PreferenceTarget[],\n ): Promise<{ deliveries: DeliveryOutcome[]; delivered: number; failed: number }> {\n const deliveries: DeliveryOutcome[] = [];\n\n for (const { recipient, channels } of targets) {\n for (const channelId of channels) {\n const channel = this.channels.get(channelId);\n if (!channel) {\n deliveries.push({\n channel: channelId,\n recipient,\n ok: false,\n error: `channel '${channelId}' not registered`,\n });\n this.ctx.logger.warn(`[messaging] emit: channel '${channelId}' not registered`);\n continue;\n }\n try {\n const result = await channel.send(this.ctx, { notification, channel: channelId, recipient });\n deliveries.push({\n channel: channelId,\n recipient,\n ok: result.ok,\n externalId: result.externalId,\n error: result.error,\n });\n } catch (err) {\n deliveries.push({\n channel: channelId,\n recipient,\n ok: false,\n error: (err as Error)?.message ?? String(err),\n });\n }\n }\n }\n\n const delivered = deliveries.filter((d) => d.ok).length;\n return { deliveries, delivered, failed: deliveries.length - delivered };\n }\n}\n\n/** Coerce a payload value to a non-empty string, else `undefined`. */\nfunction str(v: unknown): string | undefined {\n if (v == null) return undefined;\n const s = String(v);\n return s.length > 0 ? s : undefined;\n}\n\n/**\n * The deep-link the in-app materialization should carry. An explicit\n * `payload.url`/`payload.actionUrl` wins; otherwise, when the emit names a\n * `source` record, synthesize an app-relative `/{object}/{id}` link so the\n * materialization is self-sufficient for navigation (the bell no longer has the\n * L2 event's `source_object/source_id` to fall back on — ADR-0030 L5). Returns\n * `undefined` when there is nothing to link to.\n */\nfunction actionUrlFor(input: EmitInput, payload: Record<string, unknown>): string | undefined {\n const explicit = str(payload.url) ?? str(payload.actionUrl);\n if (explicit) return explicit;\n const obj = str(input.source?.object);\n const id = str(input.source?.id);\n return obj && id ? `/${obj}/${id}` : undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n Delivery,\n ErrorClass,\n MessagingChannel,\n MessagingChannelContext,\n SendResult,\n} from './channel.js';\n\n/** The object the inbox channel writes rows to. */\nexport const INBOX_OBJECT = 'sys_inbox_message';\n\n/** The receipt object the inbox channel writes a `delivered` row to (ADR-0030). */\nexport const RECEIPT_OBJECT = 'sys_notification_receipt';\n\nexport interface InboxChannelOptions {\n /**\n * Resolve the runtime data engine. Returns `undefined` when no data layer\n * is registered (e.g. a minimal test stack) — the channel then warns and\n * reports a no-op success rather than throwing, matching the platform's\n * built-in CRUD-node degradation.\n */\n getData(): IDataEngine | undefined;\n /** Object name override (default {@link INBOX_OBJECT}). */\n objectName?: string;\n /** Receipt object name override (default {@link RECEIPT_OBJECT}). */\n receiptObject?: string;\n /** Clock injection for deterministic tests. Defaults to `new Date()`. */\n now?(): string;\n}\n\n/**\n * The always-on `inbox` channel (ADR-0012 §4).\n *\n * Unlike email/webhook/push, inbox is direction-reversed: there is no outbound\n * call — we write a `sys_inbox_message` row in our own DB and the user's client\n * pulls it. So it needs no connector/transport. One delivery → one row keyed by\n * the recipient user id.\n *\n * Recipients arrive already resolved to user ids by the `RecipientResolver`\n * (ADR-0030 P1) — the email→id fallback that used to live here moved up to the\n * single resolution home, so the channel just keys the row by `recipient`.\n */\nexport function createInboxChannel(opts: InboxChannelOptions): MessagingChannel {\n const objectName = opts.objectName ?? INBOX_OBJECT;\n const receiptObject = opts.receiptObject ?? RECEIPT_OBJECT;\n const now = opts.now ?? (() => new Date().toISOString());\n\n /**\n * Write the `delivered` receipt for an inbox materialization. Best-effort:\n * receipts are the read-state spine but a failure here must never turn a\n * delivered message into a failed one — we log and move on. Skipped when the\n * event id is absent (a synthetic/minimal stack with nothing to key on).\n */\n async function writeDeliveredReceipt(\n ctx: MessagingChannelContext,\n data: IDataEngine,\n r: { notificationId?: string; userId: string; organizationId?: string; at: string },\n ): Promise<void> {\n if (!r.notificationId) return;\n try {\n await data.insert(receiptObject, {\n notification_id: r.notificationId,\n delivery_id: null,\n user_id: r.userId,\n channel: 'inbox',\n state: 'delivered',\n at: r.at,\n organization_id: r.organizationId ?? null,\n created_at: r.at,\n });\n } catch (err) {\n ctx.logger.warn(\n `[inbox] delivered receipt write failed for '${r.userId}' (${(err as Error).message}); inbox row stands`,\n );\n }\n }\n\n return {\n id: 'inbox',\n\n async send(ctx: MessagingChannelContext, delivery: Delivery): Promise<SendResult> {\n const data = opts.getData();\n const n = delivery.notification;\n\n if (!data) {\n ctx.logger.warn(\n `[inbox] no data engine registered; inbox row for '${delivery.recipient}' not persisted`,\n );\n return { ok: true };\n }\n\n const userId = delivery.recipient;\n const at = now();\n\n const row: Record<string, unknown> = {\n user_id: userId,\n notification_id: n.notificationId ?? null,\n topic: n.topic,\n title: n.title,\n body_md: n.body,\n severity: n.severity ?? 'info',\n action_url: n.actionUrl,\n organization_id: n.organizationId ?? null,\n created_at: at,\n };\n\n let inboxId: string | undefined;\n try {\n const created = await data.insert(objectName, row);\n const id = Array.isArray(created) ? created[0]?.id : created?.id ?? created;\n inboxId = id != null ? String(id) : undefined;\n } catch (err) {\n return { ok: false, error: `inbox insert failed: ${(err as Error).message}` };\n }\n\n // Read-state lives in the receipt (ADR-0030), not on the inbox row.\n // Best-effort: a missing receipt must not fail a delivered message.\n await writeDeliveredReceipt(ctx, data, {\n notificationId: n.notificationId,\n userId,\n organizationId: n.organizationId,\n at,\n });\n\n return { ok: true, externalId: inboxId };\n },\n\n classifyError(_err: unknown): ErrorClass {\n // A failed local insert is almost always transient (lock/timeout).\n return 'retryable';\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueDeliveryInput,\n INotificationOutbox,\n NotificationDeliveryRecord,\n} from './outbox.js';\nimport { hashPartition } from './backoff.js';\n\nexport const DELIVERY_OBJECT = 'sys_notification_delivery';\n\nexport interface SqlNotificationOutboxOptions {\n /** Total partitions — MUST match the dispatcher's `partitionCount`. */\n partitionCount: number;\n /** Object name override (default {@link DELIVERY_OBJECT}). */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n notification_id: string;\n recipient_id: string;\n channel: string;\n topic?: string | null;\n payload?: unknown; // json column — engine returns object or string per driver\n organization_id?: string | null;\n partition_key: number;\n status: DeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_attempt_at?: number | null;\n last_attempted_at?: number | null;\n error?: string | null;\n created_at: number;\n updated_at: number;\n}\n\n/**\n * Durable {@link INotificationOutbox} over ObjectQL — the production store.\n * Driver-agnostic (no `FOR UPDATE SKIP LOCKED`): safety comes from the\n * dispatcher's per-partition cluster lock plus the atomic\n * `UPDATE … WHERE status='pending'` claim. `partition_key` is precomputed on\n * enqueue (ObjectQL has no portable `hash()` in WHERE). Mirrors\n * `SqlWebhookOutbox`.\n */\nexport class SqlNotificationOutbox implements INotificationOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(private readonly engine: IDataEngine, opts: SqlNotificationOutboxOptions) {\n if (opts.partitionCount <= 0) throw new Error('SqlNotificationOutbox: partitionCount must be > 0');\n this.objectName = opts.objectName ?? DELIVERY_OBJECT;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueDeliveryInput): Promise<string> {\n const dedup = {\n notification_id: input.notificationId,\n recipient_id: input.recipientId,\n channel: input.channel,\n };\n const existing = await this.engine.findOne(this.objectName, { where: dedup, fields: ['id'] });\n if (existing?.id) return String(existing.id);\n\n const id = randomUUID();\n const now = Date.now();\n const row: DeliveryRow = {\n id,\n notification_id: input.notificationId,\n recipient_id: input.recipientId,\n channel: input.channel,\n topic: input.topic ?? null,\n payload: input.payload ?? {},\n organization_id: input.organizationId ?? null,\n partition_key: hashPartition(input.notificationId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n // Deferred dispatch (quiet-hours, P3): claim() skips pending rows\n // whose next_attempt_at is in the future.\n next_attempt_at: input.notBefore ?? null,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n // Unique-index collision (dedup race) → return the winner.\n const winner = await this.engine.findOne(this.objectName, { where: dedup, fields: ['id'] });\n if (winner?.id) return String(winner.id);\n throw err;\n }\n }\n\n async claim(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows (visibility-timeout recovery).\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n { where: { status: 'in_flight', claimed_at: { $lt: now - opts.claimTtlMs } }, multi: true } as any,\n );\n\n // 2. Candidate ids: ready pending rows in our partition.\n const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n ...partitionFilter,\n $or: [{ next_attempt_at: null }, { next_attempt_at: { $lte: now } }],\n },\n fields: ['id'],\n limit: opts.limit,\n });\n if (!candidates.length) return [];\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim — WHERE status='pending' rejects rows another worker took.\n await this.engine.update(\n this.objectName,\n { status: 'in_flight', claimed_by: opts.nodeId, claimed_at: now, updated_at: now },\n { where: { id: { $in: ids }, status: 'pending' }, multi: true } as any,\n );\n\n // 4. Read back only the rows we own.\n const claimed = (await this.engine.find(this.objectName, {\n where: { id: { $in: ids }, claimed_by: opts.nodeId, claimed_at: now, status: 'in_flight' },\n })) as DeliveryRow[];\n return claimed.map((r) => this.toRecord(r));\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: DeliveryStatus;\n let nextAttemptAt: number | null = null;\n let error: string | null = null;\n\n if (result.success) {\n status = 'success';\n } else if (result.suppressed) {\n status = 'suppressed';\n error = result.error ?? null;\n } else if (result.dead) {\n status = 'dead';\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextAttemptAt = result.nextAttemptAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n next_attempt_at: nextAttemptAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false } as any,\n );\n }\n\n async list(filter?: { status?: DeliveryStatus; notificationId?: string }): Promise<NotificationDeliveryRecord[]> {\n const where: Record<string, unknown> = {};\n if (filter?.status) where.status = filter.status;\n if (filter?.notificationId) where.notification_id = filter.notificationId;\n const rows = (await this.engine.find(this.objectName, { where })) as DeliveryRow[];\n return rows.map((r) => this.toRecord(r));\n }\n\n private toRecord(r: DeliveryRow): NotificationDeliveryRecord {\n let payload = r.payload ?? {};\n if (typeof payload === 'string') {\n try { payload = JSON.parse(payload); } catch { payload = {}; }\n }\n return {\n id: r.id,\n notificationId: r.notification_id,\n recipientId: r.recipient_id,\n channel: r.channel,\n topic: r.topic ?? undefined,\n payload: payload as NotificationDeliveryRecord['payload'],\n organizationId: r.organization_id ?? undefined,\n partitionKey: r.partition_key,\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextAttemptAt: r.next_attempt_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n error: r.error ?? undefined,\n createdAt: r.created_at,\n updatedAt: r.updated_at,\n };\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { AckResult } from './outbox.js';\nimport type { SendResult, ErrorClass } from './channel.js';\n\n/**\n * Stable, framework-free partition hash (32-bit FNV-1a). Both the dispatcher\n * and the outbox `claim()` filter on it, so it must be a single shared helper.\n * Same implementation as `plugin-webhooks`.\n */\nexport function hashPartition(key: string, count: number): number {\n if (count <= 0) throw new Error('partition count must be > 0');\n let h = 0x811c9dc5;\n for (let i = 0; i < key.length; i++) {\n h ^= key.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return Math.abs(h | 0) % count;\n}\n\n/**\n * Exponential retry schedule with jitter. Returns the delay (ms) before the\n * next attempt given how many attempts have already happened, or `null` once\n * the budget is exhausted (→ dead).\n *\n * attempt 1 fails → ~1s · 2 → ~10s · 3 → ~1m · 4 → ~10m · 5 → ~1h · 6+ → dead\n */\nexport function nextRetryDelayMs(attemptsSoFar: number, rng: () => number = Math.random): number | null {\n const SCHEDULE = [1_000, 10_000, 60_000, 600_000, 3_600_000];\n if (attemptsSoFar < 1 || attemptsSoFar > SCHEDULE.length) return null;\n const base = SCHEDULE[attemptsSoFar - 1];\n const jitter = 0.8 + rng() * 0.4; // ∈ [0.8, 1.2)\n return Math.floor(base * jitter);\n}\n\n/**\n * Turn a channel `send()` outcome into an {@link AckResult}, applying the retry\n * schedule on retriable failures.\n *\n * - `ok` → success.\n * - `errorClass` of `permanent` → dead immediately (no point retrying).\n * - `errorClass` of `invalid_recipient` → suppressed (not our transport's fault).\n * - otherwise (retryable / unknown) → schedule a retry, or dead once the budget\n * is exhausted.\n */\nexport function classifyDeliveryAttempt(\n result: SendResult,\n errorClass: ErrorClass | undefined,\n attemptsSoFar: number,\n now: number = Date.now(),\n rng?: () => number,\n): AckResult {\n if (result.ok) return { success: true };\n\n if (errorClass === 'invalid_recipient') {\n return { success: false, error: result.error, suppressed: true };\n }\n if (errorClass === 'permanent') {\n return { success: false, error: result.error, dead: true };\n }\n\n const delay = nextRetryDelayMs(attemptsSoFar + 1, rng);\n if (delay === null) {\n return { success: false, error: result.error, dead: true };\n }\n return { success: false, error: result.error, nextAttemptAt: now + delay };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { MessagingChannel, MessagingChannelContext, Notification, SendResult } from './channel.js';\nimport type { INotificationOutbox, NotificationDeliveryRecord } from './outbox.js';\nimport { classifyDeliveryAttempt } from './backoff.js';\n\n/** Minimal channel-registry surface the dispatcher needs (MessagingService satisfies it). */\nexport interface ChannelRegistry {\n getChannel(id: string): MessagingChannel | undefined;\n}\n\n/** A held lock; `release()` frees it, `isHeld()`/`renew()` mirror the cluster API. */\nexport interface DispatchLockHandle {\n release(): Promise<void> | void;\n isHeld?(): boolean;\n renew?(ttlMs: number): Promise<void> | void;\n}\n\n/** Just the slice of `IClusterService` the dispatcher uses. */\nexport interface DispatchCluster {\n lock: {\n acquire(key: string, opts: { ttlMs: number; waitMs: number }): Promise<DispatchLockHandle | null>;\n };\n}\n\n/**\n * Single-node fallback lock — always grants. Used when no cluster service is\n * registered, so the dispatcher runs correctly (just without cross-node\n * coordination). The per-partition serialization a single process needs is\n * already provided by the `inflightTick` guard + the outbox's atomic claim.\n */\nconst SINGLE_NODE_CLUSTER: DispatchCluster = {\n lock: {\n async acquire() {\n return { release() {}, isHeld: () => true, renew() {} };\n },\n },\n};\n\nexport interface NotificationDispatcherLogger {\n warn: (msg: string, meta?: any) => void;\n info?: (msg: string, meta?: any) => void;\n}\n\nexport interface NotificationDispatcherOptions {\n nodeId: string;\n outbox: INotificationOutbox;\n channels: ChannelRegistry;\n /** Context handed to each channel's `send()` (logger). */\n channelContext: MessagingChannelContext;\n /** Cross-node coordination. Defaults to a single-node always-grant lock. */\n cluster?: DispatchCluster;\n partitionCount?: number;\n batchSize?: number;\n intervalMs?: number;\n lockTtlMs?: number;\n claimTtlMs?: number;\n rng?: () => number;\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n now?: () => number;\n logger?: NotificationDispatcherLogger;\n /** Observability hook fired after every attempt. */\n onAttempt?: (delivery: NotificationDeliveryRecord, success: boolean) => void;\n}\n\n/**\n * NotificationDispatcher (ADR-0030 P1) — drains the `sys_notification_delivery`\n * outbox and sends each row through its channel, retrying with backoff and\n * dead-lettering once the budget is exhausted. Structurally mirrors\n * `WebhookDispatcher`: an interval loop walks `partitionCount` partitions, each\n * guarded by a per-partition cluster lock; within a held partition it claims a\n * batch (`pending → in_flight`), sends, and acks.\n *\n * At-least-once: if a channel send succeeds but the ack write fails, the row\n * reverts to pending after the claim TTL and is re-sent — the inbox channel's\n * receipt write is idempotent-friendly, and downstream channels should be too.\n */\nexport class NotificationDispatcher {\n private readonly opts: Required<\n Omit<NotificationDispatcherOptions, 'rng' | 'logger' | 'onAttempt' | 'cluster' | 'now'>\n > &\n Pick<NotificationDispatcherOptions, 'rng' | 'logger' | 'onAttempt' | 'now'> & { cluster: DispatchCluster };\n private timer: ReturnType<typeof setInterval> | undefined;\n private running = false;\n private inflightTick: Promise<void> | undefined;\n\n constructor(options: NotificationDispatcherOptions) {\n const intervalMs = options.intervalMs ?? 500;\n const lockTtlMs = options.lockTtlMs ?? intervalMs * 5;\n this.opts = {\n nodeId: options.nodeId,\n outbox: options.outbox,\n channels: options.channels,\n channelContext: options.channelContext,\n cluster: options.cluster ?? SINGLE_NODE_CLUSTER,\n partitionCount: options.partitionCount ?? 8,\n batchSize: options.batchSize ?? 32,\n intervalMs,\n lockTtlMs,\n claimTtlMs: options.claimTtlMs ?? lockTtlMs * 2,\n rng: options.rng,\n now: options.now,\n logger: options.logger,\n onAttempt: options.onAttempt,\n };\n }\n\n /** Begin the periodic loop. Idempotent. */\n start(): void {\n if (this.running) return;\n this.running = true;\n this.scheduleTick();\n this.timer = setInterval(() => this.scheduleTick(), this.opts.intervalMs);\n // Don't keep the event loop alive solely for the dispatcher.\n (this.timer as { unref?: () => void })?.unref?.();\n }\n\n /** Stop the loop and drain the in-flight tick. */\n async stop(): Promise<void> {\n if (!this.running) return;\n this.running = false;\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = undefined;\n }\n if (this.inflightTick) {\n try { await this.inflightTick; } catch { /* already logged */ }\n }\n }\n\n /** Run one full tick (all partitions). Exposed for deterministic tests. */\n async tick(): Promise<void> {\n await this.runTick();\n }\n\n private scheduleTick(): void {\n if (this.inflightTick) return;\n this.inflightTick = this.runTick()\n .catch((err) => {\n this.opts.logger?.warn?.('notification-dispatcher: tick failed', {\n nodeId: this.opts.nodeId,\n error: (err as Error)?.message ?? String(err),\n });\n })\n .finally(() => { this.inflightTick = undefined; });\n }\n\n private async runTick(): Promise<void> {\n const count = this.opts.partitionCount;\n const offset = stableNodeOffset(this.opts.nodeId, count);\n for (let step = 0; step < count; step++) {\n await this.runPartition((offset + step) % count);\n }\n }\n\n private async runPartition(index: number): Promise<void> {\n const handle = await this.opts.cluster.lock.acquire(`notify.dispatcher.partition.${index}`, {\n ttlMs: this.opts.lockTtlMs,\n waitMs: 0,\n });\n if (!handle) return;\n try {\n const claimed = await this.opts.outbox.claim({\n nodeId: this.opts.nodeId,\n limit: this.opts.batchSize,\n partition: { index, count: this.opts.partitionCount },\n claimTtlMs: this.opts.claimTtlMs,\n });\n if (claimed.length === 0) return;\n await handle.renew?.(this.opts.lockTtlMs);\n for (const row of claimed) {\n if (handle.isHeld && !handle.isHeld()) break;\n await this.processRow(row);\n }\n } finally {\n await handle.release();\n }\n }\n\n private async processRow(row: NotificationDeliveryRecord): Promise<void> {\n const channel = this.opts.channels.getChannel(row.channel);\n if (!channel) {\n // No transport for this channel → terminal, observable on the row.\n await this.opts.outbox.ack(row.id, {\n success: false,\n error: `channel '${row.channel}' not registered`,\n dead: true,\n });\n this.opts.onAttempt?.(row, false);\n return;\n }\n\n const p = row.payload ?? {};\n const notification: Notification = {\n notificationId: row.notificationId,\n organizationId: row.organizationId,\n topic: row.topic,\n title: typeof p.title === 'string' ? p.title : row.topic ?? '',\n body: typeof p.body === 'string' ? p.body : '',\n severity: (p.severity as Notification['severity']) ?? 'info',\n recipients: [row.recipientId],\n channels: [row.channel],\n actionUrl: typeof p.actionUrl === 'string' ? p.actionUrl : undefined,\n payload: p,\n };\n\n let result: SendResult;\n try {\n result = await channel.send(this.opts.channelContext, {\n notification,\n channel: row.channel,\n recipient: row.recipientId,\n });\n } catch (err) {\n result = { ok: false, error: (err as Error)?.message ?? String(err) };\n }\n\n const errorClass = !result.ok && channel.classifyError ? channel.classifyError(result.error) : undefined;\n const now = this.opts.now?.() ?? Date.now();\n const ack = classifyDeliveryAttempt(result, errorClass, row.attempts, now, this.opts.rng);\n await this.opts.outbox.ack(row.id, ack);\n this.opts.onAttempt?.(row, result.ok);\n }\n}\n\n/** Spread the starting partition per node so contention rotates fairly. */\nfunction stableNodeOffset(nodeId: string, partitionCount: number): number {\n let h = 0;\n for (let i = 0; i < nodeId.length; i++) h = (h * 31 + nodeId.charCodeAt(i)) | 0;\n return Math.abs(h) % partitionCount;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport { NOTIFICATION_EVENT_OBJECT } from './messaging-service.js';\nimport { DELIVERY_OBJECT } from './sql-outbox.js';\nimport { INBOX_OBJECT, RECEIPT_OBJECT } from './inbox-channel.js';\n\n/** Minimal logger surface (matches the channel/service context). */\ninterface RetentionLogger {\n info(msg: string): void;\n warn(msg: string): void;\n}\n\n/**\n * One object the sweeper prunes, plus how to read its age. The pipeline stores\n * timestamps two ways: the event / inbox / receipt rows carry ISO-8601 strings\n * (`created_at`), while the delivery outbox rows carry epoch-ms numbers — so the\n * cutoff value is formatted per target. `$lt` works for both (ISO-8601 sorts\n * lexicographically; epoch-ms numerically).\n */\nexport interface RetentionTarget {\n readonly object: string;\n readonly tsField: string;\n readonly format: 'iso' | 'epoch';\n}\n\n/**\n * Default sweep set, ordered leaf-first (materializations/receipts/deliveries\n * before the event) so the log reads top-down even though there are no enforced\n * FKs. A notification ages out **wholesale** — event + delivery + materialization\n * + receipt past the cutoff are all removed — keeping the model consistent (no\n * dangling `notification_id`) and the bell (which only shows recent rows)\n * unaffected.\n */\nexport const DEFAULT_RETENTION_TARGETS: readonly RetentionTarget[] = [\n { object: RECEIPT_OBJECT, tsField: 'created_at', format: 'iso' },\n { object: INBOX_OBJECT, tsField: 'created_at', format: 'iso' },\n { object: DELIVERY_OBJECT, tsField: 'created_at', format: 'epoch' },\n { object: NOTIFICATION_EVENT_OBJECT, tsField: 'created_at', format: 'iso' },\n];\n\nexport interface NotificationRetentionOptions {\n /** Resolve the data engine; `undefined` ⇒ prune is a no-op. */\n getData(): IDataEngine | undefined;\n logger: RetentionLogger;\n /** Override the swept objects (tests / custom deployments). */\n targets?: readonly RetentionTarget[];\n /** Clock injection for deterministic tests. Defaults to `Date.now()`. */\n now?(): number;\n}\n\n/** Per-object prune outcome. `deleted` is `undefined` when the driver doesn't report a count. */\nexport interface PruneOutcome {\n object: string;\n deleted?: number;\n error?: string;\n}\n\n/**\n * Retention sweeper for the notification pipeline (ADR-0030 hardening).\n *\n * Every `emit()` writes a `sys_notification` event row (plus delivery /\n * materialization / receipt rows), so a high-frequency periodic flow grows the\n * tables unbounded. {@link prune} deletes everything older than a cutoff across\n * all {@link RetentionTarget}s in one bulk `delete` per object, under a system\n * context (cross-tenant: retention is an operator policy). Each target is\n * isolated — one object's failure is logged and the sweep continues.\n *\n * Retention is **opt-in**: the plugin runs this only when `retentionDays` is\n * configured. The mechanism lives here so it's testable in isolation.\n */\nexport class NotificationRetention {\n private readonly now: () => number;\n private readonly targets: readonly RetentionTarget[];\n\n constructor(private readonly opts: NotificationRetentionOptions) {\n this.now = opts.now ?? (() => Date.now());\n this.targets = opts.targets ?? DEFAULT_RETENTION_TARGETS;\n }\n\n /**\n * Delete pipeline rows older than `retentionDays`. Returns one outcome per\n * swept object. No-op (empty result) when no data engine is available or\n * `retentionDays` is not a positive number.\n */\n async prune(retentionDays: number): Promise<PruneOutcome[]> {\n const data = this.opts.getData();\n if (!data) {\n this.opts.logger.warn('[messaging] retention: no data engine; prune skipped');\n return [];\n }\n if (!(retentionDays > 0)) {\n this.opts.logger.warn(`[messaging] retention: invalid retentionDays=${retentionDays}; prune skipped`);\n return [];\n }\n\n const cutoffMs = this.now() - retentionDays * 86_400_000;\n const cutoffIso = new Date(cutoffMs).toISOString();\n const outcomes: PruneOutcome[] = [];\n\n for (const t of this.targets) {\n const cutoff = t.format === 'epoch' ? cutoffMs : cutoffIso;\n try {\n const res = await data.delete(t.object, {\n where: { [t.tsField]: { $lt: cutoff } },\n multi: true,\n // System context: retention is an operator policy that spans\n // tenants, so it must not be scoped by the caller's RLS.\n context: { isSystem: true },\n } as any);\n const deleted = countDeleted(res);\n outcomes.push({ object: t.object, deleted });\n if (deleted === undefined || deleted > 0) {\n this.opts.logger.info(\n `[messaging] retention: pruned ${deleted ?? '?'} ${t.object} rows older than ${cutoffIso}`,\n );\n }\n } catch (err) {\n const msg = (err as Error)?.message ?? String(err);\n this.opts.logger.warn(`[messaging] retention: prune of ${t.object} failed (${msg}); continuing`);\n outcomes.push({ object: t.object, error: msg });\n }\n }\n return outcomes;\n }\n}\n\n/** Best-effort row-count extraction from a driver's delete result. */\nfunction countDeleted(res: unknown): number | undefined {\n if (typeof res === 'number') return res;\n if (Array.isArray(res)) return res.length;\n if (res && typeof res === 'object') {\n const r = res as Record<string, unknown>;\n for (const k of ['deletedCount', 'deleted', 'count', 'affected', 'affectedRows']) {\n if (typeof r[k] === 'number') return r[k] as number;\n }\n }\n return undefined;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/** The object notification templates live in. */\nexport const TEMPLATE_OBJECT = 'sys_notification_template';\n\n/** Default locale used when a delivery carries none. */\nexport const DEFAULT_LOCALE = 'en';\n\n/** A loaded template row (the columns the renderer reads). */\nexport interface NotificationTemplateRow {\n subject?: string | null;\n body?: string | null;\n format?: 'markdown' | 'html' | 'text' | 'mjml' | null;\n}\n\n/** The rendered artifact a channel turns into its transport payload. */\nexport interface RenderedNotification {\n subject: string;\n /** Set when the template/format is HTML-ish (html/mjml). */\n html?: string;\n /** Set otherwise (markdown/text, or the no-template fallback). */\n text?: string;\n}\n\n/**\n * Render context: the event payload plus a few top-level conveniences so a\n * template can write `{{ title }}` as well as `{{ payload.title }}`.\n */\nexport interface RenderInput {\n topic: string;\n payload: Record<string, unknown>;\n /** Resolved title/body from the notification (fallback when no template). */\n title?: string;\n body?: string;\n}\n\nconst TOKEN = /\\{\\{\\s*([\\w.$]+)\\s*\\}\\}/g;\n\n/**\n * Declarative `{{ path.to.value }}` interpolation over a context object. No\n * logic, no conditionals — templates stay auditable metadata (a deliberate\n * low-code constraint). An unknown path renders to an empty string. This is the\n * single, linear-time substitution pass; it never evaluates template code.\n */\nexport function interpolate(template: string, context: Record<string, unknown>): string {\n if (!template) return '';\n return template.replace(TOKEN, (_m, path: string) => {\n const v = lookup(context, path);\n return v == null ? '' : String(v);\n });\n}\n\nfunction lookup(ctx: Record<string, unknown>, path: string): unknown {\n let cur: unknown = ctx;\n for (const key of path.split('.')) {\n if (cur == null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n}\n\n/**\n * Render a notification for a channel. With a template, `subject`/`body` are\n * interpolated and `body` is routed to `html` (html/mjml) or `text`\n * (markdown/text). With no template, fall back to the notification's\n * `title`/`body` (the P0/P1 behavior) as `subject`/`text`.\n */\nexport function renderNotification(\n template: NotificationTemplateRow | null | undefined,\n input: RenderInput,\n): RenderedNotification {\n const ctx: Record<string, unknown> = {\n ...input.payload,\n payload: input.payload,\n topic: input.topic,\n title: input.title ?? input.payload.title,\n body: input.body ?? input.payload.body,\n };\n\n if (template && (template.subject || template.body)) {\n const subject = interpolate(String(template.subject ?? ''), ctx) || String(ctx.title ?? input.topic);\n const renderedBody = interpolate(String(template.body ?? ''), ctx);\n const isHtml = template.format === 'html' || template.format === 'mjml';\n return isHtml ? { subject, html: renderedBody } : { subject, text: renderedBody };\n }\n\n // Generic fallback — no template for this (topic, channel, locale).\n return {\n subject: String(ctx.title ?? input.topic),\n text: String(ctx.body ?? ''),\n };\n}\n\nexport interface NotificationTemplateStoreOptions {\n getData(): IDataEngine | undefined;\n objectName?: string;\n}\n\n/**\n * Loads `sys_notification_template` rows by `(topic, channel, locale)`, with a\n * locale fallback (`en-US` → `en` → {@link DEFAULT_LOCALE}). Best-effort: no\n * data engine or a lookup error yields `null` (→ the renderer's generic\n * fallback), never a throw — a template outage must not block delivery.\n */\nexport class NotificationTemplateStore {\n private readonly objectName: string;\n constructor(private readonly opts: NotificationTemplateStoreOptions) {\n this.objectName = opts.objectName ?? TEMPLATE_OBJECT;\n }\n\n async load(topic: string, channel: string, locale?: string): Promise<NotificationTemplateRow | null> {\n const data = this.opts.getData();\n if (!data) return null;\n const candidates = localeCandidates(locale);\n for (const loc of candidates) {\n try {\n const row = await data.findOne(this.objectName, {\n where: { topic, channel, locale: loc, is_active: true },\n fields: ['subject', 'body', 'format'],\n });\n if (row) return row as NotificationTemplateRow;\n } catch {\n return null; // best-effort — fall back to generic rendering\n }\n }\n return null;\n }\n}\n\n/** `en-US` → ['en-US','en', DEFAULT_LOCALE]; dedups, keeps order. */\nfunction localeCandidates(locale?: string): string[] {\n const out: string[] = [];\n const push = (l?: string) => { if (l && !out.includes(l)) out.push(l); };\n push(locale);\n if (locale && locale.includes('-')) push(locale.split('-')[0]);\n push(DEFAULT_LOCALE);\n return out;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n Delivery,\n ErrorClass,\n MessagingChannel,\n MessagingChannelContext,\n SendResult,\n} from './channel.js';\nimport {\n NotificationTemplateStore,\n renderNotification,\n DEFAULT_LOCALE,\n} from './template-renderer.js';\n\n/** The user identity object a recipient id is resolved to an address against. */\nexport const USER_OBJECT = 'sys_user';\n\n/**\n * Structural view of the email service (`@objectstack/plugin-email`'s\n * `EmailService`), declared locally so service-messaging takes no runtime\n * dependency on it — the channel resolves whatever is registered under the\n * `email` service and sends through this shape (mirrors the `notify` node's\n * `MessagingServiceSurface` pattern).\n */\nexport interface EmailSenderSurface {\n send(input: {\n to: string | string[];\n subject: string;\n html?: string;\n text?: string;\n }): Promise<{ id?: string } | unknown>;\n}\n\nexport interface EmailChannelOptions {\n /** Resolve the email service; `undefined` ⇒ the channel no-ops (not installed). */\n getEmail(): EmailSenderSurface | undefined;\n /** Resolve the data engine (recipient address lookup). */\n getData(): IDataEngine | undefined;\n /** Template store for `(topic, 'email', locale)` rendering. */\n store: NotificationTemplateStore;\n /** User identity object override (default {@link USER_OBJECT}). */\n userObject?: string;\n /** Locale used when the delivery carries none (default {@link DEFAULT_LOCALE}). */\n defaultLocale?: string;\n}\n\nconst EMAIL_SHAPE = (s: string): boolean => {\n // Linear, non-backtracking \"looks like an email\" — same shape as the\n // recipient resolver's check (avoids the ReDoS-prone regex).\n if (!s || /\\s/.test(s)) return false;\n const at = s.indexOf('@');\n if (at <= 0 || at !== s.lastIndexOf('@') || at === s.length - 1) return false;\n const dot = s.slice(at + 1).indexOf('.');\n return dot > 0 && dot < s.length - at - 2;\n};\n\n/**\n * The `email` channel (ADR-0030 P3) — delivers a notification by email.\n *\n * It adds only the messaging semantics on top of the existing email transport\n * (ADR-0022 \"channel delegates transport to a sub-system\"): resolve the\n * recipient's address, render `(topic, 'email', locale)` from\n * `sys_notification_template` (fallback to `payload.title`/`body`), and hand the\n * subject/body to the `email` service. Retry/backoff/dead-letter come for free\n * from the P1 outbox dispatcher.\n *\n * Degrades like the inbox channel: no email service ⇒ logged no-op success\n * (capability not installed); a recipient with no resolvable address ⇒ a\n * reported failure (so the delivery row shows why).\n */\nexport function createEmailChannel(opts: EmailChannelOptions): MessagingChannel {\n const userObject = opts.userObject ?? USER_OBJECT;\n const defaultLocale = opts.defaultLocale ?? DEFAULT_LOCALE;\n\n async function resolveAddress(\n ctx: MessagingChannelContext,\n data: IDataEngine | undefined,\n recipient: string,\n ): Promise<string | undefined> {\n if (EMAIL_SHAPE(recipient)) return recipient; // already an address\n if (!data) return undefined;\n try {\n const user = await data.findOne(userObject, { where: { id: recipient }, fields: ['email'] });\n const email = user?.email;\n return typeof email === 'string' && EMAIL_SHAPE(email) ? email : undefined;\n } catch (err) {\n ctx.logger.warn(`[email] address lookup for '${recipient}' failed (${(err as Error).message})`);\n return undefined;\n }\n }\n\n return {\n id: 'email',\n\n async send(ctx: MessagingChannelContext, delivery: Delivery): Promise<SendResult> {\n const email = opts.getEmail();\n if (!email) {\n ctx.logger.warn(`[email] no email service registered; '${delivery.recipient}' not emailed`);\n return { ok: true }; // capability not installed — no-op, like inbox w/o data\n }\n\n const n = delivery.notification;\n const address = await resolveAddress(ctx, opts.getData(), delivery.recipient);\n if (!address) {\n return { ok: false, error: `no email address for recipient '${delivery.recipient}'` };\n }\n\n const payload = (n.payload ?? {}) as Record<string, unknown>;\n const locale = typeof payload.locale === 'string' ? payload.locale : defaultLocale;\n const template = await opts.store.load(n.topic ?? '', 'email', locale);\n const rendered = renderNotification(template, {\n topic: n.topic ?? '',\n payload,\n title: n.title,\n body: n.body,\n });\n\n try {\n const result: any = await email.send({\n to: address,\n subject: rendered.subject,\n ...(rendered.html !== undefined ? { html: rendered.html } : {}),\n ...(rendered.text !== undefined ? { text: rendered.text } : {}),\n });\n const id = result?.id;\n return { ok: true, externalId: id != null ? String(id) : undefined };\n } catch (err) {\n return { ok: false, error: `email send failed: ${(err as Error).message}` };\n }\n },\n\n classifyError(_err: unknown): ErrorClass {\n return 'retryable';\n },\n };\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_inbox_message` — user-facing in-app notification rows (ADR-0012 §4, §11;\n * ADR-0030 Layer 5).\n *\n * Written by the always-on `inbox` messaging channel, one row per\n * `(notification, recipient)`, as the **materialization** of an L2\n * `sys_notification` event delivered to the in-app channel. The Console bell\n * pulls these (ADR-0030 re-points the bell here from `sys_notification`);\n * `service-realtime` decides when to ping an online user. Belongs to\n * `service-messaging` per the \"protocol + service ownership\" pattern.\n *\n * Read-state is **not** stored here — it lives in `sys_notification_receipt`\n * (ADR-0030), so cross-channel read semantics stay reachable. `notification_id`\n * links back to the event; `delivery_id` links to the outbox row (P1).\n */\nexport const InboxMessage = ObjectSchema.create({\n name: 'sys_inbox_message',\n label: 'Inbox Message',\n pluralLabel: 'Inbox Messages',\n icon: 'inbox',\n description: 'User-facing in-app notification rows materialized by the inbox messaging channel.',\n titleFormat: '{title}',\n compactLayout: ['title', 'user_id', 'severity', 'created_at'],\n\n listViews: {\n mine: {\n type: 'grid',\n name: 'mine',\n label: 'Notifications',\n data: { provider: 'object', object: 'sys_inbox_message' },\n columns: ['title', 'topic', 'severity', 'created_at'],\n filter: [{ field: 'user_id', operator: 'equals', value: '{current_user_id}' }],\n sort: [{ field: 'created_at', order: 'desc' }],\n pagination: { pageSize: 50 },\n emptyState: { title: 'Inbox zero', message: 'No notifications.' },\n },\n },\n\n fields: {\n id: Field.text({\n label: 'Inbox Message ID',\n required: true,\n readonly: true,\n }),\n\n user_id: Field.text({\n label: 'Recipient User',\n required: true,\n searchable: true,\n }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n searchable: true,\n description: 'FK → sys_notification (the L2 event this row materializes)',\n }),\n\n delivery_id: Field.text({\n label: 'Delivery',\n description: 'FK → sys_notification_delivery (outbox row); null until P1',\n }),\n\n topic: Field.text({\n label: 'Topic',\n searchable: true,\n }),\n\n title: Field.text({\n label: 'Title',\n required: true,\n }),\n\n body_md: Field.markdown({\n label: 'Body',\n }),\n\n severity: Field.select({\n label: 'Severity',\n options: [\n { label: 'Info', value: 'info' },\n { label: 'Warning', value: 'warning' },\n { label: 'Critical', value: 'critical' },\n ],\n }),\n\n action_url: Field.text({\n label: 'Action URL',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n readonly: true,\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_receipt` — per-recipient × channel delivery receipt\n * (ADR-0030 Layer 5).\n *\n * The single source of truth for **read-state**. When a channel materializes a\n * delivery it writes a `delivered` receipt; the UI flips it to `read` /\n * `clicked` / `dismissed`. Keeping read-state here (rather than on\n * `sys_inbox_message`) makes cross-channel semantics reachable later — e.g.\n * \"clicked the email → mark the inbox row read\" is a receipt update, not a\n * second source of truth.\n *\n * Keyed by `(notification_id, user_id, channel)`. `delivery_id` links to the\n * `sys_notification_delivery` outbox row once that lands (P1); it is nullable\n * in P0 where the inbox channel dispatches inline.\n *\n * Belongs to `service-messaging` (the owner of the materialization channels).\n */\nexport const NotificationReceipt = ObjectSchema.create({\n name: 'sys_notification_receipt',\n label: 'Notification Receipt',\n pluralLabel: 'Notification Receipts',\n icon: 'check-check',\n isSystem: true,\n managedBy: 'system',\n description: 'Per-recipient × channel receipt; the source of truth for notification read-state.',\n titleFormat: '{state}',\n compactLayout: ['notification_id', 'user_id', 'channel', 'state', 'at'],\n\n fields: {\n id: Field.text({\n label: 'Receipt ID',\n required: true,\n readonly: true,\n }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n required: true,\n searchable: true,\n description: 'FK → sys_notification (L2 event)',\n }),\n\n delivery_id: Field.text({\n label: 'Delivery',\n required: false,\n description: 'FK → sys_notification_delivery (outbox row); null until P1',\n }),\n\n user_id: Field.text({\n label: 'Recipient User',\n required: true,\n searchable: true,\n }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n description: 'Channel id this receipt is for (inbox / email / push / …)',\n }),\n\n state: Field.select(['delivered', 'read', 'clicked', 'dismissed'], {\n label: 'State',\n required: true,\n defaultValue: 'delivered',\n }),\n\n at: Field.datetime({\n label: 'At',\n required: false,\n description: 'When the receipt reached its current state',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n readonly: true,\n }),\n },\n\n indexes: [\n { fields: ['notification_id', 'user_id', 'channel'], unique: true },\n { fields: ['user_id', 'state'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_delivery` — the durable outbox (ADR-0030 Layer 4).\n *\n * One row per `(event × recipient × channel)`. The spine of reliable delivery:\n * `emit()` writes rows in `pending`; the `NotificationDispatcher` claims them\n * (`pending → in_flight`), sends via the channel, and acks the outcome\n * (`success` / back to `pending` with a `next_attempt_at` for retry / `dead`\n * once the retry budget is exhausted). Mirrors `sys_webhook_delivery`.\n *\n * `payload` snapshots the rendered notification content at enqueue time so a\n * later edit of the L2 event can't rewrite an in-flight delivery (and so the\n * dispatcher needs no second read to send). Timestamps are epoch ms.\n */\nexport const NotificationDelivery = ObjectSchema.create({\n name: 'sys_notification_delivery',\n label: 'Notification Delivery',\n pluralLabel: 'Notification Deliveries',\n icon: 'send',\n isSystem: true,\n managedBy: 'system',\n description: 'Durable per-recipient × channel delivery outbox (ADR-0030 Layer 4).',\n titleFormat: '{channel} → {recipient_id}',\n compactLayout: ['notification_id', 'recipient_id', 'channel', 'status', 'attempts'],\n\n fields: {\n id: Field.text({ label: 'Delivery ID', required: true, readonly: true }),\n\n notification_id: Field.text({\n label: 'Notification Event',\n required: true,\n searchable: true,\n description: 'FK → sys_notification (L2 event)',\n }),\n recipient_id: Field.text({ label: 'Recipient User', required: true, searchable: true }),\n channel: Field.text({ label: 'Channel', required: true }),\n topic: Field.text({ label: 'Topic', searchable: true }),\n\n payload: Field.json({\n label: 'Payload',\n description: 'Snapshot of the rendered notification content for dispatch.',\n }),\n\n status: Field.select(['pending', 'in_flight', 'success', 'failed', 'dead', 'suppressed'], {\n label: 'Status',\n required: true,\n defaultValue: 'pending',\n }),\n\n attempts: Field.number({ label: 'Attempts', defaultValue: 0 }),\n partition_key: Field.number({ label: 'Partition Key', defaultValue: 0 }),\n\n claimed_by: Field.text({ label: 'Claimed By', description: 'Node id while in_flight' }),\n claimed_at: Field.number({ label: 'Claimed At (ms)' }),\n next_attempt_at: Field.number({ label: 'Next Attempt At (ms)' }),\n last_attempted_at: Field.number({ label: 'Last Attempted At (ms)' }),\n error: Field.textarea({ label: 'Error' }),\n\n created_at: Field.number({ label: 'Created At (ms)', readonly: true }),\n updated_at: Field.number({ label: 'Updated At (ms)' }),\n },\n\n indexes: [\n // Dedup: one delivery per (event, recipient, channel).\n { fields: ['notification_id', 'recipient_id', 'channel'], unique: true },\n // The hot claim query.\n { fields: ['status', 'partition_key', 'next_attempt_at'] },\n // Stale-in_flight reaper.\n { fields: ['status', 'claimed_at'] },\n { fields: ['notification_id'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_preference` — per-user × topic × channel delivery toggle\n * (ADR-0030 Layer 3).\n *\n * The mute/allow matrix the preference filter consults before fan-out. A row\n * declares whether `user_id` wants `topic` on `channel`. Resolution is\n * most-specific-wins with wildcards:\n *\n * (user, topic, channel) → (user, topic, *) → (user, *, channel) →\n * (user, *, *) → ('*', topic, channel) → … → ('*', '*', '*') → default ON\n *\n * `user_id = '*'` rows are the **admin global default**; a real-user row\n * **overrides** it. `topic = '*'` / `channel = '*'` are wildcards. Mandatory\n * topics (configured on the service) bypass this object entirely.\n *\n * Belongs to `service-messaging` (owner of the delivery pipeline).\n */\nexport const NotificationPreference = ObjectSchema.create({\n name: 'sys_notification_preference',\n label: 'Notification Preference',\n pluralLabel: 'Notification Preferences',\n icon: 'bell-ring',\n isSystem: true,\n managedBy: 'system',\n description: 'Per-user × topic × channel notification toggle (mute/allow), with admin-global defaults.',\n titleFormat: '{user_id} · {topic} · {channel}',\n compactLayout: ['user_id', 'topic', 'channel', 'enabled', 'digest'],\n\n fields: {\n id: Field.text({ label: 'Preference ID', required: true, readonly: true }),\n\n user_id: Field.text({\n label: 'User',\n required: true,\n searchable: true,\n description: \"Recipient user id, or '*' for the admin-global default.\",\n }),\n\n topic: Field.text({\n label: 'Topic',\n required: true,\n searchable: true,\n defaultValue: '*',\n description: \"Notification topic, or '*' for all topics.\",\n }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n defaultValue: '*',\n description: \"Channel id (inbox/email/push/…), or '*' for all channels.\",\n }),\n\n enabled: Field.boolean({\n label: 'Enabled',\n defaultValue: true,\n description: 'When false, this (user, topic, channel) is muted.',\n }),\n\n digest: Field.select(['none', 'daily', 'weekly'], {\n label: 'Digest',\n required: false,\n defaultValue: 'none',\n description: 'Batch cadence (P3 digest middleware).',\n }),\n\n quiet_hours: Field.json({\n label: 'Quiet Hours',\n required: false,\n description: 'Optional { tz, start, end } window (P3 quiet-hours middleware).',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: false }),\n },\n\n indexes: [\n { fields: ['user_id', 'topic', 'channel'], unique: true },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_subscription` — who is subscribed to a topic (ADR-0030\n * Layer 3).\n *\n * Declares standing interest in a `topic` by a `principal` (`role:x`, `team:x`,\n * `user:id`, or a bare user id). Where a producer emits with `audience:\n * 'subscribers'` (or no explicit audience), the resolver expands the topic's\n * subscriptions into recipients — the opt-in counterpart to the explicit\n * audience most producers pass today.\n *\n * Distinct from `sys_notification_preference`: a subscription says \"include me\n * for this topic\"; a preference says \"but mute it on this channel\".\n *\n * Belongs to `service-messaging`.\n */\nexport const NotificationSubscription = ObjectSchema.create({\n name: 'sys_notification_subscription',\n label: 'Notification Subscription',\n pluralLabel: 'Notification Subscriptions',\n icon: 'rss',\n isSystem: true,\n managedBy: 'system',\n description: 'Standing subscription of a principal (role/team/user) to a notification topic.',\n titleFormat: '{principal} · {topic}',\n compactLayout: ['topic', 'principal', 'enabled', 'created_at'],\n\n fields: {\n id: Field.text({ label: 'Subscription ID', required: true, readonly: true }),\n\n topic: Field.text({\n label: 'Topic',\n required: true,\n searchable: true,\n description: 'Notification topic this principal subscribes to.',\n }),\n\n principal: Field.text({\n label: 'Principal',\n required: true,\n searchable: true,\n description: \"Subscriber selector: 'role:x' | 'team:x' | 'user:id' | bare user id.\",\n }),\n\n enabled: Field.boolean({\n label: 'Enabled',\n defaultValue: true,\n description: 'When false, the subscription is inactive.',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n },\n\n indexes: [\n { fields: ['topic', 'principal'], unique: true },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * `sys_notification_template` — channel-agnostic render template (ADR-0030\n * cross-cutting / P3).\n *\n * One row per `(topic, channel, locale)` carrying the `subject`/`body` a channel\n * renders from the event `payload` (declarative `{{ payload.x }}` interpolation\n * — see `template-renderer.ts`). `format` tells the channel how to treat `body`\n * (markdown/html/text). When no template matches, channels fall back to\n * `payload.title` / `payload.body` (the P0/P1 behavior), so templates are purely\n * additive.\n *\n * Studio-configurable (contributed to the Setup → Configuration nav). Belongs to\n * `service-messaging`.\n */\nexport const NotificationTemplate = ObjectSchema.create({\n name: 'sys_notification_template',\n label: 'Notification Template',\n pluralLabel: 'Notification Templates',\n icon: 'file-text',\n isSystem: true,\n managedBy: 'system',\n description: 'Per (topic × channel × locale) render template for notifications.',\n titleFormat: '{topic} · {channel} · {locale}',\n compactLayout: ['topic', 'channel', 'locale', 'is_active'],\n\n fields: {\n id: Field.text({ label: 'Template ID', required: true, readonly: true }),\n\n topic: Field.text({ label: 'Topic', required: true, searchable: true }),\n\n channel: Field.text({\n label: 'Channel',\n required: true,\n defaultValue: 'email',\n description: 'Channel id this template renders for (email/inbox/push/…).',\n }),\n\n locale: Field.text({\n label: 'Locale',\n required: true,\n defaultValue: 'en',\n description: \"BCP-47 locale, e.g. 'en' / 'en-US' / 'zh-CN'.\",\n }),\n\n version: Field.number({\n label: 'Version',\n required: false,\n defaultValue: 1,\n }),\n\n subject: Field.text({\n label: 'Subject / Title',\n required: false,\n description: 'Rendered into the email subject / inbox title. Supports {{ payload.x }}.',\n }),\n\n body: Field.markdown({\n label: 'Body',\n required: false,\n description: 'Template body. Supports {{ payload.x }}. Interpreted per `format`.',\n }),\n\n format: Field.select(['markdown', 'html', 'text', 'mjml'], {\n label: 'Body Format',\n required: false,\n defaultValue: 'markdown',\n }),\n\n is_active: Field.boolean({\n label: 'Active',\n defaultValue: true,\n description: 'Only active templates are selected at render time.',\n }),\n\n created_at: Field.datetime({ label: 'Created At', readonly: true }),\n updated_at: Field.datetime({ label: 'Updated At', required: false }),\n },\n\n indexes: [\n { fields: ['topic', 'channel', 'locale'] },\n { fields: ['topic'] },\n ],\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueDeliveryInput,\n INotificationOutbox,\n NotificationDeliveryRecord,\n} from './outbox.js';\nimport { hashPartition } from './backoff.js';\n\n/**\n * In-memory {@link INotificationOutbox} — the test/minimal-stack backend.\n * Single-process, so `claim()` is trivially atomic (no DB round-trips). Same\n * semantics as {@link SqlNotificationOutbox} for dedup, reaping, and acks.\n */\nexport class MemoryNotificationOutbox implements INotificationOutbox {\n private readonly rows = new Map<string, NotificationDeliveryRecord>();\n\n constructor(\n private readonly partitionCount = 8,\n /** Injectable clock (ms) for deterministic tests. Defaults to Date.now. */\n private readonly clock: () => number = () => Date.now(),\n ) {}\n\n async enqueue(input: EnqueueDeliveryInput): Promise<string> {\n for (const r of this.rows.values()) {\n if (\n r.notificationId === input.notificationId &&\n r.recipientId === input.recipientId &&\n r.channel === input.channel\n ) {\n return r.id; // dedup\n }\n }\n const id = randomUUID();\n const now = this.clock();\n this.rows.set(id, {\n id,\n notificationId: input.notificationId,\n recipientId: input.recipientId,\n channel: input.channel,\n topic: input.topic,\n payload: input.payload ?? {},\n organizationId: input.organizationId,\n partitionKey: hashPartition(input.notificationId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n // Deferred dispatch (quiet-hours, P3): claim() skips pending rows\n // whose nextAttemptAt is still in the future.\n nextAttemptAt: input.notBefore,\n createdAt: now,\n updatedAt: now,\n });\n return id;\n }\n\n async claim(opts: ClaimOptions): Promise<NotificationDeliveryRecord[]> {\n const now = opts.now ?? this.clock();\n // Reap stale in_flight.\n for (const r of this.rows.values()) {\n if (r.status === 'in_flight' && (r.claimedAt ?? 0) < now - opts.claimTtlMs) {\n r.status = 'pending';\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n }\n }\n const out: NotificationDeliveryRecord[] = [];\n for (const r of this.rows.values()) {\n if (out.length >= opts.limit) break;\n if (r.status !== 'pending') continue;\n if (opts.partition && r.partitionKey !== opts.partition.index) continue;\n if (r.nextAttemptAt != null && r.nextAttemptAt > now) continue;\n r.status = 'in_flight';\n r.claimedBy = opts.nodeId;\n r.claimedAt = now;\n r.updatedAt = now;\n out.push({ ...r });\n }\n return out;\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n const r = this.rows.get(id);\n if (!r) return;\n const now = this.clock();\n r.attempts += 1;\n r.lastAttemptedAt = now;\n r.claimedBy = undefined;\n r.claimedAt = undefined;\n r.updatedAt = now;\n if (result.success) {\n r.status = 'success';\n r.nextAttemptAt = undefined;\n r.error = undefined;\n } else if (result.suppressed) {\n r.status = 'suppressed';\n r.error = result.error;\n } else if (result.dead) {\n r.status = 'dead';\n r.error = result.error;\n } else {\n r.status = 'pending';\n r.nextAttemptAt = result.nextAttemptAt;\n r.error = result.error;\n }\n }\n\n async list(filter?: { status?: DeliveryStatus; notificationId?: string }): Promise<NotificationDeliveryRecord[]> {\n let rows = [...this.rows.values()];\n if (filter?.status) rows = rows.filter((r) => r.status === filter.status);\n if (filter?.notificationId) rows = rows.filter((r) => r.notificationId === filter.notificationId);\n return rows.map((r) => ({ ...r }));\n }\n}\n"],"mappings":"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","names":["USER_OBJECT","import_node_crypto","msg","deliveries","delivered","msg","USER_OBJECT","import_data","import_data","import_data","import_data","import_data","import_node_crypto"]}