@pgflow/dsl 0.0.0-test-snapshot-releases2-8d5d9bc1-20250922101158 → 0.0.0-testsnap-9294d743-20251207205914
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/README.md +146 -3
- package/dist/CHANGELOG.md +481 -0
- package/dist/README.md +346 -0
- package/dist/compile-flow.d.ts +10 -0
- package/dist/compile-flow.d.ts.map +1 -0
- package/dist/compile-flow.js +50 -0
- package/dist/dsl.d.ts +256 -0
- package/dist/dsl.d.ts.map +1 -0
- package/dist/dsl.js +159 -0
- package/dist/example-flow.d.ts +29 -0
- package/dist/example-flow.d.ts.map +1 -0
- package/dist/example-flow.js +41 -0
- package/dist/flow-shape.d.ts +81 -0
- package/dist/flow-shape.d.ts.map +1 -0
- package/dist/flow-shape.js +119 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/package.json +36 -0
- package/dist/platforms/index.d.ts +2 -0
- package/dist/platforms/index.d.ts.map +1 -0
- package/dist/platforms/index.js +2 -0
- package/dist/platforms/supabase.d.ts +23 -0
- package/dist/platforms/supabase.d.ts.map +1 -0
- package/dist/platforms/supabase.js +4 -0
- package/dist/supabase.d.ts +2 -0
- package/dist/supabase.d.ts.map +1 -0
- package/dist/supabase.js +2 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/dist/utils.d.ts +37 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +73 -0
- package/package.json +2 -1
package/dist/dsl.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { validateRuntimeOptions, validateSlug } from './utils.js';
|
|
2
|
+
// Flow class definition
|
|
3
|
+
export class Flow {
|
|
4
|
+
/**
|
|
5
|
+
* Store step definitions with their proper types
|
|
6
|
+
*
|
|
7
|
+
* This is typed as a generic record because TypeScript cannot track the exact relationship
|
|
8
|
+
* between step slugs and their corresponding input/output types at the container level.
|
|
9
|
+
* Type safety is enforced at the method level when adding or retrieving steps.
|
|
10
|
+
*/
|
|
11
|
+
stepDefinitions;
|
|
12
|
+
stepOrder;
|
|
13
|
+
slug;
|
|
14
|
+
options;
|
|
15
|
+
constructor(config, stepDefinitions = {}, stepOrder = []) {
|
|
16
|
+
// Extract slug and options separately
|
|
17
|
+
const { slug, ...options } = config;
|
|
18
|
+
// Validate the slug
|
|
19
|
+
validateSlug(slug);
|
|
20
|
+
// Validate runtime options (optional for Flow level)
|
|
21
|
+
validateRuntimeOptions(options, { optional: true });
|
|
22
|
+
this.slug = slug;
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.stepDefinitions = stepDefinitions;
|
|
25
|
+
// Defensive copy of stepOrder
|
|
26
|
+
this.stepOrder = [...stepOrder];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get a specific step definition by slug with proper typing
|
|
30
|
+
* @throws Error if the step with the given slug doesn't exist
|
|
31
|
+
*/
|
|
32
|
+
getStepDefinition(slug) {
|
|
33
|
+
// Check if the slug exists in stepDefinitions using a more explicit pattern
|
|
34
|
+
if (!(slug in this.stepDefinitions)) {
|
|
35
|
+
throw new Error(`Step "${String(slug)}" does not exist in flow "${this.slug}"`);
|
|
36
|
+
}
|
|
37
|
+
// Use a type assertion directive to tell TypeScript that this is safe
|
|
38
|
+
// @ts-expect-error The type system cannot track that this.stepDefinitions[slug] has the correct type
|
|
39
|
+
// but we know it's safe because we only add steps through the strongly-typed `step` method
|
|
40
|
+
return this.stepDefinitions[slug];
|
|
41
|
+
}
|
|
42
|
+
// SlugType extends keyof Steps & keyof StepDependencies
|
|
43
|
+
step(opts, handler) {
|
|
44
|
+
const slug = opts.slug;
|
|
45
|
+
// Validate the step slug
|
|
46
|
+
validateSlug(slug);
|
|
47
|
+
if (this.stepDefinitions[slug]) {
|
|
48
|
+
throw new Error(`Step "${slug}" already exists in flow "${this.slug}"`);
|
|
49
|
+
}
|
|
50
|
+
const dependencies = opts.dependsOn || [];
|
|
51
|
+
// Validate dependencies - check if all referenced steps exist
|
|
52
|
+
if (dependencies.length > 0) {
|
|
53
|
+
for (const dep of dependencies) {
|
|
54
|
+
if (!this.stepDefinitions[dep]) {
|
|
55
|
+
throw new Error(`Step "${slug}" depends on undefined step "${dep}"`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Extract RuntimeOptions from opts
|
|
60
|
+
const options = {};
|
|
61
|
+
if (opts.maxAttempts !== undefined)
|
|
62
|
+
options.maxAttempts = opts.maxAttempts;
|
|
63
|
+
if (opts.baseDelay !== undefined)
|
|
64
|
+
options.baseDelay = opts.baseDelay;
|
|
65
|
+
if (opts.timeout !== undefined)
|
|
66
|
+
options.timeout = opts.timeout;
|
|
67
|
+
if (opts.startDelay !== undefined)
|
|
68
|
+
options.startDelay = opts.startDelay;
|
|
69
|
+
// Validate runtime options (optional for step level)
|
|
70
|
+
validateRuntimeOptions(options, { optional: true });
|
|
71
|
+
// Preserve the exact type of the handler
|
|
72
|
+
const newStepDefinition = {
|
|
73
|
+
slug,
|
|
74
|
+
handler: handler, // Type assertion needed due to complex generic constraints
|
|
75
|
+
dependencies: dependencies,
|
|
76
|
+
options,
|
|
77
|
+
};
|
|
78
|
+
const newStepDefinitions = {
|
|
79
|
+
...this.stepDefinitions,
|
|
80
|
+
[slug]: newStepDefinition,
|
|
81
|
+
};
|
|
82
|
+
// Create a new stepOrder array with the new slug appended
|
|
83
|
+
const newStepOrder = [...this.stepOrder, slug];
|
|
84
|
+
// Create a new flow with the same slug and options but with updated type parameters
|
|
85
|
+
// We need to use type assertions here because TypeScript cannot track the exact relationship
|
|
86
|
+
// between the specific step definition types and the generic Flow type parameters
|
|
87
|
+
// This is safe because we're constructing the newStepDefinitions in a type-safe way above
|
|
88
|
+
return new Flow({ slug: this.slug, ...this.options }, newStepDefinitions, newStepOrder);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Add an array-returning step to the flow with compile-time type safety
|
|
92
|
+
*
|
|
93
|
+
* This method provides semantic clarity and type enforcement for steps that return arrays,
|
|
94
|
+
* while maintaining full compatibility with the existing step system by delegating to `.step()`.
|
|
95
|
+
*
|
|
96
|
+
* @template Slug - The unique identifier for this step
|
|
97
|
+
* @template THandler - The handler function that must return an array or Promise<array>
|
|
98
|
+
* @template Deps - The step dependencies (must be existing step slugs)
|
|
99
|
+
* @param opts - Step configuration including slug, dependencies, and runtime options
|
|
100
|
+
* @param handler - Function that processes input and returns an array (null/undefined rejected)
|
|
101
|
+
* @returns A new Flow instance with the array step added
|
|
102
|
+
*/
|
|
103
|
+
array(opts, handler) {
|
|
104
|
+
// Delegate to existing .step() method for maximum code reuse
|
|
105
|
+
return this.step(opts, handler);
|
|
106
|
+
}
|
|
107
|
+
// Implementation
|
|
108
|
+
map(opts, handler) {
|
|
109
|
+
const slug = opts.slug;
|
|
110
|
+
// Validate the step slug
|
|
111
|
+
validateSlug(slug);
|
|
112
|
+
if (this.stepDefinitions[slug]) {
|
|
113
|
+
throw new Error(`Step "${slug}" already exists in flow "${this.slug}"`);
|
|
114
|
+
}
|
|
115
|
+
// Determine dependencies based on whether array is specified
|
|
116
|
+
let dependencies = [];
|
|
117
|
+
const arrayDep = opts.array;
|
|
118
|
+
if (arrayDep) {
|
|
119
|
+
// Dependent map - validate single dependency exists and returns array
|
|
120
|
+
if (!this.stepDefinitions[arrayDep]) {
|
|
121
|
+
throw new Error(`Step "${slug}" depends on undefined step "${arrayDep}"`);
|
|
122
|
+
}
|
|
123
|
+
dependencies = [arrayDep];
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Root map - flow input must be an array (type system enforces this)
|
|
127
|
+
dependencies = [];
|
|
128
|
+
}
|
|
129
|
+
// Extract runtime options
|
|
130
|
+
const options = {};
|
|
131
|
+
if (opts.maxAttempts !== undefined)
|
|
132
|
+
options.maxAttempts = opts.maxAttempts;
|
|
133
|
+
if (opts.baseDelay !== undefined)
|
|
134
|
+
options.baseDelay = opts.baseDelay;
|
|
135
|
+
if (opts.timeout !== undefined)
|
|
136
|
+
options.timeout = opts.timeout;
|
|
137
|
+
if (opts.startDelay !== undefined)
|
|
138
|
+
options.startDelay = opts.startDelay;
|
|
139
|
+
// Validate runtime options
|
|
140
|
+
validateRuntimeOptions(options, { optional: true });
|
|
141
|
+
// Create the map step definition with stepType
|
|
142
|
+
// Note: We use AnyInput/AnyOutput here because the actual types are handled at the type level via overloads
|
|
143
|
+
const newStepDefinition = {
|
|
144
|
+
slug,
|
|
145
|
+
handler: handler, // Type assertion needed due to complex generic constraints
|
|
146
|
+
dependencies,
|
|
147
|
+
options,
|
|
148
|
+
stepType: 'map', // Mark this as a map step
|
|
149
|
+
};
|
|
150
|
+
const newStepDefinitions = {
|
|
151
|
+
...this.stepDefinitions,
|
|
152
|
+
[slug]: newStepDefinition,
|
|
153
|
+
};
|
|
154
|
+
// Create a new stepOrder array with the new slug appended
|
|
155
|
+
const newStepOrder = [...this.stepOrder, slug];
|
|
156
|
+
// Create and return new Flow instance with updated types
|
|
157
|
+
return new Flow({ slug: this.slug, ...this.options }, newStepDefinitions, newStepOrder); // Type assertion handled by overloads
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Flow } from './dsl.js';
|
|
2
|
+
type Input = {
|
|
3
|
+
url: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const AnalyzeWebsite: Flow<Input, {}, import("./dsl.js").EmptySteps & {
|
|
6
|
+
website: {
|
|
7
|
+
content: string;
|
|
8
|
+
};
|
|
9
|
+
} & {
|
|
10
|
+
sentiment: {
|
|
11
|
+
score: number;
|
|
12
|
+
};
|
|
13
|
+
} & {
|
|
14
|
+
summary: {
|
|
15
|
+
aiSummary: string;
|
|
16
|
+
};
|
|
17
|
+
} & {
|
|
18
|
+
saveToDb: string;
|
|
19
|
+
}, import("./dsl.js").EmptyDeps & {
|
|
20
|
+
website: never[];
|
|
21
|
+
} & {
|
|
22
|
+
sentiment: "website"[];
|
|
23
|
+
} & {
|
|
24
|
+
summary: "website"[];
|
|
25
|
+
} & {
|
|
26
|
+
saveToDb: ("sentiment" | "summary")[];
|
|
27
|
+
}, import("./dsl.js").Env>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=example-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"example-flow.d.ts","sourceRoot":"","sources":["../src/example-flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAGhC,KAAK,KAAK,GAAG;IACX,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;0BA4BxB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Flow } from './dsl.js';
|
|
2
|
+
export const AnalyzeWebsite = new Flow({
|
|
3
|
+
slug: 'analyze_website',
|
|
4
|
+
maxAttempts: 3,
|
|
5
|
+
baseDelay: 5,
|
|
6
|
+
timeout: 10,
|
|
7
|
+
})
|
|
8
|
+
.step({ slug: 'website' }, async (input) => await scrapeWebsite(input.run.url))
|
|
9
|
+
.step({ slug: 'sentiment', dependsOn: ['website'], timeout: 30, maxAttempts: 5 }, async (input) => await analyzeSentiment(input.website.content))
|
|
10
|
+
.step({ slug: 'summary', dependsOn: ['website'] }, async (input) => await summarizeWithAI(input.website.content))
|
|
11
|
+
.step({ slug: 'saveToDb', dependsOn: ['sentiment', 'summary'] }, async (input) => {
|
|
12
|
+
const results = await saveToDb({
|
|
13
|
+
websiteUrl: input.run.url,
|
|
14
|
+
sentiment: input.sentiment.score,
|
|
15
|
+
summary: input.summary.aiSummary,
|
|
16
|
+
});
|
|
17
|
+
return results.status;
|
|
18
|
+
});
|
|
19
|
+
/***********************************************************************
|
|
20
|
+
****** functions *******************************************************
|
|
21
|
+
***********************************************************************/
|
|
22
|
+
async function scrapeWebsite(url) {
|
|
23
|
+
return {
|
|
24
|
+
content: `Lorem ipsum ${url.length}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const analyzeSentiment = async (_content) => {
|
|
28
|
+
return {
|
|
29
|
+
score: Math.random(),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const summarizeWithAI = async (content) => {
|
|
33
|
+
return {
|
|
34
|
+
aiSummary: `Lorem ipsum ${content.length}`,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const saveToDb = async (_input) => {
|
|
38
|
+
return {
|
|
39
|
+
status: 'success',
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { AnyFlow } from './dsl.js';
|
|
2
|
+
/**
|
|
3
|
+
* Step-level options that can be included in the shape for creation,
|
|
4
|
+
* but are NOT compared during shape comparison (runtime tunable).
|
|
5
|
+
*/
|
|
6
|
+
export interface StepShapeOptions {
|
|
7
|
+
maxAttempts?: number;
|
|
8
|
+
baseDelay?: number;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
startDelay?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Flow-level options that can be included in the shape for creation,
|
|
14
|
+
* but are NOT compared during shape comparison (runtime tunable).
|
|
15
|
+
*/
|
|
16
|
+
export interface FlowShapeOptions {
|
|
17
|
+
maxAttempts?: number;
|
|
18
|
+
baseDelay?: number;
|
|
19
|
+
timeout?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* StepShape captures the structural definition of a step for drift detection.
|
|
23
|
+
*
|
|
24
|
+
* The `options` field is included for flow creation but NOT compared during
|
|
25
|
+
* shape comparison. Options can be tuned at runtime via SQL without
|
|
26
|
+
* requiring recompilation. See: /deploy/tune-flow-config/
|
|
27
|
+
*/
|
|
28
|
+
export interface StepShape {
|
|
29
|
+
slug: string;
|
|
30
|
+
stepType: 'single' | 'map';
|
|
31
|
+
dependencies: string[];
|
|
32
|
+
options?: StepShapeOptions;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* FlowShape captures the structural definition of a flow for drift detection.
|
|
36
|
+
*
|
|
37
|
+
* This represents the DAG topology - which steps exist, their types, and how
|
|
38
|
+
* they connect via dependencies.
|
|
39
|
+
*
|
|
40
|
+
* The `options` field is included for flow creation but NOT compared during
|
|
41
|
+
* shape comparison. Options can be tuned at runtime via SQL without recompilation.
|
|
42
|
+
*
|
|
43
|
+
* Note: flowSlug is intentionally excluded - it's an identifier, not structural
|
|
44
|
+
* data. The slug comes from context (URL, registry lookup, function parameter).
|
|
45
|
+
*/
|
|
46
|
+
export interface FlowShape {
|
|
47
|
+
steps: StepShape[];
|
|
48
|
+
options?: FlowShapeOptions;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Result of comparing two FlowShapes.
|
|
52
|
+
*/
|
|
53
|
+
export interface ShapeComparisonResult {
|
|
54
|
+
match: boolean;
|
|
55
|
+
differences: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Extracts a FlowShape from a Flow object.
|
|
59
|
+
* The shape captures structural information needed for drift detection,
|
|
60
|
+
* plus options for flow creation.
|
|
61
|
+
*
|
|
62
|
+
* Options are included in the shape for proper flow/step creation, but
|
|
63
|
+
* are NOT compared during shape comparison (they're runtime tunable).
|
|
64
|
+
*
|
|
65
|
+
* @param flow - The Flow object to extract shape from
|
|
66
|
+
* @returns A FlowShape representing the flow's structure and options
|
|
67
|
+
*/
|
|
68
|
+
export declare function extractFlowShape(flow: AnyFlow): FlowShape;
|
|
69
|
+
/**
|
|
70
|
+
* Compares two FlowShapes and returns detailed differences.
|
|
71
|
+
* Used by ControlPlane for Layer 1 comparison (Worker vs ControlPlane).
|
|
72
|
+
*
|
|
73
|
+
* Only compares structural aspects (steps, types, dependencies).
|
|
74
|
+
* Runtime options are not compared as they can be tuned independently.
|
|
75
|
+
*
|
|
76
|
+
* @param a - First FlowShape (typically from worker)
|
|
77
|
+
* @param b - Second FlowShape (typically from ControlPlane or database)
|
|
78
|
+
* @returns ShapeComparisonResult with match status and list of differences
|
|
79
|
+
*/
|
|
80
|
+
export declare function compareFlowShapes(a: FlowShape, b: FlowShape): ShapeComparisonResult;
|
|
81
|
+
//# sourceMappingURL=flow-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-shape.d.ts","sourceRoot":"","sources":["../src/flow-shape.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAMnC;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAyBD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,CAwCzD;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,SAAS,EACZ,CAAC,EAAE,SAAS,GACX,qBAAqB,CA6BvB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// ========================
|
|
2
|
+
// SHAPE EXTRACTION
|
|
3
|
+
// ========================
|
|
4
|
+
/**
|
|
5
|
+
* Checks if an options object has any defined (non-undefined) values.
|
|
6
|
+
*/
|
|
7
|
+
function hasDefinedOptions(options) {
|
|
8
|
+
return Object.values(options).some((v) => v !== undefined);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Filters out undefined values from an options object.
|
|
12
|
+
* Returns only the keys with defined values.
|
|
13
|
+
*/
|
|
14
|
+
function filterDefinedOptions(options) {
|
|
15
|
+
return Object.fromEntries(Object.entries(options).filter(([_, v]) => v !== undefined));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Extracts a FlowShape from a Flow object.
|
|
19
|
+
* The shape captures structural information needed for drift detection,
|
|
20
|
+
* plus options for flow creation.
|
|
21
|
+
*
|
|
22
|
+
* Options are included in the shape for proper flow/step creation, but
|
|
23
|
+
* are NOT compared during shape comparison (they're runtime tunable).
|
|
24
|
+
*
|
|
25
|
+
* @param flow - The Flow object to extract shape from
|
|
26
|
+
* @returns A FlowShape representing the flow's structure and options
|
|
27
|
+
*/
|
|
28
|
+
export function extractFlowShape(flow) {
|
|
29
|
+
const steps = flow.stepOrder.map((stepSlug) => {
|
|
30
|
+
const stepDef = flow.getStepDefinition(stepSlug);
|
|
31
|
+
const stepShape = {
|
|
32
|
+
slug: stepSlug,
|
|
33
|
+
stepType: stepDef.stepType ?? 'single',
|
|
34
|
+
// Sort dependencies alphabetically for deterministic comparison
|
|
35
|
+
dependencies: [...stepDef.dependencies].sort(),
|
|
36
|
+
};
|
|
37
|
+
// Only include options if at least one is defined
|
|
38
|
+
const stepOptions = {
|
|
39
|
+
maxAttempts: stepDef.options.maxAttempts,
|
|
40
|
+
baseDelay: stepDef.options.baseDelay,
|
|
41
|
+
timeout: stepDef.options.timeout,
|
|
42
|
+
startDelay: stepDef.options.startDelay,
|
|
43
|
+
};
|
|
44
|
+
if (hasDefinedOptions(stepOptions)) {
|
|
45
|
+
stepShape.options = filterDefinedOptions(stepOptions);
|
|
46
|
+
}
|
|
47
|
+
return stepShape;
|
|
48
|
+
});
|
|
49
|
+
const shape = { steps };
|
|
50
|
+
// Only include flow options if at least one is defined
|
|
51
|
+
const flowOptions = {
|
|
52
|
+
maxAttempts: flow.options.maxAttempts,
|
|
53
|
+
baseDelay: flow.options.baseDelay,
|
|
54
|
+
timeout: flow.options.timeout,
|
|
55
|
+
};
|
|
56
|
+
if (hasDefinedOptions(flowOptions)) {
|
|
57
|
+
shape.options = filterDefinedOptions(flowOptions);
|
|
58
|
+
}
|
|
59
|
+
return shape;
|
|
60
|
+
}
|
|
61
|
+
// ========================
|
|
62
|
+
// SHAPE COMPARISON
|
|
63
|
+
// ========================
|
|
64
|
+
/**
|
|
65
|
+
* Compares two FlowShapes and returns detailed differences.
|
|
66
|
+
* Used by ControlPlane for Layer 1 comparison (Worker vs ControlPlane).
|
|
67
|
+
*
|
|
68
|
+
* Only compares structural aspects (steps, types, dependencies).
|
|
69
|
+
* Runtime options are not compared as they can be tuned independently.
|
|
70
|
+
*
|
|
71
|
+
* @param a - First FlowShape (typically from worker)
|
|
72
|
+
* @param b - Second FlowShape (typically from ControlPlane or database)
|
|
73
|
+
* @returns ShapeComparisonResult with match status and list of differences
|
|
74
|
+
*/
|
|
75
|
+
export function compareFlowShapes(a, b) {
|
|
76
|
+
const differences = [];
|
|
77
|
+
// Compare step counts
|
|
78
|
+
if (a.steps.length !== b.steps.length) {
|
|
79
|
+
differences.push(`Step count differs: ${a.steps.length} vs ${b.steps.length}`);
|
|
80
|
+
}
|
|
81
|
+
// Compare steps by index (order matters)
|
|
82
|
+
const maxLen = Math.max(a.steps.length, b.steps.length);
|
|
83
|
+
for (let i = 0; i < maxLen; i++) {
|
|
84
|
+
const aStep = a.steps[i];
|
|
85
|
+
const bStep = b.steps[i];
|
|
86
|
+
if (!aStep) {
|
|
87
|
+
differences.push(`Step at index ${i}: missing in first shape (second has '${bStep.slug}')`);
|
|
88
|
+
}
|
|
89
|
+
else if (!bStep) {
|
|
90
|
+
differences.push(`Step at index ${i}: missing in second shape (first has '${aStep.slug}')`);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
compareSteps(aStep, bStep, i, differences);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
match: differences.length === 0,
|
|
98
|
+
differences,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Compares two steps at the same index and adds differences to the list.
|
|
103
|
+
*/
|
|
104
|
+
function compareSteps(a, b, index, differences) {
|
|
105
|
+
// Compare slug (order matters, so slugs must match at same index)
|
|
106
|
+
if (a.slug !== b.slug) {
|
|
107
|
+
differences.push(`Step at index ${index}: slug differs '${a.slug}' vs '${b.slug}'`);
|
|
108
|
+
}
|
|
109
|
+
// Compare step type
|
|
110
|
+
if (a.stepType !== b.stepType) {
|
|
111
|
+
differences.push(`Step at index ${index}: type differs '${a.stepType}' vs '${b.stepType}'`);
|
|
112
|
+
}
|
|
113
|
+
// Compare dependencies (already sorted)
|
|
114
|
+
const aDeps = a.dependencies.join(',');
|
|
115
|
+
const bDeps = b.dependencies.join(',');
|
|
116
|
+
if (aDeps !== bDeps) {
|
|
117
|
+
differences.push(`Step at index ${index}: dependencies differ [${a.dependencies.join(', ')}] vs [${b.dependencies.join(', ')}]`);
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pgflow/dsl",
|
|
3
|
+
"version": "0.0.0-testsnap-9294d743-20251207205914",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"private": false,
|
|
13
|
+
"exports": {
|
|
14
|
+
"./package.json": "./package.json",
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./platforms": {
|
|
20
|
+
"types": "./dist/platforms/index.d.ts",
|
|
21
|
+
"import": "./dist/platforms/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./supabase": {
|
|
24
|
+
"types": "./dist/platforms/supabase.d.ts",
|
|
25
|
+
"import": "./dist/platforms/supabase.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^22.14.1",
|
|
33
|
+
"postgres": "^3.4.5",
|
|
34
|
+
"@supabase/supabase-js": "^2.47.10"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/platforms/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Sql } from 'postgres';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import { Flow as CoreFlow, type AnyInput, type AnySteps, type AnyDeps, EmptySteps, EmptyDeps, type Env } from '../index.js';
|
|
4
|
+
export interface SupabaseResources extends Record<string, unknown> {
|
|
5
|
+
sql: Sql;
|
|
6
|
+
/**
|
|
7
|
+
* Supabase client with service role key for full database access
|
|
8
|
+
*/
|
|
9
|
+
supabase: SupabaseClient;
|
|
10
|
+
}
|
|
11
|
+
export interface SupabaseEnv extends Env {
|
|
12
|
+
SUPABASE_DB_URL: string;
|
|
13
|
+
SUPABASE_URL: string;
|
|
14
|
+
SUPABASE_ANON_KEY: string;
|
|
15
|
+
SUPABASE_SERVICE_ROLE_KEY: string;
|
|
16
|
+
SB_EXECUTION_ID: string;
|
|
17
|
+
EDGE_WORKER_DB_URL?: string;
|
|
18
|
+
EDGE_WORKER_LOG_LEVEL?: string;
|
|
19
|
+
}
|
|
20
|
+
export type SupabasePlatformContext = SupabaseResources;
|
|
21
|
+
export declare class Flow<I extends AnyInput = AnyInput, CustomCtx extends Record<string, unknown> = {}, S extends AnySteps = EmptySteps, D extends AnyDeps = EmptyDeps, TEnv extends Env = SupabaseEnv> extends CoreFlow<I, SupabasePlatformContext & CustomCtx, S, D, TEnv> {
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=supabase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/platforms/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EACL,IAAI,IAAI,QAAQ,EAChB,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAC1C,UAAU,EAAE,SAAS,EAAE,KAAK,GAAG,EAChC,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,iBAAkB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChE,GAAG,EAAc,GAAG,CAAC;IACrB;;OAEG;IACH,QAAQ,EAAS,cAAc,CAAC;CACjC;AAGD,MAAM,WAAW,WAAY,SAAQ,GAAG;IACtC,eAAe,EAAY,MAAM,CAAC;IAClC,YAAY,EAAe,MAAM,CAAC;IAClC,iBAAiB,EAAU,MAAM,CAAC;IAClC,yBAAyB,EAAE,MAAM,CAAC;IAClC,eAAe,EAAY,MAAM,CAAC;IAClC,kBAAkB,CAAC,EAAQ,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAK,MAAM,CAAC;CACnC;AAMD,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAGxD,qBAAa,IAAI,CACf,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAE7B,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,EAC9C,CAAC,SAAS,QAAQ,GAAG,UAAU,EAC/B,CAAC,SAAS,OAAO,GAAK,SAAS,EAC/B,IAAI,SAAS,GAAG,GAAG,WAAW,CAC9B,SAAQ,QAAQ,CAChB,CAAC,EACD,uBAAuB,GAAG,SAAS,EACnC,CAAC,EAAE,CAAC,EACJ,IAAI,CACL;CAAG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../src/supabase.ts"],"names":[],"mappings":"AACA,cAAc,yBAAyB,CAAC"}
|
package/dist/supabase.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"5.8.3"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a slug string according to the following rules:
|
|
3
|
+
* - Cannot start with a number
|
|
4
|
+
* - Cannot start with an underscore
|
|
5
|
+
* - Cannot contain spaces
|
|
6
|
+
* - Cannot contain special characters like /, :, ?, #
|
|
7
|
+
* - Cannot be longer than 128 characters
|
|
8
|
+
*
|
|
9
|
+
* @param slug The slug string to validate
|
|
10
|
+
* @throws Error if the slug is invalid
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateSlug(slug: string): void;
|
|
13
|
+
/**
|
|
14
|
+
* Options for validating runtime options
|
|
15
|
+
*/
|
|
16
|
+
export interface ValidateRuntimeOptionsOpts {
|
|
17
|
+
optional?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validates runtime options according to the following rules:
|
|
21
|
+
* - maxAttempts should be >= 1
|
|
22
|
+
* - baseDelay should be >= 1
|
|
23
|
+
* - timeout should be >= 3
|
|
24
|
+
* - startDelay should be >= 0
|
|
25
|
+
*
|
|
26
|
+
* @param options The runtime options to validate
|
|
27
|
+
* @param opts Configuration options for validation
|
|
28
|
+
* @param opts.optional If true, allows options to be null or undefined
|
|
29
|
+
* @throws Error if any runtime option is invalid
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateRuntimeOptions(options: {
|
|
32
|
+
maxAttempts?: number;
|
|
33
|
+
baseDelay?: number;
|
|
34
|
+
timeout?: number;
|
|
35
|
+
startDelay?: number;
|
|
36
|
+
}, opts?: ValidateRuntimeOptionsOpts): void;
|
|
37
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAsB/C;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5F,IAAI,GAAE,0BAAgD,GACrD,IAAI,CAiCN"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates a slug string according to the following rules:
|
|
3
|
+
* - Cannot start with a number
|
|
4
|
+
* - Cannot start with an underscore
|
|
5
|
+
* - Cannot contain spaces
|
|
6
|
+
* - Cannot contain special characters like /, :, ?, #
|
|
7
|
+
* - Cannot be longer than 128 characters
|
|
8
|
+
*
|
|
9
|
+
* @param slug The slug string to validate
|
|
10
|
+
* @throws Error if the slug is invalid
|
|
11
|
+
*/
|
|
12
|
+
export function validateSlug(slug) {
|
|
13
|
+
if (slug.length > 128) {
|
|
14
|
+
throw new Error(`Slug '${slug}' cannot be longer than 128 characters`);
|
|
15
|
+
}
|
|
16
|
+
if (/^\d/.test(slug)) {
|
|
17
|
+
throw new Error(`Slug '${slug}' cannot start with a number`);
|
|
18
|
+
}
|
|
19
|
+
if (/^_/.test(slug)) {
|
|
20
|
+
throw new Error(`Slug '${slug}' cannot start with an underscore`);
|
|
21
|
+
}
|
|
22
|
+
if (/\s/.test(slug)) {
|
|
23
|
+
throw new Error(`Slug '${slug}' cannot contain spaces`);
|
|
24
|
+
}
|
|
25
|
+
if (/[/:#\-?]/.test(slug)) {
|
|
26
|
+
throw new Error(`Slug '${slug}' cannot contain special characters like /, :, ?, #, -`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validates runtime options according to the following rules:
|
|
31
|
+
* - maxAttempts should be >= 1
|
|
32
|
+
* - baseDelay should be >= 1
|
|
33
|
+
* - timeout should be >= 3
|
|
34
|
+
* - startDelay should be >= 0
|
|
35
|
+
*
|
|
36
|
+
* @param options The runtime options to validate
|
|
37
|
+
* @param opts Configuration options for validation
|
|
38
|
+
* @param opts.optional If true, allows options to be null or undefined
|
|
39
|
+
* @throws Error if any runtime option is invalid
|
|
40
|
+
*/
|
|
41
|
+
export function validateRuntimeOptions(options, opts = { optional: false }) {
|
|
42
|
+
const { maxAttempts, baseDelay, timeout, startDelay } = options;
|
|
43
|
+
// If optional is true, skip validation for undefined/null values
|
|
44
|
+
if (maxAttempts !== undefined && maxAttempts !== null) {
|
|
45
|
+
if (maxAttempts < 1) {
|
|
46
|
+
throw new Error('maxAttempts must be greater than or equal to 1');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else if (!opts.optional) {
|
|
50
|
+
throw new Error('maxAttempts is required');
|
|
51
|
+
}
|
|
52
|
+
if (baseDelay !== undefined && baseDelay !== null) {
|
|
53
|
+
if (baseDelay < 1) {
|
|
54
|
+
throw new Error('baseDelay must be greater than or equal to 1');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (!opts.optional) {
|
|
58
|
+
throw new Error('baseDelay is required');
|
|
59
|
+
}
|
|
60
|
+
if (timeout !== undefined && timeout !== null) {
|
|
61
|
+
if (timeout < 3) {
|
|
62
|
+
throw new Error('timeout must be greater than or equal to 3');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (!opts.optional) {
|
|
66
|
+
throw new Error('timeout is required');
|
|
67
|
+
}
|
|
68
|
+
if (startDelay !== undefined && startDelay !== null) {
|
|
69
|
+
if (startDelay < 0) {
|
|
70
|
+
throw new Error('startDelay must be greater than or equal to 0');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|