import { type CommonReservedFields, type NoReservedFields, defaultGqlPermission, defaultPermission, } from "@tailor-platform/erp-kit/core"; import { db, type TailorAnyDBField } from "@tailor-platform/sdk"; import { destinationDeliveryLogLifecycle } from "./destinationDeliveryLog.lifecycle.generated"; /** Post-attempt lifecycle states of a DestinationDeliveryLog (PENDING, SENT, FAILED). */ export const DESTINATION_DELIVERY_LOG_STATUSES = destinationDeliveryLogLifecycle.states; const builtins = { eventType: db.string().description("Event type key resolved at plan time"), sourceType: db .string() .description("Polymorphic source-record discriminator of the originating event"), sourceId: db.string().description("Polymorphic source-record id of the originating event"), targetType: db .string() .description("Destination binding target type (from ChannelRoutingBinding)"), targetId: db.string().description("Destination binding target id (from ChannelRoutingBinding)"), channelId: db.string().description("DESTINATION channel key the post was routed to (e.g. SLACK)"), externalChannelRef: db .string() .description("Opaque destination reference posted to (e.g. a Slack channel id)"), idempotencyKey: db .string() .description( "Event logical-identity key (eventType:sourceType:sourceId:payloadHash); dedup is keyed on (idempotencyKey, channelId, externalChannelRef) so a re-planned event reuses this log instead of double-posting", ), subject: db .string() .description("Rendered subject captured at plan time, posted by the delivery worker"), body: db .string() .description("Rendered body captured at plan time, posted by the delivery worker"), status: db .enum(DESTINATION_DELIVERY_LOG_STATUSES) .description("Post attempt lifecycle status (PENDING, SENT, FAILED)"), providerMessageId: db .string({ optional: true }) .description("Provider-side message id (e.g. the Slack ts)"), providerResponse: db .string({ optional: true }) .description( "Redacted provider response { ok, ts, channel, error }; cleared by the retention sweep", ), failureReason: db .string({ optional: true }) .description("Normalized failure reason; cleared by the retention sweep"), retryCount: db.int().description("Retry counter"), attemptedAt: db.datetime().description("Attempt timestamp"), completedAt: db.datetime({ optional: true }).description("Terminal completion timestamp"), }; type ReservedFields = keyof typeof builtins | CommonReservedFields; export interface CreateDestinationDeliveryLogTypeParams< F extends Record, > { fields?: NoReservedFields; } export function createDestinationDeliveryLogType>( params: CreateDestinationDeliveryLogTypeParams, ) { return ( db .table("DestinationDeliveryLog", { ...builtins, ...((params.fields ?? {}) as F), ...db.fields.timestamps(), }) // Close the GraphQL gateway's auto-CRUD entirely. The dispatcher writes // these rows through the in-transaction db handle (not the gateway), so // leaving auto-CRUD open only risks cross-tenant read exposure; closing // it removes that exposure without losing any functionality. .features({ gqlOperations: { create: false, read: false, update: false, delete: false }, }) .permission(defaultPermission) .gqlPermission(defaultGqlPermission) ); } export const destinationDeliveryLog = createDestinationDeliveryLogType({});