@multiplier-labs/stepflow 0.3.3 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +9 -0
  2. package/dist/{chunk-GYPNJBDP.js → chunk-D5HW7VEG.js} +269 -48
  3. package/dist/chunk-D5HW7VEG.js.map +1 -0
  4. package/dist/{chunk-KHTUWLVZ.js → chunk-FMPJBK2N.js} +55 -10
  5. package/dist/chunk-FMPJBK2N.js.map +1 -0
  6. package/dist/{chunk-ML35PIHX.js → chunk-JPXPB7U5.js} +52 -2
  7. package/dist/chunk-JPXPB7U5.js.map +1 -0
  8. package/dist/{chunk-4VOKMGP6.js → chunk-JUPZEBR3.js} +519 -522
  9. package/dist/chunk-JUPZEBR3.js.map +1 -0
  10. package/dist/events/index.d.ts +58 -3
  11. package/dist/events/index.js +1 -1
  12. package/dist/{index-Dzr437iW.d.ts → index-Cd0IZ40L.d.ts} +50 -11
  13. package/dist/index.d.ts +2 -12
  14. package/dist/index.js +764 -485
  15. package/dist/index.js.map +1 -1
  16. package/dist/scheduler/index.d.ts +1 -1
  17. package/dist/scheduler/index.js +2 -2
  18. package/dist/storage/index.d.ts +313 -47
  19. package/dist/storage/index.js +4 -2
  20. package/package.json +27 -13
  21. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +0 -179
  22. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +0 -152
  23. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +0 -94
  24. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +0 -10
  25. package/.codekin/reports/security/2026-04-03_security-audit.md +0 -208
  26. package/.github/CODEOWNERS +0 -15
  27. package/.github/dependabot.yml +0 -23
  28. package/.github/workflows/ci.yml +0 -38
  29. package/.github/workflows/publish.yml +0 -36
  30. package/.github/workflows/security.yml +0 -29
  31. package/CHANGELOG.md +0 -34
  32. package/SECURITY.md +0 -40
  33. package/dist/chunk-4VOKMGP6.js.map +0 -1
  34. package/dist/chunk-GYPNJBDP.js.map +0 -1
  35. package/dist/chunk-KHTUWLVZ.js.map +0 -1
  36. package/dist/chunk-ML35PIHX.js.map +0 -1
  37. package/docs/github-packages-setup.md +0 -97
  38. package/docs/stepflow-api-reference.md +0 -1661
  39. package/src/core/engine.test.ts +0 -1365
  40. package/src/core/engine.ts +0 -692
  41. package/src/core/index.ts +0 -7
  42. package/src/core/orchestrator.ts +0 -571
  43. package/src/core/types.ts +0 -220
  44. package/src/events/index.ts +0 -8
  45. package/src/events/memory.test.ts +0 -107
  46. package/src/events/memory.ts +0 -92
  47. package/src/events/socketio.test.ts +0 -378
  48. package/src/events/socketio.ts +0 -262
  49. package/src/events/types.ts +0 -118
  50. package/src/events/webhook.test.ts +0 -657
  51. package/src/events/webhook.ts +0 -564
  52. package/src/index.ts +0 -270
  53. package/src/planning/index.ts +0 -55
  54. package/src/planning/planner.ts +0 -576
  55. package/src/planning/planning.test.ts +0 -727
  56. package/src/planning/registry.test.ts +0 -398
  57. package/src/planning/registry.ts +0 -365
  58. package/src/planning/types.ts +0 -566
  59. package/src/scheduler/cron.test.ts +0 -471
  60. package/src/scheduler/cron.ts +0 -414
  61. package/src/scheduler/index.ts +0 -8
  62. package/src/scheduler/postgres-persistence.ts +0 -518
  63. package/src/scheduler/sqlite-persistence.test.ts +0 -235
  64. package/src/scheduler/sqlite-persistence.ts +0 -261
  65. package/src/scheduler/types.ts +0 -73
  66. package/src/storage/index.ts +0 -27
  67. package/src/storage/memory.test.ts +0 -349
  68. package/src/storage/memory.ts +0 -227
  69. package/src/storage/postgres.ts +0 -1354
  70. package/src/storage/sqlite.test.ts +0 -466
  71. package/src/storage/sqlite.ts +0 -627
  72. package/src/storage/types.ts +0 -343
  73. package/src/utils/errors.test.ts +0 -150
  74. package/src/utils/errors.ts +0 -161
  75. package/src/utils/id.ts +0 -21
  76. package/src/utils/index.ts +0 -8
  77. package/src/utils/logger.test.ts +0 -121
  78. package/src/utils/logger.ts +0 -96
  79. package/src/utils/postgres-deps.ts +0 -63
  80. package/src/utils/retry.test.ts +0 -177
  81. package/src/utils/retry.ts +0 -121
  82. package/tsconfig.json +0 -21
  83. package/tsup.config.ts +0 -14
  84. package/vitest.config.ts +0 -9
