runsheet 0.0.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -14
- package/dist/index.cjs +599 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +330 -7
- package/dist/index.d.ts +330 -7
- package/dist/index.js +584 -29
- package/dist/index.js.map +1 -1
- package/llms.txt +231 -8
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -33,6 +33,10 @@ and runsheet makes sure they execute in order with clear contracts between them.
|
|
|
33
33
|
|
|
34
34
|
## What this is
|
|
35
35
|
|
|
36
|
+
Args persist and outputs accumulate. That's the core model — initial arguments
|
|
37
|
+
flow through the entire pipeline, each step's output merges into the context,
|
|
38
|
+
and every step sees the full picture of everything before it.
|
|
39
|
+
|
|
36
40
|
A pipeline orchestration library with:
|
|
37
41
|
|
|
38
42
|
- **Strongly typed steps** — each step's `run`, `rollback`, `requires`, and
|
|
@@ -247,6 +251,225 @@ const placeOrder = createPipeline<{ orderId: string }>('placeOrder')
|
|
|
247
251
|
.build();
|
|
248
252
|
```
|
|
249
253
|
|
|
254
|
+
## Retry and timeout
|
|
255
|
+
|
|
256
|
+
Steps can declare retry policies and timeouts directly:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const callExternalApi = defineStep({
|
|
260
|
+
name: 'callExternalApi',
|
|
261
|
+
provides: z.object({ response: z.string() }),
|
|
262
|
+
retry: { count: 3, delay: 200, backoff: 'exponential' },
|
|
263
|
+
timeout: 5000,
|
|
264
|
+
run: async () => {
|
|
265
|
+
const res = await fetch('https://api.example.com/data');
|
|
266
|
+
return { response: await res.text() };
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Retry** re-executes the step's `run` function on failure. The `retryIf`
|
|
272
|
+
predicate lets you inspect errors and decide whether to retry:
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
retry: {
|
|
276
|
+
count: 3,
|
|
277
|
+
retryIf: (errors) => errors.some((e) => e.message.includes('ECONNRESET')),
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
**Timeout** races `run` against a timer. If the step exceeds the limit, it fails
|
|
282
|
+
with a `RunsheetError` code `'TIMEOUT'`. When both are set, each retry attempt
|
|
283
|
+
gets its own timeout.
|
|
284
|
+
|
|
285
|
+
## Parallel steps
|
|
286
|
+
|
|
287
|
+
Run steps concurrently with `parallel()`. Outputs merge in array order:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { parallel } from 'runsheet';
|
|
291
|
+
|
|
292
|
+
const placeOrder = buildPipeline({
|
|
293
|
+
name: 'placeOrder',
|
|
294
|
+
steps: [
|
|
295
|
+
validateOrder,
|
|
296
|
+
parallel(reserveInventory, chargePayment),
|
|
297
|
+
sendConfirmation,
|
|
298
|
+
],
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
On partial failure, succeeded inner steps are rolled back before the error
|
|
303
|
+
propagates. Inner steps retain their own `requires`/`provides` validation,
|
|
304
|
+
`retry`, and `timeout` behavior. Conditional steps (via `when()`) work inside
|
|
305
|
+
`parallel()`.
|
|
306
|
+
|
|
307
|
+
## Dependency injection
|
|
308
|
+
|
|
309
|
+
No special mechanism needed — pass dependencies as pipeline args and they're
|
|
310
|
+
available to every step through the accumulated context:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
const chargePayment = defineStep({
|
|
314
|
+
name: 'chargePayment',
|
|
315
|
+
requires: z.object({
|
|
316
|
+
order: z.object({ total: z.number() }),
|
|
317
|
+
stripe: z.custom<Stripe>(),
|
|
318
|
+
}),
|
|
319
|
+
provides: z.object({ chargeId: z.string() }),
|
|
320
|
+
run: async (ctx) => {
|
|
321
|
+
const charge = await ctx.stripe.charges.create({ amount: ctx.order.total });
|
|
322
|
+
return { chargeId: charge.id };
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const pipeline = createPipeline<{
|
|
327
|
+
orderId: string;
|
|
328
|
+
stripe: Stripe;
|
|
329
|
+
db: Database;
|
|
330
|
+
}>('placeOrder')
|
|
331
|
+
.step(validateOrder)
|
|
332
|
+
.step(chargePayment)
|
|
333
|
+
.build();
|
|
334
|
+
|
|
335
|
+
await pipeline.run({
|
|
336
|
+
orderId: '123',
|
|
337
|
+
stripe: stripeClient,
|
|
338
|
+
db: dbClient,
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Args persist through the entire pipeline without any step needing to `provides`
|
|
343
|
+
them. TypeScript enforces at compile time that every step's `requires` are
|
|
344
|
+
satisfied by the accumulated context. For testing, swap in mocks at the call
|
|
345
|
+
site.
|
|
346
|
+
|
|
347
|
+
## Choice (branching)
|
|
348
|
+
|
|
349
|
+
Execute the first branch whose predicate returns `true` — like an AWS Step
|
|
350
|
+
Functions Choice state:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { choice } from 'runsheet';
|
|
354
|
+
|
|
355
|
+
const placeOrder = buildPipeline({
|
|
356
|
+
name: 'placeOrder',
|
|
357
|
+
steps: [
|
|
358
|
+
validateOrder,
|
|
359
|
+
choice(
|
|
360
|
+
[(ctx) => ctx.method === 'card', chargeCard],
|
|
361
|
+
[(ctx) => ctx.method === 'bank', chargeBankTransfer],
|
|
362
|
+
chargeDefault, // default (bare step)
|
|
363
|
+
),
|
|
364
|
+
sendConfirmation,
|
|
365
|
+
],
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Predicates are evaluated in order — first match wins. A bare step (without a
|
|
370
|
+
tuple) can be passed as the last argument to serve as a default — equivalent to
|
|
371
|
+
`[() => true, step]`. If no predicate matches, the step fails with a
|
|
372
|
+
`CHOICE_NO_MATCH` error. Only the matched branch participates in rollback.
|
|
373
|
+
|
|
374
|
+
## Map (collection iteration)
|
|
375
|
+
|
|
376
|
+
Iterate over a collection and run a function or step per item, concurrently —
|
|
377
|
+
like an AWS Step Functions Map state:
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { map } from 'runsheet';
|
|
381
|
+
|
|
382
|
+
// Function form — items can be any type
|
|
383
|
+
const pipeline = buildPipeline({
|
|
384
|
+
name: 'notify',
|
|
385
|
+
steps: [
|
|
386
|
+
map(
|
|
387
|
+
'emails',
|
|
388
|
+
(ctx) => ctx.users,
|
|
389
|
+
async (user) => {
|
|
390
|
+
await sendEmail(user.email);
|
|
391
|
+
return { email: user.email, sentAt: new Date() };
|
|
392
|
+
},
|
|
393
|
+
),
|
|
394
|
+
],
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Step form — reuse existing steps
|
|
398
|
+
const pipeline = buildPipeline({
|
|
399
|
+
name: 'process',
|
|
400
|
+
steps: [map('results', (ctx) => ctx.items, processItem)],
|
|
401
|
+
});
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Items run concurrently via `Promise.allSettled`. Results are collected into an
|
|
405
|
+
array under the given key. In step form, each item is spread into the pipeline
|
|
406
|
+
context (`{ ...ctx, ...item }`) so the step sees both pipeline-level and
|
|
407
|
+
per-item values. On partial failure, succeeded items are rolled back (step form
|
|
408
|
+
only).
|
|
409
|
+
|
|
410
|
+
### Filter (collection filtering)
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { filter, map } from 'runsheet';
|
|
414
|
+
|
|
415
|
+
const pipeline = buildPipeline({
|
|
416
|
+
name: 'notify',
|
|
417
|
+
steps: [
|
|
418
|
+
filter(
|
|
419
|
+
'eligible',
|
|
420
|
+
(ctx) => ctx.users,
|
|
421
|
+
(user) => user.optedIn,
|
|
422
|
+
),
|
|
423
|
+
map('emails', (ctx) => ctx.eligible, sendEmail),
|
|
424
|
+
],
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Async predicate
|
|
428
|
+
filter(
|
|
429
|
+
'valid',
|
|
430
|
+
(ctx) => ctx.orders,
|
|
431
|
+
async (order) => {
|
|
432
|
+
const inventory = await checkInventory(order.sku);
|
|
433
|
+
return inventory.available >= order.quantity;
|
|
434
|
+
},
|
|
435
|
+
);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Predicates run concurrently via `Promise.allSettled`. Original order is
|
|
439
|
+
preserved. If any predicate throws, the step fails. No rollback (filtering is a
|
|
440
|
+
pure operation).
|
|
441
|
+
|
|
442
|
+
### FlatMap (collection expansion)
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { flatMap } from 'runsheet';
|
|
446
|
+
|
|
447
|
+
const pipeline = buildPipeline({
|
|
448
|
+
name: 'process',
|
|
449
|
+
steps: [
|
|
450
|
+
flatMap(
|
|
451
|
+
'lineItems',
|
|
452
|
+
(ctx) => ctx.orders,
|
|
453
|
+
(order) => order.items,
|
|
454
|
+
),
|
|
455
|
+
],
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
// Async callback
|
|
459
|
+
flatMap(
|
|
460
|
+
'emails',
|
|
461
|
+
(ctx) => ctx.teams,
|
|
462
|
+
async (team) => {
|
|
463
|
+
const members = await fetchMembers(team.id);
|
|
464
|
+
return members.map((m) => m.email);
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Maps each item to an array, then flattens one level. Callbacks run concurrently
|
|
470
|
+
via `Promise.allSettled`. If any callback throws, the step fails. No rollback
|
|
471
|
+
(pure operation).
|
|
472
|
+
|
|
250
473
|
## Rollback
|
|
251
474
|
|
|
252
475
|
When a step fails, rollback handlers for all previously completed steps execute
|
|
@@ -314,13 +537,15 @@ Define a pipeline step. Returns a strongly typed `TypedStep` — `run`,
|
|
|
314
537
|
`rollback`, `requires`, and `provides` all carry concrete types matching the
|
|
315
538
|
schemas or generics you provide.
|
|
316
539
|
|
|
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
|
|
540
|
+
| Option | Type | Description |
|
|
541
|
+
| ---------- | ----------------------- | -------------------------------------------------- |
|
|
542
|
+
| `name` | `string` | Step name (used in metadata and rollback reports) |
|
|
543
|
+
| `requires` | `ZodSchema` | Optional schema for required context keys |
|
|
544
|
+
| `provides` | `ZodSchema` | Optional schema for provided context keys |
|
|
545
|
+
| `run` | `(ctx) => output` | Step implementation (sync or async) |
|
|
546
|
+
| `rollback` | `(ctx, output) => void` | Optional rollback handler |
|
|
547
|
+
| `retry` | `RetryPolicy` | Optional retry policy for transient failures |
|
|
548
|
+
| `timeout` | `number` | Optional max duration in ms for the `run` function |
|
|
324
549
|
|
|
325
550
|
### `buildPipeline(config)`
|
|
326
551
|
|
|
@@ -328,14 +553,15 @@ Build a pipeline from an array of steps. The result type is inferred from the
|
|
|
328
553
|
steps — `pipeline.run()` returns a `PipelineResult` whose `data` is the
|
|
329
554
|
intersection of all step output types.
|
|
330
555
|
|
|
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
|
|
556
|
+
| Option | Type | Description |
|
|
557
|
+
| ------------ | ------------------ | ----------------------------------------------------------------- |
|
|
558
|
+
| `name` | `string` | Pipeline name |
|
|
559
|
+
| `steps` | `Step[]` | Steps to execute in order |
|
|
560
|
+
| `middleware` | `StepMiddleware[]` | Optional middleware |
|
|
561
|
+
| `argsSchema` | `ZodSchema` | Optional schema for pipeline input validation |
|
|
562
|
+
| `strict` | `boolean` | Optional — throws at build time if two steps provide the same key |
|
|
337
563
|
|
|
338
|
-
### `createPipeline(name, argsSchema?)`
|
|
564
|
+
### `createPipeline(name, argsSchema?, options?)`
|
|
339
565
|
|
|
340
566
|
Start a fluent pipeline builder. Returns a `PipelineBuilder` with:
|
|
341
567
|
|
|
@@ -343,6 +569,46 @@ Start a fluent pipeline builder. Returns a `PipelineBuilder` with:
|
|
|
343
569
|
- `.use(...middleware)` — add middleware
|
|
344
570
|
- `.build()` — produce the pipeline
|
|
345
571
|
|
|
572
|
+
The second argument accepts a schema (for runtime args validation) or an options
|
|
573
|
+
object:
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
createPipeline('order', z.object({ id: z.string() }));
|
|
577
|
+
createPipeline('order', { strict: true });
|
|
578
|
+
createPipeline('order', z.object({ id: z.string() }), { strict: true });
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### `parallel(...steps)`
|
|
582
|
+
|
|
583
|
+
Run steps concurrently and merge their outputs. Returns a single step usable
|
|
584
|
+
anywhere a regular step is accepted. On partial failure, succeeded inner steps
|
|
585
|
+
are rolled back before the error propagates. p
|
|
586
|
+
|
|
587
|
+
### `choice(...branches)`
|
|
588
|
+
|
|
589
|
+
Execute the first branch whose predicate returns `true`. Each branch is a
|
|
590
|
+
`[predicate, step]` tuple. A bare step can be passed as the last argument as a
|
|
591
|
+
default. Returns a single step usable anywhere a regular step is accepted. Only
|
|
592
|
+
the matched branch participates in rollback.
|
|
593
|
+
|
|
594
|
+
### `map(key, collection, fnOrStep)`
|
|
595
|
+
|
|
596
|
+
Iterate over a collection and run a function or step per item, concurrently.
|
|
597
|
+
Results are collected into `{ [key]: Result[] }`. Accepts a plain function
|
|
598
|
+
`(item, ctx) => result` or a `TypedStep` (items must be objects, spread into
|
|
599
|
+
context). Step form supports per-item rollback on partial and external failure.
|
|
600
|
+
|
|
601
|
+
### `filter(key, collection, predicate)`
|
|
602
|
+
|
|
603
|
+
Filter a collection from context using a sync or async predicate. Predicates run
|
|
604
|
+
concurrently. Items where the predicate returns `true` are kept; original order
|
|
605
|
+
is preserved. Results are collected into `{ [key]: Item[] }`. No rollback.
|
|
606
|
+
|
|
607
|
+
### `flatMap(key, collection, fn)`
|
|
608
|
+
|
|
609
|
+
Map each item in a collection to an array, then flatten one level. Callbacks run
|
|
610
|
+
concurrently. Results are collected into `{ [key]: Result[] }`. No rollback.
|
|
611
|
+
|
|
346
612
|
### `when(predicate, step)`
|
|
347
613
|
|
|
348
614
|
Wrap a step with a conditional predicate. The step only executes when the
|