@typeonce/effect-machine 0.31.2 → 0.33.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.
Files changed (151) hide show
  1. package/README.md +168 -162
  2. package/dist/Machine.d.ts +495 -284
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +91 -111
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +3 -4
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +0 -2
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/childRegistry.d.ts +52 -0
  11. package/dist/internal/machine/childRegistry.d.ts.map +1 -0
  12. package/dist/internal/machine/childRegistry.js +74 -0
  13. package/dist/internal/machine/childRegistry.js.map +1 -0
  14. package/dist/internal/machine/cluster.d.ts +2 -3
  15. package/dist/internal/machine/cluster.d.ts.map +1 -1
  16. package/dist/internal/machine/cluster.js.map +1 -1
  17. package/dist/internal/machine/configuration.d.ts.map +1 -1
  18. package/dist/internal/machine/configuration.js +14 -4
  19. package/dist/internal/machine/configuration.js.map +1 -1
  20. package/dist/internal/machine/executionPlan.d.ts +1 -0
  21. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  22. package/dist/internal/machine/executionPlan.js +97 -53
  23. package/dist/internal/machine/executionPlan.js.map +1 -1
  24. package/dist/internal/machine/invocation.d.ts.map +1 -1
  25. package/dist/internal/machine/invocation.js +4 -2
  26. package/dist/internal/machine/invocation.js.map +1 -1
  27. package/dist/internal/machine/invocationDefinition.d.ts +35 -0
  28. package/dist/internal/machine/invocationDefinition.d.ts.map +1 -0
  29. package/dist/internal/machine/invocationDefinition.js +21 -0
  30. package/dist/internal/machine/invocationDefinition.js.map +1 -0
  31. package/dist/internal/machine/invocationEvent.d.ts +0 -2
  32. package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
  33. package/dist/internal/machine/invocationEvent.js +0 -8
  34. package/dist/internal/machine/invocationEvent.js.map +1 -1
  35. package/dist/internal/machine/machine.d.ts +21 -43
  36. package/dist/internal/machine/machine.d.ts.map +1 -1
  37. package/dist/internal/machine/machine.js +134 -403
  38. package/dist/internal/machine/machine.js.map +1 -1
  39. package/dist/internal/machine/planner.d.ts +13 -19
  40. package/dist/internal/machine/planner.d.ts.map +1 -1
  41. package/dist/internal/machine/planner.js +63 -35
  42. package/dist/internal/machine/planner.js.map +1 -1
  43. package/dist/internal/machine/process.d.ts +6 -8
  44. package/dist/internal/machine/process.d.ts.map +1 -1
  45. package/dist/internal/machine/process.js +1 -0
  46. package/dist/internal/machine/process.js.map +1 -1
  47. package/dist/internal/machine/protocol.d.ts +2 -2
  48. package/dist/internal/machine/protocol.d.ts.map +1 -1
  49. package/dist/internal/machine/protocol.js +4 -18
  50. package/dist/internal/machine/protocol.js.map +1 -1
  51. package/dist/internal/machine/readiness.d.ts +1 -1
  52. package/dist/internal/machine/readiness.d.ts.map +1 -1
  53. package/dist/internal/machine/requirements.d.ts +4 -0
  54. package/dist/internal/machine/requirements.d.ts.map +1 -0
  55. package/dist/internal/machine/requirements.js +2 -0
  56. package/dist/internal/machine/requirements.js.map +1 -0
  57. package/dist/internal/machine/runtime.d.ts +4 -58
  58. package/dist/internal/machine/runtime.d.ts.map +1 -1
  59. package/dist/internal/machine/runtime.js +1 -69
  60. package/dist/internal/machine/runtime.js.map +1 -1
  61. package/dist/internal/machine/serialization.d.ts.map +1 -1
  62. package/dist/internal/machine/serialization.js +16 -0
  63. package/dist/internal/machine/serialization.js.map +1 -1
  64. package/dist/internal/machine/stateDefinition.d.ts +3 -1
  65. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  66. package/dist/internal/machine/stateDefinition.js +36 -2
  67. package/dist/internal/machine/stateDefinition.js.map +1 -1
  68. package/dist/internal/machine/targetBuilder.d.ts +16 -0
  69. package/dist/internal/machine/targetBuilder.d.ts.map +1 -0
  70. package/dist/internal/machine/targetBuilder.js +333 -0
  71. package/dist/internal/machine/targetBuilder.js.map +1 -0
  72. package/dist/internal/machine/topology.js +3 -3
  73. package/dist/internal/machine/topology.js.map +1 -1
  74. package/dist/internal/machine/transition.d.ts +16 -0
  75. package/dist/internal/machine/transition.d.ts.map +1 -0
  76. package/dist/internal/machine/transition.js +2 -0
  77. package/dist/internal/machine/transition.js.map +1 -0
  78. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  79. package/dist/internal/testing/machine/finiteModel.js +11 -13
  80. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  81. package/dist/internal/testing/machine/format.d.ts +7 -0
  82. package/dist/internal/testing/machine/format.d.ts.map +1 -0
  83. package/dist/internal/testing/machine/format.js +104 -0
  84. package/dist/internal/testing/machine/format.js.map +1 -0
  85. package/dist/internal/testing/machine/probe.d.ts +1 -1
  86. package/dist/internal/testing/machine/probe.d.ts.map +1 -1
  87. package/dist/internal/testing/machine/probe.js +1 -1
  88. package/dist/internal/testing/machine/probe.js.map +1 -1
  89. package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -1
  90. package/dist/internal/testing/machine/referenceModel.js +15 -19
  91. package/dist/internal/testing/machine/referenceModel.js.map +1 -1
  92. package/dist/internal/testing/machine/trace.d.ts +1 -1
  93. package/dist/internal/testing/machine/trace.d.ts.map +1 -1
  94. package/dist/internal/testing/machine/trace.js +3 -3
  95. package/dist/internal/testing/machine/trace.js.map +1 -1
  96. package/dist/internal/testing/machine/value.d.ts +8 -0
  97. package/dist/internal/testing/machine/value.d.ts.map +1 -0
  98. package/dist/internal/testing/machine/value.js +82 -0
  99. package/dist/internal/testing/machine/value.js.map +1 -0
  100. package/dist/internal/testing/machine/verification.d.ts +2 -13
  101. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  102. package/dist/internal/testing/machine/verification.js +21 -113
  103. package/dist/internal/testing/machine/verification.js.map +1 -1
  104. package/dist/testing/MachineTest.d.ts +89 -104
  105. package/dist/testing/MachineTest.d.ts.map +1 -1
  106. package/dist/testing/MachineTest.js +70 -84
  107. package/dist/testing/MachineTest.js.map +1 -1
  108. package/dist/unstable/cluster/ClusterMachine.d.ts +10 -13
  109. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  110. package/dist/unstable/cluster/ClusterMachine.js +5 -8
  111. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  112. package/dist/unstable/reactivity/AtomMachine.d.ts +26 -61
  113. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  114. package/dist/unstable/reactivity/AtomMachine.js +17 -26
  115. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  116. package/docs/agent-guide.md +45 -45
  117. package/docs/effect-atom-react.md +35 -63
  118. package/docs/machine-review.md +7 -7
  119. package/docs/root-api.md +259 -0
  120. package/package.json +1 -1
  121. package/src/Machine.ts +1053 -538
  122. package/src/internal/machine/atom.ts +3 -13
  123. package/src/internal/machine/childRegistry.ts +141 -0
  124. package/src/internal/machine/cluster.ts +5 -14
  125. package/src/internal/machine/configuration.ts +20 -4
  126. package/src/internal/machine/executionPlan.ts +109 -52
  127. package/src/internal/machine/invocation.ts +10 -6
  128. package/src/internal/machine/invocationDefinition.ts +52 -0
  129. package/src/internal/machine/invocationEvent.ts +0 -8
  130. package/src/internal/machine/machine.ts +222 -716
  131. package/src/internal/machine/planner.ts +74 -62
  132. package/src/internal/machine/process.ts +14 -21
  133. package/src/internal/machine/protocol.ts +9 -32
  134. package/src/internal/machine/readiness.ts +1 -0
  135. package/src/internal/machine/requirements.ts +11 -0
  136. package/src/internal/machine/runtime.ts +17 -186
  137. package/src/internal/machine/serialization.ts +22 -0
  138. package/src/internal/machine/stateDefinition.ts +36 -3
  139. package/src/internal/machine/targetBuilder.ts +525 -0
  140. package/src/internal/machine/topology.ts +3 -3
  141. package/src/internal/machine/transition.ts +26 -0
  142. package/src/internal/testing/machine/finiteModel.ts +23 -24
  143. package/src/internal/testing/machine/format.ts +120 -0
  144. package/src/internal/testing/machine/probe.ts +4 -4
  145. package/src/internal/testing/machine/referenceModel.ts +25 -17
  146. package/src/internal/testing/machine/trace.ts +4 -4
  147. package/src/internal/testing/machine/value.ts +77 -0
  148. package/src/internal/testing/machine/verification.ts +904 -1109
  149. package/src/testing/MachineTest.ts +103 -114
  150. package/src/unstable/cluster/ClusterMachine.ts +10 -20
  151. package/src/unstable/reactivity/AtomMachine.ts +42 -79
