@tanstack/workflow-store-drizzle-postgres 0.0.1 → 0.0.3

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.
package/src/store.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  import { LogConflictError } from '@tanstack/workflow-core'
2
2
  import { sql } from 'drizzle-orm'
3
+ import {
4
+ getDrizzlePostgresWorkflowStoreSchemaStatements,
5
+ qualifiedTableName,
6
+ resolveDrizzlePostgresWorkflowStoreTables,
7
+ } from './schema-contract'
3
8
  import type { RunState, WorkflowEvent } from '@tanstack/workflow-core'
4
9
  import type { SQL } from 'drizzle-orm'
10
+ import type { DrizzlePostgresWorkflowStoreTables } from './schema-contract'
5
11
  import type {
6
12
  AppendEventsArgs,
7
13
  AppendEventsResult,
@@ -50,17 +56,6 @@ export interface DrizzlePostgresDatabase {
50
56
  ) => Promise<TResult>
51
57
  }
52
58
 
53
- export interface DrizzlePostgresWorkflowStoreTables {
54
- runs: string
55
- runStates: string
56
- eventLocks: string
57
- events: string
58
- timers: string
59
- signalDeliveries: string
60
- schedules: string
61
- scheduleBuckets: string
62
- }
63
-
64
59
  export interface DrizzlePostgresWorkflowStoreOptions {
65
60
  db: DrizzlePostgresDatabase
66
61
  schema?: string
@@ -72,31 +67,19 @@ export type DrizzlePostgresWorkflowStore = WorkflowExecutionStore &
72
67
  ensureSchema: () => Promise<void>
73
68
  }
74
69
 
