@pgflow/core 0.0.0-test-snapshot-releases-8d5d9bc1-20250922101013 → 0.0.0-testsnap-9294d743-20251207205914

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.
Files changed (32) hide show
  1. package/README.md +177 -73
  2. package/dist/ATLAS.md +32 -0
  3. package/dist/CHANGELOG.md +824 -0
  4. package/dist/PgflowSqlClient.d.ts +17 -0
  5. package/dist/PgflowSqlClient.d.ts.map +1 -0
  6. package/dist/PgflowSqlClient.js +70 -0
  7. package/dist/README.md +497 -0
  8. package/dist/database-types.d.ts +1041 -0
  9. package/dist/database-types.d.ts.map +1 -0
  10. package/dist/database-types.js +8 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +2 -0
  14. package/dist/package.json +31 -0
  15. package/dist/supabase/migrations/20250429164909_pgflow_initial.sql +579 -0
  16. package/dist/supabase/migrations/20250517072017_pgflow_fix_poll_for_tasks_to_use_separate_statement_for_polling.sql +101 -0
  17. package/dist/supabase/migrations/20250609105135_pgflow_add_start_tasks_and_started_status.sql +371 -0
  18. package/dist/supabase/migrations/20250610180554_pgflow_add_set_vt_batch_and_use_it_in_start_tasks.sql +127 -0
  19. package/dist/supabase/migrations/20250614124241_pgflow_add_realtime.sql +501 -0
  20. package/dist/supabase/migrations/20250619195327_pgflow_fix_fail_task_missing_realtime_event.sql +185 -0
  21. package/dist/supabase/migrations/20250627090700_pgflow_fix_function_search_paths.sql +6 -0
  22. package/dist/supabase/migrations/20250707210212_pgflow_add_opt_start_delay.sql +103 -0
  23. package/dist/supabase/migrations/20250719205006_pgflow_worker_deprecation.sql +2 -0
  24. package/dist/supabase/migrations/20251006073122_pgflow_add_map_step_type.sql +1244 -0
  25. package/dist/supabase/migrations/20251103222045_pgflow_fix_broadcast_order_and_timestamp_handling.sql +622 -0
  26. package/dist/supabase/migrations/20251104080523_pgflow_upgrade_pgmq_1_5_1.sql +93 -0
  27. package/dist/supabase/migrations/20251130000000_pgflow_auto_compilation.sql +268 -0
  28. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  29. package/dist/types.d.ts +93 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +1 -0
  32. package/package.json +4 -5
