@orcalang/orca-lang 0.1.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.
- package/LICENSE +176 -0
- package/README.md +128 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/lock.d.ts +2 -0
- package/dist/auth/lock.d.ts.map +1 -0
- package/dist/auth/lock.js +59 -0
- package/dist/auth/lock.js.map +1 -0
- package/dist/auth/providers/anthropic.d.ts +14 -0
- package/dist/auth/providers/anthropic.d.ts.map +1 -0
- package/dist/auth/providers/anthropic.js +145 -0
- package/dist/auth/providers/anthropic.js.map +1 -0
- package/dist/auth/providers/index.d.ts +3 -0
- package/dist/auth/providers/index.d.ts.map +1 -0
- package/dist/auth/providers/index.js +3 -0
- package/dist/auth/providers/index.js.map +1 -0
- package/dist/auth/providers/minimax.d.ts +6 -0
- package/dist/auth/providers/minimax.d.ts.map +1 -0
- package/dist/auth/providers/minimax.js +65 -0
- package/dist/auth/providers/minimax.js.map +1 -0
- package/dist/auth/refresh.d.ts +8 -0
- package/dist/auth/refresh.d.ts.map +1 -0
- package/dist/auth/refresh.js +104 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/auth/store.d.ts +11 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +63 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/types.d.ts +51 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/compiler/mermaid.d.ts +3 -0
- package/dist/compiler/mermaid.d.ts.map +1 -0
- package/dist/compiler/mermaid.js +86 -0
- package/dist/compiler/mermaid.js.map +1 -0
- package/dist/compiler/xstate.d.ts +15 -0
- package/dist/compiler/xstate.d.ts.map +1 -0
- package/dist/compiler/xstate.js +542 -0
- package/dist/compiler/xstate.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +4 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +109 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +13 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +8 -0
- package/dist/config/types.js.map +1 -0
- package/dist/generators/index.d.ts +5 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +5 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/registry.d.ts +12 -0
- package/dist/generators/registry.d.ts.map +1 -0
- package/dist/generators/registry.js +15 -0
- package/dist/generators/registry.js.map +1 -0
- package/dist/generators/typescript.d.ts +9 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +55 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +630 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/anthropic.d.ts +14 -0
- package/dist/llm/anthropic.d.ts.map +1 -0
- package/dist/llm/anthropic.js +87 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/grok.d.ts +13 -0
- package/dist/llm/grok.d.ts.map +1 -0
- package/dist/llm/grok.js +60 -0
- package/dist/llm/grok.js.map +1 -0
- package/dist/llm/index.d.ts +11 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +23 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/ollama.d.ts +11 -0
- package/dist/llm/ollama.d.ts.map +1 -0
- package/dist/llm/ollama.js +51 -0
- package/dist/llm/ollama.js.map +1 -0
- package/dist/llm/openai.d.ts +13 -0
- package/dist/llm/openai.d.ts.map +1 -0
- package/dist/llm/openai.js +61 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.d.ts.map +1 -0
- package/dist/llm/provider.js +2 -0
- package/dist/llm/provider.js.map +1 -0
- package/dist/parser/ast-to-markdown.d.ts +3 -0
- package/dist/parser/ast-to-markdown.d.ts.map +1 -0
- package/dist/parser/ast-to-markdown.js +209 -0
- package/dist/parser/ast-to-markdown.js.map +1 -0
- package/dist/parser/ast.d.ts +183 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +3 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/markdown-parser.d.ts +8 -0
- package/dist/parser/markdown-parser.d.ts.map +1 -0
- package/dist/parser/markdown-parser.js +838 -0
- package/dist/parser/markdown-parser.js.map +1 -0
- package/dist/runtime/effects.d.ts +17 -0
- package/dist/runtime/effects.d.ts.map +1 -0
- package/dist/runtime/effects.js +28 -0
- package/dist/runtime/effects.js.map +1 -0
- package/dist/runtime/machine.d.ts +8 -0
- package/dist/runtime/machine.d.ts.map +1 -0
- package/dist/runtime/machine.js +158 -0
- package/dist/runtime/machine.js.map +1 -0
- package/dist/runtime/types.d.ts +37 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +3 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/skills.d.ts +114 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +1103 -0
- package/dist/skills.js.map +1 -0
- package/dist/tools.d.ts +18 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +124 -0
- package/dist/tools.js.map +1 -0
- package/dist/verifier/completeness.d.ts +4 -0
- package/dist/verifier/completeness.d.ts.map +1 -0
- package/dist/verifier/completeness.js +82 -0
- package/dist/verifier/completeness.js.map +1 -0
- package/dist/verifier/determinism.d.ts +17 -0
- package/dist/verifier/determinism.d.ts.map +1 -0
- package/dist/verifier/determinism.js +301 -0
- package/dist/verifier/determinism.js.map +1 -0
- package/dist/verifier/properties.d.ts +6 -0
- package/dist/verifier/properties.d.ts.map +1 -0
- package/dist/verifier/properties.js +404 -0
- package/dist/verifier/properties.js.map +1 -0
- package/dist/verifier/structural.d.ts +50 -0
- package/dist/verifier/structural.d.ts.map +1 -0
- package/dist/verifier/structural.js +692 -0
- package/dist/verifier/structural.js.map +1 -0
- package/dist/verifier/types.d.ts +40 -0
- package/dist/verifier/types.d.ts.map +1 -0
- package/dist/verifier/types.js +2 -0
- package/dist/verifier/types.js.map +1 -0
- package/package.json +49 -0
- package/src/auth/index.ts +5 -0
- package/src/auth/lock.ts +71 -0
- package/src/auth/providers/anthropic.ts +192 -0
- package/src/auth/providers/index.ts +17 -0
- package/src/auth/providers/minimax.ts +100 -0
- package/src/auth/refresh.ts +138 -0
- package/src/auth/store.ts +75 -0
- package/src/auth/types.ts +62 -0
- package/src/compiler/mermaid.ts +109 -0
- package/src/compiler/xstate.ts +615 -0
- package/src/config/index.ts +2 -0
- package/src/config/loader.ts +122 -0
- package/src/config/types.ts +21 -0
- package/src/generators/index.ts +6 -0
- package/src/generators/registry.ts +27 -0
- package/src/generators/typescript.ts +67 -0
- package/src/index.ts +671 -0
- package/src/llm/anthropic.ts +102 -0
- package/src/llm/grok.ts +73 -0
- package/src/llm/index.ts +29 -0
- package/src/llm/ollama.ts +62 -0
- package/src/llm/openai.ts +74 -0
- package/src/llm/provider.ts +35 -0
- package/src/parser/ast-to-markdown.ts +220 -0
- package/src/parser/ast.ts +236 -0
- package/src/parser/markdown-parser.ts +844 -0
- package/src/runtime/effects.ts +48 -0
- package/src/runtime/machine.ts +201 -0
- package/src/runtime/types.ts +44 -0
- package/src/skills.ts +1339 -0
- package/src/tools.ts +144 -0
- package/src/verifier/completeness.ts +89 -0
- package/src/verifier/determinism.ts +328 -0
- package/src/verifier/properties.ts +507 -0
- package/src/verifier/structural.ts +803 -0
- package/src/verifier/types.ts +45 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { MachineDef, Transition, ActionSignature, StateDef } from '../parser/ast.js';
|
|
2
|
+
import { createMachine, assign } from 'xstate';
|
|
3
|
+
|
|
4
|
+
// Helper to find a state with a specific action (searches nested states)
|
|
5
|
+
function findStateByAction(machine: MachineDef, actionName: string): StateDef | undefined {
|
|
6
|
+
for (const state of machine.states) {
|
|
7
|
+
const found = findStateRecursively(state, actionName);
|
|
8
|
+
if (found) return found;
|
|
9
|
+
}
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function findStateRecursively(state: StateDef, actionName: string): StateDef | undefined {
|
|
14
|
+
if (state.onEntry === actionName || state.onExit === actionName) {
|
|
15
|
+
return state;
|
|
16
|
+
}
|
|
17
|
+
if (state.contains) {
|
|
18
|
+
for (const child of state.contains) {
|
|
19
|
+
const found = findStateRecursively(child, actionName);
|
|
20
|
+
if (found) return found;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (state.parallel) {
|
|
24
|
+
for (const region of state.parallel.regions) {
|
|
25
|
+
for (const child of region.states) {
|
|
26
|
+
const found = findStateRecursively(child, actionName);
|
|
27
|
+
if (found) return found;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CompiledMachine {
|
|
35
|
+
config: any; // XState MachineConfig type
|
|
36
|
+
effectMeta: {
|
|
37
|
+
effectfulActions: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
effectType: string;
|
|
40
|
+
state: string;
|
|
41
|
+
transition?: string;
|
|
42
|
+
}>;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function compileToXStateMachine(machine: MachineDef): CompiledMachine {
|
|
47
|
+
const effectMeta = {
|
|
48
|
+
effectfulActions: findEffectfulActions(machine),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const config: any = {
|
|
52
|
+
id: machine.name,
|
|
53
|
+
types: {
|
|
54
|
+
context: {} as any,
|
|
55
|
+
events: {} as any,
|
|
56
|
+
},
|
|
57
|
+
context: buildContext(machine),
|
|
58
|
+
initial: getInitialState(machine),
|
|
59
|
+
states: buildStates(machine),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return { config, effectMeta };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function findEffectfulActions(machine: MachineDef) {
|
|
66
|
+
const effectful: CompiledMachine['effectMeta']['effectfulActions'] = [];
|
|
67
|
+
|
|
68
|
+
for (const action of machine.actions) {
|
|
69
|
+
if (action.hasEffect && action.effectType) {
|
|
70
|
+
// Find which state uses this action (search all states including nested)
|
|
71
|
+
const stateWithAction = findStateByAction(machine, action.name);
|
|
72
|
+
if (stateWithAction) {
|
|
73
|
+
effectful.push({ name: action.name, effectType: action.effectType, state: stateWithAction.name });
|
|
74
|
+
}
|
|
75
|
+
// Find transitions using this action
|
|
76
|
+
for (const t of machine.transitions) {
|
|
77
|
+
if (t.action === action.name) {
|
|
78
|
+
effectful.push({ name: action.name, effectType: action.effectType, state: t.source, transition: t.target });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return effectful;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildContext(machine: MachineDef): Record<string, unknown> {
|
|
88
|
+
const ctx: Record<string, unknown> = {};
|
|
89
|
+
for (const field of machine.context) {
|
|
90
|
+
ctx[field.name] = field.defaultValue !== undefined
|
|
91
|
+
? field.defaultValue
|
|
92
|
+
: getDefaultForType(field);
|
|
93
|
+
}
|
|
94
|
+
return ctx;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildStates(machine: MachineDef): Record<string, any> {
|
|
98
|
+
const states: Record<string, any> = {};
|
|
99
|
+
|
|
100
|
+
for (const state of machine.states) {
|
|
101
|
+
states[state.name] = buildStateConfig(state, machine, machine.states);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return states;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildStateConfig(state: StateDef, machine: MachineDef, allStates: StateDef[]): any {
|
|
108
|
+
const config: any = {};
|
|
109
|
+
|
|
110
|
+
if (state.description) {
|
|
111
|
+
config.description = state.description;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check if this is a compound state (has nested states)
|
|
115
|
+
if (state.contains && state.contains.length > 0) {
|
|
116
|
+
// Compound state with nested states
|
|
117
|
+
const initialChild = state.contains.find(s => s.isInitial) || state.contains[0];
|
|
118
|
+
config.initial = initialChild.name;
|
|
119
|
+
config.states = {};
|
|
120
|
+
|
|
121
|
+
// Handle transitions for compound states (BEFORE recursive calls)
|
|
122
|
+
// These transitions fire from any child state via XState event bubbling
|
|
123
|
+
const thisStateTransitions = machine.transitions.filter(t => t.source === state.name);
|
|
124
|
+
if (thisStateTransitions.length > 0) {
|
|
125
|
+
config.on = buildTransitions(thisStateTransitions, machine);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const child of state.contains) {
|
|
129
|
+
config.states[child.name] = buildStateConfig(child, machine, state.contains);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Compound states don't use type: 'initial' or 'final' - they use initial + nested states
|
|
133
|
+
return config;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check if this is a parallel state (has parallel regions)
|
|
137
|
+
if (state.parallel) {
|
|
138
|
+
config.type = 'parallel';
|
|
139
|
+
config.states = {};
|
|
140
|
+
|
|
141
|
+
for (const region of state.parallel.regions) {
|
|
142
|
+
const regionConfig: any = {};
|
|
143
|
+
const initialChild = region.states.find(s => s.isInitial) || region.states[0];
|
|
144
|
+
regionConfig.initial = initialChild.name;
|
|
145
|
+
regionConfig.states = {};
|
|
146
|
+
|
|
147
|
+
for (const child of region.states) {
|
|
148
|
+
regionConfig.states[child.name] = buildStateConfig(child, machine, region.states);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
config.states[region.name] = regionConfig;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// onDone for synchronization (all-final is the XState default)
|
|
155
|
+
if (state.onDone) {
|
|
156
|
+
config.onDone = { target: state.onDone };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Parent-level transitions (event bubbling to all regions)
|
|
160
|
+
const thisStateTransitions = machine.transitions.filter(t => t.source === state.name);
|
|
161
|
+
if (thisStateTransitions.length > 0) {
|
|
162
|
+
config.on = buildTransitions(thisStateTransitions, machine);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return config;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Leaf state configuration
|
|
169
|
+
// Note: initial state is designated by the parent's `initial` property, not by type
|
|
170
|
+
if (state.isFinal) {
|
|
171
|
+
config.type = 'final';
|
|
172
|
+
}
|
|
173
|
+
if (state.onEntry) {
|
|
174
|
+
const action = machine.actions.find(a => a.name === state.onEntry);
|
|
175
|
+
if (action?.hasEffect) {
|
|
176
|
+
// Effectful action - use invoke at state level to run the effect
|
|
177
|
+
// Don't set entry - the invoke replaces the entry action
|
|
178
|
+
config.invoke = buildEffectInvoke(state.onEntry, action, machine);
|
|
179
|
+
} else {
|
|
180
|
+
config.entry = state.onEntry;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (state.onExit) {
|
|
184
|
+
config.exit = state.onExit;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Handle machine invocation
|
|
188
|
+
if (state.invoke) {
|
|
189
|
+
config.invoke = buildMachineInvoke(state.invoke);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Handle transitions - only use this state's transitions
|
|
193
|
+
// (transitions on compound states fire from any child via XState's event bubbling)
|
|
194
|
+
const thisStateTransitions = machine.transitions.filter(t => t.source === state.name);
|
|
195
|
+
|
|
196
|
+
if (thisStateTransitions.length > 0) {
|
|
197
|
+
config.on = buildTransitions(thisStateTransitions, machine);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle timeout
|
|
201
|
+
if (state.timeout) {
|
|
202
|
+
config.after = {
|
|
203
|
+
[state.timeout.duration]: { target: state.timeout.target },
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return config;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Get transitions from parent compound states (these fire from any child state)
|
|
211
|
+
function getParentTransitions(state: StateDef, machine: MachineDef, allStates: StateDef[]): Transition[] {
|
|
212
|
+
if (!state.parent) return [];
|
|
213
|
+
|
|
214
|
+
// Find parent state
|
|
215
|
+
const parent = findStateByName(allStates, state.parent);
|
|
216
|
+
if (!parent) return [];
|
|
217
|
+
|
|
218
|
+
// Get parent's transitions
|
|
219
|
+
const parentTransitions = machine.transitions.filter(t => t.source === parent.name);
|
|
220
|
+
|
|
221
|
+
// Recursively get grandparent transitions
|
|
222
|
+
const grandparentTransitions = getParentTransitions(parent, machine, allStates);
|
|
223
|
+
|
|
224
|
+
return [...parentTransitions, ...grandparentTransitions];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Find a state by name in a flat list of states (including nested and parallel regions)
|
|
228
|
+
function findStateByName(states: StateDef[], name: string): StateDef | undefined {
|
|
229
|
+
for (const state of states) {
|
|
230
|
+
if (state.name === name) return state;
|
|
231
|
+
if (state.contains) {
|
|
232
|
+
const found = findStateByName(state.contains, name);
|
|
233
|
+
if (found) return found;
|
|
234
|
+
}
|
|
235
|
+
if (state.parallel) {
|
|
236
|
+
for (const region of state.parallel.regions) {
|
|
237
|
+
const found = findStateByName(region.states, name);
|
|
238
|
+
if (found) return found;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function buildEffectInvoke(actionName: string, action: ActionSignature, machine: MachineDef): any {
|
|
246
|
+
// Find the state that has this action as entry or exit
|
|
247
|
+
// Search all states including nested ones
|
|
248
|
+
const stateWithAction = findStateByAction(machine, actionName);
|
|
249
|
+
const stateName = stateWithAction?.name;
|
|
250
|
+
let doneTarget: string | undefined;
|
|
251
|
+
|
|
252
|
+
// Find transitions from this state to determine the completion target.
|
|
253
|
+
// When an effect is invoked, the machine waits for the effect to complete.
|
|
254
|
+
// The completion event is derived from transitions that exit this state.
|
|
255
|
+
if (stateName) {
|
|
256
|
+
// Look for transitions that exit this state (these handle effect completion)
|
|
257
|
+
const exitTransitions = machine.transitions.filter(t => t.source === stateName);
|
|
258
|
+
if (exitTransitions.length > 0) {
|
|
259
|
+
// Use the first exit transition's target as the done target.
|
|
260
|
+
// This assumes effects have a simple completion path.
|
|
261
|
+
doneTarget = exitTransitions[0].target;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const effectType = action.effectType || 'Effect';
|
|
266
|
+
|
|
267
|
+
// Build the input expression - it will be used in the fromPromise
|
|
268
|
+
const inputExpr = ({ context, event }: { context: any; event: any }) => ({ context, event, action: actionName });
|
|
269
|
+
|
|
270
|
+
// Find an error/failed state if one exists
|
|
271
|
+
const errorState = machine.states.find(s => s.name === 'error')
|
|
272
|
+
|| machine.states.find(s => s.name === 'failed');
|
|
273
|
+
const errorTarget = errorState?.name;
|
|
274
|
+
|
|
275
|
+
// Return the invoke config directly, not wrapped in another object.
|
|
276
|
+
const invokeConfig: any = {
|
|
277
|
+
src: `__effect__:${effectType}`,
|
|
278
|
+
input: inputExpr,
|
|
279
|
+
onDone: doneTarget ? {
|
|
280
|
+
target: doneTarget,
|
|
281
|
+
actions: assign({
|
|
282
|
+
_effectResult: ({ event }: any) => event.output,
|
|
283
|
+
}),
|
|
284
|
+
} : {
|
|
285
|
+
actions: assign({
|
|
286
|
+
_effectResult: ({ event }: any) => event.output,
|
|
287
|
+
}),
|
|
288
|
+
},
|
|
289
|
+
onError: {
|
|
290
|
+
actions: assign({
|
|
291
|
+
_effectError: ({ event }: any) => event.error?.message,
|
|
292
|
+
}),
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
if (errorTarget) {
|
|
297
|
+
invokeConfig.onError.target = errorTarget;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return invokeConfig;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function buildMachineInvoke(invokeDef: { machine: string; input?: Record<string, string>; onDone?: string; onError?: string }): any {
|
|
304
|
+
// Build input expression from input mapping
|
|
305
|
+
// input is like { id: "ctx.order_id" } -> { id: context.order_id }
|
|
306
|
+
const inputExpr = ({ context, event }: { context: any; event: any }) => {
|
|
307
|
+
const input: Record<string, unknown> = {};
|
|
308
|
+
if (invokeDef.input) {
|
|
309
|
+
for (const [key, value] of Object.entries(invokeDef.input)) {
|
|
310
|
+
// value is like "ctx.order_id" - extract the field name
|
|
311
|
+
const fieldName = value.replace(/^ctx\./, '');
|
|
312
|
+
input[key] = context[fieldName];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return input;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const invokeConfig: any = {
|
|
319
|
+
src: `__machine__:${invokeDef.machine}`,
|
|
320
|
+
input: inputExpr,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
if (invokeDef.onDone) {
|
|
324
|
+
invokeConfig.onDone = { target: invokeDef.onDone };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (invokeDef.onError) {
|
|
328
|
+
invokeConfig.onError = { target: invokeDef.onError };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return invokeConfig;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function buildTransitions(transitions: Transition[], machine: MachineDef): Record<string, any> {
|
|
335
|
+
const on: Record<string, any> = {};
|
|
336
|
+
const eventGroups = groupByEvent(transitions);
|
|
337
|
+
|
|
338
|
+
for (const [eventName, trans] of Object.entries(eventGroups)) {
|
|
339
|
+
if (trans.length === 1 && !trans[0].guard) {
|
|
340
|
+
// Single unguarded transition
|
|
341
|
+
const t = trans[0];
|
|
342
|
+
const action = machine.actions.find(a => a.name === t.action);
|
|
343
|
+
|
|
344
|
+
if (action?.hasEffect) {
|
|
345
|
+
// Effectful transition
|
|
346
|
+
on[eventName] = {
|
|
347
|
+
target: t.target,
|
|
348
|
+
actions: {
|
|
349
|
+
type: 'effectful',
|
|
350
|
+
name: t.action,
|
|
351
|
+
effectType: action.effectType,
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
} else {
|
|
355
|
+
on[eventName] = {
|
|
356
|
+
target: t.target,
|
|
357
|
+
actions: t.action || undefined,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
// Multiple transitions or guarded - use array format
|
|
362
|
+
on[eventName] = trans.map(t => {
|
|
363
|
+
const action = machine.actions.find(a => a.name === t.action);
|
|
364
|
+
const target = t.target;
|
|
365
|
+
|
|
366
|
+
const transition: any = { target };
|
|
367
|
+
if (t.guard) {
|
|
368
|
+
const guardName = t.guard.negated ? `!${t.guard.name}` : t.guard.name;
|
|
369
|
+
transition.guard = { type: guardName };
|
|
370
|
+
}
|
|
371
|
+
if (t.action) {
|
|
372
|
+
transition.actions = t.action;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return transition;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return on;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function compileToXState(machine: MachineDef): string {
|
|
384
|
+
const lines: string[] = [];
|
|
385
|
+
|
|
386
|
+
lines.push(`import { createMachine, assign } from 'xstate';`);
|
|
387
|
+
lines.push(``);
|
|
388
|
+
lines.push(`export const ${machine.name}Machine = createMachine({`);
|
|
389
|
+
lines.push(` id: '${machine.name}',`);
|
|
390
|
+
lines.push(` types: {} as {`);
|
|
391
|
+
lines.push(` context: {`);
|
|
392
|
+
for (const field of machine.context) {
|
|
393
|
+
lines.push(` ${field.name}: ${typeToTs(field)},`);
|
|
394
|
+
}
|
|
395
|
+
lines.push(` },`);
|
|
396
|
+
lines.push(` events: |`);
|
|
397
|
+
for (let i = 0; i < machine.events.length; i++) {
|
|
398
|
+
const event = machine.events[i];
|
|
399
|
+
lines.push(` | { type: '${event.name}' }${i < machine.events.length - 1 ? '' : ''}`);
|
|
400
|
+
}
|
|
401
|
+
lines.push(` },`);
|
|
402
|
+
lines.push(` context: {`);
|
|
403
|
+
for (const field of machine.context) {
|
|
404
|
+
const defaultVal = field.defaultValue || getDefaultForType(field);
|
|
405
|
+
lines.push(` ${field.name}: ${defaultVal},`);
|
|
406
|
+
}
|
|
407
|
+
lines.push(` },`);
|
|
408
|
+
lines.push(` initial: '${getInitialState(machine)}',`);
|
|
409
|
+
lines.push(` states: {`);
|
|
410
|
+
|
|
411
|
+
for (let i = 0; i < machine.states.length; i++) {
|
|
412
|
+
const state = machine.states[i];
|
|
413
|
+
lines.push(` ${state.name}: {`);
|
|
414
|
+
if (state.description) {
|
|
415
|
+
lines.push(` description: '${escapeString(state.description)}',`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Check if this is a compound state (has nested states)
|
|
419
|
+
if (state.contains && state.contains.length > 0) {
|
|
420
|
+
// Compound state with nested states
|
|
421
|
+
const initialChild = state.contains.find(s => s.isInitial) || state.contains[0];
|
|
422
|
+
lines.push(` initial: '${initialChild.name}',`);
|
|
423
|
+
lines.push(` states: {`);
|
|
424
|
+
for (let j = 0; j < state.contains.length; j++) {
|
|
425
|
+
const child = state.contains[j];
|
|
426
|
+
lines.push(` ${child.name}: {`);
|
|
427
|
+
if (child.description) {
|
|
428
|
+
lines.push(` description: '${escapeString(child.description)}',`);
|
|
429
|
+
}
|
|
430
|
+
if (child.isFinal) {
|
|
431
|
+
lines.push(` type: 'final',`);
|
|
432
|
+
}
|
|
433
|
+
lines.push(` }${j < state.contains.length - 1 ? ',' : ''}`);
|
|
434
|
+
}
|
|
435
|
+
lines.push(` },`);
|
|
436
|
+
lines.push(` }${i < machine.states.length - 1 ? ',' : ''}`);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Check if this is a parallel state
|
|
441
|
+
if (state.parallel) {
|
|
442
|
+
lines.push(` type: 'parallel',`);
|
|
443
|
+
lines.push(` states: {`);
|
|
444
|
+
for (let r = 0; r < state.parallel.regions.length; r++) {
|
|
445
|
+
const region = state.parallel.regions[r];
|
|
446
|
+
const initialChild = region.states.find(s => s.isInitial) || region.states[0];
|
|
447
|
+
lines.push(` ${region.name}: {`);
|
|
448
|
+
lines.push(` initial: '${initialChild.name}',`);
|
|
449
|
+
lines.push(` states: {`);
|
|
450
|
+
for (let j = 0; j < region.states.length; j++) {
|
|
451
|
+
const child = region.states[j];
|
|
452
|
+
lines.push(` ${child.name}: {`);
|
|
453
|
+
if (child.description) {
|
|
454
|
+
lines.push(` description: '${escapeString(child.description)}',`);
|
|
455
|
+
}
|
|
456
|
+
if (child.isFinal) {
|
|
457
|
+
lines.push(` type: 'final',`);
|
|
458
|
+
}
|
|
459
|
+
if (child.onEntry) {
|
|
460
|
+
lines.push(` entry: '${child.onEntry}',`);
|
|
461
|
+
}
|
|
462
|
+
if (child.onExit) {
|
|
463
|
+
lines.push(` exit: '${child.onExit}',`);
|
|
464
|
+
}
|
|
465
|
+
lines.push(` }${j < region.states.length - 1 ? ',' : ''}`);
|
|
466
|
+
}
|
|
467
|
+
lines.push(` },`);
|
|
468
|
+
lines.push(` }${r < state.parallel.regions.length - 1 ? ',' : ''}`);
|
|
469
|
+
}
|
|
470
|
+
lines.push(` },`);
|
|
471
|
+
if (state.onDone) {
|
|
472
|
+
lines.push(` onDone: { target: '${state.onDone}' },`);
|
|
473
|
+
}
|
|
474
|
+
lines.push(` }${i < machine.states.length - 1 ? ',' : ''}`);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Leaf state configuration
|
|
479
|
+
if (state.isFinal) {
|
|
480
|
+
lines.push(` type: 'final',`);
|
|
481
|
+
}
|
|
482
|
+
if (state.onEntry) {
|
|
483
|
+
lines.push(` entry: '${state.onEntry}',`);
|
|
484
|
+
}
|
|
485
|
+
if (state.onExit) {
|
|
486
|
+
lines.push(` exit: '${state.onExit}',`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Handle machine invocation
|
|
490
|
+
if (state.invoke) {
|
|
491
|
+
lines.push(` invoke: {`);
|
|
492
|
+
lines.push(` src: '__machine__:${state.invoke.machine}',`);
|
|
493
|
+
if (state.invoke.input) {
|
|
494
|
+
// Build input mapping expression
|
|
495
|
+
const inputPairs: string[] = [];
|
|
496
|
+
for (const [key, value] of Object.entries(state.invoke.input)) {
|
|
497
|
+
const fieldName = value.replace(/^ctx\./, '');
|
|
498
|
+
inputPairs.push(`${key}: context.${fieldName}`);
|
|
499
|
+
}
|
|
500
|
+
lines.push(` input: ({ context }) => ({ ${inputPairs.join(', ')} }),`);
|
|
501
|
+
}
|
|
502
|
+
if (state.invoke.onDone) {
|
|
503
|
+
lines.push(` onDone: { target: '${state.invoke.onDone}' },`);
|
|
504
|
+
}
|
|
505
|
+
if (state.invoke.onError) {
|
|
506
|
+
lines.push(` onError: { target: '${state.invoke.onError}' },`);
|
|
507
|
+
}
|
|
508
|
+
lines.push(` },`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Collect transitions for this state
|
|
512
|
+
const stateTransitions = machine.transitions.filter(t => t.source === state.name);
|
|
513
|
+
|
|
514
|
+
if (stateTransitions.length > 0) {
|
|
515
|
+
lines.push(` on: {`);
|
|
516
|
+
const eventGroups = groupByEvent(stateTransitions);
|
|
517
|
+
const eventEntries = Object.entries(eventGroups);
|
|
518
|
+
for (let ei = 0; ei < eventEntries.length; ei++) {
|
|
519
|
+
const [eventName, trans] = eventEntries[ei];
|
|
520
|
+
const isArrayFormat = trans.length > 1 || trans.some(t => t.guard);
|
|
521
|
+
|
|
522
|
+
if (isArrayFormat) {
|
|
523
|
+
// Multiple transitions or guarded transitions - use array format
|
|
524
|
+
lines.push(` ${eventName}: [`);
|
|
525
|
+
for (const t of trans) {
|
|
526
|
+
lines.push(` {`);
|
|
527
|
+
const target = t.target;
|
|
528
|
+
lines.push(` target: '${target}',`);
|
|
529
|
+
if (t.guard) {
|
|
530
|
+
const guardName = t.guard.negated ? `!${t.guard.name}` : t.guard.name;
|
|
531
|
+
lines.push(` guard: '${guardName}',`);
|
|
532
|
+
}
|
|
533
|
+
if (t.action) {
|
|
534
|
+
lines.push(` actions: '${t.action}',`);
|
|
535
|
+
}
|
|
536
|
+
lines.push(` },`);
|
|
537
|
+
}
|
|
538
|
+
lines.push(` ],`);
|
|
539
|
+
} else {
|
|
540
|
+
// Single unguarded transition - use object format
|
|
541
|
+
const t = trans[0];
|
|
542
|
+
lines.push(` ${eventName}: {`);
|
|
543
|
+
lines.push(` target: '${t.target}',`);
|
|
544
|
+
if (t.action) {
|
|
545
|
+
lines.push(` actions: '${t.action}',`);
|
|
546
|
+
}
|
|
547
|
+
lines.push(` },`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
lines.push(` },`);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
lines.push(` }${i < machine.states.length - 1 ? ',' : ''}`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
lines.push(` },`);
|
|
557
|
+
lines.push(`});`);
|
|
558
|
+
|
|
559
|
+
return lines.join('\n');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function typeToTs(field: { type: { kind: string; name?: string; elementType?: string; innerType?: string; keyType?: string; valueType?: string } }): string {
|
|
563
|
+
const t = field.type;
|
|
564
|
+
switch (t.kind) {
|
|
565
|
+
case 'string': return 'string';
|
|
566
|
+
case 'int': return 'number';
|
|
567
|
+
case 'decimal': return 'number';
|
|
568
|
+
case 'bool': return 'boolean';
|
|
569
|
+
case 'array': return `${t.elementType}[]`;
|
|
570
|
+
case 'optional': return `${t.innerType} | null`;
|
|
571
|
+
case 'map': return `Record<${t.keyType || 'string'}, ${t.valueType || 'any'}>`;
|
|
572
|
+
case 'custom': return t.name || 'any';
|
|
573
|
+
default: return 'any';
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function getDefaultForType(field: { type: { kind: string } }): string {
|
|
578
|
+
switch (field.type.kind) {
|
|
579
|
+
case 'string': return "''";
|
|
580
|
+
case 'int':
|
|
581
|
+
case 'decimal': return '0';
|
|
582
|
+
case 'bool': return 'false';
|
|
583
|
+
case 'array': return '[]';
|
|
584
|
+
case 'map': return '{}';
|
|
585
|
+
case 'optional': return 'null';
|
|
586
|
+
default: return 'undefined';
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function getInitialState(machine: MachineDef): string {
|
|
591
|
+
// Find top-level initial state (not nested)
|
|
592
|
+
const initial = machine.states.find(s => s.isInitial);
|
|
593
|
+
if (initial) {
|
|
594
|
+
// If initial is compound, return its initial child
|
|
595
|
+
if (initial.contains && initial.contains.length > 0) {
|
|
596
|
+
const initialChild = initial.contains.find(s => s.isInitial) || initial.contains[0];
|
|
597
|
+
return initial.name; // XState will enter the compound state and use its initial
|
|
598
|
+
}
|
|
599
|
+
return initial.name;
|
|
600
|
+
}
|
|
601
|
+
return machine.states[0]?.name || 'unknown';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function groupByEvent(transitions: Transition[]): Record<string, Transition[]> {
|
|
605
|
+
const groups: Record<string, Transition[]> = {};
|
|
606
|
+
for (const t of transitions) {
|
|
607
|
+
if (!groups[t.event]) groups[t.event] = [];
|
|
608
|
+
groups[t.event].push(t);
|
|
609
|
+
}
|
|
610
|
+
return groups;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function escapeString(s: string): string {
|
|
614
|
+
return s.replace(/'/g, "\\'");
|
|
615
|
+
}
|