@multiplier-labs/stepflow 0.3.3 → 0.3.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.
Files changed (84) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-GYPNJBDP.js → chunk-D5HW7VEG.js} +269 -48
  3. package/dist/chunk-D5HW7VEG.js.map +1 -0
  4. package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
  5. package/dist/chunk-FMPJBK2N.js.map +1 -0
  6. package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
  7. package/dist/chunk-JPXPB7U5.js.map +1 -0
  8. package/dist/{chunk-4VOKMGP6.js → chunk-JUPZEBR3.js} +519 -522
  9. package/dist/chunk-JUPZEBR3.js.map +1 -0
  10. package/dist/events/index.d.ts +58 -3
  11. package/dist/events/index.js +1 -1
  12. package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
  13. package/dist/index.d.ts +2 -12
  14. package/dist/index.js +764 -485
  15. package/dist/index.js.map +1 -1
  16. package/dist/scheduler/index.d.ts +1 -1
  17. package/dist/scheduler/index.js +2 -2
  18. package/dist/storage/index.d.ts +313 -47
  19. package/dist/storage/index.js +4 -2
  20. package/package.json +27 -13
  21. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  22. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  23. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  24. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  25. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  26. package/.github/CODEOWNERS +0 -15
  27. package/.github/dependabot.yml +0 -23
  28. package/.github/workflows/ci.yml +0 -38
  29. package/.github/workflows/publish.yml +0 -36
  30. package/.github/workflows/security.yml +0 -29
  31. package/CHANGELOG.md +0 -34
  32. package/SECURITY.md +0 -40
  33. package/dist/chunk-4VOKMGP6.js.map +0 -1
  34. package/dist/chunk-GYPNJBDP.js.map +0 -1
  35. package/dist/chunk-KHTUWLVZ.js.map +0 -1
  36. package/dist/chunk-ML35PIHX.js.map +0 -1
  37. package/docs/github-packages-setup.md +0 -97
  38. package/docs/stepflow-api-reference.md +0 -1661
  39. package/src/core/engine.test.ts +0 -1365
  40. package/src/core/engine.ts +0 -692
  41. package/src/core/index.ts +0 -7
  42. package/src/core/orchestrator.ts +0 -571
  43. package/src/core/types.ts +0 -220
  44. package/src/events/index.ts +0 -8
  45. package/src/events/memory.test.ts +0 -107
  46. package/src/events/memory.ts +0 -92
  47. package/src/events/socketio.test.ts +0 -378
  48. package/src/events/socketio.ts +0 -262
  49. package/src/events/types.ts +0 -118
  50. package/src/events/webhook.test.ts +0 -657
  51. package/src/events/webhook.ts +0 -564
  52. package/src/index.ts +0 -270
  53. package/src/planning/index.ts +0 -55
  54. package/src/planning/planner.ts +0 -576
  55. package/src/planning/planning.test.ts +0 -727
  56. package/src/planning/registry.test.ts +0 -398
  57. package/src/planning/registry.ts +0 -365
  58. package/src/planning/types.ts +0 -566
  59. package/src/scheduler/cron.test.ts +0 -471
  60. package/src/scheduler/cron.ts +0 -414
  61. package/src/scheduler/index.ts +0 -8
  62. package/src/scheduler/postgres-persistence.ts +0 -518
  63. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  64. package/src/scheduler/sqlite-persistence.ts +0 -261
  65. package/src/scheduler/types.ts +0 -73
  66. package/src/storage/index.ts +0 -27
  67. package/src/storage/memory.test.ts +0 -349
  68. package/src/storage/memory.ts +0 -227
  69. package/src/storage/postgres.ts +0 -1354
  70. package/src/storage/sqlite.test.ts +0 -466
  71. package/src/storage/sqlite.ts +0 -627
  72. package/src/storage/types.ts +0 -343
  73. package/src/utils/errors.test.ts +0 -150
  74. package/src/utils/errors.ts +0 -161
  75. package/src/utils/id.ts +0 -21
  76. package/src/utils/index.ts +0 -8
  77. package/src/utils/logger.test.ts +0 -121
  78. package/src/utils/logger.ts +0 -96
  79. package/src/utils/postgres-deps.ts +0 -63
  80. package/src/utils/retry.test.ts +0 -177
  81. package/src/utils/retry.ts +0 -121
  82. package/tsconfig.json +0 -21
  83. package/tsup.config.ts +0 -14
  84. package/vitest.config.ts +0 -9