@@ -1,121 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { ConsoleLogger, SilentLogger, createScopedLogger } from './logger';
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');
8
-
9
- logger.debug('hello', { extra: true });
10
-
11
- expect(spy).toHaveBeenCalledWith('[test] [DEBUG]', 'hello', { extra: true });
12
- spy.mockRestore();
13
- });
14
-
15
- it('should log info messages with prefix', () => {
16
- const spy = vi.spyOn(console, 'info').mockImplementation(() => {});
17
- const logger = new ConsoleLogger('[test]');
18
-
19
- logger.info('info msg');
20
-
21
- expect(spy).toHaveBeenCalledWith('[test] [INFO]', 'info msg');
22
- spy.mockRestore();
23
- });
24
-
25
- it('should log warn messages with prefix', () => {
26
- const spy = vi.spyOn(console, 'warn').mockImplementation(() => {});
27
- const logger = new ConsoleLogger('[test]');
28
-
29
- logger.warn('warning');
30
-
31
- expect(spy).toHaveBeenCalledWith('[test] [WARN]', 'warning');
32
- spy.mockRestore();
33
- });
34
-
35
- it('should log error messages with prefix', () => {
36
- const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
37
- const logger = new ConsoleLogger('[test]');
38
-
39
- logger.error('error msg', new Error('boom'));
40
-
41
- expect(spy).toHaveBeenCalledWith('[test] [ERROR]', 'error msg', expect.any(Error));
42
- spy.mockRestore();
43
- });
44
-
45
- it('should use default prefix when none provided', () => {
46
- const spy = vi.spyOn(console, 'info').mockImplementation(() => {});
47
- const logger = new ConsoleLogger();
48
-
49
- logger.info('test');
50
-
51
- expect(spy).toHaveBeenCalledWith('[workflow] [INFO]', 'test');
52
- spy.mockRestore();
53
- });
54
- });
55
-
56
- describe('SilentLogger', () => {
57
- it('should not throw on any log level', () => {
58
- const logger = new SilentLogger();
59
-
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();
64
- });
65
- });
66
-
67
- describe('createScopedLogger', () => {
68
- it('should prefix messages with run ID', () => {
69
- const calls: string[] = [];
70
- const baseLogger = {
71
- debug: (msg: string) => calls.push(`debug:${msg}`),
72
- info: (msg: string) => calls.push(`info:${msg}`),
73
- warn: (msg: string) => calls.push(`warn:${msg}`),
74
- error: (msg: string) => calls.push(`error:${msg}`),
75
- };
76
-
77
- const scoped = createScopedLogger(baseLogger, 'run-123');
78
-
79
- scoped.debug('d');
80
- scoped.info('i');
81
- scoped.warn('w');
82
- scoped.error('e');
83
-
84
- expect(calls).toEqual([
85
- 'debug:[run:run-123] d',
86
- 'info:[run:run-123] i',
87
- 'warn:[run:run-123] w',
88
- 'error:[run:run-123] e',
89
- ]);
90
- });
91
-
92
- it('should include step key when provided', () => {
93
- const calls: string[] = [];
94
- const baseLogger = {
95
- debug: (msg: string) => calls.push(msg),
96
- info: (msg: string) => calls.push(msg),
97
- warn: (msg: string) => calls.push(msg),
98
- error: (msg: string) => calls.push(msg),
99
- };
100
-
101
- const scoped = createScopedLogger(baseLogger, 'run-1', 'step-a');
102
- scoped.info('hello');
103
-
104
- expect(calls[0]).toBe('[run:run-1][step:step-a] hello');
105
- });
106
-
107
- it('should forward extra arguments', () => {
108
- const captured: unknown[][] = [];
109
- const baseLogger = {
110
- debug: (...args: unknown[]) => captured.push(args),
111
- info: (...args: unknown[]) => captured.push(args),
112
- warn: (...args: unknown[]) => captured.push(args),
113
- error: (...args: unknown[]) => captured.push(args),
114
- };
115
-
116
- const scoped = createScopedLogger(baseLogger, 'r1');
117
- scoped.info('msg', { data: 1 }, 42);
118
-
119
- expect(captured[0]).toEqual(['[run:r1] msg', { data: 1 }, 42]);
120
- });
121
- });
@@ -1,96 +0,0 @@
1
- /**
2
- * Logger utilities for the workflow engine.
3
- */
4
-
5
- import type { Logger, WorkflowError } from '../core/types';
6
-
7
- /** Log level for ConsoleLogger. Levels are ordered: debug < info < warn < error. */
8
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
9
-
10
- const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
11
- debug: 0,
12
- info: 1,
13
- warn: 2,
14
- error: 3,
15
- };
16
-
17
- /**
18
- * Console-based logger implementation.
19
- * Uses console.log with prefixes for different levels.
20
- * Supports a configurable minimum log level (default: 'info').
21
- */
22
- export class ConsoleLogger implements Logger {
23
- private prefix: string;
24
- private minLevel: number;
25
-
26
- constructor(prefix = '[workflow]', level: LogLevel = 'info') {
27
- this.prefix = prefix;
28
- this.minLevel = LOG_LEVEL_PRIORITY[level];
29
- }
30
-
31
- debug(message: string, ...args: unknown[]): void {
32
- if (this.minLevel <= LOG_LEVEL_PRIORITY.debug) {
33
- console.debug(`${this.prefix} [DEBUG]`, message, ...args);
34
- }
35
- }
36
-
37
- info(message: string, ...args: unknown[]): void {
38
- if (this.minLevel <= LOG_LEVEL_PRIORITY.info) {
39
- console.info(`${this.prefix} [INFO]`, message, ...args);
40
- }
41
- }
42
-
43
- warn(message: string, ...args: unknown[]): void {
44
- if (this.minLevel <= LOG_LEVEL_PRIORITY.warn) {
45
- console.warn(`${this.prefix} [WARN]`, message, ...args);
46
- }
47
- }
48
-
49
- error(message: string, ...args: unknown[]): void {
50
- if (this.minLevel <= LOG_LEVEL_PRIORITY.error) {
51
- console.error(`${this.prefix} [ERROR]`, message, ...args);
52
- }
53
- }
54
- }
55
-
56
- /**
57
- * Silent logger that does nothing.
58
- * Useful for testing or when logs are not wanted.
59
- */
60
- export class SilentLogger implements Logger {
61
- debug(): void {}
62
- info(): void {}
63
- warn(): void {}
64
- error(): void {}
65
- }
66
-
67
- /**
68
- * Strip stack traces from a WorkflowError before persisting to storage.
69
- * Stack traces expose internal file paths and should be kept in logs only.
70
- */
71
- export function sanitizeErrorForStorage(error: WorkflowError): WorkflowError {
72
- const { stack: _stack, ...rest } = error;
73
- return rest;
74
- }
75
-
76
- /**
77
- * Create a scoped logger that includes run/step context.
78
- */
79
- export function createScopedLogger(
80
- logger: Logger,
81
- runId: string,
82
- stepKey?: string
83
- ): Logger {
84
- const prefix = stepKey ? `[run:${runId}][step:${stepKey}]` : `[run:${runId}]`;
85
-
86
- return {
87
- debug: (message: string, ...args: unknown[]) =>
88
- logger.debug(`${prefix} ${message}`, ...args),
89
- info: (message: string, ...args: unknown[]) =>
90
- logger.info(`${prefix} ${message}`, ...args),
91
- warn: (message: string, ...args: unknown[]) =>
92
- logger.warn(`${prefix} ${message}`, ...args),
93
- error: (message: string, ...args: unknown[]) =>
94
- logger.error(`${prefix} ${message}`, ...args),
95
- };
96
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * Shared lazy-loader for PostgreSQL dependencies (kysely + pg).
3
- *
4
- * Both `PostgresStorageAdapter` and `PostgresSchedulePersistence` need the same
5
- * set of optional peer dependencies. This module loads them once and caches the
6
- * result so the logic isn't duplicated across files.
7
- */
8
-
9
- /**
10
- * Dynamic imports prevent static type resolution for these modules;
11
- * `any` is intentional here — callers cast to the appropriate Kysely/pg types.
12
- */
13
- export interface PostgresDeps {
14
- Kysely: any;
15
- PostgresDialect: any;
16
- sql: any;
17
- pgModule: any;
18
- }
19
-
20
- let cached: PostgresDeps | undefined;
21
- let loadingPromise: Promise<PostgresDeps> | undefined;
22
-
23
- /**
24
- * Lazily load and cache the PostgreSQL peer dependencies (kysely + pg).
25
- * @returns The cached `{Kysely, PostgresDialect, sql, pgModule}` object.
26
- * @throws {Error} If the `kysely` optional peer dependency is not installed.
27
- * @throws {Error} If the `pg` optional peer dependency is not installed.
28
- */
29
- export async function loadPostgresDeps(): Promise<PostgresDeps> {
30
- if (cached) return cached;
31
- if (loadingPromise) return loadingPromise;
32
-
33
- loadingPromise = (async () => {
34
- let Kysely: any;
35
- let PostgresDialect: any;
36
- let sql: any;
37
- let pgModule: any;
38
-
39
- try {
40
- const kyselyMod = await import('kysely');
41
- Kysely = kyselyMod.Kysely;
42
- PostgresDialect = kyselyMod.PostgresDialect;
43
- sql = kyselyMod.sql;
44
- } catch {
45
- throw new Error(
46
- 'PostgreSQL support requires the "kysely" package. Install it with: npm install kysely'
47
- );
48
- }
49
- try {
50
- const pg = await import('pg');
51
- pgModule = pg.default ?? pg;
52
- } catch {
53
- throw new Error(
54
- 'PostgreSQL support requires the "pg" package. Install it with: npm install pg'
55
- );
56
- }
57
-
58
- cached = { Kysely, PostgresDialect, sql, pgModule };
59
- return cached;
60
- })();
61
-
62
- return loadingPromise;
63
- }
@@ -1,177 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { sleep, withRetry, calculateRetryDelay, DEFAULT_RETRY_OPTIONS } from './retry';
3
- import { WorkflowCanceledError } from './errors';
4
-
5
- describe('sleep', () => {
6
- it('should resolve after the given time', async () => {
7
- const start = Date.now();
8
- await sleep(50);
9
- const elapsed = Date.now() - start;
10
- expect(elapsed).toBeGreaterThanOrEqual(40);
11
- });
12
-
13
- it('should reject immediately if signal is already aborted', async () => {
14
- const controller = new AbortController();
15
- controller.abort();
16
-
17
- await expect(sleep(1000, controller.signal)).rejects.toThrow(WorkflowCanceledError);
18
- });
19
-
20
- it('should reject when signal is aborted during sleep', async () => {
21
- const controller = new AbortController();
22
-
23
- const promise = sleep(5000, controller.signal);
24
- setTimeout(() => controller.abort(), 20);
25
-
26
- await expect(promise).rejects.toThrow(WorkflowCanceledError);
27
- });
28
- });
29
-
30
- describe('calculateRetryDelay', () => {
31
- it('should return base delay for first attempt', () => {
32
- expect(calculateRetryDelay(1, 1000, 2)).toBe(1000);
33
- });
34
-
35
- it('should apply backoff multiplier', () => {
36
- expect(calculateRetryDelay(2, 1000, 2)).toBe(2000);
37
- expect(calculateRetryDelay(3, 1000, 2)).toBe(4000);
38
- });
39
-
40
- it('should work with non-integer backoff', () => {
41
- expect(calculateRetryDelay(2, 100, 1.5)).toBe(150);
42
- });
43
- });
44
-
45
- describe('DEFAULT_RETRY_OPTIONS', () => {
46
- it('should have expected defaults', () => {
47
- expect(DEFAULT_RETRY_OPTIONS.maxRetries).toBe(3);
48
- expect(DEFAULT_RETRY_OPTIONS.delay).toBe(1000);
49
- expect(DEFAULT_RETRY_OPTIONS.backoff).toBe(2);
50
- });
51
- });
52
-
53
- describe('withRetry', () => {
54
- it('should return result on first success', async () => {
55
- const fn = vi.fn().mockResolvedValue('ok');
56
- const result = await withRetry(fn, { maxRetries: 3, delay: 10, backoff: 1 });
57
-
58
- expect(result).toBe('ok');
59
- expect(fn).toHaveBeenCalledTimes(1);
60
- });
61
-
62
- it('should retry on failure and succeed', async () => {
63
- let attempt = 0;
64
- const fn = vi.fn(async () => {
65
- attempt++;
66
- if (attempt < 3) throw new Error(`fail ${attempt}`);
67
- return 'success';
68
- });
69
-
70
- const result = await withRetry(fn, { maxRetries: 3, delay: 10, backoff: 1 });
71
-
72
- expect(result).toBe('success');
73
- expect(fn).toHaveBeenCalledTimes(3);
74
- });
75
-
76
- it('should throw after exhausting retries', async () => {
77
- const fn = vi.fn().mockRejectedValue(new Error('always fails'));
78
-
79
- await expect(
80
- withRetry(fn, { maxRetries: 2, delay: 10, backoff: 1 })
81
- ).rejects.toThrow('always fails');
82
-
83
- // 1 initial + 2 retries = 3 calls
84
- expect(fn).toHaveBeenCalledTimes(3);
85
- });
86
-
87
- it('should call onRetry callback', async () => {
88
- let attempt = 0;
89
- const onRetryCalls: Array<{ attempt: number; delay: number }> = [];
90
-
91
- const fn = vi.fn(async () => {
92
- attempt++;
93
- if (attempt < 3) throw new Error('fail');
94
- return 'ok';
95
- });
96
-
97
- await withRetry(fn, {
98
- maxRetries: 3,
99
- delay: 100,
100
- backoff: 2,
101
- onRetry: (a, _err, d) => onRetryCalls.push({ attempt: a, delay: d }),
102
- });
103
-
104
- expect(onRetryCalls).toHaveLength(2);
105
- expect(onRetryCalls[0]).toEqual({ attempt: 1, delay: 100 });
106
- expect(onRetryCalls[1]).toEqual({ attempt: 2, delay: 200 });
107
- });
108
-
109
- it('should respect abort signal before first attempt', async () => {
110
- const controller = new AbortController();
111
- controller.abort();
112
-
113
- const fn = vi.fn().mockResolvedValue('ok');
114
-
115
- await expect(
116
- withRetry(fn, { maxRetries: 3, delay: 10, backoff: 1, signal: controller.signal })
117
- ).rejects.toThrow('canceled');
118
-
119
- expect(fn).not.toHaveBeenCalled();
120
- });
121
-
122
- it('should respect abort signal during retry wait', async () => {
123
- const controller = new AbortController();
124
-
125
- const fn = vi.fn().mockRejectedValue(new Error('fail'));
126
-
127
- const promise = withRetry(fn, {
128
- maxRetries: 5,
129
- delay: 5000,
130
- backoff: 1,
131
- signal: controller.signal,
132
- });
133
-
134
- // Abort during the retry delay
135
- setTimeout(() => controller.abort(), 50);
136
-
137
- await expect(promise).rejects.toThrow(WorkflowCanceledError);
138
- });
139
-
140
- it('should apply backoff between retries', async () => {
141
- const timestamps: number[] = [];
142
- let attempt = 0;
143
-
144
- const fn = vi.fn(async () => {
145
- timestamps.push(Date.now());
146
- attempt++;
147
- if (attempt <= 2) throw new Error('fail');
148
- return 'ok';
149
- });
150
-
151
- await withRetry(fn, { maxRetries: 3, delay: 50, backoff: 2 });
152
-
153
- // Check that delay increases between retries
154
- const gap1 = timestamps[1] - timestamps[0];
155
- const gap2 = timestamps[2] - timestamps[1];
156
-
157
- expect(gap1).toBeGreaterThanOrEqual(40); // ~50ms first delay
158
- expect(gap2).toBeGreaterThanOrEqual(80); // ~100ms second delay (50*2)
159
- });
160
-
161
- it('should convert non-Error throws to Error', async () => {
162
- const fn = vi.fn(async () => {
163
- throw 'string error';
164
- });
165
-
166
- await expect(
167
- withRetry(fn, { maxRetries: 0, delay: 10, backoff: 1 })
168
- ).rejects.toThrow('string error');
169
- });
170
-
171
- it('should use default options when none provided', async () => {
172
- const fn = vi.fn().mockResolvedValue('ok');
173
- const result = await withRetry(fn);
174
-
175
- expect(result).toBe('ok');
176
- });
177
- });
@@ -1,121 +0,0 @@
1
- /**
2
- * Retry utilities for the workflow engine.
3
- */
4
-
5
- import { WorkflowCanceledError } from './errors';
6
-
7
- /**
8
- * Options for retry behavior.
9
- */
10
- export interface RetryOptions {
11
- /** Maximum number of retry attempts (default: 3) */
12
- maxRetries: number;
13
- /** Initial delay between retries in ms (default: 1000) */
14
- delay: number;
15
- /** Backoff multiplier (default: 2) */
16
- backoff: number;
17
- /** Optional abort signal to cancel retries */
18
- signal?: AbortSignal;
19
- /** Optional callback before each retry */
20
- onRetry?: (attempt: number, error: Error, nextDelay: number) => void;
21
- }
22
-
23
- /**
24
- * Default retry options.
25
- */
26
- export const DEFAULT_RETRY_OPTIONS: RetryOptions = {
27
- maxRetries: 3,
28
- delay: 1000,
29
- backoff: 2,
30
- };
31
-
32
- /**
33
- * Sleep for a given number of milliseconds.
34
- * Can be canceled via AbortSignal.
35
- */
36
- export function sleep(ms: number, signal?: AbortSignal): Promise<void> {
37
- return new Promise((resolve, reject) => {
38
- if (signal?.aborted) {
39
- reject(new WorkflowCanceledError('run'));
40
- return;
41
- }
42
-
43
- const cleanup = () => {
44
- clearTimeout(timeoutId);
45
- if (onAbort) signal?.removeEventListener('abort', onAbort);
46
- };
47
-
48
- let onAbort: (() => void) | undefined;
49
-
50
- const timeoutId = setTimeout(() => {
51
- cleanup();
52
- resolve();
53
- }, ms);
54
-
55
- if (signal) {
56
- onAbort = () => {
57
- cleanup();
58
- reject(new WorkflowCanceledError('run'));
59
- };
60
- signal.addEventListener('abort', onAbort, { once: true });
61
- }
62
- });
63
- }
64
-
65
- /**
66
- * Execute a function with retry logic.
67
- *
68
- * @param fn - The async function to execute
69
- * @param options - Retry configuration
70
- * @returns The result of the function
71
- * @throws The last error if all retries are exhausted
72
- */
73
- export async function withRetry<T>(
74
- fn: () => Promise<T>,
75
- options: Partial<RetryOptions> = {}
76
- ): Promise<T> {
77
- const opts: RetryOptions = { ...DEFAULT_RETRY_OPTIONS, ...options };
78
- let lastError: Error | undefined;
79
- let currentDelay = opts.delay;
80
-
81
- for (let attempt = 1; attempt <= opts.maxRetries + 1; attempt++) {
82
- // Check if aborted before attempting
83
- if (opts.signal?.aborted) {
84
- throw new WorkflowCanceledError('run');
85
- }
86
-
87
- try {
88
- return await fn();
89
- } catch (error) {
90
- lastError = error instanceof Error ? error : new Error(String(error));
91
-
92
- // If this was the last attempt, throw
93
- if (attempt > opts.maxRetries) {
94
- throw lastError;
95
- }
96
-
97
- // Call onRetry callback if provided
98
- opts.onRetry?.(attempt, lastError, currentDelay);
99
-
100
- // Wait before next attempt
101
- await sleep(currentDelay, opts.signal);
102
-
103
- // Apply backoff multiplier
104
- currentDelay = Math.round(currentDelay * opts.backoff);
105
- }
106
- }
107
-
108
- // This should never be reached, but TypeScript needs it
109
- throw lastError ?? new Error('Retry failed');
110
- }
111
-
112
- /**
113
- * Calculate the delay for a specific retry attempt.
114
- */
115
- export function calculateRetryDelay(
116
- attempt: number,
117
- baseDelay: number,
118
- backoff: number
119
- ): number {
120
- return Math.round(baseDelay * Math.pow(backoff, attempt - 1));
121
- }
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "lib": ["ES2022", "DOM"],
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "declaration": true,
11
- "declarationMap": true,
12
- "outDir": "./dist",
13
- "rootDir": "./src",
14
- "forceConsistentCasingInFileNames": true,
15
- "resolveJsonModule": true,
16
- "isolatedModules": true,
17
- "noEmit": true
18
- },
19
- "include": ["src/**/*"],
20
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
- }
package/tsup.config.ts DELETED
@@ -1,14 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: {
5
- index: 'src/index.ts',
6
- 'storage/index': 'src/storage/index.ts',
7
- 'events/index': 'src/events/index.ts',
8
- 'scheduler/index': 'src/scheduler/index.ts',
9
- },
10
- format: ['esm'],
11
- dts: true,
12
- clean: true,
13
- sourcemap: true,
14
- });
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- include: ['src/**/*.test.ts'],
8
- },
9
- });