@doeixd/machine 0.0.7 → 0.0.8

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.
@@ -0,0 +1,1048 @@
1
+ /**
2
+ * @file Middleware/interception system for state machines
3
+ * @description Provides composable middleware for logging, analytics, validation, and more.
4
+ */
5
+ import type { Context, BaseMachine } from './index';
6
+ /**
7
+ * Context object passed to middleware hooks containing transition metadata.
8
+ * @template C - The context object type
9
+ */
10
+ export interface MiddlewareContext<C extends object> {
11
+ /** The name of the transition being called */
12
+ transitionName: string;
13
+ /** The current machine context before the transition */
14
+ context: Readonly<C>;
15
+ /** Arguments passed to the transition function */
16
+ args: any[];
17
+ }
18
+ /**
19
+ * Result object passed to after hooks containing transition outcome.
20
+ * @template C - The context object type
21
+ */
22
+ export interface MiddlewareResult<C extends object> {
23
+ /** The name of the transition that was called */
24
+ transitionName: string;
25
+ /** The context before the transition */
26
+ prevContext: Readonly<C>;
27
+ /** The context after the transition */
28
+ nextContext: Readonly<C>;
29
+ /** Arguments that were passed to the transition */
30
+ args: any[];
31
+ }
32
+ /**
33
+ * Error context passed to error hooks.
34
+ * @template C - The context object type
35
+ */
36
+ export interface MiddlewareError<C extends object> {
37
+ /** The name of the transition that failed */
38
+ transitionName: string;
39
+ /** The context when the error occurred */
40
+ context: Readonly<C>;
41
+ /** Arguments that were passed to the transition */
42
+ args: any[];
43
+ /** The error that was thrown */
44
+ error: Error;
45
+ }
46
+ /**
47
+ * Configuration object for middleware hooks.
48
+ * All hooks are optional - provide only the ones you need.
49
+ * @template C - The context object type
50
+ */
51
+ /**
52
+ * Strongly typed middleware hooks with precise context and return types.
53
+ * All hooks are optional - provide only the ones you need.
54
+ *
55
+ * @template C - The machine context type for precise type inference
56
+ */
57
+ export interface MiddlewareHooks<C extends object> {
58
+ /**
59
+ * Called before a transition executes.
60
+ * Can be used for validation, logging, analytics, etc.
61
+ *
62
+ * @param ctx - Transition context with machine state and transition details
63
+ * @returns void to continue, CANCEL to abort silently, or Promise for async validation
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * before: ({ transitionName, args, context }) => {
68
+ * if (transitionName === 'withdraw' && context.balance < args[0]) {
69
+ * throw new Error('Insufficient funds');
70
+ * }
71
+ * }
72
+ * ```
73
+ */
74
+ before?: (ctx: MiddlewareContext<C>) => void | typeof CANCEL | Promise<void | typeof CANCEL>;
75
+ /**
76
+ * Called after a transition successfully executes.
77
+ * Receives both the previous and next context.
78
+ * Cannot prevent the transition (it already happened).
79
+ *
80
+ * @param result - Transition result with before/after contexts and transition details
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * after: ({ transitionName, before, after }) => {
85
+ * console.log(`${transitionName}: ${before.count} -> ${after.count}`);
86
+ * }
87
+ * ```
88
+ */
89
+ after?: (result: MiddlewareResult<C>) => void | Promise<void>;
90
+ /**
91
+ * Called if a transition throws an error.
92
+ * Can be used for error logging, Sentry reporting, fallback states, etc.
93
+ *
94
+ * @param error - Error context with transition details and the thrown error
95
+ * @returns
96
+ * - void/undefined/null: Re-throw the original error (default)
97
+ * - BaseMachine: Use this as fallback state instead of throwing
98
+ * - throw new Error: Transform the error
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * error: ({ transitionName, error, context }) => {
103
+ * // Log to error reporting service
104
+ * reportError(error, { transitionName, context });
105
+ *
106
+ * // Return fallback state for recoverable errors
107
+ * if (error.message.includes('network')) {
108
+ * return createMachine({ ...context, error: 'offline' }, transitions);
109
+ * }
110
+ * }
111
+ * ```
112
+ */
113
+ error?: (error: MiddlewareError<C>) => void | null | BaseMachine<C> | Promise<void | null | BaseMachine<C>>;
114
+ }
115
+ /**
116
+ * Options for middleware configuration.
117
+ */
118
+ export interface MiddlewareOptions {
119
+ /**
120
+ * Execution mode for middleware hooks.
121
+ * - 'sync': Hooks must be synchronous, throws if hooks return Promise
122
+ * - 'async': Always await hooks and transition
123
+ * - 'auto' (default): Adaptive mode - starts synchronously, automatically handles async results if encountered
124
+ *
125
+ * Note: 'auto' mode provides the best of both worlds - zero overhead for sync transitions
126
+ * while seamlessly handling async ones when they occur.
127
+ * @default 'auto'
128
+ */
129
+ mode?: 'sync' | 'async' | 'auto';
130
+ /**
131
+ * Properties to exclude from middleware interception.
132
+ * Useful for excluding utility methods or getters.
133
+ * @default ['context']
134
+ */
135
+ exclude?: string[];
136
+ }
137
+ /**
138
+ * Special symbol that can be returned from before hooks to cancel a transition.
139
+ * When returned, the transition will not execute and the current machine state is preserved.
140
+ *
141
+ * @example
142
+ * createMiddleware(machine, {
143
+ * before: ({ transitionName, context }) => {
144
+ * if (shouldCancel(context)) {
145
+ * return CANCEL; // Abort transition without throwing
146
+ * }
147
+ * }
148
+ * });
149
+ */
150
+ export declare const CANCEL: unique symbol;
151
+ /**
152
+ * Augmented machine type with history tracking capabilities.
153
+ * @template M - The base machine type
154
+ * @template C - The context type
155
+ */
156
+ export type WithHistory<M extends BaseMachine<any>> = M & {
157
+ /** Array of recorded transition history entries */
158
+ history: HistoryEntry[];
159
+ /** Clear all history entries */
160
+ clearHistory: () => void;
161
+ };
162
+ /**
163
+ * Augmented machine type with snapshot tracking capabilities.
164
+ * @template M - The base machine type
165
+ * @template C - The context type
166
+ */
167
+ export type WithSnapshot<M extends BaseMachine<any>, C extends object = Context<M>> = M & {
168
+ /** Array of recorded context snapshots */
169
+ snapshots: ContextSnapshot<C>[];
170
+ /** Clear all snapshots */
171
+ clearSnapshots: () => void;
172
+ /** Restore machine to a previous context state */
173
+ restoreSnapshot: (context: C) => M;
174
+ };
175
+ /**
176
+ * Augmented machine type with full time-travel debugging capabilities.
177
+ * Combines both history and snapshot tracking.
178
+ * @template M - The base machine type
179
+ * @template C - The context type
180
+ */
181
+ export type WithTimeTravel<M extends BaseMachine<any>, C extends object = Context<M>> = M & {
182
+ /** Array of recorded transition history entries */
183
+ history: HistoryEntry[];
184
+ /** Array of recorded context snapshots */
185
+ snapshots: ContextSnapshot<C>[];
186
+ /** Clear all history and snapshots */
187
+ clearTimeTravel: () => void;
188
+ /** Restore machine to a previous context state */
189
+ restoreSnapshot: (context: C) => M;
190
+ /** Replay all transitions from a specific snapshot */
191
+ replayFrom: (snapshotIndex: number) => M;
192
+ };
193
+ /**
194
+ * Wraps a machine with middleware hooks that intercept all transitions.
195
+ * Uses direct property wrapping for optimal performance (3x faster than Proxy).
196
+ *
197
+ * The middleware preserves:
198
+ * - Full type safety (return type matches input machine)
199
+ * - `this` binding for transitions
200
+ * - Async and sync transitions
201
+ * - Machine immutability
202
+ *
203
+ * @template M - The machine type
204
+ * @param machine - The machine to wrap with middleware
205
+ * @param hooks - Middleware hooks (before, after, error)
206
+ * @param options - Configuration options
207
+ * @returns A new machine with middleware applied
208
+ *
209
+ * @example
210
+ * const instrumented = createMiddleware(counter, {
211
+ * before: ({ transitionName, context, args }) => {
212
+ * console.log(`→ ${transitionName}`, args);
213
+ * },
214
+ * after: ({ transitionName, prevContext, nextContext }) => {
215
+ * console.log(`✓ ${transitionName}`, nextContext);
216
+ * },
217
+ * error: ({ transitionName, error }) => {
218
+ * console.error(`✗ ${transitionName}:`, error);
219
+ * }
220
+ * });
221
+ */
222
+ export declare function createMiddleware<M extends BaseMachine<any>>(machine: M, hooks: MiddlewareHooks<Context<M>>, options?: MiddlewareOptions): M;
223
+ /**
224
+ * Logging middleware that logs transition calls and results to console.
225
+ * Useful for debugging and development.
226
+ *
227
+ * @template M - The machine type
228
+ * @param machine - The machine to add logging to
229
+ * @param options - Optional configuration for logging format
230
+ * @returns A new machine with logging middleware
231
+ *
232
+ * @example
233
+ * const logged = withLogging(counter);
234
+ * logged.increment(); // Console: "→ increment []" then "✓ increment { count: 1 }"
235
+ */
236
+ export declare function withLogging<M extends BaseMachine<any>>(machine: M, options?: {
237
+ /** Custom logger function (default: console.log) */
238
+ logger?: (message: string, ...args: any[]) => void;
239
+ /** Include context in logs (default: true) */
240
+ includeContext?: boolean;
241
+ /** Include arguments in logs (default: true) */
242
+ includeArgs?: boolean;
243
+ }): M;
244
+ /**
245
+ * Analytics middleware that tracks state transitions.
246
+ * Compatible with any analytics service (Segment, Mixpanel, GA, etc.).
247
+ *
248
+ * @template M - The machine type
249
+ * @param machine - The machine to track
250
+ * @param track - Analytics tracking function
251
+ * @param options - Optional configuration for event naming
252
+ * @returns A new machine with analytics middleware
253
+ *
254
+ * @example
255
+ * const tracked = withAnalytics(machine, (event, props) => {
256
+ * analytics.track(event, props);
257
+ * });
258
+ */
259
+ export declare function withAnalytics<M extends BaseMachine<any>>(machine: M, track: (event: string, properties: Record<string, any>) => void | Promise<void>, options?: {
260
+ /** Prefix for event names (default: "state_transition") */
261
+ eventPrefix?: string;
262
+ /** Include previous context in properties (default: false) */
263
+ includePrevContext?: boolean;
264
+ /** Include arguments in properties (default: true) */
265
+ includeArgs?: boolean;
266
+ }): M;
267
+ /**
268
+ * Validation middleware that validates transitions before they execute.
269
+ * Throws an error if validation fails, preventing the transition.
270
+ *
271
+ * @template M - The machine type
272
+ * @param machine - The machine to validate
273
+ * @param validate - Validation function that throws or returns false on invalid transitions
274
+ * @returns A new machine with validation middleware
275
+ *
276
+ * @example
277
+ * const validated = withValidation(counter, ({ transitionName, context, args }) => {
278
+ * if (transitionName === 'decrement' && context.count === 0) {
279
+ * throw new Error('Cannot decrement below zero');
280
+ * }
281
+ * });
282
+ */
283
+ export declare function withValidation<M extends BaseMachine<any>>(machine: M, validate: (ctx: MiddlewareContext<Context<M>>) => void | boolean | Promise<void | boolean>, options?: Pick<MiddlewareOptions, 'mode'>): M;
284
+ /**
285
+ * Permission/authorization middleware that checks if a transition is allowed.
286
+ * Useful for implementing role-based access control (RBAC) in state machines.
287
+ *
288
+ * @template M - The machine type
289
+ * @param machine - The machine to protect
290
+ * @param canPerform - Function that checks if the transition is allowed
291
+ * @returns A new machine with permission checks
292
+ *
293
+ * @example
294
+ * const protected = withPermissions(machine, (user) => ({ transitionName }) => {
295
+ * if (transitionName === 'delete' && user.role !== 'admin') {
296
+ * return false;
297
+ * }
298
+ * return true;
299
+ * });
300
+ */
301
+ export declare function withPermissions<M extends BaseMachine<any>>(machine: M, canPerform: (ctx: MiddlewareContext<Context<M>>) => boolean | Promise<boolean>, options?: Pick<MiddlewareOptions, 'mode'>): M;
302
+ /**
303
+ * Error reporting middleware that sends errors to an error tracking service.
304
+ * Compatible with Sentry, Bugsnag, Rollbar, etc.
305
+ *
306
+ * @template M - The machine type
307
+ * @param machine - The machine to monitor
308
+ * @param captureError - Error capture function (e.g., Sentry.captureException)
309
+ * @param options - Optional configuration for error context
310
+ * @returns A new machine with error reporting
311
+ *
312
+ * @example
313
+ * const monitored = withErrorReporting(machine, (error, context) => {
314
+ * Sentry.captureException(error, { extra: context });
315
+ * });
316
+ */
317
+ export declare function withErrorReporting<M extends BaseMachine<any>>(machine: M, captureError: (error: Error, context: Record<string, any>) => void | Promise<void>, options?: {
318
+ /** Include machine context in error report (default: true) */
319
+ includeContext?: boolean;
320
+ /** Include arguments in error report (default: true) */
321
+ includeArgs?: boolean;
322
+ /** Middleware execution mode */
323
+ mode?: MiddlewareOptions['mode'];
324
+ }): M;
325
+ /**
326
+ * Performance monitoring middleware that tracks transition execution time.
327
+ * Useful for identifying slow transitions and performance bottlenecks.
328
+ *
329
+ * @template M - The machine type
330
+ * @param machine - The machine to monitor
331
+ * @param onMetric - Callback to receive performance metrics
332
+ * @returns A new machine with performance monitoring
333
+ *
334
+ * @example
335
+ * const monitored = withPerformanceMonitoring(machine, ({ transition, duration }) => {
336
+ * if (duration > 100) {
337
+ * console.warn(`Slow transition: ${transition} took ${duration}ms`);
338
+ * }
339
+ * });
340
+ */
341
+ export declare function withPerformanceMonitoring<M extends BaseMachine<any>>(machine: M, onMetric: (metric: {
342
+ transitionName: string;
343
+ duration: number;
344
+ context: Readonly<Context<M>>;
345
+ }) => void | Promise<void>): M;
346
+ /**
347
+ * Retry middleware that automatically retries failed transitions.
348
+ * Uses direct property wrapping for optimal performance.
349
+ * Useful for handling transient failures in async operations.
350
+ *
351
+ * @template M - The machine type
352
+ * @param machine - The machine to add retry logic to
353
+ * @param options - Retry configuration
354
+ * @returns A new machine with retry logic
355
+ *
356
+ * @example
357
+ * const resilient = withRetry(machine, {
358
+ * maxRetries: 3,
359
+ * delay: 1000,
360
+ * shouldRetry: (error) => error.message.includes('network')
361
+ * });
362
+ */
363
+ export declare function withRetry<M extends BaseMachine<any>>(machine: M, options?: {
364
+ /** Maximum number of retry attempts (default: 3) */
365
+ maxRetries?: number;
366
+ /** Delay between retries in milliseconds (default: 1000) */
367
+ delay?: number;
368
+ /** Exponential backoff multiplier (default: 1, no backoff) */
369
+ backoffMultiplier?: number;
370
+ /** Function to determine if error should trigger retry (default: always retry) */
371
+ shouldRetry?: (error: Error) => boolean;
372
+ /** Callback when retry occurs */
373
+ onRetry?: (attempt: number, error: Error) => void;
374
+ }): M;
375
+ /**
376
+ * Guard configuration for a single transition.
377
+ */
378
+ export interface GuardConfig<C extends object> {
379
+ /** Guard predicate function that returns true if transition is allowed */
380
+ guard: (ctx: MiddlewareContext<C>, ...args: any[]) => boolean | Promise<boolean>;
381
+ /**
382
+ * Action to take when guard fails.
383
+ * - 'throw': Throw an error (default)
384
+ * - 'ignore': Silently cancel the transition
385
+ *
386
+ * Note: For custom fallback machines, use the error hook in createMiddleware:
387
+ * @example
388
+ * createMiddleware(machine, {
389
+ * error: ({ error, context }) => {
390
+ * if (error.message.includes('Guard failed')) {
391
+ * return createMachine({ ...context, error: 'Unauthorized' }, machine);
392
+ * }
393
+ * }
394
+ * });
395
+ */
396
+ onFail?: 'throw' | 'ignore';
397
+ }
398
+ /**
399
+ * Guard middleware that prevents transitions based on predicate functions.
400
+ * Implements fundamental FSM guard concept - transitions only occur when guards pass.
401
+ *
402
+ * @template M - The machine type
403
+ * @param machine - The machine to protect with guards
404
+ * @param guards - Object mapping transition names to guard configurations
405
+ * @returns A new machine with guard checks
406
+ *
407
+ * @example
408
+ * const guarded = withGuards(counter, {
409
+ * decrement: {
410
+ * guard: ({ context }) => context.count > 0,
411
+ * onFail: 'throw' // or 'ignore'
412
+ * },
413
+ * delete: {
414
+ * guard: ({ context }) => context.user?.isAdmin === true,
415
+ * onFail: 'throw'
416
+ * }
417
+ * });
418
+ *
419
+ * guarded.decrement(); // Throws if count === 0
420
+ *
421
+ * // For custom fallback machines, combine with error middleware:
422
+ * const guardedWithFallback = createMiddleware(guarded, {
423
+ * error: ({ error, context }) => {
424
+ * if (error.message.includes('Guard failed')) {
425
+ * return createMachine({ ...context, error: 'Unauthorized' }, machine);
426
+ * }
427
+ * }
428
+ * });
429
+ */
430
+ export declare function withGuards<M extends BaseMachine<any>>(machine: M, guards: Record<string, GuardConfig<Context<M>> | ((ctx: MiddlewareContext<Context<M>>, ...args: any[]) => boolean | Promise<boolean>)>, options?: Pick<MiddlewareOptions, 'mode'>): M;
431
+ /**
432
+ * Creates conditional middleware that only applies to specific transitions.
433
+ * Useful for targeted instrumentation without affecting all transitions.
434
+ *
435
+ * @template M - The machine type
436
+ * @param machine - The machine to instrument
437
+ * @param config - Configuration specifying which transitions to instrument
438
+ * @returns A new machine with conditional middleware
439
+ *
440
+ * @example
441
+ * const conditional = createConditionalMiddleware(counter, {
442
+ * only: ['delete', 'update'], // Only these transitions
443
+ * hooks: {
444
+ * before: ({ transitionName }) => console.log('Sensitive operation:', transitionName),
445
+ * after: ({ transitionName }) => auditLog(transitionName)
446
+ * }
447
+ * });
448
+ *
449
+ * @example
450
+ * const excluding = createConditionalMiddleware(counter, {
451
+ * except: ['increment'], // All except these
452
+ * hooks: {
453
+ * before: ({ transitionName, args }) => validate(transitionName, args)
454
+ * }
455
+ * });
456
+ */
457
+ export declare function createConditionalMiddleware<M extends BaseMachine<any>>(machine: M, config: {
458
+ /** Only apply to these transitions (mutually exclusive with except) */
459
+ only?: string[];
460
+ /** Apply to all except these transitions (mutually exclusive with only) */
461
+ except?: string[];
462
+ /** Middleware hooks to apply */
463
+ hooks: MiddlewareHooks<Context<M>>;
464
+ /** Middleware options */
465
+ options?: MiddlewareOptions;
466
+ }): M;
467
+ /**
468
+ * Creates state-dependent middleware that only applies when a predicate is true.
469
+ * Allows middleware behavior to change based on current context/state.
470
+ *
471
+ * @template M - The machine type
472
+ * @param machine - The machine to instrument
473
+ * @param config - Configuration with predicate and hooks
474
+ * @returns A new machine with state-dependent middleware
475
+ *
476
+ * @example
477
+ * const stateful = createStateMiddleware(counter, {
478
+ * when: (ctx) => ctx.debugMode === true,
479
+ * hooks: {
480
+ * before: (ctx) => console.log('Debug:', ctx),
481
+ * after: (result) => console.log('Debug result:', result)
482
+ * }
483
+ * });
484
+ *
485
+ * // Logging only happens when context.debugMode === true
486
+ */
487
+ export declare function createStateMiddleware<M extends BaseMachine<any>>(machine: M, config: {
488
+ /** Predicate that determines if middleware should apply */
489
+ when: (ctx: Context<M>) => boolean | Promise<boolean>;
490
+ /** Middleware hooks to apply when predicate is true */
491
+ hooks: MiddlewareHooks<Context<M>>;
492
+ /** Middleware options */
493
+ options?: MiddlewareOptions;
494
+ }): M;
495
+ /**
496
+ * Represents a recorded transition call in the history.
497
+ */
498
+ export interface HistoryEntry {
499
+ /** Unique ID for this history entry */
500
+ id: string;
501
+ /** The transition that was called */
502
+ transitionName: string;
503
+ /** Arguments passed to the transition */
504
+ args: any[];
505
+ /** Timestamp when the transition was called */
506
+ timestamp: number;
507
+ /** Optional serialized version of args (if serializer provided) */
508
+ serializedArgs?: string;
509
+ }
510
+ /**
511
+ * Generic serializer/deserializer interface.
512
+ * Used for serializing history arguments, context snapshots, etc.
513
+ * @template T - The type being serialized
514
+ */
515
+ export interface Serializer<T = any> {
516
+ /** Serialize data to a string */
517
+ serialize: (data: T) => string;
518
+ /** Deserialize string back to data */
519
+ deserialize: (serialized: string) => T;
520
+ }
521
+ /**
522
+ * History tracking middleware that records all transition calls.
523
+ * Useful for debugging, replay, undo/redo, and audit logging.
524
+ *
525
+ * @template M - The machine type
526
+ * @param machine - The machine to track
527
+ * @param options - Configuration options
528
+ * @returns A new machine with history tracking and a history array
529
+ *
530
+ * Note: Arguments are shallow-cloned by default. If you need deep cloning or
531
+ * serialization for persistence, provide a serializer:
532
+ *
533
+ * @example
534
+ * const tracked = withHistory(counter, {
535
+ * maxSize: 100,
536
+ * serializer: {
537
+ * serialize: (args) => JSON.stringify(args), // For deep clone or persistence
538
+ * deserialize: (str) => JSON.parse(str)
539
+ * }
540
+ * });
541
+ *
542
+ * tracked.increment();
543
+ * tracked.add(5);
544
+ * console.log(tracked.history); // [{ transitionName: 'increment', args: [], ... }, ...]
545
+ * tracked.clearHistory(); // Clear history
546
+ */
547
+ export declare function withHistory<M extends BaseMachine<any>>(machine: M, options?: {
548
+ /** Maximum number of entries to keep (default: unlimited) */
549
+ maxSize?: number;
550
+ /** Optional serializer for arguments */
551
+ serializer?: Serializer<any[]>;
552
+ /** Filter function to exclude certain transitions from history */
553
+ filter?: (transitionName: string, args: any[]) => boolean;
554
+ /** Callback when new entry is added */
555
+ onEntry?: (entry: HistoryEntry) => void;
556
+ /** Internal flag to prevent rewrapping */
557
+ _isRewrap?: boolean;
558
+ }): WithHistory<M>;
559
+ /**
560
+ * Represents a snapshot of context at a point in time.
561
+ * @template C - The context type
562
+ */
563
+ export interface ContextSnapshot<C extends object = any> {
564
+ /** Unique ID for this snapshot */
565
+ id: string;
566
+ /** The transition that caused this change */
567
+ transitionName: string;
568
+ /** Context before the transition */
569
+ before: C;
570
+ /** Context after the transition */
571
+ after: C;
572
+ /** Timestamp of the snapshot */
573
+ timestamp: number;
574
+ /** Optional serialized version of contexts */
575
+ serializedBefore?: string;
576
+ serializedAfter?: string;
577
+ /** Optional diff information */
578
+ diff?: any;
579
+ }
580
+ /**
581
+ * Snapshot middleware that records context before/after each transition.
582
+ * Useful for time-travel debugging, undo/redo, and state inspection.
583
+ *
584
+ * @template M - The machine type
585
+ * @param machine - The machine to track
586
+ * @param options - Configuration options
587
+ * @returns A new machine with snapshot tracking and snapshots array
588
+ *
589
+ * @example
590
+ * const tracked = withSnapshot(counter, {
591
+ * maxSize: 50,
592
+ * serializer: {
593
+ * serialize: (ctx) => JSON.stringify(ctx),
594
+ * deserialize: (str) => JSON.parse(str)
595
+ * },
596
+ * captureSnapshot: (before, after) => ({
597
+ * changed: before.count !== after.count
598
+ * })
599
+ * });
600
+ *
601
+ * tracked.increment();
602
+ * console.log(tracked.snapshots); // [{ before: { count: 0 }, after: { count: 1 }, ... }]
603
+ *
604
+ * // Time-travel: restore to previous state
605
+ * const previousState = tracked.restoreSnapshot(tracked.snapshots[0].before);
606
+ */
607
+ export declare function withSnapshot<M extends BaseMachine<any>>(machine: M, options?: {
608
+ /** Maximum number of snapshots to keep (default: unlimited) */
609
+ maxSize?: number;
610
+ /** Optional serializer for context */
611
+ serializer?: Serializer<Context<M>>;
612
+ /** Custom function to capture additional snapshot data */
613
+ captureSnapshot?: (before: Context<M>, after: Context<M>) => any;
614
+ /** Only capture snapshots where context actually changed */
615
+ onlyIfChanged?: boolean;
616
+ /** Filter function to exclude certain transitions from snapshots */
617
+ filter?: (transitionName: string) => boolean;
618
+ /** Callback when new snapshot is taken */
619
+ onSnapshot?: (snapshot: ContextSnapshot<Context<M>>) => void;
620
+ /** Additional properties to exclude from middleware (for composition) */
621
+ _extraExclusions?: string[];
622
+ /** Internal flag to prevent rewrapping */
623
+ _isRewrap?: boolean;
624
+ }): WithSnapshot<M, Context<M>>;
625
+ /**
626
+ * Combined history and snapshot middleware for full time-travel debugging.
627
+ * Records both transition calls and context changes.
628
+ *
629
+ * @template M - The machine type
630
+ * @param machine - The machine to track
631
+ * @param options - Configuration options
632
+ * @returns Machine with both history and snapshot tracking
633
+ *
634
+ * @example
635
+ * const tracker = withTimeTravel(counter, {
636
+ * maxSize: 100,
637
+ * serializer: {
638
+ * serialize: (data) => JSON.stringify(data),
639
+ * deserialize: (str) => JSON.parse(str)
640
+ * }
641
+ * });
642
+ *
643
+ * tracker.increment();
644
+ * tracker.add(5);
645
+ *
646
+ * console.log(tracker.history); // All transitions
647
+ * console.log(tracker.snapshots); // All state changes
648
+ *
649
+ * // Replay from a specific snapshot
650
+ * const replayed = tracker.replayFrom(0);
651
+ *
652
+ * // Restore to specific snapshot
653
+ * const restored = tracker.restoreSnapshot(tracker.snapshots[0].before);
654
+ *
655
+ * // Clear all tracking data
656
+ * tracker.clearTimeTravel();
657
+ */
658
+ export declare function withTimeTravel<M extends BaseMachine<any>>(machine: M, options?: {
659
+ /** Maximum size for both history and snapshots */
660
+ maxSize?: number;
661
+ /** Serializer for both args and context */
662
+ serializer?: Serializer<any>;
663
+ /** Callback for each recorded action */
664
+ onRecord?: (type: 'history' | 'snapshot', data: any) => void;
665
+ }): WithTimeTravel<M, Context<M>>;
666
+ /**
667
+ * Compose multiple middleware functions into a single middleware stack.
668
+ * Middleware is applied left-to-right (first middleware wraps outermost).
669
+ *
670
+ * @template M - The machine type
671
+ * @param machine - The base machine
672
+ * @param middlewares - Array of middleware functions
673
+ * @returns A new machine with all middleware applied
674
+ *
675
+ * @example
676
+ * const instrumented = compose(
677
+ * counter,
678
+ * withLogging,
679
+ * withAnalytics(analytics.track),
680
+ * withValidation(validator),
681
+ * withErrorReporting(Sentry.captureException)
682
+ * );
683
+ */
684
+ export declare function compose<M extends BaseMachine<any>>(machine: M, ...middlewares: Array<(m: M) => M>): M;
685
+ /**
686
+ * Create a reusable middleware function from hooks.
687
+ * Useful for defining custom middleware that can be applied to multiple machines.
688
+ *
689
+ * @template M - The machine type
690
+ * @param hooks - Middleware hooks configuration
691
+ * @param options - Middleware options
692
+ * @returns A middleware function that can be applied to machines
693
+ *
694
+ * @example
695
+ * const myMiddleware = createCustomMiddleware({
696
+ * before: ({ transitionName }) => console.log('Before:', transitionName),
697
+ * after: ({ transitionName }) => console.log('After:', transitionName)
698
+ * });
699
+ *
700
+ * const machine1 = myMiddleware(counter1);
701
+ * const machine2 = myMiddleware(counter2);
702
+ */
703
+ export declare function createCustomMiddleware<M extends BaseMachine<any>>(hooks: MiddlewareHooks<Context<M>>, options?: MiddlewareOptions): (machine: M) => M;
704
+ /**
705
+ * A middleware function that transforms a machine.
706
+ * @template M - The input machine type
707
+ * @template R - The output machine type (usually extends M)
708
+ */
709
+ /**
710
+ * A middleware function that transforms a machine.
711
+ * @template M - Input machine type
712
+ * @template R - Output machine type (defaults to same as input if no augmentation)
713
+ */
714
+ export type MiddlewareFn<M extends BaseMachine<any>, R extends BaseMachine<any> = M> = (machine: M) => R;
715
+ /**
716
+ * A conditional middleware that may or may not be applied based on a predicate.
717
+ * @template M - The machine type
718
+ */
719
+ export type ConditionalMiddleware<M extends BaseMachine<any>> = {
720
+ /** The middleware function to apply */
721
+ middleware: MiddlewareFn<M>;
722
+ /** Predicate function that determines if the middleware should be applied */
723
+ when: (machine: M) => boolean;
724
+ };
725
+ /**
726
+ * A named middleware entry for registry-based composition.
727
+ * @template M - The machine type
728
+ */
729
+ export type NamedMiddleware<M extends BaseMachine<any>> = {
730
+ /** Unique name for the middleware */
731
+ name: string;
732
+ /** The middleware function */
733
+ middleware: MiddlewareFn<M>;
734
+ /** Optional description */
735
+ description?: string;
736
+ /** Optional priority for ordering (higher numbers = applied later) */
737
+ priority?: number;
738
+ };
739
+ /**
740
+ * Configuration for middleware pipeline execution.
741
+ */
742
+ export interface PipelineConfig {
743
+ /** Whether to continue executing remaining middlewares if one fails */
744
+ continueOnError?: boolean;
745
+ /** Whether to log errors to console */
746
+ logErrors?: boolean;
747
+ /** Custom error handler */
748
+ onError?: (error: Error, middlewareName?: string) => void;
749
+ }
750
+ /**
751
+ * Result of pipeline execution.
752
+ */
753
+ export interface PipelineResult<M extends BaseMachine<any>> {
754
+ /** The final machine after all middlewares */
755
+ machine: M;
756
+ /** Any errors that occurred during execution */
757
+ errors: Array<{
758
+ error: Error;
759
+ middlewareIndex: number;
760
+ middlewareName?: string;
761
+ }>;
762
+ /** Whether the pipeline completed successfully */
763
+ success: boolean;
764
+ }
765
+ /**
766
+ * Compose multiple middlewares with improved type inference.
767
+ * This is a more typesafe version of the basic compose function.
768
+ *
769
+ * @template M - The initial machine type
770
+ * @template Ms - Array of middleware functions
771
+ * @param machine - The initial machine
772
+ * @param middlewares - Array of middleware functions to apply
773
+ * @returns The machine with all middlewares applied
774
+ *
775
+ * @example
776
+ * const enhanced = composeTyped(
777
+ * counter,
778
+ * withHistory(),
779
+ * withSnapshot(),
780
+ * withTimeTravel()
781
+ * );
782
+ */
783
+ /**
784
+ * Recursively applies middlewares to infer the final machine type.
785
+ * Provides precise type inference for middleware composition chains.
786
+ */
787
+ type ComposeResult<M extends BaseMachine<any>, Ms extends readonly MiddlewareFn<any, any>[]> = Ms extends readonly [] ? M : Ms extends readonly [infer First, ...infer Rest] ? First extends MiddlewareFn<any, infer Output> ? Rest extends readonly MiddlewareFn<any, any>[] ? ComposeResult<Output, Rest> : Output : M : M;
788
+ /**
789
+ * Type-safe middleware composition with perfect inference.
790
+ * Composes multiple middlewares into a single transformation chain.
791
+ *
792
+ * @template M - The input machine type
793
+ * @template Ms - Array of middleware functions
794
+ * @param machine - The machine to enhance
795
+ * @param middlewares - Middleware functions to apply in order
796
+ * @returns The machine with all middlewares applied, with precise type inference
797
+ *
798
+ * @example
799
+ * ```typescript
800
+ * const enhanced = composeTyped(
801
+ * counter,
802
+ * withHistory(),
803
+ * withSnapshot(),
804
+ * withTimeTravel()
805
+ * );
806
+ * // enhanced: WithTimeTravel<WithSnapshot<WithHistory<Counter>>>
807
+ * // Perfect IntelliSense for all methods and properties
808
+ * ```
809
+ */
810
+ export declare function composeTyped<M extends BaseMachine<any>, Ms extends readonly MiddlewareFn<any, any>[]>(machine: M, ...middlewares: Ms): ComposeResult<M, Ms>;
811
+ /**
812
+ * Type-safe middleware composition with fluent API.
813
+ * Allows building middleware chains with method chaining.
814
+ *
815
+ * @example
816
+ * ```typescript
817
+ * const enhanced = chain(counter)
818
+ * .with(withHistory())
819
+ * .with(withSnapshot())
820
+ * .with(withTimeTravel())
821
+ * .build();
822
+ * ```
823
+ */
824
+ export declare function chain<M extends BaseMachine<any>>(machine: M): MiddlewareChainBuilder<M>;
825
+ /**
826
+ * Fluent middleware composer for building complex middleware chains.
827
+ * Provides excellent TypeScript inference and IntelliSense.
828
+ */
829
+ declare class MiddlewareChainBuilder<M extends BaseMachine<any>> {
830
+ private machine;
831
+ constructor(machine: M);
832
+ /**
833
+ * Add a middleware to the composition chain.
834
+ * @param middleware - The middleware function to add
835
+ * @returns A new composer with the middleware applied
836
+ */
837
+ with<M2 extends MiddlewareFn<any, any>>(middleware: M2): MiddlewareChainBuilder<ReturnType<M2> extends BaseMachine<any> ? ReturnType<M2> : M>;
838
+ /**
839
+ * Build the final machine with all middlewares applied.
840
+ */
841
+ build(): M;
842
+ }
843
+ /**
844
+ * Common middleware combination types for better DX.
845
+ * These types help with inference when using popular middleware combinations.
846
+ */
847
+ export type WithDebugging<M extends BaseMachine<any>> = WithTimeTravel<WithSnapshot<WithHistory<M>>>;
848
+ /**
849
+ * Convenience function for the most common debugging middleware stack.
850
+ * Combines history, snapshots, and time travel for full debugging capabilities.
851
+ *
852
+ * @example
853
+ * ```typescript
854
+ * const debugMachine = withDebugging(counter);
855
+ * debugMachine.increment();
856
+ * debugMachine.history; // Full transition history
857
+ * debugMachine.snapshots; // Context snapshots
858
+ * debugMachine.replayFrom(0); // Time travel
859
+ * ```
860
+ */
861
+ export declare function withDebugging<M extends BaseMachine<any>>(machine: M): WithDebugging<M>;
862
+ /**
863
+ * Create a middleware pipeline with error handling and conditional execution.
864
+ *
865
+ * @template M - The machine type
866
+ * @param config - Pipeline configuration
867
+ * @returns A function that executes middlewares in a pipeline
868
+ *
869
+ * @example
870
+ * const pipeline = createPipeline({ continueOnError: true });
871
+ *
872
+ * const result = pipeline(
873
+ * counter,
874
+ * withHistory(),
875
+ * withSnapshot(),
876
+ * { middleware: withLogging(), when: (m) => m.context.debug }
877
+ * );
878
+ */
879
+ export declare function createPipeline<M extends BaseMachine<any>>(config?: PipelineConfig): {
880
+ <Ms extends Array<MiddlewareFn<M> | ConditionalMiddleware<M>>>(machine: M, ...middlewares: Ms): PipelineResult<M>;
881
+ };
882
+ /**
883
+ * Create a middleware registry for named middleware composition.
884
+ * Useful for building complex middleware stacks from reusable components.
885
+ *
886
+ * @template M - The machine type
887
+ *
888
+ * @example
889
+ * const registry = createMiddlewareRegistry<CounterMachine>()
890
+ * .register('history', withHistory(), 'Track state changes')
891
+ * .register('snapshot', withSnapshot(), 'Take context snapshots', 10)
892
+ * .register('timeTravel', withTimeTravel(), 'Enable time travel debugging', 20);
893
+ *
894
+ * const machine = registry.apply(counter, ['history', 'snapshot', 'timeTravel']);
895
+ */
896
+ export declare function createMiddlewareRegistry<M extends BaseMachine<any>>(): {
897
+ /**
898
+ * Register a middleware with a name and optional metadata.
899
+ */
900
+ register(name: string, middleware: MiddlewareFn<M>, description?: string, priority?: number): {
901
+ register(name: string, middleware: MiddlewareFn<M>, description?: string, priority?: number): /*elided*/ any;
902
+ /**
903
+ * Unregister a middleware by name.
904
+ */
905
+ unregister(name: string): boolean;
906
+ /**
907
+ * Check if a middleware is registered.
908
+ */
909
+ has(name: string): boolean;
910
+ /**
911
+ * Get a registered middleware by name.
912
+ */
913
+ get(name: string): NamedMiddleware<M> | undefined;
914
+ /**
915
+ * List all registered middlewares.
916
+ */
917
+ list(): NamedMiddleware<M>[];
918
+ /**
919
+ * Apply a selection of registered middlewares to a machine.
920
+ * Middlewares are applied in priority order (lowest to highest).
921
+ */
922
+ apply(machine: M, middlewareNames: string[]): M;
923
+ /**
924
+ * Apply all registered middlewares to a machine in priority order.
925
+ */
926
+ applyAll(machine: M): M;
927
+ };
928
+ /**
929
+ * Unregister a middleware by name.
930
+ */
931
+ unregister(name: string): boolean;
932
+ /**
933
+ * Check if a middleware is registered.
934
+ */
935
+ has(name: string): boolean;
936
+ /**
937
+ * Get a registered middleware by name.
938
+ */
939
+ get(name: string): NamedMiddleware<M> | undefined;
940
+ /**
941
+ * List all registered middlewares.
942
+ */
943
+ list(): NamedMiddleware<M>[];
944
+ /**
945
+ * Apply a selection of registered middlewares to a machine.
946
+ * Middlewares are applied in priority order (lowest to highest).
947
+ */
948
+ apply(machine: M, middlewareNames: string[]): M;
949
+ /**
950
+ * Apply all registered middlewares to a machine in priority order.
951
+ */
952
+ applyAll(machine: M): M;
953
+ };
954
+ /**
955
+ * Create a conditional middleware that only applies when a predicate is true.
956
+ *
957
+ * @template M - The machine type
958
+ * @param middleware - The middleware to conditionally apply
959
+ * @param predicate - Function that determines when to apply the middleware
960
+ * @returns A conditional middleware that can be called directly or used in pipelines
961
+ *
962
+ * @example
963
+ * const debugMiddleware = when(
964
+ * withTimeTravel(),
965
+ * (machine) => machine.context.debugMode
966
+ * );
967
+ *
968
+ * // Can be called directly
969
+ * const machine = debugMiddleware(baseMachine);
970
+ *
971
+ * // Can also be used in pipelines
972
+ * const pipeline = createPipeline();
973
+ * const result = pipeline(machine, debugMiddleware);
974
+ */
975
+ export declare function when<M extends BaseMachine<any>>(middleware: MiddlewareFn<M>, predicate: (machine: M) => boolean): ConditionalMiddleware<M> & MiddlewareFn<M>;
976
+ /**
977
+ * Create a middleware that only applies in development mode.
978
+ *
979
+ * @template M - The machine type
980
+ * @param middleware - The middleware to apply in development
981
+ * @returns A conditional middleware for development mode
982
+ *
983
+ * @example
984
+ * const devMachine = composeTyped(
985
+ * counter,
986
+ * inDevelopment(withTimeTravel())
987
+ * );
988
+ */
989
+ export declare function inDevelopment<M extends BaseMachine<any>>(middleware: MiddlewareFn<M>): ConditionalMiddleware<M> & MiddlewareFn<M>;
990
+ /**
991
+ * Create a middleware that only applies when a context property matches a value.
992
+ *
993
+ * @template M - The machine type
994
+ * @template K - The context key
995
+ * @param key - The context property key
996
+ * @param value - The value to match
997
+ * @param middleware - The middleware to apply when the condition matches
998
+ * @returns A conditional middleware
999
+ *
1000
+ * @example
1001
+ * const adminMachine = composeTyped(
1002
+ * userMachine,
1003
+ * whenContext('role', 'admin', withAdminFeatures())
1004
+ * );
1005
+ */
1006
+ export declare function whenContext<M extends BaseMachine<any>, K extends keyof Context<M>>(key: K, value: Context<M>[K], middleware: MiddlewareFn<M>): ConditionalMiddleware<M> & MiddlewareFn<M>;
1007
+ /**
1008
+ * Combine multiple middlewares with short-circuiting.
1009
+ * If any middleware returns a different type, the composition stops.
1010
+ *
1011
+ * @template M - The machine type
1012
+ * @param middlewares - Array of middlewares to combine
1013
+ * @returns A combined middleware function
1014
+ *
1015
+ * @example
1016
+ * const combined = combine(
1017
+ * withHistory(),
1018
+ * withSnapshot(),
1019
+ * withValidation()
1020
+ * );
1021
+ */
1022
+ export declare function combine<M extends BaseMachine<any>>(...middlewares: Array<MiddlewareFn<M>>): MiddlewareFn<M>;
1023
+ /**
1024
+ * Create a middleware that applies different middlewares based on context.
1025
+ *
1026
+ * @template M - The machine type
1027
+ * @param branches - Array of [predicate, middleware] pairs
1028
+ * @param fallback - Optional fallback middleware if no predicates match
1029
+ * @returns A branching middleware
1030
+ *
1031
+ * @example
1032
+ * const smartMiddleware = branch(
1033
+ * [(m) => m.context.userType === 'admin', withAdminFeatures()],
1034
+ * [(m) => m.context.debug, withTimeTravel()],
1035
+ * withBasicLogging() // fallback
1036
+ * );
1037
+ */
1038
+ export declare function branch<M extends BaseMachine<any>>(branches: Array<[predicate: (machine: M) => boolean, middleware: MiddlewareFn<M>]>, fallback?: MiddlewareFn<M>): MiddlewareFn<M>;
1039
+ /**
1040
+ * Type guard to check if a value is a middleware function.
1041
+ */
1042
+ export declare function isMiddlewareFn<M extends BaseMachine<any>>(value: any): value is MiddlewareFn<M>;
1043
+ /**
1044
+ * Type guard to check if a value is a conditional middleware.
1045
+ */
1046
+ export declare function isConditionalMiddleware<M extends BaseMachine<any>>(value: any): value is ConditionalMiddleware<M>;
1047
+ export {};
1048
+ //# sourceMappingURL=middleware.d.ts.map