@doeixd/machine 0.0.6 โ†’ 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.
package/README.md CHANGED
@@ -8,6 +8,8 @@ A minimal, type-safe state machine library for TypeScript.
8
8
 
9
9
  > **Philosophy**: Provide minimal primitives that capture the essence of finite state machines, with maximum type safety and flexibility. **Type-State Programming** is our core paradigmโ€”we use TypeScript's type system itself to represent finite states, making illegal states unrepresentable and invalid transitions impossible to write. The compiler becomes your safety net, catching state-related bugs before your code ever runs.
10
10
 
11
+ > **Middleware System**: For production-ready state machines, we provide a comprehensive middleware system for cross-cutting concerns like logging, analytics, validation, error handling, and debugging. **๐Ÿ“– [Read the Middleware Guide](./docs/middleware.md)**
12
+
11
13
  ## Installation
12
14
 
13
15
  ```bash
@@ -59,6 +61,10 @@ type Machine<C extends object> = {
59
61
 
60
62
  **Read more about our core principles:** [ ๐Ÿ“– Core Principles Guide ](./docs/principles.md)
61
63
 
64
+ ## Choosing the Right Pattern
65
+
66
+ The library offers multiple patterns for different use cases. **๐Ÿ“– [Pattern Decision Guide](./docs/patterns.md)** - A comprehensive guide to help you choose between Basic Machines, Runner, Ensemble, Generators, Classes, and more.
67
+
62
68
  ## Quick Start
63
69
 
64
70
  ### Basic Counter (Simple State)
@@ -499,6 +505,68 @@ import { next } from "@doeixd/machine";
499
505
  const updated = next(counter, (ctx) => ({ count: ctx.count + 1 }));
500
506
  ```
501
507
 
508
+ ### Transition Binding Helpers
509
+
510
+ These utilities eliminate the need for `.call(m.context, ...)` boilerplate when invoking transitions.
511
+
512
+ #### `call<C, F>(fn, context, ...args)`
513
+
514
+ Explicitly binds a transition function to a context and invokes it. Useful when you need to call a transition with proper `this` binding.
515
+
516
+ ```typescript
517
+ import { call } from "@doeixd/machine";
518
+
519
+ type MyContext = { count: number };
520
+ const increment = function(this: MyContext) {
521
+ return { count: this.count + 1 };
522
+ };
523
+
524
+ const result = call(increment, { count: 5 }); // Returns { count: 6 }
525
+
526
+ // Particularly useful with generator-based flows:
527
+ const result = run(function* (m) {
528
+ m = yield* step(call(m.increment, m.context));
529
+ m = yield* step(call(m.add, m.context, 5));
530
+ return m;
531
+ }, counter);
532
+ ```
533
+
534
+ #### `bindTransitions<M>(machine)`
535
+
536
+ Returns a Proxy that automatically binds all transition methods to the machine's context. Eliminates `.call(m.context, ...)` boilerplate entirely.
537
+
538
+ ```typescript
539
+ import { bindTransitions } from "@doeixd/machine";
540
+
541
+ const counter = bindTransitions(createMachine(
542
+ { count: 0 },
543
+ {
544
+ increment(this: { count: number }) {
545
+ return createMachine({ count: this.count + 1 }, this);
546
+ },
547
+ add(this: { count: number }, n: number) {
548
+ return createMachine({ count: this.count + n }, this);
549
+ }
550
+ }
551
+ ));
552
+
553
+ // All methods are automatically bound - no need for .call()!
554
+ const next = counter.increment(); // Works!
555
+ const result = counter.add(5); // Works!
556
+
557
+ // Great for generator-based flows:
558
+ const result = run(function* (m) {
559
+ m = yield* step(m.increment()); // Clean syntax!
560
+ m = yield* step(m.add(5)); // No .call() needed
561
+ return m;
562
+ }, counter);
563
+ ```
564
+
565
+ **How it works:**
566
+ The Proxy intercepts all property access on the machine. When a property is a function (transition method), it wraps it to automatically call `.apply(machine.context, args)` before invoking. Non-callable properties are returned as-is.
567
+
568
+ **Note:** The Proxy preserves type safety while providing ergonomic syntax. Use this when writing generator-based flows or any code that frequently calls transitions.
569
+
502
570
  #### `matchMachine<M, K, R>(machine, key, handlers)`
