import { type CommonReservedFields, type NoReservedFields, defaultGqlPermission, defaultPermission, } from "@tailor-platform/erp-kit/core"; import { db, type TailorAnyDBField } from "@tailor-platform/sdk"; import { notificationEventLifecycle } from "./notificationEvent.lifecycle.generated"; /** Processing states of a NotificationEvent ingress row (PENDING, DISPATCHED, NO_DELIVERY). */ export const NOTIFICATION_EVENT_STATUSES = notificationEventLifecycle.states; const builtins = { eventType: db .string() .description("Event discriminator resolved to a NotificationCategory at dispatch time"), sourceType: db .string() .description("Polymorphic discriminator of the source aggregate (e.g. PURCHASE_ORDER)"), sourceId: db.string().description("Polymorphic id of the source aggregate (no FK constraint)"), actorUserId: db .uuid({ optional: true }) .description("User who caused the event (used for actor self-suppression)"), payload: db.string().description("Stringified JSON snapshot: recipient hints + template vars"), payloadHash: db.string().description("Stable hash of the normalized payload (idempotency)"), status: db .enum(NOTIFICATION_EVENT_STATUSES) .description("Dispatch lifecycle: PENDING -> DISPATCHED | NO_DELIVERY"), dispatchedAt: db .datetime({ optional: true }) .description("When dispatch completed (null while PENDING)"), }; type ReservedFields = keyof typeof builtins | CommonReservedFields; export interface CreateNotificationEventTypeParams> { fields?: NoReservedFields; } /** * NotificationEvent — append-only ingress record for the notification pipeline. * * Source modules append one PENDING row per domain change (in the same * transaction as the change). The insert is observed by the * `dispatch-notification-events` executor (CDC `recordCreatedTrigger`) which * drives `dispatchNotification`. The event is the trigger surface, not the * delivery engine — see the notification-delivery feature. * * Idempotency boundary: `(eventType, sourceType, sourceId, payloadHash)`. * `eventType` / `sourceType` are stored as opaque strings (no module-owned * vocabulary); the dispatcher resolves `eventType` against the Event Catalog. */ export function createNotificationEventType>( params: CreateNotificationEventTypeParams, ) { return db .table("NotificationEvent", { ...builtins, ...((params.fields ?? {}) as F), ...db.fields.timestamps(), }) .indexes({ fields: ["eventType", "sourceType", "sourceId", "payloadHash"], unique: true, name: "notification_event_idempotency_idx", }) .permission(defaultPermission) .gqlPermission(defaultGqlPermission); } export const notificationEvent = createNotificationEventType({});