@doeixd/machine 0.0.4 → 0.0.5

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,141 @@
1
+ /**
2
+ * @file Runtime statechart extraction utilities
3
+ * @description Extract statecharts from running machine instances using Symbol-based metadata
4
+ */
5
+
6
+ import { RUNTIME_META, type RuntimeTransitionMeta } from './primitives';
7
+
8
+ /**
9
+ * Extract metadata from a single function if it has runtime metadata attached
10
+ *
11
+ * @param fn - Function to extract from
12
+ * @returns Metadata object or null if no metadata
13
+ */
14
+ export function extractFunctionMetadata(fn: any): RuntimeTransitionMeta | null {
15
+ if (typeof fn !== 'function') {
16
+ return null;
17
+ }
18
+
19
+ const meta = fn[RUNTIME_META];
20
+ return meta || null;
21
+ }
22
+
23
+ /**
24
+ * Extract state node from a machine class instance
25
+ *
26
+ * @param stateInstance - Instance of a machine state class
27
+ * @returns State node with transitions
28
+ */
29
+ export function extractStateNode(stateInstance: any): any {
30
+ const stateNode: any = { on: {} };
31
+ const invoke: any[] = [];
32
+
33
+ // Iterate over all properties
34
+ for (const key in stateInstance) {
35
+ const value = stateInstance[key];
36
+
37
+ if (typeof value !== 'function') {
38
+ continue;
39
+ }
40
+
41
+ const meta = extractFunctionMetadata(value);
42
+ if (!meta) {
43
+ continue;
44
+ }
45
+
46
+ // Separate invoke from transitions
47
+ if (meta.invoke) {
48
+ invoke.push({
49
+ src: meta.invoke.src,
50
+ onDone: { target: meta.invoke.onDone },
51
+ onError: { target: meta.invoke.onError },
52
+ description: meta.invoke.description
53
+ });
54
+ }
55
+
56
+ // If has target, it's a transition
57
+ if (meta.target) {
58
+ const transition: any = { target: meta.target };
59
+
60
+ if (meta.description) {
61
+ transition.description = meta.description;
62
+ }
63
+
64
+ if (meta.guards && meta.guards.length > 0) {
65
+ transition.cond = meta.guards.map(g => g.name).join(' && ');
66
+ }
67
+
68
+ if (meta.actions && meta.actions.length > 0) {
69
+ transition.actions = meta.actions.map(a => a.name);
70
+ }
71
+
72
+ stateNode.on[key] = transition;
73
+ }
74
+ }
75
+
76
+ if (invoke.length > 0) {
77
+ stateNode.invoke = invoke;
78
+ }
79
+
80
+ return stateNode;
81
+ }
82
+
83
+ /**
84
+ * Generate a complete statechart from multiple state class instances
85
+ *
86
+ * @param states - Object mapping state names to state instances
87
+ * @param config - Chart configuration
88
+ * @returns XState-compatible statechart JSON
89
+ *
90
+ * @example
91
+ * const chart = generateStatechart({
92
+ * 'LoggedOut': new LoggedOutMachine(),
93
+ * 'LoggedIn': new LoggedInMachine()
94
+ * }, {
95
+ * id: 'auth',
96
+ * initial: 'LoggedOut'
97
+ * });
98
+ */
99
+ export function generateStatechart(
100
+ states: Record<string, any>,
101
+ config: { id: string; initial: string; description?: string }
102
+ ): any {
103
+ const chart: any = {
104
+ id: config.id,
105
+ initial: config.initial,
106
+ states: {}
107
+ };
108
+
109
+ if (config.description) {
110
+ chart.description = config.description;
111
+ }
112
+
113
+ for (const [stateName, stateInstance] of Object.entries(states)) {
114
+ chart.states[stateName] = extractStateNode(stateInstance);
115
+ }
116
+
117
+ return chart;
118
+ }
119
+
120
+ /**
121
+ * Convenience function to extract statechart from a single machine instance
122
+ * Useful for simple machines with a single context but multiple transitions
123
+ *
124
+ * @param machineInstance - Machine instance
125
+ * @param config - Chart configuration
126
+ * @returns XState-compatible statechart JSON
127
+ */
128
+ export function extractFromInstance(
129
+ machineInstance: any,
130
+ config: { id: string; stateName?: string }
131
+ ): any {
132
+ const stateName = config.stateName || machineInstance.constructor.name || 'State';
133
+
134
+ return {
135
+ id: config.id,
136
+ initial: stateName,
137
+ states: {
138
+ [stateName]: extractStateNode(machineInstance)
139
+ }
140
+ };
141
+ }