503
571
 
504
572
  Type-safe pattern matching on discriminated unions in context.
@@ -594,6 +662,41 @@ const alice = buildUser({ id: 1, name: "Alice" });
594
662
  const bob = buildUser({ id: 2, name: "Bob" });
595
663
  ```
596
664
 
665
+ ### Middleware System
666
+
667
+ For production-ready state machines with logging, analytics, validation, error handling, and debugging capabilities:
668
+
669
+ ```typescript
670
+ import { createMiddleware, withLogging, withValidation, withAnalytics } from "@doeixd/machine";
671
+
672
+ // Wrap machines with middleware
673
+ const instrumented = createMiddleware(machine, {
674
+ before: ({ transitionName, context, args }) => {
675
+ console.log(`โ†’ ${transitionName}`, args);
676
+ },
677
+ after: ({ transitionName, prevContext, nextContext }) => {
678
+ console.log(`โœ“ ${transitionName}`);
679
+ },
680
+ error: ({ transitionName, error }) => {
681
+ console.error(`โœ— ${transitionName}:`, error);
682
+ }
683
+ });
684
+
685
+ // Or use pre-built middleware
686
+ const logged = withLogging(machine);
687
+ const validated = withValidation(machine, validateFn);
688
+ const tracked = withAnalytics(machine, trackEvent);
689
+ ```
690
+
691
+ **Features:**
692
+ - Type-safe interception layer
693
+ - Pre-built middleware for common use cases
694
+ - History tracking and time-travel debugging
695
+ - Performance monitoring and error reporting
696
+ - Composable and configurable
697
+
698
+ **๐Ÿ“– [Complete Middleware Documentation](./docs/middleware.md)**
699
+
597
700
  ### Type Utilities
598
701
 
599
702
  #### Type Extraction
@@ -649,7 +752,7 @@ For advanced use cases, the library provides optional patterns that offer better
649
752
 
650
753
  **Runner (createRunner):** A stateful controller that wraps a single machine. It provides a stable actions object (runner.actions.increment()) to eliminate the need for manual state reassignment, which is ideal for complex local state.
651
754
 
652
- **Ensemble (createEnsemble / createMultiMachine):** An orchestration engine that decouples state logic from state storage. It plugs into external stores (like React or Solid state) to create framework-agnostic, global state machines.
755
+ **Ensemble (createEnsemble / createMultiMachine):** Coordinates multiple independent state machines that share the same context store, like musicians in an orchestra following a shared conductor. Each machine handles its domain (auth, data, UI) while operating on the same global state.
653
756
 
654
757
  ### Managed State with Runner & Ensemble
655
758
 
@@ -687,312 +790,129 @@ if (runner.state.context.status === 'loggedIn') {
687
790
  - Perfect for React hooks, component state, or form handling
688
791
  - Type-safe state narrowing still works
689
792
 
690
- #### Ensemble: Framework-Agnostic Global State Orchestration
793
+ #### Ensemble: Coordinating Multiple Machines with Shared Context
691
794
 
692
- The `Ensemble` decouples state logic (the machine) from state storage, plugging into any state management solution (React hooks, Solid stores, Zustand, Redux, etc.) via a simple `StateStore` interface.
795
+ The `Ensemble` coordinates multiple independent state machines that all operate on the same shared context store, like musicians in an orchestra following a shared conductor. Each machine handles its own domain while reading/writing to the same global state.
693
796
 
694
797
  ```typescript
695
798
  import { createEnsemble } from "@doeixd/machine/multi";
696
799
 
