@pgflow/dsl 0.0.5 → 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/CHANGELOG.md +0 -7
- 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
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
# Breaking vs. Non-Breaking Flow Changes: Implementation Guidelines
|
|
2
|
-
|
|
3
|
-
When working with the pgflow orchestration framework, understanding which changes to flows require a new `flow_slug` and which ones can be safely updated in-place is crucial for maintainability and reliability. This document provides comprehensive guidance on making this distinction and implementing changes correctly.
|
|
4
|
-
|
|
5
|
-
## Core Principles of Flow Versioning
|
|
6
|
-
|
|
7
|
-
1. **Flows are immutable once registered**: The graph structure and dependencies should not change for an existing flow_slug.
|
|
8
|
-
2. **Message compatibility**: When updating flows, consider whether existing in-flight messages would be compatible with the new flow structure.
|
|
9
|
-
3. **Graph shape integrity**: Changes that alter the graph's topology require a new `flow_slug`.
|
|
10
|
-
|
|
11
|
-
## Types of Changes
|
|
12
|
-
|
|
13
|
-
### Breaking Changes (Require New flow_slug)
|
|
14
|
-
|
|
15
|
-
Breaking changes affect the shape of the graph or the data flow between steps, making them incompatible with messages already in the queue for previous versions.
|
|
16
|
-
|
|
17
|
-
| Change Type | Example | Why Breaking |
|
|
18
|
-
|-------------|---------|--------------|
|
|
19
|
-
| Adding a step | Adding a new validation step | Changes graph topology |
|
|
20
|
-
| Removing a step | Removing a data processing step | Breaks existing dependencies |
|
|
21
|
-
| Changing step dependencies | Adding/removing a dependency between steps | Alters execution order and data flow |
|
|
22
|
-
| Changing step slug | Renaming `process` to `process_data` | Affects message routing |
|
|
23
|
-
| Changing step return type | Changing return from `string` to `{ value: string }` | Downstream steps expect different format |
|
|
24
|
-
| Changing input payload type | Adding required fields | In-flight messages lack newly required fields |
|
|
25
|
-
| Reordering steps (with different dependencies) | Changing execution sequence | Alters expected data flow |
|
|
26
|
-
|
|
27
|
-
### Non-Breaking Changes (Compatible with existing flow_slug)
|
|
28
|
-
|
|
29
|
-
Non-breaking changes don't alter the graph's structure or affect message compatibility, making them safe to update in-place.
|
|
30
|
-
|
|
31
|
-
| Change Type | Example | Why Non-Breaking |
|
|
32
|
-
|-------------|---------|------------------|
|
|
33
|
-
| Updating `maxAttempts` | Changing from 3 to 5 retries | Only affects execution behavior, not graph structure |
|
|
34
|
-
| Modifying `baseDelay` | Changing from 5s to 10s retry delay | Only affects timing, not data flow |
|
|
35
|
-
| Changing `timeout` value | Extending timeout from 30s to 60s | Only affects execution parameters |
|
|
36
|
-
| Optimizing step handler logic | Improving algorithm efficiency | Internal implementation detail |
|
|
37
|
-
| Adding optional fields to return type | Adding optional metadata | Doesn't break dependent steps |
|
|
38
|
-
| Updating documentation/comments | Adding explanation comments | No runtime impact |
|
|
39
|
-
|
|
40
|
-
## Implementation Best Practices
|
|
41
|
-
|
|
42
|
-
### When to Create a New Flow Version
|
|
43
|
-
|
|
44
|
-
1. **Explicit Versioning**: When making breaking changes, use either:
|
|
45
|
-
- Semantic versioning: `analyze_website_v1.0.0` → `analyze_website_v1.1.0`
|
|
46
|
-
- Date-based versioning: `analyze_website_20231001` → `analyze_website_20231115`
|
|
47
|
-
|
|
48
|
-
2. **Migration Strategy**: When introducing a new flow version:
|
|
49
|
-
```typescript
|
|
50
|
-
// New version with breaking changes
|
|
51
|
-
export const AnalyzeWebsiteV2 = new Flow<Input>({
|
|
52
|
-
slug: 'analyze_website_v2', // Note the new slug
|
|
53
|
-
maxAttempts: 3,
|
|
54
|
-
})
|
|
55
|
-
.step(/* updated step definitions */);
|
|
56
|
-
|
|
57
|
-
// Keep old version for in-flight workflows
|
|
58
|
-
export const AnalyzeWebsiteV1 = new Flow<Input>({
|
|
59
|
-
slug: 'analyze_website_v1',
|
|
60
|
-
maxAttempts: 3,
|
|
61
|
-
})
|
|
62
|
-
.step(/* original step definitions */);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
3. **Transitioning to New Versions**:
|
|
66
|
-
- Run both versions in parallel during transition
|
|
67
|
-
- Direct new runs to new version
|
|
68
|
-
- Allow old version to complete in-flight runs
|
|
69
|
-
- Consider a formal deprecation period for old versions
|
|
70
|
-
|
|
71
|
-
### How to Safely Update Non-Breaking Changes
|
|
72
|
-
|
|
73
|
-
For runtime options like `maxAttempts`, `baseDelay`, or `timeout`:
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
// Original flow with updated non-breaking parameters
|
|
77
|
-
export const AnalyzeWebsite = new Flow<Input>({
|
|
78
|
-
slug: 'analyze_website',
|
|
79
|
-
maxAttempts: 5, // Updated from 3
|
|
80
|
-
baseDelay: 10, // Updated from 5
|
|
81
|
-
})
|
|
82
|
-
.step(
|
|
83
|
-
{
|
|
84
|
-
slug: 'sentiment',
|
|
85
|
-
dependsOn: ['website'],
|
|
86
|
-
timeout: 60, // Updated from 30
|
|
87
|
-
maxAttempts: 7 // Updated from 5
|
|
88
|
-
},
|
|
89
|
-
async (input) => await analyzeSentiment(input.website.content)
|
|
90
|
-
);
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### Runtime Options Handling
|
|
94
|
-
|
|
95
|
-
The implementation should separate runtime execution parameters from the flow structure definition:
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
// Implementation pseudocode for flow registration system
|
|
99
|
-
|
|
100
|
-
function registerFlow(flow) {
|
|
101
|
-
const existingFlow = getFlowFromDatabase(flow.flowOptions.slug);
|
|
102
|
-
|
|
103
|
-
if (existingFlow) {
|
|
104
|
-
// Check if graph shape is identical
|
|
105
|
-
if (!areGraphShapesIdentical(existingFlow, flow)) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`Flow with slug "${flow.flowOptions.slug}" already exists with different shape. ` +
|
|
108
|
-
`Use a new flow_slug for breaking changes.`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// For non-breaking changes, update runtime options only
|
|
113
|
-
updateFlowRuntimeOptions(
|
|
114
|
-
flow.flowOptions.slug,
|
|
115
|
-
flow.flowOptions
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
// Update step-specific runtime options
|
|
119
|
-
Object.entries(flow.getSteps()).forEach(([stepSlug, stepDefinition]) => {
|
|
120
|
-
updateStepRuntimeOptions(
|
|
121
|
-
flow.flowOptions.slug,
|
|
122
|
-
stepSlug,
|
|
123
|
-
{
|
|
124
|
-
maxAttempts: stepDefinition.maxAttempts,
|
|
125
|
-
baseDelay: stepDefinition.baseDelay,
|
|
126
|
-
timeout: stepDefinition.timeout,
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
console.log(`Updated runtime options for flow "${flow.flowOptions.slug}"`);
|
|
132
|
-
} else {
|
|
133
|
-
// Register new flow with full definition
|
|
134
|
-
createNewFlow(flow);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
## Technical Implementation Considerations
|
|
140
|
-
|
|
141
|
-
### 1. Flow Shape Comparison
|
|
142
|
-
|
|
143
|
-
To determine if a flow structure has changed (requiring a new slug):
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
function areGraphShapesIdentical(flow1, flow2) {
|
|
147
|
-
// Check if both flows have the same steps
|
|
148
|
-
const steps1 = Object.keys(flow1.getSteps()).sort();
|
|
149
|
-
const steps2 = Object.keys(flow2.getSteps()).sort();
|
|
150
|
-
|
|
151
|
-
if (!arraysEqual(steps1, steps2)) return false;
|
|
152
|
-
|
|
153
|
-
// Check if dependencies are identical for each step
|
|
154
|
-
for (const stepSlug of steps1) {
|
|
155
|
-
const deps1 = flow1.getSteps()[stepSlug].dependencies.sort();
|
|
156
|
-
const deps2 = flow2.getSteps()[stepSlug].dependencies.sort();
|
|
157
|
-
|
|
158
|
-
if (!arraysEqual(deps1, deps2)) return false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### 2. Database Schema Support
|
|
166
|
-
|
|
167
|
-
The database schema should separate immutable flow structure from mutable runtime options:
|
|
168
|
-
|
|
169
|
-
```sql
|
|
170
|
-
-- Immutable flow structure (never changes for same flow_slug)
|
|
171
|
-
CREATE TABLE pgflow.flows (
|
|
172
|
-
flow_slug TEXT PRIMARY KEY,
|
|
173
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
CREATE TABLE pgflow.flow_steps (
|
|
177
|
-
flow_slug TEXT REFERENCES pgflow.flows(flow_slug),
|
|
178
|
-
step_slug TEXT NOT NULL,
|
|
179
|
-
dependencies TEXT[] NOT NULL,
|
|
180
|
-
PRIMARY KEY (flow_slug, step_slug)
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
-- Mutable runtime options (can be updated)
|
|
184
|
-
CREATE TABLE pgflow.flow_options (
|
|
185
|
-
flow_slug TEXT REFERENCES pgflow.flows(flow_slug),
|
|
186
|
-
max_attempts INTEGER,
|
|
187
|
-
base_delay INTEGER,
|
|
188
|
-
timeout INTEGER,
|
|
189
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
190
|
-
PRIMARY KEY (flow_slug)
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
CREATE TABLE pgflow.step_options (
|
|
194
|
-
flow_slug TEXT,
|
|
195
|
-
step_slug TEXT,
|
|
196
|
-
max_attempts INTEGER,
|
|
197
|
-
base_delay INTEGER,
|
|
198
|
-
timeout INTEGER,
|
|
199
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
200
|
-
PRIMARY KEY (flow_slug, step_slug),
|
|
201
|
-
FOREIGN KEY (flow_slug, step_slug) REFERENCES pgflow.flow_steps(flow_slug, step_slug)
|
|
202
|
-
);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 3. CLI Support for Flow Diffing
|
|
206
|
-
|
|
207
|
-
Provide a CLI tool to help developers identify breaking vs. non-breaking changes:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
# Example CLI usage
|
|
211
|
-
pgflow diff --current-flow analyze_website --new-flow-definition ./flows/analyze_website.ts
|
|
212
|
-
|
|
213
|
-
# Output:
|
|
214
|
-
# Breaking changes detected:
|
|
215
|
-
# - Step 'validate' added
|
|
216
|
-
# - Dependencies changed for step 'saveToDb' (added: 'validate')
|
|
217
|
-
#
|
|
218
|
-
# Please create a new flow_slug to implement these changes.
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
## Workflow and Testing Recommendations
|
|
222
|
-
|
|
223
|
-
### Development Workflow
|
|
224
|
-
|
|
225
|
-
1. **Create a flow definition in TypeScript**
|
|
226
|
-
2. **Validate changes**:
|
|
227
|
-
- Use the `pgflow diff` command to check if changes are breaking
|
|
228
|
-
- For breaking changes, create a new flow version with updated slug
|
|
229
|
-
3. **Register the flow**:
|
|
230
|
-
- In development, use `pgflow deploy --dev` for quick iteration
|
|
231
|
-
- In production, follow proper migration processes
|
|
232
|
-
|
|
233
|
-
### Testing Strategy
|
|
234
|
-
|
|
235
|
-
1. **Unit test flow definitions** to verify graph structure
|
|
236
|
-
2. **Integration tests** to ensure compatible message passing between steps
|
|
237
|
-
3. **Version transition tests** to verify that both old and new versions run correctly during migration
|
|
238
|
-
|
|
239
|
-
### Monitoring Version Transitions
|
|
240
|
-
|
|
241
|
-
When transitioning between flow versions:
|
|
242
|
-
|
|
243
|
-
1. Monitor completion of in-flight executions of old versions
|
|
244
|
-
2. Track success rates of new flow versions
|
|
245
|
-
3. Implement alerting for flow execution failures
|
|
246
|
-
4. Consider a formal deprecation period before removing old versions
|
|
247
|
-
|
|
248
|
-
## Conclusion
|
|
249
|
-
|
|
250
|
-
By clearly separating breaking changes (requiring new flow slugs) from non-breaking updates (compatible with existing flow slugs), you can maintain a robust and reliable workflow orchestration system while still allowing for necessary evolution.
|
|
251
|
-
|
|
252
|
-
Remember these key principles:
|
|
253
|
-
- Changes to graph structure or data flow are breaking changes
|
|
254
|
-
- Runtime behavior adjustments are generally non-breaking
|
|
255
|
-
- Always verify compatibility before updating existing flows
|
|
256
|
-
- Use explicit versioning for breaking changes
|
|
257
|
-
- Document your versioning strategy for team alignment
|
|
258
|
-
|
|
259
|
-
Following these guidelines will help ensure that your workflow orchestration system remains stable, predictable, and maintainable as it evolves over time.
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
## Analysis of Proposed Refactoring
|
|
2
|
-
|
|
3
|
-
Below is a high-level analysis of the changes needed to achieve constructor-based dependency injection for the Worker and to generalize the polling and execution logic so that different backends (like pgflow vs. pgmq) can be plugged in.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
### 1. Constructor-Based DI for All Core Components
|
|
8
|
-
|
|
9
|
-
**Goal:** Eliminate internal construction of objects in `Worker`. Pass in each dependency (Poller, Executor, Lifecycle manager, etc.) at construction time.
|
|
10
|
-
|
|
11
|
-
1. **Worker**
|
|
12
|
-
- Currently: The `Worker` class creates and configures its own `Queue`, `ExecutionController`, `BatchProcessor`, etc. internally.
|
|
13
|
-
- Proposed: The `Worker` constructor receives fully configured instances of:
|
|
14
|
-
- A **Poller** (the component that retrieves tasks based on queue or flow logic).
|
|
15
|
-
- A **PayloadExecutor** (the component that processes a single task, including archiving, retrying, etc.).
|
|
16
|
-
- A **Lifecycle** (the component that manages worker states, heartbeats, etc.).
|
|
17
|
-
|
|
18
|
-
2. **Benefits**
|
|
19
|
-
- **Loose Coupling**: The `Worker` no longer has knowledge of how the poller or executor are implemented.
|
|
20
|
-
- **Testability**: Each component can be tested in isolation (mocks or stubs can be injected).
|
|
21
|
-
- **Extensibility**: Swapping out a `Poller` or `PayloadExecutor` for a different backend becomes straightforward.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
### 2. Rename and Generalize `MessageExecutor` → `PayloadExecutor`
|
|
26
|
-
|
|
27
|
-
**Goal:** Abstract away the notion of "messages" vs. "tasks" and unify them into a more general "payload" concept.
|
|
28
|
-
|
|
29
|
-
1. **Current State**:
|
|
30
|
-
- `MessageExecutor` is tightly coupled to the `MessageRecord` shape from pgmq.
|
|
31
|
-
- `execute()` includes logic for archiving or retrying through the pgmq queue.
|
|
32
|
-
|
|
33
|
-
2. **Proposed Adjustments**:
|
|
34
|
-
- Rename `MessageExecutor` → `PayloadExecutor<TPayload>`.
|
|
35
|
-
- Accept whatever “payload” shape is needed by the backend (`MessageRecord`, `worker_task`, etc.).
|
|
36
|
-
- A `PayloadExecutor` specifically for pgflow would handle `pgflow_worker_task` shaped payloads (i.e. handle `complete_task` or `fail_task`).
|
|
37
|
-
- Another `PayloadExecutor` could still handle `pgmq` messages in the old style of `archive`, `retry`, etc.
|
|
38
|
-
|
|
39
|
-
3. **Benefits**:
|
|
40
|
-
- **Worker-Agnostic**: The `Worker` loops over generic payloads, calling `executor.execute()`. The business logic of success/failure is in the executor.
|
|
41
|
-
- **Clearer Abstractions**: `PayloadExecutor` is now dedicated to “process this item and finalize in the store (archive/fail).”
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
### 3. Poller Abstraction
|
|
46
|
-
|
|
47
|
-
**Goal:** Replace specialized poll logic inside the worker with a clean interface. For instance, one poller for pgmq (`ReadWithPollPoller`) and another for pgflow (`poll_for_tasks`).
|
|
48
|
-
|
|
49
|
-
1. **Current State**:
|
|
50
|
-
- `BatchProcessor` or `ReadWithPollPoller` is used specifically for pgmq-based reads.
|
|
51
|
-
- `ListenNotifyPoller` is an alternative approach but still specific to pgmq.
|
|
52
|
-
|
|
53
|
-
2. **Proposed Adjustments**:
|
|
54
|
-
- Define a generic `Poller<TPayload>` interface with a `poll(): Promise<TPayload[]>` method.
|
|
55
|
-
- The worker loops over `poll()` calls on an injected `Poller`.
|
|
56
|
-
- For pgflow:
|
|
57
|
-
- Implement a `PgFlowPoller` that calls `poll_for_tasks` and returns an array of `worker_task`.
|
|
58
|
-
- For pgmq:
|
|
59
|
-
- Keep a `PgmqPoller` that calls `readWithPoll`, returning an array of `MessageRecord`.
|
|
60
|
-
|
|
61
|
-
3. **Benefits**:
|
|
62
|
-
- Each poller fully encapsulates how to fetch items.
|
|
63
|
-
- The `Worker` only needs `poll()`.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
### 4. Worker’s Main Loop Now Becomes Simple
|
|
68
|
-
|
|
69
|
-
**Goal:** The Worker core loop is only responsible for:
|
|
70
|
-
|
|
71
|
-
1. Checking lifecycle signals (e.g., if `abortSignal.aborted` or the worker state is “Stopping”).
|
|
72
|
-
2. Calling `poller.poll()` to retrieve tasks.
|
|
73
|
-
3. Passing each received payload to `executor.execute()`.
|
|
74
|
-
4. Occasionally sending heartbeats.
|
|
75
|
-
5. Handling errors in a top-level “catch” to keep the loop going.
|
|
76
|
-
|
|
77
|
-
In other words, no knowledge of *how* tasks are polled or how they are completed. All logic is delegated to the injected poller and executor.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
### 5. Impact on Lifecycle & Queries
|
|
82
|
-
|
|
83
|
-
**Goal:** Lifecycle management (start, stop, heartbeat) remains the same but is injected as a ready-to-use instance.
|
|
84
|
-
|
|
85
|
-
- **`WorkerLifecycle`** can still own references to `Queries` or `queueName` for heartbeats and updating “worker started/stopped.”
|
|
86
|
-
- It’s simply injected into the Worker constructor, along with the poller/executor, so that the Worker’s only lifecycle knowledge is:
|
|
87
|
-
- “I should call `lifecycle.acknowledgeStart()` before beginning.”
|
|
88
|
-
- “I call `lifecycle.sendHeartbeat()` on each loop iteration.”
|
|
89
|
-
- “I call `lifecycle.acknowledgeStop()` when done.”
|
|
90
|
-
|
|
91
|
-
This keeps lifecycle logic entirely separate from poller or execution logic.
|
|
92
|
-
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
### 6. Larger Changes in pgflow
|
|
96
|
-
|
|
97
|
-
**Context:** pgflow’s ability to orchestrate tasks with “complete/fail” steps means it needs a specialized poller (`poll_for_tasks`) and specialized executor (`complete_task` or `fail_task`). The result is conceptually different from the pgmq “archive” approach.
|
|
98
|
-
|
|
99
|
-
**Recommendation**:
|
|
100
|
-
- Implement a new `PgFlowPoller` returning typed `WorkerTask[]`.
|
|
101
|
-
- Implement a new `PgFlowExecutor` that processes each `WorkerTask`.
|
|
102
|
-
- On success, calls `complete_task`.
|
|
103
|
-
- On error, calls `fail_task`.
|
|
104
|
-
|
|
105
|
-
Hence, the Worker remains ignorant of whether the tasks come from pgmq or pgflow.
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
### 7. Summary of File-Level Changes
|
|
110
|
-
|
|
111
|
-
- **`Worker.ts`**
|
|
112
|
-
- Takes constructor args like `(poller: Poller<TPayload>, executor: PayloadExecutor<TPayload>, lifecycle: WorkerLifecycle, signal?: AbortSignal, ...)`.
|
|
113
|
-
- Eliminates internal references to `Queue`, `BatchProcessor`, or `ExecutionController`.
|
|
114
|
-
- Main loop is simplified: poll → execute → heartbeat → loop.
|
|
115
|
-
|
|
116
|
-
- **`MessageExecutor.ts`** → **`PayloadExecutor.ts`**
|
|
117
|
-
- Parameterize: `class PayloadExecutor<TPayload> { ... }`
|
|
118
|
-
- Implement specialized logic in separate classes if needed (e.g., `PgmqExecutor`, `PgFlowExecutor`).
|
|
119
|
-
|
|
120
|
-
- **`BatchProcessor`, `ListenNotifyPoller`, `ReadWithPollPoller`, etc.**
|
|
121
|
-
- Potentially reworked or consolidated under `Poller<TPayload>` interface.
|
|
122
|
-
- If still used for pgmq, keep them internal to a `pgmq` package or “default poller” implementation.
|
|
123
|
-
|
|
124
|
-
- **`ExecutionController`**
|
|
125
|
-
- Could be replaced or dramatically simplified: if concurrency control is still needed, it can remain a private detail inside the chosen `PayloadExecutor` or integrated into the poller. Or keep a separate concurrency-limiting layer, but construct it outside the `Worker`.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### 8. Expected Outcomes
|
|
130
|
-
|
|
131
|
-
1. **Separation of Concerns**:
|
|
132
|
-
- Worker does only “loop + lifecycle”
|
|
133
|
-
- Poller does “fetch tasks”
|
|
134
|
-
- Executor does “execute tasks”
|
|
135
|
-
2. **Adaptability**:
|
|
136
|
-
- We can create specialized pollers/executors for different backends (pgmq, pgflow, etc.) without altering `Worker`.
|
|
137
|
-
3. **Better Maintenance**:
|
|
138
|
-
- Code is easier to extend or refactor because each piece has a well-defined responsibility.
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## Conclusion
|
|
143
|
-
|
|
144
|
-
By introducing constructor-based dependency injection and splitting out pollers/executors as standalone interfaces, we cleanly decouple the Worker from specific queue or flow implementations. This approach allows for multiple polling strategies (e.g., pgflow vs. pgmq), varied execution (archiving vs. step completion), and makes the Worker’s main loop focused on managing its own lifecycle and health checks.
|
|
145
|
-
|
|
146
|
-
Overall, these changes should yield a more modular, maintainable, and testable codebase that is ready to accommodate new backend features (like pgflow’s “complete/fail” tasks) without breaking the existing pgmq-based functionality.
|
package/docs/versioning.md
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Versioning of the flows
|
|
2
|
-
|
|
3
|
-
## Agreed on immutable flow definitions (similar to Temporal)
|
|
4
|
-
|
|
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
|
-
|
|
9
|
-
## Benefits of immutable approach
|
|
10
|
-
|
|
11
|
-
- Simplifies implementation
|
|
12
|
-
- Provides natural versioning cascade for subflows
|
|
13
|
-
- Makes version transitions explicit and intentional
|
|
14
|
-
- Avoids "half-upgraded" scenarios
|
|
15
|
-
|
|
16
|
-
## Consciously decided against "latest" aliases for now
|
|
17
|
-
- Could introduce complexity and unpredictable behavior
|
|
18
|
-
- Users can implement their own aliasing logic if needed
|
|
19
|
-
- Explicit slugs provide clarity about which version is being used
|
package/eslint.config.cjs
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
const baseConfig = require('../../eslint.config.cjs');
|
|
2
|
-
|
|
3
|
-
module.exports = [
|
|
4
|
-
...baseConfig,
|
|
5
|
-
{
|
|
6
|
-
files: ['**/*.json'],
|
|
7
|
-
rules: {
|
|
8
|
-
'@nx/dependency-checks': [
|
|
9
|
-
'error',
|
|
10
|
-
{
|
|
11
|
-
ignoredFiles: [
|
|
12
|
-
'{projectRoot}/eslint.config.{js,cjs,mjs}',
|
|
13
|
-
'{projectRoot}/vite.config.{js,ts,mjs,mts}*',
|
|
14
|
-
],
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
languageOptions: {
|
|
19
|
-
parser: require('jsonc-eslint-parser'),
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"flow.test.d.ts","sourceRoot":"","sources":["../../../../__tests__/runtime/flow.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"steps.test.d.ts","sourceRoot":"","sources":["../../../../__tests__/runtime/steps.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../../../__tests__/runtime/utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dsl-types.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/dsl-types.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"example-flow.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/example-flow.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extract-flow-input.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/extract-flow-input.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extract-flow-steps.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/extract-flow-steps.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getStepDefinition.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/getStepDefinition.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"step-input.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/step-input.test-d.ts"],"names":[],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"step-output.test-d.d.ts","sourceRoot":"","sources":["../../../../__tests__/types/step-output.test-d.ts"],"names":[],"mappings":""}
|