@pgflow/core 0.4.1 → 0.5.0
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/CHANGELOG.md +23 -0
- package/dist/package.json +1 -1
- package/dist/supabase/migrations/20250619195327_pgflow_fix_fail_task_missing_realtime_event.sql +185 -0
- package/dist/supabase/migrations/20250627090700_pgflow_fix_function_search_paths.sql +6 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +2 -2
package/dist/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @pgflow/core
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- @pgflow/dsl@0.5.0
|
|
8
|
+
|
|
9
|
+
## 0.4.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- fa78968: Fix Supabase Security Advisor warnings by setting empty search_path on functions
|
|
14
|
+
- @pgflow/dsl@0.4.3
|
|
15
|
+
|
|
16
|
+
## 0.4.2
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 220c867: Fix step:failed events not being broadcast when steps fail
|
|
21
|
+
|
|
22
|
+
Fixed a bug where step:failed events were not being broadcast to real-time subscribers when a step failed permanently. The issue was caused by PostgreSQL optimizing away the CTE that contained the realtime.send() call. The fix replaces the CTE approach with a direct PERFORM statement in the function body, ensuring the event is always sent when a step fails.
|
|
23
|
+
|
|
24
|
+
- @pgflow/dsl@0.4.2
|
|
25
|
+
|
|
3
26
|
## 0.4.1
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/package.json
CHANGED
package/dist/supabase/migrations/20250619195327_pgflow_fix_fail_task_missing_realtime_event.sql
ADDED
|
@@ -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" = '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"5.
|
|
1
|
+
{"version":"5.8.3"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgflow/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"postgres": "^3.4.5",
|
|
27
|
-
"@pgflow/dsl": "0.
|
|
27
|
+
"@pgflow/dsl": "0.5.0"
|
|
28
28
|
},
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public"
|