@doeixd/machine 1.0.3 → 1.2.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.
Files changed (37) hide show
  1. package/README.md +48 -0
  2. package/dist/cjs/development/core.js.map +1 -1
  3. package/dist/cjs/development/delegate.js +89 -0
  4. package/dist/cjs/development/delegate.js.map +7 -0
  5. package/dist/cjs/development/index.js +383 -158
  6. package/dist/cjs/development/index.js.map +4 -4
  7. package/dist/cjs/development/minimal.js +172 -0
  8. package/dist/cjs/development/minimal.js.map +7 -0
  9. package/dist/cjs/development/react.js.map +1 -1
  10. package/dist/cjs/production/delegate.js +1 -0
  11. package/dist/cjs/production/index.js +3 -3
  12. package/dist/cjs/production/minimal.js +1 -0
  13. package/dist/esm/development/core.js.map +1 -1
  14. package/dist/esm/development/delegate.js +68 -0
  15. package/dist/esm/development/delegate.js.map +7 -0
  16. package/dist/esm/development/index.js +389 -158
  17. package/dist/esm/development/index.js.map +4 -4
  18. package/dist/esm/development/minimal.js +149 -0
  19. package/dist/esm/development/minimal.js.map +7 -0
  20. package/dist/esm/development/react.js.map +1 -1
  21. package/dist/esm/production/delegate.js +1 -0
  22. package/dist/esm/production/index.js +3 -3
  23. package/dist/esm/production/minimal.js +1 -0
  24. package/dist/types/delegate.d.ts +101 -0
  25. package/dist/types/delegate.d.ts.map +1 -0
  26. package/dist/types/index.d.ts +3 -0
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/types/minimal.d.ts +95 -0
  29. package/dist/types/minimal.d.ts.map +1 -0
  30. package/dist/types/types.d.ts +110 -0
  31. package/dist/types/types.d.ts.map +1 -0
  32. package/package.json +25 -1
  33. package/src/delegate.ts +267 -0
  34. package/src/index.ts +13 -0
  35. package/src/middleware.ts +1049 -1050
  36. package/src/minimal.ts +269 -0
  37. package/src/types.ts +129 -0
package/src/middleware.ts CHANGED
@@ -279,1168 +279,1167 @@ export function createMiddleware<M extends BaseMachine<any>>(
279
279
  onError?.(error as Error, 'before', { transitionName, context, args });
280
280
  }
281
281
  };
282
+ }
282
283
  }
283
- }
284
-
285
- return wrappedMachine;
286
- }
287
284
 
288
- /**
289
- * Creates a simple logging middleware that logs all transitions.
290
- *
291
- * @template M - The machine type
292
- * @param machine - The machine to add logging to
293
- * @param options - Logging configuration options
294
- * @returns A new machine with logging middleware
295
- */
296
- export function withLogging<M extends BaseMachine<any>>(
297
- machine: M,
298
- options: {
299
- logger?: (message: string) => void;
300
- includeArgs?: boolean;
301
- includeContext?: boolean;
302
- } = {}
303
- ): M {
304
- const { logger = console.log, includeArgs = false, includeContext = true } = options;
305
-
306
- return createMiddleware(machine, {
307
- before: ({ transitionName, args }) => {
308
- const message = includeArgs ? `→ ${transitionName} [${args.join(', ')}]` : `→ ${transitionName}`;
309
- logger(message);
310
- },
311
- after: ({ transitionName, nextContext }) => {
312
- const contextStr = includeContext ? ` ${JSON.stringify(nextContext)}` : '';
313
- logger(`✓ ${transitionName}${contextStr}`);
314
- },
315
- error: ({ transitionName, error }) => {
316
- console.error(`[Machine] ${transitionName} failed:`, error);
317
- }
318
- });
319
- }
285
+ return wrappedMachine;
286
+ }
320
287
 
321
- /**
322
- * Creates analytics tracking middleware.
323
- *
324
- * @template M - The machine type
325
- * @param machine - The machine to track
326
- * @param track - Analytics tracking function
327
- * @param options - Configuration options
328
- * @returns A new machine with analytics tracking
329
- */
330
- export function withAnalytics<M extends BaseMachine<any>>(
331
- machine: M,
332
- track: (event: string, data?: any) => void,
333
- options: {
334
- eventPrefix?: string;
335
- includePrevContext?: boolean;
336
- includeArgs?: boolean;
337
- } = {}
338
- ): M {
339
- const { eventPrefix = 'state_transition', includePrevContext = false, includeArgs = false } = options;
340
-
341
- return createMiddleware(machine, {
342
- after: ({ transitionName, prevContext, nextContext, args }) => {
343
- const event = `${eventPrefix}.${transitionName}`;
344
- const data: any = { transition: transitionName };
345
- if (includePrevContext) data.from = prevContext;
346
- data.to = nextContext;
347
- if (includeArgs) data.args = args;
348
- track(event, data);
349
- }
350
- });
351
- }
288
+ /**
289
+ * Creates a simple logging middleware that logs all transitions.
290
+ *
291
+ * @template M - The machine type
292
+ * @param machine - The machine to add logging to
293
+ * @param options - Logging configuration options
294
+ * @returns A new machine with logging middleware
295
+ */
296
+ export function withLogging<M extends BaseMachine<any>>(
297
+ machine: M,
298
+ options: {
299
+ logger?: (message: string) => void;
300
+ includeArgs?: boolean;
301
+ includeContext?: boolean;
302
+ } = {}
303
+ ): M {
304
+ const { logger = console.log, includeArgs = false, includeContext = true } = options;
305
+
306
+ return createMiddleware(machine, {
307
+ before: ({ transitionName, args }) => {
308
+ const message = includeArgs ? `→ ${transitionName} [${args.join(', ')}]` : `→ ${transitionName}`;
309
+ logger(message);
310
+ },
311
+ after: ({ transitionName, nextContext }) => {
312
+ const contextStr = includeContext ? ` ${JSON.stringify(nextContext)}` : '';
313
+ logger(`✓ ${transitionName}${contextStr}`);
314
+ },
315
+ error: ({ transitionName, error }) => {
316
+ console.error(`[Machine] ${transitionName} failed:`, error);
317
+ }
318
+ });
319
+ }
352
320
 
