@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgflow/dsl",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"vitest": "*"
|
|
6
6
|
},
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
"import": "./dist/index.js"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
18
21
|
"types": "./dist/index.d.ts",
|
|
19
22
|
"type": "module",
|
|
20
23
|
"publishConfig": {
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { describe, it, vi, expect } from 'vitest';
|
|
2
|
-
import { Flow } from '../../src/dsl.ts';
|
|
3
|
-
import * as utils from '../../src/utils.ts';
|
|
4
|
-
|
|
5
|
-
const noop = () => null;
|
|
6
|
-
|
|
7
|
-
describe('Flow', () => {
|
|
8
|
-
describe('constructor', () => {
|
|
9
|
-
it('creates a flow with valid defaults', () => {
|
|
10
|
-
const flow = new Flow({ slug: 'valid_flow' });
|
|
11
|
-
expect(flow.slug).toBe('valid_flow');
|
|
12
|
-
expect(flow.options).toEqual({});
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('creates a flow with custom runtime options', () => {
|
|
16
|
-
const flow = new Flow({
|
|
17
|
-
slug: 'custom_flow',
|
|
18
|
-
maxAttempts: 3,
|
|
19
|
-
baseDelay: 100,
|
|
20
|
-
timeout: 30,
|
|
21
|
-
});
|
|
22
|
-
expect(flow.slug).toBe('custom_flow');
|
|
23
|
-
expect(flow.options).toEqual({
|
|
24
|
-
maxAttempts: 3,
|
|
25
|
-
baseDelay: 100,
|
|
26
|
-
timeout: 30,
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('slug validation', () => {
|
|
32
|
-
it('calls validateSlug with the correct slug', () => {
|
|
33
|
-
const validateSlugSpy = vi.spyOn(utils, 'validateSlug');
|
|
34
|
-
new Flow({ slug: 'hello_world' });
|
|
35
|
-
expect(validateSlugSpy).toHaveBeenCalledWith('hello_world');
|
|
36
|
-
validateSlugSpy.mockRestore();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('propagates errors from validateSlug', () => {
|
|
40
|
-
const validateSlugSpy = vi.spyOn(utils, 'validateSlug');
|
|
41
|
-
validateSlugSpy.mockImplementation(() => {
|
|
42
|
-
throw new Error('Mock validation error');
|
|
43
|
-
});
|
|
44
|
-
expect(() => new Flow({ slug: 'test' })).toThrowError(
|
|
45
|
-
'Mock validation error'
|
|
46
|
-
);
|
|
47
|
-
validateSlugSpy.mockRestore();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Integration tests for invalid slugs
|
|
51
|
-
it('rejects invalid slugs during flow creation', () => {
|
|
52
|
-
expect(() => new Flow({ slug: '1invalid' })).toThrowError();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('runtime options validation', () => {
|
|
57
|
-
it('calls validateRuntimeOptions with the correct options', () => {
|
|
58
|
-
const validateRuntimeOptionsSpy = vi.spyOn(
|
|
59
|
-
utils,
|
|
60
|
-
'validateRuntimeOptions'
|
|
61
|
-
);
|
|
62
|
-
new Flow({
|
|
63
|
-
slug: 'test_flow',
|
|
64
|
-
maxAttempts: 3,
|
|
65
|
-
baseDelay: 100,
|
|
66
|
-
timeout: 30,
|
|
67
|
-
});
|
|
68
|
-
expect(validateRuntimeOptionsSpy).toHaveBeenCalledWith(
|
|
69
|
-
{ maxAttempts: 3, baseDelay: 100, timeout: 30 },
|
|
70
|
-
{ optional: true }
|
|
71
|
-
);
|
|
72
|
-
validateRuntimeOptionsSpy.mockRestore();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('propagates errors from validateRuntimeOptions', () => {
|
|
76
|
-
const validateRuntimeOptionsSpy = vi.spyOn(
|
|
77
|
-
utils,
|
|
78
|
-
'validateRuntimeOptions'
|
|
79
|
-
);
|
|
80
|
-
validateRuntimeOptionsSpy.mockImplementation(() => {
|
|
81
|
-
throw new Error('Mock validation error');
|
|
82
|
-
});
|
|
83
|
-
expect(
|
|
84
|
-
() => new Flow({ slug: 'test_flow', maxAttempts: 0 })
|
|
85
|
-
).toThrowError('Mock validation error');
|
|
86
|
-
validateRuntimeOptionsSpy.mockRestore();
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('getStepDefinition', () => {
|
|
91
|
-
it('returns the root step definition when step exists', () => {
|
|
92
|
-
const flow = new Flow<{ value: number }>({ slug: 'test_flow' }).step(
|
|
93
|
-
{ slug: 'root_step' },
|
|
94
|
-
noop
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const stepDef = flow.getStepDefinition('root_step');
|
|
98
|
-
expect(stepDef.slug).toBe('root_step');
|
|
99
|
-
expect(stepDef.dependencies).toEqual([]);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('returns the normal step definition when step exists', () => {
|
|
103
|
-
const flow = new Flow<{ value: number }>({ slug: 'test_flow' })
|
|
104
|
-
.step({ slug: 'root_step' }, noop)
|
|
105
|
-
.step({ slug: 'last_step', dependsOn: ['root_step'] }, noop);
|
|
106
|
-
|
|
107
|
-
const stepDef = flow.getStepDefinition('last_step');
|
|
108
|
-
expect(stepDef.slug).toBe('last_step');
|
|
109
|
-
expect(stepDef.dependencies).toEqual(['root_step']);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('throws error when step does not exist', () => {
|
|
113
|
-
const flow = new Flow<{ value: number }>({ slug: 'test_flow' });
|
|
114
|
-
|
|
115
|
-
// @ts-expect-error - intentionally testing invalid slug
|
|
116
|
-
expect(() => flow.getStepDefinition('non_existent')).toThrowError(
|
|
117
|
-
'Step "non_existent" does not exist in flow "test_flow"'
|
|
118
|
-
);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { describe, it, vi, beforeEach, expect } from 'vitest';
|
|
2
|
-
import { Flow } from '../../src/dsl.ts';
|
|
3
|
-
import * as utils from '../../src/utils.ts';
|
|
4
|
-
|
|
5
|
-
describe('Steps', () => {
|
|
6
|
-
let flow: Flow<any>;
|
|
7
|
-
const noop = () => null;
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
flow = new Flow({ slug: 'test_flow' });
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
describe('step addition', () => {
|
|
14
|
-
it('adds a step with the correct handler', () => {
|
|
15
|
-
const handler = () => ({ result: 42 });
|
|
16
|
-
const newFlow = flow.step({ slug: 'test_step' }, handler);
|
|
17
|
-
|
|
18
|
-
expect(newFlow.getStepDefinition('test_step').handler).toBe(handler);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('throws when adding step with the same slug', () => {
|
|
22
|
-
const newFlow = flow.step({ slug: 'test_step' }, noop);
|
|
23
|
-
|
|
24
|
-
expect(() => newFlow.step({ slug: 'test_step' }, noop)).toThrowError(
|
|
25
|
-
'Step "test_step" already exists in flow "test_flow"'
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('slug validation', () => {
|
|
31
|
-
it('calls validateSlug with the correct slug', () => {
|
|
32
|
-
const validateSlugSpy = vi.spyOn(utils, 'validateSlug');
|
|
33
|
-
flow.step({ slug: 'test_step' }, noop);
|
|
34
|
-
expect(validateSlugSpy).toHaveBeenCalledWith('test_step');
|
|
35
|
-
validateSlugSpy.mockRestore();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('propagates errors from validateSlug', () => {
|
|
39
|
-
const validateSlugSpy = vi.spyOn(utils, 'validateSlug');
|
|
40
|
-
validateSlugSpy.mockImplementation(() => {
|
|
41
|
-
throw new Error('Mock validation error');
|
|
42
|
-
});
|
|
43
|
-
expect(() => flow.step({ slug: 'test' }, noop)).toThrowError(
|
|
44
|
-
'Mock validation error'
|
|
45
|
-
);
|
|
46
|
-
validateSlugSpy.mockRestore();
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('dependencies', () => {
|
|
51
|
-
it('can add step without dependencies', () => {
|
|
52
|
-
expect(() => flow.step({ slug: 'no_deps' }, noop)).not.toThrowError();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('can add step with explicit empty array of dependencies', () => {
|
|
56
|
-
expect(() =>
|
|
57
|
-
flow.step({ slug: 'empty_deps', dependsOn: [] }, noop)
|
|
58
|
-
).not.toThrowError();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('allows adding step with valid dependencies', () => {
|
|
62
|
-
const newFlow = flow.step({ slug: 'first_step' }, noop);
|
|
63
|
-
|
|
64
|
-
expect(() =>
|
|
65
|
-
newFlow.step({ slug: 'second_step', dependsOn: ['first_step'] }, noop)
|
|
66
|
-
).not.toThrowError();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('does not allow adding step with non-existing dependencies', () => {
|
|
70
|
-
expect(() =>
|
|
71
|
-
flow.step(
|
|
72
|
-
// @ts-expect-error - dependsOn references non-existing step
|
|
73
|
-
{ slug: 'invalid_deps', dependsOn: ['non_existing_step'] },
|
|
74
|
-
noop
|
|
75
|
-
)
|
|
76
|
-
).toThrowError(
|
|
77
|
-
'Step "invalid_deps" depends on undefined step "non_existing_step"'
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('does not allow adding step with non-string dependencies', () => {
|
|
82
|
-
expect(() =>
|
|
83
|
-
// @ts-expect-error - dependsOn contains non-string value
|
|
84
|
-
flow.step({ slug: 'invalid_deps', dependsOn: [12345] }, noop)
|
|
85
|
-
).toThrowError('Step "invalid_deps" depends on undefined step "12345"');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('runtime options', () => {
|
|
90
|
-
it('calls validateRuntimeOptions with the correct options', () => {
|
|
91
|
-
const validateRuntimeOptionsSpy = vi.spyOn(
|
|
92
|
-
utils,
|
|
93
|
-
'validateRuntimeOptions'
|
|
94
|
-
);
|
|
95
|
-
flow.step(
|
|
96
|
-
{
|
|
97
|
-
slug: 'test_step',
|
|
98
|
-
maxAttempts: 3,
|
|
99
|
-
baseDelay: 100,
|
|
100
|
-
timeout: 30,
|
|
101
|
-
},
|
|
102
|
-
noop
|
|
103
|
-
);
|
|
104
|
-
expect(validateRuntimeOptionsSpy).toHaveBeenCalledWith(
|
|
105
|
-
{ maxAttempts: 3, baseDelay: 100, timeout: 30 },
|
|
106
|
-
{ optional: true }
|
|
107
|
-
);
|
|
108
|
-
validateRuntimeOptionsSpy.mockRestore();
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('propagates errors from validateRuntimeOptions', () => {
|
|
112
|
-
const validateRuntimeOptionsSpy = vi.spyOn(
|
|
113
|
-
utils,
|
|
114
|
-
'validateRuntimeOptions'
|
|
115
|
-
);
|
|
116
|
-
validateRuntimeOptionsSpy.mockImplementation(() => {
|
|
117
|
-
throw new Error('Mock validation error');
|
|
118
|
-
});
|
|
119
|
-
expect(() =>
|
|
120
|
-
flow.step(
|
|
121
|
-
{
|
|
122
|
-
slug: 'test_step',
|
|
123
|
-
maxAttempts: 0,
|
|
124
|
-
},
|
|
125
|
-
noop
|
|
126
|
-
)
|
|
127
|
-
).toThrowError('Mock validation error');
|
|
128
|
-
validateRuntimeOptionsSpy.mockRestore();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('stores runtime options on the step definition', () => {
|
|
132
|
-
const newFlow = flow.step(
|
|
133
|
-
{
|
|
134
|
-
slug: 'test_step',
|
|
135
|
-
maxAttempts: 3,
|
|
136
|
-
baseDelay: 100,
|
|
137
|
-
timeout: 30,
|
|
138
|
-
},
|
|
139
|
-
noop
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
const stepDef = newFlow.getStepDefinition('test_step');
|
|
143
|
-
expect(stepDef.options).toEqual({
|
|
144
|
-
maxAttempts: 3,
|
|
145
|
-
baseDelay: 100,
|
|
146
|
-
timeout: 30,
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe('step integration', () => {
|
|
152
|
-
it('allows steps to access run input', async () => {
|
|
153
|
-
const testFlow = new Flow<{ value: number }>({ slug: 'test_flow' }).step(
|
|
154
|
-
{ slug: 'step1' },
|
|
155
|
-
({ run }) => ({ doubled: run.value * 2 })
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
const step1Def = testFlow.getStepDefinition('step1');
|
|
159
|
-
const result = await step1Def.handler({ run: { value: 5 } });
|
|
160
|
-
|
|
161
|
-
expect(result).toEqual({ doubled: 10 });
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('allows steps to access dependencies', async () => {
|
|
165
|
-
const testFlow = new Flow<{ value: number }>({ slug: 'test_flow' })
|
|
166
|
-
.step({ slug: 'step1' }, ({ run }) => ({ doubled: run.value * 2 }))
|
|
167
|
-
.step({ slug: 'step2', dependsOn: ['step1'] }, ({ step1 }) => ({
|
|
168
|
-
quadrupled: step1.doubled * 2,
|
|
169
|
-
}));
|
|
170
|
-
|
|
171
|
-
const step1Def = testFlow.getStepDefinition('step1');
|
|
172
|
-
const step1Result = await step1Def.handler({ run: { value: 5 } });
|
|
173
|
-
|
|
174
|
-
const step2Def = testFlow.getStepDefinition('step2');
|
|
175
|
-
const step2Result = await step2Def.handler({
|
|
176
|
-
run: { value: 5 },
|
|
177
|
-
step1: step1Result,
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
expect(step2Result).toEqual({ quadrupled: 20 });
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
});
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { validateSlug, validateRuntimeOptions } from '../../src/utils.ts';
|
|
3
|
-
|
|
4
|
-
describe('validateSlug', () => {
|
|
5
|
-
it('accepts valid slugs', () => {
|
|
6
|
-
expect(() => validateSlug('valid_slug')).not.toThrowError();
|
|
7
|
-
expect(() => validateSlug('valid_slug_123')).not.toThrowError();
|
|
8
|
-
expect(() => validateSlug('validSlug123')).not.toThrowError();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('rejects slugs that start with numbers', () => {
|
|
12
|
-
expect(() => validateSlug('1invalid')).toThrowError(
|
|
13
|
-
'Slug cannot start with a number'
|
|
14
|
-
);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('rejects slugs that start with underscores', () => {
|
|
18
|
-
expect(() => validateSlug('_invalid')).toThrowError(
|
|
19
|
-
'Slug cannot start with an underscore'
|
|
20
|
-
);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('rejects slugs containing spaces', () => {
|
|
24
|
-
expect(() => validateSlug('invalid slug')).toThrowError(
|
|
25
|
-
'Slug cannot contain spaces'
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('rejects slugs containing special characters', () => {
|
|
30
|
-
expect(() => validateSlug('invalid/slug')).toThrowError(
|
|
31
|
-
'Slug cannot contain special characters like /, :, ?, #, -'
|
|
32
|
-
);
|
|
33
|
-
expect(() => validateSlug('invalid:slug')).toThrowError(
|
|
34
|
-
'Slug cannot contain special characters like /, :, ?, #, -'
|
|
35
|
-
);
|
|
36
|
-
expect(() => validateSlug('invalid?slug')).toThrowError(
|
|
37
|
-
'Slug cannot contain special characters like /, :, ?, #, -'
|
|
38
|
-
);
|
|
39
|
-
expect(() => validateSlug('invalid#slug')).toThrowError(
|
|
40
|
-
'Slug cannot contain special characters like /, :, ?, #, -'
|
|
41
|
-
);
|
|
42
|
-
expect(() => validateSlug('invalid-slug')).toThrowError(
|
|
43
|
-
'Slug cannot contain special characters like /, :, ?, #, -'
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('rejects slugs longer than 128 characters', () => {
|
|
48
|
-
const longSlug = 'a'.repeat(129);
|
|
49
|
-
expect(() => validateSlug(longSlug)).toThrowError(
|
|
50
|
-
'Slug cannot be longer than 128 characters'
|
|
51
|
-
);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('validateRuntimeOptions', () => {
|
|
56
|
-
describe('when optional is false (default)', () => {
|
|
57
|
-
it('throws when required options are missing', () => {
|
|
58
|
-
expect(() => validateRuntimeOptions({})).toThrowError(
|
|
59
|
-
'maxAttempts is required'
|
|
60
|
-
);
|
|
61
|
-
expect(() => validateRuntimeOptions({ maxAttempts: 1 })).toThrowError(
|
|
62
|
-
'baseDelay is required'
|
|
63
|
-
);
|
|
64
|
-
expect(() =>
|
|
65
|
-
validateRuntimeOptions({ maxAttempts: 1, baseDelay: 5 })
|
|
66
|
-
).toThrowError('timeout is required');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('validates maxAttempts is >= 1', () => {
|
|
70
|
-
expect(() =>
|
|
71
|
-
validateRuntimeOptions({
|
|
72
|
-
maxAttempts: 0,
|
|
73
|
-
baseDelay: 10,
|
|
74
|
-
timeout: 10,
|
|
75
|
-
})
|
|
76
|
-
).toThrowError('maxAttempts must be greater than or equal to 1');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('validates baseDelay is >= 1', () => {
|
|
80
|
-
expect(() =>
|
|
81
|
-
validateRuntimeOptions({
|
|
82
|
-
maxAttempts: 3,
|
|
83
|
-
baseDelay: 0,
|
|
84
|
-
timeout: 10,
|
|
85
|
-
})
|
|
86
|
-
).toThrowError('baseDelay must be greater than or equal to 1');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('validates timeout is >= 3', () => {
|
|
90
|
-
expect(() =>
|
|
91
|
-
validateRuntimeOptions({
|
|
92
|
-
maxAttempts: 3,
|
|
93
|
-
baseDelay: 10,
|
|
94
|
-
timeout: 2,
|
|
95
|
-
})
|
|
96
|
-
).toThrowError('timeout must be greater than or equal to 3');
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('when optional is true', () => {
|
|
101
|
-
it('accepts missing options', () => {
|
|
102
|
-
expect(() =>
|
|
103
|
-
validateRuntimeOptions({}, { optional: true })
|
|
104
|
-
).not.toThrowError();
|
|
105
|
-
expect(() =>
|
|
106
|
-
validateRuntimeOptions({ maxAttempts: 1 }, { optional: true })
|
|
107
|
-
).not.toThrowError();
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('still validates provided options', () => {
|
|
111
|
-
expect(() =>
|
|
112
|
-
validateRuntimeOptions(
|
|
113
|
-
{
|
|
114
|
-
maxAttempts: 0,
|
|
115
|
-
},
|
|
116
|
-
{ optional: true }
|
|
117
|
-
)
|
|
118
|
-
).toThrowError('maxAttempts must be greater than or equal to 1');
|
|
119
|
-
|
|
120
|
-
expect(() =>
|
|
121
|
-
validateRuntimeOptions(
|
|
122
|
-
{
|
|
123
|
-
baseDelay: 0,
|
|
124
|
-
},
|
|
125
|
-
{ optional: true }
|
|
126
|
-
)
|
|
127
|
-
).toThrowError('baseDelay must be greater than or equal to 1');
|
|
128
|
-
|
|
129
|
-
expect(() =>
|
|
130
|
-
validateRuntimeOptions(
|
|
131
|
-
{
|
|
132
|
-
timeout: 2,
|
|
133
|
-
},
|
|
134
|
-
{ optional: true }
|
|
135
|
-
)
|
|
136
|
-
).toThrowError('timeout must be greater than or equal to 3');
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('accepts valid options', () => {
|
|
141
|
-
expect(() =>
|
|
142
|
-
validateRuntimeOptions({
|
|
143
|
-
maxAttempts: 3,
|
|
144
|
-
baseDelay: 10,
|
|
145
|
-
timeout: 30,
|
|
146
|
-
})
|
|
147
|
-
).not.toThrowError();
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { Flow } from '../../src/index.ts';
|
|
2
|
-
import { describe, it, expectTypeOf } from 'vitest';
|
|
3
|
-
|
|
4
|
-
describe('Flow Type System Tests', () => {
|
|
5
|
-
it('should properly type input argument for root steps', () => {
|
|
6
|
-
new Flow<{ id: number; email: string }>({
|
|
7
|
-
slug: 'test_flow',
|
|
8
|
-
}).step({ slug: 'root_a' }, (input) => {
|
|
9
|
-
expectTypeOf(input).toMatchTypeOf<{
|
|
10
|
-
run: { id: number; email: string };
|
|
11
|
-
}>();
|
|
12
|
-
return { result: 'test-result' };
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should properly type input arguments for dependent steps', () => {
|
|
17
|
-
new Flow<{ id: number; email: string }>({
|
|
18
|
-
slug: 'test_flow',
|
|
19
|
-
})
|
|
20
|
-
.step({ slug: 'root_a' }, (input) => {
|
|
21
|
-
expectTypeOf(input).toMatchTypeOf<{
|
|
22
|
-
run: { id: number; email: string };
|
|
23
|
-
}>();
|
|
24
|
-
return { result: 'test-result' };
|
|
25
|
-
})
|
|
26
|
-
.step({ slug: 'step_a', dependsOn: ['root_a'] }, (input) => {
|
|
27
|
-
expectTypeOf(input).toMatchTypeOf<{
|
|
28
|
-
run: { id: number; email: string };
|
|
29
|
-
root_a: { result: string };
|
|
30
|
-
}>();
|
|
31
|
-
return { count: 42 };
|
|
32
|
-
})
|
|
33
|
-
.step(
|
|
34
|
-
{ slug: 'final_step', dependsOn: ['root_a', 'step_a'] },
|
|
35
|
-
(input) => {
|
|
36
|
-
expectTypeOf(input).toMatchTypeOf<{
|
|
37
|
-
run: { id: number; email: string };
|
|
38
|
-
root_a: { result: string };
|
|
39
|
-
step_a: { count: number };
|
|
40
|
-
}>();
|
|
41
|
-
return { flag: true };
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe('Flow dependency validation', () => {
|
|
47
|
-
it('should catch non-existent steps at compile time', () => {
|
|
48
|
-
const testFlow = new Flow<string>({ slug: 'test_flow' }).step(
|
|
49
|
-
{ slug: 'step1' },
|
|
50
|
-
() => 5
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
// Type assertion to verify compile-time error
|
|
54
|
-
type TestType = Parameters<typeof testFlow.step>[0]['dependsOn'];
|
|
55
|
-
// @ts-expect-error - should only allow 'step1' as a valid dependency
|
|
56
|
-
const invalidDeps: TestType = ['nonExistentStep'];
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('should not allow access to non-dependencies', () => {
|
|
60
|
-
new Flow<string>({ slug: 'test_flow' })
|
|
61
|
-
.step({ slug: 'step1' }, () => 1)
|
|
62
|
-
.step({ slug: 'step2' }, () => 2)
|
|
63
|
-
.step({ slug: 'step3', dependsOn: ['step1'] }, (payload) => {
|
|
64
|
-
expectTypeOf(payload).toMatchTypeOf<{
|
|
65
|
-
run: string;
|
|
66
|
-
step1: number;
|
|
67
|
-
}>();
|
|
68
|
-
|
|
69
|
-
// Verify that step2 is not accessible
|
|
70
|
-
expectTypeOf(payload).not.toHaveProperty('step2');
|
|
71
|
-
|
|
72
|
-
return payload.step1;
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe('Multi-level dependencies', () => {
|
|
78
|
-
it('should correctly type multi-level dependencies', () => {
|
|
79
|
-
new Flow<string>({ slug: 'test_flow' })
|
|
80
|
-
.step({ slug: 'first' }, (payload) => {
|
|
81
|
-
expectTypeOf(payload).toMatchTypeOf<{ run: string }>();
|
|
82
|
-
return 5;
|
|
83
|
-
})
|
|
84
|
-
.step({ slug: 'second', dependsOn: ['first'] }, (payload) => {
|
|
85
|
-
expectTypeOf(payload).toMatchTypeOf<{
|
|
86
|
-
run: string;
|
|
87
|
-
first: number;
|
|
88
|
-
}>();
|
|
89
|
-
|
|
90
|
-
return [payload.run] as string[];
|
|
91
|
-
})
|
|
92
|
-
.step({ slug: 'third', dependsOn: ['first', 'second'] }, (payload) => {
|
|
93
|
-
expectTypeOf(payload).toMatchTypeOf<{
|
|
94
|
-
run: string;
|
|
95
|
-
first: number;
|
|
96
|
-
second: string[];
|
|
97
|
-
}>();
|
|
98
|
-
|
|
99
|
-
return 15;
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
});
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { it, expectTypeOf } from 'vitest';
|
|
2
|
-
import { AnalyzeWebsite } from '../../src/example-flow.ts';
|
|
3
|
-
|
|
4
|
-
const websiteStepDef = AnalyzeWebsite.getStepDefinition('website');
|
|
5
|
-
const sentimentStepDef = AnalyzeWebsite.getStepDefinition('sentiment');
|
|
6
|
-
const summaryStepDef = AnalyzeWebsite.getStepDefinition('summary');
|
|
7
|
-
const saveToDbStepDef = AnalyzeWebsite.getStepDefinition('saveToDb');
|
|
8
|
-
|
|
9
|
-
const run = { url: 'https://example.com' };
|
|
10
|
-
const website = { content: 'holahola' };
|
|
11
|
-
const summary = { aiSummary: 'holahola' };
|
|
12
|
-
const sentiment = { score: 0.5 };
|
|
13
|
-
|
|
14
|
-
it('should correctly handle AnalyzeWebsite flow steps with proper types', () => {
|
|
15
|
-
// Test website step handler type
|
|
16
|
-
expectTypeOf(websiteStepDef.handler).toBeFunction();
|
|
17
|
-
expectTypeOf(websiteStepDef.handler).parameters.toMatchTypeOf<
|
|
18
|
-
[{ run: { url: string } }]
|
|
19
|
-
>();
|
|
20
|
-
expectTypeOf(websiteStepDef.handler).returns.toMatchTypeOf<
|
|
21
|
-
Promise<{ content: string }> | { content: string }
|
|
22
|
-
>();
|
|
23
|
-
|
|
24
|
-
// Test sentiment step handler type
|
|
25
|
-
expectTypeOf(sentimentStepDef.handler).toBeFunction();
|
|
26
|
-
expectTypeOf(sentimentStepDef.handler).parameters.toMatchTypeOf<
|
|
27
|
-
[{ run: { url: string }; website: { content: string } }]
|
|
28
|
-
>();
|
|
29
|
-
expectTypeOf(sentimentStepDef.handler).returns.toMatchTypeOf<
|
|
30
|
-
Promise<{ score: number }> | { score: number }
|
|
31
|
-
>();
|
|
32
|
-
|
|
33
|
-
// Test summary step handler type
|
|
34
|
-
expectTypeOf(summaryStepDef.handler).toBeFunction();
|
|
35
|
-
expectTypeOf(summaryStepDef.handler).parameters.toMatchTypeOf<
|
|
36
|
-
[{ run: { url: string }; website: { content: string } }]
|
|
37
|
-
>();
|
|
38
|
-
expectTypeOf(summaryStepDef.handler).returns.toMatchTypeOf<
|
|
39
|
-
Promise<{ aiSummary: string }> | { aiSummary: string }
|
|
40
|
-
>();
|
|
41
|
-
|
|
42
|
-
// Test saveToDb step handler type
|
|
43
|
-
expectTypeOf(saveToDbStepDef.handler).toBeFunction();
|
|
44
|
-
expectTypeOf(saveToDbStepDef.handler).parameters.toMatchTypeOf<
|
|
45
|
-
[
|
|
46
|
-
{
|
|
47
|
-
run: { url: string };
|
|
48
|
-
sentiment: { score: number };
|
|
49
|
-
summary: { aiSummary: string };
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
>();
|
|
53
|
-
expectTypeOf(saveToDbStepDef.handler).returns.toMatchTypeOf<
|
|
54
|
-
Promise<string> | string
|
|
55
|
-
>();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('allows to call handlers with matching inputs', () => {
|
|
59
|
-
websiteStepDef.handler({ run });
|
|
60
|
-
sentimentStepDef.handler({ run, website });
|
|
61
|
-
summaryStepDef.handler({ run, website });
|
|
62
|
-
saveToDbStepDef.handler({ run, summary, sentiment });
|
|
63
|
-
});
|
|
64
|
-
it('does not allow to call with additional keys', () => {
|
|
65
|
-
// @ts-expect-error - no additional keys allowed
|
|
66
|
-
websiteStepDef.handler({ run, newKey: true });
|
|
67
|
-
|
|
68
|
-
// @ts-expect-error - no additional keys allowed
|
|
69
|
-
sentimentStepDef.handler({ run, website, newKey: true });
|
|
70
|
-
|
|
71
|
-
// @ts-expect-error - no additional keys allowed
|
|
72
|
-
summaryStepDef.handler({ run, website, newKey: true });
|
|
73
|
-
|
|
74
|
-
// @ts-expect-error - no additional keys allowed
|
|
75
|
-
saveToDbStepDef.handler({ run, summary, sentiment, newKey: true });
|
|
76
|
-
});
|