@doeixd/machine 0.0.18 β†’ 0.0.19

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,7 +8,6 @@ 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
12
  ## Installation
14
13
 
@@ -41,7 +40,7 @@ An FSM is a 5-tuple: **M = (S, Ξ£, Ξ΄, sβ‚€, F)** where:
41
40
  3. **Finite States**: Only a limited number of discrete configurations exist
42
41
 
43
42
  ### How `@doeixd/machine` Implements These Tenets
44
-
43
+ A smiplified version of the core type / primitive:
45
44
  ```typescript
46
45
  type Machine<C extends object> = {
47
46
  context: C; // Encodes the current state (s ∈ S)
@@ -819,6 +818,160 @@ function enterState(): MachineResult<{ timer: number }> {
819
818
  }
820
819
  ```
821
820
 
821
+ ### Pattern Matching
822
+
823
+ **NEW**: Advanced pattern matching utilities for type-safe discrimination between machine states.
824
+
825
+ The `createMatcher` function provides three complementary APIs for matching and narrowing machine types:
826
+
827
+ #### Quick Example
828
+
829
+ ```typescript
830
+ import { createMatcher, classCase, MachineBase } from "@doeixd/machine";
831
+
832
+ // Define state machines
833
+ class IdleMachine extends MachineBase<{ status: 'idle' }> {
834
+ start() { return new LoadingMachine(); }
835
+ }
836
+
837
+ class LoadingMachine extends MachineBase<{ status: 'loading' }> {
838
+ success(data: string) { return new SuccessMachine(data); }
839
+ error(err: Error) { return new ErrorMachine(err); }
840
+ }
841
+
842
+ class SuccessMachine extends MachineBase<{ status: 'success'; data: string }> {
843
+ reset() { return new IdleMachine(); }
844
+ }
845
+
846
+ class ErrorMachine extends MachineBase<{ status: 'error'; error: Error }> {
847
+ retry() { return new LoadingMachine(); }
848
+ }
849
+
850
+ // Create reusable matcher
851
+ const match = createMatcher(
852
+ classCase('idle', IdleMachine),
853
+ classCase('loading', LoadingMachine),
854
+ classCase('success', SuccessMachine),
855
+ classCase('error', ErrorMachine)
856
+ );
857
+
858
+ type FetchMachine = IdleMachine | LoadingMachine | SuccessMachine | ErrorMachine;
859
+
860
+ const machine: FetchMachine = new LoadingMachine();
861
+ ```
862
+
863
+ #### API 1: Type Guards
864
+
865
+ Use `match.is.<case>()` for type narrowing in conditionals:
866
+
867
+ ```typescript
868
+ if (match.is.loading(machine)) {
869
+ // βœ“ machine is narrowed to LoadingMachine
870
+ console.log(machine.context.startTime);
871
+ }
872
+
873
+ if (match.is.success(machine)) {
874
+ // βœ“ machine is narrowed to SuccessMachine
875
+ console.log(machine.context.data);
876
+ }
877
+ ```
878
+
879
+ #### API 2: Exhaustive Pattern Matching
880
+
881
+ Use `match.when(...).is(...)` for complex branching with compile-time exhaustiveness checking:
882
+
883
+ ```typescript
884
+ const message = match.when(machine).is<string>(
885
+ match.case.idle(() => 'Ready to start'),
886
+ match.case.loading(() => 'Loading...'),
887
+ match.case.success(m => `Done: ${m.context.data}`),
888
+ match.case.error(m => `Error: ${m.context.error.message}`),
889
+ match.exhaustive // ← TypeScript error if any case is missing
890
+ );
891
+ ```
892
+
893
+ **Benefits:**
894
+ - **Compile-time exhaustiveness** - TypeScript catches missing cases
895
+ - **Type narrowing** - Each handler receives the narrowed machine type
896
+ - **Reusable** - Define matcher once, use everywhere
897
+
898
+ #### API 3: Simple Match
899
+
900
+ Use `match(machine)` to get the matched case name:
901
+
902
+ ```typescript
903
+ const stateName = match(machine); // 'idle' | 'loading' | 'success' | 'error' | null
904
+
905
+ switch (stateName) {
906
+ case 'idle': return 'Ready';
907
+ case 'loading': return 'In progress';
908
+ case 'success': return 'Complete';
909
+ case 'error': return 'Failed';
910
+ default: return 'Unknown';
911
+ }
912
+ ```
913
+
914
+ #### Helper Functions
915
+
916
+ **`classCase`** - For class-based machines (most common):
917
+
918
+ ```typescript
919
+ createMatcher(
920
+ classCase('idle', IdleMachine),
921
+ classCase('loading', LoadingMachine)
922
+ );
923
+ ```
924
+
925
+ **`discriminantCase`** - For discriminated unions:
926
+
927
+ ```typescript
928
+ type Context =
929
+ | { status: 'idle' }
930
+ | { status: 'loading' }
931
+ | { status: 'success'; data: string };
932
+
933
+ const match = createMatcher(
934
+ discriminantCase<'idle', Machine<Context>, 'status', 'idle'>('idle', 'status', 'idle'),
935
+ discriminantCase<'loading', Machine<Context>, 'status', 'loading'>('loading', 'status', 'loading'),
936
+ discriminantCase<'success', Machine<Context>, 'status', 'success'>('success', 'status', 'success')
937
+ );
938
+
939
+ const machine = createMachine<Context>({ status: 'success', data: 'test' }, {});
940
+
941
+ if (match.is.success(machine)) {
942
+ console.log(machine.context.data); // βœ“ TypeScript knows data exists
943
+ }
944
+ ```
945
+
946
+ **`customCase`** - For custom predicates:
947
+
948
+ ```typescript
949
+ createMatcher(
950
+ customCase('complex', (m): m is ComplexMachine => {
951
+ return m.context.value > 10 && m.context.status === 'active';
952
+ })
953
+ );
954
+ ```
955
+
956
+ #### Comparison with Existing Utilities
957
+
958
+ | Utility | Use Case | Type Narrowing | Reusable |
959
+ |---------|----------|----------------|----------|
960
+ | `hasState(m, key, value)` | Single discriminant check | βœ… | ❌ |
961
+ | `isState(m, Class)` | Single class check | βœ… | ❌ |
962
+ | `matchMachine(m, key, handlers)` | Exhaustive matching | βœ… | ❌ |
963
+ | `createMatcher(...)` | All of the above | βœ… | βœ… |
964
+
965
+ **When to use `createMatcher`:**
966
+ - You need to match the same states in multiple places
967
+ - You want exhaustive pattern matching with compile-time checking
968
+ - You're working with union types of multiple machine classes
969
+ - You need flexible type guards for conditionals
970
+
971
+ **When to use simpler utilities:**
972
+ - One-off checks: Use `hasState` or `isState`
973
+ - Single location matching: Use `matchMachine`
974
+
822
975
  ## Advanced Features
823
976
 
824
977
  ### Ergonomic & Integration Patterns
@@ -1641,8 +1794,16 @@ The extraction system uses **AST-based static analysis**:
1641
1794
 
1642
1795
  ### Static Extraction API (Build-Time)
1643
1796
 
1797
+ > **Tree-Shaking**: Extraction tools are in a separate entry point (`@doeixd/machine/extract`) and are NOT included in your production bundle when you import from the main package. The heavy `ts-morph` dependency (used for AST parsing) will only be included if you explicitly import from `/extract`.
1798
+ >
1799
+ > **Type Imports**: Configuration types (`MachineConfig`, `ExtractionConfig`, etc.) are available from the main package for type safety without bundle impact:
1800
+ > ```typescript
1801
+ > import type { MachineConfig, ExtractionConfig } from '@doeixd/machine';
1802
+ > ```
1803
+
1644
1804
  ```typescript
1645
- import { extractMachine, extractMachines } from '@doeixd/machine';
1805
+ // Import from the separate extract entry point (NOT included in main bundle)
1806
+ import { extractMachine, extractMachines } from '@doeixd/machine/extract';
1646
1807
  import { Project } from 'ts-morph';
1647
1808
 
1648
1809
  // Extract single machine