@multiplier-labs/stepflow 0.3.1 → 0.3.4
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/code-review/2026-04-11_code-review-daily.md +146 -0
- package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
- package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
- package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
- package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
- package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
- package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
- package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
- package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
- package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
- package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
- package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
- package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
- package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
- package/.github/CODEOWNERS +15 -0
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/publish.yml +17 -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-5XJBAPXF.js} +527 -551
- package/dist/chunk-5XJBAPXF.js.map +1 -0
- package/dist/{chunk-U5QGN3RT.js → chunk-FMPJBK2N.js} +67 -55
- package/dist/chunk-FMPJBK2N.js.map +1 -0
- package/dist/{chunk-GXY66V7Q.js → chunk-JPXPB7U5.js} +52 -2
- package/dist/chunk-JPXPB7U5.js.map +1 -0
- package/dist/{chunk-XCF4PLN7.js → chunk-U3PCVK6O.js} +276 -61
- package/dist/chunk-U3PCVK6O.js.map +1 -0
- package/dist/events/index.d.ts +60 -7
- package/dist/events/index.js +1 -1
- package/dist/{index-CPfGsmrp.d.ts → index-Cd0IZ40L.d.ts} +56 -17
- package/dist/index.d.ts +15 -23
- package/dist/index.js +784 -547
- 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 +322 -56
- package/dist/storage/index.js +4 -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 +5 -4
- package/package.json +12 -8
- package/src/core/engine/concurrency.ts +229 -0
- package/src/core/engine/lifecycle.ts +176 -0
- package/src/core/engine.test.ts +536 -522
- package/src/core/engine.ts +116 -235
- package/src/core/index.ts +3 -3
- package/src/core/orchestrator/persistence.ts +165 -0
- package/src/core/orchestrator/plan-resolution.ts +140 -0
- package/src/core/orchestrator/state-transitions.ts +109 -0
- package/src/core/orchestrator/step-runner.ts +427 -0
- package/src/core/orchestrator.ts +57 -395
- 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 +489 -235
- package/src/events/webhook.ts +355 -139
- package/src/index.ts +19 -19
- package/src/planning/evaluate-condition.test.ts +153 -0
- package/src/planning/evaluate-condition.ts +105 -0
- package/src/planning/index.ts +6 -3
- package/src/planning/planner.ts +45 -152
- package/src/planning/planning.test.ts +303 -366
- package/src/planning/registry.test.ts +204 -251
- package/src/planning/registry.ts +20 -106
- package/src/planning/types.ts +23 -27
- package/src/scheduler/cron.test.ts +149 -150
- package/src/scheduler/cron.ts +58 -69
- package/src/scheduler/index.ts +4 -14
- package/src/scheduler/postgres-persistence.ts +89 -105
- package/src/scheduler/sqlite-persistence.test.ts +96 -96
- package/src/scheduler/sqlite-persistence.ts +40 -22
- 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-core.ts +550 -0
- package/src/storage/postgres-migration.test.ts +69 -0
- package/src/storage/postgres-transaction-pagination.test.ts +107 -0
- package/src/storage/postgres.ts +222 -937
- package/src/storage/sqlite-core.ts +151 -0
- package/src/storage/sqlite.test.ts +330 -281
- package/src/storage/sqlite.ts +225 -170
- 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 +6 -4
- package/src/utils/logger.test.ts +52 -58
- package/src/utils/logger.ts +4 -4
- package/src/utils/pinned-fetch.test.ts +84 -0
- package/src/utils/pinned-fetch.ts +118 -0
- package/src/utils/postgres-deps.ts +20 -4
- package/src/utils/retry.test.ts +47 -67
- package/src/utils/retry.ts +9 -9
- package/src/utils/safe-json.test.ts +166 -0
- package/src/utils/safe-json.ts +107 -0
- package/src/utils/webhook-signature.test.ts +111 -0
- package/src/utils/webhook-signature.ts +74 -0
- 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,128 +1,103 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from
|
|
2
|
-
import { MemoryStorageAdapter } from
|
|
3
|
-
import type { WorkflowRunRecord, WorkflowRunStepRecord } from
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { MemoryStorageAdapter } from './memory';
|
|
3
|
+
import type { WorkflowRunRecord, WorkflowRunStepRecord } from './types';
|
|
4
4
|
|
|
5
|
-
describe(
|
|
5
|
+
describe('MemoryStorageAdapter', () => {
|
|
6
6
|
let storage: MemoryStorageAdapter;
|
|
7
7
|
|
|
8
8
|
beforeEach(() => {
|
|
9
9
|
storage = new MemoryStorageAdapter();
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
describe(
|
|
13
|
-
it(
|
|
12
|
+
describe('createRun / getRun', () => {
|
|
13
|
+
it('should create and retrieve a run', async () => {
|
|
14
14
|
const run = await storage.createRun({
|
|
15
|
-
kind:
|
|
16
|
-
status:
|
|
15
|
+
kind: 'test.workflow',
|
|
16
|
+
status: 'queued',
|
|
17
17
|
input: { x: 1 },
|
|
18
|
-
metadata: { userId:
|
|
18
|
+
metadata: { userId: 'u1' },
|
|
19
19
|
context: {},
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
expect(run.id).toBeDefined();
|
|
23
|
-
expect(run.kind).toBe(
|
|
23
|
+
expect(run.kind).toBe('test.workflow');
|
|
24
24
|
expect(run.createdAt).toBeInstanceOf(Date);
|
|
25
25
|
|
|
26
26
|
const fetched = await storage.getRun(run.id);
|
|
27
27
|
expect(fetched).toEqual(run);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
it(
|
|
31
|
-
const run = await storage.getRun(
|
|
30
|
+
it('should return null for non-existent run', async () => {
|
|
31
|
+
const run = await storage.getRun('nonexistent');
|
|
32
32
|
expect(run).toBeNull();
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
describe(
|
|
37
|
-
it(
|
|
36
|
+
describe('updateRun', () => {
|
|
37
|
+
it('should update run fields', async () => {
|
|
38
38
|
const run = await storage.createRun({
|
|
39
|
-
kind:
|
|
40
|
-
status:
|
|
39
|
+
kind: 'test',
|
|
40
|
+
status: 'queued',
|
|
41
41
|
input: {},
|
|
42
42
|
metadata: {},
|
|
43
43
|
context: {},
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
await storage.updateRun(run.id, {
|
|
47
|
-
status:
|
|
47
|
+
status: 'running',
|
|
48
48
|
startedAt: new Date(),
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
const updated = await storage.getRun(run.id);
|
|
52
|
-
expect(updated?.status).toBe(
|
|
52
|
+
expect(updated?.status).toBe('running');
|
|
53
53
|
expect(updated?.startedAt).toBeInstanceOf(Date);
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it(
|
|
56
|
+
it('should do nothing for non-existent run', async () => {
|
|
57
57
|
await expect(
|
|
58
|
-
storage.updateRun(
|
|
58
|
+
storage.updateRun('nonexistent', { status: 'failed' })
|
|
59
59
|
).resolves.not.toThrow();
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
describe(
|
|
63
|
+
describe('listRuns', () => {
|
|
64
64
|
beforeEach(async () => {
|
|
65
|
-
await storage.createRun({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
metadata: {},
|
|
70
|
-
context: {},
|
|
71
|
-
});
|
|
72
|
-
await storage.createRun({
|
|
73
|
-
kind: "a",
|
|
74
|
-
status: "running",
|
|
75
|
-
input: {},
|
|
76
|
-
metadata: {},
|
|
77
|
-
context: {},
|
|
78
|
-
});
|
|
79
|
-
await storage.createRun({
|
|
80
|
-
kind: "b",
|
|
81
|
-
status: "succeeded",
|
|
82
|
-
input: {},
|
|
83
|
-
metadata: {},
|
|
84
|
-
context: {},
|
|
85
|
-
});
|
|
86
|
-
await storage.createRun({
|
|
87
|
-
kind: "a",
|
|
88
|
-
status: "failed",
|
|
89
|
-
input: {},
|
|
90
|
-
metadata: {},
|
|
91
|
-
context: {},
|
|
92
|
-
parentRunId: "parent-1",
|
|
93
|
-
});
|
|
65
|
+
await storage.createRun({ kind: 'a', status: 'queued', input: {}, metadata: {}, context: {} });
|
|
66
|
+
await storage.createRun({ kind: 'a', status: 'running', input: {}, metadata: {}, context: {} });
|
|
67
|
+
await storage.createRun({ kind: 'b', status: 'succeeded', input: {}, metadata: {}, context: {} });
|
|
68
|
+
await storage.createRun({ kind: 'a', status: 'failed', input: {}, metadata: {}, context: {}, parentRunId: 'parent-1' });
|
|
94
69
|
});
|
|
95
70
|
|
|
96
|
-
it(
|
|
71
|
+
it('should list all runs with default pagination', async () => {
|
|
97
72
|
const result = await storage.listRuns();
|
|
98
73
|
expect(result.items).toHaveLength(4);
|
|
99
74
|
expect(result.total).toBe(4);
|
|
100
75
|
});
|
|
101
76
|
|
|
102
|
-
it(
|
|
103
|
-
const result = await storage.listRuns({ kind:
|
|
77
|
+
it('should filter by kind', async () => {
|
|
78
|
+
const result = await storage.listRuns({ kind: 'a' });
|
|
104
79
|
expect(result.items).toHaveLength(3);
|
|
105
|
-
expect(result.items.every(
|
|
80
|
+
expect(result.items.every(r => r.kind === 'a')).toBe(true);
|
|
106
81
|
});
|
|
107
82
|
|
|
108
|
-
it(
|
|
109
|
-
const result = await storage.listRuns({ status:
|
|
83
|
+
it('should filter by single status', async () => {
|
|
84
|
+
const result = await storage.listRuns({ status: 'queued' });
|
|
110
85
|
expect(result.items).toHaveLength(1);
|
|
111
|
-
expect(result.items[0].status).toBe(
|
|
86
|
+
expect(result.items[0].status).toBe('queued');
|
|
112
87
|
});
|
|
113
88
|
|
|
114
|
-
it(
|
|
115
|
-
const result = await storage.listRuns({ status: [
|
|
89
|
+
it('should filter by multiple statuses', async () => {
|
|
90
|
+
const result = await storage.listRuns({ status: ['queued', 'running'] });
|
|
116
91
|
expect(result.items).toHaveLength(2);
|
|
117
92
|
});
|
|
118
93
|
|
|
119
|
-
it(
|
|
120
|
-
const result = await storage.listRuns({ parentRunId:
|
|
94
|
+
it('should filter by parentRunId', async () => {
|
|
95
|
+
const result = await storage.listRuns({ parentRunId: 'parent-1' });
|
|
121
96
|
expect(result.items).toHaveLength(1);
|
|
122
|
-
expect(result.items[0].parentRunId).toBe(
|
|
97
|
+
expect(result.items[0].parentRunId).toBe('parent-1');
|
|
123
98
|
});
|
|
124
99
|
|
|
125
|
-
it(
|
|
100
|
+
it('should apply limit and offset', async () => {
|
|
126
101
|
const result = await storage.listRuns({ limit: 2, offset: 1 });
|
|
127
102
|
expect(result.items).toHaveLength(2);
|
|
128
103
|
expect(result.total).toBe(4);
|
|
@@ -130,196 +105,188 @@ describe("MemoryStorageAdapter", () => {
|
|
|
130
105
|
expect(result.offset).toBe(1);
|
|
131
106
|
});
|
|
132
107
|
|
|
133
|
-
it(
|
|
134
|
-
const result = await storage.listRuns({
|
|
135
|
-
|
|
136
|
-
orderDirection: "asc",
|
|
137
|
-
});
|
|
138
|
-
const times = result.items.map((r) => r.createdAt.getTime());
|
|
108
|
+
it('should sort by createdAt ascending', async () => {
|
|
109
|
+
const result = await storage.listRuns({ orderBy: 'createdAt', orderDirection: 'asc' });
|
|
110
|
+
const times = result.items.map(r => r.createdAt.getTime());
|
|
139
111
|
for (let i = 1; i < times.length; i++) {
|
|
140
112
|
expect(times[i]).toBeGreaterThanOrEqual(times[i - 1]);
|
|
141
113
|
}
|
|
142
114
|
});
|
|
143
115
|
});
|
|
144
116
|
|
|
145
|
-
describe(
|
|
146
|
-
it(
|
|
117
|
+
describe('createStep / getStep / updateStep', () => {
|
|
118
|
+
it('should create and retrieve a step', async () => {
|
|
147
119
|
const step = await storage.createStep({
|
|
148
|
-
runId:
|
|
149
|
-
stepKey:
|
|
150
|
-
stepName:
|
|
151
|
-
status:
|
|
120
|
+
runId: 'run-1',
|
|
121
|
+
stepKey: 'step1',
|
|
122
|
+
stepName: 'Step 1',
|
|
123
|
+
status: 'running',
|
|
152
124
|
attempt: 1,
|
|
153
125
|
startedAt: new Date(),
|
|
154
126
|
});
|
|
155
127
|
|
|
156
128
|
expect(step.id).toBeDefined();
|
|
157
|
-
expect(step.stepKey).toBe(
|
|
129
|
+
expect(step.stepKey).toBe('step1');
|
|
158
130
|
|
|
159
131
|
const fetched = await storage.getStep(step.id);
|
|
160
132
|
expect(fetched).toEqual(step);
|
|
161
133
|
});
|
|
162
134
|
|
|
163
|
-
it(
|
|
164
|
-
const step = await storage.getStep(
|
|
135
|
+
it('should return null for non-existent step', async () => {
|
|
136
|
+
const step = await storage.getStep('nonexistent');
|
|
165
137
|
expect(step).toBeNull();
|
|
166
138
|
});
|
|
167
139
|
|
|
168
|
-
it(
|
|
140
|
+
it('should update step fields', async () => {
|
|
169
141
|
const step = await storage.createStep({
|
|
170
|
-
runId:
|
|
171
|
-
stepKey:
|
|
172
|
-
stepName:
|
|
173
|
-
status:
|
|
142
|
+
runId: 'run-1',
|
|
143
|
+
stepKey: 'step1',
|
|
144
|
+
stepName: 'Step 1',
|
|
145
|
+
status: 'running',
|
|
174
146
|
attempt: 1,
|
|
175
147
|
});
|
|
176
148
|
|
|
177
149
|
await storage.updateStep(step.id, {
|
|
178
|
-
status:
|
|
179
|
-
result: { output:
|
|
150
|
+
status: 'succeeded',
|
|
151
|
+
result: { output: 'done' },
|
|
180
152
|
finishedAt: new Date(),
|
|
181
153
|
});
|
|
182
154
|
|
|
183
155
|
const updated = await storage.getStep(step.id);
|
|
184
|
-
expect(updated?.status).toBe(
|
|
185
|
-
expect(updated?.result).toEqual({ output:
|
|
156
|
+
expect(updated?.status).toBe('succeeded');
|
|
157
|
+
expect(updated?.result).toEqual({ output: 'done' });
|
|
186
158
|
});
|
|
187
159
|
|
|
188
|
-
it(
|
|
160
|
+
it('should do nothing when updating non-existent step', async () => {
|
|
189
161
|
await expect(
|
|
190
|
-
storage.updateStep(
|
|
162
|
+
storage.updateStep('nonexistent', { status: 'failed' })
|
|
191
163
|
).resolves.not.toThrow();
|
|
192
164
|
});
|
|
193
165
|
});
|
|
194
166
|
|
|
195
|
-
describe(
|
|
196
|
-
it(
|
|
167
|
+
describe('getStepsForRun', () => {
|
|
168
|
+
it('should return steps for a specific run sorted by startedAt', async () => {
|
|
197
169
|
const now = Date.now();
|
|
198
170
|
await storage.createStep({
|
|
199
|
-
runId:
|
|
200
|
-
stepKey:
|
|
201
|
-
stepName:
|
|
202
|
-
status:
|
|
171
|
+
runId: 'run-1',
|
|
172
|
+
stepKey: 'b',
|
|
173
|
+
stepName: 'B',
|
|
174
|
+
status: 'succeeded',
|
|
203
175
|
attempt: 1,
|
|
204
176
|
startedAt: new Date(now + 100),
|
|
205
177
|
});
|
|
206
178
|
await storage.createStep({
|
|
207
|
-
runId:
|
|
208
|
-
stepKey:
|
|
209
|
-
stepName:
|
|
210
|
-
status:
|
|
179
|
+
runId: 'run-1',
|
|
180
|
+
stepKey: 'a',
|
|
181
|
+
stepName: 'A',
|
|
182
|
+
status: 'succeeded',
|
|
211
183
|
attempt: 1,
|
|
212
184
|
startedAt: new Date(now),
|
|
213
185
|
});
|
|
214
186
|
await storage.createStep({
|
|
215
|
-
runId:
|
|
216
|
-
stepKey:
|
|
217
|
-
stepName:
|
|
218
|
-
status:
|
|
187
|
+
runId: 'run-2',
|
|
188
|
+
stepKey: 'c',
|
|
189
|
+
stepName: 'C',
|
|
190
|
+
status: 'running',
|
|
219
191
|
attempt: 1,
|
|
220
192
|
});
|
|
221
193
|
|
|
222
|
-
const steps = await storage.getStepsForRun(
|
|
194
|
+
const steps = await storage.getStepsForRun('run-1');
|
|
223
195
|
expect(steps).toHaveLength(2);
|
|
224
|
-
expect(steps[0].stepKey).toBe(
|
|
225
|
-
expect(steps[1].stepKey).toBe(
|
|
196
|
+
expect(steps[0].stepKey).toBe('a');
|
|
197
|
+
expect(steps[1].stepKey).toBe('b');
|
|
226
198
|
});
|
|
227
199
|
});
|
|
228
200
|
|
|
229
|
-
describe(
|
|
230
|
-
it(
|
|
201
|
+
describe('saveEvent / getEventsForRun', () => {
|
|
202
|
+
it('should save and retrieve events', async () => {
|
|
231
203
|
await storage.saveEvent({
|
|
232
|
-
runId:
|
|
233
|
-
eventType:
|
|
234
|
-
level:
|
|
204
|
+
runId: 'run-1',
|
|
205
|
+
eventType: 'step.started',
|
|
206
|
+
level: 'info',
|
|
235
207
|
timestamp: new Date(),
|
|
236
208
|
});
|
|
237
209
|
await storage.saveEvent({
|
|
238
|
-
runId:
|
|
239
|
-
stepKey:
|
|
240
|
-
eventType:
|
|
241
|
-
level:
|
|
210
|
+
runId: 'run-1',
|
|
211
|
+
stepKey: 'step1',
|
|
212
|
+
eventType: 'step.completed',
|
|
213
|
+
level: 'info',
|
|
242
214
|
timestamp: new Date(),
|
|
243
215
|
});
|
|
244
216
|
await storage.saveEvent({
|
|
245
|
-
runId:
|
|
246
|
-
eventType:
|
|
247
|
-
level:
|
|
217
|
+
runId: 'run-2',
|
|
218
|
+
eventType: 'run.started',
|
|
219
|
+
level: 'info',
|
|
248
220
|
timestamp: new Date(),
|
|
249
221
|
});
|
|
250
222
|
|
|
251
|
-
const events = await storage.getEventsForRun(
|
|
223
|
+
const events = await storage.getEventsForRun('run-1');
|
|
252
224
|
expect(events).toHaveLength(2);
|
|
253
225
|
});
|
|
254
226
|
|
|
255
|
-
it(
|
|
227
|
+
it('should filter events by stepKey', async () => {
|
|
256
228
|
await storage.saveEvent({
|
|
257
|
-
runId:
|
|
258
|
-
eventType:
|
|
259
|
-
level:
|
|
229
|
+
runId: 'run-1',
|
|
230
|
+
eventType: 'run.started',
|
|
231
|
+
level: 'info',
|
|
260
232
|
timestamp: new Date(),
|
|
261
233
|
});
|
|
262
234
|
await storage.saveEvent({
|
|
263
|
-
runId:
|
|
264
|
-
stepKey:
|
|
265
|
-
eventType:
|
|
266
|
-
level:
|
|
235
|
+
runId: 'run-1',
|
|
236
|
+
stepKey: 'step1',
|
|
237
|
+
eventType: 'step.started',
|
|
238
|
+
level: 'info',
|
|
267
239
|
timestamp: new Date(),
|
|
268
240
|
});
|
|
269
241
|
|
|
270
|
-
const events = await storage.getEventsForRun(
|
|
271
|
-
stepKey: "step1",
|
|
272
|
-
});
|
|
242
|
+
const events = await storage.getEventsForRun('run-1', { stepKey: 'step1' });
|
|
273
243
|
expect(events).toHaveLength(1);
|
|
274
|
-
expect(events[0].stepKey).toBe(
|
|
244
|
+
expect(events[0].stepKey).toBe('step1');
|
|
275
245
|
});
|
|
276
246
|
|
|
277
|
-
it(
|
|
247
|
+
it('should filter events by level', async () => {
|
|
278
248
|
await storage.saveEvent({
|
|
279
|
-
runId:
|
|
280
|
-
eventType:
|
|
281
|
-
level:
|
|
249
|
+
runId: 'run-1',
|
|
250
|
+
eventType: 'info-event',
|
|
251
|
+
level: 'info',
|
|
282
252
|
timestamp: new Date(),
|
|
283
253
|
});
|
|
284
254
|
await storage.saveEvent({
|
|
285
|
-
runId:
|
|
286
|
-
eventType:
|
|
287
|
-
level:
|
|
255
|
+
runId: 'run-1',
|
|
256
|
+
eventType: 'error-event',
|
|
257
|
+
level: 'error',
|
|
288
258
|
timestamp: new Date(),
|
|
289
259
|
});
|
|
290
260
|
|
|
291
|
-
const events = await storage.getEventsForRun(
|
|
261
|
+
const events = await storage.getEventsForRun('run-1', { level: 'error' });
|
|
292
262
|
expect(events).toHaveLength(1);
|
|
293
|
-
expect(events[0].level).toBe(
|
|
263
|
+
expect(events[0].level).toBe('error');
|
|
294
264
|
});
|
|
295
265
|
|
|
296
|
-
it(
|
|
266
|
+
it('should paginate events', async () => {
|
|
297
267
|
for (let i = 0; i < 5; i++) {
|
|
298
268
|
await storage.saveEvent({
|
|
299
|
-
runId:
|
|
269
|
+
runId: 'run-1',
|
|
300
270
|
eventType: `event-${i}`,
|
|
301
|
-
level:
|
|
271
|
+
level: 'info',
|
|
302
272
|
timestamp: new Date(Date.now() + i),
|
|
303
273
|
});
|
|
304
274
|
}
|
|
305
275
|
|
|
306
|
-
const events = await storage.getEventsForRun(
|
|
307
|
-
limit: 2,
|
|
308
|
-
offset: 1,
|
|
309
|
-
});
|
|
276
|
+
const events = await storage.getEventsForRun('run-1', { limit: 2, offset: 1 });
|
|
310
277
|
expect(events).toHaveLength(2);
|
|
311
278
|
});
|
|
312
279
|
});
|
|
313
280
|
|
|
314
|
-
describe(
|
|
315
|
-
it(
|
|
316
|
-
const oldDate = new Date(
|
|
317
|
-
const newDate = new Date(
|
|
281
|
+
describe('deleteOldRuns', () => {
|
|
282
|
+
it('should delete runs older than the cutoff date', async () => {
|
|
283
|
+
const oldDate = new Date('2020-01-01');
|
|
284
|
+
const newDate = new Date('2025-01-01');
|
|
318
285
|
|
|
319
286
|
// Create an old run with steps and events
|
|
320
287
|
const oldRun = await storage.createRun({
|
|
321
|
-
kind:
|
|
322
|
-
status:
|
|
288
|
+
kind: 'test',
|
|
289
|
+
status: 'succeeded',
|
|
323
290
|
input: {},
|
|
324
291
|
metadata: {},
|
|
325
292
|
context: {},
|
|
@@ -329,28 +296,28 @@ describe("MemoryStorageAdapter", () => {
|
|
|
329
296
|
|
|
330
297
|
await storage.createStep({
|
|
331
298
|
runId: oldRun.id,
|
|
332
|
-
stepKey:
|
|
333
|
-
stepName:
|
|
334
|
-
status:
|
|
299
|
+
stepKey: 's1',
|
|
300
|
+
stepName: 'S1',
|
|
301
|
+
status: 'succeeded',
|
|
335
302
|
attempt: 1,
|
|
336
303
|
});
|
|
337
304
|
await storage.saveEvent({
|
|
338
305
|
runId: oldRun.id,
|
|
339
|
-
eventType:
|
|
340
|
-
level:
|
|
306
|
+
eventType: 'run.completed',
|
|
307
|
+
level: 'info',
|
|
341
308
|
timestamp: new Date(),
|
|
342
309
|
});
|
|
343
310
|
|
|
344
311
|
// Create a recent run
|
|
345
312
|
const newRun = await storage.createRun({
|
|
346
|
-
kind:
|
|
347
|
-
status:
|
|
313
|
+
kind: 'test',
|
|
314
|
+
status: 'succeeded',
|
|
348
315
|
input: {},
|
|
349
316
|
metadata: {},
|
|
350
317
|
context: {},
|
|
351
318
|
});
|
|
352
319
|
|
|
353
|
-
const deleted = await storage.deleteOldRuns(new Date(
|
|
320
|
+
const deleted = await storage.deleteOldRuns(new Date('2024-01-01'));
|
|
354
321
|
|
|
355
322
|
expect(deleted).toBe(1);
|
|
356
323
|
expect(await storage.getRun(oldRun.id)).toBeNull();
|
|
@@ -364,28 +331,11 @@ describe("MemoryStorageAdapter", () => {
|
|
|
364
331
|
});
|
|
365
332
|
});
|
|
366
333
|
|
|
367
|
-
describe(
|
|
368
|
-
it(
|
|
369
|
-
await storage.createRun({
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
input: {},
|
|
373
|
-
metadata: {},
|
|
374
|
-
context: {},
|
|
375
|
-
});
|
|
376
|
-
await storage.createStep({
|
|
377
|
-
runId: "r",
|
|
378
|
-
stepKey: "s",
|
|
379
|
-
stepName: "S",
|
|
380
|
-
status: "running",
|
|
381
|
-
attempt: 1,
|
|
382
|
-
});
|
|
383
|
-
await storage.saveEvent({
|
|
384
|
-
runId: "r",
|
|
385
|
-
eventType: "e",
|
|
386
|
-
level: "info",
|
|
387
|
-
timestamp: new Date(),
|
|
388
|
-
});
|
|
334
|
+
describe('clear / getStats', () => {
|
|
335
|
+
it('should clear all data', async () => {
|
|
336
|
+
await storage.createRun({ kind: 'a', status: 'queued', input: {}, metadata: {}, context: {} });
|
|
337
|
+
await storage.createStep({ runId: 'r', stepKey: 's', stepName: 'S', status: 'running', attempt: 1 });
|
|
338
|
+
await storage.saveEvent({ runId: 'r', eventType: 'e', level: 'info', timestamp: new Date() });
|
|
389
339
|
|
|
390
340
|
expect(storage.getStats().runs).toBe(1);
|
|
391
341
|
expect(storage.getStats().steps).toBe(1);
|