@@ -0,0 +1,501 @@
1
+ -- Modify "step_states" table
2
+ ALTER TABLE "pgflow"."step_states" ADD COLUMN "error_message" text NULL;
3
+ -- Create index "idx_step_states_run_id" to table: "step_states"
4
+ CREATE INDEX "idx_step_states_run_id" ON "pgflow"."step_states" ("run_id");
5
+ -- Modify "maybe_complete_run" function
6
+ CREATE OR REPLACE FUNCTION "pgflow"."maybe_complete_run" ("run_id" uuid) RETURNS void LANGUAGE plpgsql SET "search_path" = '' AS $$
7
+ declare
8
+ v_completed_run pgflow.runs%ROWTYPE;
9
+ begin
10
+ -- Update run status to completed and set output when there are no remaining steps
11
+ WITH run_output AS (
12
+ -- Get outputs from final steps (steps that are not dependencies for other steps)
13
+ SELECT jsonb_object_agg(st.step_slug, st.output) as final_output
14
+ FROM pgflow.step_tasks st
15
+ JOIN pgflow.step_states ss ON ss.run_id = st.run_id AND ss.step_slug = st.step_slug
16
+ JOIN pgflow.runs r ON r.run_id = ss.run_id AND r.flow_slug = ss.flow_slug
17
+ WHERE st.run_id = maybe_complete_run.run_id
18
+ AND st.status = 'completed'
19
+ AND NOT EXISTS (
20
+ SELECT 1
21
+ FROM pgflow.deps d
22
+ WHERE d.flow_slug = ss.flow_slug
23
+ AND d.dep_slug = ss.step_slug
24
+ )
25
+ )
26
+ UPDATE pgflow.runs
27
+ SET
28
+ status = 'completed',
29
+ completed_at = now(),
30
+ output = (SELECT final_output FROM run_output)
31
+ WHERE pgflow.runs.run_id = maybe_complete_run.run_id
32
+ AND pgflow.runs.remaining_steps = 0
33
+ AND pgflow.runs.status != 'completed'
34
+ RETURNING * INTO v_completed_run;
35
+
36
+ -- Only send broadcast if run was completed
37
+ IF v_completed_run.run_id IS NOT NULL THEN
38
+ PERFORM realtime.send(
39
+ jsonb_build_object(
40
+ 'event_type', 'run:completed',
41
+ 'run_id', v_completed_run.run_id,
42
+ 'flow_slug', v_completed_run.flow_slug,
43
+ 'status', 'completed',
44
+ 'output', v_completed_run.output,
45
+ 'completed_at', v_completed_run.completed_at
46
+ ),
47
+ 'run:completed',
48
+ concat('pgflow:run:', v_completed_run.run_id),
49
+ false
50
+ );
51
+ END IF;
52
+ end;
53
+ $$;
54
+ -- Modify "start_ready_steps" function
55
+ CREATE OR REPLACE FUNCTION "pgflow"."start_ready_steps" ("run_id" uuid) RETURNS void LANGUAGE sql SET "search_path" = '' AS $$
56
+ WITH ready_steps AS (
57
+ SELECT *
58
+ FROM pgflow.step_states AS step_state
59
+ WHERE step_state.run_id = start_ready_steps.run_id
60
+ AND step_state.status = 'created'
61
+ AND step_state.remaining_deps = 0
62
+ ORDER BY step_state.step_slug
63
+ FOR UPDATE
64
+ ),
65
+ started_step_states AS (
66
+ UPDATE pgflow.step_states
67
+ SET status = 'started',
68
+ started_at = now()
69
+ FROM ready_steps
70
+ WHERE pgflow.step_states.run_id = start_ready_steps.run_id
71
+ AND pgflow.step_states.step_slug = ready_steps.step_slug
72
+ RETURNING pgflow.step_states.*
73
+ ),
74
+ sent_messages AS (
75
+ SELECT
76
+ started_step.flow_slug,
77
+ started_step.run_id,
78
+ started_step.step_slug,
79
+ pgmq.send(started_step.flow_slug, jsonb_build_object(
80
+ 'flow_slug', started_step.flow_slug,
81
+ 'run_id', started_step.run_id,
82
+ 'step_slug', started_step.step_slug,
83
+ 'task_index', 0
84
+ )) AS msg_id
85
+ FROM started_step_states AS started_step
86
+ ),
87
+ broadcast_events AS (
88
+ SELECT
89
+ realtime.send(
90
+ jsonb_build_object(
91
+ 'event_type', 'step:started',
92
+ 'run_id', started_step.run_id,
93
+ 'step_slug', started_step.step_slug,
94
+ 'status', 'started',
95
+ 'started_at', started_step.started_at,
96
+ 'remaining_tasks', 1,
97
+ 'remaining_deps', started_step.remaining_deps
98
+ ),
99
+ concat('step:', started_step.step_slug, ':started'),
100
+ concat('pgflow:run:', started_step.run_id),
101
+ false
102
+ )
103
+ FROM started_step_states AS started_step
104
+ )
105
+ INSERT INTO pgflow.step_tasks (flow_slug, run_id, step_slug, message_id)
106
+ SELECT
107
+ sent_messages.flow_slug,
108
+ sent_messages.run_id,
109
+ sent_messages.step_slug,
110
+ sent_messages.msg_id
111
+ FROM sent_messages;
112
+ $$;
113
+ -- Modify "complete_task" function
114
+ CREATE OR REPLACE FUNCTION "pgflow"."complete_task" ("run_id" uuid, "step_slug" text, "task_index" integer, "output" jsonb) RETURNS SETOF "pgflow"."step_tasks" LANGUAGE plpgsql SET "search_path" = '' AS $$
115
+ declare
116
+ v_step_state pgflow.step_states%ROWTYPE;
117
+ begin
118
+
119
+ WITH run_lock AS (
120
+ SELECT * FROM pgflow.runs
121
+ WHERE pgflow.runs.run_id = complete_task.run_id
122
+ FOR UPDATE
123
+ ),
124
+ step_lock AS (
125
+ SELECT * FROM pgflow.step_states
126
+ WHERE pgflow.step_states.run_id = complete_task.run_id
127
+ AND pgflow.step_states.step_slug = complete_task.step_slug
128
+ FOR UPDATE
129
+ ),
130
+ task AS (
131
+ UPDATE pgflow.step_tasks
132
+ SET
133
+ status = 'completed',
134
+ completed_at = now(),
135
+ output = complete_task.output
136
+ WHERE pgflow.step_tasks.run_id = complete_task.run_id
137
+ AND pgflow.step_tasks.step_slug = complete_task.step_slug
138
+ AND pgflow.step_tasks.task_index = complete_task.task_index
139
+ AND pgflow.step_tasks.status = 'started'
140
+ RETURNING *
141
+ ),
142
+ step_state AS (
143
+ UPDATE pgflow.step_states
144
+ SET
145
+ status = CASE
146
+ WHEN pgflow.step_states.remaining_tasks = 1 THEN 'completed' -- Will be 0 after decrement
147
+ ELSE 'started'
148
+ END,
149
+ completed_at = CASE
150
+ WHEN pgflow.step_states.remaining_tasks = 1 THEN now() -- Will be 0 after decrement
151
+ ELSE NULL
152
+ END,
153
+ remaining_tasks = pgflow.step_states.remaining_tasks - 1
154
+ FROM task
155
+ WHERE pgflow.step_states.run_id = complete_task.run_id
156
+ AND pgflow.step_states.step_slug = complete_task.step_slug
157
+ RETURNING pgflow.step_states.*
158
+ ),
159
+ -- Find all dependent steps if the current step was completed
160
+ dependent_steps AS (
161
+ SELECT d.step_slug AS dependent_step_slug
162
+ FROM pgflow.deps d
163
+ JOIN step_state s ON s.status = 'completed' AND d.flow_slug = s.flow_slug
164
+ WHERE d.dep_slug = complete_task.step_slug
165
+ ORDER BY d.step_slug -- Ensure consistent ordering
166
+ ),
167
+ -- Lock dependent steps before updating
168
+ dependent_steps_lock AS (
169
+ SELECT * FROM pgflow.step_states
170
+ WHERE pgflow.step_states.run_id = complete_task.run_id
171
+ AND pgflow.step_states.step_slug IN (SELECT dependent_step_slug FROM dependent_steps)
172
+ FOR UPDATE
173
+ ),
174
+ -- Update all dependent steps
175
+ dependent_steps_update AS (
176
+ UPDATE pgflow.step_states
177
+ SET remaining_deps = pgflow.step_states.remaining_deps - 1
178
+ FROM dependent_steps
179
+ WHERE pgflow.step_states.run_id = complete_task.run_id
180
+ AND pgflow.step_states.step_slug = dependent_steps.dependent_step_slug
181
+ )
182
+ -- Only decrement remaining_steps, don't update status
183
+ UPDATE pgflow.runs
184
+ SET remaining_steps = pgflow.runs.remaining_steps - 1
185
+ FROM step_state
186
+ WHERE pgflow.runs.run_id = complete_task.run_id
187
+ AND step_state.status = 'completed';
188
+
189
+ -- Get the updated step state for broadcasting
190
+ SELECT * INTO v_step_state FROM pgflow.step_states
191
+ WHERE pgflow.step_states.run_id = complete_task.run_id AND pgflow.step_states.step_slug = complete_task.step_slug;
192
+
193
+ -- Send broadcast event for step completed if the step is completed
194
+ IF v_step_state.status = 'completed' THEN
195
+ PERFORM realtime.send(
196
+ jsonb_build_object(
197
+ 'event_type', 'step:completed',
198
+ 'run_id', complete_task.run_id,
199
+ 'step_slug', complete_task.step_slug,
200
+ 'status', 'completed',
201
+ 'output', complete_task.output,
202
+ 'completed_at', v_step_state.completed_at
203
+ ),
204
+ concat('step:', complete_task.step_slug, ':completed'),
205
+ concat('pgflow:run:', complete_task.run_id),
206
+ false
207
+ );
208
+ END IF;
209
+
210
+ -- For completed tasks: archive the message
211
+ PERFORM (
212
+ WITH completed_tasks AS (
213
+ SELECT r.flow_slug, st.message_id
214
+ FROM pgflow.step_tasks st
215
+ JOIN pgflow.runs r ON st.run_id = r.run_id
216
+ WHERE st.run_id = complete_task.run_id
217
+ AND st.step_slug = complete_task.step_slug
218
+ AND st.task_index = complete_task.task_index
219
+ AND st.status = 'completed'
220
+ )
221
+ SELECT pgmq.archive(ct.flow_slug, ct.message_id)
222
+ FROM completed_tasks ct
223
+ WHERE EXISTS (SELECT 1 FROM completed_tasks)
224
+ );
225
+
226
+ PERFORM pgflow.start_ready_steps(complete_task.run_id);
227
+
228
+ PERFORM pgflow.maybe_complete_run(complete_task.run_id);
229
+
230
+ RETURN QUERY SELECT *
231
+ FROM pgflow.step_tasks AS step_task
232
+ WHERE step_task.run_id = complete_task.run_id
233
+ AND step_task.step_slug = complete_task.step_slug
234
+ AND step_task.task_index = complete_task.task_index;
235
+
236
+ end;
237
+ $$;
238
+ -- Modify "fail_task" function
239
+ CREATE OR REPLACE FUNCTION "pgflow"."fail_task" ("run_id" uuid, "step_slug" text, "task_index" integer, "error_message" text) RETURNS SETOF "pgflow"."step_tasks" LANGUAGE plpgsql SET "search_path" = '' AS $$
240
+ DECLARE
241
+ v_run_failed boolean;
242
+ begin
243
+
244
+ WITH run_lock AS (
245
+ SELECT * FROM pgflow.runs
246
+ WHERE pgflow.runs.run_id = fail_task.run_id
247
+ FOR UPDATE
248
+ ),
249
+ step_lock AS (
250
+ SELECT * FROM pgflow.step_states
251
+ WHERE pgflow.step_states.run_id = fail_task.run_id
252
+ AND pgflow.step_states.step_slug = fail_task.step_slug
253
+ FOR UPDATE
254
+ ),
255
+ flow_info AS (
256
+ SELECT r.flow_slug
257
+ FROM pgflow.runs r
258
+ WHERE r.run_id = fail_task.run_id
259
+ ),
260
+ config AS (
261
+ SELECT
262
+ COALESCE(s.opt_max_attempts, f.opt_max_attempts) AS opt_max_attempts,
263
+ COALESCE(s.opt_base_delay, f.opt_base_delay) AS opt_base_delay
264
+ FROM pgflow.steps s
265
+ JOIN pgflow.flows f ON f.flow_slug = s.flow_slug
266
+ JOIN flow_info fi ON fi.flow_slug = s.flow_slug
267
+ WHERE s.flow_slug = fi.flow_slug AND s.step_slug = fail_task.step_slug
268
+ ),
269
+ fail_or_retry_task as (
270
+ UPDATE pgflow.step_tasks as task
271
+ SET
272
+ status = CASE
273
+ WHEN task.attempts_count < (SELECT opt_max_attempts FROM config) THEN 'queued'
274
+ ELSE 'failed'
275
+ END,
276
+ failed_at = CASE
277
+ WHEN task.attempts_count >= (SELECT opt_max_attempts FROM config) THEN now()
278
+ ELSE NULL
279
+ END,
280
+ started_at = CASE
281
+ WHEN task.attempts_count < (SELECT opt_max_attempts FROM config) THEN NULL
282
+ ELSE task.started_at
283
+ END,
284
+ error_message = fail_task.error_message
285
+ WHERE task.run_id = fail_task.run_id
286
+ AND task.step_slug = fail_task.step_slug
287
+ AND task.task_index = fail_task.task_index
288
+ AND task.status = 'started'
289
+ RETURNING *
290
+ ),
291
+ maybe_fail_step AS (
292
+ UPDATE pgflow.step_states
293
+ SET
294
+ status = CASE
295
+ WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN 'failed'
296
+ ELSE pgflow.step_states.status
297
+ END,
298
+ failed_at = CASE
299
+ WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN now()
300
+ ELSE NULL
301
+ END,
302
+ error_message = CASE
303
+ WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN fail_task.error_message
304
+ ELSE NULL
305
+ END
306
+ FROM fail_or_retry_task
307
+ WHERE pgflow.step_states.run_id = fail_task.run_id
308
+ AND pgflow.step_states.step_slug = fail_task.step_slug
309
+ RETURNING pgflow.step_states.*
310
+ ),
311
+ -- Send broadcast event for step failed if necessary
312
+ broadcast_step_failed AS (
313
+ SELECT
314
+ realtime.send(
315
+ jsonb_build_object(
316
+ 'event_type', 'step:failed',
317
+ 'run_id', fail_task.run_id,
318
+ 'step_slug', fail_task.step_slug,
319
+ 'status', 'failed',
320
+ 'error_message', fail_task.error_message,
321
+ 'failed_at', now()
322
+ ),
323
+ concat('step:', fail_task.step_slug, ':failed'),
324
+ concat('pgflow:run:', fail_task.run_id),
325
+ false
326
+ )
327
+ FROM maybe_fail_step
328
+ WHERE maybe_fail_step.status = 'failed'
329
+ )
330
+ -- Only decrement remaining_steps, don't update status
331
+ UPDATE pgflow.runs
332
+ SET status = CASE
333
+ WHEN (select status from maybe_fail_step) = 'failed' THEN 'failed'
334
+ ELSE status
335
+ END,
336
+ failed_at = CASE
337
+ WHEN (select status from maybe_fail_step) = 'failed' THEN now()
338
+ ELSE NULL
339
+ END
340
+ WHERE pgflow.runs.run_id = fail_task.run_id
341
+ RETURNING (status = 'failed') INTO v_run_failed;
342
+
343
+ -- Send broadcast event for run failure if the run was failed
344
+ IF v_run_failed THEN
345
+ DECLARE
346
+ v_flow_slug text;
347
+ BEGIN
348
+ SELECT flow_slug INTO v_flow_slug FROM pgflow.runs WHERE pgflow.runs.run_id = fail_task.run_id;
349
+
350
+ PERFORM realtime.send(
351
+ jsonb_build_object(
352
+ 'event_type', 'run:failed',
353
+ 'run_id', fail_task.run_id,
354
+ 'flow_slug', v_flow_slug,
355
+ 'status', 'failed',
356
+ 'error_message', fail_task.error_message,
357
+ 'failed_at', now()
358
+ ),
359
+ 'run:failed',
360
+ concat('pgflow:run:', fail_task.run_id),
361
+ false
362
+ );
363
+ END;
364
+ END IF;
365
+
366
+ -- For queued tasks: delay the message for retry with exponential backoff
367
+ PERFORM (
368
+ WITH retry_config AS (
369
+ SELECT
370
+ COALESCE(s.opt_base_delay, f.opt_base_delay) AS base_delay
371
+ FROM pgflow.steps s
372
+ JOIN pgflow.flows f ON f.flow_slug = s.flow_slug
373
+ JOIN pgflow.runs r ON r.flow_slug = f.flow_slug
374
+ WHERE r.run_id = fail_task.run_id
375
+ AND s.step_slug = fail_task.step_slug
376
+ ),
377
+ queued_tasks AS (
378
+ SELECT
379
+ r.flow_slug,
380
+ st.message_id,
381
+ pgflow.calculate_retry_delay((SELECT base_delay FROM retry_config), st.attempts_count) AS calculated_delay
382
+ FROM pgflow.step_tasks st
383
+ JOIN pgflow.runs r ON st.run_id = r.run_id
384
+ WHERE st.run_id = fail_task.run_id
385
+ AND st.step_slug = fail_task.step_slug
386
+ AND st.task_index = fail_task.task_index
387
+ AND st.status = 'queued'
388
+ )
389
+ SELECT pgmq.set_vt(qt.flow_slug, qt.message_id, qt.calculated_delay)
390
+ FROM queued_tasks qt
391
+ WHERE EXISTS (SELECT 1 FROM queued_tasks)
392
+ );
393
+
394
+ -- For failed tasks: archive the message
395
+ PERFORM (
396
+ WITH failed_tasks AS (
397
+ SELECT r.flow_slug, st.message_id
398
+ FROM pgflow.step_tasks st
399
+ JOIN pgflow.runs r ON st.run_id = r.run_id
400
+ WHERE st.run_id = fail_task.run_id
401
+ AND st.step_slug = fail_task.step_slug
402
+ AND st.task_index = fail_task.task_index
403
+ AND st.status = 'failed'
404
+ )
405
+ SELECT pgmq.archive(ft.flow_slug, ft.message_id)
406
+ FROM failed_tasks ft
407
+ WHERE EXISTS (SELECT 1 FROM failed_tasks)
408
+ );
409
+
410
+ return query select *
411
+ from pgflow.step_tasks st
412
+ where st.run_id = fail_task.run_id
413
+ and st.step_slug = fail_task.step_slug
414
+ and st.task_index = fail_task.task_index;
415
+
416
+ end;
417
+ $$;
418
+ -- Create "get_run_with_states" function
419
+ CREATE FUNCTION "pgflow"."get_run_with_states" ("run_id" uuid) RETURNS jsonb LANGUAGE sql SECURITY DEFINER AS $$
420
+ SELECT jsonb_build_object(
421
+ 'run', to_jsonb(r),
422
+ 'steps', COALESCE(jsonb_agg(to_jsonb(s)) FILTER (WHERE s.run_id IS NOT NULL), '[]'::jsonb)
423
+ )
424
+ FROM pgflow.runs r
425
+ LEFT JOIN pgflow.step_states s ON s.run_id = r.run_id
426
+ WHERE r.run_id = get_run_with_states.run_id
427
+ GROUP BY r.run_id;
428
+ $$;
429
+ -- Create "start_flow" function
430
+ CREATE FUNCTION "pgflow"."start_flow" ("flow_slug" text, "input" jsonb, "run_id" uuid DEFAULT NULL::uuid) RETURNS SETOF "pgflow"."runs" LANGUAGE plpgsql SET "search_path" = '' AS $$
431
+ declare
432
+ v_created_run pgflow.runs%ROWTYPE;
433
+ begin
434
+
435
+ WITH
436
+ flow_steps AS (
437
+ SELECT steps.flow_slug, steps.step_slug, steps.deps_count
438
+ FROM pgflow.steps
439
+ WHERE steps.flow_slug = start_flow.flow_slug
440
+ ),
441
+ created_run AS (
442
+ INSERT INTO pgflow.runs (run_id, flow_slug, input, remaining_steps)
443
+ VALUES (
444
+ COALESCE(start_flow.run_id, gen_random_uuid()),
445
+ start_flow.flow_slug,
446
+ start_flow.input,
447
+ (SELECT count(*) FROM flow_steps)
448
+ )
449
+ RETURNING *
450
+ ),
451
+ created_step_states AS (
452
+ INSERT INTO pgflow.step_states (flow_slug, run_id, step_slug, remaining_deps)
453
+ SELECT
454
+ fs.flow_slug,
455
+ (SELECT created_run.run_id FROM created_run),
456
+ fs.step_slug,
457
+ fs.deps_count
458
+ FROM flow_steps fs
459
+ )
460
+ SELECT * FROM created_run INTO v_created_run;
461
+
462
+ -- Send broadcast event for run started
463
+ PERFORM realtime.send(
464
+ jsonb_build_object(
465
+ 'event_type', 'run:started',
466
+ 'run_id', v_created_run.run_id,
467
+ 'flow_slug', v_created_run.flow_slug,
468
+ 'input', v_created_run.input,
469
+ 'status', 'started',
470
+ 'remaining_steps', v_created_run.remaining_steps,
471
+ 'started_at', v_created_run.started_at
472
+ ),
473
+ 'run:started',
474
+ concat('pgflow:run:', v_created_run.run_id),
475
+ false
476
+ );
477
+
478
+ PERFORM pgflow.start_ready_steps(v_created_run.run_id);
479
+
480
+ RETURN QUERY SELECT * FROM pgflow.runs where pgflow.runs.run_id = v_created_run.run_id;
481
+
482
+ end;
483
+ $$;
484
+ -- Create "start_flow_with_states" function
485
+ CREATE FUNCTION "pgflow"."start_flow_with_states" ("flow_slug" text, "input" jsonb, "run_id" uuid DEFAULT NULL::uuid) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$
486
+ DECLARE
487
+ v_run_id UUID;
488
+ BEGIN
489
+ -- Start the flow using existing function
490
+ SELECT r.run_id INTO v_run_id FROM pgflow.start_flow(
491
+ start_flow_with_states.flow_slug,
492
+ start_flow_with_states.input,
493
+ start_flow_with_states.run_id
494
+ ) AS r LIMIT 1;
495
+
496
+ -- Use get_run_with_states to return the complete state
497
+ RETURN pgflow.get_run_with_states(v_run_id);
498
+ END;
499
+ $$;
500
+ -- Drop "start_flow" function
501
+ DROP FUNCTION "pgflow"."start_flow" (text, jsonb);
@@ -0,0 +1,185 @@
1
+ -- Modify "fail_task" function
2
+ CREATE OR REPLACE FUNCTION "pgflow"."fail_task" ("run_id" uuid, "step_slug" text, "task_index" integer, "error_message" text) RETURNS SETOF "pgflow"."step_tasks" LANGUAGE plpgsql SET "search_path" = '' AS $$
3
+ DECLARE
4
+ v_run_failed boolean;
5
+ v_step_failed boolean;
6
+ begin
7
+
8
+ WITH run_lock AS (
9
+ SELECT * FROM pgflow.runs
10
+ WHERE pgflow.runs.run_id = fail_task.run_id
11
+ FOR UPDATE
12
+ ),
13
+ step_lock AS (
14
+ SELECT * FROM pgflow.step_states
15
+ WHERE pgflow.step_states.run_id = fail_task.run_id
16
+ AND pgflow.step_states.step_slug = fail_task.step_slug
17
+ FOR UPDATE
18
+ ),
19
+ flow_info AS (
20
+ SELECT r.flow_slug
21
+ FROM pgflow.runs r
22
+ WHERE r.run_id = fail_task.run_id
23
+ ),
24
+ config AS (
25
+ SELECT
26
+ COALESCE(s.opt_max_attempts, f.opt_max_attempts) AS opt_max_attempts,
27
+ COALESCE(s.opt_base_delay, f.opt_base_delay) AS opt_base_delay
28
+ FROM pgflow.steps s
29
+ JOIN pgflow.flows f ON f.flow_slug = s.flow_slug
30
+ JOIN flow_info fi ON fi.flow_slug = s.flow_slug
31
+ WHERE s.flow_slug = fi.flow_slug AND s.step_slug = fail_task.step_slug
32
+ ),
33
+ fail_or_retry_task as (
34
+ UPDATE pgflow.step_tasks as task
35
+ SET
36
+ status = CASE
37
+ WHEN task.attempts_count < (SELECT opt_max_attempts FROM config) THEN 'queued'
38
+ ELSE 'failed'
39
+ END,
40
+ failed_at = CASE
41
+ WHEN task.attempts_count >= (SELECT opt_max_attempts FROM config) THEN now()
42
+ ELSE NULL
43
+ END,
44
+ started_at = CASE
45
+ WHEN task.attempts_count < (SELECT opt_max_attempts FROM config) THEN NULL
46
+ ELSE task.started_at
47
+ END,
48
+ error_message = fail_task.error_message
49
+ WHERE task.run_id = fail_task.run_id
50
+ AND task.step_slug = fail_task.step_slug
51
+ AND task.task_index = fail_task.task_index
52
+ AND task.status = 'started'
53
+ RETURNING *
54
+ ),
55
+ maybe_fail_step AS (
56
+ UPDATE pgflow.step_states
57
+ SET
58
+ status = CASE
59
+ WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN 'failed'
60
+ ELSE pgflow.step_states.status
61
+ END,
62
+ failed_at = CASE
63
+ WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN now()
64
+ ELSE NULL
65
+ END,
66
+ error_message = CASE
67
+ WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN fail_task.error_message
68
+ ELSE NULL
69
+ END
70
+ FROM fail_or_retry_task
71
+ WHERE pgflow.step_states.run_id = fail_task.run_id
72
+ AND pgflow.step_states.step_slug = fail_task.step_slug
73
+ RETURNING pgflow.step_states.*
74
+ )
75
+ -- Update run status
76
+ UPDATE pgflow.runs
77
+ SET status = CASE
78
+ WHEN (select status from maybe_fail_step) = 'failed' THEN 'failed'
79
+ ELSE status
80
+ END,
81
+ failed_at = CASE
82
+ WHEN (select status from maybe_fail_step) = 'failed' THEN now()
83
+ ELSE NULL
84
+ END
85
+ WHERE pgflow.runs.run_id = fail_task.run_id
86
+ RETURNING (status = 'failed') INTO v_run_failed;
87
+
88
+ -- Check if step failed by querying the step_states table
89
+ SELECT (status = 'failed') INTO v_step_failed
90
+ FROM pgflow.step_states
91
+ WHERE pgflow.step_states.run_id = fail_task.run_id
92
+ AND pgflow.step_states.step_slug = fail_task.step_slug;
93
+
94
+ -- Send broadcast event for step failure if the step was failed
95
+ IF v_step_failed THEN
96
+ PERFORM realtime.send(
97
+ jsonb_build_object(
98
+ 'event_type', 'step:failed',
99
+ 'run_id', fail_task.run_id,
100
+ 'step_slug', fail_task.step_slug,
101
+ 'status', 'failed',
102
+ 'error_message', fail_task.error_message,
103
+ 'failed_at', now()
104
+ ),
105
+ concat('step:', fail_task.step_slug, ':failed'),
106
+ concat('pgflow:run:', fail_task.run_id),
107
+ false
108
+ );
109
+ END IF;
110
+
111
+ -- Send broadcast event for run failure if the run was failed
112
+ IF v_run_failed THEN
113
+ DECLARE
114
+ v_flow_slug text;
115
+ BEGIN
116
+ SELECT flow_slug INTO v_flow_slug FROM pgflow.runs WHERE pgflow.runs.run_id = fail_task.run_id;
117
+
118
+ PERFORM realtime.send(
119
+ jsonb_build_object(
120
+ 'event_type', 'run:failed',
121
+ 'run_id', fail_task.run_id,
122
+ 'flow_slug', v_flow_slug,
123
+ 'status', 'failed',
124
+ 'error_message', fail_task.error_message,
125
+ 'failed_at', now()
126
+ ),
127
+ 'run:failed',
128
+ concat('pgflow:run:', fail_task.run_id),
129
+ false
130
+ );
131
+ END;
132
+ END IF;
133
+
134
+ -- For queued tasks: delay the message for retry with exponential backoff
135
+ PERFORM (
136
+ WITH retry_config AS (
137
+ SELECT
138
+ COALESCE(s.opt_base_delay, f.opt_base_delay) AS base_delay
139
+ FROM pgflow.steps s
140
+ JOIN pgflow.flows f ON f.flow_slug = s.flow_slug
141
+ JOIN pgflow.runs r ON r.flow_slug = f.flow_slug
142
+ WHERE r.run_id = fail_task.run_id
143
+ AND s.step_slug = fail_task.step_slug
144
+ ),
145
+ queued_tasks AS (
146
+ SELECT
147
+ r.flow_slug,
148
+ st.message_id,
149
+ pgflow.calculate_retry_delay((SELECT base_delay FROM retry_config), st.attempts_count) AS calculated_delay
150
+ FROM pgflow.step_tasks st
151
+ JOIN pgflow.runs r ON st.run_id = r.run_id
152
+ WHERE st.run_id = fail_task.run_id
153
+ AND st.step_slug = fail_task.step_slug
154
+ AND st.task_index = fail_task.task_index
155
+ AND st.status = 'queued'
156
+ )
157
+ SELECT pgmq.set_vt(qt.flow_slug, qt.message_id, qt.calculated_delay)
158
+ FROM queued_tasks qt
159
+ WHERE EXISTS (SELECT 1 FROM queued_tasks)
160
+ );
161
+
162
+ -- For failed tasks: archive the message
163
+ PERFORM (
164
+ WITH failed_tasks AS (
165
+ SELECT r.flow_slug, st.message_id
166
+ FROM pgflow.step_tasks st
167
+ JOIN pgflow.runs r ON st.run_id = r.run_id
168
+ WHERE st.run_id = fail_task.run_id
169
+ AND st.step_slug = fail_task.step_slug
170
+ AND st.task_index = fail_task.task_index
171
+ AND st.status = 'failed'
172
+ )
173
+ SELECT pgmq.archive(ft.flow_slug, ft.message_id)
174
+ FROM failed_tasks ft
175
+ WHERE EXISTS (SELECT 1 FROM failed_tasks)
176
+ );
177
+
178
+ return query select *
179
+ from pgflow.step_tasks st
180
+ where st.run_id = fail_task.run_id
181
+ and st.step_slug = fail_task.step_slug
182
+ and st.task_index = fail_task.task_index;
183
+
184
+ end;
185
+ $$;
@@ -0,0 +1,6 @@
1
+ -- Add "calculate_retry_delay" function configuration parameter
2
+ ALTER FUNCTION "pgflow"."calculate_retry_delay" SET "search_path" = '';
3
+ -- Add "is_valid_slug" function configuration parameter
4
+ ALTER FUNCTION "pgflow"."is_valid_slug" SET "search_path" = '';
5
+ -- Add "read_with_poll" function configuration parameter
6
+ ALTER FUNCTION "pgflow"."read_with_poll" SET "search_path" = '';