runsheet 0.5.0 → 0.7.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/llms.txt CHANGED
@@ -7,8 +7,8 @@ focused steps with explicit inputs and outputs, then compose them into pipelines
7
7
  that handle context passing, rollback on failure, and schema validation at every
8
8
  boundary.
9
9
 
10
- Built on composable-functions for Result semantics. Zod is supported for runtime
11
- schema validation but is optional — TypeScript generics alone provide
10
+ Uses its own `StepResult` type for result semantics. Zod is supported for
11
+ runtime schema validation but is optional — TypeScript generics alone provide
12
12
  compile-time safety.
13
13
 
14
14
  ## Core concepts
@@ -20,7 +20,9 @@ compile-time safety.
20
20
  context).
21
21
  - Pipelines run steps sequentially. On failure, rollback handlers execute in
22
22
  reverse order with snapshot-based context.
23
- - Results use `{ success, data, errors }` — never throws.
23
+ - Results use `StepResult<T>` a discriminated union: `StepSuccess<T>`
24
+ (`success`, `data`, `meta`) or `StepFailure` (`success`, `error`, `meta`,
25
+ `failedStep`, `rollback`). Never throws.
24
26
 
25
27
  ## Quick reference
26
28
 
@@ -34,16 +36,16 @@ zod is an optional peer dependency — only needed if you use schema validation.
34
36
  Install it alongside runsheet if you want runtime schema validation at step
35
37
  boundaries: `pnpm add runsheet zod`
36
38
 
37
- ### defineStep
39
+ ### step
38
40
 
39
41
  Define a pipeline step with typed inputs and outputs.
40
42
 
41
43
  ```typescript
42
- import { defineStep } from 'runsheet';
44
+ import { step } from 'runsheet';
43
45
  import { z } from 'zod';
44
46
 
45
47
  // With Zod schemas (runtime + compile-time validation)
46
- const fetchUser = defineStep({
48
+ const fetchUser = step({
47
49
  name: 'fetchUser',
48
50
  requires: z.object({ userId: z.string() }),
49
51
  provides: z.object({ user: z.object({ name: z.string() }) }),
@@ -54,7 +56,7 @@ const fetchUser = defineStep({
54
56
  });
55
57
 
56
58
  // With generics only (compile-time validation, no runtime schemas)
57
- const logUser = defineStep<{ user: { name: string } }, { loggedAt: Date }>({
59
+ const logUser = step<{ user: { name: string } }, { loggedAt: Date }>({
58
60
  name: 'logUser',
59
61
  run: async (ctx) => {
60
62
  console.log(ctx.user.name);
@@ -63,7 +65,7 @@ const logUser = defineStep<{ user: { name: string } }, { loggedAt: Date }>({
63
65
  });
64
66
 
65
67
  // With rollback
66
- const chargePayment = defineStep({
68
+ const chargePayment = step({
67
69
  name: 'chargePayment',
68
70
  requires: z.object({ amount: z.number() }),
69
71
  provides: z.object({ chargeId: z.string() }),
@@ -82,7 +84,7 @@ Step run functions can be sync or async.
82
84
  ### Retry and timeout
83
85
 
84
86
  ```typescript