353
- /**
354
- * Creates validation middleware that runs before transitions.
355
- *
356
- * @template M - The machine type
357
- * @param machine - The machine to validate
358
- * @param validator - Validation function
359
- * @returns A new machine with validation
360
- */
361
- export function withValidation<M extends BaseMachine<any>>(
362
- machine: M,
363
- validator: (ctx: MiddlewareContext<Context<M>>) => boolean | void
364
- ): M {
365
- return createMiddleware(machine, {
366
- before: (ctx) => {
367
- const result = validator(ctx);
368
- if (result === false) {
369
- throw new Error(`Validation failed for transition: ${ctx.transitionName}`);
321
+ /**
322
+ * Creates analytics tracking middleware.
323
+ *
324
+ * @template M - The machine type
325
+ * @param machine - The machine to track
326
+ * @param track - Analytics tracking function
327
+ * @param options - Configuration options
328
+ * @returns A new machine with analytics tracking
329
+ */
330
+ export function withAnalytics<M extends BaseMachine<any>>(
331
+ machine: M,
332
+ track: (event: string, data?: any) => void,
333
+ options: {
334
+ eventPrefix?: string;
335
+ includePrevContext?: boolean;
336
+ includeArgs?: boolean;
337
+ } = {}
338
+ ): M {
339
+ const { eventPrefix = 'state_transition', includePrevContext = false, includeArgs = false } = options;
340
+
341
+ return createMiddleware(machine, {
342
+ after: ({ transitionName, prevContext, nextContext, args }) => {
343
+ const event = `${eventPrefix}.${transitionName}`;
344
+ const data: any = { transition: transitionName };
345
+ if (includePrevContext) data.from = prevContext;
346
+ data.to = nextContext;
347
+ if (includeArgs) data.args = args;
348
+ track(event, data);
370
349
  }
371
- }
372
- });
373
- }
350
+ });
351
+ }
374
352
 
375
- /**
376
- * Creates permission-checking middleware.
377
- *
378
- * @template M - The machine type
379
- * @param machine - The machine to protect
380
- * @param checker - Permission checking function
381
- * @returns A new machine with permission checks
382
- */
383
- export function withPermissions<M extends BaseMachine<any>>(
384
- machine: M,
385
- checker: (ctx: MiddlewareContext<Context<M>>) => boolean
386
- ): M {
387
- return createMiddleware(machine, {
388
- before: (ctx) => {
389
- if (!checker(ctx)) {
390
- throw new Error(`Unauthorized transition: ${ctx.transitionName}`);
353
+ /**
354
+ * Creates validation middleware that runs before transitions.
355
+ *
356
+ * @template M - The machine type
357
+ * @param machine - The machine to validate
358
+ * @param validator - Validation function
359
+ * @returns A new machine with validation
360
+ */
361
+ export function withValidation<M extends BaseMachine<any>>(
362
+ machine: M,
363
+ validator: (ctx: MiddlewareContext<Context<M>>) => boolean | void
364
+ ): M {
365
+ return createMiddleware(machine, {
366
+ before: (ctx) => {
367
+ const result = validator(ctx);
368
+ if (result === false) {
369
+ throw new Error(`Validation failed for transition: ${ctx.transitionName}`);
370
+ }
391
371
  }
392
- }
393
- });
394
- }
372
+ });
373
+ }
395
374
 
396
- /**
397
- * Creates error reporting middleware.
398
- *
399
- * @template M - The machine type
400
- * @param machine - The machine to monitor
401
- * @param reporter - Error reporting function
402
- * @param options - Configuration options
403
- * @returns A new machine with error reporting
404
- */
405
- export function withErrorReporting<M extends BaseMachine<any>>(
406
- machine: M,
407
- reporter: (error: Error, ctx: any) => void,
408
- options: { includeArgs?: boolean } = {}
409
- ): M {
410
- const { includeArgs = false } = options;
411
-
412
- return createMiddleware(machine, {
413
- error: (errorCtx) => {
414
- // Format the context to match test expectations
415
- const formattedCtx = {
416
- transition: errorCtx.transitionName,
417
- context: errorCtx.context,
418
- ...(includeArgs && { args: errorCtx.args })
419
- };
420
- reporter(errorCtx.error, formattedCtx);
421
- }
422
- });
423
- }
375
+ /**
376
+ * Creates permission-checking middleware.
377
+ *
378
+ * @template M - The machine type
379
+ * @param machine - The machine to protect
380
+ * @param checker - Permission checking function
381
+ * @returns A new machine with permission checks
382
+ */
383
+ export function withPermissions<M extends BaseMachine<any>>(
384
+ machine: M,
385
+ checker: (ctx: MiddlewareContext<Context<M>>) => boolean
386
+ ): M {
387
+ return createMiddleware(machine, {
388
+ before: (ctx) => {
389
+ if (!checker(ctx)) {
390
+ throw new Error(`Unauthorized transition: ${ctx.transitionName}`);
391
+ }
392
+ }
393
+ });
394
+ }
424
395
 
425
- /**
426
- * Creates performance monitoring middleware.
427
- *
428
- * @template M - The machine type
429
- * @param machine - The machine to monitor
430
- * @param tracker - Performance tracking function
431
- * @returns A new machine with performance monitoring
432
- */
433
- export function withPerformanceMonitoring<M extends BaseMachine<any>>(
434
- machine: M,
435
- tracker: (metric: { transitionName: string; duration: number; context: Context<M> }) => void
436
- ): M {
437
- const startTimes = new Map<string, number>();
438
-
439
- return createMiddleware(machine, {
440
- before: (ctx) => {
441
- startTimes.set(ctx.transitionName, Date.now());
442
- },
443
- after: (result) => {
444
- const startTime = startTimes.get(result.transitionName);
445
- if (startTime) {
446
- const duration = Date.now() - startTime;
447
- startTimes.delete(result.transitionName);
448
- // For test compatibility, pass a single object as expected
449
- const testResult = {
450
- transitionName: result.transitionName,
451
- duration,
452
- context: result.nextContext || result.prevContext
396
+ /**
397
+ * Creates error reporting middleware.
398
+ *
399
+ * @template M - The machine type
400
+ * @param machine - The machine to monitor
401
+ * @param reporter - Error reporting function
402
+ * @param options - Configuration options
403
+ * @returns A new machine with error reporting
404
+ */
405
+ export function withErrorReporting<M extends BaseMachine<any>>(
406
+ machine: M,
407
+ reporter: (error: Error, ctx: any) => void,
408
+ options: { includeArgs?: boolean } = {}
409
+ ): M {
410
+ const { includeArgs = false } = options;
411
+
412
+ return createMiddleware(machine, {
413
+ error: (errorCtx) => {
414
+ // Format the context to match test expectations
415
+ const formattedCtx = {
416
+ transition: errorCtx.transitionName,
417
+ context: errorCtx.context,
418
+ ...(includeArgs && { args: errorCtx.args })
453
419
  };
454
- tracker(testResult);
420
+ reporter(errorCtx.error, formattedCtx);
455
421
  }
456
- }
457
- });
458
- }
459
-
460
- /**
461
- * Creates retry middleware for failed transitions.
462
- *
463
- * @template M - The machine type
464
- * @param machine - The machine to add retry logic to
465
- * @param options - Retry configuration
466
- * @returns A new machine with retry logic
467
- */
468
- export function withRetry<M extends BaseMachine<any>>(
469
- machine: M,
470
- options: {
471
- maxAttempts?: number;
472
- maxRetries?: number; // alias for maxAttempts
473
- shouldRetry?: (error: Error, attempt: number) => boolean;
474
- backoffMs?: number | ((attempt: number) => number);
475
- delay?: number | ((attempt: number) => number); // alias for backoffMs
476
- backoffMultiplier?: number; // multiplier for exponential backoff
477
- onRetry?: (error: Error, attempt: number) => void;
478
- } = {}
479
- ): M {
480
- const {
481
- maxAttempts = options.maxRetries ?? 3,
482
- shouldRetry = () => true,
483
- backoffMs = options.delay ?? 100,
484
- backoffMultiplier = 2,
485
- onRetry
486
- } = options;
487
-
488
- // Create a wrapped machine that adds retry logic
489
- const wrappedMachine: any = { ...machine };
422
+ });
423
+ }
490
424
 
491
- // Wrap each transition function with retry logic
492
- for (const prop in machine) {
493
- if (!Object.prototype.hasOwnProperty.call(machine, prop)) continue;
494
- const value = machine[prop];
495
- if (typeof value === 'function' && prop !== 'context') {
496
- wrappedMachine[prop] = async function (this: any, ...args: any[]) {
497
- let lastError: Error;
498
- let attempt = 0;
425
+ /**
426
+ * Creates performance monitoring middleware.
427
+ *
428
+ * @template M - The machine type
429
+ * @param machine - The machine to monitor
430
+ * @param tracker - Performance tracking function
431
+ * @returns A new machine with performance monitoring
432
+ */
433
+ export function withPerformanceMonitoring<M extends BaseMachine<any>>(
434
+ machine: M,
435
+ tracker: (metric: { transitionName: string; duration: number; context: Context<M> }) => void
436
+ ): M {
437
+ const startTimes = new Map<string, number>();
438
+
439
+ return createMiddleware(machine, {
440
+ before: (ctx) => {
441
+ startTimes.set(ctx.transitionName, Date.now());
442
+ },
443
+ after: (result) => {
444
+ const startTime = startTimes.get(result.transitionName);
445
+ if (startTime) {
446
+ const duration = Date.now() - startTime;
447
+ startTimes.delete(result.transitionName);
448
+ // For test compatibility, pass a single object as expected
449
+ const testResult = {
450
+ transitionName: result.transitionName,
451
+ duration,
452
+ context: result.nextContext || result.prevContext
453
+ };
454
+ tracker(testResult);
455
+ }
456
+ }
457
+ });
458
+ }
499
459
 
500
- while (attempt < maxAttempts) {
501
- try {
502
- return await value.apply(this, args);
503
- } catch (error) {
504
- lastError = error as Error;
505
- attempt++;
506
-
507
- if (attempt < maxAttempts && shouldRetry(lastError, attempt)) {
508
- onRetry?.(lastError, attempt);
509
- const baseDelay = typeof backoffMs === 'function' ? backoffMs(attempt) : backoffMs;
510
- const delay = baseDelay * Math.pow(backoffMultiplier, attempt - 1);
511
- await new Promise(resolve => setTimeout(resolve, delay));
512
- } else {
513
- throw lastError;
460
+ /**
461
+ * Creates retry middleware for failed transitions.
462
+ *
463
+ * @template M - The machine type
464
+ * @param machine - The machine to add retry logic to
465
+ * @param options - Retry configuration
466
+ * @returns A new machine with retry logic
467
+ */
468
+ export function withRetry<M extends BaseMachine<any>>(
469
+ machine: M,
470
+ options: {
471
+ maxAttempts?: number;
472
+ maxRetries?: number; // alias for maxAttempts
473
+ shouldRetry?: (error: Error, attempt: number) => boolean;
474
+ backoffMs?: number | ((attempt: number) => number);
475
+ delay?: number | ((attempt: number) => number); // alias for backoffMs
476
+ backoffMultiplier?: number; // multiplier for exponential backoff
477
+ onRetry?: (error: Error, attempt: number) => void;
478
+ } = {}
479
+ ): M {
480
+ const {
481
+ maxAttempts = options.maxRetries ?? 3,
482
+ shouldRetry = () => true,
483
+ backoffMs = options.delay ?? 100,
484
+ backoffMultiplier = 2,
485
+ onRetry
486
+ } = options;
487
+
488
+ // Create a wrapped machine that adds retry logic
489
+ const wrappedMachine: any = { ...machine };
490
+
491
+ // Wrap each transition function with retry logic
492
+ for (const prop in machine) {
493
+ if (!Object.prototype.hasOwnProperty.call(machine, prop)) continue;
494
+ const value = machine[prop];
495
+ if (typeof value === 'function' && prop !== 'context') {
496
+ wrappedMachine[prop] = async function (this: any, ...args: any[]) {
497
+ let lastError: Error;
498
+ let attempt = 0;
499
+
500
+ while (attempt < maxAttempts) {
501
+ try {
502
+ return await value.apply(this, args);
503
+ } catch (error) {
504
+ lastError = error as Error;
505
+ attempt++;
506
+
507
+ if (attempt < maxAttempts && shouldRetry(lastError, attempt)) {
508
+ onRetry?.(lastError, attempt);
509
+ const baseDelay = typeof backoffMs === 'function' ? backoffMs(attempt) : backoffMs;
510
+ const delay = baseDelay * Math.pow(backoffMultiplier, attempt - 1);
511
+ await new Promise(resolve => setTimeout(resolve, delay));
512
+ } else {
513
+ throw lastError;
514
+ }
514
515
  }
515
516
  }
516
- }
517
517
 
518
- throw lastError!;
518
+ throw lastError!;
519
519
  };
520
520
 
521
521
 
522
- }
523
- }
522
+ }
523
+ }
524
524
 
525
- return wrappedMachine;
526
- }
525
+ return wrappedMachine;
526
+ }
527
527
 
528
- /**
529
- * Creates custom middleware from hooks.
530
- *
531
- * @template M - The machine type
532
- * @param hooks - Middleware hooks
533
- * @param options - Middleware options
534
- * @returns A middleware function
535
- */
536
- export function createCustomMiddleware<M extends BaseMachine<any>>(
537
- hooks: MiddlewareHooks<Context<M>>,
538
- options?: MiddlewareOptions
539
- ): (machine: M) => M {
540
- return (machine: M) => createMiddleware(machine, hooks, options);
541
- }
528
+ /**
529
+ * Creates custom middleware from hooks.
530
+ *
531
+ * @template M - The machine type
532
+ * @param hooks - Middleware hooks
533
+ * @param options - Middleware options
534
+ * @returns A middleware function
535
+ */
536
+ export function createCustomMiddleware<M extends BaseMachine<any>>(
537
+ hooks: MiddlewareHooks<Context<M>>,
538
+ options?: MiddlewareOptions
539
+ ): (machine: M) => M {
540
+ return (machine: M) => createMiddleware(machine, hooks, options);
541
+ }
542
542
 
543
- // =============================================================================
544
- // SECTION: HISTORY TRACKING
545
- // =============================================================================
543
+ // =============================================================================
544
+ // SECTION: HISTORY TRACKING
545
+ // =============================================================================
546
546
 
547
- /**
548
- * A single history entry recording a transition.
549
- */
550
- export interface HistoryEntry {
551
- /** Unique ID for this history entry */
552
- id: string;
553
- /** Name of the transition that was called */
554
- transitionName: string;
555
- /** Arguments passed to the transition */
556
- args: any[];
557
- /** Timestamp when the transition occurred */
558
- timestamp: number;
559
- /** Optional serialized version of args for persistence */
560
- serializedArgs?: string;
561
- }
547
+ /**
548
+ * A single history entry recording a transition.
549
+ */
550
+ export interface HistoryEntry {
551
+ /** Unique ID for this history entry */
552
+ id: string;
553
+ /** Name of the transition that was called */
554
+ transitionName: string;
555
+ /** Arguments passed to the transition */
556
+ args: any[];
557
+ /** Timestamp when the transition occurred */
558
+ timestamp: number;
559
+ /** Optional serialized version of args for persistence */
560
+ serializedArgs?: string;
561
+ }
562
562
 
563
- /**
564
- * Serializer interface for converting context/args to/from strings.
565
- */
566
- export interface Serializer<T = any> {
567
- serialize: (value: T) => string;
568
- deserialize: (str: string) => T;
569
- }
563
+ /**
564
+ * Serializer interface for converting context/args to/from strings.
565
+ */
566
+ export interface Serializer<T = any> {
567
+ serialize: (value: T) => string;
568
+ deserialize: (str: string) => T;
569
+ }
570
570
 
571
- /**
572
- * Creates a machine with history tracking capabilities.
573
- * Records all transitions that occur, allowing you to see the sequence of state changes.
574
- *
575
- * @template M - The machine type
576
- * @param machine - The machine to track
577
- * @param options - Configuration options
578
- * @returns A new machine with history tracking
579
- *
580
- * @example
581
- * ```typescript
582
- * const tracked = withHistory(counter, { maxSize: 50 });
583
- * tracked.increment();
584
- * console.log(tracked.history); // [{ id: "entry-1", transitionName: "increment", ... }]
585
- * ```
586
- */
587
- export function withHistory<M extends BaseMachine<any>>(
588
- machine: M,
589
- options: {
590
- /** Maximum number of history entries to keep (default: unlimited) */
591
- maxSize?: number;
592
- /** Optional serializer for transition arguments */
593
- serializer?: Serializer<any[]>;
594
- /** Callback when a transition occurs */
595
- onEntry?: (entry: HistoryEntry) => void;
596
- } = {}
597
- ): M & { history: HistoryEntry[]; clearHistory: () => void } {
598
- const { maxSize, serializer, onEntry } = options;
599
- const history: HistoryEntry[] = [];
600
- let entryId = 0;
601
-
602
- const instrumentedMachine = createMiddleware(machine, {
603
- before: ({ transitionName, args }) => {
604
- const entry: HistoryEntry = {
605
- id: `entry-${entryId++}`,
606
- transitionName,
607
- args: [...args],
608
- timestamp: Date.now()
609
- };
610
-
611
- if (serializer) {
612
- try {
613
- entry.serializedArgs = serializer.serialize(args);
614
- } catch (err) {
615
- console.error('Failed to serialize history args:', err);
571
+ /**
572
+ * Creates a machine with history tracking capabilities.
573
+ * Records all transitions that occur, allowing you to see the sequence of state changes.
574
+ *
575
+ * @template M - The machine type
576
+ * @param machine - The machine to track
577
+ * @param options - Configuration options
578
+ * @returns A new machine with history tracking
579
+ *
580
+ * @example
581
+ * ```typescript
582
+ * const tracked = withHistory(counter, { maxSize: 50 });
583
+ * tracked.increment();
584
+ * console.log(tracked.history); // [{ id: "entry-1", transitionName: "increment", ... }]
585
+ * ```
586
+ */
587
+ export function withHistory<M extends BaseMachine<any>>(
588
+ machine: M,
589
+ options: {
590
+ /** Maximum number of history entries to keep (default: unlimited) */
591
+ maxSize?: number;
592
+ /** Optional serializer for transition arguments */
593
+ serializer?: Serializer<any[]>;
594
+ /** Callback when a transition occurs */
595
+ onEntry?: (entry: HistoryEntry) => void;
596
+ } = {}
597
+ ): M & { history: HistoryEntry[]; clearHistory: () => void } {
598
+ const { maxSize, serializer, onEntry } = options;
599
+ const history: HistoryEntry[] = [];
600
+ let entryId = 0;
601
+
602
+ const instrumentedMachine = createMiddleware(machine, {
603
+ before: ({ transitionName, args }) => {
604
+ const entry: HistoryEntry = {
605
+ id: `entry-${entryId++}`,
606
+ transitionName,
607
+ args: [...args],
608
+ timestamp: Date.now()
609
+ };
610
+
611
+ if (serializer) {
612
+ try {
613
+ entry.serializedArgs = serializer.serialize(args);
614
+ } catch (err) {
615
+ console.error('Failed to serialize history args:', err);
616
+ }
616
617
  }
617
- }
618
618
 
619
- history.push(entry);
619
+ history.push(entry);
620
620
 
621
- // Enforce max size
622
- if (maxSize && history.length > maxSize) {
623
- history.shift();
624
- }
621
+ // Enforce max size
622
+ if (maxSize && history.length > maxSize) {
623
+ history.shift();
624
+ }
625
625
 
626
- onEntry?.(entry);
627
- }
628
- });
626
+ onEntry?.(entry);
627
+ }
628
+ });
629
629
 
630
- // Attach history properties to the machine
631
- return Object.assign(instrumentedMachine, {
632
- history,
633
- clearHistory: () => { history.length = 0; entryId = 0; }
634
- });
635
- }
630
+ // Attach history properties to the machine
631
+ return Object.assign(instrumentedMachine, {
632
+ history,
633
+ clearHistory: () => { history.length = 0; entryId = 0; }
634
+ });
635
+ }
636
636
 
637
- // =============================================================================
638
- // SECTION: SNAPSHOT TRACKING
639
- // =============================================================================
637
+ // =============================================================================
638
+ // SECTION: SNAPSHOT TRACKING
639
+ // =============================================================================
640
640
 
641
- /**
642
- * A snapshot of machine context before and after a transition.
643
- */
644
- export interface ContextSnapshot<C extends object> {
645
- /** Unique ID for this snapshot */
646
- id: string;
647
- /** Name of the transition that caused this snapshot */
648
- transitionName: string;
649
- /** Context before the transition */
650
- before: C;
651
- /** Context after the transition */
652
- after: C;
653
- /** Timestamp of the snapshot */
654
- timestamp: number;
655
- /** Optional serialized versions of contexts */
656
- serializedBefore?: string;
657
- serializedAfter?: string;
658
- /** Optional diff information */
659
- diff?: any;
660
- }
641
+ /**
642
+ * A snapshot of machine context before and after a transition.
643
+ */
644
+ export interface ContextSnapshot<C extends object> {
645
+ /** Unique ID for this snapshot */
646
+ id: string;
647
+ /** Name of the transition that caused this snapshot */
648
+ transitionName: string;
649
+ /** Context before the transition */
650
+ before: C;
651
+ /** Context after the transition */
652
+ after: C;
653
+ /** Timestamp of the snapshot */
654
+ timestamp: number;
655
+ /** Optional serialized versions of contexts */
656
+ serializedBefore?: string;
657
+ serializedAfter?: string;
658
+ /** Optional diff information */
659
+ diff?: any;
660
+ }
661
661
 
662
- /**
663
- * Creates a machine with snapshot tracking capabilities.
664
- * Records context state before and after each transition for debugging and inspection.
665
- *
666
- * @template M - The machine type
667
- * @param machine - The machine to track
668
- * @param options - Configuration options
669
- * @returns A new machine with snapshot tracking
670
- *
671
- * @example
672
- * ```typescript
673
- * const tracked = withSnapshot(counter, {
674
- * maxSize: 50,
675
- * serializer: {
676
- * serialize: (ctx) => JSON.stringify(ctx),
677
- * deserialize: (str) => JSON.parse(str)
678
- * }
679
- * });
680
- *
681
- * tracked.increment();
682
- * console.log(tracked.snapshots); // [{ before: { count: 0 }, after: { count: 1 }, ... }]
683
- * ```
684
- */
685
- export function withSnapshot<M extends BaseMachine<any>>(
686
- machine: M,
687
- options: {
688
- /** Maximum number of snapshots to keep (default: unlimited) */
689
- maxSize?: number;
690
- /** Optional serializer for context */
691
- serializer?: Serializer<Context<M>>;
692
- /** Custom function to capture additional snapshot data */
693
- captureSnapshot?: (before: Context<M>, after: Context<M>) => any;
694
- /** Only capture snapshots where context actually changed */
695
- onlyOnChange?: boolean;
696
- } = {}
697
- ): M & {
698
- snapshots: ContextSnapshot<Context<M>>[];
699
- clearSnapshots: () => void;
700
- restoreSnapshot: (snapshot: ContextSnapshot<Context<M>>['before']) => M;
701
- } {
702
- const {
703
- maxSize,
704
- serializer,
705
- captureSnapshot,
706
- onlyOnChange = false
707
- } = options;
708
-
709
- const snapshots: ContextSnapshot<Context<M>>[] = [];
710
- let snapshotId = 0;
711
-
712
- const instrumentedMachine = createMiddleware(machine, {
713
- after: ({ transitionName, prevContext, nextContext }) => {
714
- // Skip if only capturing on change and context didn't change
715
- if (onlyOnChange && JSON.stringify(prevContext) === JSON.stringify(nextContext)) {
716
- return;
717
- }
662
+ /**
663
+ * Creates a machine with snapshot tracking capabilities.
664
+ * Records context state before and after each transition for debugging and inspection.
665
+ *
666
+ * @template M - The machine type
667
+ * @param machine - The machine to track
668
+ * @param options - Configuration options
669
+ * @returns A new machine with snapshot tracking
670
+ *
671
+ * @example
672
+ * ```typescript
673
+ * const tracked = withSnapshot(counter, {
674
+ * maxSize: 50,
675
+ * serializer: {
676
+ * serialize: (ctx) => JSON.stringify(ctx),
677
+ * deserialize: (str) => JSON.parse(str)
678
+ * }
679
+ * });
680
+ *
681
+ * tracked.increment();
682
+ * console.log(tracked.snapshots); // [{ before: { count: 0 }, after: { count: 1 }, ... }]
683
+ * ```
684
+ */
685
+ export function withSnapshot<M extends BaseMachine<any>>(
686
+ machine: M,
687
+ options: {
688
+ /** Maximum number of snapshots to keep (default: unlimited) */
689
+ maxSize?: number;
690
+ /** Optional serializer for context */
691
+ serializer?: Serializer<Context<M>>;
692
+ /** Custom function to capture additional snapshot data */
693
+ captureSnapshot?: (before: Context<M>, after: Context<M>) => any;
694
+ /** Only capture snapshots where context actually changed */
695
+ onlyOnChange?: boolean;
696
+ } = {}
697
+ ): M & {
698
+ snapshots: ContextSnapshot<Context<M>>[];
699
+ clearSnapshots: () => void;
700
+ restoreSnapshot: (snapshot: ContextSnapshot<Context<M>>['before']) => M;
701
+ } {
702
+ const {
703
+ maxSize,
704
+ serializer,
705
+ captureSnapshot,
706
+ onlyOnChange = false
707
+ } = options;
708
+
709
+ const snapshots: ContextSnapshot<Context<M>>[] = [];
710
+ let snapshotId = 0;
711
+
712
+ const instrumentedMachine = createMiddleware(machine, {
713
+ after: ({ transitionName, prevContext, nextContext }) => {
714
+ // Skip if only capturing on change and context didn't change
715
+ if (onlyOnChange && JSON.stringify(prevContext) === JSON.stringify(nextContext)) {
716
+ return;
717
+ }
718
718
 
719
- const snapshot: ContextSnapshot<Context<M>> = {
720
- id: `snapshot-${snapshotId++}`,
721
- transitionName,
722
- before: { ...prevContext },
723
- after: { ...nextContext },
724
- timestamp: Date.now()
725
- };
719
+ const snapshot: ContextSnapshot<Context<M>> = {
720
+ id: `snapshot-${snapshotId++}`,
721
+ transitionName,
722
+ before: { ...prevContext },
723
+ after: { ...nextContext },
724
+ timestamp: Date.now()
725
+ };
726
726
 
727
- // Serialize contexts if serializer provided
728
- if (serializer) {
729
- try {
730
- snapshot.serializedBefore = serializer.serialize(prevContext);
731
- snapshot.serializedAfter = serializer.serialize(nextContext);
732
- } catch (err) {
733
- console.error('Failed to serialize snapshot:', err);
727
+ // Serialize contexts if serializer provided
728
+ if (serializer) {
729
+ try {
730
+ snapshot.serializedBefore = serializer.serialize(prevContext);
731
+ snapshot.serializedAfter = serializer.serialize(nextContext);
732
+ } catch (err) {
733
+ console.error('Failed to serialize snapshot:', err);
734
+ }
734
735
  }
735
- }
736
736
 
737
- // Capture custom snapshot data
738
- if (captureSnapshot) {
739
- try {
740
- snapshot.diff = captureSnapshot(prevContext, nextContext);
741
- } catch (err) {
742
- console.error('Failed to capture snapshot:', err);
737
+ // Capture custom snapshot data
738
+ if (captureSnapshot) {
739
+ try {
740
+ snapshot.diff = captureSnapshot(prevContext, nextContext);
741
+ } catch (err) {
742
+ console.error('Failed to capture snapshot:', err);
743
+ }
743
744
  }
744
- }
745
745
 
746
- snapshots.push(snapshot);
746
+ snapshots.push(snapshot);
747
747
 
748
- // Enforce max size
749
- if (maxSize && snapshots.length > maxSize) {
750
- snapshots.shift();
748
+ // Enforce max size
749
+ if (maxSize && snapshots.length > maxSize) {
750
+ snapshots.shift();
751
+ }
751
752
  }
752
- }
753
- });
754
-
755
- // Helper to restore machine to a previous state
756
- const restoreSnapshot = (context: Context<M>): M => {
757
- // Find the machine's transition functions (excluding context and snapshot properties)
758
- const transitions = Object.fromEntries(
759
- Object.entries(machine).filter(([key]) =>
760
- key !== 'context' &&
761
- key !== 'snapshots' &&
762
- key !== 'clearSnapshots' &&
763
- key !== 'restoreSnapshot' &&
764
- typeof machine[key as keyof M] === 'function'
765
- )
766
- );
753
+ });
767
754
 
768
- return Object.assign({ context }, transitions) as M;
769
- };
755
+ // Helper to restore machine to a previous state
756
+ const restoreSnapshot = (context: Context<M>): M => {
757
+ // Find the machine's transition functions (excluding context and snapshot properties)
758
+ const transitions = Object.fromEntries(
759
+ Object.entries(machine).filter(([key]) =>
760
+ key !== 'context' &&
761
+ key !== 'snapshots' &&
762
+ key !== 'clearSnapshots' &&
763
+ key !== 'restoreSnapshot' &&
764
+ typeof machine[key as keyof M] === 'function'
765
+ )
766
+ );
770
767
 
771
- // Attach snapshot properties to the machine
772
- return Object.assign(instrumentedMachine, {
773
- snapshots,
774
- clearSnapshots: () => { snapshots.length = 0; snapshotId = 0; },
775
- restoreSnapshot
776
- });
777
- }
768
+ return Object.assign({ context }, transitions) as M;
769
+ };
778
770
 
779
- // =============================================================================
780
- // SECTION: TIME TRAVEL DEBUGGING
781
- // =============================================================================
782
-
783
- /**
784
- * A machine enhanced with history tracking capabilities.
785
- */
786
- export type WithHistory<M extends BaseMachine<any>> = M & {
787
- /** History of all transitions */
788
- history: HistoryEntry[];
789
- /** Clear all history */
790
- clearHistory: () => void;
791
- };
771
+ // Attach snapshot properties to the machine
772
+ return Object.assign(instrumentedMachine, {
773
+ snapshots,
774
+ clearSnapshots: () => { snapshots.length = 0; snapshotId = 0; },
775
+ restoreSnapshot
776
+ });
777
+ }
792
778
 
793
- /**
794
- * A machine enhanced with snapshot tracking capabilities.
795
- */
796
- export type WithSnapshot<M extends BaseMachine<any>> = M & {
797
- /** Snapshots of context before/after each transition */
798
- snapshots: ContextSnapshot<Context<M>>[];
799
- /** Clear all snapshots */
800
- clearSnapshots: () => void;
801
- /** Restore machine to a previous context state */
802
- restoreSnapshot: (context: Context<M>) => M;
803
- };
779
+ // =============================================================================
780
+ // SECTION: TIME TRAVEL DEBUGGING
781
+ // =============================================================================
804
782
 
805
- /**
806
- * A machine enhanced with time travel capabilities.
807
- */
808
- export type WithTimeTravel<M extends BaseMachine<any>> = M & {
809
- /** History of all transitions */
810
- history: HistoryEntry[];
811
- /** Snapshots of context before/after each transition */
812
- snapshots: ContextSnapshot<Context<M>>[];
813
- /** Clear all history and snapshots */
814
- clearTimeTravel: () => void;
815
- /** Clear just the history */
816
- clearHistory: () => void;
817
- /** Clear just the snapshots */
818
- clearSnapshots: () => void;
819
- /** Restore machine to a previous context state */
820
- restoreSnapshot: (context: Context<M>) => M;
821
- /** Replay transitions from a specific point in history */
822
- replayFrom: (startIndex: number) => M;
823
- };
783
+ /**
784
+ * A machine enhanced with history tracking capabilities.
785
+ */
786
+ export type WithHistory<M extends BaseMachine<any>> = M & {
787
+ /** History of all transitions */
788
+ history: HistoryEntry[];
789
+ /** Clear all history */
790
+ clearHistory: () => void;
791
+ };
824
792
 
825
- /**
826
- * Creates a machine with full time travel debugging capabilities.
827
- * Combines history tracking, snapshots, and replay functionality.
828
- *
829
- * @template M - The machine type
830
- * @param machine - The machine to enhance
831
- * @param options - Configuration options
832
- * @returns A machine with time travel capabilities
833
- *
834
- * @example
835
- * ```typescript
836
- * const debugMachine = withTimeTravel(counter);
837
- *
838
- * // Make some transitions
839
- * debugMachine.increment();
840
- * debugMachine.increment();
841
- * debugMachine.decrement();
842
- *
843
- * // Time travel to previous states
844
- * const previousState = debugMachine.replayFrom(0); // Replay from start
845
- * const undoLast = debugMachine.restoreSnapshot(debugMachine.snapshots[1].before);
846
- *
847
- * // Inspect history
848
- * console.log(debugMachine.history);
849
- * console.log(debugMachine.snapshots);
850
- * ```
851
- */
852
- export function withTimeTravel<M extends BaseMachine<any>>(
853
- machine: M,
854
- options: {
855
- /** Maximum number of history entries/snapshots to keep */
856
- maxSize?: number;
857
- /** Optional serializer for persistence */
858
- serializer?: Serializer;
859
- /** Callback when history/snapshot events occur */
860
- onRecord?: (type: 'history' | 'snapshot', data: any) => void;
861
- } = {}
862
- ): WithTimeTravel<M> {
863
- const { maxSize, serializer, onRecord } = options;
864
-
865
- // Create separate history and snapshot tracking
866
- const history: HistoryEntry[] = [];
867
- const snapshots: ContextSnapshot<Context<M>>[] = [];
868
- let historyId = 0;
869
- let snapshotId = 0;
870
-
871
- // Create middleware that handles both history and snapshots
872
- const instrumentedMachine = createMiddleware(machine, {
873
- before: ({ transitionName, args }) => {
874
- const entry: HistoryEntry = {
875
- id: `entry-${historyId++}`,
876
- transitionName,
877
- args: [...args],
878
- timestamp: Date.now()
879
- };
880
-
881
- if (serializer) {
882
- try {
883
- entry.serializedArgs = serializer.serialize(args);
884
- } catch (err) {
885
- console.error('Failed to serialize history args:', err);
886
- }
887
- }
793
+ /**
794
+ * A machine enhanced with snapshot tracking capabilities.
795
+ */
796
+ export type WithSnapshot<M extends BaseMachine<any>> = M & {
797
+ /** Snapshots of context before/after each transition */
798
+ snapshots: ContextSnapshot<Context<M>>[];
799
+ /** Clear all snapshots */
800
+ clearSnapshots: () => void;
801
+ /** Restore machine to a previous context state */
802
+ restoreSnapshot: (context: Context<M>) => M;
803
+ };
888
804
 
889
- history.push(entry);
805
+ /**
806
+ * A machine enhanced with time travel capabilities.
807
+ */
808
+ export type WithTimeTravel<M extends BaseMachine<any>> = M & {
809
+ /** History of all transitions */
810
+ history: HistoryEntry[];
811
+ /** Snapshots of context before/after each transition */
812
+ snapshots: ContextSnapshot<Context<M>>[];
813
+ /** Clear all history and snapshots */
814
+ clearTimeTravel: () => void;
815
+ /** Clear just the history */
816
+ clearHistory: () => void;
817
+ /** Clear just the snapshots */
818
+ clearSnapshots: () => void;
819
+ /** Restore machine to a previous context state */
820
+ restoreSnapshot: (context: Context<M>) => M;
821
+ /** Replay transitions from a specific point in history */
822
+ replayFrom: (startIndex: number) => M;
823
+ };
890
824
 
891
- // Enforce max size
892
- if (maxSize && history.length > maxSize) {
893
- history.shift();
894
- }
825
+ /**
826
+ * Creates a machine with full time travel debugging capabilities.
827
+ * Combines history tracking, snapshots, and replay functionality.
828
+ *
829
+ * @template M - The machine type
830
+ * @param machine - The machine to enhance
831
+ * @param options - Configuration options
832
+ * @returns A machine with time travel capabilities
833
+ *
834
+ * @example
835
+ * ```typescript
836
+ * const debugMachine = withTimeTravel(counter);
837
+ *
838
+ * // Make some transitions
839
+ * debugMachine.increment();
840
+ * debugMachine.increment();
841
+ * debugMachine.decrement();
842
+ *
843
+ * // Time travel to previous states
844
+ * const previousState = debugMachine.replayFrom(0); // Replay from start
845
+ * const undoLast = debugMachine.restoreSnapshot(debugMachine.snapshots[1].before);
846
+ *
847
+ * // Inspect history
848
+ * console.log(debugMachine.history);
849
+ * console.log(debugMachine.snapshots);
850
+ * ```
851
+ */
852
+ export function withTimeTravel<M extends BaseMachine<any>>(
853
+ machine: M,
854
+ options: {
855
+ /** Maximum number of history entries/snapshots to keep */
856
+ maxSize?: number;
857
+ /** Optional serializer for persistence */
858
+ serializer?: Serializer;
859
+ /** Callback when history/snapshot events occur */
860
+ onRecord?: (type: 'history' | 'snapshot', data: any) => void;
861
+ } = {}
862
+ ): WithTimeTravel<M> {
863
+ const { maxSize, serializer, onRecord } = options;
864
+
865
+ // Create separate history and snapshot tracking
866
+ const history: HistoryEntry[] = [];
867
+ const snapshots: ContextSnapshot<Context<M>>[] = [];
868
+ let historyId = 0;
869
+ let snapshotId = 0;
870
+
871
+ // Create middleware that handles both history and snapshots
872
+ const instrumentedMachine = createMiddleware(machine, {
873
+ before: ({ transitionName, args }) => {
874
+ const entry: HistoryEntry = {
875
+ id: `entry-${historyId++}`,
876
+ transitionName,
877
+ args: [...args],
878
+ timestamp: Date.now()
879
+ };
895
880
 
896
- onRecord?.('history', entry);
897
- },
898
- after: ({ transitionName, prevContext, nextContext }) => {
899
- const snapshot: ContextSnapshot<Context<M>> = {
900
- id: `snapshot-${snapshotId++}`,
901
- transitionName,
902
- before: { ...prevContext },
903
- after: { ...nextContext },
904
- timestamp: Date.now()
905
- };
906
-
907
- // Serialize contexts if serializer provided
908
- if (serializer) {
909
- try {
910
- snapshot.serializedBefore = serializer.serialize(prevContext);
911
- snapshot.serializedAfter = serializer.serialize(nextContext);
912
- } catch (err) {
913
- console.error('Failed to serialize snapshot:', err);
881
+ if (serializer) {
882
+ try {
883
+ entry.serializedArgs = serializer.serialize(args);
884
+ } catch (err) {
885
+ console.error('Failed to serialize history args:', err);
886
+ }
914
887
  }
915
- }
916
888
 
917
- snapshots.push(snapshot);
889
+ history.push(entry);
918
890
 
919
- // Enforce max size
920
- if (maxSize && snapshots.length > maxSize) {
921
- snapshots.shift();
922
- }
891
+ // Enforce max size
892
+ if (maxSize && history.length > maxSize) {
893
+ history.shift();
894
+ }
923
895
 
924
- onRecord?.('snapshot', snapshot);
925
- }
926
- });
927
-
928
- // Helper to restore machine to a previous state
929
- const restoreSnapshot = (context: Context<M>): M => {
930
- // Find the machine's transition functions (excluding context and snapshot properties)
931
- const transitions = Object.fromEntries(
932
- Object.entries(machine).filter(([key]) =>
933
- key !== 'context' &&
934
- key !== 'history' &&
935
- key !== 'snapshots' &&
936
- key !== 'clearHistory' &&
937
- key !== 'clearSnapshots' &&
938
- key !== 'restoreSnapshot' &&
939
- key !== 'clearTimeTravel' &&
940
- key !== 'replayFrom' &&
941
- typeof machine[key as keyof M] === 'function'
942
- )
943
- );
896
+ onRecord?.('history', entry);
897
+ },
898
+ after: ({ transitionName, prevContext, nextContext }) => {
899
+ const snapshot: ContextSnapshot<Context<M>> = {
900
+ id: `snapshot-${snapshotId++}`,
901
+ transitionName,
902
+ before: { ...prevContext },
903
+ after: { ...nextContext },
904
+ timestamp: Date.now()
905
+ };
944
906
 
945
- return Object.assign({ context }, transitions) as M;
946
- };
907
+ // Serialize contexts if serializer provided
908
+ if (serializer) {
909
+ try {
910
+ snapshot.serializedBefore = serializer.serialize(prevContext);
911
+ snapshot.serializedAfter = serializer.serialize(nextContext);
912
+ } catch (err) {
913
+ console.error('Failed to serialize snapshot:', err);
914
+ }
915
+ }
947
916
 
948
- // Create replay functionality
949
- const replayFrom = (startIndex: number): M => {
950
- if (startIndex < 0 || startIndex >= history.length) {
951
- throw new Error(`Invalid replay start index: ${startIndex}`);
952
- }
917
+ snapshots.push(snapshot);
953
918
 
954
- // Start from the context at the specified history index
955
- let currentContext = snapshots[startIndex]?.before;
956
- if (!currentContext) {
957
- throw new Error(`No snapshot available for index ${startIndex}`);
958
- }
919
+ // Enforce max size
920
+ if (maxSize && snapshots.length > maxSize) {
921
+ snapshots.shift();
922
+ }
959
923
 
960
- // Get all transitions from start index to end
961
- const transitionsToReplay = history.slice(startIndex);
924
+ onRecord?.('snapshot', snapshot);
925
+ }
926
+ });
962
927
 
963
- // Create a fresh machine instance
964
- const freshMachine = Object.assign(
965
- { context: currentContext },
966
- Object.fromEntries(
928
+ // Helper to restore machine to a previous state
929
+ const restoreSnapshot = (context: Context<M>): M => {
930
+ // Find the machine's transition functions (excluding context and snapshot properties)
931
+ const transitions = Object.fromEntries(
967
932
  Object.entries(machine).filter(([key]) =>
968
933
  key !== 'context' &&
934
+ key !== 'history' &&
935
+ key !== 'snapshots' &&
936
+ key !== 'clearHistory' &&
937
+ key !== 'clearSnapshots' &&
938
+ key !== 'restoreSnapshot' &&
939
+ key !== 'clearTimeTravel' &&
940
+ key !== 'replayFrom' &&
969
941
  typeof machine[key as keyof M] === 'function'
970
942
  )
971
- )
972
- ) as M;
973
-
974
- // Replay each transition
975
- let replayedMachine = freshMachine;
976
- for (const entry of transitionsToReplay) {
977
- const transitionFn = replayedMachine[entry.transitionName as keyof M] as Function;
978
- if (transitionFn) {
979
- replayedMachine = transitionFn.apply(replayedMachine, entry.args);
980
- }
981
- }
943
+ );
982
944
 
983
- return replayedMachine;
984
- };
945
+ return Object.assign({ context }, transitions) as M;
946
+ };
985
947
 
986
- // Return machine with all time travel capabilities
987
- return Object.assign(instrumentedMachine, {
988
- history,
989
- snapshots,
990
- clearHistory: () => { history.length = 0; historyId = 0; },
991
- clearSnapshots: () => { snapshots.length = 0; snapshotId = 0; },
992
- clearTimeTravel: () => {
993
- history.length = 0;
994
- snapshots.length = 0;
995
- historyId = 0;
996
- snapshotId = 0;
997
- },
998
- restoreSnapshot,
999
- replayFrom
1000
- }) as WithTimeTravel<M>;
1001
- }
1002
-
1003
- // =============================================================================
1004
- // SECTION: MIDDLEWARE COMPOSITION
1005
- // =============================================================================
1006
-
1007
- /**
1008
- * Compose multiple middleware functions into a single middleware stack.
1009
- * Middleware is applied left-to-right (first middleware wraps outermost).
1010
- *
1011
- * @template M - The machine type
1012
- * @param machine - The base machine
1013
- * @param middlewares - Array of middleware functions
1014
- * @returns A new machine with all middleware applied
1015
- */
1016
- export function compose<M extends BaseMachine<any>>(
1017
- machine: M,
1018
- ...middlewares: Array<(m: M) => M>
1019
- ): M {
1020
- return middlewares.reduce((acc, middleware) => middleware(acc), machine);
1021
- }
1022
-
1023
- /**
1024
- * Type-safe middleware composition with perfect inference.
1025
- * Composes multiple middlewares into a single transformation chain.
1026
- *
1027
- * @template M - The input machine type
1028
- * @template Ms - Array of middleware functions
1029
- * @param machine - The machine to enhance
1030
- * @param middlewares - Middleware functions to apply in order
1031
- * @returns The machine with all middlewares applied, with precise type inference
1032
- */
1033
- export function composeTyped<
1034
- M extends BaseMachine<any>,
1035
- Ms extends readonly MiddlewareFn<any, any>[]
1036
- >(
1037
- machine: M,
1038
- ...middlewares: Ms
1039
- ): ComposeResult<M, Ms> {
1040
- return middlewares.reduce((acc, middleware) => middleware(acc), machine) as ComposeResult<M, Ms>;
1041
- }
1042
-
1043
- // =============================================================================
1044
- // SECTION: TYPE-LEVEL COMPOSITION
1045
- // =============================================================================
1046
-
1047
- /**
1048
- * Type-level utility for composing middleware return types.
1049
- * This enables perfect TypeScript inference when chaining middlewares.
1050
- */
1051
- export type ComposeResult<
1052
- M extends BaseMachine<any>,
1053
- Ms extends readonly MiddlewareFn<any, any>[]
1054
- > = Ms extends readonly [infer First, ...infer Rest]
1055
- ? First extends MiddlewareFn<any, infer R>
1056
- ? Rest extends readonly MiddlewareFn<any, any>[]
1057
- ? ComposeResult<R, Rest>
1058
- : R
1059
- : M
1060
- : M;
1061
-
1062
- /**
1063
- * A middleware function that transforms a machine.
1064
- * @template M - The input machine type
1065
- * @template R - The output machine type (usually extends M)
1066
- */
1067
- export type MiddlewareFn<M extends BaseMachine<any>, R extends BaseMachine<any> = M> = (machine: M) => R;
1068
-
1069
- /**
1070
- * A conditional middleware that may or may not be applied based on a predicate.
1071
- * @template M - The machine type
1072
- */
1073
- export type ConditionalMiddleware<M extends BaseMachine<any>> = {
1074
- /** The middleware function to apply */
1075
- middleware: MiddlewareFn<M>;
1076
- /** Predicate function that determines if the middleware should be applied */
1077
- when: (machine: M) => boolean;
1078
- };
1079
-
1080
- /**
1081
- * A named middleware entry for registry-based composition.
1082
- * @template M - The machine type
1083
- */
1084
- export type NamedMiddleware<M extends BaseMachine<any>> = {
1085
- /** Unique name for the middleware */
1086
- name: string;
1087
- /** The middleware function */
1088
- middleware: MiddlewareFn<M>;
1089
- /** Optional description */
1090
- description?: string;
1091
- /** Optional priority for ordering (higher numbers = applied later) */
1092
- priority?: number;
1093
- };
948
+ // Create replay functionality
949
+ const replayFrom = (startIndex: number): M => {
950
+ if (startIndex < 0 || startIndex >= history.length) {
951
+ throw new Error(`Invalid replay start index: ${startIndex}`);
952
+ }
1094
953
 
1095
- /**
1096
- * Configuration for middleware pipeline execution.
1097
- */
1098
- export interface PipelineConfig {
1099
- /** Whether to continue execution if a middleware throws an error */
1100
- continueOnError?: boolean;
1101
- /** Whether to log errors from middlewares */
1102
- logErrors?: boolean;
1103
- /** Custom error handler */
1104
- onError?: (error: Error, middlewareIndex: number, middlewareName?: string) => void;
1105
- }
954
+ // Start from the context at the specified history index
955
+ let currentContext = snapshots[startIndex]?.before;
956
+ if (!currentContext) {
957
+ throw new Error(`No snapshot available for index ${startIndex}`);
958
+ }
1106
959
 
1107
- /**
1108
- * Result of pipeline execution.
1109
- */
1110
- export type PipelineResult<M extends BaseMachine<any>> = M;
960
+ // Get all transitions from start index to end
961
+ const transitionsToReplay = history.slice(startIndex);
962
+
963
+ // Create a fresh machine instance
964
+ const freshMachine = Object.assign(
965
+ { context: currentContext },
966
+ Object.fromEntries(
967
+ Object.entries(machine).filter(([key]) =>
968
+ key !== 'context' &&
969
+ typeof machine[key as keyof M] === 'function'
970
+ )
971
+ )
972
+ ) as M;
973
+
974
+ // Replay each transition
975
+ let replayedMachine = freshMachine;
976
+ for (const entry of transitionsToReplay) {
977
+ const transitionFn = replayedMachine[entry.transitionName as keyof M] as Function;
978
+ if (transitionFn) {
979
+ replayedMachine = transitionFn.apply(replayedMachine, entry.args);
980
+ }
981
+ }
1111
982
 
1112
- // =============================================================================
1113
- // SECTION: FLUENT API
1114
- // =============================================================================
983
+ return replayedMachine;
984
+ };
985
+
986
+ // Return machine with all time travel capabilities
987
+ return Object.assign(instrumentedMachine, {
988
+ history,
989
+ snapshots,
990
+ clearHistory: () => { history.length = 0; historyId = 0; },
991
+ clearSnapshots: () => { snapshots.length = 0; snapshotId = 0; },
992
+ clearTimeTravel: () => {
993
+ history.length = 0;
994
+ snapshots.length = 0;
995
+ historyId = 0;
996
+ snapshotId = 0;
997
+ },
998
+ restoreSnapshot,
999
+ replayFrom
1000
+ }) as WithTimeTravel<M>;
1001
+ }
1115
1002
 
1116
- /**
1117
- * Fluent middleware composer for building complex middleware chains.
1118
- * Provides excellent TypeScript inference and IntelliSense.
1119
- */
1120
- class MiddlewareChainBuilder<M extends BaseMachine<any>> {
1121
- constructor(private machine: M) {}
1003
+ // =============================================================================
1004
+ // SECTION: MIDDLEWARE COMPOSITION
1005
+ // =============================================================================
1122
1006
 
1123
1007
  /**
1124
- * Add a middleware to the composition chain.
1125
- * @param middleware - The middleware function to add
1126
- * @returns A new composer with the middleware applied
1008
+ * Compose multiple middleware functions into a single middleware stack.
1009
+ * Middleware is applied left-to-right (first middleware wraps outermost).
1010
+ *
1011
+ * @template M - The machine type
1012
+ * @param machine - The base machine
1013
+ * @param middlewares - Array of middleware functions
1014
+ * @returns A new machine with all middleware applied
1127
1015
  */
1128
- with<M2 extends MiddlewareFn<any, any>>(
1129
- middleware: M2
1130
- ): MiddlewareChainBuilder<ReturnType<M2> extends BaseMachine<any> ? ReturnType<M2> : M> {
1131
- const result = middleware(this.machine);
1132
- return new MiddlewareChainBuilder(result as any);
1016
+ export function compose<M extends BaseMachine<any>>(
1017
+ machine: M,
1018
+ ...middlewares: Array<(m: M) => M>
1019
+ ): M {
1020
+ return middlewares.reduce((acc, middleware) => middleware(acc), machine);
1133
1021
  }
1134
1022
 
1135
1023
  /**
1136
- * Build the final machine with all middlewares applied.
1024
+ * Type-safe middleware composition with perfect inference.
1025
+ * Composes multiple middlewares into a single transformation chain.
1026
+ *
1027
+ * @template M - The input machine type
1028
+ * @template Ms - Array of middleware functions
1029
+ * @param machine - The machine to enhance
1030
+ * @param middlewares - Middleware functions to apply in order
1031
+ * @returns The machine with all middlewares applied, with precise type inference
1137
1032
  */
1138
- build(): M {
1139
- return this.machine;
1033
+ export function composeTyped<
1034
+ M extends BaseMachine<any>,
1035
+ Ms extends readonly MiddlewareFn<any, any>[]
1036
+ >(
1037
+ machine: M,
1038
+ ...middlewares: Ms
1039
+ ): ComposeResult<M, Ms> {
1040
+ return middlewares.reduce((acc, middleware) => middleware(acc), machine) as ComposeResult<M, Ms>;
1140
1041
  }
1141
- }
1142
1042
 
1143
- /**
1144
- * Create a fluent middleware chain builder.
1145
- *
1146
- * @example
1147
- * ```typescript
1148
- * const enhanced = chain(counter)
1149
- * .with(withHistory())
1150
- * .with(withSnapshot())
1151
- * .with(withTimeTravel())
1152
- * .build();
1153
- * ```
1154
- */
1155
- export function chain<M extends BaseMachine<any>>(machine: M) {
1156
- return new MiddlewareChainBuilder(machine);
1157
- }
1043
+ // =============================================================================
1044
+ // SECTION: TYPE-LEVEL COMPOSITION
1045
+ // =============================================================================
1158
1046
 
1159
- // =============================================================================
1160
- // SECTION: CONDITIONAL MIDDLEWARE
1161
- // =============================================================================
1047
+ /**
1048
+ * Type-level utility for composing middleware return types.
1049
+ * This enables perfect TypeScript inference when chaining middlewares.
1050
+ */
1051
+ export type ComposeResult<
1052
+ M extends BaseMachine<any>,
1053
+ Ms extends readonly MiddlewareFn<any, any>[]
1054
+ > = Ms extends readonly [infer First, ...infer Rest]
1055
+ ? First extends MiddlewareFn<any, infer R>
1056
+ ? Rest extends readonly MiddlewareFn<any, any>[]
1057
+ ? ComposeResult<R, Rest>
1058
+ : R
1059
+ : M
1060
+ : M;
1162
1061
 
1163
- /**
1164
- * Create a conditional middleware that only applies when a predicate is true.
1165
- *
1166
- * @template M - The machine type
1167
- * @param middleware - The middleware to conditionally apply
1168
- * @param predicate - Function that determines when to apply the middleware
1169
- * @returns A conditional middleware that can be called directly or used in pipelines
1170
- */
1171
- export function when<M extends BaseMachine<any>>(
1172
- middleware: MiddlewareFn<M>,
1173
- predicate: (machine: M) => boolean
1174
- ): ConditionalMiddleware<M> & MiddlewareFn<M> {
1175
- const conditional: ConditionalMiddleware<M> & MiddlewareFn<M> = function(machine: M) {
1176
- return predicate(machine) ? middleware(machine) : machine;
1177
- };
1062
+ /**
1063
+ * A middleware function that transforms a machine.
1064
+ * @template M - The input machine type
1065
+ * @template R - The output machine type (usually extends M)
1066
+ */
1067
+ export type MiddlewareFn<M extends BaseMachine<any>, R extends BaseMachine<any> = M> = (machine: M) => R;
1178
1068
 
1179
- conditional.middleware = middleware;
1180
- conditional.when = predicate;
1069
+ /**
1070
+ * A conditional middleware that may or may not be applied based on a predicate.
1071
+ * @template M - The machine type
1072
+ */
1073
+ export type ConditionalMiddleware<M extends BaseMachine<any>> = {
1074
+ /** The middleware function to apply */
1075
+ middleware: MiddlewareFn<M>;
1076
+ /** Predicate function that determines if the middleware should be applied */
1077
+ when: (machine: M) => boolean;
1078
+ };
1181
1079
 
1182
- return conditional;
1183
- }
1080
+ /**
1081
+ * A named middleware entry for registry-based composition.
1082
+ * @template M - The machine type
1083
+ */
1084
+ export type NamedMiddleware<M extends BaseMachine<any>> = {
1085
+ /** Unique name for the middleware */
1086
+ name: string;
1087
+ /** The middleware function */
1088
+ middleware: MiddlewareFn<M>;
1089
+ /** Optional description */
1090
+ description?: string;
1091
+ /** Optional priority for ordering (higher numbers = applied later) */
1092
+ priority?: number;
1093
+ };
1184
1094
 
1185
- /**
1186
- * Create a middleware that only applies in development mode.
1187
- *
1188
- * @template M - The machine type
1189
- * @param middleware - The middleware to apply in development
1190
- * @returns A conditional middleware for development mode
1191
- */
1192
- export function inDevelopment<M extends BaseMachine<any>>(
1193
- middleware: MiddlewareFn<M>
1194
- ): ConditionalMiddleware<M> & MiddlewareFn<M> {
1195
- return when(middleware, () => {
1196
- return typeof process !== 'undefined'
1197
- ? process.env.NODE_ENV === 'development'
1198
- : typeof window !== 'undefined'
1199
- ? !window.location.hostname.includes('production')
1200
- : false;
1201
- });
1202
- }
1095
+ /**
1096
+ * Configuration for middleware pipeline execution.
1097
+ */
1098
+ export interface PipelineConfig {
1099
+ /** Whether to continue execution if a middleware throws an error */
1100
+ continueOnError?: boolean;
1101
+ /** Whether to log errors from middlewares */
1102
+ logErrors?: boolean;
1103
+ /** Custom error handler */
1104
+ onError?: (error: Error, middlewareIndex: number, middlewareName?: string) => void;
1105
+ }
1203
1106
 
1204
- /**
1205
- * Create a middleware that only applies when a context property matches a value.
1206
- *
1207
- * @template M - The machine type
1208
- * @template K - The context key
1209
- * @param key - The context property key
1210
- * @param value - The value to match
1211
- * @param middleware - The middleware to apply when the condition matches
1212
- * @returns A conditional middleware
1213
- */
1214
- export function whenContext<M extends BaseMachine<any>, K extends keyof Context<M>>(
1215
- key: K,
1216
- value: Context<M>[K],
1217
- middleware: MiddlewareFn<M>
1218
- ): ConditionalMiddleware<M> & MiddlewareFn<M> {
1219
- return when(middleware, (machine) => machine.context[key] === value);
1220
- }
1107
+ /**
1108
+ * Result of pipeline execution.
1109
+ */
1110
+ export type PipelineResult<M extends BaseMachine<any>> = M;
1221
1111
 
1222
- // =============================================================================
1223
- // SECTION: MIDDLEWARE REGISTRY
1224
- // =============================================================================
1112
+ // =============================================================================
1113
+ // SECTION: FLUENT API
1114
+ // =============================================================================
1225
1115
 
1226
- /**
1227
- * Create a middleware registry for managing reusable middleware configurations.
1228
- */
1229
- export function createMiddlewareRegistry<M extends BaseMachine<any>>() {
1230
- const registry = new Map<string, NamedMiddleware<M>>();
1116
+ /**
1117
+ * Fluent middleware composer for building complex middleware chains.
1118
+ * Provides excellent TypeScript inference and IntelliSense.
1119
+ */
1120
+ class MiddlewareChainBuilder<M extends BaseMachine<any>> {
1121
+ constructor(private machine: M) { }
1231
1122
 
1232
- return {
1233
1123
  /**
1234
- * Register a middleware by name.
1124
+ * Add a middleware to the composition chain.
1125
+ * @param middleware - The middleware function to add
1126
+ * @returns A new composer with the middleware applied
1235
1127
  */
1236
- register(
1237
- name: string,
1238
- middleware: MiddlewareFn<M>,
1239
- description?: string,
1240
- priority?: number
1241
- ): typeof this {
1242
- if (registry.has(name)) {
1243
- throw new Error(`Middleware '${name}' is already registered`);
1244
- }
1245
-
1246
- registry.set(name, { name, middleware, description, priority });
1247
- return this;
1248
- },
1128
+ with<M2 extends MiddlewareFn<any, any>>(
1129
+ middleware: M2
1130
+ ): MiddlewareChainBuilder<ReturnType<M2> extends BaseMachine<any> ? ReturnType<M2> : M> {
1131
+ const result = middleware(this.machine);
1132
+ return new MiddlewareChainBuilder(result as any);
1133
+ }
1249
1134
 
1250
1135
  /**
1251
- * Unregister a middleware by name.
1136
+ * Build the final machine with all middlewares applied.
1252
1137
  */
1253
- unregister(name: string): boolean {
1254
- return registry.delete(name);
1255
- },
1138
+ build(): M {
1139
+ return this.machine;
1140
+ }
1141
+ }
1256
1142
 
1257
- /**
1258
- * Check if a middleware is registered.
1259
- */
1260
- has(name: string): boolean {
1261
- return registry.has(name);
1262
- },
1143
+ /**
1144
+ * Create a fluent middleware chain builder.
1145
+ *
1146
+ * @example
1147
+ * ```typescript
1148
+ * const enhanced = chain(counter)
1149
+ * .with(withHistory())
1150
+ * .with(withSnapshot())
1151
+ * .with(withTimeTravel())
1152
+ * .build();
1153
+ * ```
1154
+ */
1155
+ export function chain<M extends BaseMachine<any>>(machine: M) {
1156
+ return new MiddlewareChainBuilder(machine);
1157
+ }
1263
1158
 
1264
- /**
1265
- * Get a registered middleware by name.
1266
- */
1267
- get(name: string): NamedMiddleware<M> | undefined {
1268
- return registry.get(name);
1269
- },
1159
+ // =============================================================================
1160
+ // SECTION: CONDITIONAL MIDDLEWARE
1161
+ // =============================================================================
1270
1162
 
1271
- /**
1272
- * List all registered middlewares.
1273
- */
1274
- list(): NamedMiddleware<M>[] {
1275
- return Array.from(registry.values()).sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
1276
- },
1163
+ /**
1164
+ * Create a conditional middleware that only applies when a predicate is true.
1165
+ *
1166
+ * @template M - The machine type
1167
+ * @param middleware - The middleware to conditionally apply
1168
+ * @param predicate - Function that determines when to apply the middleware
1169
+ * @returns A conditional middleware that can be called directly or used in pipelines
1170
+ */
1171
+ export function when<M extends BaseMachine<any>>(
1172
+ middleware: MiddlewareFn<M>,
1173
+ predicate: (machine: M) => boolean
1174
+ ): ConditionalMiddleware<M> & MiddlewareFn<M> {
1175
+ const conditional: ConditionalMiddleware<M> & MiddlewareFn<M> = function (machine: M) {
1176
+ return predicate(machine) ? middleware(machine) : machine;
1177
+ };
1178
+
1179
+ conditional.middleware = middleware;
1180
+ conditional.when = predicate;
1181
+
1182
+ return conditional;
1183
+ }
1277
1184
 
1278
- /**
1279
- * Apply a selection of registered middlewares to a machine.
1280
- * Middlewares are applied in priority order (lowest to highest).
1281
- */
1282
- apply(machine: M, middlewareNames: string[]): M {
1283
- const middlewares = middlewareNames
1284
- .map(name => {
1285
- const entry = registry.get(name);
1286
- if (!entry) {
1287
- throw new Error(`Middleware '${name}' is not registered`);
1288
- }
1289
- return entry;
1290
- })
1291
- .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
1185
+ /**
1186
+ * Create a middleware that only applies in development mode.
1187
+ *
1188
+ * @template M - The machine type
1189
+ * @param middleware - The middleware to apply in development
1190
+ * @returns A conditional middleware for development mode
1191
+ */
1192
+ export function inDevelopment<M extends BaseMachine<any>>(
1193
+ middleware: MiddlewareFn<M>
1194
+ ): ConditionalMiddleware<M> & MiddlewareFn<M> {
1195
+ return when(middleware, () => {
1196
+ return typeof process !== 'undefined'
1197
+ ? process.env.NODE_ENV === 'development'
1198
+ : typeof window !== 'undefined'
1199
+ ? !window.location.hostname.includes('production')
1200
+ : false;
1201
+ });
1202
+ }
1292
1203
 
1293
- return composeTyped(machine, ...middlewares.map(m => m.middleware));
1294
- },
1204
+ /**
1205
+ * Create a middleware that only applies when a context property matches a value.
1206
+ *
1207
+ * @template M - The machine type
1208
+ * @template K - The context key
1209
+ * @param key - The context property key
1210
+ * @param value - The value to match
1211
+ * @param middleware - The middleware to apply when the condition matches
1212
+ * @returns A conditional middleware
1213
+ */
1214
+ export function whenContext<M extends BaseMachine<any>, K extends keyof Context<M>>(
1215
+ key: K,
1216
+ value: Context<M>[K],
1217
+ middleware: MiddlewareFn<M>
1218
+ ): ConditionalMiddleware<M> & MiddlewareFn<M> {
1219
+ return when(middleware, (machine) => machine.context[key] === value);
1220
+ }
1295
1221
 
1296
- /**
1297
- * Apply all registered middlewares to a machine in priority order.
1298
- */
1299
- applyAll(machine: M): M {
1300
- const middlewares = this.list();
1301
- return composeTyped(machine, ...middlewares.map(m => m.middleware));
1302
- }
1303
- };
1304
- }
1222
+ // =============================================================================
1223
+ // SECTION: MIDDLEWARE REGISTRY
1224
+ // =============================================================================
1305
1225
 
1306
- // =============================================================================
1307
- // SECTION: PIPELINES
1308
- // =============================================================================
1226
+ /**
1227
+ * Create a middleware registry for managing reusable middleware configurations.
1228
+ */
1229
+ export function createMiddlewareRegistry<M extends BaseMachine<any>>() {
1230
+ const registry = new Map<string, NamedMiddleware<M>>();
1231
+
1232
+ return {
1233
+ /**
1234
+ * Register a middleware by name.
1235
+ */
1236
+ register(
1237
+ name: string,
1238
+ middleware: MiddlewareFn<M>,
1239
+ description?: string,
1240
+ priority?: number
1241
+ ): typeof this {
1242
+ if (registry.has(name)) {
1243
+ throw new Error(`Middleware '${name}' is already registered`);
1244
+ }
1309
1245
 
1310
- /**
1311
- * Create a middleware pipeline with error handling and conditional execution.
1312
- *
1313
- * @template M - The machine type
1314
- * @param config - Pipeline configuration
1315
- * @returns A function that executes middlewares in a pipeline
1316
- */
1317
- export function createPipeline<M extends BaseMachine<any>>(
1318
- config: PipelineConfig = {}
1319
- ): {
1320
- <Ms extends Array<MiddlewareFn<M> | ConditionalMiddleware<M>>>(
1321
- machine: M,
1322
- ...middlewares: Ms
1323
- ): { machine: M; errors: Array<{ error: Error; middlewareIndex: number; middlewareName?: string }>; success: boolean };
1324
- } {
1325
- const {
1326
- continueOnError = false,
1327
- logErrors = true,
1328
- onError
1329
- } = config;
1330
-
1331
- return (machine: M, ...middlewares: Array<MiddlewareFn<M> | ConditionalMiddleware<M>>) => {
1332
- let currentMachine = machine;
1333
- const errors: Array<{ error: Error; middlewareIndex: number; middlewareName?: string }> = [];
1334
- let success = true;
1335
-
1336
- for (let i = 0; i < middlewares.length; i++) {
1337
- const middleware = middlewares[i];
1338
-
1339
- try {
1340
- // Handle conditional middleware
1341
- if ('middleware' in middleware && 'when' in middleware) {
1342
- if (!middleware.when(currentMachine)) {
1343
- continue; // Skip this middleware
1246
+ registry.set(name, { name, middleware, description, priority });
1247
+ return this;
1248
+ },
1249
+
1250
+ /**
1251
+ * Unregister a middleware by name.
1252
+ */
1253
+ unregister(name: string): boolean {
1254
+ return registry.delete(name);
1255
+ },
1256
+
1257
+ /**
1258
+ * Check if a middleware is registered.
1259
+ */
1260
+ has(name: string): boolean {
1261
+ return registry.has(name);
1262
+ },
1263
+
1264
+ /**
1265
+ * Get a registered middleware by name.
1266
+ */
1267
+ get(name: string): NamedMiddleware<M> | undefined {
1268
+ return registry.get(name);
1269
+ },
1270
+
1271
+ /**
1272
+ * List all registered middlewares.
1273
+ */
1274
+ list(): NamedMiddleware<M>[] {
1275
+ return Array.from(registry.values()).sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
1276
+ },
1277
+
1278
+ /**
1279
+ * Apply a selection of registered middlewares to a machine.
1280
+ * Middlewares are applied in priority order (lowest to highest).
1281
+ */
1282
+ apply(machine: M, middlewareNames: string[]): M {
1283
+ const middlewares = middlewareNames
1284
+ .map(name => {
1285
+ const entry = registry.get(name);
1286
+ if (!entry) {
1287
+ throw new Error(`Middleware '${name}' is not registered`);
1288
+ }
1289
+ return entry;
1290
+ })
1291
+ .sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
1292
+
1293
+ return composeTyped(machine, ...middlewares.map(m => m.middleware));
1294
+ },
1295
+
1296
+ /**
1297
+ * Apply all registered middlewares to a machine in priority order.
1298
+ */
1299
+ applyAll(machine: M): M {
1300
+ const middlewares = this.list();
1301
+ return composeTyped(machine, ...middlewares.map(m => m.middleware));
1302
+ }
1303
+ };
1304
+ }
1305
+
1306
+ // =============================================================================
1307
+ // SECTION: PIPELINES
1308
+ // =============================================================================
1309
+
1310
+ /**
1311
+ * Create a middleware pipeline with error handling and conditional execution.
1312
+ *
1313
+ * @template M - The machine type
1314
+ * @param config - Pipeline configuration
1315
+ * @returns A function that executes middlewares in a pipeline
1316
+ */
1317
+ export function createPipeline<M extends BaseMachine<any>>(
1318
+ config: PipelineConfig = {}
1319
+ ): {
1320
+ <Ms extends Array<MiddlewareFn<M> | ConditionalMiddleware<M>>>(
1321
+ machine: M,
1322
+ ...middlewares: Ms
1323
+ ): { machine: M; errors: Array<{ error: Error; middlewareIndex: number; middlewareName?: string }>; success: boolean };
1324
+ } {
1325
+ const {
1326
+ continueOnError = false,
1327
+ logErrors = true,
1328
+ onError
1329
+ } = config;
1330
+
1331
+ return (machine: M, ...middlewares: Array<MiddlewareFn<M> | ConditionalMiddleware<M>>) => {
1332
+ let currentMachine = machine;
1333
+ const errors: Array<{ error: Error; middlewareIndex: number; middlewareName?: string }> = [];
1334
+ let success = true;
1335
+
1336
+ for (let i = 0; i < middlewares.length; i++) {
1337
+ const middleware = middlewares[i];
1338
+
1339
+ try {
1340
+ // Handle conditional middleware
1341
+ if ('middleware' in middleware && 'when' in middleware) {
1342
+ if (!middleware.when(currentMachine)) {
1343
+ continue; // Skip this middleware
1344
+ }
1345
+ currentMachine = middleware.middleware(currentMachine);
1346
+ } else {
1347
+ // Regular middleware
1348
+ currentMachine = (middleware as MiddlewareFn<M>)(currentMachine);
1349
+ }
1350
+ } catch (error) {
1351
+ success = false;
1352
+ if (!continueOnError) {
1353
+ throw error;
1344
1354
  }
1345
- currentMachine = middleware.middleware(currentMachine);
1346
- } else {
1347
- // Regular middleware
1348
- currentMachine = (middleware as MiddlewareFn<M>)(currentMachine);
1349
- }
1350
- } catch (error) {
1351
- success = false;
1352
- if (!continueOnError) {
1353
- throw error;
1354
- }
1355
1355
 
1356
- errors.push({
1357
- error: error as Error,
1358
- middlewareIndex: i,
1359
- middlewareName: (middleware as any).name
1360
- });
1356
+ errors.push({
1357
+ error: error as Error,
1358
+ middlewareIndex: i,
1359
+ middlewareName: (middleware as any).name
1360
+ });
1361
1361
 
1362
- if (logErrors) {
1363
- console.error(`Pipeline middleware error at index ${i}:`, error);
1364
- }
1362
+ if (logErrors) {
1363
+ console.error(`Pipeline middleware error at index ${i}:`, error);
1364
+ }
1365
1365
 
1366
- onError?.(error as Error, i, (middleware as any).name);
1366
+ onError?.(error as Error, i, (middleware as any).name);
1367
+ }
1367
1368
  }
1368
- }
1369
1369
 
1370
- return { machine: currentMachine, errors, success };
1371
- };
1372
- }
1370
+ return { machine: currentMachine, errors, success };
1371
+ };
1372
+ }
1373
1373
 
1374
- // =============================================================================
1375
- // SECTION: UTILITY FUNCTIONS
1376
- // =============================================================================
1374
+ // =============================================================================
1375
+ // SECTION: UTILITY FUNCTIONS
1376
+ // =============================================================================
1377
1377
 
1378
- /**
1379
- * Combine multiple middlewares with short-circuiting.
1380
- */
1381
- export function combine<M extends BaseMachine<any>>(
1382
- ...middlewares: Array<MiddlewareFn<M>>
1383
- ): MiddlewareFn<M> {
1384
- return (machine: M) => composeTyped(machine, ...middlewares);
1385
- }
1378
+ /**
1379
+ * Combine multiple middlewares with short-circuiting.
1380
+ */
1381
+ export function combine<M extends BaseMachine<any>>(
1382
+ ...middlewares: Array<MiddlewareFn<M>>
1383
+ ): MiddlewareFn<M> {
1384
+ return (machine: M) => composeTyped(machine, ...middlewares);
1385
+ }
1386
1386
 
1387
- /**
1388
- * Create a middleware that applies different middlewares based on context.
1389
- */
1390
- export function branch<M extends BaseMachine<any>>(
1391
- branches: Array<[predicate: (machine: M) => boolean, middleware: MiddlewareFn<M>]>,
1392
- fallback?: MiddlewareFn<M>
1393
- ): MiddlewareFn<M> {
1394
- return (machine: M) => {
1395
- for (const [predicate, middleware] of branches) {
1396
- if (predicate(machine)) {
1397
- return middleware(machine);
1387
+ /**
1388
+ * Create a middleware that applies different middlewares based on context.
1389
+ */
1390
+ export function branch<M extends BaseMachine<any>>(
1391
+ branches: Array<[predicate: (machine: M) => boolean, middleware: MiddlewareFn<M>]>,
1392
+ fallback?: MiddlewareFn<M>
1393
+ ): MiddlewareFn<M> {
1394
+ return (machine: M) => {
1395
+ for (const [predicate, middleware] of branches) {
1396
+ if (predicate(machine)) {
1397
+ return middleware(machine);
1398
+ }
1398
1399
  }
1399
- }
1400
- return fallback ? fallback(machine) : machine;
1401
- };
1402
- }
1403
-
1404
- // =============================================================================
1405
- // SECTION: TYPE GUARDS
1406
- // =============================================================================
1400
+ return fallback ? fallback(machine) : machine;
1401
+ };
1402
+ }
1407
1403
 
1408
- /**
1409
- * Type guard to check if a value is a middleware function.
1410
- */
1411
- export function isMiddlewareFn<M extends BaseMachine<any>>(
1412
- value: any
1413
- ): value is MiddlewareFn<M> {
1414
- return typeof value === 'function' && value.length === 1;
1415
- }
1404
+ // =============================================================================
1405
+ // SECTION: TYPE GUARDS
1406
+ // =============================================================================
1416
1407
 
1417
- /**
1418
- * Type guard to check if a value is a conditional middleware.
1419
- */
1420
- export function isConditionalMiddleware<M extends BaseMachine<any>>(
1421
- value: any
1422
- ): value is ConditionalMiddleware<M> {
1423
- return (
1424
- value !== null &&
1425
- 'middleware' in value &&
1426
- 'when' in value &&
1427
- isMiddlewareFn(value.middleware) &&
1428
- typeof value.when === 'function'
1429
- );
1430
- }
1408
+ /**
1409
+ * Type guard to check if a value is a middleware function.
1410
+ */
1411
+ export function isMiddlewareFn<M extends BaseMachine<any>>(
1412
+ value: any
1413
+ ): value is MiddlewareFn<M> {
1414
+ return typeof value === 'function' && value.length === 1;
1415
+ }
1431
1416
 
1432
- // =============================================================================
1433
- // SECTION: COMMON COMBINATIONS
1434
- // =============================================================================
1417
+ /**
1418
+ * Type guard to check if a value is a conditional middleware.
1419
+ */
1420
+ export function isConditionalMiddleware<M extends BaseMachine<any>>(
1421
+ value: any
1422
+ ): value is ConditionalMiddleware<M> {
1423
+ return (
1424
+ value !== null &&
1425
+ 'middleware' in value &&
1426
+ 'when' in value &&
1427
+ isMiddlewareFn(value.middleware) &&
1428
+ typeof value.when === 'function'
1429
+ );
1430
+ }
1435
1431
 
1436
- /**
1437
- * Common middleware combination types for better DX.
1438
- */
1439
- export type WithDebugging<M extends BaseMachine<any>> = WithTimeTravel<WithSnapshot<WithHistory<M>>>;
1432
+ // =============================================================================
1433
+ // SECTION: COMMON COMBINATIONS
1434
+ // =============================================================================
1440
1435
 
1441
- /**
1442
- * Convenience function for the most common debugging middleware stack.
1443
- */
1444
- export function withDebugging<M extends BaseMachine<any>>(machine: M): WithDebugging<M> {
1445
- return withTimeTravel(withSnapshot(withHistory(machine)));
1446
- }
1436
+ /**
1437
+ * Common middleware combination types for better DX.
1438
+ */
1439
+ export type WithDebugging<M extends BaseMachine<any>> = WithTimeTravel<WithSnapshot<WithHistory<M>>>;
1440
+ /**
1441
+ * Convenience function for the most common debugging middleware stack.
1442
+ */
1443
+ export function withDebugging<M extends BaseMachine<any>>(machine: M): WithDebugging<M> {
1444
+ return withTimeTravel(withSnapshot(withHistory(machine)));
1445
+ }