697
- // 1. Define your state store interface
698
- const store = {
699
- getContext: () => sharedContext,
700
- setContext: (newCtx) => { sharedContext = newCtx; }
800
+ // Shared application state
801
+ type AppState = {
802
+ auth: { status: 'loggedIn' | 'loggedOut'; user?: string };
803
+ data: { status: 'idle' | 'loading' | 'success'; items?: any[] };
804
+ ui: { modal: 'open' | 'closed'; theme: 'light' | 'dark' };
701
805
  };
702
806
 
703
- // 2. Define machine factories for each state
704
- const factories = {
807
+ const globalStore = {
808
+ getContext: () => appState,
809
+ setContext: (newState) => setAppState(newState)
810
+ };
811
+
812
+ // Auth ensemble - manages auth slice
813
+ const authEnsemble = createEnsemble(globalStore, {
814
+ loggedOut: (ctx) => createMachine(ctx, {
815
+ login: (user) => ({ ...ctx, auth: { status: 'loggedIn', user } })
816
+ }),
817
+ loggedIn: (ctx) => createMachine(ctx, {
818
+ logout: () => ({ ...ctx, auth: { status: 'loggedOut' } })
819
+ })
820
+ }, (ctx) => ctx.auth.status);
821
+
822
+ // Data ensemble - manages data slice
823
+ const dataEnsemble = createEnsemble(globalStore, {
705
824
  idle: (ctx) => createMachine(ctx, {
706
- fetch: () => store.setContext({ ...ctx, status: 'loading' })
825
+ fetch: async () => {
826
+ const items = await api.fetch();
827
+ return { ...ctx, data: { status: 'success', items } };
828
+ }
707
829
  }),
708
- loading: (ctx) => createMachine(ctx, {
709
- succeed: (data) => store.setContext({ ...ctx, status: 'success', data }),
710
- fail: (error) => store.setContext({ ...ctx, status: 'error', error })
830
+ loading: (ctx) => createMachine(ctx, { /* ... */ }),
831
+ success: (ctx) => createMachine(ctx, { /* ... */ })
832
+ }, (ctx) => ctx.data.status);
833
+
834
+ // UI ensemble - manages UI slice
835
+ const uiEnsemble = createEnsemble(globalStore, {
836
+ closed: (ctx) => createMachine(ctx, {
837
+ open: () => ({ ...ctx, ui: { ...ctx.ui, modal: 'open' } })
711
838
  }),
712
- success: (ctx) => createMachine(ctx, {
713
- refetch: () => store.setContext({ ...ctx, status: 'loading' })
839
+ open: (ctx) => createMachine(ctx, {
840
+ close: () => ({ ...ctx, ui: { ...ctx.ui, modal: 'closed' } })
714
841
  })
715
- };
716
-
717
- // 3. Create the Ensemble with an accessor function for refactoring safety
718
- const ensemble = createEnsemble(store, factories, (ctx) => ctx.status);
842
+ }, (ctx) => ctx.ui.modal);
719
843
 
720
- // 4. Use it with type-safe dispatch
721
- ensemble.actions.fetch(); // Transitions to loading
722
- console.log(ensemble.context.status); // 'loading'
723
-
724
- // Type narrowing
725
- if (ensemble.state.context.status === 'success') {
726
- console.log(ensemble.state.context.data); // TypeScript knows data exists
727
- }
844
+ // They coordinate through shared state
845
+ authEnsemble.actions.login('alice'); // Updates global auth status
846
+ dataEnsemble.actions.fetch(); // Reads from same global state
847
+ uiEnsemble.actions.showModal(); // Also uses same global state
728
848
  ```
729
849
 
730
850
  **Perfect for:**
731
- - Global application state orchestration
732
- - Decoupling business logic from framework-specific state management
851
+ - Coordinating multiple state machines that share context
852
+ - Complex applications with independent domains (auth, data, UI, etc.)
853
+ - Framework-agnostic state logic that works with React, Solid, Vue, etc.
854
+ - Global state orchestration across your entire application
855
+ - **Syncing to external libraries/stores** - easy integration with Zustand, Redux, databases, APIs, etc.
733
856
  - Testing (swap the store for a test stub)
734
- - Multiple framework support (same machine logic for React, Solid, Vue, etc.)
735
-
736
- **Workflow Pattern:**
737
- ```typescript
738
- // React example
739
- function MyComponent() {
740
- const [context, setContext] = useState(initialContext);
741
-
742
- const store = {
743
- getContext: () => context,
744
- setContext: setContext
745
- };
746
-
747
- const ensemble = useMemo(() =>
748
- createEnsemble(store, factories, (ctx) => ctx.status),
749
- [context]
750
- );
751
-
752
- return (
753
- <div>
754
- <p>Status: {ensemble.context.status}</p>
755
- <button onClick={() => ensemble.actions.fetch()}>Load Data</button>
756
- </div>
757
- );
758
- }
759
- ```
760
857
 
761
- #### Generator-Based Workflows with Runner & Ensemble
858
+ **Analogy**: A musical ensemble. Each musician (machine) plays their part following the same conductor (shared store). Together they create coordinated harmony, where one instrument's change can influence the others.
762
859
 
763
- Run complex, multi-step workflows imperatively using generators:
860
+ **Great for External Integration:**
861
+ The `StateStore` interface makes it trivial to sync with external systems:
764
862
 
765
863
  ```typescript
766
- import { runWithRunner, runWithEnsemble } from "@doeixd/machine/multi";
767
-
768
- // With Runner (local state)
769
- const result = runWithRunner(function* (runner) {
770
- yield runner.actions.increment();
771
- yield runner.actions.add(10);
772
- if (runner.context.count > 5) {
773
- yield runner.actions.reset();
774
- }
775
- return runner.context;
776
- }, createCounterMachine());
777
-
778
- // With Ensemble (global state)
779
- const result = runWithEnsemble(function* (ensemble) {
780
- yield ensemble.actions.fetch();
781
- yield ensemble.actions.process();
782
- if (ensemble.context.status === 'success') {
783
- yield ensemble.actions.commit();
784
- }
785
- return ensemble.context.data;
786
- }, ensemble);
787
- ```
788
-
789
- #### Mutable Machine (Experimental)
864
+ // Zustand store integration
865
+ import { create } from 'zustand';
790
866
 
791
- For non-UI environments where a stable object reference is critical, `createMutableMachine` provides a highly imperative API with direct in-place mutations.
867
+ const useAppStore = create<AppState>((set, get) => ({
868
+ // ... your Zustand store
869
+ }));
792
870
 
793
- **Key Characteristics:**
794
- - **Stable Object Reference**: The machine is a single object whose properties mutate in place
795
- - **Direct Imperative API**: Call transitions like methods (`machine.login('user')`) with immediate updates
796
- - **No State History**: Previous states are not preserved (no time-travel debugging)
797
- - **Not for Reactive UIs**: Won't trigger component re-renders in React, Solid, Vue, etc.
798
-
799
- **Best for:**
800
- - Backend services and game loops
801
- - Complex synchronous scripts and data pipelines
802
- - Non-UI environments where a stable state object is essential
803
-
804
- **Example: Authentication State**
805
-
806
- ```typescript
807
- import { createMutableMachine } from "@doeixd/machine/multi";
808
-
809
- type AuthContext =
810
- | { status: 'loggedOut'; error?: string }
811
- | { status: 'loggedIn'; username: string };
812
-
813
- const authFactories = {
814
- loggedOut: (ctx: AuthContext) => ({
815
- context: ctx,
816
- login: (username: string) => ({ status: 'loggedIn', username }),
817
- }),
818
- loggedIn: (ctx: AuthContext) => ({
819
- context: ctx,
820
- logout: () => ({ status: 'loggedOut' }),
821
- }),
871
+ const zustandStore = {
872
+ getContext: () => useAppStore.getState(),
873
+ setContext: (newState) => useAppStore.setState(newState)
822
874
  };
823
875
 
824
- const auth = createMutableMachine(
825
- { status: 'loggedOut' } as AuthContext,
826
- authFactories,
827
- (ctx) => ctx.status // Accessor function - refactor-safe
828
- );
829
-
830
- // Stable reference - keep this, the object will mutate
831
- const userRef = auth;
876
+ const ensemble = createEnsemble(zustandStore, factories, (ctx) => ctx.status);
832
877
 
833
- console.log(auth.status); // 'loggedOut'
878
+ // Redux integration
879
+ import { store } from './reduxStore';
834
880
 
835
- auth.login('alice'); // Mutates in place
836
-
837
- console.log(auth.status); // 'loggedIn'
838
- console.log(auth.username); // 'alice'
839
- console.log(userRef === auth); // true - same object reference
840
- ```
841
-
842
- **Example: Game State Loop**
843
-
844
- ```typescript
845
- type PlayerContext = {
846
- state: 'idle' | 'walking' | 'attacking';
847
- hp: number;
848
- position: { x: number; y: number };
881
+ const reduxStore = {
882
+ getContext: () => store.getState(),
883
+ setContext: (newState) => store.dispatch(setAppState(newState))
849
884
  };
850
885
 
851
- const player = createMutableMachine(
852
- { state: 'idle', hp: 100, position: { x: 0, y: 0 } },
853
- {
854
- idle: (ctx) => ({
855
- context: ctx,
856
- walk: (dx: number, dy: number) => ({
857
- ...ctx,
858
- state: 'walking',
859
- position: { x: ctx.position.x + dx, y: ctx.position.y + dy }
860
- }),
861
- attack: () => ({ ...ctx, state: 'attacking' }),
862
- }),
863
- walking: (ctx) => ({
864
- context: ctx,
865
- stop: () => ({ ...ctx, state: 'idle' }),
866
- }),
867
- attacking: (ctx) => ({
868
- context: ctx,
869
- finishAttack: () => ({ ...ctx, state: 'idle' }),
870
- }),
871
- },
872
- (ctx) => ctx.state // Accessor function - refactor-safe
873
- );
874
-
875
- // Game loop
876
- player.walk(1, 0);
877
- console.log(player.position); // { x: 1, y: 0 }
878
- console.log(player.state); // 'walking'
879
-
880
- player.stop();
881
- console.log(player.state); // 'idle'
882
- ```
883
-
884
- โš ๏ธ **Trade-offs**: Breaks immutability principle. Only use when:
885
- - Working in non-UI environments (backend, CLI, game logic)
886
- - Stable object reference is critical
887
- - You accept no reactive UI updates or state history
888
-
889
- **Not suitable for**: React, Solid, Vue, or any reactive framework.
890
-
891
- #### Comparison: Runner vs Ensemble vs Mutable Machine
892
-
893
- | Feature | Runner | Ensemble | Mutable Machine |
894
- |---------|--------|----------|-----------------|
895
- | **State Philosophy** | Immutable core with ergonomic wrapper | External immutable state store integration | Mutable in-place context |
896
- | **Primary Use Case** | Complex local/component state | Global state with framework integration | Backend, game loops, non-UI |
897
- | **API Style** | `runner.actions.increment()` | `ensemble.actions.increment()` | `machine.increment()` |
898
- | **UI Frameworks** | โœ… Excellent (React, Solid, Vue) | โœ… Designed for frameworks | โŒ Won't trigger re-renders |
899
- | **State History** | โœ… Preserved (immutable snapshots) | โœ… Preserved (by external store) | โŒ Lost (mutated in place) |
900
- | **Object Stability** | Runner reference stable, internal machine changes | Ensemble reference stable, reconstructed per access | โœ… Single object reference |
901
- | **Time-Travel Debugging** | โœ… Possible | โœ… Possible | โŒ Not possible |
902
- | **Performance** | Standard | Standard | โœ… Optimal (no allocations) |
903
-
904
- **Quick Decision Tree:**
905
-
906
- 1. **Do you need a UI framework** (React, Solid, Vue)?
907
- - **Yes** โ†’ Use **Ensemble** if global/shared state, or **Runner** if local/component state
908
- - **No** (Backend, game, CLI) โ†’ Use **Mutable Machine**
909
-
910
- 2. **Is state global/shared across your app?**
911
- - **Yes** โ†’ Use **Ensemble** (hooks into your framework's state manager)
912
- - **No** โ†’ Use **Runner** (simpler, still immutable)
913
-
914
- 3. **Do you need immutability for debugging/testing?**
915
- - **Yes** โ†’ Use **Runner** or **Ensemble**
916
- - **No** (performance critical) โ†’ Use **Mutable Machine**
917
-
918
- ##### Deep Dive: Runner (createRunner)
886
+ const ensemble = createEnsemble(reduxStore, factories, (ctx) => ctx.status);
919
887
 
920
- **The Pattern**: A stateful wrapper that handles internal reassignments for you.
921
-
922
- ```typescript
923
- // Without Runner (verbose reassignment chain)
924
- let machine = createCounterMachine();
925
- machine = machine.increment();
926
- machine = machine.add(5);
927
- machine = machine.reset();
928
-
929
- // With Runner (stable reference, less boilerplate)
930
- const runner = createRunner(createCounterMachine());
931
- runner.actions.increment();
932
- runner.actions.add(5);
933
- runner.actions.reset();
934
- console.log(runner.context); // Access state directly
935
- ```
936
-
937
- **How it Works:**
938
- - Holds a private `currentMachine` variable
939
- - Wraps each transition method to update `currentMachine` before returning
940
- - Provides stable `runner.actions` and `runner.context` references
941
- - The underlying immutable machine is still pure; the Runner just manages the reassignments
942
-
943
- **When to Use:**
944
- - Complex local state (forms, multi-step wizards, component logic)
945
- - Generator-based workflows (cleaner syntax with `yield runner.actions.xxx()`)
946
- - You want immutability's safety without constant `machine = machine.xxx()` chains
947
- - Perfect for React component state or Solid signals
948
-
949
- **Analogy**: An automatic transmission. The immutable engine is still doing powerful, pure work. The Runner just handles the "gear shifting" automatically.
950
-
951
- ##### Deep Dive: Ensemble (createEnsemble)
888
+ // Database/API integration
889
+ const apiStore = {
890
+ getContext: async () => await api.getAppState(),
891
+ setContext: async (newState) => {
892
+ await api.saveAppState(newState);
893
+ // Trigger real-time updates to other clients
894
+ socket.emit('state-changed', newState);
895
+ }
896
+ };
952
897
 
953
- **The Pattern**: Decouples machine logic from framework-specific state management.
898
+ const ensemble = createEnsemble(apiStore, factories, (ctx) => ctx.status);
954
899
 
955
- ```typescript
956
- // Your pure machine logic
957
- const factories = {
958
- idle: (ctx) => createMachine(ctx, { fetch: () => { /* ... */ } }),
959
- loading: (ctx) => createMachine(ctx, { succeed: (data) => { /* ... */ } }),
900
+ // LocalStorage persistence
901
+ const persistentStore = {
902
+ getContext: () => {
903
+ const saved = localStorage.getItem('app-state');
904
+ return saved ? JSON.parse(saved) : defaultState;
905
+ },
906
+ setContext: (newState) => {
907
+ localStorage.setItem('app-state', JSON.stringify(newState));
908
+ return newState;
909
+ }
960
910
  };
961
911
 
962
- // Your framework's state (React example)
963
- const [context, setContext] = useState(initialContext);
964
-
965
- // The Ensemble bridges them - use an accessor function for refactoring safety
966
- const ensemble = useMemo(() =>
967
- createEnsemble(
968
- { getContext: () => context, setContext },
969
- factories,
970
- (ctx) => ctx.status // Accessor function - fully refactor-safe!
971
- ),
972
- [context]
973
- );
974
-
975
- // Type-safe dispatch from any part of your app
976
- ensemble.actions.fetch();
912
+ const ensemble = createEnsemble(persistentStore, factories, (ctx) => ctx.status);
977
913
  ```
978
914
 
979
- **How it Works:**
980
- - Takes a `StateStore` (get/set functions) that communicate with your state manager
981
- - Takes a set of factory functions that create machines for each state
982
- - When you call `ensemble.actions.fetch()`:
983
- 1. Gets current context from the store
984
- 2. Determines the active machine based on discriminant key
985
- 3. Executes the transition (which calls `store.setContext()` internally)
986
- 4. Reconstructs the machine with updated context on next access
987
-
988
- **When to Use:**
989
- - Global/application state that multiple components need
990
- - You want machine logic completely decoupled from your UI framework
991
- - Testing (swap the store for a test stub)
992
- - Portable state logic (same machine works with React, Solid, Vue, etc.)
993
- - Complex state that's read/updated from multiple places in your app
994
-
995
- **Analogy**: A custom car build. The machine provides expert logic on "how a car behaves," but you provide the engine (your framework's state manager). Perfect integration, total flexibility.
915
+ **Your machine logic stays pure** - just swap the store implementation to change how state is persisted, synchronized, or shared.
996
916
 
997
917
  ##### Deep Dive: Mutable Machine (createMutableMachine)
998
918
 
@@ -1673,6 +1593,98 @@ const charts = extractMachines({
1673
1593
  });
1674
1594
  ```
1675
1595
 
1596
+ ### Advanced Patterns: Hierarchical and Parallel Machines
1597
+
1598
+ **NEW**: The extractor now supports advanced state machine patterns for complex systems.
1599
+
1600
+ #### Hierarchical (Nested States)
1601
+
1602
+ Model parent states containing child states:
1603
+
1604
+ ```typescript
1605
+ const config: MachineConfig = {
1606
+ input: 'src/dashboard.ts',
1607
+ classes: ['Dashboard', 'ErrorState'],
1608
+ id: 'dashboard',
1609
+ initialState: 'Dashboard',
1610
+ children: {
1611
+ contextProperty: 'child',
1612
+ initialState: 'ViewingMachine',
1613
+ classes: ['ViewingMachine', 'EditingMachine']
1614
+ }
1615
+ };
1616
+ ```
1617
+
1618
+ Generates:
1619
+
1620
+ ```json
1621
+ {
1622
+ "id": "dashboard",
1623
+ "initial": "Dashboard",
1624
+ "states": {
1625
+ "Dashboard": {
1626
+ "initial": "ViewingMachine",
1627
+ "states": {
1628
+ "ViewingMachine": { "on": { /* ... */ } },
1629
+ "EditingMachine": { "on": { /* ... */ } }
1630
+ }
1631
+ }
1632
+ }
1633
+ }
1634
+ ```
1635
+
1636
+ #### Parallel (Orthogonal Regions)
1637
+
1638
+ Model independent regions that evolve simultaneously:
1639
+
1640
+ ```typescript
1641
+ const config: MachineConfig = {
1642
+ input: 'src/editor.ts',
1643
+ id: 'editor',
1644
+ parallel: {
1645
+ regions: [
1646
+ {
1647
+ name: 'fontWeight',
1648
+ initialState: 'Normal',
1649
+ classes: ['Normal', 'Bold']
1650
+ },
1651
+ {
1652
+ name: 'textDecoration',
1653
+ initialState: 'None',
1654
+ classes: ['None', 'Underline']
1655
+ }
1656
+ ]
1657
+ }
1658
+ };
1659
+ ```
1660
+
1661
+ Generates:
1662
+
1663
+ ```json
1664
+ {
1665
+ "id": "editor",
1666
+ "type": "parallel",
1667
+ "states": {
1668
+ "fontWeight": {
1669
+ "initial": "Normal",
1670
+ "states": {
1671
+ "Normal": { "on": { /* ... */ } },
1672
+ "Bold": { "on": { /* ... */ } }
1673
+ }
1674
+ },
1675
+ "textDecoration": {
1676
+ "initial": "None",
1677
+ "states": {
1678
+ "None": { "on": { /* ... */ } },
1679
+ "Underline": { "on": { /* ... */ } }
1680
+ }
1681
+ }
1682
+ }
1683
+ }
1684
+ ```
1685
+
1686
+ **See [docs/ADVANCED_EXTRACTION.md](./docs/ADVANCED_EXTRACTION.md) for complete guide.**
1687
+
1676
1688
  ### Runtime Extraction API
1677
1689
 
1678
1690
  Extract statecharts from **running machine instances** without requiring source code access:
@@ -1884,6 +1896,10 @@ overrideTransitions<M, T>(machine: M, overrides: T): M & T
1884
1896
  extendTransitions<M, T>(machine: M, newTransitions: T): M & T
1885
1897
  createMachineBuilder<M>(template: M): (context) => M
1886
1898
 
1899
+ // Transition Binding
1900
+ call<C, F>(fn: F, context: C, ...args): ReturnType<F>
1901
+ bindTransitions<M extends Machine<any>>(machine: M): M
1902
+
1887
1903
  // Pattern Matching
1888
1904
  matchMachine<M, K, R>(machine: M, key: K, handlers): R
1889
1905
  hasState<M, K, V>(machine: M, key: K, value: V): boolean