@@ -0,0 +1,525 @@
1
+ /** Constructs decoded snapshots and transition targets from a captured state tree. */
2
+ import { hasProperty } from "effect/Predicate"
3
+ import type { Machine } from "../../Machine.js"
4
+ import { SnapshotBuilderStateTypeId } from "./symbols.js"
5
+ import * as Topology from "./topology.js"
6
+
7
+ type SnapshotBuilderOptions = {
8
+ readonly mode: "initial" | "full"
9
+ readonly prefix: string
10
+ }
11
+
12
+ type FromMethodKind = "leaf" | "nested"
13
+
14
+ export const withFrom = <Method extends (value: unknown, ...args: ReadonlyArray<any>) => unknown>(
15
+ method: Method,
16
+ kind: FromMethodKind,
17
+ valued: boolean
18
+ ): {
19
+ readonly decoded?: Method
20
+ readonly from: (...args: ReadonlyArray<any>) => unknown
21
+ } => {
22
+ const builder: Record<string, unknown> = {}
23
+ if (valued) {
24
+ Object.defineProperty(builder, "decoded", {
25
+ value: method,
26
+ enumerable: false
27
+ })
28
+ }
29
+ Object.defineProperty(builder, "from", {
30
+ value: (...args: ReadonlyArray<any>) => {
31
+ if (!valued) {
32
+ return method(undefined, ...args)
33
+ }
34
+ const omitted = args.length === 0 || (kind === "nested" && args.length === 1 && typeof args[0] === "function")
35
+ const input = omitted ? {} : args[0]
36
+ const rest = omitted ? args : args.slice(1)
37
+ return method(Topology.makeStateInput(input), ...rest)
38
+ },
39
+ enumerable: false
40
+ })
41
+ return builder as {
42
+ readonly decoded?: Method
43
+ readonly from: (...args: ReadonlyArray<any>) => unknown
44
+ }
45
+ }
46
+
47
+ const withInitial = <Builder extends object>(
48
+ builder: Builder,
49
+ path: string,
50
+ valued: boolean,
51
+ values?: Readonly<Record<string, unknown>>
52
+ ): Builder => {
53
+ const initial = withFrom(
54
+ (value: unknown) => Topology.makeInitialTarget(path, value, values),
55
+ "leaf",
56
+ valued
57
+ )
58
+ Object.defineProperty(builder, "initial", {
59
+ value: Object.freeze(initial),
60
+ enumerable: false
61
+ })
62
+ return builder
63
+ }
64
+
65
+ export const makeSnapshotBuilder = (
66
+ states: Machine.StateTree,
67
+ options: SnapshotBuilderOptions
68
+ ): unknown => {
69
+ const builder: Record<string, unknown> = {}
70
+ for (const key of Object.keys(states)) {
71
+ const definition = states[key]!
72
+ const pseudoType = (definition as { readonly type?: unknown }).type
73
+ if (pseudoType === "history") {
74
+ continue
75
+ }
76
+ const path = options.prefix === "" ? key : `${options.prefix}.${key}`
77
+ if (pseudoType === "choice") {
78
+ builder[key] = Object.freeze(() => Topology.makeChoiceTarget(path, getParentPathRuntime(path)))
79
+ continue
80
+ }
81
+ const node = Topology.getStateNodeDefinition(path, definition)
82
+ const method = withFrom(
83
+ makeSnapshotFactory(definition, key, options),
84
+ node.states === undefined ? "leaf" : "nested",
85
+ node.schema !== undefined
86
+ )
87
+ builder[key] = Object.freeze(
88
+ node.states === undefined || options.mode !== "full" || options.prefix !== ""
89
+ ? method
90
+ : withInitial(method, path, node.schema !== undefined)
91
+ )
92
+ }
93
+ return Object.freeze(builder)
94
+ }
95
+
96
+ const makeParallelSnapshotBuilder = (
97
+ states: Machine.StateTree,
98
+ options: SnapshotBuilderOptions,
99
+ regions: Readonly<Record<string, unknown>>
100
+ ): unknown => {
101
+ const builder: Record<string, unknown> = {}
102
+ Object.defineProperty(builder, SnapshotBuilderStateTypeId, {
103
+ value: regions,
104
+ enumerable: false
105
+ })
106
+ for (const key of Object.keys(states)) {
107
+ const definition = states[key]!
108
+ const pseudoType = (definition as { readonly type?: unknown }).type
109
+ if (pseudoType === "history" || pseudoType === "choice") {
110
+ continue
111
+ }
112
+ if (hasProperty(regions, key)) {
113
+ continue
114
+ }
115
+ const path = options.prefix === "" ? key : `${options.prefix}.${key}`
116
+ const node = Topology.getStateNodeDefinition(path, definition)
117
+ const method = withFrom(
118
+ (value: unknown, selector?: (builder: unknown) => unknown) => {
119
+ const nextRegions: Record<string, unknown> = {}
120
+ for (const regionKey of Object.keys(regions)) {
121
+ nextRegions[regionKey] = regions[regionKey]
122
+ }
123
+ nextRegions[key] = makeSnapshotForNode(definition, key, value, selector, options)
124
+ return makeParallelSnapshotBuilder(states, options, nextRegions)
125
+ },
126
+ node.states === undefined ? "leaf" : "nested",
127
+ node.schema !== undefined
128
+ )
129
+ builder[key] = method
130
+ }
131
+ return builder
132
+ }
133
+
134
+ const getParallelSnapshotBuilderRegions = (
135
+ path: string,
136
+ states: Machine.StateTree,
137
+ builder: unknown
138
+ ): Readonly<Record<string, unknown>> => {
139
+ if (typeof builder !== "object" || builder === null || !hasProperty(builder, SnapshotBuilderStateTypeId)) {
140
+ throw new Error(`Machine expected parallel state "${path}" builder callback to return a builder`)
141
+ }
142
+ const regions = (builder as { readonly [SnapshotBuilderStateTypeId]: Readonly<Record<string, unknown>> })[
143
+ SnapshotBuilderStateTypeId
144
+ ]
145
+ for (const key of Object.keys(states)) {
146
+ const pseudoType = (states[key] as { readonly type?: unknown }).type
147
+ if (pseudoType === "history" || pseudoType === "choice") {
148
+ continue
149
+ }
150
+ if (!hasProperty(regions, key)) {
151
+ throw new Error(`Machine expected parallel state "${path}" builder callback to provide region "${key}"`)
152
+ }
153
+ }
154
+ return regions
155
+ }
156
+
157
+ /**
158
+ * A factory owns its lazily compiled child builder. The builder captures only
159
+ * immutable topology; every call constructs fresh values and configurations.
160
+ * Parallel builders remain per-call because they accumulate selected regions.
161
+ */
162
+ const makeSnapshotFactory = (
163
+ definition: Machine.TaggedSchema | Machine.StateNodeConfig,
164
+ key: string,
165
+ options: SnapshotBuilderOptions
166
+ ): (value: unknown, selector?: (builder: unknown) => unknown) => Record<string, unknown> => {
167
+ const path = options.prefix === "" ? key : `${options.prefix}.${key}`
168
+ const mode = options.mode
169
+ const node = Topology.getStateNodeDefinition(path, definition)
170
+ let childBuilder: unknown
171
+ return (value, selector) => {
172
+ const snapshot: Record<string, unknown> = { path, value }
173
+ if (node.states === undefined) return snapshot
174
+ if (selector === undefined) {
175
+ throw new Error(`Machine expected state "${path}" builder to provide active child states`)
176
+ }
177
+ if (node.type === "parallel") {
178
+ const builder = makeParallelSnapshotBuilder(node.states, { mode, prefix: path }, {})
179
+ snapshot.states = getParallelSnapshotBuilderRegions(path, node.states, selector(builder))
180
+ return snapshot
181
+ }
182
+ if (childBuilder === undefined) {
183
+ const childStates = mode === "initial" && node.initial !== undefined
184
+ ? { [node.initial]: node.states[node.initial]! }
185
+ : node.states
186
+ childBuilder = makeSnapshotBuilder(childStates, { mode, prefix: path })
187
+ }
188
+ snapshot.state = selector(childBuilder)
189
+ return snapshot
190
+ }
191
+ }
192
+
193
+ const makeSnapshotForNode = (
194
+ definition: Machine.TaggedSchema | Machine.StateNodeConfig,
195
+ key: string,
196
+ value: unknown,
197
+ selector: ((builder: unknown) => unknown) | undefined,
198
+ options: SnapshotBuilderOptions
199
+ ): Record<string, unknown> => makeSnapshotFactory(definition, key, options)(value, selector)
200
+
201
+ export const getTargetBuilderNode = (
202
+ stateNodes: Machine.StateNodes,
203
+ path: string
204
+ ): Machine.StateNode => {
205
+ const node = stateNodes.byPath.get(path)
206
+ if (node === undefined) {
207
+ throw new Error(`Machine expected state path "${path}" to exist`)
208
+ }
209
+ return node
210
+ }
211
+
212
+ export const getLocalTargetScope = (
213
+ stateNodes: Machine.StateNodes,
214
+ source: string
215
+ ): string | undefined => {
216
+ let current: string | undefined = source
217
+ while (current !== undefined) {
218
+ const node = stateNodes.byPath.get(current)
219
+ if (node === undefined) {
220
+ return undefined
221
+ }
222
+ if (node.type === "compound") {
223
+ return node.path
224
+ }
225
+ current = node.parent
226
+ }
227
+ return undefined
228
+ }
229
+
230
+ const hasTargetValues = (
231
+ values: Readonly<Record<string, unknown>> | undefined
232
+ ): values is Readonly<Record<string, unknown>> => values !== undefined && Object.keys(values).length > 0
233
+
234
+ const makeTargetWithValues = (
235
+ path: string,
236
+ value: unknown,
237
+ values: Readonly<Record<string, unknown>> | undefined
238
+ ): Machine.Target<any, any> =>
239
+ hasTargetValues(values)
240
+ ? Topology.makeTarget(path as any, value as any, { values: values as any })
241
+ : Topology.makeTarget(path as any, value as any)
242
+
243
+ const getTargetBuilderDefinition = (
244
+ states: Machine.StateTree,
245
+ targetPath: string
246
+ ): Machine.TaggedSchema | Machine.StateNodeConfig => {
247
+ let children = states
248
+ let path = ""
249
+ let definition: Machine.TaggedSchema | Machine.StateNodeConfig | undefined
250
+ const segments = Object.hasOwn(states, "") && targetPath !== ""
251
+ ? ["", ...targetPath.split(".")]
252
+ : targetPath.split(".")
253
+ for (const key of segments) {
254
+ if (!hasProperty(children, key)) {
255
+ throw new Error(`Machine expected state path "${targetPath}" to exist`)
256
+ }
257
+ definition = children[key]!
258
+ path = path === "" ? key : `${path}.${key}`
259
+ const node = Topology.getStateNodeDefinition(path, definition)
260
+ children = node.states ?? {}
261
+ }
262
+ return definition!
263
+ }
264
+
265
+ const makeParallelTarget = (
266
+ states: Machine.StateTree,
267
+ node: Machine.StateNode,
268
+ value: unknown,
269
+ selector: ((builder: unknown) => unknown) | undefined,
270
+ values: Readonly<Record<string, unknown>> | undefined
271
+ ): Machine.Target<any, any> => {
272
+ if (selector === undefined) {
273
+ throw new Error(`Machine expected parallel target "${node.path}" builder to provide every active region`)
274
+ }
275
+ const snapshot = makeSnapshotForNode(
276
+ getTargetBuilderDefinition(states, node.path),
277
+ node.key,
278
+ value,
279
+ selector,
280
+ { mode: "full", prefix: node.parent ?? "" }
281
+ )
282
+ return Topology.makeTarget(node.path as any, value as any, {
283
+ snapshot: snapshot as any,
284
+ values: values as any
285
+ })
286
+ }
287
+
288
+ const extendTargetValues = (
289
+ values: Readonly<Record<string, unknown>> | undefined,
290
+ path: string,
291
+ value: unknown
292
+ ): Readonly<Record<string, unknown>> => {
293
+ const next: Record<string, unknown> = {}
294
+ if (values !== undefined) {
295
+ for (const key of Object.keys(values)) {
296
+ next[key] = values[key]
297
+ }
298
+ }
299
+ next[path] = value
300
+ return next
301
+ }
302
+
303
+ const makeLocalTargetChildBuilder = (
304
+ states: Machine.StateTree,
305
+ stateNodes: Machine.StateNodes,
306
+ parentPath: string,
307
+ values: Readonly<Record<string, unknown>> | undefined,
308
+ source: string
309
+ ): unknown => {
310
+ const parent = getTargetBuilderNode(stateNodes, parentPath)
311
+ const builder: Record<string, unknown> = {}
312
+ for (
313
+ const childPath of Array.from(stateNodes.byPath.values())
314
+ .filter((node) => node.parent === parent.path && node.type !== "history")
315
+ .map((node) => node.path)
316
+ ) {
317
+ const child = getTargetBuilderNode(stateNodes, childPath)
318
+ if (child.type === "choice") {
319
+ builder[child.key] = () => Topology.makeChoiceTarget(child.path, parent.path, values)
320
+ continue
321
+ }
322
+ const method = withFrom(
323
+ (value: unknown, selector?: (builder: unknown) => unknown) => {
324
+ if (child.type === "atomic" || child.type === "final") {
325
+ return makeTargetWithValues(child.path, value, values)
326
+ }
327
+ if (child.type === "parallel") {
328
+ if (source !== child.path && !(child.path === "" || source.startsWith(`${child.path}.`))) {
329
+ return makeParallelTarget(states, child, value, selector, values)
330
+ }
331
+ if (selector === undefined) {
332
+ throw new Error(`Machine expected target "${child.path}" builder to provide an active child state`)
333
+ }
334
+ return selector(makeLocalTargetChildBuilder(
335
+ states,
336
+ stateNodes,
337
+ child.path,
338
+ child.schema === undefined ? values : extendTargetValues(values, child.path, value),
339
+ source
340
+ ))
341
+ }
342
+ if (selector === undefined) {
343
+ throw new Error(`Machine expected target "${child.path}" builder to provide an active child state`)
344
+ }
345
+ return selector(makeLocalTargetChildBuilder(
346
+ states,
347
+ stateNodes,
348
+ child.path,
349
+ child.schema === undefined ? values : extendTargetValues(values, child.path, value),
350
+ source
351
+ ))
352
+ },
353
+ child.type === "atomic" || child.type === "final" ? "leaf" : "nested",
354
+ child.schema !== undefined
355
+ )
356
+ builder[child.key] = child.type === "atomic" || child.type === "final"
357
+ ? method
358
+ : withInitial(method, child.path, child.schema !== undefined, values)
359
+ }
360
+ return builder
361
+ }
362
+
363
+ const makeLocalTargetBuilder = (
364
+ states: Machine.StateTree,
365
+ stateNodes: Machine.StateNodes,
366
+ source: string
367
+ ): unknown => {
368
+ const scope = getLocalTargetScope(stateNodes, source)
369
+ if (scope === undefined) {
370
+ return {}
371
+ }
372
+ const builder = makeLocalTargetChildBuilder(states, stateNodes, scope, undefined, source) as Record<string, unknown>
373
+ const scopeNode = getTargetBuilderNode(stateNodes, scope)
374
+ if (scopeNode.schema !== undefined) {
375
+ builder.with = withFrom(
376
+ (value: unknown, selector?: (builder: unknown) => unknown) => {
377
+ if (selector === undefined) {
378
+ throw new Error(`Machine expected target "${scope}" builder to provide an active child state`)
379
+ }
380
+ return selector(makeLocalTargetChildBuilder(states, stateNodes, scope, { [scope]: value }, source))
381
+ },
382
+ "nested",
383
+ true
384
+ )
385
+ }
386
+ return builder
387
+ }
388
+
389
+ const addBranchTargetChildren = (
390
+ builder: Record<string, unknown>,
391
+ states: Machine.StateTree,
392
+ stateNodes: Machine.StateNodes,
393
+ parentPath: string,
394
+ values: Readonly<Record<string, unknown>> | undefined,
395
+ source: string
396
+ ): void => {
397
+ const parent = getTargetBuilderNode(stateNodes, parentPath)
398
+ for (
399
+ const childPath of Array.from(stateNodes.byPath.values())
400
+ .filter((node) => node.parent === parent.path && node.type !== "history")
401
+ .map((node) => node.path)
402
+ ) {
403
+ const child = getTargetBuilderNode(stateNodes, childPath)
404
+ if (child.type === "choice") {
405
+ builder[child.key] = () => Topology.makeChoiceTarget(child.path, parent.path, values)
406
+ continue
407
+ }
408
+ builder[child.key] = makeBranchTargetNodeBuilder(states, stateNodes, child.path, values, source)
409
+ }
410
+ }
411
+
412
+ const makeBranchTargetNodeBuilder = (
413
+ states: Machine.StateTree,
414
+ stateNodes: Machine.StateNodes,
415
+ path: string,
416
+ values: Readonly<Record<string, unknown>> | undefined,
417
+ source: string
418
+ ): unknown => {
419
+ const node = getTargetBuilderNode(stateNodes, path)
420
+ if (node.type === "atomic" || node.type === "final") {
421
+ return withFrom(
422
+ (value: unknown) => makeTargetWithValues(node.path, value, values),
423
+ "leaf",
424
+ node.schema !== undefined
425
+ )
426
+ }
427
+ const builder = withFrom(
428
+ (value: unknown, selector?: (builder: unknown) => unknown) => {
429
+ if (node.type === "parallel") {
430
+ if (source !== node.path && !(node.path === "" || source.startsWith(`${node.path}.`))) {
431
+ return makeParallelTarget(states, node, value, selector, values)
432
+ }
433
+ if (selector === undefined) {
434
+ throw new Error(`Machine expected target "${node.path}" builder to provide an active child state`)
435
+ }
436
+ const nextBuilder: Record<string, unknown> = {}
437
+ addBranchTargetChildren(
438
+ nextBuilder,
439
+ states,
440
+ stateNodes,
441
+ node.path,
442
+ node.schema === undefined ? values : extendTargetValues(values, node.path, value),
443
+ source
444
+ )
445
+ return selector(nextBuilder)
446
+ }
447
+ if (selector === undefined) {
448
+ throw new Error(`Machine expected target "${node.path}" builder to provide an active child state`)
449
+ }
450
+ const nextBuilder: Record<string, unknown> = {}
451
+ addBranchTargetChildren(
452
+ nextBuilder,
453
+ states,
454
+ stateNodes,
455
+ node.path,
456
+ node.schema === undefined ? values : extendTargetValues(values, node.path, value),
457
+ source
458
+ )
459
+ return selector(nextBuilder)
460
+ },
461
+ "nested",
462
+ node.schema !== undefined
463
+ ) as unknown as Record<string, unknown>
464
+ withInitial(builder, node.path, node.schema !== undefined, values)
465
+ if (node.type !== "parallel" || source === node.path || node.path === "" || source.startsWith(`${node.path}.`)) {
466
+ addBranchTargetChildren(builder, states, stateNodes, node.path, values, source)
467
+ }
468
+ return builder
469
+ }
470
+
471
+ const makeBranchTargetBuilder = (
472
+ states: Machine.StateTree,
473
+ stateNodes: Machine.StateNodes,
474
+ source: string
475
+ ): unknown => {
476
+ const rootPath = stateNodes.roots[0]!
477
+ const root = getTargetBuilderNode(stateNodes, rootPath)
478
+ return {
479
+ [root.key]: makeBranchTargetNodeBuilder(states, stateNodes, root.path, undefined, source)
480
+ }
481
+ }
482
+
483
+ const makeHistoryTargetBuilder = (
484
+ states: Machine.StateTree,
485
+ prefix: string
486
+ ): unknown => {
487
+ const builder: Record<string, unknown> = {}
488
+ for (const key of Object.keys(states)) {
489
+ const path = prefix === "" ? key : `${prefix}.${key}`
490
+ const definition = Topology.getStateNodeDefinition(path, states[key]!)
491
+ if (definition.type === "history") {
492
+ const parent = getParentPathRuntime(path)
493
+ builder[key] = () => Topology.makeHistoryTarget(path, parent)
494
+ continue
495
+ }
496
+ if (definition.states !== undefined) {
497
+ builder[key] = makeHistoryTargetBuilder(definition.states, path)
498
+ }
499
+ }
500
+ return builder
501
+ }
502
+
503
+ const getParentPathRuntime = (path: string): string => {
504
+ const separator = path.lastIndexOf(".")
505
+ if (separator < 0) {
506
+ return ""
507
+ }
508
+ return path.slice(0, separator)
509
+ }
510
+
511
+ export const makeTargetBuilder = <const States extends Machine.StateSchemas>(
512
+ states: States,
513
+ stateNodes: Machine.StateNodes
514
+ ) => {
515
+ const full = makeSnapshotBuilder(states, { mode: "full", prefix: "" }) as Machine.FullTargetBuilder<States>
516
+ const history = makeHistoryTargetBuilder(states, "") as Machine.HistoryTargetBuilder<States>
517
+ return <Source extends Machine.StateNodeIdentifier<States>>(source: Source): Machine.TargetBuilder<States, Source> =>
518
+ ({
519
+ none: Topology.makeNoTarget,
520
+ local: makeLocalTargetBuilder(states, stateNodes, source),
521
+ branch: makeBranchTargetBuilder(states, stateNodes, source),
522
+ full,
523
+ history
524
+ }) as Machine.TargetBuilder<States, Source>
525
+ }
@@ -374,7 +374,7 @@ export const compileStateNodes = (states: Machine.StateSchemas): Machine.StateNo
374
374
  if (key.includes(".")) {
375
375
  throw new Error(`Machine state keys cannot contain ".": "${key}"`)
376
376
  }