@@ -1,466 +0,0 @@
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
-
6
- describe('SQLiteStorageAdapter', () => {
7
- let db: Database.Database;
8
- let storage: SQLiteStorageAdapter;
9
-
10
- beforeEach(() => {
11
- // Use in-memory database for testing
12
- db = new Database(':memory:');
13
- storage = new SQLiteStorageAdapter({ db });
14
- });
15
-
16
- afterEach(() => {
17
- storage.close();
18
- });
19
-
20
- describe('createRun', () => {
21
- it('should create a run and return it with generated id and createdAt', async () => {
22
- const run = await storage.createRun({
23
- kind: 'test.workflow',
24
- status: 'queued',
25
- input: { foo: 'bar' },
26
- metadata: { userId: 'user1' },
27
- context: {},
28
- });
29
-
30
- expect(run.id).toBeDefined();
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
- expect(run.createdAt).toBeInstanceOf(Date);
36
- });
37
- });
38
-
39
- describe('getRun', () => {
40
- it('should return null for non-existent run', async () => {
41
- const run = await storage.getRun('nonexistent');
42
- expect(run).toBeNull();
43
- });
44
-
45
- it('should retrieve a created run', async () => {
46
- const created = await storage.createRun({
47
- kind: 'test.workflow',
48
- status: 'queued',
49
- input: { value: 123 },
50
- metadata: {},
51
- context: {},
52
- });
53
-
54
- const retrieved = await storage.getRun(created.id);
55
- expect(retrieved).not.toBeNull();
56
- expect(retrieved!.id).toBe(created.id);
57
- expect(retrieved!.input).toEqual({ value: 123 });
58
- });
59
- });
60
-
61
- describe('updateRun', () => {
62
- it('should update run status', async () => {
63
- const run = await storage.createRun({
64
- kind: 'test.workflow',
65
- status: 'queued',
66
- input: {},
67
- metadata: {},
68
- context: {},
69
- });
70
-
71
- await storage.updateRun(run.id, { status: 'running' });
72
-
73
- const updated = await storage.getRun(run.id);
74
- expect(updated!.status).toBe('running');
75
- });
76
-
77
- it('should update run context', async () => {
78
- const run = await storage.createRun({
79
- kind: 'test.workflow',
80
- status: 'running',
81
- input: {},
82
- metadata: {},
83
- context: { step1: 'result1' },
84
- });
85
-
86
- await storage.updateRun(run.id, {
87
- context: { step1: 'result1', step2: 'result2' },
88
- });
89
-
90
- const updated = await storage.getRun(run.id);
91
- expect(updated!.context).toEqual({ step1: 'result1', step2: 'result2' });
92
- });
93
-
94
- it('should update run error', async () => {
95
- const run = await storage.createRun({
96
- kind: 'test.workflow',
97
- status: 'running',
98
- input: {},
99
- metadata: {},
100
- context: {},
101
- });
102
-
103
- await storage.updateRun(run.id, {
104
- status: 'failed',
105
- error: { code: 'TEST_ERROR', message: 'Something went wrong' },
106
- });
107
-
108
- const updated = await storage.getRun(run.id);
109
- expect(updated!.status).toBe('failed');
110
- expect(updated!.error).toEqual({ code: 'TEST_ERROR', message: 'Something went wrong' });
111
- });
112
- });
113
-
114
- describe('listRuns', () => {
115
- beforeEach(async () => {
116
- // Create some test runs
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: {} });
121
- });
122
-
123
- it('should list all runs', async () => {
124
- const result = await storage.listRuns();
125
- expect(result.items).toHaveLength(4);
126
- expect(result.total).toBe(4);
127
- });
128
-
129
- it('should filter by kind', async () => {
130
- const result = await storage.listRuns({ kind: 'workflow.a' });
131
- expect(result.items).toHaveLength(2);
132
- expect(result.items.every(r => r.kind === 'workflow.a')).toBe(true);
133
- });
134
-
135
- it('should filter by single status', async () => {
136
- const result = await storage.listRuns({ status: 'running' });
137
- expect(result.items).toHaveLength(1);
138
- expect(result.items[0].status).toBe('running');
139
- });
140
-
141
- it('should filter by multiple statuses', async () => {
142
- const result = await storage.listRuns({ status: ['queued', 'running'] });
143
- expect(result.items).toHaveLength(2);
144
- });
145
-
146
- it('should support pagination', async () => {
147
- const page1 = await storage.listRuns({ limit: 2, offset: 0 });
148
- const page2 = await storage.listRuns({ limit: 2, offset: 2 });
149
-
150
- expect(page1.items).toHaveLength(2);
151
- expect(page2.items).toHaveLength(2);
152
- expect(page1.items[0].id).not.toBe(page2.items[0].id);
153
- });
154
- });
155
-
156
- describe('step operations', () => {
157
- let runId: string;
158
-
159
- beforeEach(async () => {
160
- const run = await storage.createRun({
161
- kind: 'test.workflow',
162
- status: 'running',
163
- input: {},
164
- metadata: {},
165
- context: {},
166
- });
167
- runId = run.id;
168
- });
169
-
170
- it('should create and retrieve a step', async () => {
171
- const step = await storage.createStep({
172
- runId,
173
- stepKey: 'step1',
174
- stepName: 'First Step',
175
- status: 'running',
176
- attempt: 1,
177
- startedAt: new Date(),
178
- });
179
-
180
- expect(step.id).toBeDefined();
181
- expect(step.stepKey).toBe('step1');
182
-
183
- const retrieved = await storage.getStep(step.id);
184
- expect(retrieved).not.toBeNull();
185
- expect(retrieved!.stepKey).toBe('step1');
186
- });
187
-
188
- it('should update step status and result', async () => {
189
- const step = await storage.createStep({
190
- runId,
191
- stepKey: 'step1',
192
- stepName: 'First Step',
193
- status: 'running',
194
- attempt: 1,
195
- startedAt: new Date(),
196
- });
197
-
198
- await storage.updateStep(step.id, {
199
- status: 'succeeded',
200
- result: { data: [1, 2, 3] },
201
- finishedAt: new Date(),
202
- });
203
-
204
- const updated = await storage.getStep(step.id);
205
- expect(updated!.status).toBe('succeeded');
206
- expect(updated!.result).toEqual({ data: [1, 2, 3] });
207
- });
208
-
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() });
213
-
214
- const steps = await storage.getStepsForRun(runId);
215
- expect(steps).toHaveLength(3);
216
- });
217
- });
218
-
219
- describe('event operations', () => {
220
- let runId: string;
221
-
222
- beforeEach(async () => {
223
- const run = await storage.createRun({
224
- kind: 'test.workflow',
225
- status: 'running',
226
- input: {},
227
- metadata: {},
228
- context: {},
229
- });
230
- runId = run.id;
231
- });
232
-
233
- it('should save and retrieve events', async () => {
234
- await storage.saveEvent({
235
- runId,
236
- eventType: 'run.started',
237
- level: 'info',
238
- timestamp: new Date(),
239
- });
240
-
241
- await storage.saveEvent({
242
- runId,
243
- stepKey: 'step1',
244
- eventType: 'step.completed',
245
- level: 'info',
246
- payload: { result: 'success' },
247
- timestamp: new Date(),
248
- });
249
-
250
- const events = await storage.getEventsForRun(runId);
251
- expect(events).toHaveLength(2);
252
- expect(events[0].eventType).toBe('run.started');
253
- expect(events[1].eventType).toBe('step.completed');
254
- });
255
-
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() });
261
-
262
- const step1Events = await storage.getEventsForRun(runId, { stepKey: 'step1' });
263
- expect(step1Events).toHaveLength(2);
264
- });
265
-
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() });
270
-
271
- const errorEvents = await storage.getEventsForRun(runId, { level: 'error' });
272
- expect(errorEvents).toHaveLength(1);
273
- expect(errorEvents[0].eventType).toBe('step.failed');
274
- });
275
- });
276
-
277
- describe('transaction', () => {
278
- it('should execute operations within a synchronous transaction', () => {
279
- // Use the synchronous transaction method for better-sqlite3
280
- const result = storage.transactionSync(() => {
281
- // Note: We access the underlying sync methods via the statements
282
- const id = 'test-tx-run';
283
- storage['stmts']!.insertRun.run(
284
- id,
285
- 'test.workflow',
286
- 'queued',
287
- null,
288
- JSON.stringify({}),
289
- JSON.stringify({}),
290
- JSON.stringify({}),
291
- null,
292
- null,
293
- new Date().toISOString(),
294
- null,
295
- null
296
- );
297
- storage['stmts']!.updateRun.run('running', null, null, null, null, null, id);
298
- return id;
299
- });
300
-
301
- expect(result).toBe('test-tx-run');
302
- });
303
-
304
- it('should rollback on error', async () => {
305
- const initialStats = storage.getStats();
306
-
307
- try {
308
- storage.transactionSync(() => {
309
- storage['stmts']!.insertRun.run(
310
- 'rollback-test',
311
- 'test.workflow',
312
- 'queued',
313
- null,
314
- JSON.stringify({}),
315
- JSON.stringify({}),
316
- JSON.stringify({}),
317
- null,
318
- new Date().toISOString(),
319
- null,
320
- null
321
- );
322
- throw new Error('Simulated error');
323
- });
324
- } catch {
325
- // Expected
326
- }
327
-
328
- const finalStats = storage.getStats();
329
- expect(finalStats.runs).toBe(initialStats.runs);
330
- });
331
- });
332
-
333
- describe('deleteOldRuns', () => {
334
- it('should delete runs older than the specified date', async () => {
335
- // Create a run
336
- const run = await storage.createRun({
337
- kind: 'test.workflow',
338
- status: 'succeeded',
339
- input: {},
340
- metadata: {},
341
- context: {},
342
- });
343
-
344
- // Create a step for the run
345
- await storage.createStep({
346
- runId: run.id,
347
- stepKey: 'step1',
348
- stepName: 'Step 1',
349
- status: 'succeeded',
350
- attempt: 1,
351
- });
352
-
353
- // Create an event for the run
354
- await storage.saveEvent({
355
- runId: run.id,
356
- eventType: 'run.completed',
357
- level: 'info',
358
- timestamp: new Date(),
359
- });
360
-
361
- // Delete runs older than tomorrow
362
- const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
363
- const deleted = await storage.deleteOldRuns(tomorrow);
364
-
365
- expect(deleted).toBe(1);
366
-
367
- // Verify run is deleted
368
- const retrieved = await storage.getRun(run.id);
369
- expect(retrieved).toBeNull();
370
-
371
- // Verify steps are deleted
372
- const steps = await storage.getStepsForRun(run.id);
373
- expect(steps).toHaveLength(0);
374
-
375
- // Verify events are deleted
376
- const events = await storage.getEventsForRun(run.id);
377
- expect(events).toHaveLength(0);
378
- });
379
- });
380
-
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: {} });
387
-
388
- const interrupted = await storage.getInterruptedRuns();
389
- expect(interrupted).toHaveLength(2);
390
- expect(interrupted.every(r => ['queued', 'running'].includes(r.status))).toBe(true);
391
- });
392
-
393
- it('should get last completed step', async () => {
394
- const run = await storage.createRun({
395
- kind: 'test.workflow',
396
- status: 'running',
397
- input: {},
398
- metadata: {},
399
- context: {},
400
- });
401
-
402
- const now = new Date();
403
- await storage.createStep({
404
- runId: run.id,
405
- stepKey: 'step1',
406
- stepName: 'Step 1',
407
- status: 'succeeded',
408
- attempt: 1,
409
- startedAt: now,
410
- finishedAt: new Date(now.getTime() + 100),
411
- });
412
-
413
- await storage.createStep({
414
- runId: run.id,
415
- stepKey: 'step2',
416
- stepName: 'Step 2',
417
- status: 'succeeded',
418
- attempt: 1,
419
- startedAt: new Date(now.getTime() + 200),
420
- finishedAt: new Date(now.getTime() + 300),
421
- });
422
-
423
- await storage.createStep({
424
- runId: run.id,
425
- stepKey: 'step3',
426
- stepName: 'Step 3',
427
- status: 'running',
428
- attempt: 1,
429
- startedAt: new Date(now.getTime() + 400),
430
- });
431
-
432
- const lastCompleted = await storage.getLastCompletedStep(run.id);
433
- expect(lastCompleted).not.toBeNull();
434
- expect(lastCompleted!.stepKey).toBe('step2');
435
- });
436
- });
437
-
438
- describe('deprecated transaction() returns callback result', () => {
439
- it('should return the callback result for sync operations', async () => {
440
- const result = await storage.transaction(async () => {
441
- return 42;
442
- });
443
- expect(result).toBe(42);
444
- });
445
-
446
- it('should return the callback result when awaiting resolved promises', async () => {
447
- const result = await storage.transaction(async () => {
448
- await Promise.resolve();
449
- return 'hello';
450
- });
451
- expect(result).toBe('hello');
452
- });
453
- });
454
-
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: {} });
459
-
460
- const stats = storage.getStats();
461
- expect(stats.runs).toBe(2);
462
- expect(stats.steps).toBe(0);
463
- expect(stats.events).toBe(0);
464
- });
465
- });
466
- });