75
- export const defaultDrizzlePostgresWorkflowStoreTables: DrizzlePostgresWorkflowStoreTables =
76
- {
77
- runs: 'workflow_runs',
78
- runStates: 'workflow_run_states',
79
- eventLocks: 'workflow_event_locks',
80
- events: 'workflow_events',
81
- timers: 'workflow_timers',
82
- signalDeliveries: 'workflow_signal_deliveries',
83
- schedules: 'workflow_schedules',
84
- scheduleBuckets: 'workflow_schedule_buckets',
85
- }
86
-
87
70
  export function createDrizzlePostgresWorkflowStore(
88
71
  options: DrizzlePostgresWorkflowStoreOptions,
89
72
  ): DrizzlePostgresWorkflowStore {
90
- const tableNames = {
91
- ...defaultDrizzlePostgresWorkflowStoreTables,
92
- ...options.tables,
93
- }
73
+ const tableNames = resolveDrizzlePostgresWorkflowStoreTables(options.tables)
94
74
  const tableSql = tableSqls(options.schema, tableNames)
95
75
  const db = options.db
96
76
 
97
77
  return {
98
78
  async ensureSchema() {
99
- for (const statement of schemaStatements(options.schema, tableNames)) {
79
+ for (const statement of getDrizzlePostgresWorkflowStoreSchemaStatements({
80
+ schema: options.schema,
81
+ tables: tableNames,
82
+ })) {
100
83
  await db.execute(sql.raw(statement))
101
84
  }
102
85
  },
@@ -165,6 +148,7 @@ export function createDrizzlePostgresWorkflowStore(
165
148
  input,
166
149
  output,
167
150
  error,
151
+ awaiting,
168
152
  waiting_for,
169
153
  pending_approval,
170
154
  created_at,
@@ -178,6 +162,7 @@ export function createDrizzlePostgresWorkflowStore(
178
162
  ${encodeJson(state.input)}::jsonb,
179
163
  ${encodeJsonOrNull(state.output)}::jsonb,
180
164
  ${encodeJsonOrNull(state.error)}::jsonb,
165
+ ${encodeJsonOrNull(state.awaiting)}::jsonb,
181
166
  ${encodeJsonOrNull(state.waitingFor)}::jsonb,
182
167
  ${encodeJsonOrNull(state.pendingApproval)}::jsonb,
183
168
  ${state.createdAt},
@@ -190,6 +175,7 @@ export function createDrizzlePostgresWorkflowStore(
190
175
  input = excluded.input,
191
176
  output = excluded.output,
192
177
  error = excluded.error,
178
+ awaiting = excluded.awaiting,
193
179
  waiting_for = excluded.waiting_for,
194
180
  pending_approval = excluded.pending_approval,
195
181
  created_at = excluded.created_at,
@@ -204,6 +190,7 @@ export function createDrizzlePostgresWorkflowStore(
204
190
  input,
205
191
  output,
206
192
  error,
193
+ awaiting,
207
194
  waiting_for,
208
195
  pending_approval,
209
196
  wake_at,
@@ -218,6 +205,7 @@ export function createDrizzlePostgresWorkflowStore(
218
205
  ${encodeJson(state.input)}::jsonb,
219
206
  ${encodeJsonOrNull(state.output)}::jsonb,
220
207
  ${encodeJsonOrNull(state.error)}::jsonb,
208
+ ${encodeJsonOrNull(state.awaiting)}::jsonb,
221
209
  ${encodeJsonOrNull(state.waitingFor)}::jsonb,
222
210
  ${encodeJsonOrNull(state.pendingApproval)}::jsonb,
223
211
  ${
@@ -235,6 +223,7 @@ export function createDrizzlePostgresWorkflowStore(
235
223
  input = excluded.input,
236
224
  output = excluded.output,
237
225
  error = excluded.error,
226
+ awaiting = excluded.awaiting,
238
227
  waiting_for = excluded.waiting_for,
239
228
  pending_approval = excluded.pending_approval,
240
229
  wake_at = excluded.wake_at,
@@ -402,6 +391,7 @@ export function createDrizzlePostgresWorkflowStore(
402
391
  update ${tableSql.runs}
403
392
  set
404
393
  status = 'paused',
394
+ awaiting = ${encodeJsonOrNull(args.awaiting)}::jsonb,
405
395
  waiting_for = ${encodeJsonOrNull(args.waitingFor)}::jsonb,
406
396
  pending_approval = ${encodeJsonOrNull(args.pendingApproval)}::jsonb,
407
397
  wake_at = ${args.wakeAt ?? null},
@@ -418,6 +408,7 @@ export function createDrizzlePostgresWorkflowStore(
418
408
  set
419
409
  status = 'finished',
420
410
  output = ${encodeJson(args.output)}::jsonb,
411
+ awaiting = null,
421
412
  waiting_for = null,
422
413
  pending_approval = null,
423
414
  wake_at = null,
@@ -435,6 +426,7 @@ export function createDrizzlePostgresWorkflowStore(
435
426
  set
436
427
  status = 'errored',
437
428
  error = ${encodeJson(args.error)}::jsonb,
429
+ awaiting = null,
438
430
  waiting_for = null,
439
431
  pending_approval = null,
440
432
  wake_at = null,
@@ -524,7 +516,7 @@ export function createDrizzlePostgresWorkflowStore(
524
516
  )
525
517
  if (existingDelivery) return { kind: 'duplicate', run }
526
518
 
527
- if (run.waitingFor?.signalName !== args.delivery.name) {
519
+ if (!isRunWaitingForSignal(run, args.delivery)) {
528
520
  return { kind: 'not-waiting', run }
529
521
  }
530
522
 
@@ -549,6 +541,7 @@ export function createDrizzlePostgresWorkflowStore(
549
541
  update ${tableSql.runs}
550
542
  set
551
543
  status = 'queued',
544
+ awaiting = null,
552
545
  waiting_for = null,
553
546
  pending_approval = null,
554
547
  wake_at = null,
@@ -578,7 +571,7 @@ export function createDrizzlePostgresWorkflowStore(
578
571
  )
579
572
  if (existingDelivery) return { kind: 'duplicate', run }
580
573
 
581
- if (run.pendingApproval?.approvalId !== args.approval.approvalId) {
574
+ if (!isRunWaitingForApproval(run, args.approval)) {
582
575
  return { kind: 'not-waiting', run }
583
576
  }
584
577
 
@@ -597,6 +590,7 @@ export function createDrizzlePostgresWorkflowStore(
597
590
  update ${tableSql.runs}
598
591
  set
599
592
  status = 'queued',
593
+ awaiting = null,
600
594
  waiting_for = null,
601
595
  pending_approval = null,
602
596
  wake_at = null,
@@ -650,12 +644,26 @@ export function createDrizzlePostgresWorkflowStore(
650
644
  const schedules = await queryRows<ScheduleRow>(
651
645
  db,
652
646
  sql`
653
- select *
654
- from ${tableSql.schedules}
655
- where enabled = true
656
- and next_fire_at is not null
657
- and next_fire_at <= ${args.now}
658
- order by next_fire_at asc, schedule_id asc
647
+ select schedule.*
648
+ from ${tableSql.schedules} schedule
649
+ left join ${tableSql.scheduleBuckets} bucket
650
+ on bucket.schedule_id = schedule.schedule_id
651
+ and bucket.bucket_id = schedule.next_fire_at::text
652
+ where schedule.enabled = true
653
+ and schedule.next_fire_at is not null
654
+ and schedule.next_fire_at <= ${args.now}
655
+ and (
656
+ bucket.schedule_id is null
657
+ or (
658
+ bucket.status <> 'started'
659
+ and (
660
+ bucket.lease_owner is null
661
+ or bucket.lease_owner = ${args.leaseOwner}
662
+ or bucket.lease_expires_at <= ${args.now}
663
+ )
664
+ )
665
+ )
666
+ order by schedule.next_fire_at asc, schedule.schedule_id asc
659
667
  limit ${args.limit}
660
668
  `,
661
669
  )
@@ -810,6 +818,7 @@ interface RunRow {
810
818
  input: unknown
811
819
  output: unknown
812
820
  error: unknown
821
+ awaiting: unknown
813
822
  waiting_for: unknown
814
823
  pending_approval: unknown
815
824
  wake_at: number | string | null
@@ -827,6 +836,7 @@ interface RunStateRow {
827
836
  input: unknown
828
837
  output: unknown
829
838
  error: unknown
839
+ awaiting: unknown
830
840
  waiting_for: unknown
831
841
  pending_approval: unknown
832
842
  created_at: number | string
@@ -890,122 +900,6 @@ function tableSqls(
890
900
  }
891
901
  }
892
902
 
893
- function schemaStatements(
894
- schema: string | undefined,
895
- tables: DrizzlePostgresWorkflowStoreTables,
896
- ): Array<string> {
897
- const runs = qualifiedTableName(schema, tables.runs)
898
- const runStates = qualifiedTableName(schema, tables.runStates)
899
- const eventLocks = qualifiedTableName(schema, tables.eventLocks)
900
- const events = qualifiedTableName(schema, tables.events)
901
- const timers = qualifiedTableName(schema, tables.timers)
902
- const signalDeliveries = qualifiedTableName(schema, tables.signalDeliveries)
903
- const schedules = qualifiedTableName(schema, tables.schedules)
904
- const scheduleBuckets = qualifiedTableName(schema, tables.scheduleBuckets)
905
-
906
- return [
907
- ...(schema ? [`create schema if not exists ${quoteIdent(schema)}`] : []),
908
- `create table if not exists ${runs} (
909
- run_id text primary key,
910
- workflow_id text not null,
911
- workflow_version text,
912
- status text not null,
913
- input jsonb not null,
914
- output jsonb,
915
- error jsonb,
916
- waiting_for jsonb,
917
- pending_approval jsonb,
918
- wake_at bigint,
919
- lease_owner text,
920
- lease_expires_at bigint,
921
- created_at bigint not null,
922
- updated_at bigint not null
923
- )`,
924
- `create index if not exists ${quoteIdent(`${tables.runs}_status_idx`)}
925
- on ${runs} (status, updated_at)`,
926
- `create index if not exists ${quoteIdent(`${tables.runs}_lease_idx`)}
927
- on ${runs} (status, lease_expires_at)`,
928
- `create table if not exists ${runStates} (
929
- run_id text primary key,
930
- workflow_id text not null,
931
- workflow_version text,
932
- status text not null,
933
- input jsonb not null,
934
- output jsonb,
935
- error jsonb,
936
- waiting_for jsonb,
937
- pending_approval jsonb,
938
- created_at bigint not null,
939
- updated_at bigint not null
940
- )`,
941
- `create table if not exists ${eventLocks} (
942
- run_id text primary key,
943
- created_at bigint not null
944
- )`,
945
- `create table if not exists ${events} (
946
- run_id text not null,
947
- event_index integer not null,
948
- event_type text not null,
949
- step_id text,
950
- event jsonb not null,
951
- created_at bigint not null,
952
- primary key (run_id, event_index)
953
- )`,
954
- `create index if not exists ${quoteIdent(`${tables.events}_type_idx`)}
955
- on ${events} (run_id, event_type)`,
956
- `create table if not exists ${timers} (
957
- run_id text not null,
958
- signal_id text not null,
959
- workflow_id text not null,
960
- workflow_version text,
961
- wake_at bigint not null,
962
- lease_owner text,
963
- lease_expires_at bigint,
964
- primary key (run_id, signal_id)
965
- )`,
966
- `create index if not exists ${quoteIdent(`${tables.timers}_due_idx`)}
967
- on ${timers} (wake_at, lease_expires_at)`,
968
- `create table if not exists ${signalDeliveries} (
969
- run_id text not null,
970
- signal_id text not null,
971
- created_at bigint not null,
972
- primary key (run_id, signal_id)
973
- )`,
974
- `create table if not exists ${schedules} (
975
- schedule_id text primary key,
976
- workflow_id text not null,
977
- workflow_version text,
978
- schedule jsonb not null,
979
- overlap_policy text not null,
980
- input jsonb,
981
- next_fire_at bigint,
982
- enabled boolean not null,
983
- updated_at bigint not null
984
- )`,
985
- `create index if not exists ${quoteIdent(`${tables.schedules}_due_idx`)}
986
- on ${schedules} (enabled, next_fire_at)`,
987
- `create table if not exists ${scheduleBuckets} (
988
- schedule_id text not null,
989
- bucket_id text not null,
990
- workflow_id text not null,
991
- workflow_version text,
992
- run_id text not null,
993
- fire_at bigint not null,
994
- input jsonb,
995
- overlap_policy text not null,
996
- status text not null,
997
- lease_owner text,
998
- lease_expires_at bigint,
999
- started_at bigint,
1000
- primary key (schedule_id, bucket_id)
1001
- )`,
1002
- `create index if not exists ${quoteIdent(
1003
- `${tables.scheduleBuckets}_lease_idx`,
1004
- )}
1005
- on ${scheduleBuckets} (status, fire_at, lease_expires_at)`,
1006
- ]
1007
- }
1008
-
1009
903
  async function loadRunById(
1010
904
  db: DrizzlePostgresDatabase,
1011
905
  tables: TableSqls,
@@ -1144,6 +1038,7 @@ function runFromRow(row: RunRow): WorkflowExecution {
1144
1038
  input: decodeJson(row.input),
1145
1039
  output: decodeJsonOrUndefined(row.output),
1146
1040
  error: decodeJsonOrUndefined(row.error),
1041
+ awaiting: decodeJsonOrUndefined(row.awaiting),
1147
1042
  waitingFor: decodeJsonOrUndefined(row.waiting_for),
1148
1043
  pendingApproval: decodeJsonOrUndefined(row.pending_approval),
1149
1044
  wakeAt: numberOrUndefined(row.wake_at),
@@ -1168,6 +1063,7 @@ function runStateFromRow(row: RunStateRow): RunState {
1168
1063
  input: decodeJson(row.input),
1169
1064
  output: decodeJsonOrUndefined(row.output),
1170
1065
  error: decodeJsonOrUndefined(row.error),
1066
+ awaiting: decodeJsonOrUndefined(row.awaiting),
1171
1067
  waitingFor: decodeJsonOrUndefined(row.waiting_for),
1172
1068
  pendingApproval: decodeJsonOrUndefined(row.pending_approval),
1173
1069
  createdAt: Number(row.created_at),
@@ -1196,6 +1092,54 @@ function timerFromRow(row: TimerRow): TimerWakeup {
1196
1092
  }
1197
1093
  }
1198
1094
 
1095
+ function isRunWaitingForSignal(
1096
+ run: WorkflowExecution,
1097
+ delivery: DeliverSignalArgs['delivery'],
1098
+ ) {
1099
+ return (
1100
+ signalAwaitableMatches(run.waitingFor, delivery) ||
1101
+ run.awaiting?.some(
1102
+ (awaitable) =>
1103
+ awaitable.type === 'signal' &&
1104
+ signalAwaitableMatches(awaitable, delivery),
1105
+ ) === true
1106
+ )
1107
+ }
1108
+
1109
+ function signalAwaitableMatches(
1110
+ awaitable:
1111
+ | NonNullable<WorkflowExecution['waitingFor']>
1112
+ | Extract<
1113
+ NonNullable<WorkflowExecution['awaiting']>[number],
1114
+ {
1115
+ type: 'signal'
1116
+ }
1117
+ >
1118
+ | undefined,
1119
+ delivery: DeliverSignalArgs['delivery'],
1120
+ ) {
1121
+ return (
1122
+ awaitable?.signalName === delivery.name &&
1123
+ (delivery.stepId === undefined ||
1124
+ awaitable.stepId === undefined ||
1125
+ awaitable.stepId === delivery.stepId)
1126
+ )
1127
+ }
1128
+
1129
+ function isRunWaitingForApproval(
1130
+ run: WorkflowExecution,
1131
+ approval: DeliverApprovalArgs['approval'],
1132
+ ) {
1133
+ return (
1134
+ run.pendingApproval?.approvalId === approval.approvalId ||
1135
+ run.awaiting?.some(
1136
+ (awaitable) =>
1137
+ awaitable.type === 'approval' &&
1138
+ awaitable.approvalId === approval.approvalId,
1139
+ ) === true
1140
+ )
1141
+ }
1142
+
1199
1143
  function scheduleFromRow(row: ScheduleRow) {
1200
1144
  return {
1201
1145
  scheduleId: row.schedule_id satisfies ScheduleId,
@@ -1227,6 +1171,7 @@ function toRunSummary(row: RunRow): RunSummary {
1227
1171
  workflowId: run.workflowId,
1228
1172
  workflowVersion: run.workflowVersion,
1229
1173
  status: run.status,
1174
+ awaiting: run.awaiting,
1230
1175
  waitingFor: run.waitingFor,
1231
1176
  pendingApproval: run.pendingApproval,
1232
1177
  wakeAt: run.wakeAt,
@@ -1266,13 +1211,3 @@ function numberOrUndefined(value: number | string | null) {
1266
1211
  function getStepId(event: WorkflowEvent) {
1267
1212
  return 'stepId' in event ? event.stepId : undefined
1268
1213
  }
1269
-
1270
- function qualifiedTableName(schema: string | undefined, table: string) {
1271
- return schema
1272
- ? `${quoteIdent(schema)}.${quoteIdent(table)}`
1273
- : quoteIdent(table)
1274
- }
1275
-
1276
- function quoteIdent(identifier: string) {
1277
- return `"${identifier.replaceAll('"', '""')}"`
1278
- }
package/src/tables.ts ADDED
@@ -0,0 +1,149 @@
1
+ import {
2
+ bigint,
3
+ boolean,
4
+ index,
5
+ integer,
6
+ jsonb,
7
+ pgTable,
8
+ primaryKey,
9
+ text,
10
+ } from 'drizzle-orm/pg-core'
11
+
12
+ export const workflowSchemaMigrations = pgTable('workflow_schema_migrations', {
13
+ migrationId: text('migration_id').primaryKey(),
14
+ packageName: text('package_name').notNull(),
15
+ packageVersion: text('package_version'),
16
+ appliedAt: bigint('applied_at', { mode: 'number' }).notNull(),
17
+ })
18
+
19
+ export const workflowRuns = pgTable(
20
+ 'workflow_runs',
21
+ {
22
+ runId: text('run_id').primaryKey(),
23
+ workflowId: text('workflow_id').notNull(),
24
+ workflowVersion: text('workflow_version'),
25
+ status: text('status').notNull(),
26
+ input: jsonb('input').notNull(),
27
+ output: jsonb('output'),
28
+ error: jsonb('error'),
29
+ awaiting: jsonb('awaiting'),
30
+ waitingFor: jsonb('waiting_for'),
31
+ pendingApproval: jsonb('pending_approval'),
32
+ wakeAt: bigint('wake_at', { mode: 'number' }),
33
+ leaseOwner: text('lease_owner'),
34
+ leaseExpiresAt: bigint('lease_expires_at', { mode: 'number' }),
35
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
36
+ updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
37
+ },
38
+ (table) => [
39
+ index('workflow_runs_status_idx').on(table.status, table.updatedAt),
40
+ index('workflow_runs_lease_idx').on(table.status, table.leaseExpiresAt),
41
+ ],
42
+ )
43
+
44
+ export const workflowRunStates = pgTable('workflow_run_states', {
45
+ runId: text('run_id').primaryKey(),
46
+ workflowId: text('workflow_id').notNull(),
47
+ workflowVersion: text('workflow_version'),
48
+ status: text('status').notNull(),
49
+ input: jsonb('input').notNull(),
50
+ output: jsonb('output'),
51
+ error: jsonb('error'),
52
+ awaiting: jsonb('awaiting'),
53
+ waitingFor: jsonb('waiting_for'),
54
+ pendingApproval: jsonb('pending_approval'),
55
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
56
+ updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
57
+ })
58
+
59
+ export const workflowEventLocks = pgTable('workflow_event_locks', {
60
+ runId: text('run_id').primaryKey(),
61
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
62
+ })
63
+
64
+ export const workflowEvents = pgTable(
65
+ 'workflow_events',
66
+ {
67
+ runId: text('run_id').notNull(),
68
+ eventIndex: integer('event_index').notNull(),
69
+ eventType: text('event_type').notNull(),
70
+ stepId: text('step_id'),
71
+ event: jsonb('event').notNull(),
72
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
73
+ },
74
+ (table) => [
75
+ primaryKey({ columns: [table.runId, table.eventIndex] }),
76
+ index('workflow_events_type_idx').on(table.runId, table.eventType),
77
+ ],
78
+ )
79
+
80
+ export const workflowTimers = pgTable(
81
+ 'workflow_timers',
82
+ {
83
+ runId: text('run_id').notNull(),
84
+ signalId: text('signal_id').notNull(),
85
+ workflowId: text('workflow_id').notNull(),
86
+ workflowVersion: text('workflow_version'),
87
+ wakeAt: bigint('wake_at', { mode: 'number' }).notNull(),
88
+ leaseOwner: text('lease_owner'),
89
+ leaseExpiresAt: bigint('lease_expires_at', { mode: 'number' }),
90
+ },
91
+ (table) => [
92
+ primaryKey({ columns: [table.runId, table.signalId] }),
93
+ index('workflow_timers_due_idx').on(table.wakeAt, table.leaseExpiresAt),
94
+ ],
95
+ )
96
+
97
+ export const workflowSignalDeliveries = pgTable(
98
+ 'workflow_signal_deliveries',
99
+ {
100
+ runId: text('run_id').notNull(),
101
+ signalId: text('signal_id').notNull(),
102
+ createdAt: bigint('created_at', { mode: 'number' }).notNull(),
103
+ },
104
+ (table) => [primaryKey({ columns: [table.runId, table.signalId] })],
105
+ )
106
+
107
+ export const workflowSchedules = pgTable(
108
+ 'workflow_schedules',
109
+ {
110
+ scheduleId: text('schedule_id').primaryKey(),
111
+ workflowId: text('workflow_id').notNull(),
112
+ workflowVersion: text('workflow_version'),
113
+ schedule: jsonb('schedule').notNull(),
114
+ overlapPolicy: text('overlap_policy').notNull(),
115
+ input: jsonb('input'),
116
+ nextFireAt: bigint('next_fire_at', { mode: 'number' }),
117
+ enabled: boolean('enabled').notNull(),
118
+ updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
119
+ },
120
+ (table) => [
121
+ index('workflow_schedules_due_idx').on(table.enabled, table.nextFireAt),
122
+ ],
123
+ )
124
+
125
+ export const workflowScheduleBuckets = pgTable(
126
+ 'workflow_schedule_buckets',
127
+ {
128
+ scheduleId: text('schedule_id').notNull(),
129
+ bucketId: text('bucket_id').notNull(),
130
+ workflowId: text('workflow_id').notNull(),
131
+ workflowVersion: text('workflow_version'),
132
+ runId: text('run_id').notNull(),
133
+ fireAt: bigint('fire_at', { mode: 'number' }).notNull(),
134
+ input: jsonb('input'),
135
+ overlapPolicy: text('overlap_policy').notNull(),
136
+ status: text('status').notNull(),
137
+ leaseOwner: text('lease_owner'),
138
+ leaseExpiresAt: bigint('lease_expires_at', { mode: 'number' }),
139
+ startedAt: bigint('started_at', { mode: 'number' }),
140
+ },
141
+ (table) => [
142
+ primaryKey({ columns: [table.scheduleId, table.bucketId] }),
143
+ index('workflow_schedule_buckets_lease_idx').on(
144
+ table.status,
145
+ table.fireAt,
146
+ table.leaseExpiresAt,
147
+ ),
148
+ ],
149
+ )