@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.
Files changed (84) hide show
  1. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
  2. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
  3. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
  4. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
  5. package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
  6. package/.github/CODEOWNERS +15 -0
  7. package/.github/dependabot.yml +23 -0
  8. package/.github/workflows/ci.yml +38 -0
  9. package/.github/workflows/publish.yml +11 -5
  10. package/.github/workflows/security.yml +29 -0
  11. package/CHANGELOG.md +34 -0
  12. package/README.md +24 -0
  13. package/SECURITY.md +40 -0
  14. package/dist/{chunk-FQCT3I2A.js → chunk-4VOKMGP6.js} +23 -42
  15. package/dist/chunk-4VOKMGP6.js.map +1 -0
  16. package/dist/{chunk-XCF4PLN7.js → chunk-GYPNJBDP.js} +12 -16
  17. package/dist/chunk-GYPNJBDP.js.map +1 -0
  18. package/dist/{chunk-U5QGN3RT.js → chunk-KHTUWLVZ.js} +14 -47
  19. package/dist/chunk-KHTUWLVZ.js.map +1 -0
  20. package/dist/{chunk-GXY66V7Q.js → chunk-ML35PIHX.js} +1 -1
  21. package/dist/chunk-ML35PIHX.js.map +1 -0
  22. package/dist/events/index.d.ts +4 -4
  23. package/dist/events/index.js +1 -1
  24. package/dist/{index-CPfGsmrp.d.ts → index-Dzr437iW.d.ts} +6 -6
  25. package/dist/index.d.ts +14 -12
  26. package/dist/index.js +40 -82
  27. package/dist/index.js.map +1 -1
  28. package/dist/scheduler/index.d.ts +4 -4
  29. package/dist/scheduler/index.js +2 -2
  30. package/dist/storage/index.d.ts +13 -13
  31. package/dist/storage/index.js +2 -2
  32. package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
  33. package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
  34. package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
  35. package/docs/stepflow-api-reference.md +2 -3
  36. package/package.json +7 -6
  37. package/src/core/engine.test.ts +466 -522
  38. package/src/core/engine.ts +42 -67
  39. package/src/core/index.ts +3 -3
  40. package/src/core/orchestrator.ts +64 -83
  41. package/src/core/types.ts +7 -29
  42. package/src/events/index.ts +4 -4
  43. package/src/events/memory.test.ts +39 -41
  44. package/src/events/memory.ts +7 -10
  45. package/src/events/socketio.test.ts +112 -162
  46. package/src/events/socketio.ts +20 -27
  47. package/src/events/types.ts +16 -19
  48. package/src/events/webhook.test.ts +197 -235
  49. package/src/events/webhook.ts +44 -71
  50. package/src/index.ts +19 -19
  51. package/src/planning/index.ts +6 -3
  52. package/src/planning/planner.ts +66 -91
  53. package/src/planning/planning.test.ts +303 -366
  54. package/src/planning/registry.test.ts +204 -251
  55. package/src/planning/registry.ts +39 -43
  56. package/src/planning/types.ts +23 -27
  57. package/src/scheduler/cron.test.ts +141 -150
  58. package/src/scheduler/cron.ts +32 -67
  59. package/src/scheduler/index.ts +4 -14
  60. package/src/scheduler/postgres-persistence.ts +71 -99
  61. package/src/scheduler/sqlite-persistence.test.ts +96 -96
  62. package/src/scheduler/sqlite-persistence.ts +12 -19
  63. package/src/scheduler/types.ts +4 -9
  64. package/src/storage/index.ts +5 -5
  65. package/src/storage/memory.test.ts +133 -183
  66. package/src/storage/memory.ts +21 -42
  67. package/src/storage/postgres.ts +197 -393
  68. package/src/storage/sqlite.test.ts +154 -281
  69. package/src/storage/sqlite.ts +50 -85
  70. package/src/storage/types.ts +33 -58
  71. package/src/utils/errors.test.ts +78 -80
  72. package/src/utils/errors.ts +27 -57
  73. package/src/utils/id.ts +3 -3
  74. package/src/utils/index.ts +4 -4
  75. package/src/utils/logger.test.ts +52 -58
  76. package/src/utils/logger.ts +4 -4
  77. package/src/utils/postgres-deps.ts +4 -4
  78. package/src/utils/retry.test.ts +47 -67
  79. package/src/utils/retry.ts +9 -9
  80. package/dist/chunk-FQCT3I2A.js.map +0 -1
  81. package/dist/chunk-GXY66V7Q.js.map +0 -1
  82. package/dist/chunk-U5QGN3RT.js.map +0 -1
  83. package/dist/chunk-XCF4PLN7.js.map +0 -1
  84. package/src/scheduler/postgres-persistence.test.ts +0 -906
