pure-effect 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +3 -1
- package/.dirac-symbol-index/data.db +0 -0
- package/CLAUDE.md +29 -15
- package/README.md +261 -100
- package/index.d.ts +197 -73
- package/index.js +139 -33
- package/package.json +2 -2
- package/test/all.js +196 -20
- package/test/types.test-d.ts +148 -2
package/index.d.ts
CHANGED
|
@@ -10,96 +10,213 @@ export type FailureState<E = unknown> = {
|
|
|
10
10
|
initialInput?: unknown;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export type CommandState<R, T, E = unknown> = {
|
|
13
|
+
export type CommandState<R, T, E = unknown, Ctx = unknown> = {
|
|
14
14
|
type: 'Command';
|
|
15
15
|
cmd: () => Promise<R> | R;
|
|
16
|
-
next: (result: R) => Effect<T, E>;
|
|
16
|
+
next: (result: R) => Effect<T, E, Ctx>;
|
|
17
17
|
meta?: unknown;
|
|
18
18
|
initialInput?: unknown;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export type AskState<T, E = unknown> = {
|
|
21
|
+
export type AskState<T, E = unknown, Ctx = unknown> = {
|
|
22
22
|
type: 'Ask';
|
|
23
|
-
next: (context:
|
|
23
|
+
next: (context: Ctx) => Effect<T, E, Ctx>;
|
|
24
24
|
initialInput?: unknown;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
export type
|
|
27
|
+
export type RetryOptions = {
|
|
28
|
+
attempts?: number;
|
|
29
|
+
delay?: number;
|
|
30
|
+
backoff?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type RetryState<T, E = unknown, Ctx = unknown> = {
|
|
34
|
+
type: 'Retry';
|
|
35
|
+
effect: Effect<T, E, Ctx>;
|
|
36
|
+
options: RetryOptions;
|
|
37
|
+
next: (value: T) => Effect<T, E, Ctx>;
|
|
38
|
+
initialInput?: unknown;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type RetryExhaustedError<E = unknown> = {
|
|
42
|
+
retryExhausted: true;
|
|
43
|
+
lastError: E;
|
|
44
|
+
attempts: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ParallelState<T extends readonly unknown[], R, E = unknown, Ctx = unknown> = {
|
|
48
|
+
type: 'Parallel';
|
|
49
|
+
effects: { [K in keyof T]: Effect<T[K], E, Ctx> };
|
|
50
|
+
next: (values: [...T]) => Effect<R, E, Ctx>;
|
|
51
|
+
initialInput?: unknown;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type Effect<T, E = unknown, Ctx = unknown> =
|
|
55
|
+
| SuccessState<T>
|
|
56
|
+
| FailureState<E>
|
|
57
|
+
| CommandState<any, T, E, Ctx>
|
|
58
|
+
| AskState<T, E, Ctx>
|
|
59
|
+
| RetryState<T, E, Ctx>
|
|
60
|
+
| ParallelState<any, T, E, Ctx>;
|
|
28
61
|
|
|
29
62
|
export declare function Success<T>(value: T): SuccessState<T>;
|
|
30
63
|
|
|
31
64
|
export declare function Failure<E = unknown>(error: E, initialInput?: unknown): FailureState<E>;
|
|
32
65
|
|
|
33
|
-
export declare function Command<R, T, E = unknown>(
|
|
66
|
+
export declare function Command<R, T, E = unknown, Ctx = unknown>(
|
|
34
67
|
cmd: () => Promise<R> | R,
|
|
35
|
-
next: (result: R) => Effect<T, E>,
|
|
68
|
+
next: (result: R) => Effect<T, E, Ctx>,
|
|
36
69
|
meta?: unknown
|
|
37
|
-
): CommandState<R, T, E>;
|
|
38
|
-
|
|
39
|
-
export declare function Ask<T, E = unknown
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export declare function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
):
|
|
47
|
-
|
|
48
|
-
export declare function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
70
|
+
): CommandState<R, T, E, Ctx>;
|
|
71
|
+
|
|
72
|
+
export declare function Ask<T, E = unknown, Ctx = unknown>(
|
|
73
|
+
next: (context: Ctx) => Effect<T, E, Ctx>
|
|
74
|
+
): AskState<T, E, Ctx>;
|
|
75
|
+
|
|
76
|
+
export declare function Retry<T, E = unknown, Ctx = unknown>(
|
|
77
|
+
effect: Effect<T, E, Ctx>,
|
|
78
|
+
options?: RetryOptions
|
|
79
|
+
): RetryState<T, E, Ctx>;
|
|
80
|
+
|
|
81
|
+
export declare function Parallel<T extends readonly unknown[], R, E = unknown, Ctx = unknown>(
|
|
82
|
+
effects: { [K in keyof T]: Effect<T[K], E, Ctx> },
|
|
83
|
+
next: (values: [...T]) => Effect<R, E, Ctx>
|
|
84
|
+
): ParallelState<[...T], R, E, Ctx>;
|
|
85
|
+
|
|
86
|
+
export declare function effectPipe<A, B, E1 = unknown, Ctx = unknown>(
|
|
87
|
+
f1: (a: A) => Effect<B, E1, Ctx>
|
|
88
|
+
): (start: A) => Effect<B, E1, Ctx>;
|
|
89
|
+
|
|
90
|
+
export declare function effectPipe<A, B, C, E1 = unknown, E2 = unknown, Ctx = unknown>(
|
|
91
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
92
|
+
f2: (b: B) => Effect<C, E2, Ctx>
|
|
93
|
+
): (start: A) => Effect<C, E1 | E2, Ctx>;
|
|
94
|
+
|
|
95
|
+
export declare function effectPipe<A, B, C, D, E1 = unknown, E2 = unknown, E3 = unknown, Ctx = unknown>(
|
|
96
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
97
|
+
f2: (b: B) => Effect<C, E2, Ctx>,
|
|
98
|
+
f3: (c: C) => Effect<D, E3, Ctx>
|
|
99
|
+
): (start: A) => Effect<D, E1 | E2 | E3, Ctx>;
|
|
100
|
+
|
|
101
|
+
export declare function effectPipe<
|
|
102
|
+
A,
|
|
103
|
+
B,
|
|
104
|
+
C,
|
|
105
|
+
D,
|
|
106
|
+
F,
|
|
107
|
+
E1 = unknown,
|
|
108
|
+
E2 = unknown,
|
|
109
|
+
E3 = unknown,
|
|
110
|
+
E4 = unknown,
|
|
111
|
+
Ctx = unknown
|
|
112
|
+
>(
|
|
113
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
114
|
+
f2: (b: B) => Effect<C, E2, Ctx>,
|
|
115
|
+
f3: (c: C) => Effect<D, E3, Ctx>,
|
|
116
|
+
f4: (d: D) => Effect<F, E4, Ctx>
|
|
117
|
+
): (start: A) => Effect<F, E1 | E2 | E3 | E4, Ctx>;
|
|
118
|
+
|
|
119
|
+
export declare function effectPipe<
|
|
120
|
+
A,
|
|
121
|
+
B,
|
|
122
|
+
C,
|
|
123
|
+
D,
|
|
124
|
+
F,
|
|
125
|
+
G,
|
|
126
|
+
E1 = unknown,
|
|
127
|
+
E2 = unknown,
|
|
128
|
+
E3 = unknown,
|
|
129
|
+
E4 = unknown,
|
|
130
|
+
E5 = unknown,
|
|
131
|
+
Ctx = unknown
|
|
132
|
+
>(
|
|
133
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
134
|
+
f2: (b: B) => Effect<C, E2, Ctx>,
|
|
135
|
+
f3: (c: C) => Effect<D, E3, Ctx>,
|
|
136
|
+
f4: (d: D) => Effect<F, E4, Ctx>,
|
|
137
|
+
f5: (f: F) => Effect<G, E5, Ctx>
|
|
138
|
+
): (start: A) => Effect<G, E1 | E2 | E3 | E4 | E5, Ctx>;
|
|
139
|
+
|
|
140
|
+
export declare function effectPipe<
|
|
141
|
+
A,
|
|
142
|
+
B,
|
|
143
|
+
C,
|
|
144
|
+
D,
|
|
145
|
+
F,
|
|
146
|
+
G,
|
|
147
|
+
H,
|
|
148
|
+
E1 = unknown,
|
|
149
|
+
E2 = unknown,
|
|
150
|
+
E3 = unknown,
|
|
151
|
+
E4 = unknown,
|
|
152
|
+
E5 = unknown,
|
|
153
|
+
E6 = unknown,
|
|
154
|
+
Ctx = unknown
|
|
155
|
+
>(
|
|
156
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
157
|
+
f2: (b: B) => Effect<C, E2, Ctx>,
|
|
158
|
+
f3: (c: C) => Effect<D, E3, Ctx>,
|
|
159
|
+
f4: (d: D) => Effect<F, E4, Ctx>,
|
|
160
|
+
f5: (f: F) => Effect<G, E5, Ctx>,
|
|
161
|
+
f6: (g: G) => Effect<H, E6, Ctx>
|
|
162
|
+
): (start: A) => Effect<H, E1 | E2 | E3 | E4 | E5 | E6, Ctx>;
|
|
163
|
+
|
|
164
|
+
export declare function effectPipe<
|
|
165
|
+
A,
|
|
166
|
+
B,
|
|
167
|
+
C,
|
|
168
|
+
D,
|
|
169
|
+
F,
|
|
170
|
+
G,
|
|
171
|
+
H,
|
|
172
|
+
I,
|
|
173
|
+
E1 = unknown,
|
|
174
|
+
E2 = unknown,
|
|
175
|
+
E3 = unknown,
|
|
176
|
+
E4 = unknown,
|
|
177
|
+
E5 = unknown,
|
|
178
|
+
E6 = unknown,
|
|
179
|
+
E7 = unknown,
|
|
180
|
+
Ctx = unknown
|
|
181
|
+
>(
|
|
182
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
183
|
+
f2: (b: B) => Effect<C, E2, Ctx>,
|
|
184
|
+
f3: (c: C) => Effect<D, E3, Ctx>,
|
|
185
|
+
f4: (d: D) => Effect<F, E4, Ctx>,
|
|
186
|
+
f5: (f: F) => Effect<G, E5, Ctx>,
|
|
187
|
+
f6: (g: G) => Effect<H, E6, Ctx>,
|
|
188
|
+
f7: (h: H) => Effect<I, E7, Ctx>
|
|
189
|
+
): (start: A) => Effect<I, E1 | E2 | E3 | E4 | E5 | E6 | E7, Ctx>;
|
|
190
|
+
|
|
191
|
+
export declare function effectPipe<
|
|
192
|
+
A,
|
|
193
|
+
B,
|
|
194
|
+
C,
|
|
195
|
+
D,
|
|
196
|
+
F,
|
|
197
|
+
G,
|
|
198
|
+
H,
|
|
199
|
+
I,
|
|
200
|
+
J,
|
|
201
|
+
E1 = unknown,
|
|
202
|
+
E2 = unknown,
|
|
203
|
+
E3 = unknown,
|
|
204
|
+
E4 = unknown,
|
|
205
|
+
E5 = unknown,
|
|
206
|
+
E6 = unknown,
|
|
207
|
+
E7 = unknown,
|
|
208
|
+
E8 = unknown,
|
|
209
|
+
Ctx = unknown
|
|
210
|
+
>(
|
|
211
|
+
f1: (a: A) => Effect<B, E1, Ctx>,
|
|
212
|
+
f2: (b: B) => Effect<C, E2, Ctx>,
|
|
213
|
+
f3: (c: C) => Effect<D, E3, Ctx>,
|
|
214
|
+
f4: (d: D) => Effect<F, E4, Ctx>,
|
|
215
|
+
f5: (f: F) => Effect<G, E5, Ctx>,
|
|
216
|
+
f6: (g: G) => Effect<H, E6, Ctx>,
|
|
217
|
+
f7: (h: H) => Effect<I, E7, Ctx>,
|
|
218
|
+
f8: (i: I) => Effect<J, E8, Ctx>
|
|
219
|
+
): (start: A) => Effect<J, E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8, Ctx>;
|
|
103
220
|
|
|
104
221
|
export type StepRunner = (name: string, type: string, op: () => Promise<unknown>) => Promise<unknown>;
|
|
105
222
|
|
|
@@ -115,6 +232,13 @@ export interface EffectConfiguration {
|
|
|
115
232
|
onStep?: StepRunner;
|
|
116
233
|
onRun?: RunWrapper;
|
|
117
234
|
onBeforeCommand?: CommandInterceptor;
|
|
235
|
+
retry?: RetryOptions;
|
|
118
236
|
}
|
|
119
237
|
|
|
120
238
|
export declare function configureEffect(options: EffectConfiguration): void;
|
|
239
|
+
|
|
240
|
+
export declare function runEffect<T, E = unknown, Ctx = unknown>(
|
|
241
|
+
effect: Effect<T, E, Ctx>,
|
|
242
|
+
context?: Ctx,
|
|
243
|
+
callConfig?: EffectConfiguration
|
|
244
|
+
): Promise<SuccessState<T> | FailureState<E>>;
|
package/index.js
CHANGED
|
@@ -19,9 +19,28 @@
|
|
|
19
19
|
* }} AskState
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {{
|
|
24
|
+
* type: 'Retry',
|
|
25
|
+
* effect: Effect,
|
|
26
|
+
* options: { attempts?: number, delay?: number, backoff?: number },
|
|
27
|
+
* next: (value: any) => Effect,
|
|
28
|
+
* initialInput?: any
|
|
29
|
+
* }} RetryState
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {{
|
|
34
|
+
* type: 'Parallel',
|
|
35
|
+
* effects: Effect[],
|
|
36
|
+
* next: (values: any[]) => Effect,
|
|
37
|
+
* initialInput?: any
|
|
38
|
+
* }} ParallelState
|
|
39
|
+
*/
|
|
40
|
+
|
|
22
41
|
/**
|
|
23
42
|
* The Union type for all possible states
|
|
24
|
-
* @typedef {SuccessState | FailureState | CommandState | AskState} Effect
|
|
43
|
+
* @typedef {SuccessState | FailureState | CommandState | AskState | RetryState | ParallelState} Effect
|
|
25
44
|
*/
|
|
26
45
|
|
|
27
46
|
/**
|
|
@@ -59,27 +78,68 @@ const Command = (cmd, next, meta) => ({ type: 'Command', cmd, next, meta });
|
|
|
59
78
|
*/
|
|
60
79
|
const Ask = (next) => ({ type: 'Ask', next });
|
|
61
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Wraps an Effect tree with retry-on-failure semantics.
|
|
83
|
+
* @param {Effect} effect - The inner Effect tree to retry
|
|
84
|
+
* @param {Object} [options] - Per-use retry options; merged over global defaults at runtime
|
|
85
|
+
* @param {number} [options.attempts] - Max retries (not counting first try)
|
|
86
|
+
* @param {number} [options.delay] - Ms before first retry
|
|
87
|
+
* @param {number} [options.backoff] - Multiplier applied to delay on each subsequent retry
|
|
88
|
+
* @returns {RetryState}
|
|
89
|
+
*/
|
|
90
|
+
const Retry = (effect, options = {}) => ({
|
|
91
|
+
type: 'Retry',
|
|
92
|
+
effect,
|
|
93
|
+
options,
|
|
94
|
+
next: (value) => Success(value)
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Runs multiple Effect trees concurrently. If any effect fails, returns the first Failure by array order and skips next.
|
|
99
|
+
* @param {Effect[]} effects - Array of Effect trees to run concurrently
|
|
100
|
+
* @param {(values: any[]) => Effect} next - Receives array of success values in order, returns next Effect
|
|
101
|
+
* @returns {ParallelState}
|
|
102
|
+
*/
|
|
103
|
+
const Parallel = (effects, next) => ({ type: 'Parallel', effects, next });
|
|
104
|
+
|
|
62
105
|
/**
|
|
63
106
|
* Connects an Effect to the next function in the pipeline.
|
|
64
|
-
* Handles the branching logic for Success, Failure, Command, and
|
|
107
|
+
* Handles the branching logic for Success, Failure, Command, Ask, and Retry.
|
|
65
108
|
*
|
|
66
109
|
* @param {Effect} effect - The current Effect object
|
|
67
110
|
* @param {(value: any) => Effect} fn - The next function to run if the current effect is a Success
|
|
68
111
|
* @returns {Effect} The composed Effect
|
|
69
112
|
*/
|
|
70
|
-
|
|
113
|
+
/**
|
|
114
|
+
* @param {Effect} effect
|
|
115
|
+
* @param {(value: any) => Effect} fn
|
|
116
|
+
* @param {any} [initialInput]
|
|
117
|
+
* @returns {Effect}
|
|
118
|
+
*/
|
|
119
|
+
const chain = (effect, fn, initialInput) => {
|
|
120
|
+
const withII = (/** @type {Effect} */ e) =>
|
|
121
|
+
initialInput !== undefined && e.initialInput === undefined ? { ...e, initialInput } : e;
|
|
122
|
+
|
|
71
123
|
switch (effect.type) {
|
|
72
124
|
case 'Success':
|
|
73
|
-
return fn(effect.value);
|
|
125
|
+
return withII(fn(effect.value));
|
|
74
126
|
case 'Failure':
|
|
75
|
-
return effect;
|
|
127
|
+
return withII(effect);
|
|
76
128
|
case 'Command': {
|
|
77
|
-
const next = (/** @type {any} */ result) => chain(effect.next(result), fn);
|
|
78
|
-
return Command(effect.cmd, next, effect.meta);
|
|
129
|
+
const next = (/** @type {any} */ result) => chain(effect.next(result), fn, initialInput);
|
|
130
|
+
return withII(Command(effect.cmd, next, effect.meta));
|
|
79
131
|
}
|
|
80
132
|
case 'Ask': {
|
|
81
|
-
const next = (/** @type {any} */ ctx) => chain(effect.next(ctx), fn);
|
|
82
|
-
return Ask(next);
|
|
133
|
+
const next = (/** @type {any} */ ctx) => chain(effect.next(ctx), fn, initialInput);
|
|
134
|
+
return withII(Ask(next));
|
|
135
|
+
}
|
|
136
|
+
case 'Retry': {
|
|
137
|
+
const next = (/** @type {any} */ result) => chain(effect.next(result), fn, initialInput);
|
|
138
|
+
return withII({ ...effect, next });
|
|
139
|
+
}
|
|
140
|
+
case 'Parallel': {
|
|
141
|
+
const next = (/** @type {any} */ result) => chain(effect.next(result), fn, initialInput);
|
|
142
|
+
return withII({ ...effect, next });
|
|
83
143
|
}
|
|
84
144
|
}
|
|
85
145
|
};
|
|
@@ -93,9 +153,8 @@ const chain = (effect, fn) => {
|
|
|
93
153
|
*/
|
|
94
154
|
const effectPipe = (...fns) => {
|
|
95
155
|
return (start) => {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
return effect;
|
|
156
|
+
const chainWithII = (/** @type {Effect} */ eff, /** @type {(v: any) => Effect} */ fn) => chain(eff, fn, start);
|
|
157
|
+
return fns.reduce(chainWithII, /** @type {Effect} */ (Success(start)));
|
|
99
158
|
};
|
|
100
159
|
};
|
|
101
160
|
|
|
@@ -115,11 +174,15 @@ let stepRunner = defaultStepRunner;
|
|
|
115
174
|
let runWrapper = defaultRunWrapper;
|
|
116
175
|
let commandInterceptor = defaultCommandInterceptor;
|
|
117
176
|
|
|
177
|
+
const defaultRetryOptions = { attempts: 3, delay: 100, backoff: 1 };
|
|
178
|
+
let retryDefaults = { ...defaultRetryOptions };
|
|
179
|
+
|
|
118
180
|
/**
|
|
119
181
|
* @typedef {Object} EffectConfiguration
|
|
120
182
|
* @property {StepRunner} [onStep] - Fires once per runEffect call. It wraps the entire workflow execution.
|
|
121
183
|
* @property {RunWrapper} [onRun] - Fires every time a Command is executed.
|
|
122
184
|
* @property {CommandInterceptor} [onBeforeCommand] - Intercepts a Command and any context passed to runEffect before execution.
|
|
185
|
+
* @property {{ attempts?: number, delay?: number, backoff?: number }} [retry] - Global Retry defaults; merged under per-use options.
|
|
123
186
|
*/
|
|
124
187
|
|
|
125
188
|
/**
|
|
@@ -131,6 +194,7 @@ const configureEffect = (options) => {
|
|
|
131
194
|
stepRunner = options.onStep ? options.onStep : defaultStepRunner;
|
|
132
195
|
runWrapper = options.onRun ? options.onRun : defaultRunWrapper;
|
|
133
196
|
commandInterceptor = options.onBeforeCommand ? options.onBeforeCommand : defaultCommandInterceptor;
|
|
197
|
+
retryDefaults = options.retry ? { ...defaultRetryOptions, ...options.retry } : defaultRetryOptions;
|
|
134
198
|
};
|
|
135
199
|
|
|
136
200
|
const runEffect =
|
|
@@ -139,34 +203,76 @@ const runEffect =
|
|
|
139
203
|
* Iterates through the Effect tree, executing Commands and handling async flow.
|
|
140
204
|
* Ask effects are resolved synchronously with the context object.
|
|
141
205
|
*
|
|
206
|
+
* Per-call config takes precedence over global configureEffect defaults.
|
|
207
|
+
* onRun fires exactly once per runEffect call — Retry attempts run inside that
|
|
208
|
+
* single span rather than spawning their own, keeping telemetry non-duplicated.
|
|
209
|
+
*
|
|
142
210
|
* @param {Effect} effect - The Effect tree returned by a pipeline
|
|
143
211
|
* @param {any} [context] - Optional context object. Passed to Ask continuations and the Command Interceptor.
|
|
212
|
+
* @param {EffectConfiguration} [callConfig] - Per-call overrides; merged over global configureEffect defaults.
|
|
144
213
|
* @returns {Promise<SuccessState | FailureState>}
|
|
145
214
|
*/
|
|
146
|
-
async function runEffect(effect, context = {}) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
215
|
+
async function runEffect(effect, context = {}, callConfig = {}) {
|
|
216
|
+
const localStepRunner = callConfig.onStep ? callConfig.onStep : stepRunner;
|
|
217
|
+
const localRunWrapper = callConfig.onRun ? callConfig.onRun : runWrapper;
|
|
218
|
+
const localCommandInterceptor = callConfig.onBeforeCommand ? callConfig.onBeforeCommand : commandInterceptor;
|
|
219
|
+
const localRetryDefaults = callConfig.retry ? { ...retryDefaults, ...callConfig.retry } : retryDefaults;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @param {Effect} eff
|
|
223
|
+
* @returns {Promise<SuccessState | FailureState>}
|
|
224
|
+
*/
|
|
225
|
+
async function execute(eff) {
|
|
226
|
+
while (eff.type === 'Command' || eff.type === 'Ask' || eff.type === 'Retry' || eff.type === 'Parallel') {
|
|
227
|
+
if (eff.type === 'Ask') {
|
|
228
|
+
eff = eff.next(context);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (eff.type === 'Retry') {
|
|
232
|
+
const opts = { ...localRetryDefaults, ...eff.options };
|
|
233
|
+
const { attempts } = opts;
|
|
234
|
+
let lastError;
|
|
235
|
+
let succeeded = false;
|
|
236
|
+
|
|
237
|
+
for (let attempt = 0; attempt <= attempts; attempt++) {
|
|
238
|
+
if (attempt > 0) {
|
|
239
|
+
await new Promise((r) => setTimeout(r, opts.delay * Math.pow(opts.backoff, attempt - 1)));
|
|
240
|
+
}
|
|
241
|
+
const result = await execute(eff.effect);
|
|
242
|
+
if (result.type === 'Success') {
|
|
243
|
+
eff = eff.next(result.value);
|
|
244
|
+
succeeded = true;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
lastError = result.error;
|
|
154
248
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
await commandInterceptor(effect, context);
|
|
159
|
-
const result = await stepRunner(cmdName, 'Command', effect.cmd);
|
|
160
|
-
effect = effect.next(result);
|
|
161
|
-
} catch (e) {
|
|
162
|
-
return Failure(e, initialInput);
|
|
249
|
+
|
|
250
|
+
if (!succeeded) {
|
|
251
|
+
return Failure({ retryExhausted: true, lastError, attempts }, eff.initialInput);
|
|
163
252
|
}
|
|
253
|
+
continue;
|
|
164
254
|
}
|
|
255
|
+
if (eff.type === 'Parallel') {
|
|
256
|
+
const results = await Promise.all(eff.effects.map((e) => execute(e)));
|
|
257
|
+
const failure = results.find((r) => r.type === 'Failure');
|
|
258
|
+
if (failure) return failure;
|
|
259
|
+
eff = eff.next(results.map((r) => /** @type {SuccessState} */ (r).value));
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const cmdName = eff.cmd.name || 'anonymous';
|
|
263
|
+
const initialInput = eff.initialInput;
|
|
264
|
+
try {
|
|
265
|
+
await localCommandInterceptor(eff, context);
|
|
266
|
+
const result = await localStepRunner(cmdName, 'Command', eff.cmd);
|
|
267
|
+
eff = eff.next(result);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
return Failure(e, initialInput);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return eff;
|
|
273
|
+
}
|
|
165
274
|
|
|
166
|
-
|
|
167
|
-
},
|
|
168
|
-
context?.flowName || ''
|
|
169
|
-
);
|
|
275
|
+
return localRunWrapper(effect, () => execute(effect), context?.flowName || '');
|
|
170
276
|
};
|
|
171
277
|
|
|
172
|
-
export { Success, Failure, Command, Ask, effectPipe, runEffect, configureEffect };
|
|
278
|
+
export { Success, Failure, Command, Ask, Retry, Parallel, effectPipe, runEffect, configureEffect };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pure-effect",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "A zero-dependency effect library for JavaScript and TypeScript with built-in support for dependency injection, retry, and OpenTelemetry where business logic is plain data you can test without mocks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./index.js",
|
|
7
7
|
"types": "./index.d.ts",
|