@multiplier-labs/stepflow 0.3.4 → 0.3.8

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 (109) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-5XJBAPXF.js → chunk-4IDPWWGS.js} +15 -4
  3. package/dist/chunk-4IDPWWGS.js.map +1 -0
  4. package/dist/{chunk-U3PCVK6O.js → chunk-T6GAZBTQ.js} +70 -38
  5. package/dist/chunk-T6GAZBTQ.js.map +1 -0
  6. package/dist/{chunk-FMPJBK2N.js → chunk-TVSADUYU.js} +3 -5
  7. package/dist/{chunk-FMPJBK2N.js.map → chunk-TVSADUYU.js.map} +1 -1
  8. package/dist/events/index.d.ts +43 -3
  9. package/dist/events/index.js +7 -3
  10. package/dist/{index-Cd0IZ40L.d.ts → index-CdXDv30T.d.ts} +10 -2
  11. package/dist/index.d.ts +5 -4
  12. package/dist/index.js +159 -46
  13. package/dist/index.js.map +1 -1
  14. package/dist/scheduler/index.d.ts +2 -2
  15. package/dist/scheduler/index.js +1 -1
  16. package/dist/storage/index.d.ts +6 -4
  17. package/dist/storage/index.js +1 -1
  18. package/dist/{types-DSEjiLMj.d.ts → types-BfWe5yP6.d.ts} +2 -0
  19. package/package.json +26 -14
  20. package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +0 -146
  21. package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +0 -74
  22. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  23. package/.codekin/reports/comments/2026-04-11_comment-assessment.md +0 -161
  24. package/.codekin/reports/comments/2026-04-14_comment-assessment.md +0 -34
  25. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  26. package/.codekin/reports/complexity/2026-04-11_complexity-report.md +0 -152
  27. package/.codekin/reports/complexity/2026-04-14_complexity-report.md +0 -34
  28. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  29. package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +0 -93
  30. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  31. package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +0 -138
  32. package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +0 -121
  33. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  34. package/.codekin/reports/security/2026-04-11_security-audit.md +0 -297
  35. package/.github/CODEOWNERS +0 -15
  36. package/.github/dependabot.yml +0 -23
  37. package/.github/workflows/ci.yml +0 -38
  38. package/.github/workflows/publish.yml +0 -42
  39. package/.github/workflows/security.yml +0 -29
  40. package/CHANGELOG.md +0 -34
  41. package/SECURITY.md +0 -40
  42. package/dist/chunk-5XJBAPXF.js.map +0 -1
  43. package/dist/chunk-U3PCVK6O.js.map +0 -1
  44. package/docs/github-packages-setup.md +0 -97
  45. package/docs/stepflow-api-reference.md +0 -1663
  46. package/src/core/engine/concurrency.ts +0 -229
  47. package/src/core/engine/lifecycle.ts +0 -176
  48. package/src/core/engine.test.ts +0 -1435
  49. package/src/core/engine.ts +0 -598
  50. package/src/core/index.ts +0 -7
  51. package/src/core/orchestrator/persistence.ts +0 -165
  52. package/src/core/orchestrator/plan-resolution.ts +0 -140
  53. package/src/core/orchestrator/state-transitions.ts +0 -109
  54. package/src/core/orchestrator/step-runner.ts +0 -427
  55. package/src/core/orchestrator.ts +0 -252
  56. package/src/core/types.ts +0 -220
  57. package/src/events/index.ts +0 -8
  58. package/src/events/memory.test.ts +0 -107
  59. package/src/events/memory.ts +0 -92
  60. package/src/events/socketio.test.ts +0 -378
  61. package/src/events/socketio.ts +0 -262
  62. package/src/events/types.ts +0 -118
  63. package/src/events/webhook.test.ts +0 -949
  64. package/src/events/webhook.ts +0 -807
  65. package/src/index.ts +0 -270
  66. package/src/planning/evaluate-condition.test.ts +0 -153
  67. package/src/planning/evaluate-condition.ts +0 -105
  68. package/src/planning/index.ts +0 -55
  69. package/src/planning/planner.ts +0 -494
  70. package/src/planning/planning.test.ts +0 -727
  71. package/src/planning/registry.test.ts +0 -398
  72. package/src/planning/registry.ts +0 -283
  73. package/src/planning/types.ts +0 -566
  74. package/src/scheduler/cron.test.ts +0 -479
  75. package/src/scheduler/cron.ts +0 -438
  76. package/src/scheduler/index.ts +0 -8
  77. package/src/scheduler/postgres-persistence.ts +0 -530
  78. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  79. package/src/scheduler/sqlite-persistence.ts +0 -286
  80. package/src/scheduler/types.ts +0 -73
  81. package/src/storage/index.ts +0 -27
  82. package/src/storage/memory.test.ts +0 -349
  83. package/src/storage/memory.ts +0 -227
  84. package/src/storage/postgres-core.ts +0 -550
  85. package/src/storage/postgres-migration.test.ts +0 -69
  86. package/src/storage/postgres-transaction-pagination.test.ts +0 -107
  87. package/src/storage/postgres.ts +0 -835
  88. package/src/storage/sqlite-core.ts +0 -151
  89. package/src/storage/sqlite.test.ts +0 -642
  90. package/src/storage/sqlite.ts +0 -717
  91. package/src/storage/types.ts +0 -343
  92. package/src/utils/errors.test.ts +0 -150
  93. package/src/utils/errors.ts +0 -161
  94. package/src/utils/id.ts +0 -21
  95. package/src/utils/index.ts +0 -10
  96. package/src/utils/logger.test.ts +0 -121
  97. package/src/utils/logger.ts +0 -96
  98. package/src/utils/pinned-fetch.test.ts +0 -84
  99. package/src/utils/pinned-fetch.ts +0 -118
  100. package/src/utils/postgres-deps.ts +0 -79
  101. package/src/utils/retry.test.ts +0 -177
  102. package/src/utils/retry.ts +0 -121
  103. package/src/utils/safe-json.test.ts +0 -166
  104. package/src/utils/safe-json.ts +0 -107
  105. package/src/utils/webhook-signature.test.ts +0 -111
  106. package/src/utils/webhook-signature.ts +0 -74
  107. package/tsconfig.json +0 -21
  108. package/tsup.config.ts +0 -14
  109. package/vitest.config.ts +0 -9
