runsheet 0.0.1 → 0.5.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/README.md +280 -14
- package/dist/index.cjs +599 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +330 -7
- package/dist/index.d.ts +330 -7
- package/dist/index.js +584 -29
- package/dist/index.js.map +1 -1
- package/llms.txt +231 -8
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,20 +1,5 @@
|
|
|
1
1
|
// src/define-step.ts
|
|
2
2
|
import { composable } from "composable-functions";
|
|
3
|
-
function defineStep(config) {
|
|
4
|
-
const wrappedRun = composable(config.run);
|
|
5
|
-
return Object.freeze({
|
|
6
|
-
name: config.name,
|
|
7
|
-
requires: config.requires ?? void 0,
|
|
8
|
-
provides: config.provides ?? void 0,
|
|
9
|
-
run: wrappedRun,
|
|
10
|
-
rollback: config.rollback ? async (ctx, output) => {
|
|
11
|
-
await config.rollback(
|
|
12
|
-
ctx,
|
|
13
|
-
output
|
|
14
|
-
);
|
|
15
|
-
} : void 0
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
3
|
|
|
19
4
|
// src/errors.ts
|
|
20
5
|
var RunsheetError = class extends Error {
|
|
@@ -30,6 +15,144 @@ var RunsheetError = class extends Error {
|
|
|
30
15
|
this.code = code;
|
|
31
16
|
}
|
|
32
17
|
};
|
|
18
|
+
var RequiresValidationError = class extends RunsheetError {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super("REQUIRES_VALIDATION", message);
|
|
21
|
+
this.name = "RequiresValidationError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var ProvidesValidationError = class extends RunsheetError {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super("PROVIDES_VALIDATION", message);
|
|
27
|
+
this.name = "ProvidesValidationError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var ArgsValidationError = class extends RunsheetError {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super("ARGS_VALIDATION", message);
|
|
33
|
+
this.name = "ArgsValidationError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var PredicateError = class extends RunsheetError {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super("PREDICATE", message);
|
|
39
|
+
this.name = "PredicateError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var TimeoutError = class extends RunsheetError {
|
|
43
|
+
/** The timeout duration in milliseconds that was exceeded. */
|
|
44
|
+
timeoutMs;
|
|
45
|
+
constructor(message, timeoutMs) {
|
|
46
|
+
super("TIMEOUT", message);
|
|
47
|
+
this.name = "TimeoutError";
|
|
48
|
+
this.timeoutMs = timeoutMs;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var RetryExhaustedError = class extends RunsheetError {
|
|
52
|
+
/** Total number of attempts (initial + retries). */
|
|
53
|
+
attempts;
|
|
54
|
+
constructor(message, attempts) {
|
|
55
|
+
super("RETRY_EXHAUSTED", message);
|
|
56
|
+
this.name = "RetryExhaustedError";
|
|
57
|
+
this.attempts = attempts;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var StrictOverlapError = class extends RunsheetError {
|
|
61
|
+
/** The key that is provided by multiple steps. */
|
|
62
|
+
key;
|
|
63
|
+
/** The names of the two steps that both provide the key. */
|
|
64
|
+
steps;
|
|
65
|
+
constructor(message, key, steps) {
|
|
66
|
+
super("STRICT_OVERLAP", message);
|
|
67
|
+
this.name = "StrictOverlapError";
|
|
68
|
+
this.key = key;
|
|
69
|
+
this.steps = steps;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var ChoiceNoMatchError = class extends RunsheetError {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super("CHOICE_NO_MATCH", message);
|
|
75
|
+
this.name = "ChoiceNoMatchError";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var UnknownError = class extends RunsheetError {
|
|
79
|
+
/** The original thrown value before stringification. */
|
|
80
|
+
originalValue;
|
|
81
|
+
constructor(message, originalValue) {
|
|
82
|
+
super("UNKNOWN", message);
|
|
83
|
+
this.name = "UnknownError";
|
|
84
|
+
this.originalValue = originalValue;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var RollbackError = class extends RunsheetError {
|
|
88
|
+
constructor(message) {
|
|
89
|
+
super("ROLLBACK", message);
|
|
90
|
+
this.name = "RollbackError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/define-step.ts
|
|
95
|
+
function withTimeout(run, stepName, ms) {
|
|
96
|
+
return async (ctx) => {
|
|
97
|
+
let timer;
|
|
98
|
+
const timeout = new Promise((resolve) => {
|
|
99
|
+
timer = setTimeout(() => {
|
|
100
|
+
const error = new TimeoutError(`${stepName} timed out after ${ms}ms`, ms);
|
|
101
|
+
resolve({ success: false, errors: [error] });
|
|
102
|
+
}, ms);
|
|
103
|
+
});
|
|
104
|
+
try {
|
|
105
|
+
return await Promise.race([run(ctx), timeout]);
|
|
106
|
+
} finally {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function computeDelay(policy, attempt) {
|
|
112
|
+
const base = policy.delay ?? 0;
|
|
113
|
+
if (base === 0) return 0;
|
|
114
|
+
const strategy = policy.backoff ?? "linear";
|
|
115
|
+
return strategy === "exponential" ? base * 2 ** (attempt - 1) : base * attempt;
|
|
116
|
+
}
|
|
117
|
+
function withRetry(run, stepName, policy) {
|
|
118
|
+
return async (ctx) => {
|
|
119
|
+
let lastResult = await run(ctx);
|
|
120
|
+
if (lastResult.success) return lastResult;
|
|
121
|
+
for (let attempt = 1; attempt <= policy.count; attempt++) {
|
|
122
|
+
if (policy.retryIf && !policy.retryIf(lastResult.errors)) return lastResult;
|
|
123
|
+
const delay = computeDelay(policy, attempt);
|
|
124
|
+
if (delay > 0) await new Promise((r) => setTimeout(r, delay));
|
|
125
|
+
lastResult = await run(ctx);
|
|
126
|
+
if (lastResult.success) return lastResult;
|
|
127
|
+
}
|
|
128
|
+
const error = new RetryExhaustedError(
|
|
129
|
+
`${stepName} failed after ${policy.count} retries`,
|
|
130
|
+
policy.count + 1
|
|
131
|
+
);
|
|
132
|
+
return { success: false, errors: [...lastResult.errors, error] };
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function wrapWithTimeoutAndRetry(run, stepName, timeout, retry) {
|
|
136
|
+
let wrapped = run;
|
|
137
|
+
if (timeout !== void 0) wrapped = withTimeout(wrapped, stepName, timeout);
|
|
138
|
+
if (retry !== void 0) wrapped = withRetry(wrapped, stepName, retry);
|
|
139
|
+
return wrapped;
|
|
140
|
+
}
|
|
141
|
+
function defineStep(config) {
|
|
142
|
+
const baseRun = composable(config.run);
|
|
143
|
+
const wrappedRun = wrapWithTimeoutAndRetry(baseRun, config.name, config.timeout, config.retry);
|
|
144
|
+
return Object.freeze({
|
|
145
|
+
name: config.name,
|
|
146
|
+
requires: config.requires ?? void 0,
|
|
147
|
+
provides: config.provides ?? void 0,
|
|
148
|
+
run: wrappedRun,
|
|
149
|
+
rollback: config.rollback ? async (ctx, output) => {
|
|
150
|
+
await config.rollback(ctx, output);
|
|
151
|
+
} : void 0,
|
|
152
|
+
retry: config.retry ?? void 0,
|
|
153
|
+
timeout: config.timeout ?? void 0
|
|
154
|
+
});
|
|
155
|
+
}
|
|
33
156
|
|
|
34
157
|
// src/middleware.ts
|
|
35
158
|
function applyMiddleware(middlewares, step, executor) {
|
|
@@ -44,16 +167,68 @@ function when(predicate, step) {
|
|
|
44
167
|
});
|
|
45
168
|
}
|
|
46
169
|
function isConditionalStep(step) {
|
|
47
|
-
return "predicate" in step && typeof step
|
|
170
|
+
return "predicate" in step && typeof step.predicate === "function";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/internal.ts
|
|
174
|
+
function toError(err) {
|
|
175
|
+
if (err instanceof Error) return err;
|
|
176
|
+
return new UnknownError(String(err), err);
|
|
177
|
+
}
|
|
178
|
+
function validateInnerSchema(schema, data, label, ErrorClass) {
|
|
179
|
+
if (!schema) return null;
|
|
180
|
+
const parsed = schema.safeParse(data);
|
|
181
|
+
if (parsed.success) return null;
|
|
182
|
+
return parsed.error.issues.map(
|
|
183
|
+
(issue) => new ErrorClass(`${label}: ${issue.path.join(".")}: ${issue.message}`)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
async function runInnerStep(step, ctx) {
|
|
187
|
+
const requiresErrors = validateInnerSchema(
|
|
188
|
+
step.requires,
|
|
189
|
+
ctx,
|
|
190
|
+
`${step.name} requires`,
|
|
191
|
+
RequiresValidationError
|
|
192
|
+
);
|
|
193
|
+
if (requiresErrors) return { success: false, errors: requiresErrors };
|
|
194
|
+
const result = await step.run(ctx);
|
|
195
|
+
if (!result.success) return result;
|
|
196
|
+
const providesErrors = validateInnerSchema(
|
|
197
|
+
step.provides,
|
|
198
|
+
result.data,
|
|
199
|
+
`${step.name} provides`,
|
|
200
|
+
ProvidesValidationError
|
|
201
|
+
);
|
|
202
|
+
if (providesErrors) return { success: false, errors: providesErrors };
|
|
203
|
+
return result;
|
|
48
204
|
}
|
|
49
205
|
|
|
50
206
|
// src/pipeline.ts
|
|
51
|
-
function
|
|
207
|
+
function checkStrictOverlap(steps) {
|
|
208
|
+
const seen = /* @__PURE__ */ new Map();
|
|
209
|
+
for (const step of steps) {
|
|
210
|
+
if (!step.provides) continue;
|
|
211
|
+
const shape = step.provides.shape;
|
|
212
|
+
if (!shape || typeof shape !== "object") continue;
|
|
213
|
+
for (const key of Object.keys(shape)) {
|
|
214
|
+
const existing = seen.get(key);
|
|
215
|
+
if (existing) {
|
|
216
|
+
throw new StrictOverlapError(
|
|
217
|
+
`strict mode: key "${key}" is provided by both "${existing}" and "${step.name}"`,
|
|
218
|
+
key,
|
|
219
|
+
[existing, step.name]
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
seen.set(key, step.name);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function validateSchema(schema, data, label, ErrorClass) {
|
|
52
227
|
if (!schema) return { success: true, data };
|
|
53
228
|
const parsed = schema.safeParse(data);
|
|
54
229
|
if (parsed.success) return { success: true, data: parsed.data };
|
|
55
230
|
const errors = parsed.error.issues.map(
|
|
56
|
-
(issue) => new
|
|
231
|
+
(issue) => new ErrorClass(`${label}: ${issue.path.join(".")}: ${issue.message}`)
|
|
57
232
|
);
|
|
58
233
|
return { success: false, errors };
|
|
59
234
|
}
|
|
@@ -69,7 +244,7 @@ async function executeRollback(executedSteps, snapshots, outputs) {
|
|
|
69
244
|
} catch (err) {
|
|
70
245
|
failed.push({
|
|
71
246
|
step: step.name,
|
|
72
|
-
error:
|
|
247
|
+
error: toError(err)
|
|
73
248
|
});
|
|
74
249
|
}
|
|
75
250
|
}
|
|
@@ -118,7 +293,7 @@ function createStepExecutor(step) {
|
|
|
118
293
|
step.requires,
|
|
119
294
|
ctx,
|
|
120
295
|
`${step.name} requires`,
|
|
121
|
-
|
|
296
|
+
RequiresValidationError
|
|
122
297
|
);
|
|
123
298
|
if (!requiresCheck.success) {
|
|
124
299
|
return { success: false, errors: requiresCheck.errors };
|
|
@@ -129,7 +304,7 @@ function createStepExecutor(step) {
|
|
|
129
304
|
step.provides,
|
|
130
305
|
result.data,
|
|
131
306
|
`${step.name} provides`,
|
|
132
|
-
|
|
307
|
+
ProvidesValidationError
|
|
133
308
|
);
|
|
134
309
|
if (!providesCheck.success) {
|
|
135
310
|
return { success: false, errors: providesCheck.errors };
|
|
@@ -147,7 +322,7 @@ async function executePipeline(config, args) {
|
|
|
147
322
|
config.argsSchema,
|
|
148
323
|
args,
|
|
149
324
|
`${config.name} args`,
|
|
150
|
-
|
|
325
|
+
ArgsValidationError
|
|
151
326
|
);
|
|
152
327
|
if (!argsCheck.success) {
|
|
153
328
|
const state2 = createExecutionState(args);
|
|
@@ -170,9 +345,9 @@ async function executePipeline(config, args) {
|
|
|
170
345
|
continue;
|
|
171
346
|
}
|
|
172
347
|
} catch (err) {
|
|
173
|
-
const
|
|
174
|
-
const error = new
|
|
175
|
-
|
|
348
|
+
const cause = toError(err);
|
|
349
|
+
const error = new PredicateError(`${step.name} predicate: ${cause.message}`);
|
|
350
|
+
error.cause = cause;
|
|
176
351
|
const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
|
|
177
352
|
return pipelineFailure(config.name, args, state, step.name, [error], rollback);
|
|
178
353
|
}
|
|
@@ -187,7 +362,7 @@ async function executePipeline(config, args) {
|
|
|
187
362
|
try {
|
|
188
363
|
result = await executor(state.context);
|
|
189
364
|
} catch (err) {
|
|
190
|
-
const error =
|
|
365
|
+
const error = toError(err);
|
|
191
366
|
state.snapshots.pop();
|
|
192
367
|
const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
|
|
193
368
|
return pipelineFailure(config.name, args, state, step.name, [error], rollback);
|
|
@@ -206,12 +381,365 @@ async function executePipeline(config, args) {
|
|
|
206
381
|
return pipelineSuccess(config.name, args, state);
|
|
207
382
|
}
|
|
208
383
|
function buildPipeline(config) {
|
|
384
|
+
if (config.strict) checkStrictOverlap(config.steps);
|
|
209
385
|
return Object.freeze({
|
|
210
386
|
name: config.name,
|
|
211
387
|
run: (args) => executePipeline(config, args)
|
|
212
388
|
});
|
|
213
389
|
}
|
|
214
390
|
|
|
391
|
+
// src/parallel.ts
|
|
392
|
+
async function executeInner(step, ctx) {
|
|
393
|
+
try {
|
|
394
|
+
if (isConditionalStep(step) && !step.predicate(ctx)) {
|
|
395
|
+
return { step, skipped: true };
|
|
396
|
+
}
|
|
397
|
+
} catch (err) {
|
|
398
|
+
const cause = toError(err);
|
|
399
|
+
const error = new PredicateError(`${step.name} predicate: ${cause.message}`);
|
|
400
|
+
error.cause = cause;
|
|
401
|
+
return { step, skipped: false, errors: [error] };
|
|
402
|
+
}
|
|
403
|
+
const result = await runInnerStep(step, ctx);
|
|
404
|
+
if (!result.success) return { step, skipped: false, errors: [...result.errors] };
|
|
405
|
+
return { step, skipped: false, output: result.data };
|
|
406
|
+
}
|
|
407
|
+
function parallel(...steps) {
|
|
408
|
+
const name = `parallel(${steps.map((s) => s.name).join(", ")})`;
|
|
409
|
+
const innerSteps = steps;
|
|
410
|
+
const run = async (ctx) => {
|
|
411
|
+
const settled = await Promise.allSettled(innerSteps.map((step) => executeInner(step, ctx)));
|
|
412
|
+
const succeeded = [];
|
|
413
|
+
const allErrors = [];
|
|
414
|
+
for (const s of settled) {
|
|
415
|
+
if (s.status === "rejected") {
|
|
416
|
+
allErrors.push(toError(s.reason));
|
|
417
|
+
} else {
|
|
418
|
+
const r = s.value;
|
|
419
|
+
if (r.skipped) continue;
|
|
420
|
+
if (r.output) {
|
|
421
|
+
succeeded.push({ step: r.step, output: r.output });
|
|
422
|
+
} else if (r.errors) {
|
|
423
|
+
allErrors.push(...r.errors);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (allErrors.length > 0) {
|
|
428
|
+
for (let i = succeeded.length - 1; i >= 0; i--) {
|
|
429
|
+
const { step, output } = succeeded[i];
|
|
430
|
+
if (step.rollback) {
|
|
431
|
+
try {
|
|
432
|
+
await step.rollback(ctx, output);
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { success: false, errors: allErrors };
|
|
438
|
+
}
|
|
439
|
+
const merged = {};
|
|
440
|
+
for (const { output } of succeeded) {
|
|
441
|
+
Object.assign(merged, output);
|
|
442
|
+
}
|
|
443
|
+
return { success: true, data: merged, errors: [] };
|
|
444
|
+
};
|
|
445
|
+
const rollback = async (ctx, mergedOutput) => {
|
|
446
|
+
const errors = [];
|
|
447
|
+
for (let i = innerSteps.length - 1; i >= 0; i--) {
|
|
448
|
+
const step = innerSteps[i];
|
|
449
|
+
if (!step.rollback) continue;
|
|
450
|
+
try {
|
|
451
|
+
await step.rollback(ctx, mergedOutput);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
errors.push(toError(err));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (errors.length > 0) {
|
|
457
|
+
const error = new RollbackError(`${name}: ${errors.length} rollback(s) failed`);
|
|
458
|
+
error.cause = errors;
|
|
459
|
+
throw error;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
return Object.freeze({
|
|
463
|
+
name,
|
|
464
|
+
requires: void 0,
|
|
465
|
+
provides: void 0,
|
|
466
|
+
run,
|
|
467
|
+
rollback,
|
|
468
|
+
retry: void 0,
|
|
469
|
+
timeout: void 0
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// src/choice.ts
|
|
474
|
+
function normalizeBranches(args) {
|
|
475
|
+
return args.map((arg) => {
|
|
476
|
+
if (Array.isArray(arg)) return arg;
|
|
477
|
+
return [() => true, arg];
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function choice(...args) {
|
|
481
|
+
const innerBranches = normalizeBranches(args);
|
|
482
|
+
const name = `choice(${innerBranches.map(([, step]) => step.name).join(", ")})`;
|
|
483
|
+
const branchMap = /* @__PURE__ */ new WeakMap();
|
|
484
|
+
const run = async (ctx) => {
|
|
485
|
+
for (let i = 0; i < innerBranches.length; i++) {
|
|
486
|
+
const [predicate, step] = innerBranches[i];
|
|
487
|
+
let matches;
|
|
488
|
+
try {
|
|
489
|
+
matches = predicate(ctx);
|
|
490
|
+
} catch (err) {
|
|
491
|
+
const cause = toError(err);
|
|
492
|
+
const error = new PredicateError(`${name} predicate: ${cause.message}`);
|
|
493
|
+
error.cause = cause;
|
|
494
|
+
return { success: false, errors: [error] };
|
|
495
|
+
}
|
|
496
|
+
if (!matches) continue;
|
|
497
|
+
const result = await runInnerStep(step, ctx);
|
|
498
|
+
if (!result.success) return result;
|
|
499
|
+
branchMap.set(result.data, i);
|
|
500
|
+
return { success: true, data: result.data, errors: [] };
|
|
501
|
+
}
|
|
502
|
+
return {
|
|
503
|
+
success: false,
|
|
504
|
+
errors: [new ChoiceNoMatchError(`${name}: no branch matched`)]
|
|
505
|
+
};
|
|
506
|
+
};
|
|
507
|
+
const rollback = async (ctx, output) => {
|
|
508
|
+
const branchIndex = branchMap.get(output);
|
|
509
|
+
if (branchIndex === void 0) return;
|
|
510
|
+
const [, step] = innerBranches[branchIndex];
|
|
511
|
+
if (step.rollback) {
|
|
512
|
+
try {
|
|
513
|
+
await step.rollback(ctx, output);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
const error = new RollbackError(`${name}: 1 rollback(s) failed`);
|
|
516
|
+
error.cause = [toError(err)];
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
return Object.freeze({
|
|
522
|
+
name,
|
|
523
|
+
requires: void 0,
|
|
524
|
+
provides: void 0,
|
|
525
|
+
run,
|
|
526
|
+
rollback,
|
|
527
|
+
retry: void 0,
|
|
528
|
+
timeout: void 0
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/map.ts
|
|
533
|
+
function isStep(x) {
|
|
534
|
+
return typeof x === "object" && x !== null && "run" in x && "name" in x;
|
|
535
|
+
}
|
|
536
|
+
function map(key, collection, fnOrStep) {
|
|
537
|
+
const stepMode = isStep(fnOrStep);
|
|
538
|
+
const name = stepMode ? `map(${key}, ${fnOrStep.name})` : `map(${key})`;
|
|
539
|
+
const executionMap = /* @__PURE__ */ new WeakMap();
|
|
540
|
+
const run = async (ctx) => {
|
|
541
|
+
let items;
|
|
542
|
+
try {
|
|
543
|
+
items = collection(ctx);
|
|
544
|
+
} catch (err) {
|
|
545
|
+
return {
|
|
546
|
+
success: false,
|
|
547
|
+
errors: [toError(err)]
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
if (stepMode) {
|
|
551
|
+
return runStepMode(fnOrStep, items, ctx, name, key, executionMap);
|
|
552
|
+
} else {
|
|
553
|
+
return runFunctionMode(
|
|
554
|
+
fnOrStep,
|
|
555
|
+
items,
|
|
556
|
+
ctx,
|
|
557
|
+
key
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
const rollback = stepMode ? async (_ctx, output) => {
|
|
562
|
+
const step = fnOrStep;
|
|
563
|
+
if (!step.rollback) return;
|
|
564
|
+
const exec = executionMap.get(output);
|
|
565
|
+
if (!exec) return;
|
|
566
|
+
const results = output[key];
|
|
567
|
+
const errors = [];
|
|
568
|
+
for (let i = results.length - 1; i >= 0; i--) {
|
|
569
|
+
try {
|
|
570
|
+
const itemCtx = { ...exec.ctx, ...exec.items[i] };
|
|
571
|
+
await step.rollback(itemCtx, results[i]);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
errors.push(toError(err));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (errors.length > 0) {
|
|
577
|
+
const error = new RollbackError(`${name}: ${errors.length} rollback(s) failed`);
|
|
578
|
+
error.cause = errors;
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
} : void 0;
|
|
582
|
+
return Object.freeze({
|
|
583
|
+
name,
|
|
584
|
+
requires: void 0,
|
|
585
|
+
provides: void 0,
|
|
586
|
+
run,
|
|
587
|
+
rollback,
|
|
588
|
+
retry: void 0,
|
|
589
|
+
timeout: void 0
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
async function runStepMode(step, items, ctx, name, key, executionMap) {
|
|
593
|
+
const settled = await Promise.allSettled(
|
|
594
|
+
items.map(async (item) => {
|
|
595
|
+
const itemCtx = { ...ctx, ...item };
|
|
596
|
+
return runInnerStep(step, itemCtx);
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
const succeeded = [];
|
|
600
|
+
const allErrors = [];
|
|
601
|
+
for (let i = 0; i < settled.length; i++) {
|
|
602
|
+
const s = settled[i];
|
|
603
|
+
if (s.status === "rejected") {
|
|
604
|
+
allErrors.push(toError(s.reason));
|
|
605
|
+
} else if (!s.value.success) {
|
|
606
|
+
allErrors.push(...s.value.errors);
|
|
607
|
+
} else {
|
|
608
|
+
succeeded.push({ index: i, output: s.value.data });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (allErrors.length > 0) {
|
|
612
|
+
if (step.rollback) {
|
|
613
|
+
for (let i = succeeded.length - 1; i >= 0; i--) {
|
|
614
|
+
try {
|
|
615
|
+
const itemCtx = { ...ctx, ...items[succeeded[i].index] };
|
|
616
|
+
await step.rollback(itemCtx, succeeded[i].output);
|
|
617
|
+
} catch {
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return { success: false, errors: allErrors };
|
|
622
|
+
}
|
|
623
|
+
const results = succeeded.map((s) => s.output);
|
|
624
|
+
const data = { [key]: results };
|
|
625
|
+
executionMap.set(data, { items, ctx: { ...ctx } });
|
|
626
|
+
return { success: true, data, errors: [] };
|
|
627
|
+
}
|
|
628
|
+
async function runFunctionMode(fn, items, ctx, key) {
|
|
629
|
+
const settled = await Promise.allSettled(items.map(async (item) => fn(item, ctx)));
|
|
630
|
+
const results = [];
|
|
631
|
+
const allErrors = [];
|
|
632
|
+
for (const s of settled) {
|
|
633
|
+
if (s.status === "rejected") {
|
|
634
|
+
allErrors.push(toError(s.reason));
|
|
635
|
+
} else {
|
|
636
|
+
results.push(s.value);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (allErrors.length > 0) {
|
|
640
|
+
return { success: false, errors: allErrors };
|
|
641
|
+
}
|
|
642
|
+
const data = { [key]: results };
|
|
643
|
+
return { success: true, data, errors: [] };
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/filter.ts
|
|
647
|
+
function filter(key, collection, predicate) {
|
|
648
|
+
const name = `filter(${key})`;
|
|
649
|
+
const run = async (ctx) => {
|
|
650
|
+
let items;
|
|
651
|
+
try {
|
|
652
|
+
items = collection(ctx);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
return {
|
|
655
|
+
success: false,
|
|
656
|
+
errors: [toError(err)]
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return runFilter(
|
|
660
|
+
items,
|
|
661
|
+
ctx,
|
|
662
|
+
predicate,
|
|
663
|
+
key
|
|
664
|
+
);
|
|
665
|
+
};
|
|
666
|
+
return Object.freeze({
|
|
667
|
+
name,
|
|
668
|
+
requires: void 0,
|
|
669
|
+
provides: void 0,
|
|
670
|
+
run,
|
|
671
|
+
rollback: void 0,
|
|
672
|
+
retry: void 0,
|
|
673
|
+
timeout: void 0
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
async function runFilter(items, ctx, predicate, key) {
|
|
677
|
+
const settled = await Promise.allSettled(items.map(async (item) => predicate(item, ctx)));
|
|
678
|
+
const results = [];
|
|
679
|
+
const allErrors = [];
|
|
680
|
+
for (let i = 0; i < settled.length; i++) {
|
|
681
|
+
const s = settled[i];
|
|
682
|
+
if (s.status === "rejected") {
|
|
683
|
+
allErrors.push(toError(s.reason));
|
|
684
|
+
} else if (s.value) {
|
|
685
|
+
results.push(items[i]);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (allErrors.length > 0) {
|
|
689
|
+
return { success: false, errors: allErrors };
|
|
690
|
+
}
|
|
691
|
+
const data = { [key]: results };
|
|
692
|
+
return { success: true, data, errors: [] };
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/flat-map.ts
|
|
696
|
+
function flatMap(key, collection, fn) {
|
|
697
|
+
const name = `flatMap(${key})`;
|
|
698
|
+
const run = async (ctx) => {
|
|
699
|
+
let items;
|
|
700
|
+
try {
|
|
701
|
+
items = collection(ctx);
|
|
702
|
+
} catch (err) {
|
|
703
|
+
return {
|
|
704
|
+
success: false,
|
|
705
|
+
errors: [toError(err)]
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return runFlatMap(
|
|
709
|
+
items,
|
|
710
|
+
ctx,
|
|
711
|
+
fn,
|
|
712
|
+
key
|
|
713
|
+
);
|
|
714
|
+
};
|
|
715
|
+
return Object.freeze({
|
|
716
|
+
name,
|
|
717
|
+
requires: void 0,
|
|
718
|
+
provides: void 0,
|
|
719
|
+
run,
|
|
720
|
+
rollback: void 0,
|
|
721
|
+
retry: void 0,
|
|
722
|
+
timeout: void 0
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
async function runFlatMap(items, ctx, fn, key) {
|
|
726
|
+
const settled = await Promise.allSettled(items.map(async (item) => fn(item, ctx)));
|
|
727
|
+
const results = [];
|
|
728
|
+
const allErrors = [];
|
|
729
|
+
for (const s of settled) {
|
|
730
|
+
if (s.status === "rejected") {
|
|
731
|
+
allErrors.push(toError(s.reason));
|
|
732
|
+
} else {
|
|
733
|
+
results.push(...s.value);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (allErrors.length > 0) {
|
|
737
|
+
return { success: false, errors: allErrors };
|
|
738
|
+
}
|
|
739
|
+
const data = { [key]: results };
|
|
740
|
+
return { success: true, data, errors: [] };
|
|
741
|
+
}
|
|
742
|
+
|
|
215
743
|
// src/builder.ts
|
|
216
744
|
function makeBuilder(state) {
|
|
217
745
|
return Object.freeze({
|
|
@@ -227,23 +755,50 @@ function makeBuilder(state) {
|
|
|
227
755
|
name: state.name,
|
|
228
756
|
steps: state.steps,
|
|
229
757
|
middleware: state.middleware.length > 0 ? state.middleware : void 0,
|
|
230
|
-
argsSchema: state.argsSchema
|
|
758
|
+
argsSchema: state.argsSchema,
|
|
759
|
+
strict: state.strict || void 0
|
|
231
760
|
})
|
|
232
761
|
});
|
|
233
762
|
}
|
|
234
|
-
function createPipeline(name,
|
|
763
|
+
function createPipeline(name, schemaOrOptions, options) {
|
|
764
|
+
let argsSchema;
|
|
765
|
+
let strict = false;
|
|
766
|
+
if (schemaOrOptions != null) {
|
|
767
|
+
if ("safeParse" in schemaOrOptions) {
|
|
768
|
+
argsSchema = schemaOrOptions;
|
|
769
|
+
strict = options?.strict ?? false;
|
|
770
|
+
} else {
|
|
771
|
+
strict = schemaOrOptions.strict ?? false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
235
774
|
return makeBuilder({
|
|
236
775
|
name,
|
|
237
776
|
steps: [],
|
|
238
777
|
middleware: [],
|
|
239
|
-
argsSchema
|
|
778
|
+
argsSchema,
|
|
779
|
+
strict
|
|
240
780
|
});
|
|
241
781
|
}
|
|
242
782
|
export {
|
|
783
|
+
ArgsValidationError,
|
|
784
|
+
ChoiceNoMatchError,
|
|
785
|
+
PredicateError,
|
|
786
|
+
ProvidesValidationError,
|
|
787
|
+
RequiresValidationError,
|
|
788
|
+
RetryExhaustedError,
|
|
789
|
+
RollbackError,
|
|
243
790
|
RunsheetError,
|
|
791
|
+
StrictOverlapError,
|
|
792
|
+
TimeoutError,
|
|
793
|
+
UnknownError,
|
|
244
794
|
buildPipeline,
|
|
795
|
+
choice,
|
|
245
796
|
createPipeline,
|
|
246
797
|
defineStep,
|
|
798
|
+
filter,
|
|
799
|
+
flatMap,
|
|
800
|
+
map,
|
|
801
|
+
parallel,
|
|
247
802
|
when
|
|
248
803
|
};
|
|
249
804
|
//# sourceMappingURL=index.js.map
|