@multiplier-labs/stepflow 0.3.1 → 0.3.3
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/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +11 -5
- package/.github/workflows/security.yml +29 -0
- package/CHANGELOG.md +34 -0
- package/README.md +24 -0
- package/SECURITY.md +40 -0
- package/dist/{chunk-FQCT3I2A.js → chunk-4VOKMGP6.js} +23 -42
- package/dist/chunk-4VOKMGP6.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-GYPNJBDP.js} +12 -16
- package/dist/chunk-GYPNJBDP.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-KHTUWLVZ.js} +14 -47
- package/dist/chunk-KHTUWLVZ.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-ML35PIHX.js} +1 -1
- package/dist/chunk-ML35PIHX.js.map +1 -0
- package/dist/events/index.d.ts +4 -4
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Dzr437iW.d.ts} +6 -6
- package/dist/index.d.ts +14 -12
- package/dist/index.js +40 -82
- package/dist/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +4 -4
- package/dist/scheduler/index.js +2 -2
- package/dist/storage/index.d.ts +13 -13
- package/dist/storage/index.js +2 -2
- package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
- package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
- package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
- package/docs/stepflow-api-reference.md +2 -3
- package/package.json +7 -6
- package/src/core/engine.test.ts +466 -522
- package/src/core/engine.ts +42 -67
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator.ts +64 -83
- package/src/core/types.ts +7 -29
- package/src/events/index.ts +4 -4
- package/src/events/memory.test.ts +39 -41
- package/src/events/memory.ts +7 -10
- package/src/events/socketio.test.ts +112 -162
- package/src/events/socketio.ts +20 -27
- package/src/events/types.ts +16 -19
- package/src/events/webhook.test.ts +197 -235
- package/src/events/webhook.ts +44 -71
- package/src/index.ts +19 -19
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +66 -91
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +39 -43
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +141 -150
- package/src/scheduler/cron.ts +32 -67
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +71 -99
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +12 -19
- package/src/scheduler/types.ts +4 -9
- package/src/storage/index.ts +5 -5
- package/src/storage/memory.test.ts +133 -183
- package/src/storage/memory.ts +21 -42
- package/src/storage/postgres.ts +197 -393
- package/src/storage/sqlite.test.ts +154 -281
- package/src/storage/sqlite.ts +50 -85
- package/src/storage/types.ts +33 -58
- package/src/utils/errors.test.ts +78 -80
- package/src/utils/errors.ts +27 -57
- package/src/utils/id.ts +3 -3
- package/src/utils/index.ts +4 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/postgres-deps.ts +4 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- package/dist/chunk-FQCT3I2A.js.map +0 -1
- package/dist/chunk-GXY66V7Q.js.map +0 -1
- package/dist/chunk-U5QGN3RT.js.map +0 -1
- package/dist/chunk-XCF4PLN7.js.map +0 -1
- package/src/scheduler/postgres-persistence.test.ts +0 -906
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from
|
|
2
|
-
import Database from
|
|
3
|
-
import { SQLiteStorageAdapter } from
|
|
4
|
-
import type { WorkflowRunRecord, WorkflowRunStepRecord } from
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
3
|
+
import { SQLiteStorageAdapter } from './sqlite';
|
|
4
|
+
import type { WorkflowRunRecord, WorkflowRunStepRecord } from './types';
|
|
5
5
|
|
|
6
|
-
describe(
|
|
6
|
+
describe('SQLiteStorageAdapter', () => {
|
|
7
7
|
let db: Database.Database;
|
|
8
8
|
let storage: SQLiteStorageAdapter;
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
11
11
|
// Use in-memory database for testing
|
|
12
|
-
db = new Database(
|
|
12
|
+
db = new Database(':memory:');
|
|
13
13
|
storage = new SQLiteStorageAdapter({ db });
|
|
14
14
|
});
|
|
15
15
|
|
|
@@ -17,35 +17,35 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
17
17
|
storage.close();
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
describe(
|
|
21
|
-
it(
|
|
20
|
+
describe('createRun', () => {
|
|
21
|
+
it('should create a run and return it with generated id and createdAt', async () => {
|
|
22
22
|
const run = await storage.createRun({
|
|
23
|
-
kind:
|
|
24
|
-
status:
|
|
25
|
-
input: { foo:
|
|
26
|
-
metadata: { userId:
|
|
23
|
+
kind: 'test.workflow',
|
|
24
|
+
status: 'queued',
|
|
25
|
+
input: { foo: 'bar' },
|
|
26
|
+
metadata: { userId: 'user1' },
|
|
27
27
|
context: {},
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
expect(run.id).toBeDefined();
|
|
31
|
-
expect(run.kind).toBe(
|
|
32
|
-
expect(run.status).toBe(
|
|
33
|
-
expect(run.input).toEqual({ foo:
|
|
34
|
-
expect(run.metadata).toEqual({ userId:
|
|
31
|
+
expect(run.kind).toBe('test.workflow');
|
|
32
|
+
expect(run.status).toBe('queued');
|
|
33
|
+
expect(run.input).toEqual({ foo: 'bar' });
|
|
34
|
+
expect(run.metadata).toEqual({ userId: 'user1' });
|
|
35
35
|
expect(run.createdAt).toBeInstanceOf(Date);
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
describe(
|
|
40
|
-
it(
|
|
41
|
-
const run = await storage.getRun(
|
|
39
|
+
describe('getRun', () => {
|
|
40
|
+
it('should return null for non-existent run', async () => {
|
|
41
|
+
const run = await storage.getRun('nonexistent');
|
|
42
42
|
expect(run).toBeNull();
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
it(
|
|
45
|
+
it('should retrieve a created run', async () => {
|
|
46
46
|
const created = await storage.createRun({
|
|
47
|
-
kind:
|
|
48
|
-
status:
|
|
47
|
+
kind: 'test.workflow',
|
|
48
|
+
status: 'queued',
|
|
49
49
|
input: { value: 123 },
|
|
50
50
|
metadata: {},
|
|
51
51
|
context: {},
|
|
@@ -58,119 +58,92 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
describe(
|
|
62
|
-
it(
|
|
61
|
+
describe('updateRun', () => {
|
|
62
|
+
it('should update run status', async () => {
|
|
63
63
|
const run = await storage.createRun({
|
|
64
|
-
kind:
|
|
65
|
-
status:
|
|
64
|
+
kind: 'test.workflow',
|
|
65
|
+
status: 'queued',
|
|
66
66
|
input: {},
|
|
67
67
|
metadata: {},
|
|
68
68
|
context: {},
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
-
await storage.updateRun(run.id, { status:
|
|
71
|
+
await storage.updateRun(run.id, { status: 'running' });
|
|
72
72
|
|
|
73
73
|
const updated = await storage.getRun(run.id);
|
|
74
|
-
expect(updated!.status).toBe(
|
|
74
|
+
expect(updated!.status).toBe('running');
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
it(
|
|
77
|
+
it('should update run context', async () => {
|
|
78
78
|
const run = await storage.createRun({
|
|
79
|
-
kind:
|
|
80
|
-
status:
|
|
79
|
+
kind: 'test.workflow',
|
|
80
|
+
status: 'running',
|
|
81
81
|
input: {},
|
|
82
82
|
metadata: {},
|
|
83
|
-
context: { step1:
|
|
83
|
+
context: { step1: 'result1' },
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
await storage.updateRun(run.id, {
|
|
87
|
-
context: { step1:
|
|
87
|
+
context: { step1: 'result1', step2: 'result2' },
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
const updated = await storage.getRun(run.id);
|
|
91
|
-
expect(updated!.context).toEqual({ step1:
|
|
91
|
+
expect(updated!.context).toEqual({ step1: 'result1', step2: 'result2' });
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
it(
|
|
94
|
+
it('should update run error', async () => {
|
|
95
95
|
const run = await storage.createRun({
|
|
96
|
-
kind:
|
|
97
|
-
status:
|
|
96
|
+
kind: 'test.workflow',
|
|
97
|
+
status: 'running',
|
|
98
98
|
input: {},
|
|
99
99
|
metadata: {},
|
|
100
100
|
context: {},
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
await storage.updateRun(run.id, {
|
|
104
|
-
status:
|
|
105
|
-
error: { code:
|
|
104
|
+
status: 'failed',
|
|
105
|
+
error: { code: 'TEST_ERROR', message: 'Something went wrong' },
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
const updated = await storage.getRun(run.id);
|
|
109
|
-
expect(updated!.status).toBe(
|
|
110
|
-
expect(updated!.error).toEqual({
|
|
111
|
-
code: "TEST_ERROR",
|
|
112
|
-
message: "Something went wrong",
|
|
113
|
-
});
|
|
109
|
+
expect(updated!.status).toBe('failed');
|
|
110
|
+
expect(updated!.error).toEqual({ code: 'TEST_ERROR', message: 'Something went wrong' });
|
|
114
111
|
});
|
|
115
112
|
});
|
|
116
113
|
|
|
117
|
-
describe(
|
|
114
|
+
describe('listRuns', () => {
|
|
118
115
|
beforeEach(async () => {
|
|
119
116
|
// Create some test runs
|
|
120
|
-
await storage.createRun({
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
metadata: {},
|
|
125
|
-
context: {},
|
|
126
|
-
});
|
|
127
|
-
await storage.createRun({
|
|
128
|
-
kind: "workflow.a",
|
|
129
|
-
status: "failed",
|
|
130
|
-
input: {},
|
|
131
|
-
metadata: {},
|
|
132
|
-
context: {},
|
|
133
|
-
});
|
|
134
|
-
await storage.createRun({
|
|
135
|
-
kind: "workflow.b",
|
|
136
|
-
status: "running",
|
|
137
|
-
input: {},
|
|
138
|
-
metadata: {},
|
|
139
|
-
context: {},
|
|
140
|
-
});
|
|
141
|
-
await storage.createRun({
|
|
142
|
-
kind: "workflow.b",
|
|
143
|
-
status: "queued",
|
|
144
|
-
input: {},
|
|
145
|
-
metadata: {},
|
|
146
|
-
context: {},
|
|
147
|
-
});
|
|
117
|
+
await storage.createRun({ kind: 'workflow.a', status: 'succeeded', input: {}, metadata: {}, context: {} });
|
|
118
|
+
await storage.createRun({ kind: 'workflow.a', status: 'failed', input: {}, metadata: {}, context: {} });
|
|
119
|
+
await storage.createRun({ kind: 'workflow.b', status: 'running', input: {}, metadata: {}, context: {} });
|
|
120
|
+
await storage.createRun({ kind: 'workflow.b', status: 'queued', input: {}, metadata: {}, context: {} });
|
|
148
121
|
});
|
|
149
122
|
|
|
150
|
-
it(
|
|
123
|
+
it('should list all runs', async () => {
|
|
151
124
|
const result = await storage.listRuns();
|
|
152
125
|
expect(result.items).toHaveLength(4);
|
|
153
126
|
expect(result.total).toBe(4);
|
|
154
127
|
});
|
|
155
128
|
|
|
156
|
-
it(
|
|
157
|
-
const result = await storage.listRuns({ kind:
|
|
129
|
+
it('should filter by kind', async () => {
|
|
130
|
+
const result = await storage.listRuns({ kind: 'workflow.a' });
|
|
158
131
|
expect(result.items).toHaveLength(2);
|
|
159
|
-
expect(result.items.every(
|
|
132
|
+
expect(result.items.every(r => r.kind === 'workflow.a')).toBe(true);
|
|
160
133
|
});
|
|
161
134
|
|
|
162
|
-
it(
|
|
163
|
-
const result = await storage.listRuns({ status:
|
|
135
|
+
it('should filter by single status', async () => {
|
|
136
|
+
const result = await storage.listRuns({ status: 'running' });
|
|
164
137
|
expect(result.items).toHaveLength(1);
|
|
165
|
-
expect(result.items[0].status).toBe(
|
|
138
|
+
expect(result.items[0].status).toBe('running');
|
|
166
139
|
});
|
|
167
140
|
|
|
168
|
-
it(
|
|
169
|
-
const result = await storage.listRuns({ status: [
|
|
141
|
+
it('should filter by multiple statuses', async () => {
|
|
142
|
+
const result = await storage.listRuns({ status: ['queued', 'running'] });
|
|
170
143
|
expect(result.items).toHaveLength(2);
|
|
171
144
|
});
|
|
172
145
|
|
|
173
|
-
it(
|
|
146
|
+
it('should support pagination', async () => {
|
|
174
147
|
const page1 = await storage.listRuns({ limit: 2, offset: 0 });
|
|
175
148
|
const page2 = await storage.listRuns({ limit: 2, offset: 2 });
|
|
176
149
|
|
|
@@ -180,13 +153,13 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
180
153
|
});
|
|
181
154
|
});
|
|
182
155
|
|
|
183
|
-
describe(
|
|
156
|
+
describe('step operations', () => {
|
|
184
157
|
let runId: string;
|
|
185
158
|
|
|
186
159
|
beforeEach(async () => {
|
|
187
160
|
const run = await storage.createRun({
|
|
188
|
-
kind:
|
|
189
|
-
status:
|
|
161
|
+
kind: 'test.workflow',
|
|
162
|
+
status: 'running',
|
|
190
163
|
input: {},
|
|
191
164
|
metadata: {},
|
|
192
165
|
context: {},
|
|
@@ -194,83 +167,62 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
194
167
|
runId = run.id;
|
|
195
168
|
});
|
|
196
169
|
|
|
197
|
-
it(
|
|
170
|
+
it('should create and retrieve a step', async () => {
|
|
198
171
|
const step = await storage.createStep({
|
|
199
172
|
runId,
|
|
200
|
-
stepKey:
|
|
201
|
-
stepName:
|
|
202
|
-
status:
|
|
173
|
+
stepKey: 'step1',
|
|
174
|
+
stepName: 'First Step',
|
|
175
|
+
status: 'running',
|
|
203
176
|
attempt: 1,
|
|
204
177
|
startedAt: new Date(),
|
|
205
178
|
});
|
|
206
179
|
|
|
207
180
|
expect(step.id).toBeDefined();
|
|
208
|
-
expect(step.stepKey).toBe(
|
|
181
|
+
expect(step.stepKey).toBe('step1');
|
|
209
182
|
|
|
210
183
|
const retrieved = await storage.getStep(step.id);
|
|
211
184
|
expect(retrieved).not.toBeNull();
|
|
212
|
-
expect(retrieved!.stepKey).toBe(
|
|
185
|
+
expect(retrieved!.stepKey).toBe('step1');
|
|
213
186
|
});
|
|
214
187
|
|
|
215
|
-
it(
|
|
188
|
+
it('should update step status and result', async () => {
|
|
216
189
|
const step = await storage.createStep({
|
|
217
190
|
runId,
|
|
218
|
-
stepKey:
|
|
219
|
-
stepName:
|
|
220
|
-
status:
|
|
191
|
+
stepKey: 'step1',
|
|
192
|
+
stepName: 'First Step',
|
|
193
|
+
status: 'running',
|
|
221
194
|
attempt: 1,
|
|
222
195
|
startedAt: new Date(),
|
|
223
196
|
});
|
|
224
197
|
|
|
225
198
|
await storage.updateStep(step.id, {
|
|
226
|
-
status:
|
|
199
|
+
status: 'succeeded',
|
|
227
200
|
result: { data: [1, 2, 3] },
|
|
228
201
|
finishedAt: new Date(),
|
|
229
202
|
});
|
|
230
203
|
|
|
231
204
|
const updated = await storage.getStep(step.id);
|
|
232
|
-
expect(updated!.status).toBe(
|
|
205
|
+
expect(updated!.status).toBe('succeeded');
|
|
233
206
|
expect(updated!.result).toEqual({ data: [1, 2, 3] });
|
|
234
207
|
});
|
|
235
208
|
|
|
236
|
-
it(
|
|
237
|
-
await storage.createStep({
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
stepName: "Step 1",
|
|
241
|
-
status: "succeeded",
|
|
242
|
-
attempt: 1,
|
|
243
|
-
startedAt: new Date(),
|
|
244
|
-
});
|
|
245
|
-
await storage.createStep({
|
|
246
|
-
runId,
|
|
247
|
-
stepKey: "step2",
|
|
248
|
-
stepName: "Step 2",
|
|
249
|
-
status: "succeeded",
|
|
250
|
-
attempt: 1,
|
|
251
|
-
startedAt: new Date(),
|
|
252
|
-
});
|
|
253
|
-
await storage.createStep({
|
|
254
|
-
runId,
|
|
255
|
-
stepKey: "step3",
|
|
256
|
-
stepName: "Step 3",
|
|
257
|
-
status: "running",
|
|
258
|
-
attempt: 1,
|
|
259
|
-
startedAt: new Date(),
|
|
260
|
-
});
|
|
209
|
+
it('should get all steps for a run', async () => {
|
|
210
|
+
await storage.createStep({ runId, stepKey: 'step1', stepName: 'Step 1', status: 'succeeded', attempt: 1, startedAt: new Date() });
|
|
211
|
+
await storage.createStep({ runId, stepKey: 'step2', stepName: 'Step 2', status: 'succeeded', attempt: 1, startedAt: new Date() });
|
|
212
|
+
await storage.createStep({ runId, stepKey: 'step3', stepName: 'Step 3', status: 'running', attempt: 1, startedAt: new Date() });
|
|
261
213
|
|
|
262
214
|
const steps = await storage.getStepsForRun(runId);
|
|
263
215
|
expect(steps).toHaveLength(3);
|
|
264
216
|
});
|
|
265
217
|
});
|
|
266
218
|
|
|
267
|
-
describe(
|
|
219
|
+
describe('event operations', () => {
|
|
268
220
|
let runId: string;
|
|
269
221
|
|
|
270
222
|
beforeEach(async () => {
|
|
271
223
|
const run = await storage.createRun({
|
|
272
|
-
kind:
|
|
273
|
-
status:
|
|
224
|
+
kind: 'test.workflow',
|
|
225
|
+
status: 'running',
|
|
274
226
|
input: {},
|
|
275
227
|
metadata: {},
|
|
276
228
|
context: {},
|
|
@@ -278,127 +230,86 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
278
230
|
runId = run.id;
|
|
279
231
|
});
|
|
280
232
|
|
|
281
|
-
it(
|
|
233
|
+
it('should save and retrieve events', async () => {
|
|
282
234
|
await storage.saveEvent({
|
|
283
235
|
runId,
|
|
284
|
-
eventType:
|
|
285
|
-
level:
|
|
236
|
+
eventType: 'run.started',
|
|
237
|
+
level: 'info',
|
|
286
238
|
timestamp: new Date(),
|
|
287
239
|
});
|
|
288
240
|
|
|
289
241
|
await storage.saveEvent({
|
|
290
242
|
runId,
|
|
291
|
-
stepKey:
|
|
292
|
-
eventType:
|
|
293
|
-
level:
|
|
294
|
-
payload: { result:
|
|
243
|
+
stepKey: 'step1',
|
|
244
|
+
eventType: 'step.completed',
|
|
245
|
+
level: 'info',
|
|
246
|
+
payload: { result: 'success' },
|
|
295
247
|
timestamp: new Date(),
|
|
296
248
|
});
|
|
297
249
|
|
|
298
250
|
const events = await storage.getEventsForRun(runId);
|
|
299
251
|
expect(events).toHaveLength(2);
|
|
300
|
-
expect(events[0].eventType).toBe(
|
|
301
|
-
expect(events[1].eventType).toBe(
|
|
252
|
+
expect(events[0].eventType).toBe('run.started');
|
|
253
|
+
expect(events[1].eventType).toBe('step.completed');
|
|
302
254
|
});
|
|
303
255
|
|
|
304
|
-
it(
|
|
305
|
-
await storage.saveEvent({
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
timestamp: new Date(),
|
|
310
|
-
});
|
|
311
|
-
await storage.saveEvent({
|
|
312
|
-
runId,
|
|
313
|
-
stepKey: "step1",
|
|
314
|
-
eventType: "step.started",
|
|
315
|
-
level: "info",
|
|
316
|
-
timestamp: new Date(),
|
|
317
|
-
});
|
|
318
|
-
await storage.saveEvent({
|
|
319
|
-
runId,
|
|
320
|
-
stepKey: "step1",
|
|
321
|
-
eventType: "step.completed",
|
|
322
|
-
level: "info",
|
|
323
|
-
timestamp: new Date(),
|
|
324
|
-
});
|
|
325
|
-
await storage.saveEvent({
|
|
326
|
-
runId,
|
|
327
|
-
stepKey: "step2",
|
|
328
|
-
eventType: "step.started",
|
|
329
|
-
level: "info",
|
|
330
|
-
timestamp: new Date(),
|
|
331
|
-
});
|
|
256
|
+
it('should filter events by step', async () => {
|
|
257
|
+
await storage.saveEvent({ runId, eventType: 'run.started', level: 'info', timestamp: new Date() });
|
|
258
|
+
await storage.saveEvent({ runId, stepKey: 'step1', eventType: 'step.started', level: 'info', timestamp: new Date() });
|
|
259
|
+
await storage.saveEvent({ runId, stepKey: 'step1', eventType: 'step.completed', level: 'info', timestamp: new Date() });
|
|
260
|
+
await storage.saveEvent({ runId, stepKey: 'step2', eventType: 'step.started', level: 'info', timestamp: new Date() });
|
|
332
261
|
|
|
333
|
-
const step1Events = await storage.getEventsForRun(runId, {
|
|
334
|
-
stepKey: "step1",
|
|
335
|
-
});
|
|
262
|
+
const step1Events = await storage.getEventsForRun(runId, { stepKey: 'step1' });
|
|
336
263
|
expect(step1Events).toHaveLength(2);
|
|
337
264
|
});
|
|
338
265
|
|
|
339
|
-
it(
|
|
340
|
-
await storage.saveEvent({
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
level: "info",
|
|
344
|
-
timestamp: new Date(),
|
|
345
|
-
});
|
|
346
|
-
await storage.saveEvent({
|
|
347
|
-
runId,
|
|
348
|
-
eventType: "step.failed",
|
|
349
|
-
level: "error",
|
|
350
|
-
timestamp: new Date(),
|
|
351
|
-
});
|
|
352
|
-
await storage.saveEvent({
|
|
353
|
-
runId,
|
|
354
|
-
eventType: "step.retry",
|
|
355
|
-
level: "warn",
|
|
356
|
-
timestamp: new Date(),
|
|
357
|
-
});
|
|
266
|
+
it('should filter events by level', async () => {
|
|
267
|
+
await storage.saveEvent({ runId, eventType: 'run.started', level: 'info', timestamp: new Date() });
|
|
268
|
+
await storage.saveEvent({ runId, eventType: 'step.failed', level: 'error', timestamp: new Date() });
|
|
269
|
+
await storage.saveEvent({ runId, eventType: 'step.retry', level: 'warn', timestamp: new Date() });
|
|
358
270
|
|
|
359
|
-
const errorEvents = await storage.getEventsForRun(runId, {
|
|
360
|
-
level: "error",
|
|
361
|
-
});
|
|
271
|
+
const errorEvents = await storage.getEventsForRun(runId, { level: 'error' });
|
|
362
272
|
expect(errorEvents).toHaveLength(1);
|
|
363
|
-
expect(errorEvents[0].eventType).toBe(
|
|
273
|
+
expect(errorEvents[0].eventType).toBe('step.failed');
|
|
364
274
|
});
|
|
365
275
|
});
|
|
366
276
|
|
|
367
|
-
describe(
|
|
368
|
-
it(
|
|
277
|
+
describe('transaction', () => {
|
|
278
|
+
it('should execute operations within a synchronous transaction', () => {
|
|
369
279
|
// Use the synchronous transaction method for better-sqlite3
|
|
370
280
|
const result = storage.transactionSync(() => {
|
|
371
281
|
// Note: We access the underlying sync methods via the statements
|
|
372
|
-
const id =
|
|
373
|
-
storage[
|
|
282
|
+
const id = 'test-tx-run';
|
|
283
|
+
storage['stmts']!.insertRun.run(
|
|
374
284
|
id,
|
|
375
|
-
|
|
376
|
-
|
|
285
|
+
'test.workflow',
|
|
286
|
+
'queued',
|
|
377
287
|
null,
|
|
378
288
|
JSON.stringify({}),
|
|
379
289
|
JSON.stringify({}),
|
|
380
290
|
JSON.stringify({}),
|
|
381
291
|
null,
|
|
382
|
-
new Date().toISOString(),
|
|
383
292
|
null,
|
|
293
|
+
new Date().toISOString(),
|
|
384
294
|
null,
|
|
295
|
+
null
|
|
385
296
|
);
|
|
386
|
-
storage[
|
|
297
|
+
storage['stmts']!.updateRun.run('running', null, null, null, null, null, id);
|
|
387
298
|
return id;
|
|
388
299
|
});
|
|
389
300
|
|
|
390
|
-
expect(result).toBe(
|
|
301
|
+
expect(result).toBe('test-tx-run');
|
|
391
302
|
});
|
|
392
303
|
|
|
393
|
-
it(
|
|
304
|
+
it('should rollback on error', async () => {
|
|
394
305
|
const initialStats = storage.getStats();
|
|
395
306
|
|
|
396
307
|
try {
|
|
397
308
|
storage.transactionSync(() => {
|
|
398
|
-
storage[
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
309
|
+
storage['stmts']!.insertRun.run(
|
|
310
|
+
'rollback-test',
|
|
311
|
+
'test.workflow',
|
|
312
|
+
'queued',
|
|
402
313
|
null,
|
|
403
314
|
JSON.stringify({}),
|
|
404
315
|
JSON.stringify({}),
|
|
@@ -406,9 +317,9 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
406
317
|
null,
|
|
407
318
|
new Date().toISOString(),
|
|
408
319
|
null,
|
|
409
|
-
null
|
|
320
|
+
null
|
|
410
321
|
);
|
|
411
|
-
throw new Error(
|
|
322
|
+
throw new Error('Simulated error');
|
|
412
323
|
});
|
|
413
324
|
} catch {
|
|
414
325
|
// Expected
|
|
@@ -419,12 +330,12 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
419
330
|
});
|
|
420
331
|
});
|
|
421
332
|
|
|
422
|
-
describe(
|
|
423
|
-
it(
|
|
333
|
+
describe('deleteOldRuns', () => {
|
|
334
|
+
it('should delete runs older than the specified date', async () => {
|
|
424
335
|
// Create a run
|
|
425
336
|
const run = await storage.createRun({
|
|
426
|
-
kind:
|
|
427
|
-
status:
|
|
337
|
+
kind: 'test.workflow',
|
|
338
|
+
status: 'succeeded',
|
|
428
339
|
input: {},
|
|
429
340
|
metadata: {},
|
|
430
341
|
context: {},
|
|
@@ -433,17 +344,17 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
433
344
|
// Create a step for the run
|
|
434
345
|
await storage.createStep({
|
|
435
346
|
runId: run.id,
|
|
436
|
-
stepKey:
|
|
437
|
-
stepName:
|
|
438
|
-
status:
|
|
347
|
+
stepKey: 'step1',
|
|
348
|
+
stepName: 'Step 1',
|
|
349
|
+
status: 'succeeded',
|
|
439
350
|
attempt: 1,
|
|
440
351
|
});
|
|
441
352
|
|
|
442
353
|
// Create an event for the run
|
|
443
354
|
await storage.saveEvent({
|
|
444
355
|
runId: run.id,
|
|
445
|
-
eventType:
|
|
446
|
-
level:
|
|
356
|
+
eventType: 'run.completed',
|
|
357
|
+
level: 'info',
|
|
447
358
|
timestamp: new Date(),
|
|
448
359
|
});
|
|
449
360
|
|
|
@@ -467,48 +378,22 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
467
378
|
});
|
|
468
379
|
});
|
|
469
380
|
|
|
470
|
-
describe(
|
|
471
|
-
it(
|
|
472
|
-
await storage.createRun({
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
metadata: {},
|
|
477
|
-
context: {},
|
|
478
|
-
});
|
|
479
|
-
await storage.createRun({
|
|
480
|
-
kind: "workflow.a",
|
|
481
|
-
status: "running",
|
|
482
|
-
input: {},
|
|
483
|
-
metadata: {},
|
|
484
|
-
context: {},
|
|
485
|
-
});
|
|
486
|
-
await storage.createRun({
|
|
487
|
-
kind: "workflow.b",
|
|
488
|
-
status: "queued",
|
|
489
|
-
input: {},
|
|
490
|
-
metadata: {},
|
|
491
|
-
context: {},
|
|
492
|
-
});
|
|
493
|
-
await storage.createRun({
|
|
494
|
-
kind: "workflow.b",
|
|
495
|
-
status: "failed",
|
|
496
|
-
input: {},
|
|
497
|
-
metadata: {},
|
|
498
|
-
context: {},
|
|
499
|
-
});
|
|
381
|
+
describe('resume support', () => {
|
|
382
|
+
it('should get interrupted runs', async () => {
|
|
383
|
+
await storage.createRun({ kind: 'workflow.a', status: 'succeeded', input: {}, metadata: {}, context: {} });
|
|
384
|
+
await storage.createRun({ kind: 'workflow.a', status: 'running', input: {}, metadata: {}, context: {} });
|
|
385
|
+
await storage.createRun({ kind: 'workflow.b', status: 'queued', input: {}, metadata: {}, context: {} });
|
|
386
|
+
await storage.createRun({ kind: 'workflow.b', status: 'failed', input: {}, metadata: {}, context: {} });
|
|
500
387
|
|
|
501
388
|
const interrupted = await storage.getInterruptedRuns();
|
|
502
389
|
expect(interrupted).toHaveLength(2);
|
|
503
|
-
expect(
|
|
504
|
-
interrupted.every((r) => ["queued", "running"].includes(r.status)),
|
|
505
|
-
).toBe(true);
|
|
390
|
+
expect(interrupted.every(r => ['queued', 'running'].includes(r.status))).toBe(true);
|
|
506
391
|
});
|
|
507
392
|
|
|
508
|
-
it(
|
|
393
|
+
it('should get last completed step', async () => {
|
|
509
394
|
const run = await storage.createRun({
|
|
510
|
-
kind:
|
|
511
|
-
status:
|
|
395
|
+
kind: 'test.workflow',
|
|
396
|
+
status: 'running',
|
|
512
397
|
input: {},
|
|
513
398
|
metadata: {},
|
|
514
399
|
context: {},
|
|
@@ -517,9 +402,9 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
517
402
|
const now = new Date();
|
|
518
403
|
await storage.createStep({
|
|
519
404
|
runId: run.id,
|
|
520
|
-
stepKey:
|
|
521
|
-
stepName:
|
|
522
|
-
status:
|
|
405
|
+
stepKey: 'step1',
|
|
406
|
+
stepName: 'Step 1',
|
|
407
|
+
status: 'succeeded',
|
|
523
408
|
attempt: 1,
|
|
524
409
|
startedAt: now,
|
|
525
410
|
finishedAt: new Date(now.getTime() + 100),
|
|
@@ -527,9 +412,9 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
527
412
|
|
|
528
413
|
await storage.createStep({
|
|
529
414
|
runId: run.id,
|
|
530
|
-
stepKey:
|
|
531
|
-
stepName:
|
|
532
|
-
status:
|
|
415
|
+
stepKey: 'step2',
|
|
416
|
+
stepName: 'Step 2',
|
|
417
|
+
status: 'succeeded',
|
|
533
418
|
attempt: 1,
|
|
534
419
|
startedAt: new Date(now.getTime() + 200),
|
|
535
420
|
finishedAt: new Date(now.getTime() + 300),
|
|
@@ -537,52 +422,40 @@ describe("SQLiteStorageAdapter", () => {
|
|
|
537
422
|
|
|
538
423
|
await storage.createStep({
|
|
539
424
|
runId: run.id,
|
|
540
|
-
stepKey:
|
|
541
|
-
stepName:
|
|
542
|
-
status:
|
|
425
|
+
stepKey: 'step3',
|
|
426
|
+
stepName: 'Step 3',
|
|
427
|
+
status: 'running',
|
|
543
428
|
attempt: 1,
|
|
544
429
|
startedAt: new Date(now.getTime() + 400),
|
|
545
430
|
});
|
|
546
431
|
|
|
547
432
|
const lastCompleted = await storage.getLastCompletedStep(run.id);
|
|
548
433
|
expect(lastCompleted).not.toBeNull();
|
|
549
|
-
expect(lastCompleted!.stepKey).toBe(
|
|
434
|
+
expect(lastCompleted!.stepKey).toBe('step2');
|
|
550
435
|
});
|
|
551
436
|
});
|
|
552
437
|
|
|
553
|
-
describe(
|
|
554
|
-
it(
|
|
438
|
+
describe('deprecated transaction() returns callback result', () => {
|
|
439
|
+
it('should return the callback result for sync operations', async () => {
|
|
555
440
|
const result = await storage.transaction(async () => {
|
|
556
441
|
return 42;
|
|
557
442
|
});
|
|
558
443
|
expect(result).toBe(42);
|
|
559
444
|
});
|
|
560
445
|
|
|
561
|
-
it(
|
|
446
|
+
it('should return the callback result when awaiting resolved promises', async () => {
|
|
562
447
|
const result = await storage.transaction(async () => {
|
|
563
448
|
await Promise.resolve();
|
|
564
|
-
return
|
|
449
|
+
return 'hello';
|
|
565
450
|
});
|
|
566
|
-
expect(result).toBe(
|
|
451
|
+
expect(result).toBe('hello');
|
|
567
452
|
});
|
|
568
453
|
});
|
|
569
454
|
|
|
570
|
-
describe(
|
|
571
|
-
it(
|
|
572
|
-
await storage.createRun({
|
|
573
|
-
|
|
574
|
-
status: "queued",
|
|
575
|
-
input: {},
|
|
576
|
-
metadata: {},
|
|
577
|
-
context: {},
|
|
578
|
-
});
|
|
579
|
-
await storage.createRun({
|
|
580
|
-
kind: "test",
|
|
581
|
-
status: "queued",
|
|
582
|
-
input: {},
|
|
583
|
-
metadata: {},
|
|
584
|
-
context: {},
|
|
585
|
-
});
|
|
455
|
+
describe('getStats', () => {
|
|
456
|
+
it('should return database statistics', async () => {
|
|
457
|
+
await storage.createRun({ kind: 'test', status: 'queued', input: {}, metadata: {}, context: {} });
|
|
458
|
+
await storage.createRun({ kind: 'test', status: 'queued', input: {}, metadata: {}, context: {} });
|
|
586
459
|
|
|
587
460
|
const stats = storage.getStats();
|
|
588
461
|
expect(stats.runs).toBe(2);
|