@pgflow/core 0.0.18 → 0.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgflow/core",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "license": "AGPL-3.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,7 +8,7 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
10
  "dist",
11
- "supabase/migrations/*.sql"
11
+ "dist/supabase/migrations"
12
12
  ],
13
13
  "private": false,
14
14
  "exports": {
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "postgres": "^3.4.5",
27
- "@pgflow/dsl": "0.0.18"
27
+ "@pgflow/dsl": "0.0.19"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public",
@@ -1,150 +0,0 @@
1
- create extension if not exists pgmq version '1.4.4';
2
-
3
- create schema if not exists pgflow;
4
- set search_path to pgflow;
5
-
6
- --------------------------------------------------------------------------
7
- ------------------ TODO: fix me, UNSECURE --------------------------------
8
- --------------------------------------------------------------------------
9
- grant usage on schema pgflow to anon, authenticated, service_role;
10
- grant all on all tables in schema pgflow to anon, authenticated, service_role;
11
- grant all on all routines in schema pgflow to anon, authenticated, service_role;
12
- grant all on all sequences in schema pgflow to anon,
13
- authenticated,
14
- service_role;
15
- alter default privileges for role postgres in schema pgflow
16
- grant all on tables to anon, authenticated, service_role;
17
- alter default privileges for role postgres in schema pgflow
18
- grant all on routines to anon, authenticated, service_role;
19
- alter default privileges for role postgres in schema pgflow
20
- grant all on sequences to anon, authenticated, service_role;
21
-
22
- ------------------------------------------
23
- -- Core flow definition tables
24
- ------------------------------------------
25
-
26
- ----- check constraint helper function -------
27
- create or replace function pgflow.is_valid_slug(
28
- slug text
29
- )
30
- returns boolean
31
- language plpgsql
32
- immutable
33
- as $$
34
- begin
35
- return
36
- slug is not null
37
- and slug <> ''
38
- and length(slug) <= 128
39
- and slug ~ '^[a-zA-Z_][a-zA-Z0-9_]*$'
40
- and slug NOT IN ('run'); -- reserved words
41
- end;
42
- $$;
43
-
44
- -- Flows table - stores flow definitions
45
- create table pgflow.flows (
46
- flow_slug text primary key not null, -- Unique identifier for the flow
47
- opt_max_attempts int not null default 3,
48
- opt_base_delay int not null default 1,
49
- opt_timeout int not null default 60,
50
- constraint slug_is_valid check (is_valid_slug(flow_slug)),
51
- constraint opt_max_attempts_is_nonnegative check (opt_max_attempts >= 0),
52
- constraint opt_base_delay_is_nonnegative check (opt_base_delay >= 0),
53
- constraint opt_timeout_is_positive check (opt_timeout > 0)
54
- );
55
-
56
- -- Steps table - stores individual steps within flows
57
- create table pgflow.steps (
58
- flow_slug text not null references flows (flow_slug),
59
- step_slug text not null,
60
- step_type text not null default 'single',
61
- deps_count int not null default 0 check (deps_count >= 0),
62
- opt_max_attempts int,
63
- opt_base_delay int,
64
- opt_timeout int,
65
- primary key (flow_slug, step_slug),
66
- check (is_valid_slug(step_slug)),
67
- check (step_type in ('single')),
68
- constraint opt_max_attempts_is_nonnegative check (opt_max_attempts is null or opt_max_attempts >= 0),
69
- constraint opt_base_delay_is_nonnegative check (opt_base_delay is null or opt_base_delay >= 0),
70
- constraint opt_timeout_is_positive check (opt_timeout is null or opt_timeout > 0)
71
- );
72
-
73
- -- Dependencies table - stores relationships between steps
74
- create table pgflow.deps (
75
- flow_slug text not null references pgflow.flows (flow_slug),
76
- dep_slug text not null, -- slug of the dependency
77
- step_slug text not null, -- slug of the dependent
78
- primary key (flow_slug, dep_slug, step_slug),
79
- foreign key (flow_slug, dep_slug)
80
- references pgflow.steps (flow_slug, step_slug),
81
- foreign key (flow_slug, step_slug)
82
- references pgflow.steps (flow_slug, step_slug),
83
- check (dep_slug != step_slug) -- Prevent self-dependencies
84
- );
85
-
86
- ------------------------------------------
87
- -- Runtime State Tables
88
- ------------------------------------------
89
-
90
- -- Runs table - tracks flow execution instances
91
- create table pgflow.runs (
92
- run_id uuid primary key not null default gen_random_uuid(),
93
- flow_slug text not null references pgflow.flows (flow_slug), -- denormalized
94
- status text not null default 'started',
95
- input jsonb not null,
96
- output jsonb,
97
- remaining_steps int not null default 0 check (remaining_steps >= 0),
98
- check (status in ('started', 'failed', 'completed'))
99
- );
100
-
101
- -- Step states table - tracks the state of individual steps within a run
102
- create table pgflow.step_states (
103
- flow_slug text not null references pgflow.flows (flow_slug),
104
- run_id uuid not null references pgflow.runs (run_id),
105
- step_slug text not null,
106
- status text not null default 'created',
107
- remaining_tasks int not null default 1 check (remaining_tasks >= 0),
108
- remaining_deps int not null default 0 check (remaining_deps >= 0),
109
- primary key (run_id, step_slug),
110
- foreign key (flow_slug, step_slug)
111
- references pgflow.steps (flow_slug, step_slug),
112
- check (status in ('created', 'started', 'completed', 'failed')),
113
- check (status != 'completed' or remaining_tasks = 0)
114
- );
115
-
116
- -- Step tasks table - tracks units of work for step
117
- create table pgflow.step_tasks (
118
- flow_slug text not null references pgflow.flows (flow_slug),
119
- run_id uuid not null references pgflow.runs (run_id),
120
- step_slug text not null,
121
- message_id bigint,
122
- task_index int not null default 0,
123
- status text not null default 'queued',
124
- attempts_count int not null default 0,
125
- error_message text,
126
- output jsonb,
127
- constraint step_tasks_pkey primary key (run_id, step_slug, task_index),
128
- foreign key (run_id, step_slug)
129
- references pgflow.step_states (run_id, step_slug),
130
- constraint valid_status check (
131
- status in ('queued', 'completed', 'failed')
132
- ),
133
- constraint output_valid_only_for_completed check (
134
- output is null or status = 'completed'
135
- ),
136
- constraint only_single_task_per_step check (task_index = 0),
137
- constraint attempts_count_nonnegative check (attempts_count >= 0)
138
- );
139
-
140
- ------------------------------------------
141
- -- Types
142
- ------------------------------------------
143
-
144
- create type pgflow.step_task_record as (
145
- flow_slug text,
146
- run_id uuid,
147
- step_slug text,
148
- input jsonb,
149
- msg_id bigint
150
- );
@@ -1,29 +0,0 @@
1
- create or replace function pgflow.create_flow(
2
- flow_slug text,
3
- max_attempts int default 3,
4
- base_delay int default 5,
5
- timeout int default 60
6
- )
7
- returns pgflow.flows
8
- language sql
9
- set search_path to ''
10
- volatile
11
- as $$
12
- WITH
13
- flow_upsert AS (
14
- INSERT INTO pgflow.flows (flow_slug, opt_max_attempts, opt_base_delay, opt_timeout)
15
- VALUES (flow_slug, max_attempts, base_delay, timeout)
16
- ON CONFLICT (flow_slug) DO UPDATE
17
- SET flow_slug = pgflow.flows.flow_slug -- Dummy update
18
- RETURNING *
19
- ),
20
- ensure_queue AS (
21
- SELECT pgmq.create(flow_slug)
22
- WHERE NOT EXISTS (
23
- SELECT 1 FROM pgmq.list_queues() WHERE queue_name = flow_slug
24
- )
25
- )
26
- SELECT f.*
27
- FROM flow_upsert f
28
- LEFT JOIN (SELECT 1 FROM ensure_queue) _dummy ON true; -- Left join ensures flow is returned
29
- $$;
@@ -1,48 +0,0 @@
1
- create or replace function pgflow.add_step(
2
- flow_slug text,
3
- step_slug text,
4
- deps_slugs text [],
5
- max_attempts int default null,
6
- base_delay int default null,
7
- timeout int default null
8
- )
9
- returns pgflow.steps
10
- language sql
11
- set search_path to ''
12
- volatile
13
- as $$
14
- WITH
15
- create_step AS (
16
- INSERT INTO pgflow.steps (flow_slug, step_slug, deps_count, opt_max_attempts, opt_base_delay, opt_timeout)
17
- VALUES (flow_slug, step_slug, COALESCE(array_length(deps_slugs, 1), 0), max_attempts, base_delay, timeout)
18
- ON CONFLICT (flow_slug, step_slug)
19
- DO UPDATE SET step_slug = pgflow.steps.step_slug
20
- RETURNING *
21
- ),
22
- insert_deps AS (
23
- INSERT INTO pgflow.deps (flow_slug, dep_slug, step_slug)
24
- SELECT add_step.flow_slug, d.dep_slug, add_step.step_slug
25
- FROM unnest(deps_slugs) AS d(dep_slug)
26
- ON CONFLICT (flow_slug, dep_slug, step_slug) DO NOTHING
27
- RETURNING 1
28
- )
29
- -- Return the created step
30
- SELECT * FROM create_step;
31
- $$;
32
-
33
- -- New overloaded function without deps_slugs parameter
34
- create or replace function pgflow.add_step(
35
- flow_slug text,
36
- step_slug text,
37
- max_attempts int default null,
38
- base_delay int default null,
39
- timeout int default null
40
- )
41
- returns pgflow.steps
42
- language sql
43
- set search_path to ''
44
- volatile
45
- as $$
46
- -- Call the original function with an empty array
47
- SELECT * FROM pgflow.add_step(flow_slug, step_slug, ARRAY[]::text[], max_attempts, base_delay, timeout);
48
- $$;
@@ -1,45 +0,0 @@
1
- create or replace function pgflow.start_ready_steps(run_id uuid)
2
- returns void
3
- language sql
4
- set search_path to ''
5
- as $$
6
-
7
- WITH ready_steps AS (
8
- SELECT *
9
- FROM pgflow.step_states AS step_state
10
- WHERE step_state.run_id = start_ready_steps.run_id
11
- AND step_state.status = 'created'
12
- AND step_state.remaining_deps = 0
13
- ORDER BY step_state.step_slug
14
- FOR UPDATE
15
- ),
16
- started_step_states AS (
17
- UPDATE pgflow.step_states
18
- SET status = 'started'
19
- FROM ready_steps
20
- WHERE pgflow.step_states.run_id = start_ready_steps.run_id
21
- AND pgflow.step_states.step_slug = ready_steps.step_slug
22
- RETURNING pgflow.step_states.*
23
- ),
24
- sent_messages AS (
25
- SELECT
26
- started_step.flow_slug,
27
- started_step.run_id,
28
- started_step.step_slug,
29
- pgmq.send(started_step.flow_slug, jsonb_build_object(
30
- 'flow_slug', started_step.flow_slug,
31
- 'run_id', started_step.run_id,
32
- 'step_slug', started_step.step_slug,
33
- 'task_index', 0
34
- )) AS msg_id
35
- FROM started_step_states AS started_step
36
- )
37
- INSERT INTO pgflow.step_tasks (flow_slug, run_id, step_slug, message_id)
38
- SELECT
39
- sent_messages.flow_slug,
40
- sent_messages.run_id,
41
- sent_messages.step_slug,
42
- sent_messages.msg_id
43
- FROM sent_messages;
44
-
45
- $$;
@@ -1,46 +0,0 @@
1
- -- drop function if exists pgflow.start_flow;
2
- create or replace function pgflow.start_flow(
3
- flow_slug TEXT,
4
- input JSONB
5
- )
6
- returns setof PGFLOW.RUNS
7
- language plpgsql
8
- set search_path to ''
9
- volatile
10
- as $$
11
- declare
12
- v_created_run pgflow.runs%ROWTYPE;
13
- begin
14
-
15
- WITH
16
- flow_steps AS (
17
- SELECT steps.flow_slug, steps.step_slug, steps.deps_count
18
- FROM pgflow.steps
19
- WHERE steps.flow_slug = start_flow.flow_slug
20
- ),
21
- created_run AS (
22
- INSERT INTO pgflow.runs (flow_slug, input, remaining_steps)
23
- VALUES (
24
- start_flow.flow_slug,
25
- start_flow.input,
26
- (SELECT count(*) FROM flow_steps)
27
- )
28
- RETURNING *
29
- ),
30
- created_step_states AS (
31
- INSERT INTO pgflow.step_states (flow_slug, run_id, step_slug, remaining_deps)
32
- SELECT
33
- fs.flow_slug,
34
- (SELECT run_id FROM created_run),
35
- fs.step_slug,
36
- fs.deps_count
37
- FROM flow_steps fs
38
- )
39
- SELECT * FROM created_run INTO v_created_run;
40
-
41
- PERFORM pgflow.start_ready_steps(v_created_run.run_id);
42
-
43
- RETURN QUERY SELECT * FROM pgflow.runs where run_id = v_created_run.run_id;
44
-
45
- end;
46
- $$;
@@ -1,70 +0,0 @@
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;
@@ -1,100 +0,0 @@
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;
@@ -1,30 +0,0 @@
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
- $$;
@@ -1,98 +0,0 @@
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
- $$;
@@ -1,11 +0,0 @@
1
- create or replace function pgflow.calculate_retry_delay(
2
- base_delay numeric,
3
- attempts_count int
4
- )
5
- returns int
6
- language sql
7
- immutable
8
- parallel safe
9
- as $$
10
- select floor(base_delay * power(2, attempts_count))::int
11
- $$;
@@ -1,124 +0,0 @@
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
- $$;
@@ -1,86 +0,0 @@
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;