@tanstack/workflow-store-drizzle-postgres 0.0.1

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/dist/store.cjs ADDED
@@ -0,0 +1,935 @@
1
+ let _tanstack_workflow_core = require("@tanstack/workflow-core");
2
+ let drizzle_orm = require("drizzle-orm");
3
+
4
+ //#region src/store.ts
5
+ const defaultDrizzlePostgresWorkflowStoreTables = {
6
+ runs: "workflow_runs",
7
+ runStates: "workflow_run_states",
8
+ eventLocks: "workflow_event_locks",
9
+ events: "workflow_events",
10
+ timers: "workflow_timers",
11
+ signalDeliveries: "workflow_signal_deliveries",
12
+ schedules: "workflow_schedules",
13
+ scheduleBuckets: "workflow_schedule_buckets"
14
+ };
15
+ function createDrizzlePostgresWorkflowStore(options) {
16
+ const tableNames = {
17
+ ...defaultDrizzlePostgresWorkflowStoreTables,
18
+ ...options.tables
19
+ };
20
+ const tableSql = tableSqls(options.schema, tableNames);
21
+ const db = options.db;
22
+ return {
23
+ async ensureSchema() {
24
+ for (const statement of schemaStatements(options.schema, tableNames)) await db.execute(drizzle_orm.sql.raw(statement));
25
+ },
26
+ async createRun(args) {
27
+ const rows = await queryRows(db, drizzle_orm.sql`
28
+ insert into ${tableSql.runs} (
29
+ run_id,
30
+ workflow_id,
31
+ workflow_version,
32
+ status,
33
+ input,
34
+ created_at,
35
+ updated_at
36
+ )
37
+ values (
38
+ ${args.runId},
39
+ ${args.workflowId},
40
+ ${args.workflowVersion ?? null},
41
+ 'queued',
42
+ ${encodeJson(args.input)}::jsonb,
43
+ ${args.now},
44
+ ${args.now}
45
+ )
46
+ on conflict (run_id) do nothing
47
+ returning *
48
+ `);
49
+ if (rows[0]) return {
50
+ kind: "created",
51
+ run: runFromRow(rows[0])
52
+ };
53
+ const existing = await loadRunById(db, tableSql, args.runId);
54
+ if (!existing) throw new Error(`Run "${args.runId}" was not inserted or loaded.`);
55
+ return {
56
+ kind: "existing",
57
+ run: existing
58
+ };
59
+ },
60
+ loadRun(runId) {
61
+ return loadRunById(db, tableSql, runId);
62
+ },
63
+ async loadExecution(runId) {
64
+ const run = await loadRunById(db, tableSql, runId);
65
+ if (!run) return void 0;
66
+ return {
67
+ run,
68
+ events: await readStoredEvents(db, tableSql, { runId })
69
+ };
70
+ },
71
+ async loadRunState(runId) {
72
+ return loadRunStateById(db, tableSql, runId);
73
+ },
74
+ async saveRunState(args) {
75
+ const state = args.state;
76
+ await withTransaction(db, async (tx) => {
77
+ await tx.execute(drizzle_orm.sql`
78
+ insert into ${tableSql.runStates} (
79
+ run_id,
80
+ workflow_id,
81
+ workflow_version,
82
+ status,
83
+ input,
84
+ output,
85
+ error,
86
+ waiting_for,
87
+ pending_approval,
88
+ created_at,
89
+ updated_at
90
+ )
91
+ values (
92
+ ${state.runId},
93
+ ${state.workflowId},
94
+ ${state.workflowVersion ?? null},
95
+ ${state.status},
96
+ ${encodeJson(state.input)}::jsonb,
97
+ ${encodeJsonOrNull(state.output)}::jsonb,
98
+ ${encodeJsonOrNull(state.error)}::jsonb,
99
+ ${encodeJsonOrNull(state.waitingFor)}::jsonb,
100
+ ${encodeJsonOrNull(state.pendingApproval)}::jsonb,
101
+ ${state.createdAt},
102
+ ${state.updatedAt}
103
+ )
104
+ on conflict (run_id) do update set
105
+ workflow_id = excluded.workflow_id,
106
+ workflow_version = excluded.workflow_version,
107
+ status = excluded.status,
108
+ input = excluded.input,
109
+ output = excluded.output,
110
+ error = excluded.error,
111
+ waiting_for = excluded.waiting_for,
112
+ pending_approval = excluded.pending_approval,
113
+ created_at = excluded.created_at,
114
+ updated_at = excluded.updated_at
115
+ `);
116
+ await tx.execute(drizzle_orm.sql`
117
+ insert into ${tableSql.runs} (
118
+ run_id,
119
+ workflow_id,
120
+ workflow_version,
121
+ status,
122
+ input,
123
+ output,
124
+ error,
125
+ waiting_for,
126
+ pending_approval,
127
+ wake_at,
128
+ created_at,
129
+ updated_at
130
+ )
131
+ values (
132
+ ${state.runId},
133
+ ${state.workflowId},
134
+ ${state.workflowVersion ?? null},
135
+ ${state.status},
136
+ ${encodeJson(state.input)}::jsonb,
137
+ ${encodeJsonOrNull(state.output)}::jsonb,
138
+ ${encodeJsonOrNull(state.error)}::jsonb,
139
+ ${encodeJsonOrNull(state.waitingFor)}::jsonb,
140
+ ${encodeJsonOrNull(state.pendingApproval)}::jsonb,
141
+ ${state.waitingFor?.signalName === "__timer" ? state.waitingFor.deadline : null},
142
+ ${state.createdAt},
143
+ ${state.updatedAt}
144
+ )
145
+ on conflict (run_id) do update set
146
+ workflow_id = excluded.workflow_id,
147
+ workflow_version = excluded.workflow_version,
148
+ status = excluded.status,
149
+ input = excluded.input,
150
+ output = excluded.output,
151
+ error = excluded.error,
152
+ waiting_for = excluded.waiting_for,
153
+ pending_approval = excluded.pending_approval,
154
+ wake_at = excluded.wake_at,
155
+ created_at = excluded.created_at,
156
+ updated_at = excluded.updated_at
157
+ `);
158
+ });
159
+ },
160
+ async deleteRun(runId, _reason) {
161
+ await withTransaction(db, async (tx) => {
162
+ await tx.execute(drizzle_orm.sql`
163
+ delete from ${tableSql.runStates}
164
+ where run_id = ${runId}
165
+ `);
166
+ await tx.execute(drizzle_orm.sql`
167
+ delete from ${tableSql.eventLocks}
168
+ where run_id = ${runId}
169
+ `);
170
+ await tx.execute(drizzle_orm.sql`
171
+ delete from ${tableSql.signalDeliveries}
172
+ where run_id = ${runId}
173
+ `);
174
+ await tx.execute(drizzle_orm.sql`
175
+ delete from ${tableSql.timers}
176
+ where run_id = ${runId}
177
+ `);
178
+ await tx.execute(drizzle_orm.sql`
179
+ delete from ${tableSql.events}
180
+ where run_id = ${runId}
181
+ `);
182
+ await tx.execute(drizzle_orm.sql`
183
+ delete from ${tableSql.runs}
184
+ where run_id = ${runId}
185
+ `);
186
+ });
187
+ },
188
+ async appendEvents(args) {
189
+ return withTransaction(db, async (tx) => {
190
+ await tx.execute(drizzle_orm.sql`
191
+ insert into ${tableSql.eventLocks} (run_id, created_at)
192
+ values (${args.runId}, ${Date.now()})
193
+ on conflict (run_id) do nothing
194
+ `);
195
+ await queryRows(tx, drizzle_orm.sql`
196
+ select run_id
197
+ from ${tableSql.eventLocks}
198
+ where run_id = ${args.runId}
199
+ for update
200
+ `);
201
+ const countRows = await queryRows(tx, drizzle_orm.sql`
202
+ select count(*)::int as count
203
+ from ${tableSql.events}
204
+ where run_id = ${args.runId}
205
+ `);
206
+ if (Number(countRows[0]?.count ?? 0) !== args.expectedNextIndex) {
207
+ const conflict = await queryRows(tx, drizzle_orm.sql`
208
+ select *
209
+ from ${tableSql.events}
210
+ where run_id = ${args.runId}
211
+ and event_index = ${args.expectedNextIndex}
212
+ limit 1
213
+ `);
214
+ throw new _tanstack_workflow_core.LogConflictError(args.runId, args.expectedNextIndex, conflict[0] ? eventFromRow(conflict[0]).event : void 0);
215
+ }
216
+ let nextIndex = args.expectedNextIndex;
217
+ for (const event of args.events) {
218
+ await tx.execute(drizzle_orm.sql`
219
+ insert into ${tableSql.events} (
220
+ run_id,
221
+ event_index,
222
+ event_type,
223
+ step_id,
224
+ event,
225
+ created_at
226
+ )
227
+ values (
228
+ ${args.runId},
229
+ ${nextIndex},
230
+ ${event.type},
231
+ ${getStepId(event) ?? null},
232
+ ${encodeJson(event)}::jsonb,
233
+ ${event.ts}
234
+ )
235
+ `);
236
+ nextIndex++;
237
+ }
238
+ return { nextIndex };
239
+ });
240
+ },
241
+ readEvents(args) {
242
+ return readStoredEvents(db, tableSql, args);
243
+ },
244
+ async claimRun(args) {
245
+ const rows = await queryRows(db, drizzle_orm.sql`
246
+ update ${tableSql.runs}
247
+ set
248
+ status = 'running',
249
+ lease_owner = ${args.leaseOwner},
250
+ lease_expires_at = ${args.now + args.leaseMs},
251
+ updated_at = ${args.now}
252
+ where run_id = ${args.runId}
253
+ and status not in ('finished', 'errored', 'aborted')
254
+ and (
255
+ lease_owner is null
256
+ or lease_owner = ${args.leaseOwner}
257
+ or lease_expires_at <= ${args.now}
258
+ )
259
+ returning *
260
+ `);
261
+ if (rows[0]) return {
262
+ kind: "claimed",
263
+ run: runFromRow(rows[0])
264
+ };
265
+ const run = await loadRunById(db, tableSql, args.runId);
266
+ return run ? {
267
+ kind: "not-claimable",
268
+ run
269
+ } : { kind: "not-found" };
270
+ },
271
+ async heartbeatRunLease(args) {
272
+ await db.execute(drizzle_orm.sql`
273
+ update ${tableSql.runs}
274
+ set
275
+ lease_expires_at = ${args.now + args.leaseMs},
276
+ updated_at = ${args.now}
277
+ where run_id = ${args.runId}
278
+ and lease_owner = ${args.leaseOwner}
279
+ `);
280
+ },
281
+ async releaseRunLease(args) {
282
+ await db.execute(drizzle_orm.sql`
283
+ update ${tableSql.runs}
284
+ set
285
+ lease_owner = null,
286
+ lease_expires_at = null
287
+ where run_id = ${args.runId}
288
+ and lease_owner = ${args.leaseOwner}
289
+ `);
290
+ },
291
+ async markRunPaused(args) {
292
+ await db.execute(drizzle_orm.sql`
293
+ update ${tableSql.runs}
294
+ set
295
+ status = 'paused',
296
+ waiting_for = ${encodeJsonOrNull(args.waitingFor)}::jsonb,
297
+ pending_approval = ${encodeJsonOrNull(args.pendingApproval)}::jsonb,
298
+ wake_at = ${args.wakeAt ?? null},
299
+ lease_owner = null,
300
+ lease_expires_at = null,
301
+ updated_at = ${args.now}
302
+ where run_id = ${args.runId}
303
+ `);
304
+ },
305
+ async markRunFinished(args) {
306
+ await db.execute(drizzle_orm.sql`
307
+ update ${tableSql.runs}
308
+ set
309
+ status = 'finished',
310
+ output = ${encodeJson(args.output)}::jsonb,
311
+ waiting_for = null,
312
+ pending_approval = null,
313
+ wake_at = null,
314
+ lease_owner = null,
315
+ lease_expires_at = null,
316
+ updated_at = ${args.now}
317
+ where run_id = ${args.runId}
318
+ `);
319
+ },
320
+ async markRunErrored(args) {
321
+ args.code;
322
+ await db.execute(drizzle_orm.sql`
323
+ update ${tableSql.runs}
324
+ set
325
+ status = 'errored',
326
+ error = ${encodeJson(args.error)}::jsonb,
327
+ waiting_for = null,
328
+ pending_approval = null,
329
+ wake_at = null,
330
+ lease_owner = null,
331
+ lease_expires_at = null,
332
+ updated_at = ${args.now}
333
+ where run_id = ${args.runId}
334
+ `);
335
+ },
336
+ async scheduleTimer(args) {
337
+ await withTransaction(db, async (tx) => {
338
+ await tx.execute(drizzle_orm.sql`
339
+ insert into ${tableSql.timers} (
340
+ run_id,
341
+ signal_id,
342
+ workflow_id,
343
+ workflow_version,
344
+ wake_at
345
+ )
346
+ values (
347
+ ${args.runId},
348
+ ${args.signalId},
349
+ ${args.workflowId},
350
+ ${args.workflowVersion ?? null},
351
+ ${args.wakeAt}
352
+ )
353
+ on conflict (run_id, signal_id) do update set
354
+ workflow_id = excluded.workflow_id,
355
+ workflow_version = excluded.workflow_version,
356
+ wake_at = excluded.wake_at,
357
+ lease_owner = null,
358
+ lease_expires_at = null
359
+ `);
360
+ await tx.execute(drizzle_orm.sql`
361
+ update ${tableSql.runs}
362
+ set
363
+ wake_at = ${args.wakeAt},
364
+ updated_at = ${args.now}
365
+ where run_id = ${args.runId}
366
+ `);
367
+ });
368
+ },
369
+ async claimDueTimers(args) {
370
+ return (await queryRows(db, drizzle_orm.sql`
371
+ with due as (
372
+ select run_id, signal_id
373
+ from ${tableSql.timers}
374
+ where wake_at <= ${args.now}
375
+ and (
376
+ lease_owner is null
377
+ or lease_owner = ${args.leaseOwner}
378
+ or lease_expires_at <= ${args.now}
379
+ )
380
+ order by wake_at asc, run_id asc, signal_id asc
381
+ limit ${args.limit}
382
+ for update skip locked
383
+ )
384
+ update ${tableSql.timers} timer
385
+ set
386
+ lease_owner = ${args.leaseOwner},
387
+ lease_expires_at = ${args.now + args.leaseMs}
388
+ from due
389
+ where timer.run_id = due.run_id
390
+ and timer.signal_id = due.signal_id
391
+ returning timer.*
392
+ `)).map(timerFromRow);
393
+ },
394
+ async deliverSignal(args) {
395
+ return withTransaction(db, async (tx) => {
396
+ const run = await loadRunById(tx, tableSql, args.runId);
397
+ if (!run) return { kind: "not-found" };
398
+ if (await loadSignalDelivery(tx, tableSql, args.runId, args.delivery.signalId)) return {
399
+ kind: "duplicate",
400
+ run
401
+ };
402
+ if (run.waitingFor?.signalName !== args.delivery.name) return {
403
+ kind: "not-waiting",
404
+ run
405
+ };
406
+ if (!await insertSignalDelivery(tx, tableSql, args.runId, args.delivery.signalId, args.now)) return {
407
+ kind: "duplicate",
408
+ run
409
+ };
410
+ await tx.execute(drizzle_orm.sql`
411
+ delete from ${tableSql.timers}
412
+ where run_id = ${args.runId}
413
+ and signal_id = ${args.delivery.signalId}
414
+ `);
415
+ return {
416
+ kind: "delivered",
417
+ run: runFromRow((await queryRows(tx, drizzle_orm.sql`
418
+ update ${tableSql.runs}
419
+ set
420
+ status = 'queued',
421
+ waiting_for = null,
422
+ pending_approval = null,
423
+ wake_at = null,
424
+ updated_at = ${args.now}
425
+ where run_id = ${args.runId}
426
+ returning *
427
+ `))[0])
428
+ };
429
+ });
430
+ },
431
+ async deliverApproval(args) {
432
+ return withTransaction(db, async (tx) => {
433
+ const run = await loadRunById(tx, tableSql, args.runId);
434
+ if (!run) return { kind: "not-found" };
435
+ const signalId = `approval:${args.approval.approvalId}`;
436
+ if (await loadSignalDelivery(tx, tableSql, args.runId, signalId)) return {
437
+ kind: "duplicate",
438
+ run
439
+ };
440
+ if (run.pendingApproval?.approvalId !== args.approval.approvalId) return {
441
+ kind: "not-waiting",
442
+ run
443
+ };
444
+ if (!await insertSignalDelivery(tx, tableSql, args.runId, signalId, args.now)) return {
445
+ kind: "duplicate",
446
+ run
447
+ };
448
+ return {
449
+ kind: "delivered",
450
+ run: runFromRow((await queryRows(tx, drizzle_orm.sql`
451
+ update ${tableSql.runs}
452
+ set
453
+ status = 'queued',
454
+ waiting_for = null,
455
+ pending_approval = null,
456
+ wake_at = null,
457
+ updated_at = ${args.now}
458
+ where run_id = ${args.runId}
459
+ returning *
460
+ `))[0])
461
+ };
462
+ });
463
+ },
464
+ async upsertSchedule(args) {
465
+ await db.execute(drizzle_orm.sql`
466
+ insert into ${tableSql.schedules} (
467
+ schedule_id,
468
+ workflow_id,
469
+ workflow_version,
470
+ schedule,
471
+ overlap_policy,
472
+ input,
473
+ next_fire_at,
474
+ enabled,
475
+ updated_at
476
+ )
477
+ values (
478
+ ${args.scheduleId},
479
+ ${args.workflowId},
480
+ ${args.workflowVersion ?? null},
481
+ ${encodeJson(args.schedule)}::jsonb,
482
+ ${args.overlapPolicy},
483
+ ${encodeJsonOrNull(args.input)}::jsonb,
484
+ ${args.nextFireAt ?? null},
485
+ ${args.enabled},
486
+ ${args.now}
487
+ )
488
+ on conflict (schedule_id) do update set
489
+ workflow_id = excluded.workflow_id,
490
+ workflow_version = excluded.workflow_version,
491
+ schedule = excluded.schedule,
492
+ overlap_policy = excluded.overlap_policy,
493
+ input = excluded.input,
494
+ next_fire_at = excluded.next_fire_at,
495
+ enabled = excluded.enabled,
496
+ updated_at = excluded.updated_at
497
+ `);
498
+ },
499
+ async claimDueScheduleBuckets(args) {
500
+ const schedules = await queryRows(db, drizzle_orm.sql`
501
+ select *
502
+ from ${tableSql.schedules}
503
+ where enabled = true
504
+ and next_fire_at is not null
505
+ and next_fire_at <= ${args.now}
506
+ order by next_fire_at asc, schedule_id asc
507
+ limit ${args.limit}
508
+ `);
509
+ const buckets = [];
510
+ for (const scheduleRow of schedules) {
511
+ if (buckets.length >= args.limit) break;
512
+ const schedule = scheduleFromRow(scheduleRow);
513
+ const bucketId = `${schedule.nextFireAt}`;
514
+ const runId = `${schedule.workflowId}:${schedule.scheduleId}:${bucketId}`;
515
+ await db.execute(drizzle_orm.sql`
516
+ insert into ${tableSql.scheduleBuckets} (
517
+ schedule_id,
518
+ bucket_id,
519
+ workflow_id,
520
+ workflow_version,
521
+ run_id,
522
+ fire_at,
523
+ input,
524
+ overlap_policy,
525
+ status
526
+ )
527
+ values (
528
+ ${schedule.scheduleId},
529
+ ${bucketId},
530
+ ${schedule.workflowId},
531
+ ${schedule.workflowVersion ?? null},
532
+ ${runId},
533
+ ${schedule.nextFireAt},
534
+ ${encodeJsonOrNull(schedule.input)}::jsonb,
535
+ ${schedule.overlapPolicy},
536
+ 'claimed'
537
+ )
538
+ on conflict (schedule_id, bucket_id) do nothing
539
+ `);
540
+ const rows = await queryRows(db, drizzle_orm.sql`
541
+ update ${tableSql.scheduleBuckets}
542
+ set
543
+ lease_owner = ${args.leaseOwner},
544
+ lease_expires_at = ${args.now + args.leaseMs}
545
+ where schedule_id = ${schedule.scheduleId}
546
+ and bucket_id = ${bucketId}
547
+ and status <> 'started'
548
+ and (
549
+ lease_owner is null
550
+ or lease_owner = ${args.leaseOwner}
551
+ or lease_expires_at <= ${args.now}
552
+ )
553
+ returning *
554
+ `);
555
+ if (rows[0]) buckets.push(scheduleBucketFromRow(rows[0]));
556
+ }
557
+ return buckets;
558
+ },
559
+ async markScheduleBucketStarted(args) {
560
+ await db.execute(drizzle_orm.sql`
561
+ update ${tableSql.scheduleBuckets}
562
+ set
563
+ run_id = ${args.runId},
564
+ status = 'started',
565
+ started_at = ${args.now}
566
+ where schedule_id = ${args.scheduleId}
567
+ and bucket_id = ${args.bucketId}
568
+ `);
569
+ },
570
+ async claimStaleRuns(args) {
571
+ return (await queryRows(db, drizzle_orm.sql`
572
+ with stale as (
573
+ select run_id
574
+ from ${tableSql.runs}
575
+ where status = 'running'
576
+ and lease_expires_at is not null
577
+ and lease_expires_at <= ${args.now}
578
+ order by updated_at asc, run_id asc
579
+ limit ${args.limit}
580
+ for update skip locked
581
+ )
582
+ update ${tableSql.runs} run
583
+ set
584
+ lease_owner = ${args.leaseOwner},
585
+ lease_expires_at = ${args.now + args.leaseMs},
586
+ updated_at = ${args.now}
587
+ from stale
588
+ where run.run_id = stale.run_id
589
+ returning run.*
590
+ `)).map((row) => {
591
+ const run = runFromRow(row);
592
+ return {
593
+ run,
594
+ lease: run.lease
595
+ };
596
+ });
597
+ },
598
+ async listRuns(args) {
599
+ const offset = args.cursor ? Number(args.cursor) : 0;
600
+ const start = Number.isFinite(offset) && offset > 0 ? offset : 0;
601
+ return (await queryRows(db, drizzle_orm.sql`
602
+ select *
603
+ from ${tableSql.runs}
604
+ where (${args.workflowId ?? null}::text is null or workflow_id = ${args.workflowId ?? null})
605
+ and (${args.status ?? null}::text is null or status = ${args.status ?? null})
606
+ order by updated_at desc, run_id asc
607
+ limit ${args.limit}
608
+ offset ${start}
609
+ `)).map(toRunSummary);
610
+ },
611
+ async getRunTimeline(runId) {
612
+ const run = await loadRunById(db, tableSql, runId);
613
+ if (!run) return void 0;
614
+ return {
615
+ run,
616
+ events: await readStoredEvents(db, tableSql, { runId })
617
+ };
618
+ }
619
+ };
620
+ }
621
+ function tableSqls(schema, tables) {
622
+ return {
623
+ runs: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.runs)),
624
+ runStates: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.runStates)),
625
+ eventLocks: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.eventLocks)),
626
+ events: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.events)),
627
+ timers: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.timers)),
628
+ signalDeliveries: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.signalDeliveries)),
629
+ schedules: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.schedules)),
630
+ scheduleBuckets: drizzle_orm.sql.raw(qualifiedTableName(schema, tables.scheduleBuckets))
631
+ };
632
+ }
633
+ function schemaStatements(schema, tables) {
634
+ const runs = qualifiedTableName(schema, tables.runs);
635
+ const runStates = qualifiedTableName(schema, tables.runStates);
636
+ const eventLocks = qualifiedTableName(schema, tables.eventLocks);
637
+ const events = qualifiedTableName(schema, tables.events);
638
+ const timers = qualifiedTableName(schema, tables.timers);
639
+ const signalDeliveries = qualifiedTableName(schema, tables.signalDeliveries);
640
+ const schedules = qualifiedTableName(schema, tables.schedules);
641
+ const scheduleBuckets = qualifiedTableName(schema, tables.scheduleBuckets);
642
+ return [
643
+ ...schema ? [`create schema if not exists ${quoteIdent(schema)}`] : [],
644
+ `create table if not exists ${runs} (
645
+ run_id text primary key,
646
+ workflow_id text not null,
647
+ workflow_version text,
648
+ status text not null,
649
+ input jsonb not null,
650
+ output jsonb,
651
+ error jsonb,
652
+ waiting_for jsonb,
653
+ pending_approval jsonb,
654
+ wake_at bigint,
655
+ lease_owner text,
656
+ lease_expires_at bigint,
657
+ created_at bigint not null,
658
+ updated_at bigint not null
659
+ )`,
660
+ `create index if not exists ${quoteIdent(`${tables.runs}_status_idx`)}
661
+ on ${runs} (status, updated_at)`,
662
+ `create index if not exists ${quoteIdent(`${tables.runs}_lease_idx`)}
663
+ on ${runs} (status, lease_expires_at)`,
664
+ `create table if not exists ${runStates} (
665
+ run_id text primary key,
666
+ workflow_id text not null,
667
+ workflow_version text,
668
+ status text not null,
669
+ input jsonb not null,
670
+ output jsonb,
671
+ error jsonb,
672
+ waiting_for jsonb,
673
+ pending_approval jsonb,
674
+ created_at bigint not null,
675
+ updated_at bigint not null
676
+ )`,
677
+ `create table if not exists ${eventLocks} (
678
+ run_id text primary key,
679
+ created_at bigint not null
680
+ )`,
681
+ `create table if not exists ${events} (
682
+ run_id text not null,
683
+ event_index integer not null,
684
+ event_type text not null,
685
+ step_id text,
686
+ event jsonb not null,
687
+ created_at bigint not null,
688
+ primary key (run_id, event_index)
689
+ )`,
690
+ `create index if not exists ${quoteIdent(`${tables.events}_type_idx`)}
691
+ on ${events} (run_id, event_type)`,
692
+ `create table if not exists ${timers} (
693
+ run_id text not null,
694
+ signal_id text not null,
695
+ workflow_id text not null,
696
+ workflow_version text,
697
+ wake_at bigint not null,
698
+ lease_owner text,
699
+ lease_expires_at bigint,
700
+ primary key (run_id, signal_id)
701
+ )`,
702
+ `create index if not exists ${quoteIdent(`${tables.timers}_due_idx`)}
703
+ on ${timers} (wake_at, lease_expires_at)`,
704
+ `create table if not exists ${signalDeliveries} (
705
+ run_id text not null,
706
+ signal_id text not null,
707
+ created_at bigint not null,
708
+ primary key (run_id, signal_id)
709
+ )`,
710
+ `create table if not exists ${schedules} (
711
+ schedule_id text primary key,
712
+ workflow_id text not null,
713
+ workflow_version text,
714
+ schedule jsonb not null,
715
+ overlap_policy text not null,
716
+ input jsonb,
717
+ next_fire_at bigint,
718
+ enabled boolean not null,
719
+ updated_at bigint not null
720
+ )`,
721
+ `create index if not exists ${quoteIdent(`${tables.schedules}_due_idx`)}
722
+ on ${schedules} (enabled, next_fire_at)`,
723
+ `create table if not exists ${scheduleBuckets} (
724
+ schedule_id text not null,
725
+ bucket_id text not null,
726
+ workflow_id text not null,
727
+ workflow_version text,
728
+ run_id text not null,
729
+ fire_at bigint not null,
730
+ input jsonb,
731
+ overlap_policy text not null,
732
+ status text not null,
733
+ lease_owner text,
734
+ lease_expires_at bigint,
735
+ started_at bigint,
736
+ primary key (schedule_id, bucket_id)
737
+ )`,
738
+ `create index if not exists ${quoteIdent(`${tables.scheduleBuckets}_lease_idx`)}
739
+ on ${scheduleBuckets} (status, fire_at, lease_expires_at)`
740
+ ];
741
+ }
742
+ async function loadRunById(db, tables, runId) {
743
+ const rows = await queryRows(db, drizzle_orm.sql`
744
+ select *
745
+ from ${tables.runs}
746
+ where run_id = ${runId}
747
+ limit 1
748
+ `);
749
+ return rows[0] ? runFromRow(rows[0]) : void 0;
750
+ }
751
+ async function loadRunStateById(db, tables, runId) {
752
+ const rows = await queryRows(db, drizzle_orm.sql`
753
+ select *
754
+ from ${tables.runStates}
755
+ where run_id = ${runId}
756
+ limit 1
757
+ `);
758
+ return rows[0] ? runStateFromRow(rows[0]) : void 0;
759
+ }
760
+ async function readStoredEvents(db, tables, args) {
761
+ return (await queryRows(db, drizzle_orm.sql`
762
+ select *
763
+ from ${tables.events}
764
+ where run_id = ${args.runId}
765
+ and event_index >= ${args.fromIndex ?? 0}
766
+ order by event_index asc
767
+ `)).map(eventFromRow);
768
+ }
769
+ async function loadSignalDelivery(db, tables, runId, signalId) {
770
+ const rows = await queryRows(db, drizzle_orm.sql`
771
+ select run_id
772
+ from ${tables.signalDeliveries}
773
+ where run_id = ${runId}
774
+ and signal_id = ${signalId}
775
+ limit 1
776
+ `);
777
+ return Boolean(rows[0]);
778
+ }
779
+ async function insertSignalDelivery(db, tables, runId, signalId, now) {
780
+ const rows = await queryRows(db, drizzle_orm.sql`
781
+ insert into ${tables.signalDeliveries} (run_id, signal_id, created_at)
782
+ values (${runId}, ${signalId}, ${now})
783
+ on conflict (run_id, signal_id) do nothing
784
+ returning run_id
785
+ `);
786
+ return Boolean(rows[0]);
787
+ }
788
+ async function withTransaction(db, callback) {
789
+ if (db.transaction) return db.transaction(callback);
790
+ await db.execute(drizzle_orm.sql.raw("begin"));
791
+ try {
792
+ const result = await callback(db);
793
+ await db.execute(drizzle_orm.sql.raw("commit"));
794
+ return result;
795
+ } catch (error) {
796
+ await db.execute(drizzle_orm.sql.raw("rollback"));
797
+ throw error;
798
+ }
799
+ }
800
+ async function queryRows(db, query) {
801
+ return rowsFromResult(await db.execute(query));
802
+ }
803
+ function rowsFromResult(result) {
804
+ if (Array.isArray(result)) return result;
805
+ if (isObjectWithRows(result) && Array.isArray(result.rows)) return result.rows;
806
+ return [];
807
+ }
808
+ function isObjectWithRows(value) {
809
+ return typeof value === "object" && value !== null && "rows" in value;
810
+ }
811
+ function runFromRow(row) {
812
+ return {
813
+ runId: row.run_id,
814
+ workflowId: row.workflow_id,
815
+ workflowVersion: row.workflow_version ?? void 0,
816
+ status: row.status,
817
+ input: decodeJson(row.input),
818
+ output: decodeJsonOrUndefined(row.output),
819
+ error: decodeJsonOrUndefined(row.error),
820
+ waitingFor: decodeJsonOrUndefined(row.waiting_for),
821
+ pendingApproval: decodeJsonOrUndefined(row.pending_approval),
822
+ wakeAt: numberOrUndefined(row.wake_at),
823
+ lease: row.lease_owner && row.lease_expires_at !== null ? {
824
+ owner: row.lease_owner,
825
+ expiresAt: Number(row.lease_expires_at)
826
+ } : void 0,
827
+ createdAt: Number(row.created_at),
828
+ updatedAt: Number(row.updated_at)
829
+ };
830
+ }
831
+ function runStateFromRow(row) {
832
+ return {
833
+ runId: row.run_id,
834
+ workflowId: row.workflow_id,
835
+ workflowVersion: row.workflow_version ?? void 0,
836
+ status: row.status,
837
+ input: decodeJson(row.input),
838
+ output: decodeJsonOrUndefined(row.output),
839
+ error: decodeJsonOrUndefined(row.error),
840
+ waitingFor: decodeJsonOrUndefined(row.waiting_for),
841
+ pendingApproval: decodeJsonOrUndefined(row.pending_approval),
842
+ createdAt: Number(row.created_at),
843
+ updatedAt: Number(row.updated_at)
844
+ };
845
+ }
846
+ function eventFromRow(row) {
847
+ return {
848
+ runId: row.run_id,
849
+ eventIndex: Number(row.event_index),
850
+ eventType: row.event_type,
851
+ stepId: row.step_id ?? void 0,
852
+ event: decodeJson(row.event),
853
+ createdAt: Number(row.created_at)
854
+ };
855
+ }
856
+ function timerFromRow(row) {
857
+ return {
858
+ runId: row.run_id,
859
+ workflowId: row.workflow_id,
860
+ workflowVersion: row.workflow_version ?? void 0,
861
+ wakeAt: Number(row.wake_at),
862
+ signalId: row.signal_id
863
+ };
864
+ }
865
+ function scheduleFromRow(row) {
866
+ return {
867
+ scheduleId: row.schedule_id,
868
+ workflowId: row.workflow_id,
869
+ workflowVersion: row.workflow_version ?? void 0,
870
+ input: decodeJsonOrUndefined(row.input),
871
+ overlapPolicy: row.overlap_policy,
872
+ nextFireAt: Number(row.next_fire_at)
873
+ };
874
+ }
875
+ function scheduleBucketFromRow(row) {
876
+ return {
877
+ scheduleId: row.schedule_id,
878
+ bucketId: row.bucket_id,
879
+ workflowId: row.workflow_id,
880
+ workflowVersion: row.workflow_version ?? void 0,
881
+ runId: row.run_id,
882
+ fireAt: Number(row.fire_at),
883
+ input: decodeJsonOrUndefined(row.input),
884
+ overlapPolicy: row.overlap_policy
885
+ };
886
+ }
887
+ function toRunSummary(row) {
888
+ const run = runFromRow(row);
889
+ return {
890
+ runId: run.runId,
891
+ workflowId: run.workflowId,
892
+ workflowVersion: run.workflowVersion,
893
+ status: run.status,
894
+ waitingFor: run.waitingFor,
895
+ pendingApproval: run.pendingApproval,
896
+ wakeAt: run.wakeAt,
897
+ createdAt: run.createdAt,
898
+ updatedAt: run.updatedAt
899
+ };
900
+ }
901
+ function encodeJson(value) {
902
+ return JSON.stringify(value ?? null);
903
+ }
904
+ function encodeJsonOrNull(value) {
905
+ return value === void 0 ? null : JSON.stringify(value);
906
+ }
907
+ function decodeJson(value) {
908
+ if (typeof value !== "string") return value;
909
+ try {
910
+ return JSON.parse(value);
911
+ } catch {
912
+ return value;
913
+ }
914
+ }
915
+ function decodeJsonOrUndefined(value) {
916
+ if (value === null || value === void 0) return void 0;
917
+ return decodeJson(value);
918
+ }
919
+ function numberOrUndefined(value) {
920
+ return value === null ? void 0 : Number(value);
921
+ }
922
+ function getStepId(event) {
923
+ return "stepId" in event ? event.stepId : void 0;
924
+ }
925
+ function qualifiedTableName(schema, table) {
926
+ return schema ? `${quoteIdent(schema)}.${quoteIdent(table)}` : quoteIdent(table);
927
+ }
928
+ function quoteIdent(identifier) {
929
+ return `"${identifier.replaceAll("\"", "\"\"")}"`;
930
+ }
931
+
932
+ //#endregion
933
+ exports.createDrizzlePostgresWorkflowStore = createDrizzlePostgresWorkflowStore;
934
+ exports.defaultDrizzlePostgresWorkflowStoreTables = defaultDrizzlePostgresWorkflowStoreTables;
935
+ //# sourceMappingURL=store.cjs.map