@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,4 +1,4 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect } from 'vitest';
2
2
  import {
3
3
  WorkflowEngineError,
4
4
  WorkflowNotFoundError,
@@ -8,145 +8,143 @@ import {
8
8
  StepTimeoutError,
9
9
  WorkflowCanceledError,
10
10
  WorkflowTimeoutError,
11
- } from "./errors";
11
+ } from './errors';
12
12
 
13
- describe("WorkflowEngineError", () => {
14
- it("should store code, message, and details", () => {
15
- const err = new WorkflowEngineError("TEST_CODE", "test message", {
16
- key: "val",
17
- });
13
+ describe('WorkflowEngineError', () => {
14
+ it('should store code, message, and details', () => {
15
+ const err = new WorkflowEngineError('TEST_CODE', 'test message', { key: 'val' });
18
16
 
19
- expect(err.code).toBe("TEST_CODE");
20
- expect(err.message).toBe("test message");
21
- expect(err.details).toEqual({ key: "val" });
22
- expect(err.name).toBe("WorkflowEngineError");
17
+ expect(err.code).toBe('TEST_CODE');
18
+ expect(err.message).toBe('test message');
19
+ expect(err.details).toEqual({ key: 'val' });
20
+ expect(err.name).toBe('WorkflowEngineError');
23
21
  expect(err).toBeInstanceOf(Error);
24
22
  });
25
23
 
26
- it("should convert to record", () => {
27
- const err = new WorkflowEngineError("CODE", "msg", { x: 1 });
24
+ it('should convert to record', () => {
25
+ const err = new WorkflowEngineError('CODE', 'msg', { x: 1 });
28
26
  const record = err.toRecord();
29
27
 
30
- expect(record.code).toBe("CODE");
31
- expect(record.message).toBe("msg");
28
+ expect(record.code).toBe('CODE');
29
+ expect(record.message).toBe('msg');
32
30
  expect(record.details).toEqual({ x: 1 });
33
- expect(record.stack).toBeDefined();
31
+ expect(record.stack).toBeUndefined();
34
32
  });
35
33
 
36
- describe("fromError", () => {
37
- it("should convert WorkflowEngineError to record", () => {
38
- const err = new WorkflowEngineError("MY_CODE", "my msg");
34
+ describe('fromError', () => {
35
+ it('should convert WorkflowEngineError to record', () => {
36
+ const err = new WorkflowEngineError('MY_CODE', 'my msg');
39
37
  const record = WorkflowEngineError.fromError(err);
40
38
 
41
- expect(record.code).toBe("MY_CODE");
42
- expect(record.message).toBe("my msg");
39
+ expect(record.code).toBe('MY_CODE');
40
+ expect(record.message).toBe('my msg');
43
41
  });
44
42
 
45
- it("should convert regular Error to record with default code", () => {
46
- const err = new Error("regular error");
43
+ it('should convert regular Error to record with default code', () => {
44
+ const err = new Error('regular error');
47
45
  const record = WorkflowEngineError.fromError(err);
48
46
 
49
- expect(record.code).toBe("UNKNOWN_ERROR");
50
- expect(record.message).toBe("regular error");
51
- expect(record.stack).toBeDefined();
47
+ expect(record.code).toBe('UNKNOWN_ERROR');
48
+ expect(record.message).toBe('regular error');
49
+ expect(record.stack).toBeUndefined();
52
50
  });
53
51
 
54
- it("should convert regular Error with custom code", () => {
55
- const err = new Error("oops");
56
- const record = WorkflowEngineError.fromError(err, "CUSTOM_CODE");
52
+ it('should convert regular Error with custom code', () => {
53
+ const err = new Error('oops');
54
+ const record = WorkflowEngineError.fromError(err, 'CUSTOM_CODE');
57
55
 
58
- expect(record.code).toBe("CUSTOM_CODE");
56
+ expect(record.code).toBe('CUSTOM_CODE');
59
57
  });
60
58
 
61
- it("should convert non-Error value to record", () => {
62
- const record = WorkflowEngineError.fromError("string error");
59
+ it('should convert non-Error value to record', () => {
60
+ const record = WorkflowEngineError.fromError('string error');
63
61
 
64
- expect(record.code).toBe("UNKNOWN_ERROR");
65
- expect(record.message).toBe("string error");
62
+ expect(record.code).toBe('UNKNOWN_ERROR');
63
+ expect(record.message).toBe('string error');
66
64
  expect(record.stack).toBeUndefined();
67
65
  });
68
66
 
69
- it("should convert number to record", () => {
67
+ it('should convert number to record', () => {
70
68
  const record = WorkflowEngineError.fromError(42);
71
69
 
72
- expect(record.message).toBe("42");
70
+ expect(record.message).toBe('42');
73
71
  });
74
72
  });
75
73
  });
76
74
 
77
- describe("WorkflowNotFoundError", () => {
78
- it("should have correct code and message", () => {
79
- const err = new WorkflowNotFoundError("my.workflow");
75
+ describe('WorkflowNotFoundError', () => {
76
+ it('should have correct code and message', () => {
77
+ const err = new WorkflowNotFoundError('my.workflow');
80
78
 
81
- expect(err.code).toBe("WORKFLOW_NOT_FOUND");
79
+ expect(err.code).toBe('WORKFLOW_NOT_FOUND');
82
80
  expect(err.message).toBe('Workflow "my.workflow" is not registered');
83
- expect(err.name).toBe("WorkflowNotFoundError");
84
- expect(err.details).toEqual({ kind: "my.workflow" });
81
+ expect(err.name).toBe('WorkflowNotFoundError');
82
+ expect(err.details).toEqual({ kind: 'my.workflow' });
85
83
  });
86
84
  });
87
85
 
88
- describe("WorkflowAlreadyRegisteredError", () => {
89
- it("should have correct code and message", () => {
90
- const err = new WorkflowAlreadyRegisteredError("dup.workflow");
86
+ describe('WorkflowAlreadyRegisteredError', () => {
87
+ it('should have correct code and message', () => {
88
+ const err = new WorkflowAlreadyRegisteredError('dup.workflow');
91
89
 
92
- expect(err.code).toBe("WORKFLOW_ALREADY_REGISTERED");
93
- expect(err.message).toContain("dup.workflow");
94
- expect(err.name).toBe("WorkflowAlreadyRegisteredError");
90
+ expect(err.code).toBe('WORKFLOW_ALREADY_REGISTERED');
91
+ expect(err.message).toContain('dup.workflow');
92
+ expect(err.name).toBe('WorkflowAlreadyRegisteredError');
95
93
  });
96
94
  });
97
95
 
98
- describe("RunNotFoundError", () => {
99
- it("should have correct code and message", () => {
100
- const err = new RunNotFoundError("run-123");
96
+ describe('RunNotFoundError', () => {
97
+ it('should have correct code and message', () => {
98
+ const err = new RunNotFoundError('run-123');
101
99
 
102
- expect(err.code).toBe("RUN_NOT_FOUND");
103
- expect(err.message).toContain("run-123");
104
- expect(err.name).toBe("RunNotFoundError");
100
+ expect(err.code).toBe('RUN_NOT_FOUND');
101
+ expect(err.message).toContain('run-123');
102
+ expect(err.name).toBe('RunNotFoundError');
105
103
  });
106
104
  });
107
105
 
108
- describe("StepError", () => {
109
- it("should store step key, attempt, and cause", () => {
110
- const cause = new Error("root cause");
111
- const err = new StepError("step1", "step failed", 3, cause);
106
+ describe('StepError', () => {
107
+ it('should store step key, attempt, and cause', () => {
108
+ const cause = new Error('root cause');
109
+ const err = new StepError('step1', 'step failed', 3, cause);
112
110
 
113
- expect(err.code).toBe("STEP_ERROR");
114
- expect(err.stepKey).toBe("step1");
111
+ expect(err.code).toBe('STEP_ERROR');
112
+ expect(err.stepKey).toBe('step1');
115
113
  expect(err.attempt).toBe(3);
116
114
  expect(err.cause).toBe(cause);
117
- expect(err.name).toBe("StepError");
115
+ expect(err.name).toBe('StepError');
118
116
  });
119
117
  });
120
118
 
121
- describe("StepTimeoutError", () => {
122
- it("should store step key and timeout", () => {
123
- const err = new StepTimeoutError("slow_step", 5000);
119
+ describe('StepTimeoutError', () => {
120
+ it('should store step key and timeout', () => {
121
+ const err = new StepTimeoutError('slow_step', 5000);
124
122
 
125
- expect(err.code).toBe("STEP_TIMEOUT");
126
- expect(err.stepKey).toBe("slow_step");
123
+ expect(err.code).toBe('STEP_TIMEOUT');
124
+ expect(err.stepKey).toBe('slow_step');
127
125
  expect(err.timeoutMs).toBe(5000);
128
- expect(err.message).toContain("5000ms");
129
- expect(err.name).toBe("StepTimeoutError");
126
+ expect(err.message).toContain('5000ms');
127
+ expect(err.name).toBe('StepTimeoutError');
130
128
  });
131
129
  });
132
130
 
133
- describe("WorkflowCanceledError", () => {
134
- it("should have correct code and message", () => {
135
- const err = new WorkflowCanceledError("run-456");
131
+ describe('WorkflowCanceledError', () => {
132
+ it('should have correct code and message', () => {
133
+ const err = new WorkflowCanceledError('run-456');
136
134
 
137
- expect(err.code).toBe("WORKFLOW_CANCELED");
138
- expect(err.message).toContain("run-456");
139
- expect(err.name).toBe("WorkflowCanceledError");
135
+ expect(err.code).toBe('WORKFLOW_CANCELED');
136
+ expect(err.message).toContain('run-456');
137
+ expect(err.name).toBe('WorkflowCanceledError');
140
138
  });
141
139
  });
142
140
 
143
- describe("WorkflowTimeoutError", () => {
144
- it("should store timeout value", () => {
145
- const err = new WorkflowTimeoutError("run-789", 30000);
141
+ describe('WorkflowTimeoutError', () => {
142
+ it('should store timeout value', () => {
143
+ const err = new WorkflowTimeoutError('run-789', 30000);
146
144
 
147
- expect(err.code).toBe("WORKFLOW_TIMEOUT");
145
+ expect(err.code).toBe('WORKFLOW_TIMEOUT');
148
146
  expect(err.timeoutMs).toBe(30000);
149
- expect(err.message).toContain("30000ms");
150
- expect(err.name).toBe("WorkflowTimeoutError");
147
+ expect(err.message).toContain('30000ms');
148
+ expect(err.name).toBe('WorkflowTimeoutError');
151
149
  });
152
150
  });
@@ -2,7 +2,7 @@
2
2
  * Custom error classes for the workflow engine.
3
3
  */
4
4
 
5
- import type { WorkflowError } from "../core/types";
5
+ import type { WorkflowError } from '../core/types';
6
6
 
7
7
  /**
8
8
  * Base error class for workflow-related errors.
@@ -11,13 +11,9 @@ export class WorkflowEngineError extends Error {
11
11
  readonly code: string;
12
12
  readonly details?: Record<string, unknown>;
13
13
 
14
- constructor(
15
- code: string,
16
- message: string,
17
- details?: Record<string, unknown>,
18
- ) {
14
+ constructor(code: string, message: string, details?: Record<string, unknown>) {
19
15
  super(message);
20
- this.name = "WorkflowEngineError";
16
+ this.name = 'WorkflowEngineError';
21
17
  this.code = code;
22
18
  this.details = details;
23
19
  }
@@ -29,7 +25,6 @@ export class WorkflowEngineError extends Error {
29
25
  return {
30
26
  code: this.code,
31
27
  message: this.message,
32
- stack: this.stack,
33
28
  details: this.details,
34
29
  };
35
30
  }
@@ -37,10 +32,7 @@ export class WorkflowEngineError extends Error {
37
32
  /**
38
33
  * Create a WorkflowError record from any error.
39
34
  */
40
- static fromError(
41
- error: unknown,
42
- defaultCode = "UNKNOWN_ERROR",
43
- ): WorkflowError {
35
+ static fromError(error: unknown, defaultCode = 'UNKNOWN_ERROR'): WorkflowError {
44
36
  if (error instanceof WorkflowEngineError) {
45
37
  return error.toRecord();
46
38
  }
@@ -49,7 +41,6 @@ export class WorkflowEngineError extends Error {
49
41
  return {
50
42
  code: defaultCode,
51
43
  message: error.message,
52
- stack: error.stack,
53
44
  };
54
45
  }
55
46
 
@@ -65,10 +56,8 @@ export class WorkflowEngineError extends Error {
65
56
  */
66
57
  export class WorkflowNotFoundError extends WorkflowEngineError {
67
58
  constructor(kind: string) {
68
- super("WORKFLOW_NOT_FOUND", `Workflow "${kind}" is not registered`, {
69
- kind,
70
- });
71
- this.name = "WorkflowNotFoundError";
59
+ super('WORKFLOW_NOT_FOUND', `Workflow "${kind}" is not registered`, { kind });
60
+ this.name = 'WorkflowNotFoundError';
72
61
  }
73
62
  }
74
63
 
@@ -77,12 +66,8 @@ export class WorkflowNotFoundError extends WorkflowEngineError {
77
66
  */
78
67
  export class WorkflowAlreadyRegisteredError extends WorkflowEngineError {
79
68
  constructor(kind: string) {
80
- super(
81
- "WORKFLOW_ALREADY_REGISTERED",
82
- `Workflow "${kind}" is already registered`,
83
- { kind },
84
- );
85
- this.name = "WorkflowAlreadyRegisteredError";
69
+ super('WORKFLOW_ALREADY_REGISTERED', `Workflow "${kind}" is already registered`, { kind });
70
+ this.name = 'WorkflowAlreadyRegisteredError';
86
71
  }
87
72
  }
88
73
 
@@ -91,8 +76,8 @@ export class WorkflowAlreadyRegisteredError extends WorkflowEngineError {
91
76
  */
92
77
  export class RunNotFoundError extends WorkflowEngineError {
93
78
  constructor(runId: string) {
94
- super("RUN_NOT_FOUND", `Run "${runId}" not found`, { runId });
95
- this.name = "RunNotFoundError";
79
+ super('RUN_NOT_FOUND', `Run "${runId}" not found`, { runId });
80
+ this.name = 'RunNotFoundError';
96
81
  }
97
82
  }
98
83
 
@@ -104,14 +89,9 @@ export class StepError extends WorkflowEngineError {
104
89
  readonly attempt: number;
105
90
  readonly cause?: Error;
106
91
 
107
- constructor(
108
- stepKey: string,
109
- message: string,
110
- attempt: number,
111
- cause?: Error,
112
- ) {
113
- super("STEP_ERROR", message, { stepKey, attempt });
114
- this.name = "StepError";
92
+ constructor(stepKey: string, message: string, attempt: number, cause?: Error) {
93
+ super('STEP_ERROR', message, { stepKey, attempt });
94
+ this.name = 'StepError';
115
95
  this.stepKey = stepKey;
116
96
  this.attempt = attempt;
117
97
  this.cause = cause;
@@ -126,11 +106,11 @@ export class StepTimeoutError extends WorkflowEngineError {
126
106
  readonly timeoutMs: number;
127
107
 
128
108
  constructor(stepKey: string, timeoutMs: number) {
129
- super("STEP_TIMEOUT", `Step "${stepKey}" timed out after ${timeoutMs}ms`, {
109
+ super('STEP_TIMEOUT', `Step "${stepKey}" timed out after ${timeoutMs}ms`, {
130
110
  stepKey,
131
111
  timeoutMs,
132
112
  });
133
- this.name = "StepTimeoutError";
113
+ this.name = 'StepTimeoutError';
134
114
  this.stepKey = stepKey;
135
115
  this.timeoutMs = timeoutMs;
136
116
  }
@@ -141,10 +121,8 @@ export class StepTimeoutError extends WorkflowEngineError {
141
121
  */
142
122
  export class WorkflowCanceledError extends WorkflowEngineError {
143
123
  constructor(runId: string) {
144
- super("WORKFLOW_CANCELED", `Workflow run "${runId}" was canceled`, {
145
- runId,
146
- });
147
- this.name = "WorkflowCanceledError";
124
+ super('WORKFLOW_CANCELED', `Workflow run "${runId}" was canceled`, { runId });
125
+ this.name = 'WorkflowCanceledError';
148
126
  }
149
127
  }
150
128
 
@@ -156,15 +134,11 @@ export class WaitForRunTimeoutError extends WorkflowEngineError {
156
134
  readonly timeoutMs: number;
157
135
 
158
136
  constructor(runId: string, timeoutMs: number) {
159
- super(
160
- "WAIT_FOR_RUN_TIMEOUT",
161
- `Timeout waiting for run ${runId} after ${timeoutMs}ms`,
162
- {
163
- runId,
164
- timeoutMs,
165
- },
166
- );
167
- this.name = "WaitForRunTimeoutError";
137
+ super('WAIT_FOR_RUN_TIMEOUT', `Timeout waiting for run ${runId} after ${timeoutMs}ms`, {
138
+ runId,
139
+ timeoutMs,
140
+ });
141
+ this.name = 'WaitForRunTimeoutError';
168
142
  this.runId = runId;
169
143
  this.timeoutMs = timeoutMs;
170
144
  }
@@ -177,15 +151,11 @@ export class WorkflowTimeoutError extends WorkflowEngineError {
177
151
  readonly timeoutMs: number;
178
152
 
179
153
  constructor(runId: string, timeoutMs: number) {
180
- super(
181
- "WORKFLOW_TIMEOUT",
182
- `Workflow run "${runId}" timed out after ${timeoutMs}ms`,
183
- {
184
- runId,
185
- timeoutMs,
186
- },
187
- );
188
- this.name = "WorkflowTimeoutError";
154
+ super('WORKFLOW_TIMEOUT', `Workflow run "${runId}" timed out after ${timeoutMs}ms`, {
155
+ runId,
156
+ timeoutMs,
157
+ });
158
+ this.name = 'WorkflowTimeoutError';
189
159
  this.timeoutMs = timeoutMs;
190
160
  }
191
161
  }
package/src/utils/id.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * ID generation utilities for the workflow engine.
3
3
  */
4
- import { randomBytes } from "node:crypto";
4
+
5
+ import { randomBytes } from 'node:crypto';
5
6
 
6
7
  /**
7
8
  * Generate a unique ID for database records.
@@ -10,12 +11,11 @@ import { randomBytes } from "node:crypto";
10
11
  * - Rough time-ordering (useful for debugging)
11
12
  * - High collision resistance
12
13
  * - URL-safe characters
13
- * - Cryptographically secure random component
14
14
  *
15
15
  * @returns A unique identifier string (approx 16 characters)
16
16
  */
17
17
  export function generateId(): string {
18
18
  const timestamp = Date.now().toString(36);
19
- const randomPart = randomBytes(6).toString("base64url");
19
+ const randomPart = randomBytes(6).toString('base64url');
20
20
  return `${timestamp}${randomPart}`;
21
21
  }
@@ -2,7 +2,7 @@
2
2
  * Utilities module exports.
3
3
  */
4
4
 
5
- export * from "./id";
6
- export * from "./errors";
7
- export * from "./logger";
8
- export * from "./retry";
5
+ export * from './id';
6
+ export * from './errors';
7
+ export * from './logger';
8
+ export * from './retry';
@@ -1,77 +1,71 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { ConsoleLogger, SilentLogger, createScopedLogger } from "./logger";
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { ConsoleLogger, SilentLogger, createScopedLogger } from './logger';
3
3
 
4
- describe("ConsoleLogger", () => {
5
- it("should log debug messages with prefix", () => {
6
- const spy = vi.spyOn(console, "debug").mockImplementation(() => {});
7
- const logger = new ConsoleLogger("[test]", "debug");
4
+ describe('ConsoleLogger', () => {
5
+ it('should log debug messages with prefix', () => {
6
+ const spy = vi.spyOn(console, 'debug').mockImplementation(() => {});
7
+ const logger = new ConsoleLogger('[test]', 'debug');
8
8
 
9
- logger.debug("hello", { extra: true });
9
+ logger.debug('hello', { extra: true });
10
10
 
11
- expect(spy).toHaveBeenCalledWith("[test] [DEBUG]", "hello", {
12
- extra: true,
13
- });
11
+ expect(spy).toHaveBeenCalledWith('[test] [DEBUG]', 'hello', { extra: true });
14
12
  spy.mockRestore();
15
13
  });
16
14
 
17
- it("should log info messages with prefix", () => {
18
- const spy = vi.spyOn(console, "info").mockImplementation(() => {});
19
- const logger = new ConsoleLogger("[test]");
15
+ it('should log info messages with prefix', () => {
16
+ const spy = vi.spyOn(console, 'info').mockImplementation(() => {});
17
+ const logger = new ConsoleLogger('[test]');
20
18
 
21
- logger.info("info msg");
19
+ logger.info('info msg');
22
20
 
23
- expect(spy).toHaveBeenCalledWith("[test] [INFO]", "info msg");
21
+ expect(spy).toHaveBeenCalledWith('[test] [INFO]', 'info msg');
24
22
  spy.mockRestore();
25
23
  });
26
24
 
27
- it("should log warn messages with prefix", () => {
28
- const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
29
- const logger = new ConsoleLogger("[test]");
25
+ it('should log warn messages with prefix', () => {
26
+ const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
27
+ const logger = new ConsoleLogger('[test]');
30
28
 
31
- logger.warn("warning");
29
+ logger.warn('warning');
32
30
 
33
- expect(spy).toHaveBeenCalledWith("[test] [WARN]", "warning");
31
+ expect(spy).toHaveBeenCalledWith('[test] [WARN]', 'warning');
34
32
  spy.mockRestore();
35
33
  });
36
34
 
37
- it("should log error messages with prefix", () => {
38
- const spy = vi.spyOn(console, "error").mockImplementation(() => {});
39
- const logger = new ConsoleLogger("[test]");
35
+ it('should log error messages with prefix', () => {
36
+ const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
37
+ const logger = new ConsoleLogger('[test]');
40
38
 
41
- logger.error("error msg", new Error("boom"));
39
+ logger.error('error msg', new Error('boom'));
42
40
 
43
- expect(spy).toHaveBeenCalledWith(
44
- "[test] [ERROR]",
45
- "error msg",
46
- expect.any(Error),
47
- );
41
+ expect(spy).toHaveBeenCalledWith('[test] [ERROR]', 'error msg', expect.any(Error));
48
42
  spy.mockRestore();
49
43
  });
50
44
 
51
- it("should use default prefix when none provided", () => {
52
- const spy = vi.spyOn(console, "info").mockImplementation(() => {});
45
+ it('should use default prefix when none provided', () => {
46
+ const spy = vi.spyOn(console, 'info').mockImplementation(() => {});
53
47
  const logger = new ConsoleLogger();
54
48
 
55
- logger.info("test");
49
+ logger.info('test');
56
50
 
57
- expect(spy).toHaveBeenCalledWith("[workflow] [INFO]", "test");
51
+ expect(spy).toHaveBeenCalledWith('[workflow] [INFO]', 'test');
58
52
  spy.mockRestore();
59
53
  });
60
54
  });
61
55
 
62
- describe("SilentLogger", () => {
63
- it("should not throw on any log level", () => {
56
+ describe('SilentLogger', () => {
57
+ it('should not throw on any log level', () => {
64
58
  const logger = new SilentLogger();
65
59
 
66
- expect(() => logger.debug("test")).not.toThrow();
67
- expect(() => logger.info("test")).not.toThrow();
68
- expect(() => logger.warn("test")).not.toThrow();
69
- expect(() => logger.error("test")).not.toThrow();
60
+ expect(() => logger.debug('test')).not.toThrow();
61
+ expect(() => logger.info('test')).not.toThrow();
62
+ expect(() => logger.warn('test')).not.toThrow();
63
+ expect(() => logger.error('test')).not.toThrow();
70
64
  });
71
65
  });
72
66
 
73
- describe("createScopedLogger", () => {
74
- it("should prefix messages with run ID", () => {
67
+ describe('createScopedLogger', () => {
68
+ it('should prefix messages with run ID', () => {
75
69
  const calls: string[] = [];
76
70
  const baseLogger = {
77
71
  debug: (msg: string) => calls.push(`debug:${msg}`),
@@ -80,22 +74,22 @@ describe("createScopedLogger", () => {
80
74
  error: (msg: string) => calls.push(`error:${msg}`),
81
75
  };
82
76
 
83
- const scoped = createScopedLogger(baseLogger, "run-123");
77
+ const scoped = createScopedLogger(baseLogger, 'run-123');
84
78
 
85
- scoped.debug("d");
86
- scoped.info("i");
87
- scoped.warn("w");
88
- scoped.error("e");
79
+ scoped.debug('d');
80
+ scoped.info('i');
81
+ scoped.warn('w');
82
+ scoped.error('e');
89
83
 
90
84
  expect(calls).toEqual([
91
- "debug:[run:run-123] d",
92
- "info:[run:run-123] i",
93
- "warn:[run:run-123] w",
94
- "error:[run:run-123] e",
85
+ 'debug:[run:run-123] d',
86
+ 'info:[run:run-123] i',
87
+ 'warn:[run:run-123] w',
88
+ 'error:[run:run-123] e',
95
89
  ]);
96
90
  });
97
91
 
98
- it("should include step key when provided", () => {
92
+ it('should include step key when provided', () => {
99
93
  const calls: string[] = [];
100
94
  const baseLogger = {
101
95
  debug: (msg: string) => calls.push(msg),
@@ -104,13 +98,13 @@ describe("createScopedLogger", () => {
104
98
  error: (msg: string) => calls.push(msg),
105
99
  };
106
100
 
107
- const scoped = createScopedLogger(baseLogger, "run-1", "step-a");
108
- scoped.info("hello");
101
+ const scoped = createScopedLogger(baseLogger, 'run-1', 'step-a');
102
+ scoped.info('hello');
109
103
 
110
- expect(calls[0]).toBe("[run:run-1][step:step-a] hello");
104
+ expect(calls[0]).toBe('[run:run-1][step:step-a] hello');
111
105
  });
112
106
 
113
- it("should forward extra arguments", () => {
107
+ it('should forward extra arguments', () => {
114
108
  const captured: unknown[][] = [];
115
109
  const baseLogger = {
116
110
  debug: (...args: unknown[]) => captured.push(args),
@@ -119,9 +113,9 @@ describe("createScopedLogger", () => {
119
113
  error: (...args: unknown[]) => captured.push(args),
120
114
  };
121
115
 
122
- const scoped = createScopedLogger(baseLogger, "r1");
123
- scoped.info("msg", { data: 1 }, 42);
116
+ const scoped = createScopedLogger(baseLogger, 'r1');
117
+ scoped.info('msg', { data: 1 }, 42);
124
118
 
125
- expect(captured[0]).toEqual(["[run:r1] msg", { data: 1 }, 42]);
119
+ expect(captured[0]).toEqual(['[run:r1] msg', { data: 1 }, 42]);
126
120
  });
127
121
  });
@@ -2,10 +2,10 @@
2
2
  * Logger utilities for the workflow engine.
3
3
  */
4
4
 
5
- import type { Logger, WorkflowError } from "../core/types";
5
+ import type { Logger, WorkflowError } from '../core/types';
6
6
 
7
7
  /** Log level for ConsoleLogger. Levels are ordered: debug < info < warn < error. */
8
- export type LogLevel = "debug" | "info" | "warn" | "error";
8
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
9
9
 
10
10
  const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
11
11
  debug: 0,
@@ -23,7 +23,7 @@ export class ConsoleLogger implements Logger {
23
23
  private prefix: string;
24
24
  private minLevel: number;
25
25
 
26
- constructor(prefix = "[workflow]", level: LogLevel = "info") {
26
+ constructor(prefix = '[workflow]', level: LogLevel = 'info') {
27
27
  this.prefix = prefix;
28
28
  this.minLevel = LOG_LEVEL_PRIORITY[level];
29
29
  }
@@ -79,7 +79,7 @@ export function sanitizeErrorForStorage(error: WorkflowError): WorkflowError {
79
79
  export function createScopedLogger(
80
80
  logger: Logger,
81
81
  runId: string,
82
- stepKey?: string,
82
+ stepKey?: string
83
83
  ): Logger {
84
84
  const prefix = stepKey ? `[run:${runId}][step:${stepKey}]` : `[run:${runId}]`;
85
85