45 lines
1.9 KiB
TypeScript
45 lines
1.9 KiB
TypeScript
import { index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core';
|
|
import { channelLinks } from './crm';
|
|
import { users } from './identity';
|
|
|
|
/**
|
|
* External delivery is a separate durable concern from the CRM transaction.
|
|
* A deal write only adds a row here; a provider-specific worker owns network
|
|
* failure, retry and completion without holding the request thread open.
|
|
*/
|
|
export const notificationOutbox = pgTable(
|
|
'notification_outbox',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
provider: text('provider').notNull(),
|
|
kind: text('kind').notNull(),
|
|
linkId: uuid('link_id').references(() => channelLinks.id, { onDelete: 'set null' }),
|
|
workspaceId: text('workspace_id'),
|
|
destination: text('destination').notNull(),
|
|
payload: jsonb('payload').$type<Record<string, unknown>>().notNull(),
|
|
idempotencyKey: text('idempotency_key').notNull(),
|
|
|
|
status: text('status').notNull().default('pending'),
|
|
attempts: integer('attempts').notNull().default(0),
|
|
maxAttempts: integer('max_attempts').notNull().default(5),
|
|
dueAt: timestamp('due_at', { withTimezone: true }).notNull().defaultNow(),
|
|
leasedUntil: timestamp('leased_until', { withTimezone: true }),
|
|
leasedBy: text('leased_by'),
|
|
deliveredAt: timestamp('delivered_at', { withTimezone: true }),
|
|
externalId: text('external_id'),
|
|
error: text('error'),
|
|
|
|
requestedByUserId: uuid('requested_by_user_id').references(() => users.id, {
|
|
onDelete: 'set null',
|
|
}),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(t) => [
|
|
uniqueIndex('notification_outbox_idempotency_key').on(t.idempotencyKey),
|
|
index('notification_outbox_claimable_idx').on(t.provider, t.status, t.dueAt),
|
|
index('notification_outbox_link_idx').on(t.linkId),
|
|
],
|
|
);
|
|
|
|
export type NotificationOutboxItem = typeof notificationOutbox.$inferSelect;
|