runsheet 0.5.0 → 0.6.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/dist/index.cjs CHANGED
@@ -31,21 +31,17 @@ __export(index_exports, {
31
31
  StrictOverlapError: () => StrictOverlapError,
32
32
  TimeoutError: () => TimeoutError,
33
33
  UnknownError: () => UnknownError,
34
- buildPipeline: () => buildPipeline,
35
34
  choice: () => choice,
36
- createPipeline: () => createPipeline,
37
35
  defineStep: () => defineStep,
38
36
  filter: () => filter,
39
37
  flatMap: () => flatMap,
40
38
  map: () => map,
41
39
  parallel: () => parallel,
40
+ pipeline: () => pipeline,
42
41
  when: () => when
43
42
  });
44
43
  module.exports = __toCommonJS(index_exports);
45
44
 
46
- // src/define-step.ts
47
- var import_composable_functions = require("composable-functions");
48
-
49
45
  // src/errors.ts
50
46
  var RunsheetError = class extends Error {
51
47
  /** Discriminant code identifying the type of library error. */
@@ -130,24 +126,72 @@ var UnknownError = class extends RunsheetError {
130
126
  }
131
127
  };
132
128
  var RollbackError = class extends RunsheetError {
133
- constructor(message) {
129
+ /** The individual errors from each failed rollback handler. */
130
+ causes;
131
+ constructor(message, causes = []) {
134
132
  super("ROLLBACK", message);
135
133
  this.name = "RollbackError";
134
+ this.causes = causes;
135
+ this.cause = causes.length === 1 ? causes[0] : new AggregateError(causes, message);
136
136
  }
137
137
  };
138
138
 
139
+ // src/internal.ts
140
+ function toError(err) {
141
+ if (err instanceof Error) return err;
142
+ return new UnknownError(String(err), err);
143
+ }
144
+ var EMPTY_ROLLBACK = Object.freeze({
145
+ completed: Object.freeze([]),
146
+ failed: Object.freeze([])
147
+ });
148
+ function formatIssues(issues) {
149
+ return issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
150
+ }
151
+ function collapseErrors(errors, message) {
152
+ return errors.length === 1 ? errors[0] : new AggregateError(errors, message);
153
+ }
154
+ function createStepObject(fields) {
155
+ return Object.freeze({
156
+ name: fields.name,
157
+ requires: fields.requires ?? void 0,
158
+ provides: fields.provides ?? void 0,
159
+ run: fields.run,
160
+ rollback: fields.rollback ?? void 0,
161
+ retry: fields.retry ?? void 0,
162
+ timeout: fields.timeout ?? void 0
163
+ });
164
+ }
165
+ function baseMeta(name, args) {
166
+ return Object.freeze({ name, args });
167
+ }
168
+ function aggregateMeta(name, args, stepsExecuted) {
169
+ return Object.freeze({ name, args, stepsExecuted });
170
+ }
171
+ function stepSuccess(data, meta) {
172
+ return Object.freeze({ success: true, data, meta });
173
+ }
174
+ function stepFailure(error, meta, failedStep, rollback = EMPTY_ROLLBACK) {
175
+ return Object.freeze({ success: false, error, meta, failedStep, rollback });
176
+ }
177
+ function aggregateSuccess(data, meta) {
178
+ return Object.freeze({ success: true, data, meta });
179
+ }
180
+ function aggregateFailure(error, meta, failedStep, rollback = EMPTY_ROLLBACK) {
181
+ return Object.freeze({ success: false, error, meta, failedStep, rollback });
182
+ }
183
+
139
184
  // src/define-step.ts
140
- function withTimeout(run, stepName, ms) {
141
- return async (ctx) => {
185
+ function withTimeout(fn, stepName, ms) {
186
+ return async () => {
142
187
  let timer;
143
- const timeout = new Promise((resolve) => {
188
+ const timeout = new Promise((_, reject) => {
144
189
  timer = setTimeout(() => {
145
- const error = new TimeoutError(`${stepName} timed out after ${ms}ms`, ms);
146
- resolve({ success: false, errors: [error] });
190
+ reject(new TimeoutError(`${stepName} timed out after ${ms}ms`, ms));
147
191
  }, ms);
148
192
  });
149
193
  try {
150
- return await Promise.race([run(ctx), timeout]);
194
+ return await Promise.race([Promise.resolve(fn()), timeout]);
151
195
  } finally {
152
196
  clearTimeout(timer);
153
197
  }
@@ -159,43 +203,111 @@ function computeDelay(policy, attempt) {
159
203
  const strategy = policy.backoff ?? "linear";
160
204
  return strategy === "exponential" ? base * 2 ** (attempt - 1) : base * attempt;
161
205
  }
162
- function withRetry(run, stepName, policy) {
163
- return async (ctx) => {
164
- let lastResult = await run(ctx);
165
- if (lastResult.success) return lastResult;
206
+ function withRetry(fn, stepName, policy) {
207
+ return async () => {
208
+ let lastError;
209
+ const errors = [];
210
+ try {
211
+ return await fn();
212
+ } catch (err) {
213
+ lastError = toError(err);
214
+ errors.push(lastError);
215
+ }
166
216
  for (let attempt = 1; attempt <= policy.count; attempt++) {
167
- if (policy.retryIf && !policy.retryIf(lastResult.errors)) return lastResult;
217
+ if (policy.retryIf && !policy.retryIf(errors)) throw lastError;
168
218
  const delay = computeDelay(policy, attempt);
169
- if (delay > 0) await new Promise((r) => setTimeout(r, delay));
170
- lastResult = await run(ctx);
171
- if (lastResult.success) return lastResult;
219
+ if (delay > 0) await new Promise((r) => setTimeout(() => r(void 0), delay));
220
+ try {
221
+ return await fn();
222
+ } catch (err) {
223
+ lastError = toError(err);
224
+ errors.push(lastError);
225
+ }
172
226
  }
173
227
  const error = new RetryExhaustedError(
174
228
  `${stepName} failed after ${policy.count} retries`,
175
229
  policy.count + 1
176
230
  );
177
- return { success: false, errors: [...lastResult.errors, error] };
231
+ error.cause = errors;
232
+ throw error;
178
233
  };
179
234
  }
180
- function wrapWithTimeoutAndRetry(run, stepName, timeout, retry) {
181
- let wrapped = run;
182
- if (timeout !== void 0) wrapped = withTimeout(wrapped, stepName, timeout);
183
- if (retry !== void 0) wrapped = withRetry(wrapped, stepName, retry);
184
- return wrapped;
235
+ function buildExecutor(config) {
236
+ let fn = (ctx) => Promise.resolve(config.run(ctx));
237
+ if (config.timeout !== void 0) {
238
+ const baseFn = fn;
239
+ const ms = config.timeout;
240
+ fn = (ctx) => withTimeout(() => baseFn(ctx), config.name, ms)();
241
+ }
242
+ if (config.retry !== void 0) {
243
+ const baseFn = fn;
244
+ const policy = config.retry;
245
+ fn = (ctx) => withRetry(() => baseFn(ctx), config.name, policy)();
246
+ }
247
+ return fn;
185
248
  }
186
249
  function defineStep(config) {
187
- const baseRun = (0, import_composable_functions.composable)(config.run);
188
- const wrappedRun = wrapWithTimeoutAndRetry(baseRun, config.name, config.timeout, config.retry);
189
- return Object.freeze({
250
+ const execute = buildExecutor(config);
251
+ const run = async (ctx) => {
252
+ const frozenCtx = Object.freeze({ ...ctx });
253
+ const meta = baseMeta(config.name, frozenCtx);
254
+ if (config.requires) {
255
+ const parsed = config.requires.safeParse(frozenCtx);
256
+ if (!parsed.success) {
257
+ const error = new RequiresValidationError(
258
+ `${config.name} requires: ${formatIssues(parsed.error.issues)}`
259
+ );
260
+ return stepFailure(error, meta, config.name);
261
+ }
262
+ }
263
+ let data;
264
+ try {
265
+ data = await execute(frozenCtx);
266
+ } catch (err) {
267
+ return stepFailure(toError(err), meta, config.name);
268
+ }
269
+ if (config.provides) {
270
+ const parsed = config.provides.safeParse(data);
271
+ if (!parsed.success) {
272
+ const error = new ProvidesValidationError(
273
+ `${config.name} provides: ${formatIssues(parsed.error.issues)}`
274
+ );
275
+ return stepFailure(error, meta, config.name);
276
+ }
277
+ }
278
+ return stepSuccess(data, meta);
279
+ };
280
+ return createStepObject({
190
281
  name: config.name,
191
- requires: config.requires ?? void 0,
192
- provides: config.provides ?? void 0,
193
- run: wrappedRun,
282
+ requires: config.requires,
283
+ provides: config.provides,
284
+ run,
194
285
  rollback: config.rollback ? async (ctx, output) => {
195
286
  await config.rollback(ctx, output);
196
287
  } : void 0,
197
- retry: config.retry ?? void 0,
198
- timeout: config.timeout ?? void 0
288
+ retry: config.retry,
289
+ timeout: config.timeout
290
+ });
291
+ }
292
+
293
+ // src/builder.ts
294
+ function makeBuilder(state) {
295
+ return Object.freeze({
296
+ step: (step) => makeBuilder({
297
+ ...state,
298
+ steps: [...state.steps, step]
299
+ }),
300
+ use: (...middleware) => makeBuilder({
301
+ ...state,
302
+ middleware: [...state.middleware, ...middleware]
303
+ }),
304
+ build: () => buildPipelineStep({
305
+ name: state.name,
306
+ steps: state.steps,
307
+ middleware: state.middleware.length > 0 ? state.middleware : void 0,
308
+ argsSchema: state.argsSchema,
309
+ strict: state.strict || void 0
310
+ })
199
311
  });
200
312
  }
201
313
 
@@ -206,8 +318,17 @@ function applyMiddleware(middlewares, step, executor) {
206
318
 
207
319
  // src/when.ts
208
320
  function when(predicate, step) {
321
+ const base = createStepObject({
322
+ name: step.name,
323
+ run: step.run,
324
+ rollback: step.rollback,
325
+ requires: step.requires,
326
+ provides: step.provides,
327
+ retry: step.retry,
328
+ timeout: step.timeout
329
+ });
209
330
  return Object.freeze({
210
- ...step,
331
+ ...base,
211
332
  predicate
212
333
  });
213
334
  }
@@ -215,39 +336,6 @@ function isConditionalStep(step) {
215
336
  return "predicate" in step && typeof step.predicate === "function";
216
337
  }
217
338
 
218
- // src/internal.ts
219
- function toError(err) {
220
- if (err instanceof Error) return err;
221
- return new UnknownError(String(err), err);
222
- }
223
- function validateInnerSchema(schema, data, label, ErrorClass) {
224
- if (!schema) return null;
225
- const parsed = schema.safeParse(data);
226
- if (parsed.success) return null;
227
- return parsed.error.issues.map(
228
- (issue) => new ErrorClass(`${label}: ${issue.path.join(".")}: ${issue.message}`)
229
- );
230
- }
231
- async function runInnerStep(step, ctx) {
232
- const requiresErrors = validateInnerSchema(
233
- step.requires,
234
- ctx,
235
- `${step.name} requires`,
236
- RequiresValidationError
237
- );
238
- if (requiresErrors) return { success: false, errors: requiresErrors };
239
- const result = await step.run(ctx);
240
- if (!result.success) return result;
241
- const providesErrors = validateInnerSchema(
242
- step.provides,
243
- result.data,
244
- `${step.name} provides`,
245
- ProvidesValidationError
246
- );
247
- if (providesErrors) return { success: false, errors: providesErrors };
248
- return result;
249
- }
250
-
251
339
  // src/pipeline.ts
252
340
  function checkStrictOverlap(steps) {
253
341
  const seen = /* @__PURE__ */ new Map();
@@ -268,168 +356,121 @@ function checkStrictOverlap(steps) {
268
356
  }
269
357
  }
270
358
  }
271
- function validateSchema(schema, data, label, ErrorClass) {
272
- if (!schema) return { success: true, data };
273
- const parsed = schema.safeParse(data);
274
- if (parsed.success) return { success: true, data: parsed.data };
275
- const errors = parsed.error.issues.map(
276
- (issue) => new ErrorClass(`${label}: ${issue.path.join(".")}: ${issue.message}`)
277
- );
278
- return { success: false, errors };
359
+ function createExecutionState(args) {
360
+ return {
361
+ context: Object.freeze({ ...args }),
362
+ executed: [],
363
+ stepsExecuted: []
364
+ };
279
365
  }
280
- async function executeRollback(executedSteps, snapshots, outputs) {
366
+ async function executeRollback(executed) {
281
367
  const completed = [];
282
368
  const failed = [];
283
- for (let i = executedSteps.length - 1; i >= 0; i--) {
284
- const step = executedSteps[i];
285
- if (!step.rollback) continue;
369
+ for (let i = executed.length - 1; i >= 0; i--) {
370
+ const entry = executed[i];
371
+ if (!entry.step.rollback) continue;
286
372
  try {
287
- await step.rollback(snapshots[i], outputs[i]);
288
- completed.push(step.name);
373
+ await entry.step.rollback(entry.snapshot, entry.output);
374
+ completed.push(entry.step.name);
289
375
  } catch (err) {
290
- failed.push({
291
- step: step.name,
292
- error: toError(err)
293
- });
376
+ failed.push({ step: entry.step.name, error: toError(err) });
294
377
  }
295
378
  }
296
379
  return Object.freeze({ completed, failed });
297
380
  }
298
- function createExecutionState(args) {
299
- return {
300
- context: Object.freeze({ ...args }),
301
- snapshots: [],
302
- outputs: [],
303
- executedSteps: [],
304
- stepsExecuted: [],
305
- stepsSkipped: []
306
- };
307
- }
308
- function pipelineFailure(pipelineName, args, state, failedStep, errors, rollback) {
309
- return Object.freeze({
310
- success: false,
311
- errors,
312
- meta: Object.freeze({
313
- pipeline: pipelineName,
314
- args,
315
- stepsExecuted: state.stepsExecuted,
316
- stepsSkipped: state.stepsSkipped
317
- }),
318
- failedStep,
319
- rollback
320
- });
321
- }
322
- function pipelineSuccess(pipelineName, args, state) {
323
- return Object.freeze({
324
- success: true,
325
- data: state.context,
326
- errors: [],
327
- meta: Object.freeze({
328
- pipeline: pipelineName,
329
- args,
330
- stepsExecuted: state.stepsExecuted,
331
- stepsSkipped: state.stepsSkipped
332
- })
333
- });
334
- }
335
- function createStepExecutor(step) {
336
- return async (ctx) => {
337
- const requiresCheck = validateSchema(
338
- step.requires,
339
- ctx,
340
- `${step.name} requires`,
341
- RequiresValidationError
342
- );
343
- if (!requiresCheck.success) {
344
- return { success: false, errors: requiresCheck.errors };
345
- }
346
- const result = await step.run(ctx);
347
- if (!result.success) return result;
348
- const providesCheck = validateSchema(
349
- step.provides,
350
- result.data,
351
- `${step.name} provides`,
352
- ProvidesValidationError
353
- );
354
- if (!providesCheck.success) {
355
- return { success: false, errors: providesCheck.errors };
356
- }
357
- return {
358
- success: true,
359
- data: providesCheck.data,
360
- errors: []
361
- };
362
- };
363
- }
364
381
  async function executePipeline(config, args) {
382
+ const frozenArgs = Object.freeze({ ...args });
365
383
  if (config.argsSchema) {
366
- const argsCheck = validateSchema(
367
- config.argsSchema,
368
- args,
369
- `${config.name} args`,
370
- ArgsValidationError
371
- );
372
- if (!argsCheck.success) {
373
- const state2 = createExecutionState(args);
374
- return pipelineFailure(
375
- config.name,
376
- args,
377
- state2,
378
- config.name,
379
- argsCheck.errors,
380
- Object.freeze({ completed: [], failed: [] })
384
+ const parsed = config.argsSchema.safeParse(frozenArgs);
385
+ if (!parsed.success) {
386
+ const error = new ArgsValidationError(
387
+ `${config.name} args: ${formatIssues(parsed.error.issues)}`
381
388
  );
389
+ const meta2 = aggregateMeta(config.name, frozenArgs, []);
390
+ const state2 = createExecutionState(frozenArgs);
391
+ return { result: aggregateFailure(error, meta2, config.name), state: state2 };
382
392
  }
383
393
  }
384
- const state = createExecutionState(args);
394
+ const state = createExecutionState(frozenArgs);
385
395
  const middlewares = config.middleware ?? [];
386
396
  for (const step of config.steps) {
387
397
  try {
388
398
  if (isConditionalStep(step) && !step.predicate(state.context)) {
389
- state.stepsSkipped.push(step.name);
390
399
  continue;
391
400
  }
392
401
  } catch (err) {
393
402
  const cause = toError(err);
394
403
  const error = new PredicateError(`${step.name} predicate: ${cause.message}`);
395
404
  error.cause = cause;
396
- const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
397
- return pipelineFailure(config.name, args, state, step.name, [error], rollback);
405
+ const rollback = await executeRollback(state.executed);
406
+ const meta2 = aggregateMeta(config.name, frozenArgs, [...state.stepsExecuted]);
407
+ return { result: aggregateFailure(error, meta2, step.name, rollback), state };
398
408
  }
399
- state.snapshots.push(state.context);
400
- const baseExecutor = createStepExecutor(step);
401
- const executor = applyMiddleware(
409
+ const snapshot = state.context;
410
+ const executor = middlewares.length > 0 ? applyMiddleware(
402
411
  middlewares,
403
412
  { name: step.name, requires: step.requires, provides: step.provides },
404
- baseExecutor
405
- );
413
+ (ctx) => step.run(ctx)
414
+ ) : (ctx) => step.run(ctx);
406
415
  let result;
407
416
  try {
408
417
  result = await executor(state.context);
409
418
  } catch (err) {
410
419
  const error = toError(err);
411
- state.snapshots.pop();
412
- const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
413
- return pipelineFailure(config.name, args, state, step.name, [error], rollback);
420
+ const rollback = await executeRollback(state.executed);
421
+ const meta2 = aggregateMeta(config.name, frozenArgs, [...state.stepsExecuted]);
422
+ return { result: aggregateFailure(error, meta2, step.name, rollback), state };
414
423
  }
415
424
  if (!result.success) {
416
- state.snapshots.pop();
417
- const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
418
- return pipelineFailure(config.name, args, state, step.name, result.errors, rollback);
425
+ const rollback = await executeRollback(state.executed);
426
+ const meta2 = aggregateMeta(config.name, frozenArgs, [...state.stepsExecuted]);
427
+ return { result: aggregateFailure(result.error, meta2, step.name, rollback), state };
419
428
  }
420
429
  const output = result.data;
421
- state.outputs.push(output);
422
- state.executedSteps.push(step);
430
+ state.executed.push({ step, snapshot, output });
423
431
  state.stepsExecuted.push(step.name);
424
432
  state.context = Object.freeze({ ...state.context, ...output });
425
433
  }
426
- return pipelineSuccess(config.name, args, state);
434
+ const meta = aggregateMeta(config.name, frozenArgs, [...state.stepsExecuted]);
435
+ return { result: aggregateSuccess(state.context, meta), state };
427
436
  }
428
- function buildPipeline(config) {
437
+ function buildPipelineStep(config) {
429
438
  if (config.strict) checkStrictOverlap(config.steps);
430
- return Object.freeze({
439
+ const pipelineConfig = config;
440
+ const stateMap = /* @__PURE__ */ new WeakMap();
441
+ const run = async (ctx) => {
442
+ const outcome = await executePipeline(pipelineConfig, ctx);
443
+ if (outcome.result.success) {
444
+ stateMap.set(outcome.result.data, outcome.state);
445
+ }
446
+ return outcome.result;
447
+ };
448
+ const rollback = async (_ctx, output) => {
449
+ const state = stateMap.get(output);
450
+ if (state) {
451
+ stateMap.delete(output);
452
+ await executeRollback(state.executed);
453
+ }
454
+ };
455
+ return createStepObject({
456
+ name: config.name,
457
+ requires: config.argsSchema,
458
+ run,
459
+ rollback
460
+ });
461
+ }
462
+ function pipeline(config) {
463
+ if (config.steps) {
464
+ return buildPipelineStep(
465
+ config
466
+ );
467
+ }
468
+ return makeBuilder({
431
469
  name: config.name,
432
- run: (args) => executePipeline(config, args)
470
+ steps: [],
471
+ middleware: config.middleware ? [...config.middleware] : [],
472
+ argsSchema: config.argsSchema,
473
+ strict: config.strict ?? false
433
474
  });
434
475
  }
435
476
 
@@ -443,19 +484,23 @@ async function executeInner(step, ctx) {
443
484
  const cause = toError(err);
444
485
  const error = new PredicateError(`${step.name} predicate: ${cause.message}`);
445
486
  error.cause = cause;
446
- return { step, skipped: false, errors: [error] };
487
+ return { step, skipped: false, error };
447
488
  }
448
- const result = await runInnerStep(step, ctx);
449
- if (!result.success) return { step, skipped: false, errors: [...result.errors] };
489
+ const result = await step.run(ctx);
490
+ if (!result.success) return { step, skipped: false, error: result.error };
450
491
  return { step, skipped: false, output: result.data };
451
492
  }
452
493
  function parallel(...steps) {
453
494
  const name = `parallel(${steps.map((s) => s.name).join(", ")})`;
454
495
  const innerSteps = steps;
455
496
  const run = async (ctx) => {
456
- const settled = await Promise.allSettled(innerSteps.map((step) => executeInner(step, ctx)));
497
+ const frozenCtx = Object.freeze({ ...ctx });
498
+ const settled = await Promise.allSettled(
499
+ innerSteps.map((step) => executeInner(step, frozenCtx))
500
+ );
457
501
  const succeeded = [];
458
502
  const allErrors = [];
503
+ const executed = [];
459
504
  for (const s of settled) {
460
505
  if (s.status === "rejected") {
461
506
  allErrors.push(toError(s.reason));
@@ -464,8 +509,9 @@ function parallel(...steps) {
464
509
  if (r.skipped) continue;
465
510
  if (r.output) {
466
511
  succeeded.push({ step: r.step, output: r.output });
467
- } else if (r.errors) {
468
- allErrors.push(...r.errors);
512
+ executed.push(r.step.name);
513
+ } else if (r.error) {
514
+ allErrors.push(r.error);
469
515
  }
470
516
  }
471
517
  }
@@ -474,44 +520,49 @@ function parallel(...steps) {
474
520
  const { step, output } = succeeded[i];
475
521
  if (step.rollback) {
476
522
  try {
477
- await step.rollback(ctx, output);
523
+ await step.rollback(frozenCtx, output);
478
524
  } catch {
479
525
  }
480
526
  }
481
527
  }
482
- return { success: false, errors: allErrors };
528
+ const error = collapseErrors(allErrors, `${name}: ${allErrors.length} step(s) failed`);
529
+ const meta2 = aggregateMeta(name, frozenCtx, executed);
530
+ return aggregateFailure(error, meta2, name);
483
531
  }
484
532
  const merged = {};
485
533
  for (const { output } of succeeded) {
486
534
  Object.assign(merged, output);
487
535
  }
488
- return { success: true, data: merged, errors: [] };
536
+ executedMap.set(
537
+ merged,
538
+ succeeded.map((s) => ({ step: s.step, output: s.output }))
539
+ );
540
+ const meta = aggregateMeta(name, frozenCtx, executed);
541
+ return aggregateSuccess(merged, meta);
489
542
  };
543
+ const executedMap = /* @__PURE__ */ new WeakMap();
490
544
  const rollback = async (ctx, mergedOutput) => {
545
+ const entries = executedMap.get(mergedOutput);
546
+ if (!entries) return;
547
+ executedMap.delete(mergedOutput);
491
548
  const errors = [];
492
- for (let i = innerSteps.length - 1; i >= 0; i--) {
493
- const step = innerSteps[i];
549
+ for (let i = entries.length - 1; i >= 0; i--) {
550
+ const { step, output } = entries[i];
494
551
  if (!step.rollback) continue;
495
552
  try {
496
- await step.rollback(ctx, mergedOutput);
553
+ await step.rollback(ctx, output);
497
554
  } catch (err) {
498
555
  errors.push(toError(err));
499
556
  }
500
557
  }
501
558
  if (errors.length > 0) {
502
- const error = new RollbackError(`${name}: ${errors.length} rollback(s) failed`);
503
- error.cause = errors;
504
- throw error;
559
+ throw new RollbackError(`${name}: ${errors.length} rollback(s) failed`, errors);
505
560
  }
506
561
  };
507
- return Object.freeze({
562
+ return createStepObject({
508
563
  name,
509
- requires: void 0,
510
- provides: void 0,
511
564
  run,
512
- rollback,
513
- retry: void 0,
514
- timeout: void 0
565
+ rollback
515
566
  });
516
567
  }
517
568
 
@@ -527,50 +578,49 @@ function choice(...args) {
527
578
  const name = `choice(${innerBranches.map(([, step]) => step.name).join(", ")})`;
528
579
  const branchMap = /* @__PURE__ */ new WeakMap();
529
580
  const run = async (ctx) => {
581
+ const frozenCtx = Object.freeze({ ...ctx });
530
582
  for (let i = 0; i < innerBranches.length; i++) {
531
583
  const [predicate, step] = innerBranches[i];
532
584
  let matches;
533
585
  try {
534
- matches = predicate(ctx);
586
+ matches = predicate(frozenCtx);
535
587
  } catch (err) {
536
588
  const cause = toError(err);
537
589
  const error = new PredicateError(`${name} predicate: ${cause.message}`);
538
590
  error.cause = cause;
539
- return { success: false, errors: [error] };
591
+ const meta3 = aggregateMeta(name, frozenCtx, []);
592
+ return aggregateFailure(error, meta3, name);
540
593
  }
541
594
  if (!matches) continue;
542
- const result = await runInnerStep(step, ctx);
543
- if (!result.success) return result;
595
+ const result = await step.run(frozenCtx);
596
+ if (!result.success) {
597
+ const meta3 = aggregateMeta(name, frozenCtx, [step.name]);
598
+ return aggregateFailure(result.error, meta3, name);
599
+ }
544
600
  branchMap.set(result.data, i);
545
- return { success: true, data: result.data, errors: [] };
601
+ const meta2 = aggregateMeta(name, frozenCtx, [step.name]);
602
+ return aggregateSuccess(result.data, meta2);
546
603
  }
547
- return {
548
- success: false,
549
- errors: [new ChoiceNoMatchError(`${name}: no branch matched`)]
550
- };
604
+ const meta = aggregateMeta(name, frozenCtx, []);
605
+ return aggregateFailure(new ChoiceNoMatchError(`${name}: no branch matched`), meta, name);
551
606
  };
552
607
  const rollback = async (ctx, output) => {
553
608
  const branchIndex = branchMap.get(output);
554
609
  if (branchIndex === void 0) return;
610
+ branchMap.delete(output);
555
611
  const [, step] = innerBranches[branchIndex];
556
612
  if (step.rollback) {
557
613
  try {
558
614
  await step.rollback(ctx, output);
559
615
  } catch (err) {
560
- const error = new RollbackError(`${name}: 1 rollback(s) failed`);
561
- error.cause = [toError(err)];
562
- throw error;
616
+ throw new RollbackError(`${name}: 1 rollback(s) failed`, [toError(err)]);
563
617
  }
564
618
  }
565
619
  };
566
- return Object.freeze({
620
+ return createStepObject({
567
621
  name,
568
- requires: void 0,
569
- provides: void 0,
570
622
  run,
571
- rollback,
572
- retry: void 0,
573
- timeout: void 0
623
+ rollback
574
624
  });
575
625
  }
576
626
 
@@ -583,23 +633,24 @@ function map(key, collection, fnOrStep) {
583
633
  const name = stepMode ? `map(${key}, ${fnOrStep.name})` : `map(${key})`;
584
634
  const executionMap = /* @__PURE__ */ new WeakMap();
585
635
  const run = async (ctx) => {
636
+ const frozenCtx = Object.freeze({ ...ctx });
637
+ const meta = baseMeta(name, frozenCtx);
586
638
  let items;
587
639
  try {
588
- items = collection(ctx);
640
+ items = collection(frozenCtx);
589
641
  } catch (err) {
590
- return {
591
- success: false,
592
- errors: [toError(err)]
593
- };
642
+ return stepFailure(toError(err), meta, name);
594
643
  }
595
644
  if (stepMode) {
596
- return runStepMode(fnOrStep, items, ctx, name, key, executionMap);
645
+ return runStepMode(fnOrStep, items, frozenCtx, name, key, executionMap, meta);
597
646
  } else {
598
647
  return runFunctionMode(
599
648
  fnOrStep,
600
649
  items,
601
- ctx,
602
- key
650
+ frozenCtx,
651
+ key,
652
+ name,
653
+ meta
603
654
  );
604
655
  }
605
656
  };
@@ -608,37 +659,32 @@ function map(key, collection, fnOrStep) {
608
659
  if (!step.rollback) return;
609
660
  const exec = executionMap.get(output);
610
661
  if (!exec) return;
662
+ executionMap.delete(output);
611
663
  const results = output[key];
612
664
  const errors = [];
613
665
  for (let i = results.length - 1; i >= 0; i--) {
614
666
  try {
615
- const itemCtx = { ...exec.ctx, ...exec.items[i] };
667
+ const itemCtx = Object.freeze({ ...exec.ctx, ...exec.items[i] });
616
668
  await step.rollback(itemCtx, results[i]);
617
669
  } catch (err) {
618
670
  errors.push(toError(err));
619
671
  }
620
672
  }
621
673
  if (errors.length > 0) {
622
- const error = new RollbackError(`${name}: ${errors.length} rollback(s) failed`);
623
- error.cause = errors;
624
- throw error;
674
+ throw new RollbackError(`${name}: ${errors.length} rollback(s) failed`, errors);
625
675
  }
626
676
  } : void 0;
627
- return Object.freeze({
677
+ return createStepObject({
628
678
  name,
629
- requires: void 0,
630
- provides: void 0,
631
679
  run,
632
- rollback,
633
- retry: void 0,
634
- timeout: void 0
680
+ rollback
635
681
  });
636
682
  }
637
- async function runStepMode(step, items, ctx, name, key, executionMap) {
683
+ async function runStepMode(step, items, ctx, name, key, executionMap, meta) {
638
684
  const settled = await Promise.allSettled(
639
685
  items.map(async (item) => {
640
686
  const itemCtx = { ...ctx, ...item };
641
- return runInnerStep(step, itemCtx);
687
+ return step.run(itemCtx);
642
688
  })
643
689
  );
644
690
  const succeeded = [];
@@ -648,7 +694,7 @@ async function runStepMode(step, items, ctx, name, key, executionMap) {
648
694
  if (s.status === "rejected") {
649
695
  allErrors.push(toError(s.reason));
650
696
  } else if (!s.value.success) {
651
- allErrors.push(...s.value.errors);
697
+ allErrors.push(s.value.error);
652
698
  } else {
653
699
  succeeded.push({ index: i, output: s.value.data });
654
700
  }
@@ -657,172 +703,135 @@ async function runStepMode(step, items, ctx, name, key, executionMap) {
657
703
  if (step.rollback) {
658
704
  for (let i = succeeded.length - 1; i >= 0; i--) {
659
705
  try {
660
- const itemCtx = { ...ctx, ...items[succeeded[i].index] };
706
+ const itemCtx = Object.freeze({ ...ctx, ...items[succeeded[i].index] });
661
707
  await step.rollback(itemCtx, succeeded[i].output);
662
708
  } catch {
663
709
  }
664
710
  }
665
711
  }
666
- return { success: false, errors: allErrors };
712
+ return stepFailure(
713
+ collapseErrors(allErrors, `${name}: ${allErrors.length} item(s) failed`),
714
+ meta,
715
+ name
716
+ );
667
717
  }
668
718
  const results = succeeded.map((s) => s.output);
669
719
  const data = { [key]: results };
670
720
  executionMap.set(data, { items, ctx: { ...ctx } });
671
- return { success: true, data, errors: [] };
721
+ return stepSuccess(data, meta);
672
722
  }
673
- async function runFunctionMode(fn, items, ctx, key) {
723
+ async function runFunctionMode(fn, items, ctx, key, stepName, meta) {
674
724
  const settled = await Promise.allSettled(items.map(async (item) => fn(item, ctx)));
675
- const results = [];
676
725
  const allErrors = [];
677
726
  for (const s of settled) {
678
- if (s.status === "rejected") {
679
- allErrors.push(toError(s.reason));
680
- } else {
681
- results.push(s.value);
682
- }
727
+ if (s.status === "rejected") allErrors.push(toError(s.reason));
683
728
  }
684
729
  if (allErrors.length > 0) {
685
- return { success: false, errors: allErrors };
730
+ return stepFailure(
731
+ collapseErrors(allErrors, `${stepName}: ${allErrors.length} item(s) failed`),
732
+ meta,
733
+ stepName
734
+ );
686
735
  }
736
+ const results = settled.map((s) => s.value);
687
737
  const data = { [key]: results };
688
- return { success: true, data, errors: [] };
738
+ return stepSuccess(data, meta);
689
739
  }
690
740
 
691
741
  // src/filter.ts
692
742
  function filter(key, collection, predicate) {
693
743
  const name = `filter(${key})`;
694
744
  const run = async (ctx) => {
745
+ const frozenCtx = Object.freeze({ ...ctx });
746
+ const meta = baseMeta(name, frozenCtx);
695
747
  let items;
696
748
  try {
697
- items = collection(ctx);
749
+ items = collection(frozenCtx);
698
750
  } catch (err) {
699
- return {
700
- success: false,
701
- errors: [toError(err)]
702
- };
751
+ return stepFailure(toError(err), meta, name);
703
752
  }
704
753
  return runFilter(
705
754
  items,
706
- ctx,
755
+ frozenCtx,
707
756
  predicate,
708
- key
757
+ key,
758
+ name,
759
+ meta
709
760
  );
710
761
  };
711
- return Object.freeze({
762
+ return createStepObject({
712
763
  name,
713
- requires: void 0,
714
- provides: void 0,
715
- run,
716
- rollback: void 0,
717
- retry: void 0,
718
- timeout: void 0
764
+ run
719
765
  });
720
766
  }
721
- async function runFilter(items, ctx, predicate, key) {
767
+ async function runFilter(items, ctx, predicate, key, name, meta) {
722
768
  const settled = await Promise.allSettled(items.map(async (item) => predicate(item, ctx)));
723
- const results = [];
724
769
  const allErrors = [];
770
+ for (const s of settled) {
771
+ if (s.status === "rejected") allErrors.push(toError(s.reason));
772
+ }
773
+ if (allErrors.length > 0) {
774
+ return stepFailure(
775
+ collapseErrors(allErrors, `${name}: ${allErrors.length} predicate(s) failed`),
776
+ meta,
777
+ name
778
+ );
779
+ }
780
+ const results = [];
725
781
  for (let i = 0; i < settled.length; i++) {
726
- const s = settled[i];
727
- if (s.status === "rejected") {
728
- allErrors.push(toError(s.reason));
729
- } else if (s.value) {
782
+ if (settled[i].value) {
730
783
  results.push(items[i]);
731
784
  }
732
785
  }
733
- if (allErrors.length > 0) {
734
- return { success: false, errors: allErrors };
735
- }
736
786
  const data = { [key]: results };
737
- return { success: true, data, errors: [] };
787
+ return stepSuccess(data, meta);
738
788
  }
739
789
 
740
790
  // src/flat-map.ts
741
791
  function flatMap(key, collection, fn) {
742
792
  const name = `flatMap(${key})`;
743
793
  const run = async (ctx) => {
794
+ const frozenCtx = Object.freeze({ ...ctx });
795
+ const meta = baseMeta(name, frozenCtx);
744
796
  let items;
745
797
  try {
746
- items = collection(ctx);
798
+ items = collection(frozenCtx);
747
799
  } catch (err) {
748
- return {
749
- success: false,
750
- errors: [toError(err)]
751
- };
800
+ return stepFailure(toError(err), meta, name);
752
801
  }
753
802
  return runFlatMap(
754
803
  items,
755
- ctx,
804
+ frozenCtx,
756
805
  fn,
757
- key
806
+ key,
807
+ name,
808
+ meta
758
809
  );
759
810
  };
760
- return Object.freeze({
811
+ return createStepObject({
761
812
  name,
762
- requires: void 0,
763
- provides: void 0,
764
- run,
765
- rollback: void 0,
766
- retry: void 0,
767
- timeout: void 0
813
+ run
768
814
  });
769
815
  }
770
- async function runFlatMap(items, ctx, fn, key) {
816
+ async function runFlatMap(items, ctx, fn, key, name, meta) {
771
817
  const settled = await Promise.allSettled(items.map(async (item) => fn(item, ctx)));
772
- const results = [];
773
818
  const allErrors = [];
774
819
  for (const s of settled) {
775
- if (s.status === "rejected") {
776
- allErrors.push(toError(s.reason));
777
- } else {
778
- results.push(...s.value);
779
- }
820
+ if (s.status === "rejected") allErrors.push(toError(s.reason));
780
821
  }
781
822
  if (allErrors.length > 0) {
782
- return { success: false, errors: allErrors };
823
+ return stepFailure(
824
+ collapseErrors(allErrors, `${name}: ${allErrors.length} callback(s) failed`),
825
+ meta,
826
+ name
827
+ );
783
828
  }
784
- const data = { [key]: results };
785
- return { success: true, data, errors: [] };
786
- }
787
-
788
- // src/builder.ts
789
- function makeBuilder(state) {
790
- return Object.freeze({
791
- step: (step) => makeBuilder({
792
- ...state,
793
- steps: [...state.steps, step]
794
- }),
795
- use: (...middleware) => makeBuilder({
796
- ...state,
797
- middleware: [...state.middleware, ...middleware]
798
- }),
799
- build: () => buildPipeline({
800
- name: state.name,
801
- steps: state.steps,
802
- middleware: state.middleware.length > 0 ? state.middleware : void 0,
803
- argsSchema: state.argsSchema,
804
- strict: state.strict || void 0
805
- })
806
- });
807
- }
808
- function createPipeline(name, schemaOrOptions, options) {
809
- let argsSchema;
810
- let strict = false;
811
- if (schemaOrOptions != null) {
812
- if ("safeParse" in schemaOrOptions) {
813
- argsSchema = schemaOrOptions;
814
- strict = options?.strict ?? false;
815
- } else {
816
- strict = schemaOrOptions.strict ?? false;
817
- }
829
+ const results = [];
830
+ for (const s of settled) {
831
+ results.push(...s.value);
818
832
  }
819
- return makeBuilder({
820
- name,
821
- steps: [],
822
- middleware: [],
823
- argsSchema,
824
- strict
825
- });
833
+ const data = { [key]: results };
834
+ return stepSuccess(data, meta);
826
835
  }
827
836
  // Annotate the CommonJS export names for ESM import in node:
828
837
  0 && (module.exports = {
@@ -837,14 +846,13 @@ function createPipeline(name, schemaOrOptions, options) {
837
846
  StrictOverlapError,
838
847
  TimeoutError,
839
848
  UnknownError,
840
- buildPipeline,
841
849
  choice,
842
- createPipeline,
843
850
  defineStep,
844
851
  filter,
845
852
  flatMap,
846
853
  map,
847
854
  parallel,
855
+ pipeline,
848
856
  when
849
857
  });
850
858
  //# sourceMappingURL=index.cjs.map