377
- const path = parent === undefined ? key : `${parent}.${key}`
377
+ const path = parent === undefined || parent === "" ? key : `${parent}.${key}`
378
378
  const definition = getStateNodeDefinition(path, tree[key]!)
379
379
  let node: Machine.StateNode
380
380
  let childStates: Machine.StateTree | undefined
@@ -401,7 +401,7 @@ export const compileStateNodes = (states: Machine.StateSchemas): Machine.StateNo
401
401
  history: undefined,
402
402
  parent,
403
403
  children: [],
404
- initial: `${path}.${definition.initial}`
404
+ initial: path === "" ? definition.initial : `${path}.${definition.initial}`
405
405
  }
406
406
  childStates = definition.states
407
407
  break
@@ -627,7 +627,7 @@ export const getSnapshotByPath = (
627
627
  if (snapshot.path === path) {
628
628
  return Option.some(snapshot)
629
629
  }
630
- if (!path.startsWith(`${snapshot.path}.`)) {
630
+ if (snapshot.path !== "" && !path.startsWith(`${snapshot.path}.`)) {
631
631
  return Option.none()
632
632
  }
633
633
  if (parents !== undefined) {
@@ -0,0 +1,26 @@
1
+ import type { Enqueue, Machine } from "../../Machine.js"
2
+
3
+ /** Captured callbacks shared by definition construction and both planners. */
4
+ export type TransitionHandler<States extends Machine.StateSchemas, E, R, Context> = (
5
+ context: Context,
6
+ enqueue: Enqueue<any, any>
7
+ ) => Machine.HandlerResult<States, E, R> | Machine.Declined
8
+
9
+ export type TransitionEvaluator<States extends Machine.StateSchemas, E, R, Context> = (
10
+ context: Context,
11
+ enqueue: Enqueue<any, any>
12
+ ) => {
13
+ readonly result: Machine.HandlerResult<States, E, R> | Machine.Declined
14
+ readonly branchIndex: number
15
+ readonly branchKey: string | undefined
16
+ }
17
+
18
+ export type EventTransition<States extends Machine.StateSchemas, E, R, Context> =
19
+ | TransitionHandler<States, E, R, Context>
20
+ | {
21
+ readonly reenter?: boolean
22
+ readonly declinable?: boolean
23
+ readonly targets?: ReadonlyArray<string>
24
+ readonly transition: TransitionHandler<States, E, R, Context>
25
+ readonly evaluate?: TransitionEvaluator<States, E, R, Context>
26
+ }
@@ -1352,7 +1352,7 @@ const selectDefinitionTarget = (
1352
1352
  const sourceRoot = byPath.get(source)!.root
1353
1353
  if (selected.root !== sourceRoot) {
1354
1354
  const root = target.split(".")[0]!
1355
- return selector.full[root]()
1355
+ return selector.branch[root]()
1356
1356
  }
1357
1357
  const selectable = selectableDefinitionTarget(source, target, byPath)
1358
1358
  let current = selector.branch
@@ -1442,7 +1442,7 @@ const makeHandlers = (
1442
1442
  ? () => undefined
1443
1443
  : ({ target }: { readonly target: any }) =>
1444
1444
  resolveDefinitionTarget(target, path, transition.target!, byPath, transition.targetValue)
1445
- return selected.resolve(resolve, "reenter" in transition ? { reenter: transition.reenter } : undefined)
1445
+ return ("reenter" in transition && transition.reenter ? selected.reenter() : selected).resolve(resolve)
1446
1446
  }
1447
1447
  if (transition.trigger.type === "event") on[transition.trigger.event] = config
1448
1448
  else if (transition.trigger.type === "always") always = config
@@ -1452,17 +1452,18 @@ const makeHandlers = (
1452
1452
  ? Object.fromEntries(node.states.flatMap((child) => {
1453
1453
  if (child._tag !== "History") return []
1454
1454
  return [[child.key, {
1455
- default: ({ target }: { readonly target: Record<string, any> }) => {
1456
- const fallback = byPath.get(child.fallback)!
1457
- const parts = child.fallback.split(".")
1458
- const completeRoot = selectSnapshot(target, fallback.root, byPath, parts, 0)
1459
- if (findSnapshot(completeRoot, path) === undefined) {
1460
- throw new Error(
1461
- `MachineTest.compileModel could not construct history fallback for "${path}.${child.key}"`
1462
- )
1463
- }
1464
- return completeRoot
1465
- }
1455
+ default: ({ target: root }: { readonly target: any }) =>
1456
+ root.from((target: Record<string, any>) => {
1457
+ const fallback = byPath.get(child.fallback)!
1458
+ const parts = child.fallback.split(".")
1459
+ const completeRoot = selectSnapshot(target, fallback.root, byPath, parts, 0)
1460
+ if (findSnapshot(completeRoot, path) === undefined) {
1461
+ throw new Error(
1462
+ `MachineTest.compileModel could not construct history fallback for "${path}.${child.key}"`
1463
+ )
1464
+ }
1465
+ return completeRoot
1466
+ })
1466
1467
  }]]
1467
1468
  }))
1468
1469
  : {}
@@ -1511,23 +1512,21 @@ export const compileModel = (model: FiniteModel): Machine.Machine.Any => {
1511
1512
  const flattened = validateModel(model)
1512
1513
  const byPath = new Map(flattened.map((state) => [state.path, state]))
1513
1514
  const stateTree = makeStateTree(model.roots, undefined)
1514
- const defined = Machine.states(stateTree as any)
1515
+ const defined = Machine.state({ initial: model.initial, states: stateTree } as any)
1515
1516
  const eventSchemas = model.events.map((event) => Schema.TaggedStruct(event, {}))
1516
1517
  const initial = byPath.get(model.initial)!
1517
1518
  const machine = Machine.make({
1518
- states: defined.states as any,
1519
- events: Machine.events(...eventSchemas) as any,
1520
- initial: (to: Record<string, any>) => {
1521
- const selected = to[initial.path]
1522
- const selection = typeof selected === "function" ? selected() : selected.initial
1523
- return selection.resolve(({ target }: { readonly target: any }) =>
1524
- selectSnapshot({ [initial.path]: target }, initial.path, byPath, [initial.path], 0) as any
1525
- )
1526
- }
1519
+ root: defined,
1520
+ events: Machine.eventsFromSchemas(...eventSchemas) as any
1527
1521
  } as any)
1528
1522
  const transitions = new Map(model.transitions.map((transition) => [
1529
1523
  `${transition.source}\u0000${triggerKey(transition.trigger)}`,
1530
1524
  transition
1531
1525
  ]))
1532
- return machine.handle(makeHandlers(model.roots, undefined, byPath, transitions) as any) as Machine.Machine.Any
1526
+ return machine.handle(
1527
+ {
1528
+ initialize: ({ builder }: any) => builder.decoded(stateValue(initial)),
1529
+ states: makeHandlers(model.roots, undefined, byPath, transitions)
1530
+ } as any
1531
+ ) as Machine.Machine.Any
1533
1532
  }