@jagreehal/workflow 1.6.0 → 1.8.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 CHANGED
@@ -154,6 +154,102 @@ const result = await processPayment(async (step) => {
154
154
  });
155
155
  ```
156
156
 
157
+ ### 💾 Save & Resume (Persist Workflows Across Restarts)
158
+
159
+ Save workflow state to a database and resume later from exactly where you left off. Perfect for long-running workflows, crash recovery, or pausing for approvals.
160
+
161
+ **Step 1: Collect state during execution**
162
+
163
+ ```typescript
164
+ import { createWorkflow, createStepCollector } from '@jagreehal/workflow';
165
+
166
+ // Create a collector to automatically capture step results
167
+ const collector = createStepCollector();
168
+
169
+ const workflow = createWorkflow({ fetchUser, fetchPosts }, {
170
+ onEvent: collector.handleEvent, // Automatically collects step_complete events
171
+ });
172
+
173
+ await workflow(async (step) => {
174
+ // Only steps with keys are saved
175
+ const user = await step(() => fetchUser("1"), { key: "user:1" });
176
+ const posts = await step(() => fetchPosts(user.id), { key: `posts:${user.id}` });
177
+ return { user, posts };
178
+ });
179
+
180
+ // Get the collected state
181
+ const state = collector.getState(); // Returns ResumeState
182
+ ```
183
+
184
+ **Step 2: Save to database**
185
+
186
+ ```typescript
187
+ import { stringifyState, parseState } from '@jagreehal/workflow';
188
+
189
+ // Serialize to JSON
190
+ const json = stringifyState(state, { workflowId: "123", timestamp: Date.now() });
191
+
192
+ // Save to your database
193
+ await db.workflowStates.create({
194
+ id: workflowId,
195
+ state: json,
196
+ createdAt: new Date(),
197
+ });
198
+ ```
199
+
200
+ **Step 3: Resume from saved state**
201
+
202
+ ```typescript
203
+ // Load from database
204
+ const saved = await db.workflowStates.findUnique({ where: { id: workflowId } });
205
+ const savedState = parseState(saved.state);
206
+
207
+ // Resume workflow - cached steps skip execution
208
+ const workflow = createWorkflow({ fetchUser, fetchPosts }, {
209
+ resumeState: savedState, // Pre-populates cache from saved state
210
+ });
211
+
212
+ await workflow(async (step) => {
213
+ const user = await step(() => fetchUser("1"), { key: "user:1" }); // ✅ Cache hit - no fetchUser call
214
+ const posts = await step(() => fetchPosts(user.id), { key: `posts:${user.id}` }); // ✅ Cache hit
215
+ return { user, posts };
216
+ });
217
+ ```
218
+
219
+ **With database adapter (Redis, DynamoDB, etc.)**
220
+
221
+ ```typescript
222
+ import { createStatePersistence } from '@jagreehal/workflow';
223
+ import { createClient } from 'redis';
224
+
225
+ const redis = createClient();
226
+ await redis.connect();
227
+
228
+ // Create persistence adapter
229
+ const persistence = createStatePersistence({
230
+ get: (key) => redis.get(key),
231
+ set: (key, value) => redis.set(key, value),
232
+ delete: (key) => redis.del(key).then(n => n > 0),
233
+ exists: (key) => redis.exists(key).then(n => n > 0),
234
+ keys: (pattern) => redis.keys(pattern),
235
+ }, 'workflow:state:');
236
+
237
+ // Save
238
+ await persistence.save(runId, state, { metadata: { userId: 'user-1' } });
239
+
240
+ // Load
241
+ const savedState = await persistence.load(runId);
242
+
243
+ // Resume
244
+ const workflow = createWorkflow(deps, { resumeState: savedState });
245
+ ```
246
+
247
+ **Key points:**
248
+ - Only steps with `key` options are saved (unkeyed steps execute fresh on resume)
249
+ - Error results are preserved with metadata for proper replay
250
+ - You can also pass an async function: `resumeState: async () => await loadFromDB()`
251
+ - Works seamlessly with HITL approvals and crash recovery
252
+
157
253
  ### 🧑‍💻 Human-in-the-Loop
158
254
 
159
255
  Pause for manual approvals (large transfers, deployments, refunds) and resume exactly where you left off.
@@ -281,6 +377,82 @@ That's the foundation. Now let's build on it.
281
377
 
282
378
  ---
283
379
 
380
+ ## Persistence Quickstart
381
+
382
+ Save workflow state to a database and resume later. Perfect for crash recovery, long-running workflows, or pausing for approvals.
383
+
384
+ ### Basic Save & Resume
385
+
386
+ ```typescript
387
+ import { createWorkflow, createStepCollector, stringifyState, parseState } from '@jagreehal/workflow';
388
+
389
+ // 1. Collect state during execution
390
+ const collector = createStepCollector();
391
+ const workflow = createWorkflow({ fetchUser, fetchPosts }, {
392
+ onEvent: collector.handleEvent,
393
+ });
394
+
395
+ await workflow(async (step) => {
396
+ const user = await step(() => fetchUser("1"), { key: "user:1" });
397
+ const posts = await step(() => fetchPosts(user.id), { key: `posts:${user.id}` });
398
+ return { user, posts };
399
+ });
400
+
401
+ // 2. Save to database
402
+ const state = collector.getState();
403
+ const json = stringifyState(state, { workflowId: "123" });
404
+ await db.workflowStates.create({ id: "123", state: json });
405
+
406
+ // 3. Resume later
407
+ const saved = await db.workflowStates.findUnique({ where: { id: "123" } });
408
+ const savedState = parseState(saved.state);
409
+
410
+ const resumed = createWorkflow({ fetchUser, fetchPosts }, {
411
+ resumeState: savedState,
412
+ });
413
+
414
+ // Cached steps skip execution automatically
415
+ await resumed(async (step) => {
416
+ const user = await step(() => fetchUser("1"), { key: "user:1" }); // ✅ Cache hit
417
+ const posts = await step(() => fetchPosts(user.id), { key: `posts:${user.id}` }); // ✅ Cache hit
418
+ return { user, posts };
419
+ });
420
+ ```
421
+
422
+ ### With Database Adapter (Redis, DynamoDB, etc.)
423
+
424
+ ```typescript
425
+ import { createStatePersistence } from '@jagreehal/workflow';
426
+ import { createClient } from 'redis';
427
+
428
+ const redis = createClient();
429
+ await redis.connect();
430
+
431
+ // Create persistence adapter
432
+ const persistence = createStatePersistence({
433
+ get: (key) => redis.get(key),
434
+ set: (key, value) => redis.set(key, value),
435
+ delete: (key) => redis.del(key).then(n => n > 0),
436
+ exists: (key) => redis.exists(key).then(n => n > 0),
437
+ keys: (pattern) => redis.keys(pattern),
438
+ }, 'workflow:state:');
439
+
440
+ // Save
441
+ const collector = createStepCollector();
442
+ const workflow = createWorkflow(deps, { onEvent: collector.handleEvent });
443
+ await workflow(async (step) => { /* ... */ });
444
+
445
+ await persistence.save('run-123', collector.getState(), { userId: 'user-1' });
446
+
447
+ // Load and resume
448
+ const savedState = await persistence.load('run-123');
449
+ const resumed = createWorkflow(deps, { resumeState: savedState });
450
+ ```
451
+
452
+ **See the [Save & Resume](#-save--resume-persist-workflows-across-restarts) section for more details.**
453
+
454
+ ---
455
+
284
456
  ## Guided Tutorial
285
457
 
286
458
  We'll take a single workflow through four stages - from basic to production-ready. Each stage builds on the last, so you'll see how features compose naturally.
@@ -356,29 +528,37 @@ Pause long-running workflows until an operator approves, then resume using persi
356
528
  import {
357
529
  createApprovalStep,
358
530
  createWorkflow,
531
+ createStepCollector,
359
532
  injectApproval,
360
533
  isPendingApproval,
361
- isStepComplete,
362
- type ResumeStateEntry,
363
534
  } from '@jagreehal/workflow';
364
535
 
365
- const savedSteps = new Map<string, ResumeStateEntry>();
536
+ // Use collector to automatically capture state
537
+ const collector = createStepCollector();
366
538
  const requireApproval = createApprovalStep({
367
539
  key: 'approval:deploy',
368
540
  checkApproval: async () => ({ status: 'pending' }),
369
541
  });
370
542
 
371
543
  const gatedWorkflow = createWorkflow({ requireApproval }, {
372
- onEvent: (event) => {
373
- if (isStepComplete(event)) savedSteps.set(event.stepKey, { result: event.result, meta: event.meta });
374
- },
544
+ onEvent: collector.handleEvent, // Automatically collects step results
375
545
  });
376
546
 
377
547
  const result = await gatedWorkflow(async (step) => step(requireApproval, { key: 'approval:deploy' }));
378
548
 
379
549
  if (!result.ok && isPendingApproval(result.error)) {
380
- // later
381
- injectApproval({ steps: savedSteps }, { stepKey: 'approval:deploy', value: { approvedBy: 'ops' } });
550
+ // Get collected state
551
+ const state = collector.getState();
552
+
553
+ // Later, when approval is granted, inject it and resume
554
+ const updatedState = injectApproval(state, {
555
+ stepKey: 'approval:deploy',
556
+ value: { approvedBy: 'ops' },
557
+ });
558
+
559
+ // Resume with approval injected
560
+ const resumed = createWorkflow({ requireApproval }, { resumeState: updatedState });
561
+ await resumed(async (step) => step(requireApproval, { key: 'approval:deploy' })); // Uses injected approval
382
562
  }
383
563
  ```
