runsheet 0.4.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/README.md +238 -47
- package/dist/index.cjs +639 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +497 -243
- package/dist/index.d.ts +497 -243
- package/dist/index.js +624 -247
- package/dist/index.js.map +1 -1
- package/llms.txt +191 -36
- package/package.json +4 -7
package/llms.txt
CHANGED
|
@@ -7,17 +7,22 @@ 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
|
-
|
|
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
|
|
15
15
|
|
|
16
|
+
- Args persist and outputs accumulate. Initial arguments flow through the entire
|
|
17
|
+
pipeline, each step's output merges into the context, and every step sees the
|
|
18
|
+
full picture of everything before it.
|
|
16
19
|
- Steps declare `requires` (input from context) and `provides` (output added to
|
|
17
|
-
context).
|
|
20
|
+
context).
|
|
18
21
|
- Pipelines run steps sequentially. On failure, rollback handlers execute in
|
|
19
22
|
reverse order with snapshot-based context.
|
|
20
|
-
- Results use `
|
|
23
|
+
- Results use `StepResult<T>` — a discriminated union: `StepSuccess<T>`
|
|
24
|
+
(`success`, `data`, `meta`) or `StepFailure` (`success`, `error`, `meta`,
|
|
25
|
+
`failedStep`, `rollback`). Never throws.
|
|
21
26
|
|
|
22
27
|
## Quick reference
|
|
23
28
|
|
|
@@ -114,25 +119,25 @@ RetryPolicy type:
|
|
|
114
119
|
- backoff?: 'linear' | 'exponential' (default 'linear')
|
|
115
120
|
- retryIf?: (errors: Error[]) => boolean — return false to stop retrying
|
|
116
121
|
|
|
117
|
-
###
|
|
122
|
+
### pipeline
|
|
118
123
|
|
|
119
124
|
Build a pipeline from an array of steps. The result type is inferred from the
|
|
120
125
|
steps.
|
|
121
126
|
|
|
122
127
|
```typescript
|
|
123
|
-
import {
|
|
128
|
+
import { pipeline } from 'runsheet';
|
|
124
129
|
|
|
125
|
-
const
|
|
130
|
+
const p = pipeline({
|
|
126
131
|
name: 'checkout',
|
|
127
132
|
steps: [fetchUser, chargePayment, sendEmail],
|
|
128
133
|
});
|
|
129
134
|
|
|
130
|
-
const result = await
|
|
135
|
+
const result = await p.run({ userId: '123', amount: 50 });
|
|
131
136
|
|
|
132
137
|
if (result.success) {
|
|
133
138
|
result.data.chargeId; // string — fully typed
|
|
134
139
|
} else {
|
|
135
|
-
result.
|
|
140
|
+
result.error; // what went wrong (single Error)
|
|
136
141
|
result.rollback; // { completed: string[], failed: RollbackFailure[] }
|
|
137
142
|
}
|
|
138
143
|
```
|
|
@@ -140,7 +145,7 @@ if (result.success) {
|
|
|
140
145
|
Optional argsSchema validates pipeline input:
|
|
141
146
|
|
|
142
147
|
```typescript
|
|
143
|
-
const
|
|
148
|
+
const p = pipeline({
|
|
144
149
|
name: 'checkout',
|
|
145
150
|
steps: [fetchUser, chargePayment],
|
|
146
151
|
argsSchema: z.object({ userId: z.string(), amount: z.number() }),
|
|
@@ -151,21 +156,37 @@ Use `strict: true` to detect provides key collisions at build time. Throws a
|
|
|
151
156
|
RunsheetError with code 'STRICT_OVERLAP' if two steps provide the same key:
|
|
152
157
|
|
|
153
158
|
```typescript
|
|
154
|
-
const
|
|
159
|
+
const p = pipeline({
|
|
155
160
|
name: 'checkout',
|
|
156
161
|
steps: [fetchUser, chargePayment],
|
|
157
162
|
strict: true,
|
|
158
163
|
});
|
|
159
164
|
```
|
|
160
165
|
|
|
161
|
-
###
|
|
166
|
+
### Pipeline composition
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
Pipelines are steps — pipeline returns an AggregateStep. A pipeline can be used
|
|
169
|
+
directly as a step in another pipeline's steps array:
|
|
164
170
|
|
|
165
171
|
```typescript
|
|
166
|
-
|
|
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
|
|
179
|
+
|
|
180
|
+
Omit `steps` from the config to get a fluent builder with progressive type
|
|
181
|
+
narrowing.
|
|
167
182
|
|
|
168
|
-
|
|
183
|
+
```typescript
|
|
184
|
+
import { pipeline } from 'runsheet';
|
|
185
|
+
|
|
186
|
+
const checkout = pipeline({
|
|
187
|
+
name: 'checkout',
|
|
188
|
+
argsSchema: z.object({ userId: z.string() }),
|
|
189
|
+
})
|
|
169
190
|
.step(fetchUser)
|
|
170
191
|
.step(chargePayment)
|
|
171
192
|
.step(sendEmail)
|
|
@@ -175,7 +196,7 @@ const pipeline = createPipeline('checkout', z.object({ userId: z.string() }))
|
|
|
175
196
|
Type-only args (no runtime validation of pipeline input):
|
|
176
197
|
|
|
177
198
|
```typescript
|
|
178
|
-
const
|
|
199
|
+
const checkout = pipeline<{ userId: string }>({ name: 'checkout' })
|
|
179
200
|
.step(fetchUser)
|
|
180
201
|
.build();
|
|
181
202
|
```
|
|
@@ -183,13 +204,17 @@ const pipeline = createPipeline<{ userId: string }>('checkout')
|
|
|
183
204
|
Strict mode via the builder:
|
|
184
205
|
|
|
185
206
|
```typescript
|
|
186
|
-
|
|
207
|
+
pipeline({ name: 'checkout', strict: true })
|
|
187
208
|
.step(fetchUser)
|
|
188
209
|
.step(chargePayment)
|
|
189
210
|
.build();
|
|
190
211
|
|
|
191
212
|
// Or with a schema:
|
|
192
|
-
|
|
213
|
+
pipeline({
|
|
214
|
+
name: 'checkout',
|
|
215
|
+
argsSchema: z.object({ userId: z.string() }),
|
|
216
|
+
strict: true,
|
|
217
|
+
})
|
|
193
218
|
.step(fetchUser)
|
|
194
219
|
.step(chargePayment)
|
|
195
220
|
.build();
|
|
@@ -200,7 +225,7 @@ createPipeline('checkout', z.object({ userId: z.string() }), { strict: true })
|
|
|
200
225
|
```typescript
|
|
201
226
|
import { parallel } from 'runsheet';
|
|
202
227
|
|
|
203
|
-
const
|
|
228
|
+
const p = pipeline({
|
|
204
229
|
name: 'checkout',
|
|
205
230
|
steps: [
|
|
206
231
|
validateOrder,
|
|
@@ -216,12 +241,137 @@ inner steps are rolled back before the error propagates. Inner steps retain
|
|
|
216
241
|
their own requires/provides validation, retry, and timeout. Conditional steps
|
|
217
242
|
(when()) work inside parallel().
|
|
218
243
|
|
|
244
|
+
### choice (branching)
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { choice } from 'runsheet';
|
|
248
|
+
|
|
249
|
+
const p = pipeline({
|
|
250
|
+
name: 'checkout',
|
|
251
|
+
steps: [
|
|
252
|
+
validateOrder,
|
|
253
|
+
choice(
|
|
254
|
+
[(ctx) => ctx.method === 'card', chargeCard],
|
|
255
|
+
[(ctx) => ctx.method === 'bank', chargeBankTransfer],
|
|
256
|
+
chargeDefault, // default (bare step)
|
|
257
|
+
),
|
|
258
|
+
sendConfirmation,
|
|
259
|
+
],
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Predicates are evaluated in order — first match wins. A bare step (without a
|
|
264
|
+
tuple) can be passed as the last argument to serve as a default — equivalent to
|
|
265
|
+
`[() => true, step]`. If no predicate matches, the step fails with RunsheetError
|
|
266
|
+
code 'CHOICE_NO_MATCH'. Only the matched branch participates in rollback.
|
|
267
|
+
|
|
268
|
+
### map (collection iteration)
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { map } from 'runsheet';
|
|
272
|
+
|
|
273
|
+
// Function form — items can be any type
|
|
274
|
+
const p = pipeline({
|
|
275
|
+
name: 'notify',
|
|
276
|
+
steps: [
|
|
277
|
+
map(
|
|
278
|
+
'emails',
|
|
279
|
+
(ctx) => ctx.users,
|
|
280
|
+
async (user) => {
|
|
281
|
+
await sendEmail(user.email);
|
|
282
|
+
return { email: user.email, sentAt: new Date() };
|
|
283
|
+
},
|
|
284
|
+
),
|
|
285
|
+
],
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Step form — reuse existing steps, item spread into context
|
|
289
|
+
const p = pipeline({
|
|
290
|
+
name: 'process',
|
|
291
|
+
steps: [map('results', (ctx) => ctx.items, processItem)],
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Items run concurrently via Promise.allSettled. Results collected into an array
|
|
296
|
+
under the given key. In step form, each item is spread into the pipeline context
|
|
297
|
+
({ ...ctx, ...item }) so the step sees both pipeline-level and per-item values.
|
|
298
|
+
On partial failure, succeeded items are rolled back (step form only).
|
|
299
|
+
|
|
300
|
+
### filter (collection filtering)
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
import { filter, map } from 'runsheet';
|
|
304
|
+
|
|
305
|
+
const p = pipeline({
|
|
306
|
+
name: 'notify',
|
|
307
|
+
steps: [
|
|
308
|
+
filter(
|
|
309
|
+
'eligible',
|
|
310
|
+
(ctx) => ctx.users,
|
|
311
|
+
(user) => user.optedIn,
|
|
312
|
+
),
|
|
313
|
+
map('emails', (ctx) => ctx.eligible, sendEmail),
|
|
314
|
+
],
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Predicates run concurrently via Promise.allSettled. Supports sync or async
|
|
319
|
+
predicates. Items where the predicate returns true are kept; original order is
|
|
320
|
+
preserved. If any predicate throws, the step fails. No rollback (pure
|
|
321
|
+
operation).
|
|
322
|
+
|
|
323
|
+
### flatMap (collection expansion)
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
import { flatMap } from 'runsheet';
|
|
327
|
+
|
|
328
|
+
const p = pipeline({
|
|
329
|
+
name: 'process',
|
|
330
|
+
steps: [
|
|
331
|
+
flatMap(
|
|
332
|
+
'lineItems',
|
|
333
|
+
(ctx) => ctx.orders,
|
|
334
|
+
(order) => order.items,
|
|
335
|
+
),
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Maps each item to an array, then flattens one level. Callbacks run concurrently
|
|
341
|
+
via Promise.allSettled. Supports sync or async callbacks. If any callback
|
|
342
|
+
throws, the step fails. No rollback (pure operation).
|
|
343
|
+
|
|
344
|
+
### Dependency injection
|
|
345
|
+
|
|
346
|
+
Pass dependencies as pipeline args — they're available to every step through the
|
|
347
|
+
accumulated context without any step needing to provides them:
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
const placeOrder = pipeline<{
|
|
351
|
+
orderId: string;
|
|
352
|
+
stripe: Stripe;
|
|
353
|
+
db: Database;
|
|
354
|
+
}>({ name: 'placeOrder' })
|
|
355
|
+
.step(validateOrder)
|
|
356
|
+
.step(chargePayment)
|
|
357
|
+
.build();
|
|
358
|
+
|
|
359
|
+
await placeOrder.run({
|
|
360
|
+
orderId: '123',
|
|
361
|
+
stripe: stripeClient,
|
|
362
|
+
db: dbClient,
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Steps declare infrastructure deps in requires like any other context key. For
|
|
367
|
+
testing, swap in mocks at the call site.
|
|
368
|
+
|
|
219
369
|
### when (conditional steps)
|
|
220
370
|
|
|
221
371
|
```typescript
|
|
222
372
|
import { when } from 'runsheet';
|
|
223
373
|
|
|
224
|
-
const
|
|
374
|
+
const p = pipeline({
|
|
225
375
|
name: 'checkout',
|
|
226
376
|
steps: [
|
|
227
377
|
fetchUser,
|
|
@@ -248,43 +398,41 @@ const timing: StepMiddleware = (step, next) => async (ctx) => {
|
|
|
248
398
|
return result;
|
|
249
399
|
};
|
|
250
400
|
|
|
251
|
-
const
|
|
401
|
+
const p = pipeline({
|
|
252
402
|
name: 'checkout',
|
|
253
403
|
steps: [fetchUser, chargePayment],
|
|
254
404
|
middleware: [timing],
|
|
255
405
|
});
|
|
256
406
|
|
|
257
407
|
// Or with the builder:
|
|
258
|
-
|
|
408
|
+
pipeline({ name: 'checkout' })
|
|
259
409
|
.use(timing)
|
|
260
410
|
.step(fetchUser)
|
|
261
411
|
.step(chargePayment)
|
|
262
412
|
.build();
|
|
263
413
|
```
|
|
264
414
|
|
|
265
|
-
###
|
|
415
|
+
### StepResult
|
|
266
416
|
|
|
267
|
-
Every
|
|
417
|
+
Every run() returns a StepResult (never throws):
|
|
268
418
|
|
|
269
419
|
```typescript
|
|
270
420
|
// Success
|
|
271
421
|
{
|
|
272
422
|
success: true,
|
|
273
423
|
data: { /* accumulated context */ },
|
|
274
|
-
errors: [],
|
|
275
424
|
meta: {
|
|
276
|
-
|
|
425
|
+
name: 'checkout',
|
|
277
426
|
args: { userId: '123' },
|
|
278
427
|
stepsExecuted: ['fetchUser', 'chargePayment'],
|
|
279
|
-
stepsSkipped: [],
|
|
280
428
|
},
|
|
281
429
|
}
|
|
282
430
|
|
|
283
431
|
// Failure
|
|
284
432
|
{
|
|
285
433
|
success: false,
|
|
286
|
-
|
|
287
|
-
meta: {
|
|
434
|
+
error: Error,
|
|
435
|
+
meta: { name, args, stepsExecuted },
|
|
288
436
|
failedStep: 'chargePayment',
|
|
289
437
|
rollback: { completed: ['fetchUser'], failed: [] },
|
|
290
438
|
}
|
|
@@ -298,21 +446,28 @@ RunsheetError instances with a discriminated `code` property:
|
|
|
298
446
|
- REQUIRES_VALIDATION — step's requires schema failed
|
|
299
447
|
- PROVIDES_VALIDATION — step's provides schema failed
|
|
300
448
|
- ARGS_VALIDATION — pipeline argsSchema failed
|
|
301
|
-
- PREDICATE — when() predicate threw
|
|
449
|
+
- PREDICATE — when() or choice() predicate threw
|
|
302
450
|
- TIMEOUT — step exceeded its timeout
|
|
303
451
|
- RETRY_EXHAUSTED — step failed after all retry attempts
|
|
304
452
|
- STRICT_OVERLAP — two steps provide the same key (strict mode, build time)
|
|
453
|
+
- CHOICE_NO_MATCH — no branch matched in a choice() step
|
|
454
|
+
- ROLLBACK — one or more rollback handlers failed in a combinator
|
|
455
|
+
- UNKNOWN — a non-Error value was thrown and caught by the pipeline engine
|
|
305
456
|
|
|
306
457
|
Application errors (thrown from step run functions) pass through as-is.
|
|
307
458
|
|
|
308
459
|
## Key exports
|
|
309
460
|
|
|
310
|
-
Functions: defineStep,
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
461
|
+
Functions: defineStep, pipeline, when, parallel, choice, map, flatMap, filter
|
|
462
|
+
Classes: RunsheetError, RequiresValidationError, ProvidesValidationError,
|
|
463
|
+
ArgsValidationError, PredicateError, TimeoutError, RetryExhaustedError,
|
|
464
|
+
StrictOverlapError, ChoiceNoMatchError, RollbackError, UnknownError Types: Step,
|
|
465
|
+
TypedStep, AggregateStep, StepConfig, StepContext, StepOutput, StepSchema,
|
|
466
|
+
RetryPolicy, StepResult, StepSuccess, StepFailure, StepMeta, AggregateResult,
|
|
467
|
+
AggregateSuccess, AggregateFailure, AggregateMeta, RollbackReport,
|
|
468
|
+
RollbackFailure, PipelineBuilder, PipelineConfig, StepMiddleware, StepInfo,
|
|
469
|
+
StepExecutor, ConditionalStep, RunsheetErrorCode, ExtractRequires,
|
|
470
|
+
ExtractProvides
|
|
316
471
|
|
|
317
472
|
## Important patterns
|
|
318
473
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runsheet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Type-safe, composable business logic pipelines for TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"build": "tsup",
|
|
32
32
|
"test": "vitest run",
|
|
33
33
|
"test:watch": "vitest",
|
|
34
|
-
"lint": "
|
|
35
|
-
"lint:fix": "
|
|
34
|
+
"lint": "./scripts/lint.sh",
|
|
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
|
},
|