85
- const callApi = defineStep({
87
+ const callApi = step({
86
88
  name: 'callApi',
87
89
  provides: z.object({ data: z.string() }),
88
90
  retry: { count: 3, delay: 200, backoff: 'exponential' },
@@ -94,7 +96,7 @@ const callApi = defineStep({
94
96
  });
95
97
 
96
98
  // With retryIf to only retry specific errors
97
- const selective = defineStep({
99
+ const selective = step({
98
100
  name: 'selective',
99
101
  retry: {
100
102
  count: 3,
@@ -117,25 +119,25 @@ RetryPolicy type:
117
119
  - backoff?: 'linear' | 'exponential' (default 'linear')
118
120
  - retryIf?: (errors: Error[]) => boolean — return false to stop retrying
119
121
 
120
- ### buildPipeline
122
+ ### pipeline
121
123
 
122
124
  Build a pipeline from an array of steps. The result type is inferred from the
123
125
  steps.
124
126
 
125
127
  ```typescript
126
- import { buildPipeline } from 'runsheet';
128
+ import { pipeline } from 'runsheet';
127
129
 
128
- const pipeline = buildPipeline({
130
+ const p = pipeline({
129
131
  name: 'checkout',
130
132
  steps: [fetchUser, chargePayment, sendEmail],
131
133
  });
132
134
 
133
- const result = await pipeline.run({ userId: '123', amount: 50 });
135
+ const result = await p.run({ userId: '123', amount: 50 });
134
136
 
135
137
  if (result.success) {
136
138
  result.data.chargeId; // string — fully typed
137
139
  } else {
138
- result.errors; // what went wrong
140
+ result.error; // what went wrong (single Error)
139
141
  result.rollback; // { completed: string[], failed: RollbackFailure[] }
140
142
  }
141
143
  ```
@@ -143,7 +145,7 @@ if (result.success) {
143
145
  Optional argsSchema validates pipeline input:
144
146
 
145
147
  ```typescript
146
- const pipeline = buildPipeline({
148
+ const p = pipeline({
147
149
  name: 'checkout',
148
150
  steps: [fetchUser, chargePayment],
149
151
  argsSchema: z.object({ userId: z.string(), amount: z.number() }),
@@ -154,21 +156,37 @@ Use `strict: true` to detect provides key collisions at build time. Throws a
154
156
  RunsheetError with code 'STRICT_OVERLAP' if two steps provide the same key:
155
157
 
156
158
  ```typescript
157
- const pipeline = buildPipeline({
159
+ const p = pipeline({
158
160
  name: 'checkout',
159
161
  steps: [fetchUser, chargePayment],
160
162
  strict: true,
161
163
  });
162
164
  ```
163
165
 
164
- ### createPipeline (builder API)
166
+ ### Pipeline composition
165
167
 
166
- Fluent builder with progressive type narrowing.
168
+ Pipelines are steps pipeline returns an AggregateStep. A pipeline can be used
169
+ directly as a step in another pipeline's steps array:
167
170
 
168
171
  ```typescript
169
- import { createPipeline } from 'runsheet';
172
+ const inner = pipeline({ name: 'inner', steps: [a, b] });
173
+ const outer = pipeline({ name: 'outer', steps: [inner, c, d] });
174
+ ```
175
+
176
+ If a later outer step fails, the inner pipeline's steps are rolled back.
177
+
178
+ ### Builder API
170
179
 
171
- const pipeline = createPipeline('checkout', z.object({ userId: z.string() }))
180
+ Omit `steps` from the config to get a fluent builder with progressive type
181
+ narrowing.
182
+
183
+ ```typescript
184
+ import { pipeline } from 'runsheet';
185
+
186
+ const checkout = pipeline({
187
+ name: 'checkout',
188
+ argsSchema: z.object({ userId: z.string() }),
189
+ })
172
190
  .step(fetchUser)
173
191
  .step(chargePayment)
174
192
  .step(sendEmail)
@@ -178,7 +196,7 @@ const pipeline = createPipeline('checkout', z.object({ userId: z.string() }))
178
196
  Type-only args (no runtime validation of pipeline input):
179
197
 
180
198
  ```typescript
181
- const pipeline = createPipeline<{ userId: string }>('checkout')
199
+ const checkout = pipeline<{ userId: string }>({ name: 'checkout' })
182
200
  .step(fetchUser)
183
201
  .build();
184
202
  ```
@@ -186,13 +204,17 @@ const pipeline = createPipeline<{ userId: string }>('checkout')
186
204
  Strict mode via the builder:
187
205
 
188
206
  ```typescript
189
- createPipeline('checkout', { strict: true })
207
+ pipeline({ name: 'checkout', strict: true })
190
208
  .step(fetchUser)
191
209
  .step(chargePayment)
192
210
  .build();
193
211
 
194
212
  // Or with a schema:
195
- createPipeline('checkout', z.object({ userId: z.string() }), { strict: true })
213
+ pipeline({
214
+ name: 'checkout',
215
+ argsSchema: z.object({ userId: z.string() }),
216
+ strict: true,
217
+ })
196
218
  .step(fetchUser)
197
219
  .step(chargePayment)
198
220
  .build();
@@ -203,7 +225,7 @@ createPipeline('checkout', z.object({ userId: z.string() }), { strict: true })
203
225
  ```typescript
204
226
  import { parallel } from 'runsheet';
205
227
 
206
- const pipeline = buildPipeline({
228
+ const p = pipeline({
207
229
  name: 'checkout',
208
230
  steps: [
209
231
  validateOrder,
@@ -224,7 +246,7 @@ their own requires/provides validation, retry, and timeout. Conditional steps
224
246
  ```typescript
225
247
  import { choice } from 'runsheet';
226
248
 
227
- const pipeline = buildPipeline({
249
+ const p = pipeline({
228
250
  name: 'checkout',
229
251
  steps: [
230
252
  validateOrder,
@@ -240,84 +262,34 @@ const pipeline = buildPipeline({
240
262
 
241
263
  Predicates are evaluated in order — first match wins. A bare step (without a
242
264
  tuple) can be passed as the last argument to serve as a default — equivalent to
243
- `[() => true, step]`. If no predicate matches, the step fails with RunsheetError
244
- code 'CHOICE_NO_MATCH'. Only the matched branch participates in rollback.
245
-
246
- ### map (collection iteration)
247
-
248
- ```typescript
249
- import { map } from 'runsheet';
250
-
251
- // Function form — items can be any type
252
- const pipeline = buildPipeline({
253
- name: 'notify',
254
- steps: [
255
- map(
256
- 'emails',
257
- (ctx) => ctx.users,
258
- async (user) => {
259
- await sendEmail(user.email);
260
- return { email: user.email, sentAt: new Date() };
261
- },
262
- ),
263
- ],
264
- });
265
-
266
- // Step form — reuse existing steps, item spread into context
267
- const pipeline = buildPipeline({
268
- name: 'process',
269
- steps: [map('results', (ctx) => ctx.items, processItem)],
270
- });
271
- ```
272
-
273
- Items run concurrently via Promise.allSettled. Results collected into an array
274
- under the given key. In step form, each item is spread into the pipeline context
275
- ({ ...ctx, ...item }) so the step sees both pipeline-level and per-item values.
276
- On partial failure, succeeded items are rolled back (step form only).
277
-
278
- ### filter (collection filtering)
279
-
280
- ```typescript
281
- import { filter, map } from 'runsheet';
282
-
283
- const pipeline = buildPipeline({
284
- name: 'notify',
285
- steps: [
286
- filter(
287
- 'eligible',
288
- (ctx) => ctx.users,
289
- (user) => user.optedIn,
290
- ),
291
- map('emails', (ctx) => ctx.eligible, sendEmail),
292
- ],
293
- });
294
- ```
265
+ `[() => true, step]`. If no predicate matches, the step returns empty data and
266
+ is treated as a no-op (like a skipped `when()`). Only the matched branch
267
+ participates in rollback.
295
268
 
296
- Predicates run concurrently via Promise.allSettled. Supports sync or async
297
- predicates. Items where the predicate returns true are kept; original order is
298
- preserved. If any predicate throws, the step fails. No rollback (pure
299
- operation).
300
-
301
- ### flatMap (collection expansion)
269
+ ### distribute (collection distribution)
302
270
 
303
271
  ```typescript
304
- import { flatMap } from 'runsheet';
305
-
306
- const pipeline = buildPipeline({
307
- name: 'process',
308
- steps: [
309
- flatMap(
310
- 'lineItems',
311
- (ctx) => ctx.orders,
312
- (order) => order.items,
313
- ),
314
- ],
315
- });
272
+ import { distribute } from 'runsheet';
273
+
274
+ // Single collection — run sendEmail once per accountId
275
+ const d = distribute('emails', { accountIds: 'accountId' }, sendEmail);
276
+ // Requires: { orgId: string, accountIds: string[] }
277
+ // Provides: { emails: { emailId: string }[] }
278
+
279
+ // Cross product — run once per (accountId, regionId) pair
280
+ const d = distribute(
281
+ 'reports',
282
+ { accountIds: 'accountId', regionIds: 'regionId' },
283
+ generateReport,
284
+ );
285
+ // 2 accounts × 3 regions = 6 concurrent executions
316
286
  ```
317
287
 
318
- Maps each item to an array, then flattens one level. Callbacks run concurrently
319
- via Promise.allSettled. Supports sync or async callbacks. If any callback
320
- throws, the step fails. No rollback (pure operation).
288
+ The mapping object connects context array keys to the step's scalar input keys.
289
+ Non-mapped context keys pass through unchanged. Items run concurrently via
290
+ Promise.allSettled. Supports partial-failure rollback (succeeded items rolled
291
+ back) and external-failure rollback (all items rolled back when a later pipeline
292
+ step fails).
321
293
 
322
294
  ### Dependency injection
323
295
 
@@ -325,16 +297,16 @@ Pass dependencies as pipeline args — they're available to every step through t
325
297
  accumulated context without any step needing to provides them:
326
298
 
327
299
  ```typescript
328
- const pipeline = createPipeline<{
300
+ const placeOrder = pipeline<{
329
301
  orderId: string;
330
302
  stripe: Stripe;
331
303
  db: Database;
332
- }>('placeOrder')
304
+ }>({ name: 'placeOrder' })
333
305
  .step(validateOrder)
334
306
  .step(chargePayment)
335
307
  .build();
336
308
 
337
- await pipeline.run({
309
+ await placeOrder.run({
338
310
  orderId: '123',
339
311
  stripe: stripeClient,
340
312
  db: dbClient,
@@ -349,7 +321,7 @@ testing, swap in mocks at the call site.
349
321
  ```typescript
350
322
  import { when } from 'runsheet';
351
323
 
352
- const pipeline = buildPipeline({
324
+ const p = pipeline({
353
325
  name: 'checkout',
354
326
  steps: [
355
327
  fetchUser,
@@ -376,43 +348,41 @@ const timing: StepMiddleware = (step, next) => async (ctx) => {
376
348
  return result;
377
349
  };
378
350
 
379
- const pipeline = buildPipeline({
351
+ const p = pipeline({
380
352
  name: 'checkout',
381
353
  steps: [fetchUser, chargePayment],
382
354
  middleware: [timing],
383
355
  });
384
356
 
385
357
  // Or with the builder:
386
- createPipeline('checkout')
358
+ pipeline({ name: 'checkout' })
387
359
  .use(timing)
388
360
  .step(fetchUser)
389
361
  .step(chargePayment)
390
362
  .build();
391
363
  ```
392
364
 
393
- ### PipelineResult
365
+ ### StepResult
394
366
 
395
- Every pipeline.run() returns a PipelineResult (never throws):
367
+ Every run() returns a StepResult (never throws):
396
368
 
397
369
  ```typescript
398
370
  // Success
399
371
  {
400
372
  success: true,
401
373
  data: { /* accumulated context */ },
402
- errors: [],
403
374
  meta: {
404
- pipeline: 'checkout',
375
+ name: 'checkout',
405
376
  args: { userId: '123' },
406
377
  stepsExecuted: ['fetchUser', 'chargePayment'],
407
- stepsSkipped: [],
408
378
  },
409
379
  }
410
380
 
411
381
  // Failure
412
382
  {
413
383
  success: false,
414
- errors: [Error],
415
- meta: { pipeline, args, stepsExecuted, stepsSkipped },
384
+ error: Error,
385
+ meta: { name, args, stepsExecuted },
416
386
  failedStep: 'chargePayment',
417
387
  rollback: { completed: ['fetchUser'], failed: [] },
418
388
  }
@@ -429,24 +399,23 @@ RunsheetError instances with a discriminated `code` property:
429
399
  - PREDICATE — when() or choice() predicate threw
430
400
  - TIMEOUT — step exceeded its timeout
431
401
  - RETRY_EXHAUSTED — step failed after all retry attempts
432
- - STRICT_OVERLAP — two steps provide the same key (strict mode, build time)
433
- - CHOICE_NO_MATCH — no branch matched in a choice() step
402
+ - STRICT_OVERLAP — two steps provide the same key (strict mode)
434
403
  - ROLLBACK — one or more rollback handlers failed in a combinator
435
- - UNKNOWN — a non-Error value was thrown and caught by the pipeline engine
404
+ - UNKNOWN — a non-Error value was thrown and caught by the engine
436
405
 
437
406
  Application errors (thrown from step run functions) pass through as-is.
438
407
 
439
408
  ## Key exports
440
409
 
441
- Functions: defineStep, buildPipeline, createPipeline, when, parallel, choice,
442
- map, flatMap, filter Classes: RunsheetError, RequiresValidationError,
443
- ProvidesValidationError, ArgsValidationError, PredicateError, TimeoutError,
444
- RetryExhaustedError, StrictOverlapError, ChoiceNoMatchError, RollbackError,
445
- UnknownError Types: Step, TypedStep, StepConfig, RetryPolicy, Pipeline,
446
- PipelineBuilder, PipelineResult, PipelineSuccess, PipelineFailure,
447
- PipelineExecutionMeta, RollbackReport, RollbackFailure, StepMiddleware,
448
- StepInfo, StepExecutor, ConditionalStep, RunsheetErrorCode, Result, Success,
449
- Failure
410
+ Functions: step, pipeline, when, parallel, choice, distribute Classes:
411
+ RunsheetError, RequiresValidationError, ProvidesValidationError,
412
+ ArgsValidationError, PredicateError, TimeoutError, RetryExhaustedError,
413
+ StrictOverlapError, RollbackError, UnknownError Types: Step, TypedStep,
414
+ AggregateStep, StepConfig, StepContext, StepOutput, StepSchema, RetryPolicy,
415
+ StepResult, StepSuccess, StepFailure, StepMeta, AggregateResult,
416
+ AggregateSuccess, AggregateFailure, AggregateMeta, RollbackReport,
417
+ RollbackFailure, PipelineBuilder, PipelineConfig, StepMiddleware, StepInfo,
418
+ StepExecutor, RunsheetErrorCode, ExtractRequires, ExtractProvides
450
419
 
451
420
  ## Important patterns
452
421
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runsheet",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Type-safe, composable business logic pipelines for TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -35,7 +35,7 @@
35
35
  "lint:fix": "./scripts/lint.sh --fix",
36
36
  "format": "prettier --check .",
37
37
  "format:fix": "prettier --write .",
38
- "typecheck": "tsc --noEmit",
38
+ "typecheck": "tsc --noEmit -p tsconfig.check.json",
39
39
  "prepare": "husky",
40
40
  "prepublishOnly": "pnpm run build"
41
41
  },
@@ -81,9 +81,6 @@
81
81
  "engines": {
82
82
  "node": ">=20"
83
83
  },
84
- "dependencies": {
85
- "composable-functions": "^4.0.0"
86
- },
87
84
  "peerDependencies": {
88
85
  "zod": "^3.22.0"
89
86
  },