pgflow 0.0.16 → 0.0.17-yolo
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/commands/install/copy-migrations.d.ts +4 -0
- package/dist/commands/install/copy-migrations.d.ts.map +1 -0
- package/dist/commands/install/copy-migrations.js +70 -0
- package/dist/commands/install/index.d.ts +4 -0
- package/dist/commands/install/index.d.ts.map +1 -0
- package/dist/commands/install/index.js +32 -0
- package/dist/commands/install/supabase-path-prompt.d.ts +2 -0
- package/dist/commands/install/supabase-path-prompt.d.ts.map +1 -0
- package/dist/commands/install/supabase-path-prompt.js +24 -0
- package/dist/commands/install/update-config-toml.d.ts +17 -0
- package/dist/commands/install/update-config-toml.d.ts.map +1 -0
- package/dist/commands/install/update-config-toml.js +87 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/migrations/000000_schema.sql +150 -0
- package/dist/migrations/000005_create_flow.sql +29 -0
- package/dist/migrations/000010_add_step.sql +48 -0
- package/dist/migrations/000015_start_ready_steps.sql +45 -0
- package/dist/migrations/000020_start_flow.sql +46 -0
- package/dist/migrations/000030_read_with_poll_backport.sql +70 -0
- package/dist/migrations/000040_poll_for_tasks.sql +100 -0
- package/dist/migrations/000045_maybe_complete_run.sql +30 -0
- package/dist/migrations/000050_complete_task.sql +98 -0
- package/dist/migrations/000055_calculate_retry_delay.sql +11 -0
- package/dist/migrations/000060_fail_task.sql +124 -0
- package/dist/migrations/000_edge_worker_initial.sql +86 -0
- package/dist/package.json +38 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +13 -10
- package/LICENSE.md +0 -660
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--------------------------------------------------------------------------------
|
|
2
|
+
-- Read With Poll --------------------------------------------------------------
|
|
3
|
+
-- --
|
|
4
|
+
-- This is a backport of the pgmq.read_with_poll function from version 1.5.0 --
|
|
5
|
+
-- It is required because it fixes a bug with high CPU usage and Supabase --
|
|
6
|
+
-- is still using version 1.4.4. --
|
|
7
|
+
-- --
|
|
8
|
+
-- It is slightly modified (removed headers which are not available in 1.4.1) --
|
|
9
|
+
-- --
|
|
10
|
+
-- This will be removed once Supabase upgrades to 1.5.0 or higher. --
|
|
11
|
+
--------------------------------------------------------------------------------
|
|
12
|
+
create function pgflow.read_with_poll(
|
|
13
|
+
queue_name TEXT,
|
|
14
|
+
vt INTEGER,
|
|
15
|
+
qty INTEGER,
|
|
16
|
+
max_poll_seconds INTEGER default 5,
|
|
17
|
+
poll_interval_ms INTEGER default 100,
|
|
18
|
+
conditional JSONB default '{}'
|
|
19
|
+
)
|
|
20
|
+
returns setof PGMQ.MESSAGE_RECORD as $$
|
|
21
|
+
DECLARE
|
|
22
|
+
r pgmq.message_record;
|
|
23
|
+
stop_at TIMESTAMP;
|
|
24
|
+
sql TEXT;
|
|
25
|
+
qtable TEXT := pgmq.format_table_name(queue_name, 'q');
|
|
26
|
+
BEGIN
|
|
27
|
+
stop_at := clock_timestamp() + make_interval(secs => max_poll_seconds);
|
|
28
|
+
LOOP
|
|
29
|
+
IF (SELECT clock_timestamp() >= stop_at) THEN
|
|
30
|
+
RETURN;
|
|
31
|
+
END IF;
|
|
32
|
+
|
|
33
|
+
sql := FORMAT(
|
|
34
|
+
$QUERY$
|
|
35
|
+
WITH cte AS
|
|
36
|
+
(
|
|
37
|
+
SELECT msg_id
|
|
38
|
+
FROM pgmq.%I
|
|
39
|
+
WHERE vt <= clock_timestamp() AND CASE
|
|
40
|
+
WHEN %L != '{}'::jsonb THEN (message @> %2$L)::integer
|
|
41
|
+
ELSE 1
|
|
42
|
+
END = 1
|
|
43
|
+
ORDER BY msg_id ASC
|
|
44
|
+
LIMIT $1
|
|
45
|
+
FOR UPDATE SKIP LOCKED
|
|
46
|
+
)
|
|
47
|
+
UPDATE pgmq.%I m
|
|
48
|
+
SET
|
|
49
|
+
vt = clock_timestamp() + %L,
|
|
50
|
+
read_ct = read_ct + 1
|
|
51
|
+
FROM cte
|
|
52
|
+
WHERE m.msg_id = cte.msg_id
|
|
53
|
+
RETURNING m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message;
|
|
54
|
+
$QUERY$,
|
|
55
|
+
qtable, conditional, qtable, make_interval(secs => vt)
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
FOR r IN
|
|
59
|
+
EXECUTE sql USING qty
|
|
60
|
+
LOOP
|
|
61
|
+
RETURN NEXT r;
|
|
62
|
+
END LOOP;
|
|
63
|
+
IF FOUND THEN
|
|
64
|
+
RETURN;
|
|
65
|
+
ELSE
|
|
66
|
+
PERFORM pg_sleep(poll_interval_ms::numeric / 1000);
|
|
67
|
+
END IF;
|
|
68
|
+
END LOOP;
|
|
69
|
+
END;
|
|
70
|
+
$$ language plpgsql;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
create or replace function pgflow.poll_for_tasks(
|
|
2
|
+
queue_name text,
|
|
3
|
+
vt integer,
|
|
4
|
+
qty integer,
|
|
5
|
+
max_poll_seconds integer default 5,
|
|
6
|
+
poll_interval_ms integer default 100
|
|
7
|
+
)
|
|
8
|
+
returns setof pgflow.step_task_record
|
|
9
|
+
volatile
|
|
10
|
+
set search_path to ''
|
|
11
|
+
as $$
|
|
12
|
+
|
|
13
|
+
with read_messages as (
|
|
14
|
+
select *
|
|
15
|
+
from pgflow.read_with_poll(
|
|
16
|
+
queue_name,
|
|
17
|
+
vt,
|
|
18
|
+
qty,
|
|
19
|
+
max_poll_seconds,
|
|
20
|
+
poll_interval_ms
|
|
21
|
+
)
|
|
22
|
+
),
|
|
23
|
+
tasks as (
|
|
24
|
+
select
|
|
25
|
+
task.flow_slug,
|
|
26
|
+
task.run_id,
|
|
27
|
+
task.step_slug,
|
|
28
|
+
task.task_index,
|
|
29
|
+
task.message_id
|
|
30
|
+
from pgflow.step_tasks as task
|
|
31
|
+
join read_messages as message on message.msg_id = task.message_id
|
|
32
|
+
where task.message_id = message.msg_id
|
|
33
|
+
and task.status = 'queued'
|
|
34
|
+
),
|
|
35
|
+
increment_attempts as (
|
|
36
|
+
update pgflow.step_tasks
|
|
37
|
+
set attempts_count = attempts_count + 1
|
|
38
|
+
from tasks
|
|
39
|
+
where step_tasks.message_id = tasks.message_id
|
|
40
|
+
and status = 'queued'
|
|
41
|
+
),
|
|
42
|
+
runs as (
|
|
43
|
+
select
|
|
44
|
+
r.run_id,
|
|
45
|
+
r.input
|
|
46
|
+
from pgflow.runs r
|
|
47
|
+
where r.run_id in (select run_id from tasks)
|
|
48
|
+
),
|
|
49
|
+
deps as (
|
|
50
|
+
select
|
|
51
|
+
st.run_id,
|
|
52
|
+
st.step_slug,
|
|
53
|
+
dep.dep_slug,
|
|
54
|
+
dep_task.output as dep_output
|
|
55
|
+
from tasks st
|
|
56
|
+
join pgflow.deps dep on dep.flow_slug = st.flow_slug and dep.step_slug = st.step_slug
|
|
57
|
+
join pgflow.step_tasks dep_task on
|
|
58
|
+
dep_task.run_id = st.run_id and
|
|
59
|
+
dep_task.step_slug = dep.dep_slug and
|
|
60
|
+
dep_task.status = 'completed'
|
|
61
|
+
),
|
|
62
|
+
deps_outputs as (
|
|
63
|
+
select
|
|
64
|
+
d.run_id,
|
|
65
|
+
d.step_slug,
|
|
66
|
+
jsonb_object_agg(d.dep_slug, d.dep_output) as deps_output
|
|
67
|
+
from deps d
|
|
68
|
+
group by d.run_id, d.step_slug
|
|
69
|
+
),
|
|
70
|
+
timeouts as (
|
|
71
|
+
select
|
|
72
|
+
task.message_id,
|
|
73
|
+
coalesce(step.opt_timeout, flow.opt_timeout) + 2 as vt_delay
|
|
74
|
+
from tasks task
|
|
75
|
+
join pgflow.flows flow on flow.flow_slug = task.flow_slug
|
|
76
|
+
join pgflow.steps step on step.flow_slug = task.flow_slug and step.step_slug = task.step_slug
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
select
|
|
80
|
+
st.flow_slug,
|
|
81
|
+
st.run_id,
|
|
82
|
+
st.step_slug,
|
|
83
|
+
jsonb_build_object('run', r.input) ||
|
|
84
|
+
coalesce(dep_out.deps_output, '{}'::jsonb) as input,
|
|
85
|
+
st.message_id as msg_id
|
|
86
|
+
from tasks st
|
|
87
|
+
join runs r on st.run_id = r.run_id
|
|
88
|
+
left join deps_outputs dep_out on
|
|
89
|
+
dep_out.run_id = st.run_id and
|
|
90
|
+
dep_out.step_slug = st.step_slug
|
|
91
|
+
cross join lateral (
|
|
92
|
+
-- TODO: this is slow because it calls set_vt for each row, and set_vt
|
|
93
|
+
-- builds dynamic query from string every time it is called
|
|
94
|
+
-- implement set_vt_batch(msgs_ids bigint[], vt_delays int[])
|
|
95
|
+
select pgmq.set_vt(queue_name, st.message_id,
|
|
96
|
+
(select t.vt_delay from timeouts t where t.message_id = st.message_id)
|
|
97
|
+
)
|
|
98
|
+
) set_vt;
|
|
99
|
+
|
|
100
|
+
$$ language sql;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
create or replace function pgflow.maybe_complete_run(run_id uuid)
|
|
2
|
+
returns void
|
|
3
|
+
language sql
|
|
4
|
+
volatile
|
|
5
|
+
set search_path to ''
|
|
6
|
+
as $$
|
|
7
|
+
-- Update run status to completed and set output when there are no remaining steps
|
|
8
|
+
-- All done in a single declarative SQL statement
|
|
9
|
+
UPDATE pgflow.runs
|
|
10
|
+
SET
|
|
11
|
+
status = 'completed',
|
|
12
|
+
output = (
|
|
13
|
+
-- Get outputs from final steps (steps that are not dependencies for other steps)
|
|
14
|
+
SELECT jsonb_object_agg(st.step_slug, st.output)
|
|
15
|
+
FROM pgflow.step_tasks st
|
|
16
|
+
JOIN pgflow.step_states ss ON ss.run_id = st.run_id AND ss.step_slug = st.step_slug
|
|
17
|
+
JOIN pgflow.runs r ON r.run_id = ss.run_id AND r.flow_slug = ss.flow_slug
|
|
18
|
+
WHERE st.run_id = maybe_complete_run.run_id
|
|
19
|
+
AND st.status = 'completed'
|
|
20
|
+
AND NOT EXISTS (
|
|
21
|
+
SELECT 1
|
|
22
|
+
FROM pgflow.deps d
|
|
23
|
+
WHERE d.flow_slug = ss.flow_slug
|
|
24
|
+
AND d.dep_slug = ss.step_slug
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
WHERE pgflow.runs.run_id = maybe_complete_run.run_id
|
|
28
|
+
AND pgflow.runs.remaining_steps = 0
|
|
29
|
+
AND pgflow.runs.status != 'completed';
|
|
30
|
+
$$;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
-- drop function if exists pgflow.complete_task(uuid, text, int, jsonb);
|
|
2
|
+
create or replace function pgflow.complete_task(
|
|
3
|
+
run_id uuid,
|
|
4
|
+
step_slug text,
|
|
5
|
+
task_index int,
|
|
6
|
+
output jsonb
|
|
7
|
+
)
|
|
8
|
+
returns setof pgflow.step_tasks
|
|
9
|
+
language plpgsql
|
|
10
|
+
volatile
|
|
11
|
+
set search_path to ''
|
|
12
|
+
as $$
|
|
13
|
+
begin
|
|
14
|
+
|
|
15
|
+
WITH run_lock AS (
|
|
16
|
+
SELECT * FROM pgflow.runs
|
|
17
|
+
WHERE pgflow.runs.run_id = complete_task.run_id
|
|
18
|
+
FOR UPDATE
|
|
19
|
+
),
|
|
20
|
+
step_lock AS (
|
|
21
|
+
SELECT * FROM pgflow.step_states
|
|
22
|
+
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
23
|
+
AND pgflow.step_states.step_slug = complete_task.step_slug
|
|
24
|
+
FOR UPDATE
|
|
25
|
+
),
|
|
26
|
+
task AS (
|
|
27
|
+
UPDATE pgflow.step_tasks
|
|
28
|
+
SET
|
|
29
|
+
status = 'completed',
|
|
30
|
+
output = complete_task.output
|
|
31
|
+
WHERE pgflow.step_tasks.run_id = complete_task.run_id
|
|
32
|
+
AND pgflow.step_tasks.step_slug = complete_task.step_slug
|
|
33
|
+
AND pgflow.step_tasks.task_index = complete_task.task_index
|
|
34
|
+
RETURNING *
|
|
35
|
+
),
|
|
36
|
+
step_state AS (
|
|
37
|
+
UPDATE pgflow.step_states
|
|
38
|
+
SET
|
|
39
|
+
status = CASE
|
|
40
|
+
WHEN pgflow.step_states.remaining_tasks = 1 THEN 'completed' -- Will be 0 after decrement
|
|
41
|
+
ELSE 'started'
|
|
42
|
+
END,
|
|
43
|
+
remaining_tasks = pgflow.step_states.remaining_tasks - 1
|
|
44
|
+
FROM task
|
|
45
|
+
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
46
|
+
AND pgflow.step_states.step_slug = complete_task.step_slug
|
|
47
|
+
RETURNING pgflow.step_states.*
|
|
48
|
+
),
|
|
49
|
+
-- Find all dependent steps if the current step was completed
|
|
50
|
+
dependent_steps AS (
|
|
51
|
+
SELECT d.step_slug AS dependent_step_slug
|
|
52
|
+
FROM pgflow.deps d
|
|
53
|
+
JOIN step_state s ON s.status = 'completed' AND d.flow_slug = s.flow_slug
|
|
54
|
+
WHERE d.dep_slug = complete_task.step_slug
|
|
55
|
+
ORDER BY d.step_slug -- Ensure consistent ordering
|
|
56
|
+
),
|
|
57
|
+
-- Lock dependent steps before updating
|
|
58
|
+
dependent_steps_lock AS (
|
|
59
|
+
SELECT * FROM pgflow.step_states
|
|
60
|
+
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
61
|
+
AND pgflow.step_states.step_slug IN (SELECT dependent_step_slug FROM dependent_steps)
|
|
62
|
+
FOR UPDATE
|
|
63
|
+
),
|
|
64
|
+
-- Update all dependent steps
|
|
65
|
+
dependent_steps_update AS (
|
|
66
|
+
UPDATE pgflow.step_states
|
|
67
|
+
SET remaining_deps = pgflow.step_states.remaining_deps - 1
|
|
68
|
+
FROM dependent_steps
|
|
69
|
+
WHERE pgflow.step_states.run_id = complete_task.run_id
|
|
70
|
+
AND pgflow.step_states.step_slug = dependent_steps.dependent_step_slug
|
|
71
|
+
)
|
|
72
|
+
-- Only decrement remaining_steps, don't update status
|
|
73
|
+
UPDATE pgflow.runs
|
|
74
|
+
SET remaining_steps = pgflow.runs.remaining_steps - 1
|
|
75
|
+
FROM step_state
|
|
76
|
+
WHERE pgflow.runs.run_id = complete_task.run_id
|
|
77
|
+
AND step_state.status = 'completed';
|
|
78
|
+
|
|
79
|
+
PERFORM pgmq.archive(
|
|
80
|
+
queue_name => (SELECT run.flow_slug FROM pgflow.runs AS run WHERE run.run_id = complete_task.run_id),
|
|
81
|
+
msg_id => (SELECT message_id FROM pgflow.step_tasks AS step_task
|
|
82
|
+
WHERE step_task.run_id = complete_task.run_id
|
|
83
|
+
AND step_task.step_slug = complete_task.step_slug
|
|
84
|
+
AND step_task.task_index = complete_task.task_index)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
PERFORM pgflow.start_ready_steps(complete_task.run_id);
|
|
88
|
+
|
|
89
|
+
PERFORM pgflow.maybe_complete_run(complete_task.run_id);
|
|
90
|
+
|
|
91
|
+
RETURN QUERY SELECT *
|
|
92
|
+
FROM pgflow.step_tasks AS step_task
|
|
93
|
+
WHERE step_task.run_id = complete_task.run_id
|
|
94
|
+
AND step_task.step_slug = complete_task.step_slug
|
|
95
|
+
AND step_task.task_index = complete_task.task_index;
|
|
96
|
+
|
|
97
|
+
end;
|
|
98
|
+
$$;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
create or replace function pgflow.fail_task(
|
|
2
|
+
run_id uuid,
|
|
3
|
+
step_slug text,
|
|
4
|
+
task_index int,
|
|
5
|
+
error_message text
|
|
6
|
+
)
|
|
7
|
+
returns setof pgflow.step_tasks
|
|
8
|
+
language plpgsql
|
|
9
|
+
volatile
|
|
10
|
+
set search_path to ''
|
|
11
|
+
as $$
|
|
12
|
+
begin
|
|
13
|
+
|
|
14
|
+
WITH run_lock AS (
|
|
15
|
+
SELECT * FROM pgflow.runs
|
|
16
|
+
WHERE pgflow.runs.run_id = fail_task.run_id
|
|
17
|
+
FOR UPDATE
|
|
18
|
+
),
|
|
19
|
+
step_lock AS (
|
|
20
|
+
SELECT * FROM pgflow.step_states
|
|
21
|
+
WHERE pgflow.step_states.run_id = fail_task.run_id
|
|
22
|
+
AND pgflow.step_states.step_slug = fail_task.step_slug
|
|
23
|
+
FOR UPDATE
|
|
24
|
+
),
|
|
25
|
+
flow_info AS (
|
|
26
|
+
SELECT r.flow_slug
|
|
27
|
+
FROM pgflow.runs r
|
|
28
|
+
WHERE r.run_id = fail_task.run_id
|
|
29
|
+
),
|
|
30
|
+
config AS (
|
|
31
|
+
SELECT
|
|
32
|
+
COALESCE(s.opt_max_attempts, f.opt_max_attempts) AS opt_max_attempts,
|
|
33
|
+
COALESCE(s.opt_base_delay, f.opt_base_delay) AS opt_base_delay
|
|
34
|
+
FROM pgflow.steps s
|
|
35
|
+
JOIN pgflow.flows f ON f.flow_slug = s.flow_slug
|
|
36
|
+
JOIN flow_info fi ON fi.flow_slug = s.flow_slug
|
|
37
|
+
WHERE s.flow_slug = fi.flow_slug AND s.step_slug = fail_task.step_slug
|
|
38
|
+
),
|
|
39
|
+
|
|
40
|
+
fail_or_retry_task as (
|
|
41
|
+
UPDATE pgflow.step_tasks as task
|
|
42
|
+
SET
|
|
43
|
+
status = CASE
|
|
44
|
+
WHEN task.attempts_count < (SELECT opt_max_attempts FROM config) THEN 'queued'
|
|
45
|
+
ELSE 'failed'
|
|
46
|
+
END,
|
|
47
|
+
error_message = fail_task.error_message
|
|
48
|
+
WHERE task.run_id = fail_task.run_id
|
|
49
|
+
AND task.step_slug = fail_task.step_slug
|
|
50
|
+
AND task.task_index = fail_task.task_index
|
|
51
|
+
AND task.status = 'queued'
|
|
52
|
+
RETURNING *
|
|
53
|
+
),
|
|
54
|
+
maybe_fail_step AS (
|
|
55
|
+
UPDATE pgflow.step_states
|
|
56
|
+
SET
|
|
57
|
+
status = CASE
|
|
58
|
+
WHEN (select fail_or_retry_task.status from fail_or_retry_task) = 'failed' THEN 'failed'
|
|
59
|
+
ELSE pgflow.step_states.status
|
|
60
|
+
END
|
|
61
|
+
FROM fail_or_retry_task
|
|
62
|
+
WHERE pgflow.step_states.run_id = fail_task.run_id
|
|
63
|
+
AND pgflow.step_states.step_slug = fail_task.step_slug
|
|
64
|
+
RETURNING pgflow.step_states.*
|
|
65
|
+
)
|
|
66
|
+
UPDATE pgflow.runs
|
|
67
|
+
SET status = CASE
|
|
68
|
+
WHEN (select status from maybe_fail_step) = 'failed' THEN 'failed'
|
|
69
|
+
ELSE status
|
|
70
|
+
END
|
|
71
|
+
WHERE pgflow.runs.run_id = fail_task.run_id;
|
|
72
|
+
|
|
73
|
+
-- For queued tasks: delay the message for retry with exponential backoff
|
|
74
|
+
PERFORM (
|
|
75
|
+
WITH retry_config AS (
|
|
76
|
+
SELECT
|
|
77
|
+
COALESCE(s.opt_base_delay, f.opt_base_delay) AS base_delay
|
|
78
|
+
FROM pgflow.steps s
|
|
79
|
+
JOIN pgflow.flows f ON f.flow_slug = s.flow_slug
|
|
80
|
+
JOIN pgflow.runs r ON r.flow_slug = f.flow_slug
|
|
81
|
+
WHERE r.run_id = fail_task.run_id
|
|
82
|
+
AND s.step_slug = fail_task.step_slug
|
|
83
|
+
),
|
|
84
|
+
queued_tasks AS (
|
|
85
|
+
SELECT
|
|
86
|
+
r.flow_slug,
|
|
87
|
+
st.message_id,
|
|
88
|
+
pgflow.calculate_retry_delay((SELECT base_delay FROM retry_config), st.attempts_count) AS calculated_delay
|
|
89
|
+
FROM pgflow.step_tasks st
|
|
90
|
+
JOIN pgflow.runs r ON st.run_id = r.run_id
|
|
91
|
+
WHERE st.run_id = fail_task.run_id
|
|
92
|
+
AND st.step_slug = fail_task.step_slug
|
|
93
|
+
AND st.task_index = fail_task.task_index
|
|
94
|
+
AND st.status = 'queued'
|
|
95
|
+
)
|
|
96
|
+
SELECT pgmq.set_vt(qt.flow_slug, qt.message_id, qt.calculated_delay)
|
|
97
|
+
FROM queued_tasks qt
|
|
98
|
+
WHERE EXISTS (SELECT 1 FROM queued_tasks)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
-- For failed tasks: archive the message
|
|
102
|
+
PERFORM (
|
|
103
|
+
WITH failed_tasks AS (
|
|
104
|
+
SELECT r.flow_slug, st.message_id
|
|
105
|
+
FROM pgflow.step_tasks st
|
|
106
|
+
JOIN pgflow.runs r ON st.run_id = r.run_id
|
|
107
|
+
WHERE st.run_id = fail_task.run_id
|
|
108
|
+
AND st.step_slug = fail_task.step_slug
|
|
109
|
+
AND st.task_index = fail_task.task_index
|
|
110
|
+
AND st.status = 'failed'
|
|
111
|
+
)
|
|
112
|
+
SELECT pgmq.archive(ft.flow_slug, ft.message_id)
|
|
113
|
+
FROM failed_tasks ft
|
|
114
|
+
WHERE EXISTS (SELECT 1 FROM failed_tasks)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return query select *
|
|
118
|
+
from pgflow.step_tasks st
|
|
119
|
+
where st.run_id = fail_task.run_id
|
|
120
|
+
and st.step_slug = fail_task.step_slug
|
|
121
|
+
and st.task_index = fail_task.task_index;
|
|
122
|
+
|
|
123
|
+
end;
|
|
124
|
+
$$;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
create extension if not exists pgmq version '1.4.4';
|
|
2
|
+
|
|
3
|
+
create schema if not exists edge_worker;
|
|
4
|
+
|
|
5
|
+
-------------------------------------------------------------------------------
|
|
6
|
+
-- Workers Table --------------------------------------------------------------
|
|
7
|
+
-------------------------------------------------------------------------------
|
|
8
|
+
create table if not exists edge_worker.workers (
|
|
9
|
+
worker_id uuid not null primary key,
|
|
10
|
+
queue_name text not null,
|
|
11
|
+
function_name text not null,
|
|
12
|
+
started_at timestamptz not null default now(),
|
|
13
|
+
stopped_at timestamptz,
|
|
14
|
+
last_heartbeat_at timestamptz not null default now()
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
--------------------------------------------------------------------------------
|
|
18
|
+
-- Read With Poll --------------------------------------------------------------
|
|
19
|
+
-- --
|
|
20
|
+
-- This is a backport of the pgmq.read_with_poll function from version 1.5.0 --
|
|
21
|
+
-- It is required because it fixes a bug with high CPU usage and Supabase --
|
|
22
|
+
-- is still using version 1.4.4. --
|
|
23
|
+
-- --
|
|
24
|
+
-- It is slightly modified (removed headers which are not available in 1.4.1) --
|
|
25
|
+
-- --
|
|
26
|
+
-- This will be removed once Supabase upgrades to 1.5.0 or higher. --
|
|
27
|
+
--------------------------------------------------------------------------------
|
|
28
|
+
create function edge_worker.read_with_poll(
|
|
29
|
+
queue_name text,
|
|
30
|
+
vt integer,
|
|
31
|
+
qty integer,
|
|
32
|
+
max_poll_seconds integer default 5,
|
|
33
|
+
poll_interval_ms integer default 100,
|
|
34
|
+
conditional jsonb default '{}'
|
|
35
|
+
)
|
|
36
|
+
returns setof pgmq.message_record as $$
|
|
37
|
+
DECLARE
|
|
38
|
+
r pgmq.message_record;
|
|
39
|
+
stop_at timestamp;
|
|
40
|
+
sql text;
|
|
41
|
+
qtable text := pgmq.format_table_name(queue_name, 'q');
|
|
42
|
+
BEGIN
|
|
43
|
+
stop_at := clock_timestamp() + make_interval(secs => max_poll_seconds);
|
|
44
|
+
LOOP
|
|
45
|
+
IF (SELECT clock_timestamp() >= stop_at) THEN
|
|
46
|
+
RETURN;
|
|
47
|
+
END IF;
|
|
48
|
+
|
|
49
|
+
sql := FORMAT(
|
|
50
|
+
$QUERY$
|
|
51
|
+
WITH cte AS
|
|
52
|
+
(
|
|
53
|
+
SELECT msg_id
|
|
54
|
+
FROM pgmq.%I
|
|
55
|
+
WHERE vt <= clock_timestamp() AND CASE
|
|
56
|
+
WHEN %L != '{}'::jsonb THEN (message @> %2$L)::integer
|
|
57
|
+
ELSE 1
|
|
58
|
+
END = 1
|
|
59
|
+
ORDER BY msg_id ASC
|
|
60
|
+
LIMIT $1
|
|
61
|
+
FOR UPDATE SKIP LOCKED
|
|
62
|
+
)
|
|
63
|
+
UPDATE pgmq.%I m
|
|
64
|
+
SET
|
|
65
|
+
vt = clock_timestamp() + %L,
|
|
66
|
+
read_ct = read_ct + 1
|
|
67
|
+
FROM cte
|
|
68
|
+
WHERE m.msg_id = cte.msg_id
|
|
69
|
+
RETURNING m.msg_id, m.read_ct, m.enqueued_at, m.vt, m.message;
|
|
70
|
+
$QUERY$,
|
|
71
|
+
qtable, conditional, qtable, make_interval(secs => vt)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
FOR r IN
|
|
75
|
+
EXECUTE sql USING qty
|
|
76
|
+
LOOP
|
|
77
|
+
RETURN NEXT r;
|
|
78
|
+
END LOOP;
|
|
79
|
+
IF FOUND THEN
|
|
80
|
+
RETURN;
|
|
81
|
+
ELSE
|
|
82
|
+
PERFORM pg_sleep(poll_interval_ms::numeric / 1000);
|
|
83
|
+
END IF;
|
|
84
|
+
END LOOP;
|
|
85
|
+
END;
|
|
86
|
+
$$ language plpgsql;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pgflow",
|
|
3
|
+
"version": "0.0.17-yolo",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"typings": "./dist/index.d.ts",
|
|
7
|
+
"bin": "./dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^22.14.1",
|
|
19
|
+
"tsx": "^4.19.3"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@clack/prompts": "^0.10.1",
|
|
23
|
+
"@commander-js/extra-typings": "^13.1.0",
|
|
24
|
+
"chalk": "^5.4.1",
|
|
25
|
+
"commander": "^13.1.0",
|
|
26
|
+
"toml-patch": "^0.2.3"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"directory": "."
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"postinstall": "chmod +x dist/index.js || true"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"5.6.3"}
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgflow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17-yolo",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "./dist/
|
|
6
|
-
"typings": "./dist/
|
|
7
|
-
"bin": "./dist/
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"typings": "./dist/index.d.ts",
|
|
7
|
+
"bin": "./dist/index.js",
|
|
8
8
|
"exports": {
|
|
9
9
|
"./package.json": "./package.json",
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./dist/
|
|
12
|
-
"import": "./dist/
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"types": "./dist/
|
|
16
|
-
"module": "./dist/
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^22.14.1",
|
|
19
19
|
"tsx": "^4.19.3"
|
|
@@ -31,5 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"dist"
|
|
34
|
-
]
|
|
35
|
-
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"postinstall": "chmod +x dist/index.js || true"
|
|
37
|
+
}
|
|
38
|
+
}
|