@@ -1,16 +1,12 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import {
3
- CronScheduler,
4
- type CronSchedulerConfig,
5
- type SchedulePersistence,
6
- } from "./cron";
7
- import { WorkflowEngine } from "../core/engine";
8
- import { MemoryStorageAdapter } from "../storage/memory";
9
- import { MemoryEventTransport } from "../events/memory";
10
- import { SilentLogger } from "../utils/logger";
11
- import type { WorkflowSchedule } from "./types";
12
-
13
- describe("CronScheduler", () => {
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { CronScheduler, type CronSchedulerConfig, type SchedulePersistence } from './cron';
3
+ import { WorkflowEngine } from '../core/engine';
4
+ import { MemoryStorageAdapter } from '../storage/memory';
5
+ import { MemoryEventTransport } from '../events/memory';
6
+ import { SilentLogger } from '../utils/logger';
7
+ import type { WorkflowSchedule } from './types';
8
+
9
+ describe('CronScheduler', () => {
14
10
  let engine: WorkflowEngine;
15
11
  let scheduler: CronScheduler;
16
12
 
@@ -24,13 +20,13 @@ describe("CronScheduler", () => {
24
20
 
25
21
  // Register a test workflow
26
22
  engine.registerWorkflow({
27
- kind: "test.workflow",
28
- name: "Test Workflow",
23
+ kind: 'test.workflow',
24
+ name: 'Test Workflow',
29
25
  steps: [
30
26
  {
31
- key: "step1",
32
- name: "Step 1",
33
- handler: async (ctx) => ({ value: ctx.input.value ?? "default" }),
27
+ key: 'step1',
28
+ name: 'Step 1',
29
+ handler: async (ctx) => ({ value: ctx.input.value ?? 'default' }),
34
30
  },
35
31
  ],
36
32
  });
@@ -48,64 +44,64 @@ describe("CronScheduler", () => {
48
44
  await engine.shutdown();
49
45
  });
50
46
 
51
- describe("addSchedule", () => {
52
- it("should add a cron schedule", async () => {
47
+ describe('addSchedule', () => {
48
+ it('should add a cron schedule', async () => {
53
49
  const schedule = await scheduler.addSchedule({
54
- workflowKind: "test.workflow",
55
- triggerType: "cron",
56
- cronExpression: "0 * * * *", // Every hour
50
+ workflowKind: 'test.workflow',
51
+ triggerType: 'cron',
52
+ cronExpression: '0 * * * *', // Every hour
57
53
  enabled: true,
58
54
  });
59
55
 
60
56
  expect(schedule.id).toBeDefined();
61
- expect(schedule.workflowKind).toBe("test.workflow");
62
- expect(schedule.triggerType).toBe("cron");
63
- expect(schedule.cronExpression).toBe("0 * * * *");
57
+ expect(schedule.workflowKind).toBe('test.workflow');
58
+ expect(schedule.triggerType).toBe('cron');
59
+ expect(schedule.cronExpression).toBe('0 * * * *');
64
60
  expect(schedule.nextRunAt).toBeInstanceOf(Date);
65
61
  });
66
62
 
67
- it("should add a workflow completion trigger", async () => {
63
+ it('should add a workflow completion trigger', async () => {
68
64
  const schedule = await scheduler.addSchedule({
69
- workflowKind: "notification.send",
70
- triggerType: "workflow_completed",
71
- triggerOnWorkflowKind: "order.process",
72
- triggerOnStatus: ["succeeded"],
65
+ workflowKind: 'notification.send',
66
+ triggerType: 'workflow_completed',
67
+ triggerOnWorkflowKind: 'order.process',
68
+ triggerOnStatus: ['succeeded'],
73
69
  enabled: true,
74
70
  });
75
71
 
76
72
  expect(schedule.id).toBeDefined();
77
- expect(schedule.triggerType).toBe("workflow_completed");
78
- expect(schedule.triggerOnWorkflowKind).toBe("order.process");
73
+ expect(schedule.triggerType).toBe('workflow_completed');
74
+ expect(schedule.triggerOnWorkflowKind).toBe('order.process');
79
75
  });
80
76
 
81
- it("should reject invalid cron expression", async () => {
77
+ it('should reject invalid cron expression', async () => {
82
78
  await expect(
83
79
  scheduler.addSchedule({
84
- workflowKind: "test.workflow",
85
- triggerType: "cron",
86
- cronExpression: "invalid cron",
80
+ workflowKind: 'test.workflow',
81
+ triggerType: 'cron',
82
+ cronExpression: 'invalid cron',
87
83
  enabled: true,
88
- }),
89
- ).rejects.toThrow("Invalid cron expression");
84
+ })
85
+ ).rejects.toThrow('Invalid cron expression');
90
86
  });
91
87
 
92
- it("should require triggerOnWorkflowKind for workflow_completed triggers", async () => {
88
+ it('should require triggerOnWorkflowKind for workflow_completed triggers', async () => {
93
89
  await expect(
94
90
  scheduler.addSchedule({
95
- workflowKind: "test.workflow",
96
- triggerType: "workflow_completed",
91
+ workflowKind: 'test.workflow',
92
+ triggerType: 'workflow_completed',
97
93
  enabled: true,
98
- }),
99
- ).rejects.toThrow("triggerOnWorkflowKind is required");
94
+ })
95
+ ).rejects.toThrow('triggerOnWorkflowKind is required');
100
96
  });
101
97
  });
102
98
 
103
- describe("removeSchedule", () => {
104
- it("should remove a schedule", async () => {
99
+ describe('removeSchedule', () => {
100
+ it('should remove a schedule', async () => {
105
101
  const schedule = await scheduler.addSchedule({
106
- workflowKind: "test.workflow",
107
- triggerType: "cron",
108
- cronExpression: "0 * * * *",
102
+ workflowKind: 'test.workflow',
103
+ triggerType: 'cron',
104
+ cronExpression: '0 * * * *',
109
105
  enabled: true,
110
106
  });
111
107
 
@@ -115,19 +111,19 @@ describe("CronScheduler", () => {
115
111
  expect(schedules).toHaveLength(0);
116
112
  });
117
113
 
118
- it("should throw for non-existent schedule", async () => {
119
- await expect(scheduler.removeSchedule("nonexistent")).rejects.toThrow(
120
- "Schedule not found",
114
+ it('should throw for non-existent schedule', async () => {
115
+ await expect(scheduler.removeSchedule('nonexistent')).rejects.toThrow(
116
+ 'Schedule not found'
121
117
  );
122
118
  });
123
119
  });
124
120
 
125
- describe("updateSchedule", () => {
126
- it("should update schedule properties", async () => {
121
+ describe('updateSchedule', () => {
122
+ it('should update schedule properties', async () => {
127
123
  const schedule = await scheduler.addSchedule({
128
- workflowKind: "test.workflow",
129
- triggerType: "cron",
130
- cronExpression: "0 * * * *",
124
+ workflowKind: 'test.workflow',
125
+ triggerType: 'cron',
126
+ cronExpression: '0 * * * *',
131
127
  enabled: true,
132
128
  });
133
129
 
@@ -137,38 +133,38 @@ describe("CronScheduler", () => {
137
133
  expect(updated?.enabled).toBe(false);
138
134
  });
139
135
 
140
- it("should update cron expression and recalculate next run", async () => {
136
+ it('should update cron expression and recalculate next run', async () => {
141
137
  const schedule = await scheduler.addSchedule({
142
- workflowKind: "test.workflow",
143
- triggerType: "cron",
144
- cronExpression: "0 * * * *",
138
+ workflowKind: 'test.workflow',
139
+ triggerType: 'cron',
140
+ cronExpression: '0 * * * *',
145
141
  enabled: true,
146
142
  });
147
143
 
148
144
  const originalNextRun = schedule.nextRunAt;
149
145
 
150
146
  await scheduler.updateSchedule(schedule.id, {
151
- cronExpression: "30 * * * *",
147
+ cronExpression: '30 * * * *',
152
148
  });
153
149
 
154
150
  const updated = scheduler.getSchedule(schedule.id);
155
- expect(updated?.cronExpression).toBe("30 * * * *");
151
+ expect(updated?.cronExpression).toBe('30 * * * *');
156
152
  expect(updated?.nextRunAt).not.toEqual(originalNextRun);
157
153
  });
158
154
  });
159
155
 
160
- describe("getSchedules", () => {
161
- it("should return all schedules", async () => {
156
+ describe('getSchedules', () => {
157
+ it('should return all schedules', async () => {
162
158
  await scheduler.addSchedule({
163
- workflowKind: "test.workflow",
164
- triggerType: "cron",
165
- cronExpression: "0 * * * *",
159
+ workflowKind: 'test.workflow',
160
+ triggerType: 'cron',
161
+ cronExpression: '0 * * * *',
166
162
  enabled: true,
167
163
  });
168
164
 
169
165
  await scheduler.addSchedule({
170
- workflowKind: "test.workflow",
171
- triggerType: "manual",
166
+ workflowKind: 'test.workflow',
167
+ triggerType: 'manual',
172
168
  enabled: true,
173
169
  });
174
170
 
@@ -177,13 +173,13 @@ describe("CronScheduler", () => {
177
173
  });
178
174
  });
179
175
 
180
- describe("triggerNow", () => {
181
- it("should manually trigger a schedule", async () => {
176
+ describe('triggerNow', () => {
177
+ it('should manually trigger a schedule', async () => {
182
178
  const schedule = await scheduler.addSchedule({
183
- workflowKind: "test.workflow",
184
- triggerType: "cron",
185
- cronExpression: "0 0 1 1 *", // Once a year (won't trigger naturally)
186
- input: { value: "manual" },
179
+ workflowKind: 'test.workflow',
180
+ triggerType: 'cron',
181
+ cronExpression: '0 0 1 1 *', // Once a year (won't trigger naturally)
182
+ input: { value: 'manual' },
187
183
  enabled: true,
188
184
  });
189
185
 
@@ -193,27 +189,27 @@ describe("CronScheduler", () => {
193
189
 
194
190
  // Wait for workflow to complete
195
191
  const run = await engine.waitForRun(runId);
196
- expect(run.status).toBe("succeeded");
192
+ expect(run.status).toBe('succeeded');
197
193
  });
198
194
 
199
- it("should throw for non-existent schedule", async () => {
200
- await expect(scheduler.triggerNow("nonexistent")).rejects.toThrow(
201
- "Schedule not found",
195
+ it('should throw for non-existent schedule', async () => {
196
+ await expect(scheduler.triggerNow('nonexistent')).rejects.toThrow(
197
+ 'Schedule not found'
202
198
  );
203
199
  });
204
200
  });
205
201
 
206
- describe("cron execution", () => {
207
- it("should execute scheduled workflow when cron time is reached", async () => {
202
+ describe('cron execution', () => {
203
+ it('should execute scheduled workflow when cron time is reached', async () => {
208
204
  // Use fake timers
209
205
  vi.useFakeTimers();
210
206
 
211
207
  // Create a schedule that runs every minute
212
208
  await scheduler.addSchedule({
213
- workflowKind: "test.workflow",
214
- triggerType: "cron",
215
- cronExpression: "* * * * *", // Every minute
216
- input: { value: "scheduled" },
209
+ workflowKind: 'test.workflow',
210
+ triggerType: 'cron',
211
+ cronExpression: '* * * * *', // Every minute
212
+ input: { value: 'scheduled' },
217
213
  enabled: true,
218
214
  });
219
215
 
@@ -234,16 +230,16 @@ describe("CronScheduler", () => {
234
230
  });
235
231
  });
236
232
 
237
- describe("workflow completion triggers", () => {
238
- it("should trigger workflow on completion of another workflow", async () => {
233
+ describe('workflow completion triggers', () => {
234
+ it('should trigger workflow on completion of another workflow', async () => {
239
235
  // Register a trigger workflow
240
236
  engine.registerWorkflow({
241
- kind: "trigger.workflow",
242
- name: "Trigger Workflow",
237
+ kind: 'trigger.workflow',
238
+ name: 'Trigger Workflow',
243
239
  steps: [
244
240
  {
245
- key: "trigger",
246
- name: "Trigger",
241
+ key: 'trigger',
242
+ name: 'Trigger',
247
243
  handler: async () => ({ triggered: true }),
248
244
  },
249
245
  ],
@@ -251,14 +247,14 @@ describe("CronScheduler", () => {
251
247
 
252
248
  // Register a dependent workflow
253
249
  engine.registerWorkflow({
254
- kind: "dependent.workflow",
255
- name: "Dependent Workflow",
250
+ kind: 'dependent.workflow',
251
+ name: 'Dependent Workflow',
256
252
  steps: [
257
253
  {
258
- key: "dependent",
259
- name: "Dependent",
254
+ key: 'dependent',
255
+ name: 'Dependent',
260
256
  handler: async (ctx) => ({
261
- parentRunId: ctx.metadata.triggerRunId,
257
+ parentRunId: ctx.metadata.triggerRunId
262
258
  }),
263
259
  },
264
260
  ],
@@ -266,42 +262,42 @@ describe("CronScheduler", () => {
266
262
 
267
263
  // Add a completion trigger
268
264
  await scheduler.addSchedule({
269
- workflowKind: "dependent.workflow",
270
- triggerType: "workflow_completed",
271
- triggerOnWorkflowKind: "trigger.workflow",
272
- triggerOnStatus: ["succeeded"],
265
+ workflowKind: 'dependent.workflow',
266
+ triggerType: 'workflow_completed',
267
+ triggerOnWorkflowKind: 'trigger.workflow',
268
+ triggerOnStatus: ['succeeded'],
273
269
  enabled: true,
274
270
  });
275
271
 
276
272
  await scheduler.start();
277
273
 
278
274
  // Start the trigger workflow
279
- const triggerRunId = await engine.startRun({ kind: "trigger.workflow" });
275
+ const triggerRunId = await engine.startRun({ kind: 'trigger.workflow' });
280
276
 
281
277
  // Wait for trigger workflow to complete
282
278
  await engine.waitForRun(triggerRunId);
283
279
 
284
280
  // Give time for the completion trigger to fire and dependent workflow to start
285
- await new Promise((resolve) => setTimeout(resolve, 200));
281
+ await new Promise(resolve => setTimeout(resolve, 200));
286
282
 
287
283
  // Check that the dependent workflow was started
288
284
  const storage = engine.getStorage();
289
- const { items } = await storage.listRuns({ kind: "dependent.workflow" });
285
+ const { items } = await storage.listRuns({ kind: 'dependent.workflow' });
290
286
 
291
287
  expect(items.length).toBeGreaterThanOrEqual(1);
292
288
  });
293
289
 
294
- it("should not trigger on non-matching status", async () => {
290
+ it('should not trigger on non-matching status', async () => {
295
291
  // Register a failing trigger workflow
296
292
  engine.registerWorkflow({
297
- kind: "failing.workflow",
298
- name: "Failing Workflow",
293
+ kind: 'failing.workflow',
294
+ name: 'Failing Workflow',
299
295
  steps: [
300
296
  {
301
- key: "fail",
302
- name: "Fail",
297
+ key: 'fail',
298
+ name: 'Fail',
303
299
  handler: async () => {
304
- throw new Error("Intentional failure");
300
+ throw new Error('Intentional failure');
305
301
  },
306
302
  },
307
303
  ],
@@ -309,56 +305,56 @@ describe("CronScheduler", () => {
309
305
 
310
306
  // Add a completion trigger for success only
311
307
  await scheduler.addSchedule({
312
- workflowKind: "test.workflow",
313
- triggerType: "workflow_completed",
314
- triggerOnWorkflowKind: "failing.workflow",
315
- triggerOnStatus: ["succeeded"], // Only on success
308
+ workflowKind: 'test.workflow',
309
+ triggerType: 'workflow_completed',
310
+ triggerOnWorkflowKind: 'failing.workflow',
311
+ triggerOnStatus: ['succeeded'], // Only on success
316
312
  enabled: true,
317
313
  });
318
314
 
319
315
  await scheduler.start();
320
316
 
321
317
  // Start the failing workflow
322
- const runId = await engine.startRun({ kind: "failing.workflow" });
318
+ const runId = await engine.startRun({ kind: 'failing.workflow' });
323
319
 
324
320
  // Wait for it to fail
325
321
  await engine.waitForRun(runId);
326
322
 
327
323
  // Give time for any triggers
328
- await new Promise((resolve) => setTimeout(resolve, 200));
324
+ await new Promise(resolve => setTimeout(resolve, 200));
329
325
 
330
326
  // Check that test.workflow was NOT started
331
327
  const storage = engine.getStorage();
332
- const { items } = await storage.listRuns({ kind: "test.workflow" });
328
+ const { items } = await storage.listRuns({ kind: 'test.workflow' });
333
329
 
334
330
  expect(items).toHaveLength(0);
335
331
  });
336
332
  });
337
333
 
338
- describe("start/stop", () => {
339
- it("should start and stop without error", async () => {
334
+ describe('start/stop', () => {
335
+ it('should start and stop without error', async () => {
340
336
  await scheduler.start();
341
337
  await scheduler.stop();
342
338
  });
343
339
 
344
- it("should warn when starting twice", async () => {
340
+ it('should warn when starting twice', async () => {
345
341
  await scheduler.start();
346
342
  await scheduler.start(); // Should just warn
347
343
  await scheduler.stop();
348
344
  });
349
345
 
350
- it("should handle stopping when not started", async () => {
346
+ it('should handle stopping when not started', async () => {
351
347
  await scheduler.stop(); // Should do nothing
352
348
  });
353
349
  });
354
350
 
355
- describe("persistence", () => {
356
- it("should load schedules from persistence on start", async () => {
351
+ describe('persistence', () => {
352
+ it('should load schedules from persistence on start', async () => {
357
353
  const mockSchedule: WorkflowSchedule = {
358
- id: "persisted-1",
359
- workflowKind: "test.workflow",
360
- triggerType: "cron",
361
- cronExpression: "0 * * * *",
354
+ id: 'persisted-1',
355
+ workflowKind: 'test.workflow',
356
+ triggerType: 'cron',
357
+ cronExpression: '0 * * * *',
362
358
  enabled: true,
363
359
  };
364
360
 
@@ -381,12 +377,12 @@ describe("CronScheduler", () => {
381
377
 
382
378
  const schedules = await schedulerWithPersistence.getSchedules();
383
379
  expect(schedules).toHaveLength(1);
384
- expect(schedules[0].id).toBe("persisted-1");
380
+ expect(schedules[0].id).toBe('persisted-1');
385
381
 
386
382
  await schedulerWithPersistence.stop();
387
383
  });
388
384
 
389
- it("should save schedule to persistence when added", async () => {
385
+ it('should save schedule to persistence when added', async () => {
390
386
  const mockPersistence: SchedulePersistence = {
391
387
  loadSchedules: vi.fn().mockResolvedValue([]),
392
388
  saveSchedule: vi.fn().mockResolvedValue(undefined),
@@ -401,9 +397,9 @@ describe("CronScheduler", () => {
401
397
  });
402
398
 
403
399
  await schedulerWithPersistence.addSchedule({
404
- workflowKind: "test.workflow",
405
- triggerType: "cron",
406
- cronExpression: "0 * * * *",
400
+ workflowKind: 'test.workflow',
401
+ triggerType: 'cron',
402
+ cronExpression: '0 * * * *',
407
403
  enabled: true,
408
404
  });
409
405
 
@@ -411,14 +407,9 @@ describe("CronScheduler", () => {
411
407
  });
412
408
  });
413
409
 
414
- describe("updateNextRunTime parse-error path", () => {
415
- it("should set nextRunAt to undefined and log error for malformed cron expression update", async () => {
416
- const logger = {
417
- debug: vi.fn(),
418
- info: vi.fn(),
419
- warn: vi.fn(),
420
- error: vi.fn(),
421
- };
410
+ describe('updateNextRunTime parse-error path', () => {
411
+ it('should set nextRunAt to undefined and log error for malformed cron expression update', async () => {
412
+ const logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn() };
422
413
  const errorScheduler = new CronScheduler({
423
414
  engine,
424
415
  logger,
@@ -427,9 +418,9 @@ describe("CronScheduler", () => {
427
418
 
428
419
  // Add a valid schedule first
429
420
  const schedule = await errorScheduler.addSchedule({
430
- workflowKind: "test.workflow",
431
- triggerType: "cron",
432
- cronExpression: "0 * * * *",
421
+ workflowKind: 'test.workflow',
422
+ triggerType: 'cron',
423
+ cronExpression: '0 * * * *',
433
424
  enabled: true,
434
425
  });
435
426
 
@@ -443,10 +434,10 @@ describe("CronScheduler", () => {
443
434
  const mockPersistence: SchedulePersistence = {
444
435
  loadSchedules: vi.fn().mockResolvedValue([
445
436
  {
446
- id: "bad-cron",
447
- workflowKind: "test.workflow",
448
- triggerType: "cron" as const,
449
- cronExpression: "99 99 99 99 99",
437
+ id: 'bad-cron',
438
+ workflowKind: 'test.workflow',
439
+ triggerType: 'cron' as const,
440
+ cronExpression: '99 99 99 99 99',
450
441
  enabled: true,
451
442
  },
452
443
  ]),
@@ -465,12 +456,12 @@ describe("CronScheduler", () => {
465
456
  await badScheduler.start();
466
457
 
467
458
  // The schedule should have been loaded with nextRunAt = undefined due to parse error
468
- const loaded = badScheduler.getSchedule("bad-cron");
459
+ const loaded = badScheduler.getSchedule('bad-cron');
469
460
  expect(loaded).toBeDefined();
470
461
  expect(loaded!.nextRunAt).toBeUndefined();
471
462
  expect(logger.error).toHaveBeenCalledWith(
472
- expect.stringContaining("Failed to parse cron expression"),
473
- expect.anything(),
463
+ expect.stringContaining('Failed to parse cron expression'),
464
+ expect.anything()
474
465
  );
475
466
 
476
467
  await badScheduler.stop();