runsheet 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Scott Haug
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,370 @@
1
+ # runsheet
2
+
3
+ [![npm version][npm-badge]][npm-url] [![CI][ci-badge]][ci-url]
4
+ [![license][license-badge]][license-url]
5
+
6
+ Type-safe, composable business logic pipelines for TypeScript.
7
+
8
+ Built on [composable-functions] for `Result` semantics and error handling.
9
+
10
+ ## Why runsheet
11
+
12
+ Business logic has a way of growing into tangled, hard-to-test code. A checkout
13
+ flow starts as one function, then gains validation, payment processing,
14
+ inventory reservation, email notifications, each with its own failure modes and
15
+ cleanup logic. Before long you're staring at a 300-line function with nested
16
+ try/catch blocks and no clear way to reuse any of it.
17
+
18
+ runsheet gives that logic structure. You break work into small, focused steps
19
+ with explicit inputs and outputs, then compose them into pipelines. Each step is
20
+ independently testable. The pipeline handles context passing, rollback on
21
+ failure, and schema validation at every boundary. TypeScript enforces that steps
22
+ fit together correctly at compile time, and immutable data semantics mean steps
23
+ can't accidentally interfere with each other.
24
+
25
+ It's an organizational layer for business logic that encourages reuse,
26
+ testability, type safety, and immutable data flow, without the overhead of a
27
+ full effect system or workflow engine.
28
+
29
+ The name takes its inspiration from the world of stage productions and live
30
+ broadcast events. A runsheet is the document that sequences every cue, handoff,
31
+ and contingency so the show runs smoothly. Same idea here: you define the steps,
32
+ and runsheet makes sure they execute in order with clear contracts between them.
33
+
34
+ ## What this is
35
+
36
+ A pipeline orchestration library with:
37
+
38
+ - **Strongly typed steps** — each step's `run`, `rollback`, `requires`, and
39
+ `provides` carry concrete types. Your IDE shows exact input and output shapes
40
+ on hover. Both sync and async `run` functions are supported.
41
+ - **Type-safe accumulated context** — each step declares what it requires and
42
+ provides. TypeScript enforces at compile time that requirements are satisfied,
43
+ and `buildPipeline` infers the full output type from the steps you pass.
44
+ - **Immutable step boundaries** — context is frozen between steps. Each step
45
+ receives a snapshot and returns only what it adds.
46
+ - **Rollback with snapshots** — on failure, rollback handlers execute in reverse
47
+ order. Each receives the pre-step context and the step's output.
48
+ - **Middleware** — cross-cutting concerns (logging, timing, metrics) wrap the
49
+ full step lifecycle.
50
+ - **Standalone** — no framework dependencies. Works anywhere TypeScript runs.
51
+
52
+ ## Install
53
+
54
+ ```bash
55
+ pnpm add runsheet zod
56
+ ```
57
+
58
+ `zod` is an optional peer dependency — only needed if you use schema validation.
59
+ If you only use TypeScript generics for type safety, you can install runsheet
60
+ alone:
61
+
62
+ ```bash
63
+ pnpm add runsheet
64
+ ```
65
+
66
+ ## Quick start
67
+
68
+ ### Define steps
69
+
70
+ Each step declares what it reads from context (`requires`) and what it adds
71
+ (`provides`). Schemas are optional — you can use TypeScript generics alone.
72
+
73
+ Step `run` functions can be sync or async — both are supported.
74
+
75
+ ```typescript
76
+ import { defineStep } from 'runsheet';
77
+ import { z } from 'zod';
78
+
79
+ const validateOrder = defineStep({
80
+ name: 'validateOrder',
81
+ requires: z.object({ orderId: z.string() }),
82
+ provides: z.object({
83
+ order: z.object({ id: z.string(), total: z.number() }),
84
+ }),
85
+ run: async (ctx) => {
86
+ const order = await db.orders.find(ctx.orderId);
87
+ if (!order) throw new Error(`Order ${ctx.orderId} not found`);
88
+ return { order };
89
+ },
90
+ });
91
+
92
+ const chargePayment = defineStep({
93
+ name: 'chargePayment',
94
+ requires: z.object({ order: z.object({ total: z.number() }) }),
95
+ provides: z.object({ chargeId: z.string() }),
96
+ run: async (ctx) => {
97
+ const charge = await stripe.charges.create({ amount: ctx.order.total });
98
+ return { chargeId: charge.id };
99
+ },
100
+ rollback: async (_ctx, output) => {
101
+ await stripe.refunds.create({ charge: output.chargeId });
102
+ },
103
+ });
104
+
105
+ const sendConfirmation = defineStep({
106
+ name: 'sendConfirmation',
107
+ requires: z.object({
108
+ order: z.object({ id: z.string() }),
109
+ chargeId: z.string(),
110
+ }),
111
+ provides: z.object({ sentAt: z.date() }),
112
+ run: async (ctx) => {
113
+ await email.send({ orderId: ctx.order.id, chargeId: ctx.chargeId });
114
+ return { sentAt: new Date() };
115
+ },
116
+ });
117
+ ```
118
+
119
+ Each step is fully typed — your IDE (or other favorite typechecker) can see its
120
+ exact input and output types, allowing you to compose steps that maintain type
121
+ integrity from one step to the next.
122
+
123
+ ### Build and run a pipeline
124
+
125
+ ```typescript
126
+ import { buildPipeline } from 'runsheet';
127
+
128
+ const placeOrder = buildPipeline({
129
+ name: 'placeOrder',
130
+ steps: [validateOrder, chargePayment, sendConfirmation],
131
+ });
132
+
133
+ const result = await placeOrder.run({ orderId: '123' });
134
+
135
+ if (result.success) {
136
+ console.log(result.data.chargeId); // string — fully typed
137
+ console.log(result.data.sentAt); // Date
138
+ } else {
139
+ console.error(result.errors); // what went wrong
140
+ console.log(result.rollback); // { completed: [...], failed: [...] }
141
+ }
142
+ ```
143
+
144
+ The pipeline's result type is inferred from the steps — `result.data` carries
145
+ the intersection of all step outputs, not an erased `Record<string, unknown>`.
146
+
147
+ ### Builder API
148
+
149
+ For complex pipelines, the builder gives progressive type narrowing — each
150
+ `.step()` call extends the known context type:
151
+
152
+ ```typescript
153
+ import { createPipeline } from 'runsheet';
154
+ import { z } from 'zod';
155
+
156
+ const placeOrder = createPipeline(
157
+ 'placeOrder',
158
+ z.object({ orderId: z.string() }),
159
+ )
160
+ .step(validateOrder) // context now includes order
161
+ .step(chargePayment) // context now includes chargeId
162
+ .step(sendConfirmation) // context now includes sentAt
163
+ .build();
164
+ ```
165
+
166
+ Type-only args (no runtime validation of pipeline input):
167
+
168
+ ```typescript
169
+ const placeOrder = createPipeline<{ orderId: string }>('placeOrder')
170
+ .step(validateOrder)
171
+ .step(chargePayment)
172
+ .step(sendConfirmation)
173
+ .build();
174
+ ```
175
+
176
+ ### Generics-only steps
177
+
178
+ Steps don't need Zod schemas — TypeScript generics provide compile-time safety
179
+ without runtime validation at step boundaries:
180
+
181
+ ```typescript
182
+ const logOrder = defineStep<{ order: { id: string } }, { loggedAt: Date }>({
183
+ name: 'logOrder',
184
+ run: async (ctx) => {
185
+ console.log(`Processing order ${ctx.order.id}`);
186
+ return { loggedAt: new Date() };
187
+ },
188
+ });
189
+ ```
190
+
191
+ ### Conditional steps
192
+
193
+ ```typescript
194
+ import { when } from 'runsheet';
195
+
196
+ const placeOrder = buildPipeline({
197
+ name: 'placeOrder',
198
+ steps: [
199
+ validateOrder,
200
+ chargePayment,
201
+ when((ctx) => ctx.order.total > 10000, notifyManager),
202
+ sendConfirmation,
203
+ ],
204
+ });
205
+ ```
206
+
207
+ Skipped steps produce no snapshot, no rollback entry. The pipeline result tracks
208
+ which steps were skipped in `result.meta.stepsSkipped`.
209
+
210
+ ### Middleware
211
+
212
+ Middleware wraps the entire step lifecycle including schema validation:
213
+
214
+ ```typescript
215
+ import { buildPipeline } from 'runsheet';
216
+ import type { StepMiddleware } from 'runsheet';
217
+
218
+ const timing: StepMiddleware = (step, next) => async (ctx) => {
219
+ const start = performance.now();
220
+ const result = await next(ctx);
221
+ console.log(`${step.name}: ${performance.now() - start}ms`);
222
+ return result;
223
+ };
224
+
225
+ const logging: StepMiddleware = (step, next) => async (ctx) => {
226
+ console.log(`→ ${step.name}`);
227
+ const result = await next(ctx);
228
+ console.log(`${result.success ? '✓' : '✗'} ${step.name}`);
229
+ return result;
230
+ };
231
+
232
+ const placeOrder = buildPipeline({
233
+ name: 'placeOrder',
234
+ steps: [validateOrder, chargePayment, sendConfirmation],
235
+ middleware: [logging, timing],
236
+ });
237
+ ```
238
+
239
+ Middleware with the builder:
240
+
241
+ ```typescript
242
+ const placeOrder = createPipeline<{ orderId: string }>('placeOrder')
243
+ .use(logging, timing)
244
+ .step(validateOrder)
245
+ .step(chargePayment)
246
+ .step(sendConfirmation)
247
+ .build();
248
+ ```
249
+
250
+ ## Rollback
251
+
252
+ When a step fails, rollback handlers for all previously completed steps execute
253
+ in reverse order. Each handler receives the pre-step context snapshot and the
254
+ step's output:
255
+
256
+ ```typescript
257
+ const reserveInventory = defineStep({
258
+ name: 'reserveInventory',
259
+ requires: z.object({ order: z.object({ items: z.array(z.string()) }) }),
260
+ provides: z.object({ reservationId: z.string() }),
261
+ run: async (ctx) => {
262
+ const reservation = await inventory.reserve(ctx.order.items);
263
+ return { reservationId: reservation.id };
264
+ },
265
+ rollback: async (_ctx, output) => {
266
+ await inventory.release(output.reservationId);
267
+ },
268
+ });
269
+ ```
270
+
271
+ Rollback is best-effort: if a rollback handler throws, remaining rollbacks still
272
+ execute. The result includes a structured report:
273
+
274
+ ```typescript
275
+ if (!result.success) {
276
+ result.rollback.completed; // ['chargePayment', 'reserveInventory']
277
+ result.rollback.failed; // [{ step: 'sendNotification', error: Error }]
278
+ }
279
+ ```
280
+
281
+ ## Pipeline result
282
+
283
+ Every pipeline returns a `PipelineResult` with execution metadata:
284
+
285
+ ```typescript
286
+ // Success
287
+ {
288
+ success: true,
289
+ data: { /* accumulated context — fully typed */ },
290
+ errors: [],
291
+ meta: {
292
+ pipeline: 'placeOrder',
293
+ args: { orderId: '123' },
294
+ stepsExecuted: ['validateOrder', 'chargePayment', 'sendConfirmation'],
295
+ stepsSkipped: [],
296
+ }
297
+ }
298
+
299
+ // Failure
300
+ {
301
+ success: false,
302
+ errors: [Error],
303
+ meta: { pipeline, args, stepsExecuted, stepsSkipped },
304
+ failedStep: 'chargePayment',
305
+ rollback: { completed: [...], failed: [...] },
306
+ }
307
+ ```
308
+
309
+ ## API reference
310
+
311
+ ### `defineStep(config)`
312
+
313
+ Define a pipeline step. Returns a strongly typed `TypedStep` — `run`,
314
+ `rollback`, `requires`, and `provides` all carry concrete types matching the
315
+ schemas or generics you provide.
316
+
317
+ | Option | Type | Description |
318
+ | ---------- | ----------------------- | ------------------------------------------------- |
319
+ | `name` | `string` | Step name (used in metadata and rollback reports) |
320
+ | `requires` | `ZodSchema` | Optional schema for required context keys |
321
+ | `provides` | `ZodSchema` | Optional schema for provided context keys |
322
+ | `run` | `(ctx) => output` | Step implementation (sync or async) |
323
+ | `rollback` | `(ctx, output) => void` | Optional rollback handler |
324
+
325
+ ### `buildPipeline(config)`
326
+
327
+ Build a pipeline from an array of steps. The result type is inferred from the
328
+ steps — `pipeline.run()` returns a `PipelineResult` whose `data` is the
329
+ intersection of all step output types.
330
+
331
+ | Option | Type | Description |
332
+ | ------------ | ------------------ | --------------------------------------------- |
333
+ | `name` | `string` | Pipeline name |
334
+ | `steps` | `Step[]` | Steps to execute in order |
335
+ | `middleware` | `StepMiddleware[]` | Optional middleware |
336
+ | `argsSchema` | `ZodSchema` | Optional schema for pipeline input validation |
337
+
338
+ ### `createPipeline(name, argsSchema?)`
339
+
340
+ Start a fluent pipeline builder. Returns a `PipelineBuilder` with:
341
+
342
+ - `.step(step)` — add a step
343
+ - `.use(...middleware)` — add middleware
344
+ - `.build()` — produce the pipeline
345
+
346
+ ### `when(predicate, step)`
347
+
348
+ Wrap a step with a conditional predicate. The step only executes when the
349
+ predicate returns `true`.
350
+
351
+ ### `StepMiddleware`
352
+
353
+ ```typescript
354
+ type StepMiddleware = (step: StepInfo, next: StepExecutor) => StepExecutor;
355
+ ```
356
+
357
+ ## License
358
+
359
+ MIT
360
+
361
+ <!-- Reference links — please keep alphabetized -->
362
+
363
+ [ci-badge]:
364
+ https://github.com/shaug/runsheet-js/actions/workflows/ci.yml/badge.svg
365
+ [ci-url]: https://github.com/shaug/runsheet-js/actions/workflows/ci.yml
366
+ [composable-functions]: https://github.com/seasonedcc/composable-functions
367
+ [license-badge]: https://img.shields.io/npm/l/runsheet
368
+ [license-url]: https://github.com/shaug/runsheet-js/blob/main/LICENSE
369
+ [npm-badge]: https://img.shields.io/npm/v/runsheet
370
+ [npm-url]: https://www.npmjs.com/package/runsheet
package/dist/index.cjs ADDED
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ RunsheetError: () => RunsheetError,
24
+ buildPipeline: () => buildPipeline,
25
+ createPipeline: () => createPipeline,
26
+ defineStep: () => defineStep,
27
+ when: () => when
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/define-step.ts
32
+ var import_composable_functions = require("composable-functions");
33
+ function defineStep(config) {
34
+ const wrappedRun = (0, import_composable_functions.composable)(config.run);
35
+ return Object.freeze({
36
+ name: config.name,
37
+ requires: config.requires ?? void 0,
38
+ provides: config.provides ?? void 0,
39
+ run: wrappedRun,
40
+ rollback: config.rollback ? async (ctx, output) => {
41
+ await config.rollback(
42
+ ctx,
43
+ output
44
+ );
45
+ } : void 0
46
+ });
47
+ }
48
+
49
+ // src/errors.ts
50
+ var RunsheetError = class extends Error {
51
+ /** Discriminant code identifying the type of library error. */
52
+ code;
53
+ /**
54
+ * @param code - The error code.
55
+ * @param message - A human-readable description of the failure.
56
+ */
57
+ constructor(code, message) {
58
+ super(message);
59
+ this.name = "RunsheetError";
60
+ this.code = code;
61
+ }
62
+ };
63
+
64
+ // src/middleware.ts
65
+ function applyMiddleware(middlewares, step, executor) {
66
+ return middlewares.reduceRight((next, mw) => mw(step, next), executor);
67
+ }
68
+
69
+ // src/when.ts
70
+ function when(predicate, step) {
71
+ return Object.freeze({
72
+ ...step,
73
+ predicate
74
+ });
75
+ }
76
+ function isConditionalStep(step) {
77
+ return "predicate" in step && typeof step["predicate"] === "function";
78
+ }
79
+
80
+ // src/pipeline.ts
81
+ function validateSchema(schema, data, label, code) {
82
+ if (!schema) return { success: true, data };
83
+ const parsed = schema.safeParse(data);
84
+ if (parsed.success) return { success: true, data: parsed.data };
85
+ const errors = parsed.error.issues.map(
86
+ (issue) => new RunsheetError(code, `${label}: ${issue.path.join(".")}: ${issue.message}`)
87
+ );
88
+ return { success: false, errors };
89
+ }
90
+ async function executeRollback(executedSteps, snapshots, outputs) {
91
+ const completed = [];
92
+ const failed = [];
93
+ for (let i = executedSteps.length - 1; i >= 0; i--) {
94
+ const step = executedSteps[i];
95
+ if (!step.rollback) continue;
96
+ try {
97
+ await step.rollback(snapshots[i], outputs[i]);
98
+ completed.push(step.name);
99
+ } catch (err) {
100
+ failed.push({
101
+ step: step.name,
102
+ error: err instanceof Error ? err : new Error(String(err))
103
+ });
104
+ }
105
+ }
106
+ return Object.freeze({ completed, failed });
107
+ }
108
+ function createExecutionState(args) {
109
+ return {
110
+ context: Object.freeze({ ...args }),
111
+ snapshots: [],
112
+ outputs: [],
113
+ executedSteps: [],
114
+ stepsExecuted: [],
115
+ stepsSkipped: []
116
+ };
117
+ }
118
+ function pipelineFailure(pipelineName, args, state, failedStep, errors, rollback) {
119
+ return Object.freeze({
120
+ success: false,
121
+ errors,
122
+ meta: Object.freeze({
123
+ pipeline: pipelineName,
124
+ args,
125
+ stepsExecuted: state.stepsExecuted,
126
+ stepsSkipped: state.stepsSkipped
127
+ }),
128
+ failedStep,
129
+ rollback
130
+ });
131
+ }
132
+ function pipelineSuccess(pipelineName, args, state) {
133
+ return Object.freeze({
134
+ success: true,
135
+ data: state.context,
136
+ errors: [],
137
+ meta: Object.freeze({
138
+ pipeline: pipelineName,
139
+ args,
140
+ stepsExecuted: state.stepsExecuted,
141
+ stepsSkipped: state.stepsSkipped
142
+ })
143
+ });
144
+ }
145
+ function createStepExecutor(step) {
146
+ return async (ctx) => {
147
+ const requiresCheck = validateSchema(
148
+ step.requires,
149
+ ctx,
150
+ `${step.name} requires`,
151
+ "REQUIRES_VALIDATION"
152
+ );
153
+ if (!requiresCheck.success) {
154
+ return { success: false, errors: requiresCheck.errors };
155
+ }
156
+ const result = await step.run(ctx);
157
+ if (!result.success) return result;
158
+ const providesCheck = validateSchema(
159
+ step.provides,
160
+ result.data,
161
+ `${step.name} provides`,
162
+ "PROVIDES_VALIDATION"
163
+ );
164
+ if (!providesCheck.success) {
165
+ return { success: false, errors: providesCheck.errors };
166
+ }
167
+ return {
168
+ success: true,
169
+ data: providesCheck.data,
170
+ errors: []
171
+ };
172
+ };
173
+ }
174
+ async function executePipeline(config, args) {
175
+ if (config.argsSchema) {
176
+ const argsCheck = validateSchema(
177
+ config.argsSchema,
178
+ args,
179
+ `${config.name} args`,
180
+ "ARGS_VALIDATION"
181
+ );
182
+ if (!argsCheck.success) {
183
+ const state2 = createExecutionState(args);
184
+ return pipelineFailure(
185
+ config.name,
186
+ args,
187
+ state2,
188
+ config.name,
189
+ argsCheck.errors,
190
+ Object.freeze({ completed: [], failed: [] })
191
+ );
192
+ }
193
+ }
194
+ const state = createExecutionState(args);
195
+ const middlewares = config.middleware ?? [];
196
+ for (const step of config.steps) {
197
+ try {
198
+ if (isConditionalStep(step) && !step.predicate(state.context)) {
199
+ state.stepsSkipped.push(step.name);
200
+ continue;
201
+ }
202
+ } catch (err) {
203
+ const message = err instanceof Error ? err.message : String(err);
204
+ const error = new RunsheetError("PREDICATE", `${step.name} predicate: ${message}`);
205
+ if (err instanceof Error) error.cause = err;
206
+ const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
207
+ return pipelineFailure(config.name, args, state, step.name, [error], rollback);
208
+ }
209
+ state.snapshots.push(state.context);
210
+ const baseExecutor = createStepExecutor(step);
211
+ const executor = applyMiddleware(
212
+ middlewares,
213
+ { name: step.name, requires: step.requires, provides: step.provides },
214
+ baseExecutor
215
+ );
216
+ let result;
217
+ try {
218
+ result = await executor(state.context);
219
+ } catch (err) {
220
+ const error = err instanceof Error ? err : new Error(String(err));
221
+ state.snapshots.pop();
222
+ const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
223
+ return pipelineFailure(config.name, args, state, step.name, [error], rollback);
224
+ }
225
+ if (!result.success) {
226
+ state.snapshots.pop();
227
+ const rollback = await executeRollback(state.executedSteps, state.snapshots, state.outputs);
228
+ return pipelineFailure(config.name, args, state, step.name, result.errors, rollback);
229
+ }
230
+ const output = result.data;
231
+ state.outputs.push(output);
232
+ state.executedSteps.push(step);
233
+ state.stepsExecuted.push(step.name);
234
+ state.context = Object.freeze({ ...state.context, ...output });
235
+ }
236
+ return pipelineSuccess(config.name, args, state);
237
+ }
238
+ function buildPipeline(config) {
239
+ return Object.freeze({
240
+ name: config.name,
241
+ run: (args) => executePipeline(config, args)
242
+ });
243
+ }
244
+
245
+ // src/builder.ts
246
+ function makeBuilder(state) {
247
+ return Object.freeze({
248
+ step: (step) => makeBuilder({
249
+ ...state,
250
+ steps: [...state.steps, step]
251
+ }),
252
+ use: (...middleware) => makeBuilder({
253
+ ...state,
254
+ middleware: [...state.middleware, ...middleware]
255
+ }),
256
+ build: () => buildPipeline({
257
+ name: state.name,
258
+ steps: state.steps,
259
+ middleware: state.middleware.length > 0 ? state.middleware : void 0,
260
+ argsSchema: state.argsSchema
261
+ })
262
+ });
263
+ }
264
+ function createPipeline(name, argsSchema) {
265
+ return makeBuilder({
266
+ name,
267
+ steps: [],
268
+ middleware: [],
269
+ argsSchema
270
+ });
271
+ }
272
+ // Annotate the CommonJS export names for ESM import in node:
273
+ 0 && (module.exports = {
274
+ RunsheetError,
275
+ buildPipeline,
276
+ createPipeline,
277
+ defineStep,
278
+ when
279
+ });
280
+ //# sourceMappingURL=index.cjs.map