384
564
 
@@ -400,6 +580,250 @@ if (!result.ok && isPendingApproval(result.error)) {
400
580
  | **Events** | `onEvent` streams everything - timing, retries, failures - for visualization or logging |
401
581
  | **Resume** | Save completed steps, pick up later (great for approvals or crashes) |
402
582
  | **UnexpectedError** | Safety net for throws outside your declared union; use `strict` mode to force explicit handling |
583
+ | **TaggedError** | Factory for rich error types with exhaustive pattern matching |
584
+
585
+ ## Tagged Errors: When String Literals Aren't Enough
586
+
587
+ String literal errors like `'NOT_FOUND'` are perfect for simple cases. But sometimes you need **rich error objects** with contextual data. That's where `TaggedError` shines.
588
+
589
+ ### When to Use What
590
+
591
+ | Use Case | Recommendation |
592
+ |----------|----------------|
593
+ | Simple, distinct error states | String literals: `'NOT_FOUND' \| 'UNAUTHORIZED'` |
594
+ | Errors with contextual data | TaggedError: `NotFoundError { id, resource }` |
595
+ | Multiple error variants to handle | TaggedError with `match()` for exhaustive handling |
596
+ | API responses or user messages | TaggedError for structured error details |
597
+
598
+ ### String Literals (Simple Cases)
599
+
600
+ For most workflows, string literals are cleaner and simpler:
601
+
602
+ ```typescript
603
+ const fetchUser = async (id: string): AsyncResult<User, 'NOT_FOUND' | 'DB_ERROR'> =>
604
+ id ? ok({ id, name: 'Alice' }) : err('NOT_FOUND');
605
+
606
+ const result = await workflow(async (step) => {
607
+ const user = await step(fetchUser('123'));
608
+ return user;
609
+ });
610
+
611
+ if (!result.ok) {
612
+ switch (result.error) {
613
+ case 'NOT_FOUND': return res.status(404).send('User not found');
614
+ case 'DB_ERROR': return res.status(500).send('Database error');
615
+ }
616
+ }
617
+ ```
618
+
619
+ ### TaggedError (Rich Error Objects)
620
+
621
+ Use `TaggedError` when you need to carry additional context:
622
+
623
+ ```typescript
624
+ import { TaggedError, ok, err, type AsyncResult } from '@jagreehal/workflow';
625
+
626
+ // Pattern 1: Props via generic (default message = tag name)
627
+ class NotFoundError extends TaggedError('NotFoundError')<{
628
+ resource: string;
629
+ id: string;
630
+ }> {}
631
+
632
+ // Pattern 2: Type-safe message (Props inferred from callback annotation)
633
+ class ValidationError extends TaggedError('ValidationError', {
634
+ message: (p: { field: string; reason: string }) => `Validation failed for ${p.field}: ${p.reason}`,
635
+ }) {}
636
+
637
+ class RateLimitError extends TaggedError('RateLimitError', {
638
+ message: (p: { retryAfter: number }) => `Rate limited. Retry after ${p.retryAfter}s`,
639
+ }) {}
640
+
641
+ // Runtime type checks work!
642
+ const error = new NotFoundError({ resource: 'User', id: '123' });
643
+ console.log(error instanceof TaggedError); // true
644
+
645
+ // Use in your functions
646
+ type UserError = NotFoundError | ValidationError | RateLimitError;
647
+
648
+ const fetchUser = async (id: string): AsyncResult<User, UserError> => {
649
+ if (!id) return err(new ValidationError({ field: 'id', reason: 'required' }));
650
+ if (id === 'limited') return err(new RateLimitError({ retryAfter: 60 }));
651
+ if (id === 'missing') return err(new NotFoundError({ resource: 'User', id }));
652
+ return ok({ id, name: 'Alice' });
653
+ };
654
+ ```
655
+
656
+ ### Exhaustive Matching with `TaggedError.match()`
657
+
658
+ TypeScript enforces you handle **every** error variant:
659
+
660
+ ```typescript
661
+ const result = await workflow(async (step) => {
662
+ const user = await step(fetchUser('123'));
663
+ return user;
664
+ });
665
+
666
+ if (!result.ok) {
667
+ // TypeScript enforces all variants are handled
668
+ const response = TaggedError.match(result.error, {
669
+ NotFoundError: (e) => ({
670
+ status: 404,
671
+ body: { error: 'not_found', resource: e.resource, id: e.id },
672
+ }),
673
+ ValidationError: (e) => ({
674
+ status: 400,
675
+ body: { error: 'validation', field: e.field, reason: e.reason },
676
+ }),
677
+ RateLimitError: (e) => ({
678
+ status: 429,
679
+ body: { error: 'rate_limited', retryAfter: e.retryAfter },
680
+ }),
681
+ });
682
+
683
+ return res.status(response.status).json(response.body);
684
+ }
685
+ ```
686
+
687
+ **Add a new error type? TypeScript will error until you handle it.** This catches bugs at compile time, not production.
688
+
689
+ ### Partial Matching with Fallback
690
+
691
+ Handle specific errors and catch-all for the rest:
692
+
693
+ ```typescript
694
+ const message = TaggedError.matchPartial(
695
+ result.error,
696
+ {
697
+ RateLimitError: (e) => `Please wait ${e.retryAfter} seconds`,
698
+ },
699
+ (e) => `Something went wrong: ${e.message}` // Fallback for other errors
700
+ );
701
+ ```
702
+
703
+ ### Real-World Example: Payment Errors
704
+
705
+ ```typescript
706
+ // Type-safe message with Props inferred from callback annotation
707
+ class PaymentDeclinedError extends TaggedError('PaymentDeclinedError', {
708
+ message: (p: {
709
+ code: string;
710
+ declineReason: 'insufficient_funds' | 'card_expired' | 'fraud_suspected';
711
+ }) => `Payment declined: ${p.declineReason}`,
712
+ }) {}
713
+
714
+ class PaymentProviderError extends TaggedError('PaymentProviderError', {
715
+ message: (p: { provider: string; statusCode: number }) => `${p.provider} returned ${p.statusCode}`,
716
+ }) {}
717
+
718
+ type PaymentError = PaymentDeclinedError | PaymentProviderError;
719
+
720
+ // In your workflow
721
+ if (!result.ok) {
722
+ TaggedError.match(result.error, {
723
+ PaymentDeclinedError: (e) => {
724
+ switch (e.declineReason) {
725
+ case 'insufficient_funds': notifyUser('Please use a different card');
726
+ case 'card_expired': notifyUser('Your card has expired');
727
+ case 'fraud_suspected': alertFraudTeam(e);
728
+ }
729
+ },
730
+ PaymentProviderError: (e) => {
731
+ logToDatadog({ provider: e.provider, status: e.statusCode });
732
+ retryWithBackup(e.provider);
733
+ },
734
+ });
735
+ }
736
+ ```
737
+
738
+ ### Type Helpers
739
+
740
+ ```typescript
741
+ import { type TagOf, type ErrorByTag } from '@jagreehal/workflow';
742
+
743
+ type PaymentError = PaymentDeclinedError | PaymentProviderError;
744
+
745
+ // Extract the tag literal type
746
+ type Tags = TagOf<PaymentError>; // 'PaymentDeclinedError' | 'PaymentProviderError'
747
+
748
+ // Extract a specific variant from the union
749
+ type Declined = ErrorByTag<PaymentError, 'PaymentDeclinedError'>; // PaymentDeclinedError
750
+ ```
751
+
752
+ ### Reserved Keys & Cause Handling
753
+
754
+ **Reserved property names** are stripped from props to preserve Error semantics:
755
+
756
+ | Key | Reason |
757
+ |-----|--------|
758
+ | `_tag` | Discriminant for pattern matching (cannot be forged) |
759
+ | `name` | Error.name (shown in stack traces/logs) |
760
+ | `message` | Error.message (shown in logs/telemetry) |
761
+ | `stack` | Error.stack (the stack trace) |
762
+
763
+ ```typescript
764
+ // These reserved keys are silently stripped - use different names!
765
+ class BadExample extends TaggedError('BadExample')<{
766
+ message: string; // ❌ Won't work - use 'details' or 'description' instead
767
+ id: string;
768
+ }> {}
769
+
770
+ // Better approach:
771
+ class GoodExample extends TaggedError('GoodExample')<{
772
+ details: string; // ✅ Custom prop name
773
+ id: string;
774
+ }> {}
775
+ ```
776
+
777
+ **The `cause` property** is special - it can be used as a user prop for domain data:
778
+
779
+ ```typescript
780
+ // cause as domain data (e.g., structured validation errors)
781
+ class ValidationError extends TaggedError('ValidationError')<{
782
+ cause: { field: string; reason: string }; // ✅ Allowed as user prop
783
+ }> {}
784
+
785
+ const error = new ValidationError({
786
+ cause: { field: 'email', reason: 'invalid format' },
787
+ });
788
+ console.log(error.cause); // { field: 'email', reason: 'invalid format' }
789
+ ```
790
+
791
+ **Conflict detection**: You cannot provide `cause` in both props AND ErrorOptions:
792
+
793
+ ```typescript
794
+ // This throws TypeError at runtime!
795
+ const error = new ValidationError(
796
+ { cause: { field: 'email', reason: 'invalid' } }, // cause in props
797
+ { cause: new Error('original') } // cause in options
798
+ );
799
+ // TypeError: cannot provide 'cause' in props when also setting ErrorOptions.cause
800
+ ```
801
+
802
+ Use ErrorOptions.cause for error chaining (linking to the original error):
803
+
804
+ ```typescript
805
+ class NetworkError extends TaggedError('NetworkError')<{
806
+ url: string;
807
+ statusCode: number;
808
+ }> {}
809
+
810
+ try {
811
+ await fetch('/api');
812
+ } catch (original) {
813
+ // Chain to original error via ErrorOptions (not props)
814
+ throw new NetworkError(
815
+ { url: '/api', statusCode: 500 },
816
+ { cause: original } // ✅ Error chaining
817
+ );
818
+ }
819
+ ```
820
+
821
+ ### Summary
822
+
823
+ - **String literals**: Simple, great for distinct states without extra data
824
+ - **TaggedError**: Rich errors with context, exhaustive matching, proper stack traces
825
+ - **`match()`**: Forces you to handle every variant (compile-time safety)
826
+ - **`matchPartial()`**: Handle some variants with a catch-all fallback
403
827
 
404
828
  ## Recipes & Patterns
405
829
 
@@ -547,15 +971,24 @@ const result = await validateAndCheckout(async (step) => {
547
971
  - **State save & resume** – Persist step completions and resume later.
548
972
 
549
973
  ```typescript
550
- import { createWorkflow, isStepComplete, type ResumeStateEntry } from '@jagreehal/workflow';
974
+ import { createWorkflow, createStepCollector } from '@jagreehal/workflow';
551
975
 
552
- const savedSteps = new Map<string, ResumeStateEntry>();
976
+ // Collect state during execution
977
+ const collector = createStepCollector();
553
978
  const workflow = createWorkflow(deps, {
554
- onEvent: (event) => {
555
- if (isStepComplete(event)) savedSteps.set(event.stepKey, { result: event.result, meta: event.meta });
556
- },
979
+ onEvent: collector.handleEvent, // Automatically collects step_complete events
557
980
  });
558
- const resumed = createWorkflow(deps, { resumeState: { steps: savedSteps } });
981
+
982
+ await workflow(async (step) => {
983
+ const user = await step(() => fetchUser("1"), { key: "user:1" });
984
+ return user;
985
+ });
986
+
987
+ // Get collected state
988
+ const state = collector.getState();
989
+
990
+ // Resume later
991
+ const resumed = createWorkflow(deps, { resumeState: state });
559
992
  ```
560
993
 
561
994
  - **Human-in-the-loop approvals** – Pause a workflow until someone approves.
@@ -605,16 +1038,25 @@ const result = await validateAndCheckout(async (step) => {
605
1038
  const data = map(result, ([user, posts]) => ({ user, posts }));
606
1039
  ```
607
1040
 
608
- ## Real-World Example: Safe Payment Retries
1041
+ ## Real-World Example: Safe Payment Retries with Persistence
609
1042
 
610
1043
  The scariest failure mode in payments: **charge succeeded, but persistence failed**. If you retry naively, you charge the customer twice.
611
1044
 
612
- Step keys solve this. Once a step succeeds, it's cached - retries skip it automatically:
1045
+ Step keys + persistence solve this. Save state to a database, and if the workflow crashes, resume from the last successful step:
613
1046
 
614
1047
  ```typescript
1048
+ import { createWorkflow, createStepCollector, stringifyState, parseState } from '@jagreehal/workflow';
1049
+
615
1050
  const processPayment = createWorkflow({ validateCard, chargeProvider, persistResult });
616
1051
 
617
- const result = await processPayment(async (step) => {
1052
+ // Collect state for persistence
1053
+ const collector = createStepCollector();
1054
+ const workflow = createWorkflow(
1055
+ { validateCard, chargeProvider, persistResult },
1056
+ { onEvent: collector.handleEvent }
1057
+ );
1058
+
1059
+ const result = await workflow(async (step) => {
618
1060
  const card = await step(() => validateCard(input), { key: 'validate' });
619
1061
 
620
1062
  // This is the dangerous step. Once it succeeds, never repeat it:
@@ -622,15 +1064,53 @@ const result = await processPayment(async (step) => {
622
1064
  key: `charge:${input.idempotencyKey}`,
623
1065
  });
624
1066
 
625
- // If THIS fails (DB down), you can rerun the workflow later.
1067
+ // If THIS fails (DB down), save state and rerun later.
626
1068
  // The charge step is cached - it won't execute again.
627
1069
  await step(() => persistResult(charge), { key: `persist:${charge.id}` });
628
1070
 
629
1071
  return { paymentId: charge.id };
630
1072
  });
1073
+
1074
+ // Save state after each run (or on crash)
1075
+ if (result.ok) {
1076
+ const state = collector.getState();
1077
+ const json = stringifyState(state, { orderId: input.orderId });
1078
+ await db.workflowStates.upsert({
1079
+ where: { idempotencyKey: input.idempotencyKey },
1080
+ update: { state: json, updatedAt: new Date() },
1081
+ create: { idempotencyKey: input.idempotencyKey, state: json },
1082
+ });
1083
+ }
1084
+ ```
1085
+
1086
+ **Crash recovery:** If the workflow crashes after charging but before persisting:
1087
+
1088
+ ```typescript
1089
+ // On restart, load saved state
1090
+ const saved = await db.workflowStates.findUnique({
1091
+ where: { idempotencyKey: input.idempotencyKey },
1092
+ });
1093
+
1094
+ if (saved) {
1095
+ const savedState = parseState(saved.state);
1096
+ const workflow = createWorkflow(
1097
+ { validateCard, chargeProvider, persistResult },
1098
+ { resumeState: savedState }
1099
+ );
1100
+
1101
+ // Resume - charge step uses cached result, no double-billing!
1102
+ const result = await workflow(async (step) => {
1103
+ const card = await step(() => validateCard(input), { key: 'validate' }); // Cache hit
1104
+ const charge = await step(() => chargeProvider(card), {
1105
+ key: `charge:${input.idempotencyKey}`,
1106
+ }); // Cache hit - returns previous charge result
1107
+ await step(() => persistResult(charge), { key: `persist:${charge.id}` }); // Executes fresh
1108
+ return { paymentId: charge.id };
1109
+ });
1110
+ }
631
1111
  ```
632
1112
 
633
- Crash after charging but before persisting? Rerun the workflow. The charge step returns its cached result. No double-billing.
1113
+ Crash after charging but before persisting? Resume the workflow. The charge step returns its cached result. No double-billing.
634
1114
 
635
1115
  ## Is This Library Right for You?
636
1116
 
package/dist/core.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var Y=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var ce=Object.prototype.hasOwnProperty;var Ee=(e,t)=>{for(var n in t)Y(e,n,{get:t[n],enumerable:!0})},ye=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let l of le(t))!ce.call(e,l)&&l!==n&&Y(e,l,{get:()=>t[l],enumerable:!(s=pe(t,l))||s.enumerable});return e};var me=e=>ye(Y({},"__esModule",{value:!0}),e);var $e={};Ee($e,{EARLY_EXIT_SYMBOL:()=>G,STEP_TIMEOUT_MARKER:()=>O,UnwrapError:()=>N,all:()=>Le,allAsync:()=>We,allSettled:()=>Xe,allSettledAsync:()=>Je,andThen:()=>Me,any:()=>Be,anyAsync:()=>qe,bimap:()=>Ke,createEarlyExit:()=>ne,err:()=>T,from:()=>ge,fromNullable:()=>ve,fromPromise:()=>Ae,getStepTimeoutMeta:()=>te,isEarlyExit:()=>re,isErr:()=>Te,isOk:()=>we,isStepTimeoutError:()=>J,isUnexpectedError:()=>ee,map:()=>be,mapError:()=>he,mapErrorTry:()=>De,mapTry:()=>Ue,match:()=>_e,ok:()=>S,orElse:()=>Fe,orElseAsync:()=>je,partition:()=>Ye,recover:()=>Ne,recoverAsync:()=>Ve,run:()=>$,tap:()=>Ie,tapError:()=>Oe,tryAsync:()=>Pe,unwrap:()=>Ce,unwrapOr:()=>xe,unwrapOrElse:()=>Se});module.exports=me($e);var S=e=>({ok:!0,value:e}),T=(e,t)=>({ok:!1,error:e,...t?.cause!==void 0?{cause:t.cause}:{}}),we=e=>e.ok,Te=e=>!e.ok,ee=e=>typeof e=="object"&&e!==null&&e.type==="UNEXPECTED_ERROR",O=Symbol.for("step_timeout_marker");function J(e){return typeof e!="object"||e===null?!1:e.type==="STEP_TIMEOUT"?!0:O in e}function te(e){if(!(typeof e!="object"||e===null)){if(e.type==="STEP_TIMEOUT"){let t=e;return{timeoutMs:t.timeoutMs,stepName:t.stepName,stepKey:t.stepKey,attempt:t.attempt}}if(O in e)return e[O]}}var G=Symbol("early-exit");function ne(e,t){return{[G]:!0,error:e,meta:t}}function re(e){return typeof e=="object"&&e!==null&&e[G]===!0}var oe=Symbol("mapper-exception");function ke(e){return{[oe]:!0,thrown:e}}function de(e){return typeof e=="object"&&e!==null&&e[oe]===!0}function fe(e){return typeof e=="string"?{name:e}:e??{}}function B(e,t){let{backoff:n,initialDelay:s,maxDelay:l,jitter:A}=t,k;switch(n){case"fixed":k=s;break;case"linear":k=s*e;break;case"exponential":k=s*Math.pow(2,e-1);break}if(k=Math.min(k,l),A){let o=k*.25*Math.random();k=k+o}return Math.floor(k)}function q(e){return new Promise(t=>setTimeout(t,e))}var Z=Symbol("timeout");async function Re(e,t,n){let s=new AbortController,l=t.error??{type:"STEP_TIMEOUT",stepName:n.name,stepKey:n.key,timeoutMs:t.ms,attempt:n.attempt},A,k=new Promise((h,x)=>{A=setTimeout(()=>{s.abort(),x({[Z]:!0,error:l})},t.ms)}),o;t.signal?o=Promise.resolve(e(s.signal)):o=Promise.resolve(e());try{return await Promise.race([o,k])}catch(h){if(typeof h=="object"&&h!==null&&h[Z]===!0){let x=h.error;if(typeof x=="object"&&x!==null&&x.type!=="STEP_TIMEOUT"){let K={timeoutMs:t.ms,stepName:n.name,stepKey:n.key,attempt:n.attempt};O in x?x[O]=K:Object.defineProperty(x,O,{value:K,enumerable:!1,writable:!0,configurable:!1})}throw x}throw h}finally{clearTimeout(A)}}var D={backoff:"exponential",initialDelay:100,maxDelay:3e4,jitter:!0,retryOn:()=>!0,onRetry:()=>{}};async function $(e,t){let{onError:n,onEvent:s,catchUnexpected:l,workflowId:A,context:k}=t&&typeof t=="object"?t:{},o=A??crypto.randomUUID(),h=!n&&!l,x=[],K=0,V=r=>r??`step_${++K}`,c=r=>{if(r.type==="step_success"){let b=r.stepId;for(let v=x.length-1;v>=0;v--){let w=x[v];if(w.type==="race"&&!w.winnerId){w.winnerId=b;break}}}s?.(r,k)},M=ne,H=r=>re(r),L=(r,b)=>h?b?.origin==="result"?{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"result",error:r,...b.resultCause!==void 0?{cause:b.resultCause}:{}}}:b?.origin==="throw"?{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"throw",error:r,thrown:b.thrown}}:{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"result",error:r}}:r,se=r=>r.origin==="result"?r.resultCause:r.thrown,ue=r=>({type:"UNEXPECTED_ERROR",cause:r.meta.origin==="result"?{type:"STEP_FAILURE",origin:"result",error:r.error,...r.meta.resultCause!==void 0?{cause:r.meta.resultCause}:{}}:{type:"STEP_FAILURE",origin:"throw",error:r.error,thrown:r.meta.thrown}});try{let r=(w,i)=>(async()=>{let m=fe(i),{name:u,key:a,retry:d,timeout:E}=m,p=V(a),f=s,R=f?performance.now():0;if(!(typeof w=="function")){if(d&&d.attempts>1)throw new Error("step: retry options require a function operation. Direct Promise/Result values cannot be re-executed on retry. Wrap your operation in a function: step(() => yourOperation, { retry: {...} })");if(E)throw new Error("step: timeout options require a function operation. Direct Promise/Result values cannot be wrapped with timeout after they've started. Wrap your operation in a function: step(() => yourOperation, { timeout: {...} })")}let g={attempts:Math.max(1,d?.attempts??1),backoff:d?.backoff??D.backoff,initialDelay:d?.initialDelay??D.initialDelay,maxDelay:d?.maxDelay??D.maxDelay,jitter:d?.jitter??D.jitter,retryOn:d?.retryOn??D.retryOn,onRetry:d?.onRetry??D.onRetry};s&&c({type:"step_start",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now()});let z;for(let C=1;C<=g.attempts;C++){let ie=f?performance.now():0;try{let y;if(typeof w=="function"?E?y=await Re(w,E,{name:u,key:a,attempt:C}):y=await w():y=await w,y.ok){let _=performance.now()-R;return c({type:"step_success",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:_}),a&&c({type:"step_complete",workflowId:o,stepKey:a,name:u,ts:Date.now(),durationMs:_,result:y}),y.value}if(z=y,C<g.attempts&&g.retryOn(y.error,C)){let _=B(C,g);c({type:"step_retry",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),attempt:C+1,maxAttempts:g.attempts,delayMs:_,error:y.error}),g.onRetry(y.error,C,_),await q(_);continue}g.attempts>1&&c({type:"step_retries_exhausted",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:performance.now()-R,attempts:C,lastError:y.error});break}catch(y){let _=performance.now()-ie;if(H(y))throw c({type:"step_aborted",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:_}),y;if(J(y)){let P=te(y),W=E?.ms??P?.timeoutMs??0;if(c({type:"step_timeout",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),timeoutMs:W,attempt:C}),C<g.attempts&&g.retryOn(y,C)){let X=B(C,g);c({type:"step_retry",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),attempt:C+1,maxAttempts:g.attempts,delayMs:X,error:y}),g.onRetry(y,C,X),await q(X);continue}g.attempts>1&&c({type:"step_retries_exhausted",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:performance.now()-R,attempts:C,lastError:y})}if(C<g.attempts&&g.retryOn(y,C)){let P=B(C,g);c({type:"step_retry",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),attempt:C+1,maxAttempts:g.attempts,delayMs:P,error:y}),g.onRetry(y,C,P),await q(P);continue}g.attempts>1&&!J(y)&&c({type:"step_retries_exhausted",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:performance.now()-R,attempts:C,lastError:y});let j=performance.now()-R;if(l){let P;try{P=l(y)}catch(W){throw ke(W)}throw c({type:"step_error",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:j,error:P}),a&&c({type:"step_complete",workflowId:o,stepKey:a,name:u,ts:Date.now(),durationMs:j,result:T(P,{cause:y}),meta:{origin:"throw",thrown:y}}),n?.(P,u),M(P,{origin:"throw",thrown:y})}else{let P={type:"UNEXPECTED_ERROR",cause:{type:"UNCAUGHT_EXCEPTION",thrown:y}};throw c({type:"step_error",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:j,error:P}),a&&c({type:"step_complete",workflowId:o,stepKey:a,name:u,ts:Date.now(),durationMs:j,result:T(P,{cause:y}),meta:{origin:"throw",thrown:y}}),y}}}let I=z,Q=performance.now()-R,ae=L(I.error,{origin:"result",resultCause:I.cause});throw c({type:"step_error",workflowId:o,stepId:p,stepKey:a,name:u,ts:Date.now(),durationMs:Q,error:ae}),a&&c({type:"step_complete",workflowId:o,stepKey:a,name:u,ts:Date.now(),durationMs:Q,result:I,meta:{origin:"result",resultCause:I.cause}}),n?.(I.error,u),M(I.error,{origin:"result",resultCause:I.cause})})();r.try=(w,i)=>{let m=i.name,u=i.key,a=V(u),d="error"in i?()=>i.error:i.onError,E=s;return(async()=>{let p=E?performance.now():0;s&&c({type:"step_start",workflowId:o,stepId:a,stepKey:u,name:m,ts:Date.now()});try{let f=await w(),R=performance.now()-p;return c({type:"step_success",workflowId:o,stepId:a,stepKey:u,name:m,ts:Date.now(),durationMs:R}),u&&c({type:"step_complete",workflowId:o,stepKey:u,name:m,ts:Date.now(),durationMs:R,result:S(f)}),f}catch(f){let R=d(f),U=performance.now()-p,F=L(R,{origin:"throw",thrown:f});throw c({type:"step_error",workflowId:o,stepId:a,stepKey:u,name:m,ts:Date.now(),durationMs:U,error:F}),u&&c({type:"step_complete",workflowId:o,stepKey:u,name:m,ts:Date.now(),durationMs:U,result:T(R,{cause:f}),meta:{origin:"throw",thrown:f}}),n?.(R,m),M(R,{origin:"throw",thrown:f})}})()},r.fromResult=(w,i)=>{let m=i.name,u=i.key,a=V(u),d="error"in i?()=>i.error:i.onError,E=s;return(async()=>{let p=E?performance.now():0;s&&c({type:"step_start",workflowId:o,stepId:a,stepKey:u,name:m,ts:Date.now()});let f=await w();if(f.ok){let R=performance.now()-p;return c({type:"step_success",workflowId:o,stepId:a,stepKey:u,name:m,ts:Date.now(),durationMs:R}),u&&c({type:"step_complete",workflowId:o,stepKey:u,name:m,ts:Date.now(),durationMs:R,result:S(f.value)}),f.value}else{let R=d(f.error),U=performance.now()-p,F=L(R,{origin:"result",resultCause:f.error});throw c({type:"step_error",workflowId:o,stepId:a,stepKey:u,name:m,ts:Date.now(),durationMs:U,error:F}),u&&c({type:"step_complete",workflowId:o,stepKey:u,name:m,ts:Date.now(),durationMs:U,result:T(R,{cause:f.error}),meta:{origin:"result",resultCause:f.error}}),n?.(R,m),M(R,{origin:"result",resultCause:f.error})}})()},r.retry=(w,i)=>r(w,{name:i.name,key:i.key,retry:{attempts:i.attempts,backoff:i.backoff,initialDelay:i.initialDelay,maxDelay:i.maxDelay,jitter:i.jitter,retryOn:i.retryOn,onRetry:i.onRetry},timeout:i.timeout}),r.withTimeout=(w,i)=>r(w,{name:i.name,key:i.key,timeout:i}),r.parallel=(w,i)=>{let m=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let u=performance.now(),a=!1;x.push({scopeId:m,type:"parallel"});let d=()=>{if(a)return;a=!0;let E=x.findIndex(p=>p.scopeId===m);E!==-1&&x.splice(E,1),c({type:"scope_end",workflowId:o,scopeId:m,ts:Date.now(),durationMs:performance.now()-u})};c({type:"scope_start",workflowId:o,scopeId:m,scopeType:"parallel",name:w,ts:Date.now()});try{let E=await i();if(d(),!E.ok)throw n?.(E.error,w),M(E.error,{origin:"result",resultCause:E.cause});return E.value}catch(E){throw d(),E}})()},r.race=(w,i)=>{let m=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let u=performance.now(),a=!1,d={scopeId:m,type:"race",winnerId:void 0};x.push(d);let E=()=>{if(a)return;a=!0;let p=x.findIndex(f=>f.scopeId===m);p!==-1&&x.splice(p,1),c({type:"scope_end",workflowId:o,scopeId:m,ts:Date.now(),durationMs:performance.now()-u,winnerId:d.winnerId})};c({type:"scope_start",workflowId:o,scopeId:m,scopeType:"race",name:w,ts:Date.now()});try{let p=await i();if(E(),!p.ok)throw n?.(p.error,w),M(p.error,{origin:"result",resultCause:p.cause});return p.value}catch(p){throw E(),p}})()},r.allSettled=(w,i)=>{let m=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let u=performance.now(),a=!1;x.push({scopeId:m,type:"allSettled"});let d=()=>{if(a)return;a=!0;let E=x.findIndex(p=>p.scopeId===m);E!==-1&&x.splice(E,1),c({type:"scope_end",workflowId:o,scopeId:m,ts:Date.now(),durationMs:performance.now()-u})};c({type:"scope_start",workflowId:o,scopeId:m,scopeType:"allSettled",name:w,ts:Date.now()});try{let E=await i();if(d(),!E.ok)throw n?.(E.error,w),M(E.error,{origin:"result",resultCause:E.cause});return E.value}catch(E){throw d(),E}})()};let v=await e(r);return S(v)}catch(r){if(de(r))throw r.thrown;if(H(r)){let v=se(r.meta);if(l||n)return T(r.error,{cause:v});if(ee(r.error))return T(r.error,{cause:v});let w=ue(r);return T(w,{cause:v})}if(l){let v=l(r);return n?.(v,"unexpected"),T(v,{cause:r})}let b={type:"UNEXPECTED_ERROR",cause:{type:"UNCAUGHT_EXCEPTION",thrown:r}};return n?.(b,"unexpected"),T(b,{cause:r})}}$.strict=(e,t)=>$(e,t);var N=class extends Error{constructor(n,s){super(`Unwrap called on an error result: ${String(n)}`);this.error=n;this.cause=s;this.name="UnwrapError"}},Ce=e=>{if(e.ok)return e.value;throw new N(e.error,e.cause)},xe=(e,t)=>e.ok?e.value:t,Se=(e,t)=>e.ok?e.value:t(e.error,e.cause);function ge(e,t){try{return S(e())}catch(n){return t?T(t(n),{cause:n}):T(n)}}async function Ae(e,t){try{return S(await e)}catch(n){return t?T(t(n),{cause:n}):T(n)}}async function Pe(e,t){try{return S(await e())}catch(n){return t?T(t(n),{cause:n}):T(n)}}function ve(e,t){return e!=null?S(e):T(t())}function be(e,t){return e.ok?S(t(e.value)):e}function he(e,t){return e.ok?e:T(t(e.error),{cause:e.cause})}function _e(e,t){return e.ok?t.ok(e.value):t.err(e.error,e.cause)}function Me(e,t){return e.ok?t(e.value):e}function Ie(e,t){return e.ok&&t(e.value),e}function Oe(e,t){return e.ok||t(e.error,e.cause),e}function Ue(e,t,n){if(!e.ok)return e;try{return S(t(e.value))}catch(s){return T(n(s),{cause:s})}}function De(e,t,n){if(e.ok)return e;try{return T(t(e.error),{cause:e.cause})}catch(s){return T(n(s),{cause:s})}}function Ke(e,t,n){return e.ok?S(t(e.value)):T(n(e.error),{cause:e.cause})}function Fe(e,t){return e.ok?e:t(e.error,e.cause)}async function je(e,t){let n=await e;return n.ok?n:t(n.error,n.cause)}function Ne(e,t){return e.ok?S(e.value):S(t(e.error,e.cause))}async function Ve(e,t){let n=await e;return n.ok?S(n.value):S(await t(n.error,n.cause))}function Le(e){let t=[];for(let n of e){if(!n.ok)return n;t.push(n.value)}return S(t)}async function We(e){return e.length===0?S([]):new Promise(t=>{let n=!1,s=e.length,l=new Array(e.length);for(let A=0;A<e.length;A++){let k=A;Promise.resolve(e[k]).catch(o=>T({type:"PROMISE_REJECTED",cause:o},{cause:{type:"PROMISE_REJECTION",reason:o}})).then(o=>{if(!n){if(!o.ok){n=!0,t(o);return}l[k]=o.value,s--,s===0&&t(S(l))}})}})}function Xe(e){let t=[],n=[];for(let s of e)s.ok?t.push(s.value):n.push({error:s.error,cause:s.cause});return n.length>0?T(n):S(t)}function Ye(e){let t=[],n=[];for(let s of e)s.ok?t.push(s.value):n.push(s.error);return{values:t,errors:n}}function Be(e){if(e.length===0)return T({type:"EMPTY_INPUT",message:"any() requires at least one Result"});let t=null;for(let n of e){if(n.ok)return n;t||(t=n)}return t}async function qe(e){return e.length===0?T({type:"EMPTY_INPUT",message:"anyAsync() requires at least one Result"}):new Promise(t=>{let n=!1,s=e.length,l=null;for(let A of e)Promise.resolve(A).catch(k=>T({type:"PROMISE_REJECTED",cause:k},{cause:{type:"PROMISE_REJECTION",reason:k}})).then(k=>{if(!n){if(k.ok){n=!0,t(k);return}l||(l=k),s--,s===0&&t(l)}})})}async function Je(e){let t=await Promise.all(e.map(l=>Promise.resolve(l).then(A=>({status:"result",result:A})).catch(A=>({status:"rejected",error:{type:"PROMISE_REJECTED",cause:A},cause:{type:"PROMISE_REJECTION",reason:A}})))),n=[],s=[];for(let l of t)l.status==="rejected"?s.push({error:l.error,cause:l.cause}):l.result.ok?n.push(l.result.value):s.push({error:l.result.error,cause:l.result.cause});return s.length>0?T(s):S(n)}0&&(module.exports={EARLY_EXIT_SYMBOL,STEP_TIMEOUT_MARKER,UnwrapError,all,allAsync,allSettled,allSettledAsync,andThen,any,anyAsync,bimap,createEarlyExit,err,from,fromNullable,fromPromise,getStepTimeoutMeta,isEarlyExit,isErr,isOk,isStepTimeoutError,isUnexpectedError,map,mapError,mapErrorTry,mapTry,match,ok,orElse,orElseAsync,partition,recover,recoverAsync,run,tap,tapError,tryAsync,unwrap,unwrapOr,unwrapOrElse});
1
+ "use strict";var G=Object.defineProperty;var Ee=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var me=(e,t)=>{for(var n in t)G(e,n,{get:t[n],enumerable:!0})},Te=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let w of ye(t))!we.call(e,w)&&w!==n&&G(e,w,{get:()=>t[w],enumerable:!(a=Ee(t,w))||a.enumerable});return e};var ke=e=>Te(G({},"__esModule",{value:!0}),e);var Qe={};me(Qe,{EARLY_EXIT_SYMBOL:()=>ee,STEP_TIMEOUT_MARKER:()=>D,UnwrapError:()=>X,all:()=>$e,allAsync:()=>Je,allSettled:()=>Be,allSettledAsync:()=>ze,andThen:()=>Ue,any:()=>Ge,anyAsync:()=>He,bimap:()=>Ne,createEarlyExit:()=>se,err:()=>f,from:()=>Pe,fromNullable:()=>ve,fromPromise:()=>he,getStepTimeoutMeta:()=>oe,hydrate:()=>ie,isEarlyExit:()=>ue,isErr:()=>fe,isOk:()=>de,isSerializedResult:()=>Ye,isStepTimeoutError:()=>Q,isUnexpectedError:()=>re,map:()=>Me,mapError:()=>Ie,mapErrorTry:()=>Fe,mapTry:()=>je,match:()=>Oe,ok:()=>g,orElse:()=>Ve,orElseAsync:()=>Le,partition:()=>qe,recover:()=>We,recoverAsync:()=>Xe,run:()=>Z,tap:()=>De,tapError:()=>Ke,tryAsync:()=>_e,unwrap:()=>ge,unwrapOr:()=>Ae,unwrapOrElse:()=>be});module.exports=ke(Qe);var g=e=>({ok:!0,value:e}),f=(e,t)=>({ok:!1,error:e,...t?.cause!==void 0?{cause:t.cause}:{}}),de=e=>e.ok,fe=e=>!e.ok,re=e=>typeof e=="object"&&e!==null&&e.type==="UNEXPECTED_ERROR",D=Symbol.for("step_timeout_marker");function Q(e){return typeof e!="object"||e===null?!1:e.type==="STEP_TIMEOUT"?!0:D in e}function oe(e){if(!(typeof e!="object"||e===null)){if(e.type==="STEP_TIMEOUT"){let t=e;return{timeoutMs:t.timeoutMs,stepName:t.stepName,stepKey:t.stepKey,attempt:t.attempt}}if(D in e)return e[D]}}var ee=Symbol("early-exit");function se(e,t){return{[ee]:!0,error:e,meta:t}}function ue(e){return typeof e=="object"&&e!==null&&e[ee]===!0}var ae=Symbol("mapper-exception");function Re(e){return{[ae]:!0,thrown:e}}function Ce(e){return typeof e=="object"&&e!==null&&e[ae]===!0}function xe(e){return typeof e=="string"?{name:e}:e??{}}function H(e,t){let{backoff:n,initialDelay:a,maxDelay:w,jitter:A}=t,E;switch(n){case"fixed":E=a;break;case"linear":E=a*e;break;case"exponential":E=a*Math.pow(2,e-1);break}if(E=Math.min(E,w),A){let r=E*.25*Math.random();E=E+r}return Math.floor(E)}function z(e){return new Promise(t=>setTimeout(t,e))}var ne=Symbol("timeout");async function Se(e,t,n){let a=new AbortController,w=t.error??{type:"STEP_TIMEOUT",stepName:n.name,stepKey:n.key,timeoutMs:t.ms,attempt:n.attempt},A,E=new Promise((I,C)=>{A=setTimeout(()=>{a.abort(),C({[ne]:!0,error:w})},t.ms)}),r;t.signal?r=Promise.resolve(e(a.signal)):r=Promise.resolve(e());try{return await Promise.race([r,E])}catch(I){if(typeof I=="object"&&I!==null&&I[ne]===!0){let C=I.error;if(typeof C=="object"&&C!==null&&C.type!=="STEP_TIMEOUT"){let L={timeoutMs:t.ms,stepName:n.name,stepKey:n.key,attempt:n.attempt};D in C?C[D]=L:Object.defineProperty(C,D,{value:L,enumerable:!1,writable:!0,configurable:!1})}throw C}throw I}finally{clearTimeout(A)}}var F={backoff:"exponential",initialDelay:100,maxDelay:3e4,jitter:!0,retryOn:()=>!0,onRetry:()=>{}};async function Z(e,t){let{onError:n,onEvent:a,catchUnexpected:w,workflowId:A,context:E}=t&&typeof t=="object"?t:{},r=A??crypto.randomUUID(),I=!n&&!w,C=[],L=0,Y=o=>o??`step_${++L}`,y=o=>{let b=o.context!==void 0||E===void 0?o:{...o,context:E};if(b.type==="step_success"){let M=b.stepId;for(let K=C.length-1;K>=0;K--){let N=C[K];if(N.type==="race"&&!N.winnerId){N.winnerId=M;break}}}a?.(b,E)},O=se,te=o=>ue(o),$=(o,b)=>I?b?.origin==="result"?{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"result",error:o,...b.resultCause!==void 0?{cause:b.resultCause}:{}}}:b?.origin==="throw"?{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"throw",error:o,thrown:b.thrown}}:{type:"UNEXPECTED_ERROR",cause:{type:"STEP_FAILURE",origin:"result",error:o}}:o,pe=o=>o.origin==="result"?o.resultCause:o.thrown,le=o=>({type:"UNEXPECTED_ERROR",cause:o.meta.origin==="result"?{type:"STEP_FAILURE",origin:"result",error:o.error,...o.meta.resultCause!==void 0?{cause:o.meta.resultCause}:{}}:{type:"STEP_FAILURE",origin:"throw",error:o.error,thrown:o.meta.thrown}});try{let b=function(T,p){let i=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let s=performance.now(),u=!1;C.push({scopeId:i,type:"parallel"});let x=()=>{if(u)return;u=!0;let c=C.findIndex(l=>l.scopeId===i);c!==-1&&C.splice(c,1),y({type:"scope_end",workflowId:r,scopeId:i,ts:Date.now(),durationMs:performance.now()-s})};y({type:"scope_start",workflowId:r,scopeId:i,scopeType:"parallel",name:T,ts:Date.now()});try{let c=await p();if(x(),!c.ok)throw n?.(c.error,T,E),O(c.error,{origin:"result",resultCause:c.cause});return c.value}catch(c){throw x(),c}})()},M=function(T,p){let i=Object.keys(T),s=p.name??`Parallel(${i.join(", ")})`,u=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let x=performance.now(),c=!1;C.push({scopeId:u,type:"parallel"});let l=()=>{if(c)return;c=!0;let k=C.findIndex(R=>R.scopeId===u);k!==-1&&C.splice(k,1),y({type:"scope_end",workflowId:r,scopeId:u,ts:Date.now(),durationMs:performance.now()-x})};y({type:"scope_start",workflowId:r,scopeId:u,scopeType:"parallel",name:s,ts:Date.now()});try{let k=await new Promise(h=>{if(i.length===0){h([]);return}let _=!1,S=i.length,V=new Array(i.length);for(let v=0;v<i.length;v++){let j=i[v],J=v;Promise.resolve(T[j]()).catch(d=>f({type:"PROMISE_REJECTED",cause:d},{cause:{type:"PROMISE_REJECTION",reason:d}})).then(d=>{if(!_){if(!d.ok){_=!0,h([{key:j,result:d}]);return}V[J]={key:j,result:d},S--,S===0&&h(V)}})}});l();let R={};for(let{key:h,result:_}of k){if(!_.ok)throw n?.(_.error,h,E),O(_.error,{origin:"result",resultCause:_.cause});R[h]=_.value}return R}catch(k){throw l(),k}})()};var Ze=b,et=M;let o=(T,p)=>(async()=>{let i=xe(p),{name:s,key:u,retry:x,timeout:c}=i,l=Y(u),k=a,R=k?performance.now():0;if(!(typeof T=="function")){if(x&&x.attempts>1)throw new Error("step: retry options require a function operation. Direct Promise/Result values cannot be re-executed on retry. Wrap your operation in a function: step(() => yourOperation, { retry: {...} })");if(c)throw new Error("step: timeout options require a function operation. Direct Promise/Result values cannot be wrapped with timeout after they've started. Wrap your operation in a function: step(() => yourOperation, { timeout: {...} })")}let S={attempts:Math.max(1,x?.attempts??1),backoff:x?.backoff??F.backoff,initialDelay:x?.initialDelay??F.initialDelay,maxDelay:x?.maxDelay??F.maxDelay,jitter:x?.jitter??F.jitter,retryOn:x?.retryOn??F.retryOn,onRetry:x?.onRetry??F.onRetry};a&&y({type:"step_start",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now()});let V;for(let d=1;d<=S.attempts;d++){let ce=k?performance.now():0;try{let m;if(typeof T=="function"?c?m=await Se(T,c,{name:s,key:u,attempt:d}):m=await T():m=await T,m.ok){let U=performance.now()-R;return y({type:"step_success",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:U}),u&&y({type:"step_complete",workflowId:r,stepKey:u,name:s,ts:Date.now(),durationMs:U,result:m}),m.value}if(V=m,d<S.attempts&&S.retryOn(m.error,d)){let U=H(d,S);y({type:"step_retry",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),attempt:d+1,maxAttempts:S.attempts,delayMs:U,error:m.error}),S.onRetry(m.error,d,U),await z(U);continue}S.attempts>1&&y({type:"step_retries_exhausted",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:performance.now()-R,attempts:d,lastError:m.error});break}catch(m){let U=performance.now()-ce;if(te(m))throw y({type:"step_aborted",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:U}),m;if(Q(m)){let P=oe(m),B=c?.ms??P?.timeoutMs??0;if(y({type:"step_timeout",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),timeoutMs:B,attempt:d}),d<S.attempts&&S.retryOn(m,d)){let q=H(d,S);y({type:"step_retry",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),attempt:d+1,maxAttempts:S.attempts,delayMs:q,error:m}),S.onRetry(m,d,q),await z(q);continue}S.attempts>1&&y({type:"step_retries_exhausted",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:performance.now()-R,attempts:d,lastError:m})}if(d<S.attempts&&S.retryOn(m,d)){let P=H(d,S);y({type:"step_retry",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),attempt:d+1,maxAttempts:S.attempts,delayMs:P,error:m}),S.onRetry(m,d,P),await z(P);continue}S.attempts>1&&!Q(m)&&y({type:"step_retries_exhausted",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:performance.now()-R,attempts:d,lastError:m});let W=performance.now()-R;if(w){let P;try{P=w(m)}catch(B){throw Re(B)}throw y({type:"step_error",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:W,error:P}),u&&y({type:"step_complete",workflowId:r,stepKey:u,name:s,ts:Date.now(),durationMs:W,result:f(P,{cause:m}),meta:{origin:"throw",thrown:m}}),n?.(P,s,E),O(P,{origin:"throw",thrown:m})}else{let P={type:"UNEXPECTED_ERROR",cause:{type:"UNCAUGHT_EXCEPTION",thrown:m}};throw y({type:"step_error",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:W,error:P}),u&&y({type:"step_complete",workflowId:r,stepKey:u,name:s,ts:Date.now(),durationMs:W,result:f(P,{cause:m}),meta:{origin:"throw",thrown:m}}),m}}}let v=V,j=performance.now()-R,J=$(v.error,{origin:"result",resultCause:v.cause});throw y({type:"step_error",workflowId:r,stepId:l,stepKey:u,name:s,ts:Date.now(),durationMs:j,error:J}),u&&y({type:"step_complete",workflowId:r,stepKey:u,name:s,ts:Date.now(),durationMs:j,result:v,meta:{origin:"result",resultCause:v.cause}}),n?.(v.error,s,E),O(v.error,{origin:"result",resultCause:v.cause})})();o.try=(T,p)=>{let i=p.name,s=p.key,u=Y(s),x="error"in p?()=>p.error:p.onError,c=a;return(async()=>{let l=c?performance.now():0;a&&y({type:"step_start",workflowId:r,stepId:u,stepKey:s,name:i,ts:Date.now()});try{let k=await T(),R=performance.now()-l;return y({type:"step_success",workflowId:r,stepId:u,stepKey:s,name:i,ts:Date.now(),durationMs:R}),s&&y({type:"step_complete",workflowId:r,stepKey:s,name:i,ts:Date.now(),durationMs:R,result:g(k)}),k}catch(k){let R=x(k),h=performance.now()-l,_=$(R,{origin:"throw",thrown:k});throw y({type:"step_error",workflowId:r,stepId:u,stepKey:s,name:i,ts:Date.now(),durationMs:h,error:_}),s&&y({type:"step_complete",workflowId:r,stepKey:s,name:i,ts:Date.now(),durationMs:h,result:f(R,{cause:k}),meta:{origin:"throw",thrown:k}}),n?.(R,i,E),O(R,{origin:"throw",thrown:k})}})()},o.fromResult=(T,p)=>{let i=p.name,s=p.key,u=Y(s),x="error"in p?()=>p.error:p.onError,c=a;return(async()=>{let l=c?performance.now():0;a&&y({type:"step_start",workflowId:r,stepId:u,stepKey:s,name:i,ts:Date.now()});let k=await T();if(k.ok){let R=performance.now()-l;return y({type:"step_success",workflowId:r,stepId:u,stepKey:s,name:i,ts:Date.now(),durationMs:R}),s&&y({type:"step_complete",workflowId:r,stepKey:s,name:i,ts:Date.now(),durationMs:R,result:g(k.value)}),k.value}else{let R=x(k.error),h=performance.now()-l,_=$(R,{origin:"result",resultCause:k.error});throw y({type:"step_error",workflowId:r,stepId:u,stepKey:s,name:i,ts:Date.now(),durationMs:h,error:_}),s&&y({type:"step_complete",workflowId:r,stepKey:s,name:i,ts:Date.now(),durationMs:h,result:f(R,{cause:k.error}),meta:{origin:"result",resultCause:k.error}}),n?.(R,i,E),O(R,{origin:"result",resultCause:k.error})}})()},o.retry=(T,p)=>o(T,{name:p.name,key:p.key,retry:{attempts:p.attempts,backoff:p.backoff,initialDelay:p.initialDelay,maxDelay:p.maxDelay,jitter:p.jitter,retryOn:p.retryOn,onRetry:p.onRetry},timeout:p.timeout}),o.withTimeout=(T,p)=>o(T,{name:p.name,key:p.key,timeout:p}),o.parallel=((...T)=>{if(typeof T[0]=="string"){let p=T[0],i=T[1];return b(p,i)}else{let p=T[0],i=T[1]??{};return M(p,i)}}),o.race=(T,p)=>{let i=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let s=performance.now(),u=!1,x={scopeId:i,type:"race",winnerId:void 0};C.push(x);let c=()=>{if(u)return;u=!0;let l=C.findIndex(k=>k.scopeId===i);l!==-1&&C.splice(l,1),y({type:"scope_end",workflowId:r,scopeId:i,ts:Date.now(),durationMs:performance.now()-s,winnerId:x.winnerId})};y({type:"scope_start",workflowId:r,scopeId:i,scopeType:"race",name:T,ts:Date.now()});try{let l=await p();if(c(),!l.ok)throw n?.(l.error,T,E),O(l.error,{origin:"result",resultCause:l.cause});return l.value}catch(l){throw c(),l}})()},o.allSettled=(T,p)=>{let i=`scope_${Date.now()}_${Math.random().toString(36).slice(2,8)}`;return(async()=>{let s=performance.now(),u=!1;C.push({scopeId:i,type:"allSettled"});let x=()=>{if(u)return;u=!0;let c=C.findIndex(l=>l.scopeId===i);c!==-1&&C.splice(c,1),y({type:"scope_end",workflowId:r,scopeId:i,ts:Date.now(),durationMs:performance.now()-s})};y({type:"scope_start",workflowId:r,scopeId:i,scopeType:"allSettled",name:T,ts:Date.now()});try{let c=await p();if(x(),!c.ok)throw n?.(c.error,T,E),O(c.error,{origin:"result",resultCause:c.cause});return c.value}catch(c){throw x(),c}})()};let N=await e(o);return g(N)}catch(o){if(Ce(o))throw o.thrown;if(te(o)){let M=pe(o.meta);if(w||n)return f(o.error,{cause:M});if(re(o.error))return f(o.error,{cause:M});let K=le(o);return f(K,{cause:M})}if(w){let M=w(o);return n?.(M,"unexpected",E),f(M,{cause:o})}let b={type:"UNEXPECTED_ERROR",cause:{type:"UNCAUGHT_EXCEPTION",thrown:o}};return n?.(b,"unexpected",E),f(b,{cause:o})}}Z.strict=(e,t)=>Z(e,t);var X=class extends Error{constructor(n,a){super(`Unwrap called on an error result: ${String(n)}`);this.error=n;this.cause=a;this.name="UnwrapError"}},ge=e=>{if(e.ok)return e.value;throw new X(e.error,e.cause)},Ae=(e,t)=>e.ok?e.value:t,be=(e,t)=>e.ok?e.value:t(e.error,e.cause);function Pe(e,t){try{return g(e())}catch(n){return t?f(t(n),{cause:n}):f(n)}}async function he(e,t){try{return g(await e)}catch(n){return t?f(t(n),{cause:n}):f(n)}}async function _e(e,t){try{return g(await e())}catch(n){return t?f(t(n),{cause:n}):f(n)}}function ve(e,t){return e!=null?g(e):f(t())}function Me(e,t){return e.ok?g(t(e.value)):e}function Ie(e,t){return e.ok?e:f(t(e.error),{cause:e.cause})}function Oe(e,t){return e.ok?t.ok(e.value):t.err(e.error,e.cause)}function Ue(e,t){return e.ok?t(e.value):e}function De(e,t){return e.ok&&t(e.value),e}function Ke(e,t){return e.ok||t(e.error,e.cause),e}function je(e,t,n){if(!e.ok)return e;try{return g(t(e.value))}catch(a){return f(n(a),{cause:a})}}function Fe(e,t,n){if(e.ok)return e;try{return f(t(e.error),{cause:e.cause})}catch(a){return f(n(a),{cause:a})}}function Ne(e,t,n){return e.ok?g(t(e.value)):f(n(e.error),{cause:e.cause})}function Ve(e,t){return e.ok?e:t(e.error,e.cause)}async function Le(e,t){let n=await e;return n.ok?n:t(n.error,n.cause)}function We(e,t){return e.ok?g(e.value):g(t(e.error,e.cause))}async function Xe(e,t){let n=await e;return n.ok?g(n.value):g(await t(n.error,n.cause))}function ie(e){return e!==null&&typeof e=="object"&&"ok"in e&&typeof e.ok=="boolean"&&(e.ok===!0&&"value"in e||e.ok===!1&&"error"in e)?e:null}function Ye(e){return ie(e)!==null}function $e(e){let t=[];for(let n of e){if(!n.ok)return n;t.push(n.value)}return g(t)}async function Je(e){return e.length===0?g([]):new Promise(t=>{let n=!1,a=e.length,w=new Array(e.length);for(let A=0;A<e.length;A++){let E=A;Promise.resolve(e[E]).catch(r=>f({type:"PROMISE_REJECTED",cause:r},{cause:{type:"PROMISE_REJECTION",reason:r}})).then(r=>{if(!n){if(!r.ok){n=!0,t(r);return}w[E]=r.value,a--,a===0&&t(g(w))}})}})}function Be(e){let t=[],n=[];for(let a of e)a.ok?t.push(a.value):n.push({error:a.error,cause:a.cause});return n.length>0?f(n):g(t)}function qe(e){let t=[],n=[];for(let a of e)a.ok?t.push(a.value):n.push(a.error);return{values:t,errors:n}}function Ge(e){if(e.length===0)return f({type:"EMPTY_INPUT",message:"any() requires at least one Result"});let t=null;for(let n of e){if(n.ok)return n;t||(t=n)}return t}async function He(e){return e.length===0?f({type:"EMPTY_INPUT",message:"anyAsync() requires at least one Result"}):new Promise(t=>{let n=!1,a=e.length,w=null;for(let A of e)Promise.resolve(A).catch(E=>f({type:"PROMISE_REJECTED",cause:E},{cause:{type:"PROMISE_REJECTION",reason:E}})).then(E=>{if(!n){if(E.ok){n=!0,t(E);return}w||(w=E),a--,a===0&&t(w)}})})}async function ze(e){let t=await Promise.all(e.map(w=>Promise.resolve(w).then(A=>({status:"result",result:A})).catch(A=>({status:"rejected",error:{type:"PROMISE_REJECTED",cause:A},cause:{type:"PROMISE_REJECTION",reason:A}})))),n=[],a=[];for(let w of t)w.status==="rejected"?a.push({error:w.error,cause:w.cause}):w.result.ok?n.push(w.result.value):a.push({error:w.result.error,cause:w.result.cause});return a.length>0?f(a):g(n)}0&&(module.exports={EARLY_EXIT_SYMBOL,STEP_TIMEOUT_MARKER,UnwrapError,all,allAsync,allSettled,allSettledAsync,andThen,any,anyAsync,bimap,createEarlyExit,err,from,fromNullable,fromPromise,getStepTimeoutMeta,hydrate,isEarlyExit,isErr,isOk,isSerializedResult,isStepTimeoutError,isUnexpectedError,map,mapError,mapErrorTry,mapTry,match,ok,orElse,orElseAsync,partition,recover,recoverAsync,run,tap,tapError,tryAsync,unwrap,unwrapOr,unwrapOrElse});
2
2
  //# sourceMappingURL=core.cjs.map