@pgflow/core 0.0.5 → 0.0.7
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/{CHANGELOG.md → dist/CHANGELOG.md} +6 -0
- package/package.json +8 -5
- package/__tests__/mocks/index.ts +0 -1
- package/__tests__/mocks/postgres.ts +0 -37
- package/__tests__/types/PgflowSqlClient.test-d.ts +0 -59
- package/docs/options_for_flow_and_steps.md +0 -75
- package/docs/pgflow-blob-reference-system.md +0 -179
- package/eslint.config.cjs +0 -22
- package/example-flow.mermaid +0 -5
- package/example-flow.svg +0 -1
- package/flow-lifecycle.mermaid +0 -83
- package/flow-lifecycle.svg +0 -1
- package/out-tsc/vitest/__tests__/mocks/index.d.ts +0 -2
- package/out-tsc/vitest/__tests__/mocks/index.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/mocks/postgres.d.ts +0 -15
- package/out-tsc/vitest/__tests__/mocks/postgres.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/PgflowSqlClient.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/PgflowSqlClient.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +0 -1
- package/out-tsc/vitest/vite.config.d.ts +0 -3
- package/out-tsc/vitest/vite.config.d.ts.map +0 -1
- package/pkgs/core/dist/index.js +0 -54
- package/pkgs/core/dist/pkgs/core/LICENSE.md +0 -660
- package/pkgs/core/dist/pkgs/core/README.md +0 -373
- package/pkgs/dsl/dist/index.js +0 -123
- package/pkgs/dsl/dist/pkgs/dsl/README.md +0 -11
- package/pkgs/edge-worker/dist/index.js +0 -953
- package/pkgs/edge-worker/dist/index.js.map +0 -7
- package/pkgs/edge-worker/dist/pkgs/edge-worker/LICENSE.md +0 -660
- package/pkgs/edge-worker/dist/pkgs/edge-worker/README.md +0 -46
- package/pkgs/example-flows/dist/index.js +0 -152
- package/pkgs/example-flows/dist/pkgs/example-flows/README.md +0 -11
- package/project.json +0 -125
- package/prompts/architect.md +0 -87
- package/prompts/condition.md +0 -33
- package/prompts/declarative_sql.md +0 -15
- package/prompts/deps_in_payloads.md +0 -20
- package/prompts/dsl-multi-arg.ts +0 -48
- package/prompts/dsl-options.md +0 -39
- package/prompts/dsl-single-arg.ts +0 -51
- package/prompts/dsl-two-arg.ts +0 -61
- package/prompts/dsl.md +0 -119
- package/prompts/fanout_steps.md +0 -1
- package/prompts/json_schemas.md +0 -36
- package/prompts/one_shot.md +0 -286
- package/prompts/pgtap.md +0 -229
- package/prompts/sdk.md +0 -59
- package/prompts/step_types.md +0 -62
- package/prompts/versioning.md +0 -16
- package/queries/fail_permanently.sql +0 -17
- package/queries/fail_task.sql +0 -21
- package/queries/sequential.sql +0 -47
- package/queries/two_roots_left_right.sql +0 -59
- package/schema.svg +0 -1
- package/scripts/colorize-pgtap-output.awk +0 -72
- package/scripts/run-test-with-colors +0 -5
- package/scripts/watch-test +0 -7
- package/src/PgflowSqlClient.ts +0 -85
- package/src/database-types.ts +0 -759
- package/src/index.ts +0 -3
- package/src/types.ts +0 -103
- package/supabase/config.toml +0 -32
- package/supabase/seed.sql +0 -202
- package/supabase/tests/add_step/basic_step_addition.test.sql +0 -29
- package/supabase/tests/add_step/circular_dependency.test.sql +0 -21
- package/supabase/tests/add_step/flow_isolation.test.sql +0 -26
- package/supabase/tests/add_step/idempotent_step_addition.test.sql +0 -20
- package/supabase/tests/add_step/invalid_step_slug.test.sql +0 -16
- package/supabase/tests/add_step/nonexistent_dependency.test.sql +0 -16
- package/supabase/tests/add_step/nonexistent_flow.test.sql +0 -13
- package/supabase/tests/add_step/options.test.sql +0 -66
- package/supabase/tests/add_step/step_with_dependency.test.sql +0 -36
- package/supabase/tests/add_step/step_with_multiple_dependencies.test.sql +0 -46
- package/supabase/tests/complete_task/archives_message.test.sql +0 -67
- package/supabase/tests/complete_task/completes_run_if_no_more_remaining_steps.test.sql +0 -62
- package/supabase/tests/complete_task/completes_task_and_updates_dependents.test.sql +0 -64
- package/supabase/tests/complete_task/decrements_remaining_steps_if_completing_step.test.sql +0 -62
- package/supabase/tests/complete_task/saves_output_when_completing_run.test.sql +0 -57
- package/supabase/tests/create_flow/flow_creation.test.sql +0 -27
- package/supabase/tests/create_flow/idempotency_and_duplicates.test.sql +0 -26
- package/supabase/tests/create_flow/invalid_slug.test.sql +0 -13
- package/supabase/tests/create_flow/options.test.sql +0 -57
- package/supabase/tests/fail_task/exponential_backoff.test.sql +0 -70
- package/supabase/tests/fail_task/mark_as_failed_if_no_retries_available.test.sql +0 -49
- package/supabase/tests/fail_task/respects_flow_retry_settings.test.sql +0 -48
- package/supabase/tests/fail_task/respects_step_retry_settings.test.sql +0 -48
- package/supabase/tests/fail_task/retry_task_if_retries_available.test.sql +0 -39
- package/supabase/tests/is_valid_slug.test.sql +0 -72
- package/supabase/tests/poll_for_tasks/builds_proper_input_from_deps_outputs.test.sql +0 -35
- package/supabase/tests/poll_for_tasks/hides_messages.test.sql +0 -35
- package/supabase/tests/poll_for_tasks/increments_attempts_count.test.sql +0 -35
- package/supabase/tests/poll_for_tasks/multiple_task_processing.test.sql +0 -24
- package/supabase/tests/poll_for_tasks/polls_only_queued_tasks.test.sql +0 -35
- package/supabase/tests/poll_for_tasks/reads_messages.test.sql +0 -38
- package/supabase/tests/poll_for_tasks/returns_no_tasks_if_no_step_task_for_message.test.sql +0 -34
- package/supabase/tests/poll_for_tasks/returns_no_tasks_if_queue_is_empty.test.sql +0 -19
- package/supabase/tests/poll_for_tasks/returns_no_tasks_when_qty_set_to_0.test.sql +0 -22
- package/supabase/tests/poll_for_tasks/sets_vt_delay_based_on_opt_timeout.test.sql +0 -41
- package/supabase/tests/poll_for_tasks/tasks_reapppear_if_not_processed_in_time.test.sql +0 -59
- package/supabase/tests/start_flow/creates_run.test.sql +0 -24
- package/supabase/tests/start_flow/creates_step_states_for_all_steps.test.sql +0 -25
- package/supabase/tests/start_flow/creates_step_tasks_only_for_root_steps.test.sql +0 -54
- package/supabase/tests/start_flow/returns_run.test.sql +0 -24
- package/supabase/tests/start_flow/sends_messages_on_the_queue.test.sql +0 -50
- package/supabase/tests/start_flow/starts_only_root_steps.test.sql +0 -21
- package/supabase/tests/step_dsl_is_idempotent.test.sql +0 -34
- package/tsconfig.json +0 -16
- package/tsconfig.lib.json +0 -26
- package/tsconfig.spec.json +0 -35
- package/vite.config.ts +0 -57
package/prompts/pgtap.md
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
# PGTap Testing Guidelines
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
This document outlines a set of rules, best practices, ideas, and guidelines for writing pgTap tests for the project.
|
|
6
|
-
|
|
7
|
-
## File Organization
|
|
8
|
-
|
|
9
|
-
- Store test files under the `supabase/tests/` directory.
|
|
10
|
-
- Use descriptive file names with the `.test.sql` suffix.
|
|
11
|
-
- Organize tests in subfolders, by functionality (e.g., `start_flow`, `create_flow`, `add_step`, `poll_for_tasks`, `complete_task`, etc).
|
|
12
|
-
|
|
13
|
-
## Transactional Test Structure
|
|
14
|
-
|
|
15
|
-
Wrap each test in a transaction to ensure isolation:
|
|
16
|
-
|
|
17
|
-
```sql
|
|
18
|
-
begin;
|
|
19
|
-
select plan(2);
|
|
20
|
-
-- Test queries here
|
|
21
|
-
select finish();
|
|
22
|
-
rollback;
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Setup and Teardown
|
|
26
|
-
|
|
27
|
-
Reset and prepare the database context at the start of each test:
|
|
28
|
-
|
|
29
|
-
```sql
|
|
30
|
-
select pgflow_tests.reset_db();
|
|
31
|
-
select pgflow_tests.setup_flow('sequential');
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Terminate tests with:
|
|
35
|
-
|
|
36
|
-
```sql
|
|
37
|
-
select finish();
|
|
38
|
-
rollback;
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Declaring the Test Plan
|
|
42
|
-
|
|
43
|
-
Declare the number of tests using the `plan()` function:
|
|
44
|
-
|
|
45
|
-
```sql
|
|
46
|
-
select plan(2);
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Using pgTap Assertions
|
|
50
|
-
|
|
51
|
-
Use the following assertion functions to verify expected outcomes:
|
|
52
|
-
|
|
53
|
-
- `is(actual, expected, message)`
|
|
54
|
-
- `results_eq(actual, expected, message)`
|
|
55
|
-
- `set_eq(actual_query, expected_array, message)`
|
|
56
|
-
- `throws_ok(query, expected_error_message, message)`
|
|
57
|
-
- `ok(boolean_expression, message)`
|
|
58
|
-
|
|
59
|
-
### Example: Validating Run Creation
|
|
60
|
-
|
|
61
|
-
```sql
|
|
62
|
-
select pgflow.start_flow('sequential', '"hello"'::jsonb);
|
|
63
|
-
|
|
64
|
-
select results_eq(
|
|
65
|
-
$$ SELECT flow_slug, status, input FROM pgflow.runs $$,
|
|
66
|
-
$$ VALUES ('sequential', 'started', '"hello"'::jsonb) $$,
|
|
67
|
-
'Run should be created with appropriate status and input'
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
select is(
|
|
71
|
-
(select remaining_steps::int from pgflow.runs limit 1),
|
|
72
|
-
3::int,
|
|
73
|
-
'remaining_steps should be equal to number of steps'
|
|
74
|
-
);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Example: Testing Error Handling
|
|
78
|
-
|
|
79
|
-
```sql
|
|
80
|
-
select throws_ok(
|
|
81
|
-
$$ SELECT pgflow.create_flow('invalid-flow') $$,
|
|
82
|
-
'new row for relation "flows" violates check constraint "flows_flow_slug_check"',
|
|
83
|
-
'Should detect and prevent invalid flow slug'
|
|
84
|
-
);
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Idempotence and Duplicate Prevention
|
|
88
|
-
|
|
89
|
-
Run operations multiple times to ensure idempotency and that no duplicates are created:
|
|
90
|
-
|
|
91
|
-
```sql
|
|
92
|
-
select pgflow.create_flow('test_flow');
|
|
93
|
-
select pgflow.create_flow('test_flow');
|
|
94
|
-
|
|
95
|
-
select results_eq(
|
|
96
|
-
$$ SELECT flow_slug FROM pgflow.flows $$,
|
|
97
|
-
array['test_flow']::text [],
|
|
98
|
-
'No duplicate flow should be created'
|
|
99
|
-
);
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## Testing Dependencies and Flow Isolation
|
|
103
|
-
|
|
104
|
-
Ensure that steps and dependencies remain isolated within a flow:
|
|
105
|
-
|
|
106
|
-
```sql
|
|
107
|
-
select pgflow.create_flow('test_flow');
|
|
108
|
-
select pgflow.add_step('test_flow', 'first_step');
|
|
109
|
-
|
|
110
|
-
select pgflow.create_flow('another_flow');
|
|
111
|
-
select pgflow.add_step('another_flow', 'first_step');
|
|
112
|
-
select pgflow.add_step('another_flow', 'another_step', array['first_step']);
|
|
113
|
-
|
|
114
|
-
select set_eq(
|
|
115
|
-
$$
|
|
116
|
-
SELECT flow_slug, step_slug
|
|
117
|
-
FROM pgflow.steps WHERE flow_slug = 'another_flow'
|
|
118
|
-
$$,
|
|
119
|
-
$$ VALUES
|
|
120
|
-
('another_flow', 'another_step'),
|
|
121
|
-
('another_flow', 'first_step')
|
|
122
|
-
$$,
|
|
123
|
-
'Steps in second flow should be isolated from first flow'
|
|
124
|
-
);
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Testing Message Queues
|
|
128
|
-
|
|
129
|
-
Simulate message polling and verify visibility timeouts:
|
|
130
|
-
|
|
131
|
-
```sql
|
|
132
|
-
select is(
|
|
133
|
-
(select count(*)::integer from pgflow.poll_for_tasks(
|
|
134
|
-
queue_name => 'sequential'::text,
|
|
135
|
-
vt => 5,
|
|
136
|
-
qty => 1,
|
|
137
|
-
max_poll_seconds => 1
|
|
138
|
-
)),
|
|
139
|
-
1::integer,
|
|
140
|
-
'First poll should get the available task'
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
select is(
|
|
144
|
-
(select count(*)::integer from pgflow.poll_for_tasks(
|
|
145
|
-
queue_name => 'sequential'::text,
|
|
146
|
-
vt => 5,
|
|
147
|
-
qty => 1,
|
|
148
|
-
max_poll_seconds => 1
|
|
149
|
-
)),
|
|
150
|
-
0::integer,
|
|
151
|
-
'Concurrent poll should not get the same task (due to visibility timeout)'
|
|
152
|
-
);
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
## Completing Tasks and Flow Progression
|
|
156
|
-
|
|
157
|
-
Ensure that task completions update state and trigger dependents:
|
|
158
|
-
|
|
159
|
-
```sql
|
|
160
|
-
select pgflow.complete_task(
|
|
161
|
-
(select run_id from pgflow.runs limit 1),
|
|
162
|
-
'first',
|
|
163
|
-
0,
|
|
164
|
-
'{"result": "first completed"}'::jsonb
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
select results_eq(
|
|
168
|
-
$$ SELECT status, output FROM pgflow.step_tasks
|
|
169
|
-
WHERE run_id = (SELECT run_id FROM pgflow.runs LIMIT 1)
|
|
170
|
-
AND step_slug = 'first' $$,
|
|
171
|
-
$$ VALUES ('completed', '{"result": "first completed"}'::jsonb) $$,
|
|
172
|
-
'Task should be marked as completed with correct output'
|
|
173
|
-
);
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Archiving Processed Messages
|
|
177
|
-
|
|
178
|
-
Verify that messages are archived after task completion:
|
|
179
|
-
|
|
180
|
-
```sql
|
|
181
|
-
select is(
|
|
182
|
-
(select message ->> 'step_slug' from pgmq.q_sequential limit 1),
|
|
183
|
-
'first',
|
|
184
|
-
'First message should be in the queue'
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
select pgflow.complete_task(
|
|
188
|
-
(select run_id from pgflow.runs limit 1),
|
|
189
|
-
'first',
|
|
190
|
-
0,
|
|
191
|
-
'"first was successful"'::jsonb
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
select is(
|
|
195
|
-
(select count(*)::INT from pgmq.q_sequential where message ->> 'step_slug' = 'first'),
|
|
196
|
-
0::INT,
|
|
197
|
-
'There should be no messages in the queue'
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
select is(
|
|
201
|
-
(select count(*)::INT from pgmq.a_sequential where message ->> 'step_slug' = 'first' limit 1),
|
|
202
|
-
1::INT,
|
|
203
|
-
'The message should be archived'
|
|
204
|
-
);
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Validating Input with Custom Validators
|
|
208
|
-
|
|
209
|
-
Use custom functions to check input formats:
|
|
210
|
-
|
|
211
|
-
```sql
|
|
212
|
-
select ok(
|
|
213
|
-
pgflow.is_valid_slug('valid_slug'),
|
|
214
|
-
'is_valid_slug returns true for string with underscore'
|
|
215
|
-
);
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
## Conclusion
|
|
219
|
-
|
|
220
|
-
Adhere to the following best practices when writing pgTap tests:
|
|
221
|
-
|
|
222
|
-
- Keep tests self-contained with proper setup and teardown.
|
|
223
|
-
- Use transactions to isolate tests.
|
|
224
|
-
- Declare a clear test plan using `plan()`.
|
|
225
|
-
- Write focused tests with descriptive messages.
|
|
226
|
-
- Ensure idempotence by re-running operations.
|
|
227
|
-
- Validate both positive outcomes and error cases.
|
|
228
|
-
|
|
229
|
-
Following these guidelines will help maintain consistency, reliability, and clarity in your pgTap tests.
|
package/prompts/sdk.md
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Flow SDK
|
|
2
|
-
|
|
3
|
-
The purpose of Flow SDK is to allow users to start and observe flow runs in their apps
|
|
4
|
-
and leverage strong typing of the inputs, outputs and dependencies between steps
|
|
5
|
-
in order to improve Developer Experience.
|
|
6
|
-
|
|
7
|
-
Based on the Flow definition like this:
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
const ScrapeWebsiteFlow = new Flow<Input>()
|
|
11
|
-
.step('table_of_contents', async (payload) => {
|
|
12
|
-
// Placeholder function
|
|
13
|
-
return await fetchTableOfContents(payload.run.url);
|
|
14
|
-
})
|
|
15
|
-
.step('subpages', ['table_of_contents'], async (payload) => {
|
|
16
|
-
// Placeholder function
|
|
17
|
-
return await scrapeSubpages(payload.run.url, payload.table_of_contents.urls_of_subpages);
|
|
18
|
-
})
|
|
19
|
-
.step('summaries', ['subpages'], async (payload) => {
|
|
20
|
-
// Placeholder function
|
|
21
|
-
return await generateSummaries(payload.subpages.contentsOfSubpages);
|
|
22
|
-
})
|
|
23
|
-
.step('sentiments', ['subpages'], async (payload) => {
|
|
24
|
-
// Placeholder function
|
|
25
|
-
return await analyzeSentiments(payload.subpages.contentsOfSubpages);
|
|
26
|
-
})
|
|
27
|
-
.step('save_to_db', ['subpages', 'summaries', 'sentiments'], async (payload) => {
|
|
28
|
-
// Placeholder function
|
|
29
|
-
return await saveToDb(payload.subpages, payload.summaries, payload.sentiments);
|
|
30
|
-
});
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
We want to be able to infer the following information somehow:
|
|
34
|
-
|
|
35
|
-
- The cumulative payload types that are built step-by-step
|
|
36
|
-
- The relationships between steps that are established at runtime
|
|
37
|
-
|
|
38
|
-
Those are the most important things we need, so users can for example trigger
|
|
39
|
-
flows and get annotations for the step results etc.
|
|
40
|
-
Given the example flow I would like my users to be able to get their defined flow and do things like:
|
|
41
|
-
|
|
42
|
-
```ts
|
|
43
|
-
import type { ScrapeWebsiteFlow } from './flows/scrape_website';
|
|
44
|
-
import { createClient } from '@pgflow/sdk';
|
|
45
|
-
|
|
46
|
-
const { startFlow } = createClient(supabaseClient);
|
|
47
|
-
|
|
48
|
-
const flowRun = startFlow<ScrapeWebsiteFlow>({
|
|
49
|
-
url: 'https://example.com', // this is type checked based on the Input to ScrapeWebsiteFlow
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// here, 'subpages' (the name of step) would be type checked and only existing steps
|
|
53
|
-
// can be used here, so user cannot await for non existing step
|
|
54
|
-
const subpagesOutput = flowRun.stepCompleted('subpages');
|
|
55
|
-
|
|
56
|
-
// the subpagesOutput is also type-annotated based on the return type inferred
|
|
57
|
-
// from the handler for 'subpages' step, only based on the ScrapeWebsiteFlow type
|
|
58
|
-
subpagesOutput.forEac() // this is an array because handler for 'subpages' returns an array
|
|
59
|
-
```
|
package/prompts/step_types.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
## Step types in MVP
|
|
2
|
-
|
|
3
|
-
Regular Steps
|
|
4
|
-
|
|
5
|
-
- Basic unit of work.
|
|
6
|
-
- Executes a handler that receives outputs from its declared dependencies.
|
|
7
|
-
- Its return value is passed to dependent steps.
|
|
8
|
-
|
|
9
|
-
## Planned step types
|
|
10
|
-
|
|
11
|
-
### Map Steps (tasks fanout)
|
|
12
|
-
|
|
13
|
-
- Designed for when a dependency returns an array.
|
|
14
|
-
- The handler runs once per array element (in parallel).
|
|
15
|
-
- The outputs are collected back into an array (order preserved) and passed downstream.
|
|
16
|
-
|
|
17
|
-
### Conditional Steps
|
|
18
|
-
|
|
19
|
-
- Each step will be able to specify a condition, regardless of its type
|
|
20
|
-
- Steps that run only when certain conditions are met
|
|
21
|
-
- A condition is provided (as a JSON fragment)
|
|
22
|
-
- At runtime, the inpuyt input for a step (from all deps) is matched via @> against the condition
|
|
23
|
-
- If the condition is not met, the step does not run and marked as skipped
|
|
24
|
-
- Dependent steps are not run and should probably be marked as skipped as well.
|
|
25
|
-
|
|
26
|
-
### Manual Approval Steps
|
|
27
|
-
|
|
28
|
-
- Steps that pause for human intervention.
|
|
29
|
-
- They just differ by NOT immediately queueing a task.
|
|
30
|
-
- Instead, they wait for an external update by calling **complete_step** to set their output and trigger downstream steps.
|
|
31
|
-
|
|
32
|
-
### Subflow Steps
|
|
33
|
-
|
|
34
|
-
- Encapsulate an entire subflow (a mini workflow) as a single step.
|
|
35
|
-
- A subflow is defined using the same DSL as the main flow.
|
|
36
|
-
- Each subflow has an automatic final step that gathers the outputs of all leaf steps.
|
|
37
|
-
- The subflow step triggers the subflow and waits until its output is ready.
|
|
38
|
-
- The aggregated output from the subflow becomes the output of the subflow step.
|
|
39
|
-
|
|
40
|
-
### Fanout subflows step
|
|
41
|
-
|
|
42
|
-
- Like Map steps, but instead of a task per array item, it runs a subflow per array item.
|
|
43
|
-
- It gathers final steps from subflows into an output array for the fanout subflow step
|
|
44
|
-
|
|
45
|
-
####
|
|
46
|
-
|
|
47
|
-
• “Fanout subflow” steps do not have local tasks. Instead, they spawn child subflows and wait for them to finish.
|
|
48
|
-
• You can track subflow completion with the same remaining_tasks field:
|
|
49
|
-
– Increment remaining_tasks by the number of child subflows.
|
|
50
|
-
– Decrement it each time a child subflow completes.
|
|
51
|
-
– When remaining_tasks reaches zero, the fanout subflow step is done.
|
|
52
|
-
• Alternatively, you can add a remaining_subflows column to separate child‐subflow tracking from local tasks.
|
|
53
|
-
– This gives clearer semantics but requires extra logic to handle multiple completion conditions.
|
|
54
|
-
• Most implementations unify subflow runs under remaining_tasks to reuse existing “remaining_tasks = 0 means done” checks.
|
|
55
|
-
|
|
56
|
-
### Additional Techniques
|
|
57
|
-
|
|
58
|
-
We simplify as much as possible and use other tools instead of reinventing the wheel.
|
|
59
|
-
|
|
60
|
-
- Recurrent tasks are handled externally via Cron triggers.
|
|
61
|
-
- Delays can be implemented using pgmq visibility timeouts.
|
|
62
|
-
- The overall design treats a flow as a single function with one input (parameters) and one output (final aggregated output).
|
package/prompts/versioning.md
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Versioning
|
|
2
|
-
|
|
3
|
-
Flow Versioning Strategy #[[pgflow/Versioning]]
|
|
4
|
-
Agreed on immutable flow definitions (similar to Temporal)
|
|
5
|
-
Once a flow is uploaded to DB, it remains unchanged
|
|
6
|
-
Versioning handled through flow slugs rather than explicit version numbers
|
|
7
|
-
Users responsible for managing changes in safe, organized manner
|
|
8
|
-
Benefits of immutable approach
|
|
9
|
-
Simplifies implementation
|
|
10
|
-
Provides natural versioning cascade for subflows
|
|
11
|
-
Makes version transitions explicit and intentional
|
|
12
|
-
Avoids "half-upgraded" scenarios
|
|
13
|
-
Consciously decided against "latest" aliases for now
|
|
14
|
-
Could introduce complexity and unpredictable behavior
|
|
15
|
-
Users can implement their own aliasing logic if needed
|
|
16
|
-
Explicit slugs provide clarity about which version is being used
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
select pgflow_tests.reset_db();
|
|
2
|
-
select pgflow_tests.setup_flow('sequential');
|
|
3
|
-
|
|
4
|
-
-- SETUP
|
|
5
|
-
select pgflow.start_flow('sequential', '{"test": true}'::JSONB);
|
|
6
|
-
|
|
7
|
-
-- default opt_max_attempts is 3, so failing twice should mark the task as failed
|
|
8
|
-
select pgflow_tests.poll_and_fail('sequential');
|
|
9
|
-
select pg_sleep(1.1);
|
|
10
|
-
select pgflow_tests.poll_and_fail('sequential');
|
|
11
|
-
select * from pgflow.step_tasks;
|
|
12
|
-
select * from pgmq.q_sequential;
|
|
13
|
-
-- select * from pgflow.step_tasks;
|
|
14
|
-
--
|
|
15
|
-
select * from pgflow.step_tasks;
|
|
16
|
-
select * from pgflow.step_states;
|
|
17
|
-
select * from pgflow.runs;
|
package/queries/fail_task.sql
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
\x
|
|
2
|
-
begin;
|
|
3
|
-
select pgflow_tests.reset_db();
|
|
4
|
-
select pgflow_tests.setup_flow('two_roots_left_right');
|
|
5
|
-
|
|
6
|
-
--------------------------------------------------------------------------------
|
|
7
|
-
--------------------------------------------------------------------------------
|
|
8
|
-
--------------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
select pgflow.start_flow('two_roots_left_right', '"hello"'::jsonb);
|
|
12
|
-
|
|
13
|
-
select pgflow_tests.poll_and_complete('two_roots_left_right');
|
|
14
|
-
select pgflow_tests.poll_and_complete('two_roots_left_right');
|
|
15
|
-
select pgflow_tests.poll_and_complete('two_roots_left_right');
|
|
16
|
-
select pgflow_tests.poll_and_complete('two_roots_left_right');
|
|
17
|
-
|
|
18
|
-
select jsonb_pretty(output) from pgflow.runs;
|
|
19
|
-
select * from pgflow.runs;
|
|
20
|
-
|
|
21
|
-
rollback;
|
package/queries/sequential.sql
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
begin;
|
|
2
|
-
|
|
3
|
-
select pgflow_tests.reset_db();
|
|
4
|
-
select pgflow_tests.setup_flow('sequential');
|
|
5
|
-
|
|
6
|
-
select pgflow.start_flow('sequential', '"hello"'::jsonb);
|
|
7
|
-
select * from pgflow.step_states;
|
|
8
|
-
select * from pgflow.step_tasks;
|
|
9
|
-
|
|
10
|
-
select * from pgflow.poll_for_tasks('sequential', 1, 1);
|
|
11
|
-
select * from pgflow.step_states;
|
|
12
|
-
select * from pgflow.step_tasks;
|
|
13
|
-
|
|
14
|
-
select pgflow.complete_task(
|
|
15
|
-
(select run_id from pgflow.runs limit 1),
|
|
16
|
-
'first',
|
|
17
|
-
0,
|
|
18
|
-
'"first completed"'::jsonb
|
|
19
|
-
);
|
|
20
|
-
select * from pgflow.step_states;
|
|
21
|
-
select * from pgflow.step_tasks;
|
|
22
|
-
|
|
23
|
-
select * from pgflow.poll_for_tasks('sequential', 1, 1);
|
|
24
|
-
|
|
25
|
-
select pgflow.complete_task(
|
|
26
|
-
(select run_id from pgflow.runs limit 1),
|
|
27
|
-
'second',
|
|
28
|
-
0,
|
|
29
|
-
'"second completed"'::jsonb
|
|
30
|
-
);
|
|
31
|
-
select * from pgflow.step_states;
|
|
32
|
-
select * from pgflow.step_tasks;
|
|
33
|
-
|
|
34
|
-
select * from pgflow.poll_for_tasks('sequential', 1, 1);
|
|
35
|
-
|
|
36
|
-
select pgflow.complete_task(
|
|
37
|
-
(select run_id from pgflow.runs limit 1),
|
|
38
|
-
'last',
|
|
39
|
-
0,
|
|
40
|
-
'"last completed"'::jsonb
|
|
41
|
-
);
|
|
42
|
-
select * from pgflow.step_states;
|
|
43
|
-
select * from pgflow.step_tasks;
|
|
44
|
-
|
|
45
|
-
select * from pgflow.runs;
|
|
46
|
-
|
|
47
|
-
rollback;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
\x
|
|
2
|
-
begin;
|
|
3
|
-
|
|
4
|
-
select pgflow_tests.reset_db();
|
|
5
|
-
select pgflow_tests.setup_flow('two_roots_left_right');
|
|
6
|
-
|
|
7
|
-
select pgflow.start_flow('two_roots_left_right', '"hello"'::jsonb);
|
|
8
|
-
select pgflow.poll_for_tasks('two_roots_left_right', 1, 1);
|
|
9
|
-
select pgflow.complete_task(
|
|
10
|
-
(select run_id from pgflow.runs limit 1),
|
|
11
|
-
'connected_root',
|
|
12
|
-
0,
|
|
13
|
-
'"connected_root completed"'::jsonb
|
|
14
|
-
);
|
|
15
|
-
select pgflow.poll_for_tasks('two_roots_left_right', 1, 1);
|
|
16
|
-
select pgflow.complete_task(
|
|
17
|
-
(select run_id from pgflow.runs limit 1),
|
|
18
|
-
'left',
|
|
19
|
-
0,
|
|
20
|
-
'"left completed"'::jsonb
|
|
21
|
-
);
|
|
22
|
-
select * from pgflow.step_tasks;
|
|
23
|
-
select pgflow.poll_for_tasks('two_roots_left_right', 1, 1);
|
|
24
|
-
select pgflow.complete_task(
|
|
25
|
-
(select run_id from pgflow.runs limit 1),
|
|
26
|
-
'disconnected_root',
|
|
27
|
-
0,
|
|
28
|
-
'"disconnected_root completed"'::jsonb
|
|
29
|
-
);
|
|
30
|
-
select pgflow.poll_for_tasks('two_roots_left_right', 1, 1);
|
|
31
|
-
-- select pgflow.complete_task(
|
|
32
|
-
-- (select run_id from pgflow.runs limit 1),
|
|
33
|
-
-- 'right',
|
|
34
|
-
-- 0,
|
|
35
|
-
-- '"right completed"'::jsonb
|
|
36
|
-
-- );
|
|
37
|
-
select pgflow.fail_task(
|
|
38
|
-
(select run_id from pgflow.runs limit 1),
|
|
39
|
-
'right',
|
|
40
|
-
0,
|
|
41
|
-
'invalid http request'
|
|
42
|
-
);
|
|
43
|
-
select pgflow.fail_task(
|
|
44
|
-
(select run_id from pgflow.runs limit 1),
|
|
45
|
-
'right',
|
|
46
|
-
0,
|
|
47
|
-
'invalid http request'
|
|
48
|
-
);
|
|
49
|
-
select pgflow.fail_task(
|
|
50
|
-
(select run_id from pgflow.runs limit 1),
|
|
51
|
-
'right',
|
|
52
|
-
0,
|
|
53
|
-
'invalid http request'
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
select * from pgflow.runs;
|
|
57
|
-
select * from pgflow.step_tasks;
|
|
58
|
-
select * from pgflow.step_states;
|
|
59
|
-
rollback;
|
package/schema.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg aria-roledescription="er" role="graphics-document document" viewBox="0 0 449.7414245605469 982" style="max-width: 449.741px; background-color: white;" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="100%" id="my-svg"><style>#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#my-svg .error-icon{fill:#552222;}#my-svg .error-text{fill:#552222;stroke:#552222;}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#333333;stroke:#333333;}#my-svg .marker.cross{stroke:#333333;}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#my-svg p{margin:0;}#my-svg .entityBox{fill:#ECECFF;stroke:#9370DB;}#my-svg .attributeBoxOdd{fill:#ffffff;stroke:#9370DB;}#my-svg .attributeBoxEven{fill:#f2f2f2;stroke:#9370DB;}#my-svg .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#my-svg .relationshipLabelBox rect{opacity:0.5;}#my-svg .relationshipLine{stroke:#333333;}#my-svg .entityTitleText{text-anchor:middle;font-size:18px;fill:#333;}#my-svg #MD_PARENT_START{fill:#f5f5f5!important;stroke:#333333!important;stroke-width:1;}#my-svg #MD_PARENT_END{fill:#f5f5f5!important;stroke:#333333!important;stroke-width:1;}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g/><defs><marker orient="auto" markerHeight="240" markerWidth="190" refY="7" refX="0" id="MD_PARENT_START"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="28" markerWidth="20" refY="7" refX="19" id="MD_PARENT_END"><path d="M 18,7 L9,13 L1,7 L9,1 Z"/></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="18" refY="9" refX="0" id="ONLY_ONE_START"><path d="M9,0 L9,18 M15,0 L15,18" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="18" refY="9" refX="18" id="ONLY_ONE_END"><path d="M3,0 L3,18 M9,0 L9,18" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="30" refY="9" refX="0" id="ZERO_OR_ONE_START"><circle r="6" cy="9" cx="21" fill="white" stroke="gray"/><path d="M9,0 L9,18" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="18" markerWidth="30" refY="9" refX="30" id="ZERO_OR_ONE_END"><circle r="6" cy="9" cx="9" fill="white" stroke="gray"/><path d="M21,0 L21,18" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="45" refY="18" refX="18" id="ONE_OR_MORE_START"><path d="M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="45" refY="18" refX="27" id="ONE_OR_MORE_END"><path d="M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="57" refY="18" refX="18" id="ZERO_OR_MORE_START"><circle r="6" cy="18" cx="48" fill="white" stroke="gray"/><path d="M0,18 Q18,0 36,18 Q18,36 0,18" fill="none" stroke="gray"/></marker></defs><defs><marker orient="auto" markerHeight="36" markerWidth="57" refY="18" refX="39" id="ZERO_OR_MORE_END"><circle r="6" cy="18" cx="9" fill="white" stroke="gray"/><path d="M21,18 Q39,0 57,18 Q39,36 21,18" fill="none" stroke="gray"/></marker></defs><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M160.633,128L150.532,136.333C140.43,144.667,120.226,161.333,110.125,178C100.023,194.667,100.023,211.333,100.023,219.667L100.023,228" class="er relationshipLine"/><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M68.473,399L65.398,407.333C62.323,415.667,56.173,432.333,58.531,454.25C60.89,476.167,71.756,503.333,77.19,516.917L82.623,530.5" class="er relationshipLine"/><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M148.023,399L152.701,407.333C157.379,415.667,166.736,432.333,163.148,454.25C159.56,476.167,143.028,503.333,134.761,516.917L126.495,530.5" class="er relationshipLine"/><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M291.552,128L301.654,136.333C311.756,144.667,331.959,161.333,342.061,179.75C352.163,198.167,352.163,218.333,352.163,228.417L352.163,238.5" class="er relationshipLine"/><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M352.163,388.5L352.163,398.583C352.163,408.667,352.163,428.833,352.163,447.25C352.163,465.667,352.163,482.333,352.163,490.667L352.163,499" class="er relationshipLine"/><path style="stroke: gray; fill: none;" marker-start="url(#ONLY_ONE_START)" marker-end="url(#ZERO_OR_MORE_END)" d="M352.163,649L352.163,657.333C352.163,665.667,352.163,682.333,352.163,699C352.163,715.667,352.163,732.333,352.163,740.667L352.163,749" class="er relationshipLine"/><g transform="translate(153.99402618408203,20 )" id="entity-flows-f18f0530-88df-544b-a742-e42ede14479d"><rect height="108" width="144.19744873046875" y="0" x="0" class="er entityBox"/><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(72.09872436523438,12)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d" class="er entityLabel">flows</text><rect height="21" width="26.5899658203125" y="24" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,34.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-1-type" class="er entityLabel">text</text><rect height="21" width="93.8125" y="24" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,34.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-1-name" class="er entityLabel">flow_slug</text><rect height="21" width="23.79498291015625" y="24" x="120.4024658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,34.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-1-key" class="er entityLabel">PK</text><rect height="21" width="26.5899658203125" y="45" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,55.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-2-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="45" x="26.5899658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,55.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-2-name" class="er entityLabel">opt_max_attempts</text><rect height="21" width="23.79498291015625" y="45" x="120.4024658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,55.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-2-key" class="er entityLabel"/><rect height="21" width="26.5899658203125" y="66" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,76.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-3-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="66" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,76.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-3-name" class="er entityLabel">opt_base_delay</text><rect height="21" width="23.79498291015625" y="66" x="120.4024658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,76.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-3-key" class="er entityLabel"/><rect height="21" width="26.5899658203125" y="87" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,97.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-4-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="87" x="26.5899658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,97.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-4-name" class="er entityLabel">opt_timeout</text><rect height="21" width="23.79498291015625" y="87" x="120.4024658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,97.5)" y="0" x="0" id="text-entity-flows-f18f0530-88df-544b-a742-e42ede14479d-attr-4-key" class="er entityLabel"/></g><g transform="translate(20,228 )" id="entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66"><rect height="171" width="160.0457763671875" y="0" x="0" class="er entityBox"/><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(80.02288818359375,12)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66" class="er entityLabel">steps</text><rect height="21" width="26.5899658203125" y="24" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,34.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-1-type" class="er entityLabel">text</text><rect height="21" width="93.8125" y="24" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,34.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-1-name" class="er entityLabel">flow_slug</text><rect height="21" width="39.643310546875" y="24" x="120.4024658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,34.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-1-key" class="er entityLabel">PK,FK</text><rect height="21" width="26.5899658203125" y="45" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,55.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-2-type" class="er entityLabel">text</text><rect height="21" width="93.8125" y="45" x="26.5899658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,55.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-2-name" class="er entityLabel">step_slug</text><rect height="21" width="39.643310546875" y="45" x="120.4024658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,55.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-2-key" class="er entityLabel">PK</text><rect height="21" width="26.5899658203125" y="66" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,76.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-3-type" class="er entityLabel">text</text><rect height="21" width="93.8125" y="66" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,76.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-3-name" class="er entityLabel">step_type</text><rect height="21" width="39.643310546875" y="66" x="120.4024658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,76.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-3-key" class="er entityLabel"/><rect height="21" width="26.5899658203125" y="87" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,97.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-4-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="87" x="26.5899658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,97.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-4-name" class="er entityLabel">deps_count</text><rect height="21" width="39.643310546875" y="87" x="120.4024658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,97.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-4-key" class="er entityLabel"/><rect height="21" width="26.5899658203125" y="108" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,118.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-5-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="108" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,118.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-5-name" class="er entityLabel">opt_max_attempts</text><rect height="21" width="39.643310546875" y="108" x="120.4024658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,118.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-5-key" class="er entityLabel"/><rect height="21" width="26.5899658203125" y="129" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,139.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-6-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="129" x="26.5899658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,139.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-6-name" class="er entityLabel">opt_base_delay</text><rect height="21" width="39.643310546875" y="129" x="120.4024658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,139.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-6-key" class="er entityLabel"/><rect height="21" width="26.5899658203125" y="150" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,160.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-7-type" class="er entityLabel">int</text><rect height="21" width="93.8125" y="150" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,160.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-7-name" class="er entityLabel">opt_timeout</text><rect height="21" width="39.643310546875" y="150" x="120.4024658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.4024658203125,160.5)" y="0" x="0" id="text-entity-steps-9f52b7fe-5175-560c-8193-e79d92ebca66-attr-7-key" class="er entityLabel"/></g><g transform="translate(39.93402099609375,530.5 )" id="entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e"><rect height="87" width="120.177734375" y="0" x="0" class="er entityBox"/><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(60.0888671875,12)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e" class="er entityLabel">deps</text><rect height="21" width="26.5899658203125" y="24" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,34.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-1-type" class="er entityLabel">text</text><rect height="21" width="53.9444580078125" y="24" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,34.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-1-name" class="er entityLabel">flow_slug</text><rect height="21" width="39.643310546875" y="24" x="80.534423828125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(85.534423828125,34.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-1-key" class="er entityLabel">PK,FK</text><rect height="21" width="26.5899658203125" y="45" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,55.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-2-type" class="er entityLabel">text</text><rect height="21" width="53.9444580078125" y="45" x="26.5899658203125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,55.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-2-name" class="er entityLabel">dep_slug</text><rect height="21" width="39.643310546875" y="45" x="80.534423828125" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(85.534423828125,55.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-2-key" class="er entityLabel">PK,FK</text><rect height="21" width="26.5899658203125" y="66" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,76.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-3-type" class="er entityLabel">text</text><rect height="21" width="53.9444580078125" y="66" x="26.5899658203125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(31.5899658203125,76.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-3-name" class="er entityLabel">step_slug</text><rect height="21" width="39.643310546875" y="66" x="80.534423828125" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(85.534423828125,76.5)" y="0" x="0" id="text-entity-deps-e5301c36-0cf6-5c3a-a840-98a74414ff8e-attr-3-key" class="er entityLabel">PK,FK</text></g><g transform="translate(280.0457763671875,238.5 )" id="entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf"><rect height="150" width="144.23367309570312" y="0" x="0" class="er entityBox"/><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(72.11683654785156,12)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf" class="er entityLabel">runs</text><rect height="21" width="35.688690185546875" y="24" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,34.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-1-type" class="er entityLabel">uuid</text><rect height="21" width="84.75" y="24" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,34.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-1-name" class="er entityLabel">run_id</text><rect height="21" width="23.79498291015625" y="24" x="120.43869018554688" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.43869018554688,34.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-1-key" class="er entityLabel">PK</text><rect height="21" width="35.688690185546875" y="45" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,55.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-2-type" class="er entityLabel">text</text><rect height="21" width="84.75" y="45" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,55.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-2-name" class="er entityLabel">flow_slug</text><rect height="21" width="23.79498291015625" y="45" x="120.43869018554688" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.43869018554688,55.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-2-key" class="er entityLabel">FK</text><rect height="21" width="35.688690185546875" y="66" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,76.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-3-type" class="er entityLabel">text</text><rect height="21" width="84.75" y="66" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,76.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-3-name" class="er entityLabel">status</text><rect height="21" width="23.79498291015625" y="66" x="120.43869018554688" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.43869018554688,76.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-3-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="87" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,97.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-4-type" class="er entityLabel">jsonb</text><rect height="21" width="84.75" y="87" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,97.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-4-name" class="er entityLabel">input</text><rect height="21" width="23.79498291015625" y="87" x="120.43869018554688" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.43869018554688,97.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-4-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="108" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,118.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-5-type" class="er entityLabel">jsonb</text><rect height="21" width="84.75" y="108" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,118.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-5-name" class="er entityLabel">output</text><rect height="21" width="23.79498291015625" y="108" x="120.43869018554688" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.43869018554688,118.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-5-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="129" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,139.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-6-type" class="er entityLabel">int</text><rect height="21" width="84.75" y="129" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,139.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-6-name" class="er entityLabel">remaining_steps</text><rect height="21" width="23.79498291015625" y="129" x="120.43869018554688" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(125.43869018554688,139.5)" y="0" x="0" id="text-entity-runs-75ffb4dd-6be8-5a8d-bc72-ade53caeacdf-attr-6-key" class="er entityLabel"/></g><g transform="translate(275.4497375488281,499 )" id="entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4"><rect height="150" width="153.42575073242188" y="0" x="0" class="er entityBox"/><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(76.71287536621094,12)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4" class="er entityLabel">step_states</text><rect height="21" width="29.594940185546875" y="24" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,34.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-1-type" class="er entityLabel">text</text><rect height="21" width="84.1875" y="24" x="29.594940185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(34.594940185546875,34.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-1-name" class="er entityLabel">flow_slug</text><rect height="21" width="39.643310546875" y="24" x="113.78244018554688" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(118.78244018554688,34.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-1-key" class="er entityLabel">FK</text><rect height="21" width="29.594940185546875" y="45" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,55.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-2-type" class="er entityLabel">uuid</text><rect height="21" width="84.1875" y="45" x="29.594940185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(34.594940185546875,55.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-2-name" class="er entityLabel">run_id</text><rect height="21" width="39.643310546875" y="45" x="113.78244018554688" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(118.78244018554688,55.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-2-key" class="er entityLabel">PK,FK</text><rect height="21" width="29.594940185546875" y="66" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,76.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-3-type" class="er entityLabel">text</text><rect height="21" width="84.1875" y="66" x="29.594940185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(34.594940185546875,76.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-3-name" class="er entityLabel">step_slug</text><rect height="21" width="39.643310546875" y="66" x="113.78244018554688" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(118.78244018554688,76.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-3-key" class="er entityLabel">PK,FK</text><rect height="21" width="29.594940185546875" y="87" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,97.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-4-type" class="er entityLabel">text</text><rect height="21" width="84.1875" y="87" x="29.594940185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(34.594940185546875,97.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-4-name" class="er entityLabel">status</text><rect height="21" width="39.643310546875" y="87" x="113.78244018554688" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(118.78244018554688,97.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-4-key" class="er entityLabel"/><rect height="21" width="29.594940185546875" y="108" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,118.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-5-type" class="er entityLabel">int</text><rect height="21" width="84.1875" y="108" x="29.594940185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(34.594940185546875,118.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-5-name" class="er entityLabel">remaining_tasks</text><rect height="21" width="39.643310546875" y="108" x="113.78244018554688" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(118.78244018554688,118.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-5-key" class="er entityLabel"/><rect height="21" width="29.594940185546875" y="129" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,139.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-6-type" class="er entityLabel">int</text><rect height="21" width="84.1875" y="129" x="29.594940185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(34.594940185546875,139.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-6-name" class="er entityLabel">remaining_deps</text><rect height="21" width="39.643310546875" y="129" x="113.78244018554688" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(118.78244018554688,139.5)" y="0" x="0" id="text-entity-stepstates-d5fb431d-8c65-5bd1-8e0a-6a399ad2c7c4-attr-6-key" class="er entityLabel"/></g><g transform="translate(274.58380126953125,749 )" id="entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8"><rect height="213" width="155.15762329101562" y="0" x="0" class="er entityBox"/><text style="dominant-baseline: middle; text-anchor: middle; font-size: 12px;" transform="translate(77.57881164550781,12)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8" class="er entityLabel">step_tasks</text><rect height="21" width="35.688690185546875" y="24" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,34.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-1-type" class="er entityLabel">text</text><rect height="21" width="79.82562255859375" y="24" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,34.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-1-name" class="er entityLabel">flow_slug</text><rect height="21" width="39.643310546875" y="24" x="115.51431274414062" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,34.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-1-key" class="er entityLabel">FK</text><rect height="21" width="35.688690185546875" y="45" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,55.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-2-type" class="er entityLabel">uuid</text><rect height="21" width="79.82562255859375" y="45" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,55.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-2-name" class="er entityLabel">run_id</text><rect height="21" width="39.643310546875" y="45" x="115.51431274414062" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,55.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-2-key" class="er entityLabel">PK,FK</text><rect height="21" width="35.688690185546875" y="66" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,76.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-3-type" class="er entityLabel">text</text><rect height="21" width="79.82562255859375" y="66" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,76.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-3-name" class="er entityLabel">step_slug</text><rect height="21" width="39.643310546875" y="66" x="115.51431274414062" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,76.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-3-key" class="er entityLabel">PK,FK</text><rect height="21" width="35.688690185546875" y="87" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,97.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-4-type" class="er entityLabel">bigint</text><rect height="21" width="79.82562255859375" y="87" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,97.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-4-name" class="er entityLabel">message_id</text><rect height="21" width="39.643310546875" y="87" x="115.51431274414062" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,97.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-4-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="108" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,118.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-5-type" class="er entityLabel">int</text><rect height="21" width="79.82562255859375" y="108" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,118.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-5-name" class="er entityLabel">task_index</text><rect height="21" width="39.643310546875" y="108" x="115.51431274414062" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,118.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-5-key" class="er entityLabel">PK</text><rect height="21" width="35.688690185546875" y="129" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,139.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-6-type" class="er entityLabel">text</text><rect height="21" width="79.82562255859375" y="129" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,139.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-6-name" class="er entityLabel">status</text><rect height="21" width="39.643310546875" y="129" x="115.51431274414062" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,139.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-6-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="150" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,160.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-7-type" class="er entityLabel">int</text><rect height="21" width="79.82562255859375" y="150" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,160.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-7-name" class="er entityLabel">attempts_count</text><rect height="21" width="39.643310546875" y="150" x="115.51431274414062" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,160.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-7-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="171" x="0" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,181.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-8-type" class="er entityLabel">text</text><rect height="21" width="79.82562255859375" y="171" x="35.688690185546875" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,181.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-8-name" class="er entityLabel">error_message</text><rect height="21" width="39.643310546875" y="171" x="115.51431274414062" class="er attributeBoxEven"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,181.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-8-key" class="er entityLabel"/><rect height="21" width="35.688690185546875" y="192" x="0" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(5,202.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-9-type" class="er entityLabel">jsonb</text><rect height="21" width="79.82562255859375" y="192" x="35.688690185546875" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(40.688690185546875,202.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-9-name" class="er entityLabel">output</text><rect height="21" width="39.643310546875" y="192" x="115.51431274414062" class="er attributeBoxOdd"/><text style="dominant-baseline: middle; font-size: 10.2px;" transform="translate(120.51431274414062,202.5)" y="0" x="0" id="text-entity-steptasks-1b58e334-b1db-5be4-8d8a-3602898074c8-attr-9-key" class="er entityLabel"/></g><rect height="14" width="19.359375" y="162.96568298339844" x="106.04055786132812" class="er relationshipLabelBox"/><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="169.96568298339844" x="115.72024536132812" id="rel1" class="er relationshipLabel">has</text><rect height="14" width="80.734375" y="458.8131103515625" x="20.040935516357422" class="er relationshipLabelBox"/><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="465.8131103515625" x="60.40812301635742" id="rel2" class="er relationshipLabel">is_dependency</text><rect height="14" width="66.078125" y="460.0412902832031" x="126.8541259765625" class="er relationshipLabelBox"/><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="467.0412902832031" x="159.8931884765625" id="rel3" class="er relationshipLabel">depends_on</text><rect height="14" width="60.703125" y="167.44549560546875" x="308.50225830078125" class="er relationshipLabelBox"/><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="174.44549560546875" x="338.85382080078125" id="rel4" class="er relationshipLabel">instantiates</text><rect height="14" width="44.703125" y="436.75" x="329.8114318847656" class="er relationshipLabelBox"/><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="443.75" x="352.1629943847656" id="rel5" class="er relationshipLabel">contains</text><rect height="14" width="19.359375" y="692" x="342.4833068847656" class="er relationshipLabelBox"/><text style="text-anchor: middle; dominant-baseline: middle; font-size: 12px;" y="699" x="352.1629943847656" id="rel6" class="er relationshipLabel">has</text></svg>
|