@@ -1,438 +0,0 @@
1
- /**
2
- * CronScheduler - Scheduler implementation using cron expressions.
3
- *
4
- * Provides time-based and workflow-completion-based triggers for workflows.
5
- */
6
-
7
- import { CronExpressionParser } from 'cron-parser';
8
- import type { WorkflowKind, RunStatus, Logger } from '../core/types';
9
- import type { WorkflowEngine } from '../core/engine';
10
- import type { Scheduler, WorkflowSchedule, TriggerType } from './types';
11
- import type { WorkflowEvent } from '../events/types';
12
- import { generateId } from '../utils/id';
13
- import { ConsoleLogger } from '../utils/logger';
14
-
15
- // ============================================================================
16
- // Configuration Types
17
- // ============================================================================
18
-
19
- /**
20
- * Configuration for the CronScheduler.
21
- */
22
- export interface CronSchedulerConfig {
23
- /** The workflow engine instance */
24
- engine: WorkflowEngine;
25
-
26
- /** Logger instance */
27
- logger?: Logger;
28
-
29
- /** Poll interval for checking schedules (ms, default: 1000) */
30
- pollInterval?: number;
31
-
32
- /** Optional persistence adapter for schedules */
33
- persistence?: SchedulePersistence;
34
- }
35
-
36
- /**
37
- * Interface for schedule persistence.
38
- * Implement this to persist schedules to a database.
39
- */
40
- export interface SchedulePersistence {
41
- /** Load all schedules from storage */
42
- loadSchedules(): Promise<WorkflowSchedule[]>;
43
-
44
- /** Save a schedule */
45
- saveSchedule(schedule: WorkflowSchedule): Promise<void>;
46
-
47
- /** Update a schedule */
48
- updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void>;
49
-
50
- /** Delete a schedule */
51
- deleteSchedule(scheduleId: string): Promise<void>;
52
- }
53
-
54
- // ============================================================================
55
- // CronScheduler Class
56
- // ============================================================================
57
-
58
- /**
59
- * Scheduler implementation that supports cron expressions and workflow completion triggers.
60
- *
61
- * @example
62
- * ```typescript
63
- * const scheduler = new CronScheduler({
64
- * engine,
65
- * pollInterval: 1000,
66
- * });
67
- *
68
- * // Add a cron schedule (every day at midnight)
69
- * await scheduler.addSchedule({
70
- * workflowKind: 'cleanup.daily',
71
- * triggerType: 'cron',
72
- * cronExpression: '0 0 * * *',
73
- * enabled: true,
74
- * });
75
- *
76
- * // Add a workflow completion trigger
77
- * await scheduler.addSchedule({
78
- * workflowKind: 'notification.send',
79
- * triggerType: 'workflow_completed',
80
- * triggerOnWorkflowKind: 'order.process',
81
- * triggerOnStatus: ['succeeded'],
82
- * enabled: true,
83
- * });
84
- *
85
- * await scheduler.start();
86
- * ```
87
- */
88
- export class CronScheduler implements Scheduler {
89
- private engine: WorkflowEngine;
90
- private logger: Logger;
91
- private pollInterval: number;
92
- private persistence?: SchedulePersistence;
93
-
94
- private schedules = new Map<string, WorkflowSchedule>();
95
- private running = false;
96
- private pollTimer: ReturnType<typeof setInterval> | null = null;
97
- private eventUnsubscribe: (() => void) | null = null;
98
-
99
- constructor(config: CronSchedulerConfig) {
100
- this.engine = config.engine;
101
- this.logger = config.logger ?? new ConsoleLogger();
102
- this.pollInterval = config.pollInterval ?? 1000;
103
- this.persistence = config.persistence;
104
- }
105
-
106
- // ============================================================================
107
- // Scheduler Interface Implementation
108
- // ============================================================================
109
-
110
- /**
111
- * Start the scheduler.
112
- * Begins polling for cron schedules and subscribes to workflow completion events.
113
- */
114
- async start(): Promise<void> {
115
- if (this.running) {
116
- this.logger.warn('Scheduler is already running');
117
- return;
118
- }
119
-
120
- // Clean up any leftover subscriptions from a previous start/stop cycle
121
- if (this.eventUnsubscribe) {
122
- this.eventUnsubscribe();
123
- this.eventUnsubscribe = null;
124
- }
125
- if (this.pollTimer) {
126
- clearInterval(this.pollTimer);
127
- this.pollTimer = null;
128
- }
129
-
130
- this.logger.info('Starting scheduler...');
131
-
132
- // Load schedules from persistence if available
133
- if (this.persistence) {
134
- const loaded = await this.persistence.loadSchedules();
135
- for (const schedule of loaded) {
136
- this.schedules.set(schedule.id, schedule);
137
- }
138
- this.logger.info(`Loaded ${loaded.length} schedules from persistence`);
139
- }
140
-
141
- // Calculate next run times for cron schedules.
142
- // Wrap each schedule in try-catch so one invalid persisted cron expression
143
- // doesn't prevent the rest from loading.
144
- for (const schedule of this.schedules.values()) {
145
- if (schedule.triggerType === 'cron' && schedule.cronExpression) {
146
- try {
147
- CronExpressionParser.parse(schedule.cronExpression, {
148
- tz: schedule.timezone,
149
- });
150
- this.updateNextRunTime(schedule);
151
- } catch (error) {
152
- this.logger.error(
153
- `Invalid cron expression "${schedule.cronExpression}" for schedule ${schedule.id}, disabling schedule:`,
154
- error
155
- );
156
- schedule.enabled = false;
157
- schedule.nextRunAt = undefined;
158
- }
159
- }
160
- }
161
-
162
- // Start polling for cron schedules
163
- this.pollTimer = setInterval(() => this.checkSchedules(), this.pollInterval);
164
-
165
- // Subscribe to workflow completion events
166
- this.eventUnsubscribe = this.engine.subscribeToAll((event) => {
167
- this.handleWorkflowEvent(event);
168
- });
169
-
170
- this.running = true;
171
- this.logger.info('Scheduler started');
172
- }
173
-
174
- /**
175
- * Stop the scheduler.
176
- * Stops polling and unsubscribes from events.
177
- */
178
- async stop(): Promise<void> {
179
- if (!this.running) {
180
- return;
181
- }
182
-
183
- this.logger.info('Stopping scheduler...');
184
-
185
- if (this.pollTimer) {
186
- clearInterval(this.pollTimer);
187
- this.pollTimer = null;
188
- }
189
-
190
- if (this.eventUnsubscribe) {
191
- this.eventUnsubscribe();
192
- this.eventUnsubscribe = null;
193
- }
194
-
195
- this.running = false;
196
- this.logger.info('Scheduler stopped');
197
- }
198
-
199
- /**
200
- * Add a new schedule.
201
- */
202
- async addSchedule(scheduleData: Omit<WorkflowSchedule, 'id'>): Promise<WorkflowSchedule> {
203
- const schedule: WorkflowSchedule = {
204
- ...scheduleData,
205
- id: generateId(),
206
- };
207
-
208
- // Validate cron expression if provided
209
- if (schedule.triggerType === 'cron' && schedule.cronExpression) {
210
- try {
211
- CronExpressionParser.parse(schedule.cronExpression, {
212
- tz: schedule.timezone,
213
- });
214
- } catch (error) {
215
- throw new Error(`Invalid cron expression: ${schedule.cronExpression}`);
216
- }
217
-
218
- // Calculate next run time
219
- this.updateNextRunTime(schedule);
220
- }
221
-
222
- // Validate workflow completion trigger
223
- if (schedule.triggerType === 'workflow_completed') {
224
- if (!schedule.triggerOnWorkflowKind) {
225
- throw new Error('triggerOnWorkflowKind is required for workflow_completed triggers');
226
- }
227
- }
228
-
229
- this.schedules.set(schedule.id, schedule);
230
-
231
- // Persist if available
232
- if (this.persistence) {
233
- await this.persistence.saveSchedule(schedule);
234
- }
235
-
236
- this.logger.info(`Added schedule: ${schedule.id} (${schedule.workflowKind})`);
237
-
238
- return schedule;
239
- }
240
-
241
- /**
242
- * Remove a schedule.
243
- */
244
- async removeSchedule(scheduleId: string): Promise<void> {
245
- if (!this.schedules.has(scheduleId)) {
246
- throw new Error(`Schedule not found: ${scheduleId}`);
247
- }
248
-
249
- this.schedules.delete(scheduleId);
250
-
251
- if (this.persistence) {
252
- await this.persistence.deleteSchedule(scheduleId);
253
- }
254
-
255
- this.logger.info(`Removed schedule: ${scheduleId}`);
256
- }
257
-
258
- /**
259
- * Update a schedule.
260
- */
261
- async updateSchedule(scheduleId: string, updates: Partial<WorkflowSchedule>): Promise<void> {
262
- const schedule = this.schedules.get(scheduleId);
263
- if (!schedule) {
264
- throw new Error(`Schedule not found: ${scheduleId}`);
265
- }
266
-
267
- // Don't allow changing the ID
268
- delete updates.id;
269
-
270
- // Update the schedule
271
- Object.assign(schedule, updates);
272
-
273
- // Revalidate and update next run time if cron expression changed
274
- if (updates.cronExpression !== undefined || updates.timezone !== undefined) {
275
- if (schedule.cronExpression) {
276
- try {
277
- CronExpressionParser.parse(schedule.cronExpression, {
278
- tz: schedule.timezone,
279
- });
280
- this.updateNextRunTime(schedule);
281
- } catch (error) {
282
- throw new Error(`Invalid cron expression: ${schedule.cronExpression}`);
283
- }
284
- }
285
- }
286
-
287
- if (this.persistence) {
288
- await this.persistence.updateSchedule(scheduleId, updates);
289
- }
290
-
291
- this.logger.debug(`Updated schedule: ${scheduleId}`);
292
- }
293
-
294
- /**
295
- * Get all schedules.
296
- */
297
- async getSchedules(): Promise<WorkflowSchedule[]> {
298
- return Array.from(this.schedules.values());
299
- }
300
-
301
- /**
302
- * Get a schedule by ID.
303
- */
304
- getSchedule(scheduleId: string): WorkflowSchedule | undefined {
305
- return this.schedules.get(scheduleId);
306
- }
307
-
308
- /**
309
- * Manually trigger a scheduled workflow.
310
- */
311
- async triggerNow(scheduleId: string): Promise<string> {
312
- const schedule = this.schedules.get(scheduleId);
313
- if (!schedule) {
314
- throw new Error(`Schedule not found: ${scheduleId}`);
315
- }
316
-
317
- return this.executeSchedule(schedule);
318
- }
319
-
320
- // ============================================================================
321
- // Internal Methods
322
- // ============================================================================
323
-
324
- /**
325
- * Check all cron schedules and execute those that are due.
326
- */
327
- private checkSchedules(): void {
328
- const now = new Date();
329
-
330
- for (const schedule of this.schedules.values()) {
331
- if (!schedule.enabled) continue;
332
- if (schedule.triggerType !== 'cron') continue;
333
- if (!schedule.nextRunAt) continue;
334
-
335
- if (schedule.nextRunAt <= now) {
336
- // Execute the schedule
337
- this.executeSchedule(schedule).catch((error) => {
338
- this.logger.error(`Failed to execute schedule ${schedule.id}:`, error);
339
- });
340
-
341
- // Update next run time
342
- this.updateNextRunTime(schedule);
343
- }
344
- }
345
- }
346
-
347
- /**
348
- * Handle workflow events for completion triggers.
349
- */
350
- private handleWorkflowEvent(event: WorkflowEvent): void {
351
- // Only handle run completion events
352
- // Note: orchestrator emits 'run.completed' for success, 'run.failed' for failure
353
- if (event.eventType !== 'run.completed' && event.eventType !== 'run.failed') {
354
- return;
355
- }
356
-
357
- const completedStatus: RunStatus = event.eventType === 'run.completed' ? 'succeeded' : 'failed';
358
- const completedKind = event.kind;
359
-
360
- // Find schedules triggered by this workflow completion
361
- for (const schedule of this.schedules.values()) {
362
- if (!schedule.enabled) continue;
363
- if (schedule.triggerType !== 'workflow_completed') continue;
364
- if (schedule.triggerOnWorkflowKind !== completedKind) continue;
365
-
366
- // Check if the status matches
367
- if (schedule.triggerOnStatus && !schedule.triggerOnStatus.includes(completedStatus)) {
368
- continue;
369
- }
370
-
371
- // Execute the schedule
372
- this.executeSchedule(schedule, {
373
- triggerRunId: event.runId,
374
- triggerStatus: completedStatus,
375
- }).catch((error) => {
376
- this.logger.error(`Failed to execute schedule ${schedule.id}:`, error);
377
- });
378
- }
379
- }
380
-
381
- /**
382
- * Execute a schedule by starting the workflow.
383
- */
384
- private async executeSchedule(
385
- schedule: WorkflowSchedule,
386
- triggerContext?: { triggerRunId?: string; triggerStatus?: RunStatus }
387
- ): Promise<string> {
388
- this.logger.info(`Executing schedule: ${schedule.id} (${schedule.workflowKind})`);
389
-
390
- // Build metadata with trigger info
391
- const metadata = {
392
- ...schedule.metadata,
393
- scheduleId: schedule.id,
394
- triggerType: schedule.triggerType,
395
- ...(triggerContext ?? {}),
396
- };
397
-
398
- // Start the workflow
399
- const runId = await this.engine.startRun({
400
- kind: schedule.workflowKind,
401
- input: schedule.input,
402
- metadata,
403
- });
404
-
405
- // Update schedule state
406
- schedule.lastRunAt = new Date();
407
- schedule.lastRunId = runId;
408
-
409
- if (this.persistence) {
410
- await this.persistence.updateSchedule(schedule.id, {
411
- lastRunAt: schedule.lastRunAt,
412
- lastRunId: schedule.lastRunId,
413
- nextRunAt: schedule.nextRunAt,
414
- });
415
- }
416
-
417
- return runId;
418
- }
419
-
420
- /**
421
- * Update the next run time for a cron schedule.
422
- */
423
- private updateNextRunTime(schedule: WorkflowSchedule): void {
424
- if (!schedule.cronExpression) return;
425
-
426
- try {
427
- const interval = CronExpressionParser.parse(schedule.cronExpression, {
428
- currentDate: new Date(),
429
- tz: schedule.timezone,
430
- });
431
-
432
- schedule.nextRunAt = interval.next().toDate();
433
- } catch (error) {
434
- this.logger.error(`Failed to parse cron expression for schedule ${schedule.id}:`, error);
435
- schedule.nextRunAt = undefined;
436
- }
437
- }
438
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Scheduler module exports.
3
- */
4
-
5
- export * from './types';
6
- export { CronScheduler, type CronSchedulerConfig, type SchedulePersistence } from './cron';
7
- export { SQLiteSchedulePersistence, type SQLiteSchedulePersistenceConfig } from './sqlite-persistence';
8
- export { PostgresSchedulePersistence, type PostgresSchedulePersistenceConfig } from './postgres-persistence';