@pgflow/dsl 0.0.5-prealpha.2 → 0.0.6
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 +4 -1
- package/__tests__/runtime/flow.test.ts +0 -121
- package/__tests__/runtime/steps.test.ts +0 -183
- package/__tests__/runtime/utils.test.ts +0 -149
- package/__tests__/types/dsl-types.test-d.ts +0 -103
- package/__tests__/types/example-flow.test-d.ts +0 -76
- package/__tests__/types/extract-flow-input.test-d.ts +0 -71
- package/__tests__/types/extract-flow-steps.test-d.ts +0 -74
- package/__tests__/types/getStepDefinition.test-d.ts +0 -65
- package/__tests__/types/step-input.test-d.ts +0 -212
- package/__tests__/types/step-output.test-d.ts +0 -55
- package/brainstorming/condition/condition-alternatives.md +0 -219
- package/brainstorming/condition/condition-with-flexibility.md +0 -303
- package/brainstorming/condition/condition.md +0 -139
- package/brainstorming/condition/implementation-plan.md +0 -372
- package/brainstorming/dsl/cli-json-schema.md +0 -225
- package/brainstorming/dsl/cli.md +0 -179
- package/brainstorming/dsl/create-compilator.md +0 -25
- package/brainstorming/dsl/dsl-analysis-2.md +0 -166
- package/brainstorming/dsl/dsl-analysis.md +0 -512
- package/brainstorming/dsl/dsl-critique.md +0 -41
- package/brainstorming/fanouts/fanout-subflows-flattened-vs-subruns.md +0 -213
- package/brainstorming/fanouts/fanouts-task-index.md +0 -150
- package/brainstorming/fanouts/fanouts-with-conditions-and-subflows.md +0 -239
- package/brainstorming/subflows/branching.ts.md +0 -38
- package/brainstorming/subflows/subflows-callbacks.ts.md +0 -124
- package/brainstorming/subflows/subflows-classes.ts.md +0 -83
- package/brainstorming/subflows/subflows-flattening-versioned.md +0 -119
- package/brainstorming/subflows/subflows-flattening.md +0 -138
- package/brainstorming/subflows/subflows.md +0 -118
- package/brainstorming/subflows/subruns-table.md +0 -282
- package/brainstorming/subflows/subruns.md +0 -315
- package/brainstorming/versioning/breaking-and-non-breaking-flow-changes.md +0 -259
- package/docs/refactor-edge-worker.md +0 -146
- package/docs/versioning.md +0 -19
- package/eslint.config.cjs +0 -22
- package/out-tsc/vitest/__tests__/runtime/flow.test.d.ts +0 -2
- package/out-tsc/vitest/__tests__/runtime/flow.test.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/runtime/steps.test.d.ts +0 -2
- package/out-tsc/vitest/__tests__/runtime/steps.test.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/runtime/utils.test.d.ts +0 -2
- package/out-tsc/vitest/__tests__/runtime/utils.test.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/dsl-types.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/dsl-types.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/example-flow.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/example-flow.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/extract-flow-input.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/extract-flow-input.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/extract-flow-steps.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/extract-flow-steps.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/getStepDefinition.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/getStepDefinition.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/step-input.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/step-input.test-d.d.ts.map +0 -1
- package/out-tsc/vitest/__tests__/types/step-output.test-d.d.ts +0 -2
- package/out-tsc/vitest/__tests__/types/step-output.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/project.json +0 -28
- package/prompts/edge-worker-refactor.md +0 -105
- package/src/dsl.ts +0 -318
- package/src/example-flow.ts +0 -67
- package/src/index.ts +0 -1
- package/src/utils.ts +0 -84
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -26
- package/tsconfig.spec.json +0 -35
- package/typecheck.log +0 -120
- package/vite.config.ts +0 -57
package/brainstorming/dsl/cli.md
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# Brainstorm: Converting a TypeScript Flow DSL into pgflow Definitions
|
|
2
|
-
|
|
3
|
-
This document explores various approaches for **translating a TypeScript Flow DSL** (effectively a typed object graph) directly into SQL statements that register flows in **pgflow** via `create_flow` and `add_step`. We also discuss how to manage these flows in development and production, respecting **immutable** flow definitions, versioning via `flow_slug`, and ensuring an **exceptional developer experience**. Finally, we’ll introduce some new ideas and best practices inspired by other tools.
|
|
4
|
-
|
|
5
|
-
## Why We Need a Flow DSL → SQL Compilation Step
|
|
6
|
-
|
|
7
|
-
1. **Single Source of Truth**: The TypeScript DSL is a more developer-friendly way to define flows (with auto-complete, type inference, etc.). However, pgflow requires the flow definition to be present in the database to manage steps, dependencies, and runs.
|
|
8
|
-
2. **Consistency**: We minimize manual steps (writing raw SQL) when we can automate it. This ensures that the flow structure in code stays in sync with what’s actually in the database.
|
|
9
|
-
3. **Safety & Auditing**: Flows are **immutable** in production to avoid “half-upgraded” scenarios. We need a reliable process for introducing new flows or updated flows (via new slugs) and ensuring old ones remain intact if they’re still used.
|
|
10
|
-
|
|
11
|
-
## Summary of Key Requirements
|
|
12
|
-
|
|
13
|
-
- Take the DSL object (with steps, dependencies, timeouts, etc.) and generate:
|
|
14
|
-
- SQL queries calling `pgflow.create_flow(slug, ...)`
|
|
15
|
-
- SQL queries for each step calling `pgflow.add_step(...)`, in topological order.
|
|
16
|
-
- Provide a **development** workflow that is fast to iterate on. Possibly auto-recreate the flow in the DB on every code change.
|
|
17
|
-
- Provide a **production** workflow that is safe and auditable. Possibly generate a migration script that can be run in CI/CD pipelines.
|
|
18
|
-
- If a flow with the same slug but different shape is encountered, we must throw an error (since flows are immutable and can’t be replaced in production).
|
|
19
|
-
- If we do re-register the same slug with the same shape, no updates are needed (safe no-op).
|
|
20
|
-
- Because flows are immutable, changes to shape require a new `flow_slug`.
|
|
21
|
-
|
|
22
|
-
## Potential Approaches
|
|
23
|
-
|
|
24
|
-
### 1. pgflow CLI Tool
|
|
25
|
-
|
|
26
|
-
A dedicated `pgflow` CLI could be responsible for:
|
|
27
|
-
- **“Deploying” a Flow**:
|
|
28
|
-
- Reads the TypeScript flow definitions (compiled or at runtime).
|
|
29
|
-
- Converts them into SQL statements.
|
|
30
|
-
- Executes the statements against the specified database (development environment).
|
|
31
|
-
- **“Compiling” a Flow**:
|
|
32
|
-
- Converts the TypeScript flow definition into raw SQL (or multiple .sql files).
|
|
33
|
-
- Writes these files to a `migrations/` directory for deployment in production.
|
|
34
|
-
- **Version Checking**:
|
|
35
|
-
- If it detects the same `flow_slug` in the DB that differs from the code, it fails with a clear error (“Flow shape mismatch!”).
|
|
36
|
-
- If it’s truly identical (no changes), it does nothing.
|
|
37
|
-
- If it’s new, it proceeds to create the references.
|
|
38
|
-
|
|
39
|
-
#### Pros
|
|
40
|
-
- Straightforward user experience (just run `pgflow deploy` or `pgflow compile`).
|
|
41
|
-
- Clear separation of concerns: code → DSL → SQL → database.
|
|
42
|
-
- Allows ephemeral recreation in development or safer migrations in production.
|
|
43
|
-
|
|
44
|
-
#### Cons
|
|
45
|
-
- Might require additional tooling or configuration to integrate into existing build/deployment pipelines.
|
|
46
|
-
- Must maintain the DSL → SQL translator logic in the CLI.
|
|
47
|
-
|
|
48
|
-
### 2. Edge Worker Auto-Check & Registration
|
|
49
|
-
|
|
50
|
-
In this approach, the Edge Worker, upon startup or flow usage, does the following:
|
|
51
|
-
- Checks if the given `flow_slug` is already registered and if the shape matches.
|
|
52
|
-
- If not, it attempts to create it (in development).
|
|
53
|
-
- If shape mismatch is found in production, it throws a fatal error to prevent usage.
|
|
54
|
-
|
|
55
|
-
#### Pros
|
|
56
|
-
- Zero extra steps for developers (the system just “does the right thing”).
|
|
57
|
-
- Minimizes friction or forgetting to deploy flows.
|
|
58
|
-
|
|
59
|
-
#### Cons
|
|
60
|
-
- Potentially tricky to manage safe versioning in production (accidental shape change could break the environment).
|
|
61
|
-
- Could lead to unexpected changes or overwritten flows if not carefully locked down.
|
|
62
|
-
- Harder to integrate with staging/production pipelines that require explicit migrations.
|
|
63
|
-
|
|
64
|
-
### 3. Hybrid Approach
|
|
65
|
-
|
|
66
|
-
Use a combination of CLI tooling and an Edge Worker check:
|
|
67
|
-
|
|
68
|
-
- **CLI** for local dev:
|
|
69
|
-
- `pgflow dev deploy --force` can recreate the flow on each code change, dropping existing definitions as needed.
|
|
70
|
-
- Acceptable in dev because losing run state is less critical.
|
|
71
|
-
- **CLI** for production migrations:
|
|
72
|
-
- Instead of auto-executing, it writes `.sql` files that must be manually or automatically applied by a migration system.
|
|
73
|
-
- Reinforces the idea that “once in production, flows are immutable.”
|
|
74
|
-
- **Edge Worker**:
|
|
75
|
-
- Optionally can do a final shape check to confirm that dev or staging flows have been properly migrated. If a mismatch is found, throw an error to avoid partial updates.
|
|
76
|
-
|
|
77
|
-
This approach covers all bases: it’s frictionless in dev and strict in production.
|
|
78
|
-
|
|
79
|
-
## Immutable Flow Definitions & Versioning
|
|
80
|
-
|
|
81
|
-
Here’s a recap and deeper explanation of why flows are **immutable** in pgflow:
|
|
82
|
-
|
|
83
|
-
1. **Simplicity**: Maintaining multiple versions simultaneously might create confusion about which version is “official” or “latest.”
|
|
84
|
-
2. **Safety**: Changing a flow mid-run can cause partial upgrades. By “freezing” them, you guarantee a stable environment for ongoing runs.
|
|
85
|
-
3. **Intentional Versioning**: If a flow’s shape changes, you create a new `flow_slug`. For example:
|
|
86
|
-
- `analyze_website_v1` → initial version.
|
|
87
|
-
- `analyze_website_v2` → new shape, separate definition.
|
|
88
|
-
|
|
89
|
-
While optional aliases to represent the “latest” version can be useful, we recommend making it an explicit user-land concept, not a built-in feature. This ensures that every environment references explicit version slugs.
|
|
90
|
-
|
|
91
|
-
## Development vs. Production Strategies
|
|
92
|
-
|
|
93
|
-
### Development (Auto-Update)
|
|
94
|
-
|
|
95
|
-
- **Auto-drop & recreate**: On every run, the system checks if the flow slug exists. If it does, drop the flow definition (and any partial run state) and recreate it fresh.
|
|
96
|
-
- Advantage: Instant reflection of code changes.
|
|
97
|
-
- Disadvantage: You lose state from prior runs. But for dev, that’s often acceptable.
|
|
98
|
-
- **Alternative**: Use a randomness-based slug or incremental suffix in dev, so each new code iteration has a new slug (e.g., `flow_slug_dev_20231012_1`). This preserves old runs at the cost of clutter.
|
|
99
|
-
|
|
100
|
-
### Production (Migration & Strictness)
|
|
101
|
-
|
|
102
|
-
- **SQL Migration**:
|
|
103
|
-
- On code commit, run a command like `pgflow compile my_flow.ts --out migrations/2023-10-01_create_analyze_website.sql`.
|
|
104
|
-
- This file contains:
|
|
105
|
-
```sql
|
|
106
|
-
SELECT pgflow.create_flow('analyze_website', ...);
|
|
107
|
-
SELECT pgflow.add_step('analyze_website', 'website', ...);
|
|
108
|
-
...
|
|
109
|
-
```
|
|
110
|
-
- Then your usual migration system applies this script once. If the slug already exists but definitions differ, the migration fails. Ops can step in to handle that.
|
|
111
|
-
- **No Migration**: If your flows are brand new with brand new slugs, you just add them. If you need to retire old flows, do so manually, or let them remain unchanged.
|
|
112
|
-
|
|
113
|
-
## Potential New Ideas
|
|
114
|
-
|
|
115
|
-
1. **Flow “Insight” Command**:
|
|
116
|
-
- A cli sub-command that prints a summary or “manifest” of the entire DSL — steps, dependencies, types, etc.
|
|
117
|
-
- Helps devs see the shape of the flow quickly or compare two versions at a glance.
|
|
118
|
-
2. **Checksum-based Upsert**:
|
|
119
|
-
- The CLI or Edge Worker could compute a content-based checksum of the DSL shape. If a flow with the same slug but a *different* checksum is in the DB, it refuses to proceed. If the checksums match, it’s a no-op.
|
|
120
|
-
- This ensures no accidental mismatch or partial updates.
|
|
121
|
-
3. **Local “Flow Playground”**:
|
|
122
|
-
- A local web interface that visually shows your flow’s DAG from the DSL, letting you step through nodes or edit them.
|
|
123
|
-
- Beneath the hood, it calls the same DSL → SQL logic for clarity.
|
|
124
|
-
|
|
125
|
-
## High-Level Example: CLI Flow
|
|
126
|
-
|
|
127
|
-
Below is a hypothetical example flow from code to deploy:
|
|
128
|
-
|
|
129
|
-
1. **Write a Flow** in TypeScript:
|
|
130
|
-
|
|
131
|
-
```ts
|
|
132
|
-
const AnalyzeWebsite = new Flow<Input>({
|
|
133
|
-
slug: "analyze_website_v2",
|
|
134
|
-
...
|
|
135
|
-
})
|
|
136
|
-
.step({ slug: "website" }, async (input) => { ... })
|
|
137
|
-
.step({ slug: "sentiment", dependsOn: ["website"] }, async (input) => { ... })
|
|
138
|
-
.step({ slug: "summary", dependsOn: ["website"] }, async (input) => { ... })
|
|
139
|
-
.step({ slug: "saveToDb", dependsOn: ["sentiment", "summary"] }, async (input) => { ... });
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
2. **Compile**: Run:
|
|
143
|
-
```
|
|
144
|
-
$ pgflow compile --file=flows/analyze_website_v2.ts --output=migrations/2023-10-01_analyze_website_v2.sql
|
|
145
|
-
```
|
|
146
|
-
It generates SQL:
|
|
147
|
-
```sql
|
|
148
|
-
SELECT pgflow.create_flow('analyze_website_v2', ...);
|
|
149
|
-
SELECT pgflow.add_step('analyze_website_v2','website', ...);
|
|
150
|
-
SELECT pgflow.add_step('analyze_website_v2','sentiment',..., deps => ARRAY['website']);
|
|
151
|
-
SELECT pgflow.add_step('analyze_website_v2','summary',..., deps => ARRAY['website']);
|
|
152
|
-
SELECT pgflow.add_step('analyze_website_v2','saveToDb',..., deps => ARRAY['sentiment','summary']);
|
|
153
|
-
```
|
|
154
|
-
3. **Deploy** in Development:
|
|
155
|
-
```
|
|
156
|
-
$ pgflow deploy --dev migrations/2023-10-01_analyze_website_v2.sql
|
|
157
|
-
```
|
|
158
|
-
- Optionally it can recreate the flow if it’s changed or new.
|
|
159
|
-
4. **Run** the same file in Production:
|
|
160
|
-
- Typically through your standard migration pipeline (Alembic, Flyway, etc.).
|
|
161
|
-
- If the flow slug is found and definitions mismatch, the migration fails, requiring manual action.
|
|
162
|
-
|
|
163
|
-
## Conclusion
|
|
164
|
-
|
|
165
|
-
The overarching goal is to create an **MVP** that is simple enough for everyday users but can scale to rigorous enterprise demands. By offering a **pgflow CLI** or an **auto-registration** approach, we can cater to different workflows:
|
|
166
|
-
|
|
167
|
-
- **Dev**: Fast, ephemeral registration, possibly auto-dropping old definitions.
|
|
168
|
-
- **Prod**: Strict, immutability-enforced approach via migrations, with versioning handled through unique slugs.
|
|
169
|
-
|
|
170
|
-
### Key Takeaways
|
|
171
|
-
|
|
172
|
-
- **Immutable flows**: If the shape changes, you create a new slug (e.g., “flow_v2”). Older runs remain intact.
|
|
173
|
-
- **Choice of deployment**:
|
|
174
|
-
- *CLI-based* approach is explicit and suits mature pipelines.
|
|
175
|
-
- *Edge Worker* approach is more dynamic but risks environment mismatches if not carefully controlled.
|
|
176
|
-
- **Focus on exceptional DX**: Provide a streamlined developer experience with a one-liner to define, refine, and re-deploy flows, plus a frictionless path for production migration.
|
|
177
|
-
- **Stay flexible**: Expose simple primitives (DSL → SQL), so advanced teams can incorporate them into their own workflows, while new teams can rely on curated commands like `pgflow compile` or `pgflow deploy`.
|
|
178
|
-
|
|
179
|
-
With these foundations in place, **pgflow** can truly stand out as a robust, developer-friendly, and production-safe workflow orchestration framework built entirely in PostgreSQL.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
ai -f src/ -f ../core/README.md -c '
|
|
2
|
-
create FlowCompilator class with `constructor(flow: Flow)`
|
|
3
|
-
this class should have one method `compile(): string` that should return
|
|
4
|
-
an sql definition corresponding to the dsl version of a flow, based on how
|
|
5
|
-
it is described in the README.md of core project:
|
|
6
|
-
|
|
7
|
-
it must output a proper sql code with call to create_flow and multiple calls to add_step in the same order as they are added in the flow
|
|
8
|
-
|
|
9
|
-
make sure function covers happy path and edge cases:
|
|
10
|
-
|
|
11
|
-
- no provided options at all
|
|
12
|
-
- only flow options
|
|
13
|
-
- flow options and step options
|
|
14
|
-
- no flow options, only step options
|
|
15
|
-
- multiple root steps
|
|
16
|
-
- invalid, non-existing options (for flow and for step)
|
|
17
|
-
- invalid value for option (string or something other than number, for flow and for step)
|
|
18
|
-
|
|
19
|
-
make it as simple as readable as possible, extract StepCompilator class in the same file to help with compiling respective steps
|
|
20
|
-
|
|
21
|
-
make sure to use FlowOptions and StepOptions instead of just Record<string, any> for options
|
|
22
|
-
|
|
23
|
-
output typescript
|
|
24
|
-
|
|
25
|
-
' | tee src/compile.ts
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# Updated Analysis and Perspective
|
|
2
|
-
|
|
3
|
-
Below is a revised critique and commentary, incorporating your recent clarifications and feedback. I’ll address each point (1.1 through 1.9) with an honest, realistic view grounded in the broader context of a database-centric, TypeScript-DSL-driven workflow engine.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1.1 Conditional Flow Readability in the Flow DSL
|
|
8
|
-
|
|
9
|
-
### Your New Syntax Proposal
|
|
10
|
-
|
|
11
|
-
You introduced a syntax that uses `.branch(...)` with `runIf` or `runUnless`, for example:
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
new Flow<string>({ slug: 'analyzeWebsite' })
|
|
15
|
-
.step({ slug: 'website' }, async ({ run }) => await fetchData(run.url))
|
|
16
|
-
.branch(
|
|
17
|
-
{
|
|
18
|
-
slug: 'ifSuccess',
|
|
19
|
-
dependsOn: ['website'],
|
|
20
|
-
runIf: { website: { status: 200 } },
|
|
21
|
-
},
|
|
22
|
-
(flow) =>
|
|
23
|
-
flow
|
|
24
|
-
.step({ slug: 'sentiment' }, async ({ run, website }) => /* ... */)
|
|
25
|
-
.step({ slug: 'summary' }, async ({ run, website }) => /* ... */)
|
|
26
|
-
// ...
|
|
27
|
-
)
|
|
28
|
-
.branch(
|
|
29
|
-
{
|
|
30
|
-
slug: 'ifFailure',
|
|
31
|
-
dependsOn: ['website'],
|
|
32
|
-
runUnless: { website: { status: 200 } },
|
|
33
|
-
},
|
|
34
|
-
(flow) => flow.step({ slug: 'notifySentry' })
|
|
35
|
-
);
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
**Perspective**
|
|
39
|
-
- This more explicit `.branch()` approach might help keep complex conditionals separate from linear or parallel steps. It visually distinguishes conditional blocks from “straight line” steps.
|
|
40
|
-
- The partition into `.branch()` calls can indeed improve readability. Each branch can have a purposeful label like `ifSuccess` or `ifFailure`.
|
|
41
|
-
- There is still the underlying risk that if your condition checks are complicated, you may wind up with multiple nested branches. But the syntax is a step in the right direction—it’s relatively clear which branch executes under which condition.
|
|
42
|
-
|
|
43
|
-
**Critical Note**
|
|
44
|
-
- Ensure your team documents (or code-lints) how you want to handle edge cases, like undefined or partial outputs from upstream steps. A typed DSL helps, but corner cases might still arise if, for instance, `website.status` isn’t exactly `200` but is `undefined`.
|
|
45
|
-
- Overall, `.branch()` does not magically solve all “runIf / runUnless” confusion, but it provides a framework that is more visibly structured, which is good for maintainability.
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## 1.2 Transaction Usage Only for State Updates
|
|
50
|
-
|
|
51
|
-
### Your Clarification
|
|
52
|
-
|
|
53
|
-
You stated that transactions are only used for updating the workflow graph status (like “starting a flow,” “completing a task,” or “failing a task”). The actual, potentially long-running work is delegated to a separate task queue worker. That worker does `poll_for_tasks()` and calls `complete_task()` or `fail_task()` outside any lengthy transaction.
|
|
54
|
-
|
|
55
|
-
**Perspective**
|
|
56
|
-
- This is a sound approach. The original worry was that you could end up with long DB transactions blocking rows. But you’ve clarified that the heavy-lifting portion (e.g., fetch data, run ML) happens outside of the transactional boundary.
|
|
57
|
-
- This design is effectively “synchronous to the DB only for small window updates,” so you avoid big performance pitfalls in Postgres.
|
|
58
|
-
- As long as you carefully handle intermittent failures or worker restarts, it should scale nicely.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## 1.3 Strongly-Typed DSL vs. Raw SQL
|
|
63
|
-
|
|
64
|
-
### Your Clarification
|
|
65
|
-
|
|
66
|
-
You emphasize that the TypeScript DSL is the main interface, and it is very strongly typed to prevent cycles, bad dependencies, or invalid payload references. The SQL is only the underlying store.
|
|
67
|
-
|
|
68
|
-
**Perspective**
|
|
69
|
-
- Having a single TypeScript DSL layer that strongly enforces correctness is a major plus. It mitigates the risk of manual mistakes when defining steps, especially around accidental cycles or referencing non-existent steps.
|
|
70
|
-
- This addresses the earlier concern about “Incorrect or Missing Step Ordering” or “SQL drift.” If your DSL’s code generation (or direct usage) is the only path to define flows in the DB, it drastically reduces risk.
|
|
71
|
-
- The critical piece is to ensure the DSL actually gates all writes to the underlying DB. If a developer bypasses the DSL and manually edits SQL definitions, you still risk partial drift. In practice, many teams lock down direct DB access so the DSL is the “single source of truth.”
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## 1.4 Handling Large Outputs with a Blob Reference System
|
|
76
|
-
|
|
77
|
-
### Your Clarification
|
|
78
|
-
|
|
79
|
-
You plan a “Blob reference system” where large step outputs are stored separately, referenced by an ID, and not embedded in the main JSON fields.
|
|
80
|
-
|
|
81
|
-
**Perspective**
|
|
82
|
-
- This directly tackles data-bloating worries. Storing massive JSON outputs inline can be detrimental to performance and disk usage.
|
|
83
|
-
- By storing references in the normal flow record, you keep the critical orchestration metadata small. The actual large data can be offloaded (either to a separate table or even an object store).
|
|
84
|
-
- This approach makes queries on typical step states remain lean and avoids over-fetching huge data you might only need occasionally.
|
|
85
|
-
|
|
86
|
-
**Future Considerations**
|
|
87
|
-
- Introduce automatic TTL or archiving for older blob references. Over time, you may want to clean up or move them to cheaper storage.
|
|
88
|
-
- Provide a concise “downloadBlob(id)” or “getBlob(id)” helper in your DSL so that from a developer’s standpoint, it’s all transparent.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 1.5 Immutable Flow Definitions & Versioning
|
|
93
|
-
|
|
94
|
-
### Your Clarification
|
|
95
|
-
|
|
96
|
-
You decided on flow definitions being immutable once deployed, and you use flow slugs rather than version numbers. “If you want a new version, create a new slug.”
|
|
97
|
-
|
|
98
|
-
**Perspective**
|
|
99
|
-
- This approach prevents a lot of “in-flight mismatch” errors. In a system like Temporal, they also endorse the idea of pinned, immutable workflow definitions.
|
|
100
|
-
- If you do have old runs referencing older flow code, that’s fine—just keep that old slug around. New runs move to the new slug.
|
|
101
|
-
- The caution is that you might accumulate many old slugs over time. Usually, you do a small workflow or database cleanup step for those that are no longer active.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## 1.6 Immutability Alleviates “DB Drift”
|
|
106
|
-
|
|
107
|
-
### Connection to 1.5
|
|
108
|
-
|
|
109
|
-
You noted that thanks to immutable definitions, confusion about “latest” or incremental partial upgrades is largely avoided.
|
|
110
|
-
|
|
111
|
-
**Perspective**
|
|
112
|
-
- Indeed, forcibly using unique slugs for each new definition clarifies which code belongs to which runs.
|
|
113
|
-
- This does require discipline: you can’t just casually rename or repurpose the same slug. But that discipline is usually beneficial in production scenarios.
|
|
114
|
-
- With a strongly typed DSL that demands explicit new slugs, you reduce the risk of “accidental partial migrations.”
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## 1.7 Single-Step Shortcut for Minimal Logic
|
|
119
|
-
|
|
120
|
-
### Your Clarification
|
|
121
|
-
|
|
122
|
-
You can create a single step that does multiple tasks if you are worried about the overhead of many small steps.
|
|
123
|
-
|
|
124
|
-
**Perspective**
|
|
125
|
-
- Sometimes workflows become too granular, where each micro-step is in the DB. If that overhead feels too high, combining multiple consecutive actions in one step is valid.
|
|
126
|
-
- This can make some runs simpler, but you lose some fine-grained visibility or partial retry capability. Decide case-by-case whether each sub-operation should truly be a distinct step (with potential concurrency or separate error handling), or if you’re comfortable bundling them.
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## 1.8 Worker Failure Handling and Debugging
|
|
131
|
-
|
|
132
|
-
### Your Clarification
|
|
133
|
-
|
|
134
|
-
When a handler throws and there are no retries left, the worker calls `fail_task()` with the error, storing that info in `step_tasks`.
|
|
135
|
-
|
|
136
|
-
**Perspective**
|
|
137
|
-
- Storing the error message and stack traces (if feasible) is really helpful for debugging. You can see exactly which step crashed and why.
|
|
138
|
-
- For advanced debugging, you might still rely on logs or external systems to see the “in-progress” states. But at least you have a final resting place in the DB that references the error.
|
|
139
|
-
- This addresses the concern of partial data vanish. You do have enough info to retrospectively figure out what went wrong.
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## 1.9 Avoiding Secrets in Flow Inputs
|
|
144
|
-
|
|
145
|
-
### Your Clarification
|
|
146
|
-
|
|
147
|
-
You plan to pass sensitive secrets via an `env` or `context` object provided by the worker, rather than embedding them in the flow’s JSON.
|
|
148
|
-
|
|
149
|
-
**Perspective**
|
|
150
|
-
- This is a best practice. Storing secrets or tokens in the DB can be risky—even if it’s encrypted, you want to minimize how widely those secrets are exposed.
|
|
151
|
-
- The environment approach is common in serverless or queue-driven architectures. Each process gets the secrets from a secure source and uses them at runtime.
|
|
152
|
-
- The main watchout: ensure no step inadvertently returns these secrets in the step output, or logs them. The strongly typed DSL helps you avoid returning the “env” object as a result.
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Overall Conclusion
|
|
157
|
-
|
|
158
|
-
Your clarifications address many of the original concerns:
|
|
159
|
-
|
|
160
|
-
- **Branching**: The `.branch()` approach can indeed improve readability for conditional flows, though you still need discipline to manage complexities.
|
|
161
|
-
- **Transactions**: Limiting transactions to state-updates only is wise, preventing locking issues.
|
|
162
|
-
- **DSL vs. SQL**: The strongly typed TypeScript layer, plus immutable definitions, not only avoids cycles or ordering issues but also simplifies versioning.
|
|
163
|
-
- **Large Data**: A blob reference system will keep your main tables cleaner and more performant.
|
|
164
|
-
- **Secrets & Security**: Passing secrets around using an environment context helps avoid embedding them in the DB.
|
|
165
|
-
|
|
166
|
-
From a critical but honest standpoint, the system is shaping up to be quite robust—provided that teams adhere to best practices (avoiding direct SQL patches, using unique slugs for versioning, and carefully scoping branching logic). You’ve set a good foundation, especially by focusing on a clear “flow definition vs. flow run” distinction, frictionless concurrency, and a typed DSL that keeps everything consistent.
|