@typeonce/effect-machine 0.16.0 → 0.17.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 (61) hide show
  1. package/README.md +98 -106
  2. package/dist/Machine.d.ts +324 -367
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +61 -96
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +7 -7
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js.map +1 -1
  9. package/dist/internal/machine/cluster.d.ts +2 -2
  10. package/dist/internal/machine/cluster.d.ts.map +1 -1
  11. package/dist/internal/machine/cluster.js +6 -5
  12. package/dist/internal/machine/cluster.js.map +1 -1
  13. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  14. package/dist/internal/machine/executionPlan.js +10 -3
  15. package/dist/internal/machine/executionPlan.js.map +1 -1
  16. package/dist/internal/machine/machine.d.ts +8 -6
  17. package/dist/internal/machine/machine.d.ts.map +1 -1
  18. package/dist/internal/machine/machine.js +191 -25
  19. package/dist/internal/machine/machine.js.map +1 -1
  20. package/dist/internal/machine/planner.d.ts +19 -4
  21. package/dist/internal/machine/planner.d.ts.map +1 -1
  22. package/dist/internal/machine/planner.js +56 -20
  23. package/dist/internal/machine/planner.js.map +1 -1
  24. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  25. package/dist/internal/machine/stateDefinition.js +14 -0
  26. package/dist/internal/machine/stateDefinition.js.map +1 -1
  27. package/dist/internal/machine/topology.d.ts +9 -0
  28. package/dist/internal/machine/topology.d.ts.map +1 -1
  29. package/dist/internal/machine/topology.js +16 -0
  30. package/dist/internal/machine/topology.js.map +1 -1
  31. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  32. package/dist/internal/testing/machine/finiteModel.js +15 -20
  33. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  34. package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
  35. package/dist/internal/testing/machine/transitionCoverage.js +2 -0
  36. package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
  37. package/dist/testing/MachineTest.d.ts +11 -11
  38. package/dist/testing/MachineTest.d.ts.map +1 -1
  39. package/dist/testing/MachineTest.js +5 -8
  40. package/dist/testing/MachineTest.js.map +1 -1
  41. package/dist/unstable/cluster/ClusterMachine.d.ts +2 -2
  42. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  43. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  44. package/dist/unstable/reactivity/AtomMachine.d.ts +12 -12
  45. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  46. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  47. package/docs/agent-guide.md +144 -147
  48. package/package.json +1 -1
  49. package/src/Machine.ts +1118 -1178
  50. package/src/internal/machine/atom.ts +20 -15
  51. package/src/internal/machine/cluster.ts +14 -9
  52. package/src/internal/machine/executionPlan.ts +8 -3
  53. package/src/internal/machine/machine.ts +301 -40
  54. package/src/internal/machine/planner.ts +89 -27
  55. package/src/internal/machine/stateDefinition.ts +39 -0
  56. package/src/internal/machine/topology.ts +28 -0
  57. package/src/internal/testing/machine/finiteModel.ts +18 -21
  58. package/src/internal/testing/machine/transitionCoverage.ts +2 -0
  59. package/src/testing/MachineTest.ts +14 -11
  60. package/src/unstable/cluster/ClusterMachine.ts +8 -4
  61. package/src/unstable/reactivity/AtomMachine.ts +19 -12
@@ -19,6 +19,8 @@ import type {
19
19
  MachineRef,
20
20
  MachineSchemaDecodeError,
21
21
  MachineSchemaEncodeError,
22
+ Parent,
23
+ ParentMode,
22
24
  Runtime,
23
25
  RuntimeOutcome,
24
26
  SpawnOptions,
@@ -49,6 +51,7 @@ export {
49
51
  export { ChildMachineLogicTypeId, InitialEventTypeId, SnapshotBuilderStateTypeId } from "./symbols.js"
50
52
 
51
53
  const TypeId = "~effect/Machine"
54
+ const ParentTypeId = "~effect/Machine/Parent"
52
55
  export const InvokeTypeId: unique symbol = Symbol.for("effect/Machine/Invoke")
53
56
  export const TransitionTypeId: unique symbol = Symbol.for("effect/Machine/Transition")
54
57
  const ChildMachineTypeId = "~effect/Machine/ChildMachine"
@@ -112,8 +115,6 @@ const Proto = {
112
115
  }
113
116
  }
114
117
 
115
- const makeBoundInvoke = (config: unknown): unknown => config
116
-
117
118
  const makeWithHandlers = (
118
119
  self: Definition.Any,
119
120
  handlers: Machine.StateConfigs<any, any, any, any, any, any, any>
@@ -123,7 +124,7 @@ const makeWithHandlers = (
123
124
  machine.events = self.events
124
125
  machine.internalEvents = self.internalEvents
125
126
  machine.emittedEvents = self.emittedEvents
126
- machine.parentEvents = self.parentEvents
127
+ machine.parent = self.parent
127
128
  machine.input = self.input
128
129
  machine.id = self.id
129
130
  machine.initial = self.initial
@@ -131,7 +132,6 @@ const makeWithHandlers = (
131
132
  machine.stateNodes = self.stateNodes
132
133
  machine.makeTargetBuilder = self.makeTargetBuilder
133
134
  machine.handlers = handlers
134
- machine.invoke = makeBoundInvoke
135
135
  Protocol.copyProtocol(self, machine)
136
136
  return machine
137
137
  }
@@ -139,6 +139,7 @@ const makeWithHandlers = (
139
139
  type DefinitionBranch = {
140
140
  readonly target: (selector: unknown) => unknown
141
141
  readonly resolve?: (context: any, enqueue: unknown) => unknown
142
+ readonly declinable?: boolean
142
143
  }
143
144
 
144
145
  type CapturedBranch = DefinitionBranch & {
@@ -156,6 +157,189 @@ type CapturedNamedBranch = {
156
157
  readonly selection: Topology.TargetSelection
157
158
  }
158
159
 
160
+ const TransitionBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/TransitionBuilderDescriptor")
161
+
162
+ type DirectTransitionDescriptor = {
163
+ readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId
164
+ readonly type: "direct"
165
+ readonly selection: Topology.TargetSelection
166
+ readonly resolve?: (context: any, enqueue: unknown) => unknown
167
+ readonly reenter: boolean
168
+ readonly declinable: boolean
169
+ }
170
+
171
+ type BranchesTransitionDescriptor = {
172
+ readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId
173
+ readonly type: "branches"
174
+ readonly declarations: unknown
175
+ readonly resolve: (context: any, enqueue: unknown) => unknown
176
+ readonly reenter: boolean
177
+ readonly declinable: boolean
178
+ }
179
+
180
+ type TransitionBuilderDescriptor = DirectTransitionDescriptor | BranchesTransitionDescriptor
181
+
182
+ const InitialBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InitialBuilderDescriptor")
183
+
184
+ type InitialBuilderDescriptor = {
185
+ readonly [InitialBuilderDescriptorTypeId]: typeof InitialBuilderDescriptorTypeId
186
+ readonly selection: Topology.TargetSelection
187
+ readonly resolve: (context: any) => unknown
188
+ }
189
+
190
+ const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
191
+ selection.kind === "none"
192
+ ? Topology.noneTargetSelection
193
+ : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope)
194
+
195
+ const transitionOptions = (options: unknown): { readonly reenter: boolean; readonly declinable: boolean } => {
196
+ const configuration = typeof options === "object" && options !== null
197
+ ? options as { readonly reenter?: unknown; readonly declinable?: unknown }
198
+ : {}
199
+ return {
200
+ reenter: configuration.reenter === true,
201
+ declinable: configuration.declinable === true
202
+ }
203
+ }
204
+
205
+ const makeDirectTransitionDescriptor = (
206
+ selection: Topology.TargetSelection,
207
+ resolve: ((context: any, enqueue: unknown) => unknown) | undefined,
208
+ options: unknown
209
+ ): DirectTransitionDescriptor => {
210
+ const shared: Omit<DirectTransitionDescriptor, "resolve"> = {
211
+ [TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
212
+ type: "direct",
213
+ selection: plainTargetSelection(selection),
214
+ ...transitionOptions(options)
215
+ }
216
+ return resolve === undefined
217
+ ? Object.freeze(shared)
218
+ : Object.freeze({ ...shared, resolve })
219
+ }
220
+
221
+ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
222
+ Object.freeze({
223
+ ...selection,
224
+ resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
225
+ makeDirectTransitionDescriptor(selection, resolve, options),
226
+ reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true })
227
+ })
228
+
229
+ const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
230
+
231
+ const makeInitialBuilderDescriptor = (
232
+ selection: Topology.TargetSelection,
233
+ resolve: (context: any) => unknown
234
+ ): InitialBuilderDescriptor =>
235
+ Object.freeze({
236
+ [InitialBuilderDescriptorTypeId]: InitialBuilderDescriptorTypeId,
237
+ selection: plainTargetSelection(selection),
238
+ resolve
239
+ })
240
+
241
+ const decorateInitialSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
242
+ Object.freeze({
243
+ ...selection,
244
+ resolve: (resolve: (context: any) => unknown) => makeInitialBuilderDescriptor(selection, resolve)
245
+ })
246
+
247
+ const decorateInitialSelectorNode = (node: unknown): unknown => {
248
+ if (Topology.isTargetSelection(node)) return decorateInitialSelection(node)
249
+ if (typeof node === "function") {
250
+ const wrapped = ((...args: ReadonlyArray<unknown>) => decorateInitialSelection(node(...args))) as
251
+ & ((...args: ReadonlyArray<unknown>) => unknown)
252
+ & Record<string, unknown>
253
+ for (const key of Object.keys(node)) {
254
+ wrapped[key] = decorateInitialSelectorNode((node as unknown as Record<string, unknown>)[key])
255
+ }
256
+ return Object.freeze(wrapped)
257
+ }
258
+ if (typeof node === "object" && node !== null) {
259
+ const wrapped: Record<string, unknown> = {}
260
+ for (const key of Object.keys(node)) {
261
+ wrapped[key] = decorateInitialSelectorNode((node as Record<string, unknown>)[key])
262
+ }
263
+ return Object.freeze(wrapped)
264
+ }
265
+ return node
266
+ }
267
+
268
+ const decorateTransitionSelectorNode = (node: unknown): unknown => {
269
+ if (Topology.isTargetSelection(node)) {
270
+ return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
271
+ }
272
+ if (typeof node === "function") {
273
+ const wrapped = ((...args: ReadonlyArray<unknown>) => decorateTransitionSelection(node(...args))) as
274
+ & ((...args: ReadonlyArray<unknown>) => unknown)
275
+ & Record<string, unknown>
276
+ for (const key of Object.keys(node)) {
277
+ wrapped[key] = decorateTransitionSelectorNode((node as unknown as Record<string, unknown>)[key])
278
+ }
279
+ return Object.freeze(wrapped)
280
+ }
281
+ if (typeof node === "object" && node !== null) {
282
+ const wrapped: Record<string, unknown> = {}
283
+ for (const key of Object.keys(node)) {
284
+ wrapped[key] = decorateTransitionSelectorNode((node as Record<string, unknown>)[key])
285
+ }
286
+ return Object.freeze(wrapped)
287
+ }
288
+ return node
289
+ }
290
+
291
+ const makeTransitionSelector = (
292
+ stateNodes: Machine.StateNodes,
293
+ source: string
294
+ ): unknown => {
295
+ const selector = {
296
+ ...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record<string, unknown>
297
+ }
298
+ selector.branches = (declarations: unknown) =>
299
+ Object.freeze({
300
+ resolve: (
301
+ resolve: (context: any, enqueue: unknown) => unknown,
302
+ options?: unknown
303
+ ): BranchesTransitionDescriptor =>
304
+ Object.freeze({
305
+ [TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
306
+ type: "branches",
307
+ declarations,
308
+ resolve,
309
+ ...transitionOptions(options)
310
+ })
311
+ })
312
+ return Object.freeze(selector)
313
+ }
314
+
315
+ const normalizeTransitionBuilder = (
316
+ transition: (selector: unknown) => unknown,
317
+ stateNodes: Machine.StateNodes,
318
+ path: string
319
+ ): unknown => {
320
+ const result = transition(makeTransitionSelector(stateNodes, path))
321
+ if (Topology.isTargetSelection(result)) {
322
+ const selection = plainTargetSelection(result)
323
+ return { target: () => selection }
324
+ }
325
+ if (!hasProperty(result, TransitionBuilderDescriptorTypeId)) return result
326
+ const descriptor = result as TransitionBuilderDescriptor
327
+ if (descriptor.type === "branches") {
328
+ return {
329
+ branches: () => descriptor.declarations,
330
+ resolve: descriptor.resolve,
331
+ reenter: descriptor.reenter,
332
+ declinable: descriptor.declinable
333
+ }
334
+ }
335
+ return {
336
+ target: () => descriptor.selection,
337
+ resolve: descriptor.resolve,
338
+ reenter: descriptor.reenter,
339
+ declinable: descriptor.declinable
340
+ }
341
+ }
342
+
159
343
  const transitionTargetSelection = (
160
344
  selection: Topology.TargetSelection
161
345
  ): Machine.TransitionTargetSelection =>
@@ -172,6 +356,12 @@ const makeSelectionMethod = (
172
356
  ): () => Topology.TargetSelection =>
173
357
  () => Topology.makeTargetSelection(kind, path, scope)
174
358
 
359
+ const makeSelectionValue = (
360
+ kind: Topology.TargetSelectionKind,
361
+ path: string | undefined,
362
+ scope: Topology.TargetSelectionScope
363
+ ): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope)
364
+
175
365
  const addSelectionChildren = (
176
366
  builder: Record<string, unknown>,
177
367
  stateNodes: Machine.StateNodes,
@@ -194,7 +384,7 @@ const makeSelectionNode = (
194
384
  const method = makeSelectionMethod(kind, path, scope) as unknown as Record<string, unknown>
195
385
  if (node.type !== "atomic" && node.type !== "final" && node.type !== "choice" && node.type !== "history") {
196
386
  Object.defineProperty(method, "initial", {
197
- value: makeSelectionMethod("initial", path, scope),
387
+ value: makeSelectionValue("initial", path, scope),
198
388
  enumerable: true
199
389
  })
200
390
  if (scope === "local" || scope === "branch") {
@@ -212,7 +402,7 @@ const makeHistorySelectionTree = (
212
402
  for (const node of stateNodes.byPath.values()) {
213
403
  if (node.parent !== parent) continue
214
404
  if (node.type === "history") {
215
- builder[node.key] = makeSelectionMethod("history", node.path, "full")
405
+ builder[node.key] = makeSelectionValue("history", node.path, "full")
216
406
  } else if (node.type !== "choice") {
217
407
  const children = makeHistorySelectionTree(stateNodes, node.path)
218
408
  if (Object.keys(children).length > 0) builder[node.key] = children
@@ -239,12 +429,12 @@ const makeTargetSelector = (
239
429
  if (localScope !== undefined) {
240
430
  const localScopeNode = getTargetBuilderNode(stateNodes, localScope)
241
431
  if (localScopeNode.schema !== undefined) {
242
- local.with = makeSelectionMethod("state", localScope, "local")
432
+ local.with = makeSelectionValue("state", localScope, "local")
243
433
  }
244
434
  addSelectionChildren(local, stateNodes, localScope, "local")
245
435
  }
246
436
  return {
247
- none: makeSelectionMethod("none", undefined, "local"),
437
+ none: Topology.noneTargetSelection,
248
438
  local,
249
439
  branch,
250
440
  full,
@@ -309,7 +499,8 @@ const getSelectionBuilder = (
309
499
  return builder
310
500
  }
311
501
 
312
- const constructSelectedTarget = (builder: any): unknown => typeof builder === "function" ? builder() : builder.from()
502
+ const constructSelectedTarget = (builder: any): unknown =>
503
+ typeof builder?.from === "function" ? builder.from() : builder()
313
504
 
314
505
  const validateResolvedSelection = (
315
506
  result: unknown,
@@ -352,7 +543,14 @@ const runCapturedBranch = (
352
543
  const resolverContext = { ...context }
353
544
  if (branch.selection.kind === "none") delete resolverContext.target
354
545
  else resolverContext.target = selectedTarget
546
+ if (branch.declinable === true) resolverContext.decline = Topology.makeDeclined
355
547
  const resolved = branch.resolve(resolverContext, enqueue)
548
+ if (Topology.isDeclined(resolved)) {
549
+ if (branch.declinable !== true) {
550
+ throw new Error(`Machine transition for state "${source}" returned decline without declaring declinable: true`)
551
+ }
552
+ return resolved
553
+ }
356
554
  validateResolvedSelection(resolved, branch.selection, stateNodes)
357
555
  return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
358
556
  }
@@ -394,7 +592,7 @@ const captureNamedBranches = (
394
592
  if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
395
593
  throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
396
594
  }
397
- return Object.freeze({ key, title: title ?? key, selection: target })
595
+ return Object.freeze({ key, title: title ?? key, selection: plainTargetSelection(target) })
398
596
  }))
399
597
  }
400
598
 
@@ -474,17 +672,24 @@ const validateSelectedBranchResult = (
474
672
  }
475
673
 
476
674
  const captureTransition = (
477
- transition: unknown,
675
+ rawTransition: unknown,
478
676
  stateNodes: Machine.StateNodes,
479
677
  path: string,
480
678
  trigger: PropertyKey
481
679
  ): unknown => {
680
+ if (typeof rawTransition !== "function") {
681
+ throw new Error(
682
+ `Machine transition for state "${path}" on "${String(trigger)}" must be a target-first callback`
683
+ )
684
+ }
685
+ const transition = normalizeTransitionBuilder(rawTransition as (selector: unknown) => unknown, stateNodes, path)
482
686
  if (typeof transition !== "object" || transition === null) {
483
687
  throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must be an object`)
484
688
  }
485
689
  const definition = transition as Record<PropertyKey, unknown>
486
690
  const selector = makeTargetSelector(stateNodes, path)
487
691
  const reenter = definition.reenter === true
692
+ const declinable = definition.declinable === true
488
693
  if (hasProperty(definition, "branches")) {
489
694
  const branching = definition as { readonly branches: unknown; readonly resolve?: unknown }
490
695
  if (typeof branching.branches !== "function" || typeof branching.resolve !== "function") {
@@ -499,7 +704,18 @@ const captureTransition = (
499
704
  const resolverContext = { ...context }
500
705
  delete resolverContext.target
501
706
  resolverContext.select = makeBranchSelectors(context, branches, owner, stateNodes, path)
707
+ if (declinable) resolverContext.decline = Topology.makeDeclined
502
708
  const selected = resolve(resolverContext, enqueue)
709
+ if (Topology.isDeclined(selected)) {
710
+ if (!declinable) {
711
+ throw new Error(
712
+ `Machine branching transition for state "${path}" on "${
713
+ String(trigger)
714
+ }" returned decline without declaring declinable: true`
715
+ )
716
+ }
717
+ return { result: selected, branchIndex: -1, branchKey: undefined }
718
+ }
503
719
  if (!Topology.isSelectedBranch(selected) || selected.owner !== owner) {
504
720
  throw new Error(
505
721
  `Machine branching transition for state "${path}" on "${String(trigger)}" must select one declared branch`
@@ -518,6 +734,7 @@ const captureTransition = (
518
734
  }
519
735
  return {
520
736
  reenter,
737
+ declinable,
521
738
  targets: [
522
739
  ...new Set(
523
740
  branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
@@ -544,6 +761,7 @@ const captureTransition = (
544
761
  })
545
762
  return {
546
763
  reenter,
764
+ declinable,
547
765
  targets: branch.selection.path === undefined ? [] : [branch.selection.path],
548
766
  branches: [{
549
767
  type: "direct" as const,
@@ -1185,10 +1403,12 @@ const makeInitialSelector = (stateNodes: Machine.StateNodes): unknown => {
1185
1403
  const selector: Record<string, unknown> = {}
1186
1404
  for (const node of stateNodes.byPath.values()) {
1187
1405
  if (node.parent === undefined && node.type !== "history" && node.type !== "choice") {
1188
- selector[node.key] = makeSelectionNode(stateNodes, node.path, "initial")
1406
+ selector[node.key] = node.type === "atomic" || node.type === "final"
1407
+ ? makeSelectionMethod("state", node.path, "initial")
1408
+ : Object.freeze({ initial: makeSelectionValue("initial", node.path, "initial") })
1189
1409
  }
1190
1410
  }
1191
- return selector
1411
+ return Object.freeze(selector)
1192
1412
  }
1193
1413
 
1194
1414
  const getInitialSelectionBuilder = (
@@ -1207,15 +1427,37 @@ const getInitialSelectionBuilder = (
1207
1427
  }
1208
1428
 
1209
1429
  const captureInitialBranch = (
1210
- branch: unknown,
1211
- selector: unknown,
1430
+ definition: unknown,
1431
+ stateNodes: Machine.StateNodes,
1212
1432
  initialBuilder: Record<string, any>
1213
- ): CapturedBranch & { readonly builder: (...args: ReadonlyArray<any>) => unknown } => {
1214
- const captured = captureDefinitionBranch(branch, selector, "<machine>", "initial")
1215
- if (captured.selection.kind !== "state" && captured.selection.kind !== "initial") {
1433
+ ): {
1434
+ readonly selection: Topology.TargetSelection
1435
+ readonly resolve?: (context: any) => unknown
1436
+ readonly builder: (...args: ReadonlyArray<any>) => unknown
1437
+ } => {
1438
+ if (typeof definition !== "function") {
1439
+ throw new Error("Machine initial definition must be a target-first callback")
1440
+ }
1441
+ const result = definition(decorateInitialSelectorNode(makeInitialSelector(stateNodes)))
1442
+ let selection: Topology.TargetSelection
1443
+ let resolve: ((context: any) => unknown) | undefined
1444
+ if (Topology.isTargetSelection(result)) {
1445
+ selection = plainTargetSelection(result)
1446
+ } else if (hasProperty(result, InitialBuilderDescriptorTypeId)) {
1447
+ const descriptor = result as InitialBuilderDescriptor
1448
+ selection = descriptor.selection
1449
+ resolve = descriptor.resolve
1450
+ } else {
1451
+ throw new Error("Machine initial definition must select exactly one target")
1452
+ }
1453
+ if (selection.kind !== "state" && selection.kind !== "initial") {
1216
1454
  throw new Error("Machine initial target must select a top-level state or its declared initial entry")
1217
1455
  }
1218
- return { ...captured, builder: getInitialSelectionBuilder(initialBuilder, captured.selection) }
1456
+ const captured = {
1457
+ selection,
1458
+ builder: getInitialSelectionBuilder(initialBuilder, selection)
1459
+ }
1460
+ return resolve === undefined ? Object.freeze(captured) : Object.freeze({ ...captured, resolve })
1219
1461
  }
1220
1462
 
1221
1463
  const validateInitialSelection = (result: unknown, selection: Topology.TargetSelection): void => {
@@ -1237,17 +1479,13 @@ const compileInitial = (
1237
1479
  readonly initial: (input?: unknown) => unknown
1238
1480
  readonly definition: Machine.InitialDefinition
1239
1481
  } => {
1240
- if (typeof definition !== "object" || definition === null) {
1241
- throw new Error("Machine initial definition must be an object")
1242
- }
1243
- const selector = makeInitialSelector(stateNodes)
1244
1482
  const initialBuilder = makeSnapshotBuilder(states, { mode: "initial", prefix: "" }) as Record<string, any>
1245
- const branch = captureInitialBranch(definition, selector, initialBuilder)
1483
+ const branch = captureInitialBranch(definition, stateNodes, initialBuilder)
1246
1484
  return {
1247
1485
  initial: (input?: unknown) => {
1248
1486
  const result = branch.resolve === undefined
1249
- ? branch.builder()
1250
- : branch.resolve({ input, target: branch.builder }, undefined)
1487
+ ? constructSelectedTarget(branch.builder)
1488
+ : branch.resolve({ input, target: branch.builder })
1251
1489
  validateInitialSelection(result, branch.selection)
1252
1490
  return result
1253
1491
  },
@@ -1297,7 +1535,7 @@ type MakeConfig<
1297
1535
  InitialE,
1298
1536
  InitialR,
1299
1537
  InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
1300
- ParentEvents extends ReadonlyArray<Machine.TaggedSchema>
1538
+ ParentDeclaration extends Parent.Any | undefined
1301
1539
  > = {
1302
1540
  readonly id?: string
1303
1541
  readonly states: States & DefineStateTreeInput<NoInfer<States>>
@@ -1311,7 +1549,7 @@ type MakeConfig<
1311
1549
  NoInfer<InternalEvents>
1312
1550
  >
1313
1551
  readonly emittedEvents?: Machine.EventProtocol<"emitted", Emits>
1314
- readonly parentEvents?: Machine.EventProtocol<"public", ParentEvents>
1552
+ readonly parent?: ParentDeclaration
1315
1553
  readonly input?: Input
1316
1554
  readonly initial: unknown
1317
1555
  }
@@ -1324,7 +1562,7 @@ type MakeResult<
1324
1562
  InitialE,
1325
1563
  InitialR,
1326
1564
  InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
1327
- ParentEvents extends ReadonlyArray<Machine.TaggedSchema>
1565
+ ParentDeclaration extends Parent.Any | undefined
1328
1566
  > = Definition<
1329
1567
  States,
1330
1568
  readonly [...InputEvents, ...InternalEvents],
@@ -1335,7 +1573,7 @@ type MakeResult<
1335
1573
  Machine.TerminalOutput<States>,
1336
1574
  Emits,
1337
1575
  InputEvents,
1338
- ParentEvents
1576
+ Machine.ParentEventsOf<ParentDeclaration>
1339
1577
  >
1340
1578
 
1341
1579
  interface Make {
@@ -1347,11 +1585,11 @@ interface Make {
1347
1585
  InitialE = never,
1348
1586
  InitialR = never,
1349
1587
  const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
1350
- const ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
1588
+ const ParentDeclaration extends Parent.Any | undefined = undefined
1351
1589
  >(
1352
- config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents>,
1590
+ config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>,
1353
1591
  ..._validation: ValidateDefinedStates<NoInfer<States>>
1354
- ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents>
1592
+ ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>
1355
1593
  <
1356
1594
  const States extends Machine.StateSchemas,
1357
1595
  const InputEvents extends ReadonlyArray<Machine.TaggedSchema>,
@@ -1360,10 +1598,13 @@ interface Make {
1360
1598
  InitialE = never,
1361
1599
  InitialR = never,
1362
1600
  const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
1363
- const ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
1601
+ const ParentDeclaration extends Parent.Any | undefined = undefined
1364
1602
  >(
1365
1603
  config:
1366
- & Omit<MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents>, "states">
1604
+ & Omit<
1605
+ MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>,
1606
+ "states"
1607
+ >
1367
1608
  & { readonly states: InvalidDefinedStateTreeInput<States> }
1368
1609
  ): never
1369
1610
  }
@@ -1376,7 +1617,7 @@ export const make: Make = (<
1376
1617
  InitialE = never,
1377
1618
  InitialR = never,
1378
1619
  const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
1379
- const ParentEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly []
1620
+ const ParentDeclaration extends Parent.Any | undefined = undefined
1380
1621
  >(
1381
1622
  config: {
1382
1623
  readonly id?: string
@@ -1384,18 +1625,18 @@ export const make: Make = (<
1384
1625
  readonly events: Machine.EventProtocol<"public", InputEvents>
1385
1626
  readonly internalEvents?: Machine.EventProtocol<"internal", InternalEvents>
1386
1627
  readonly emittedEvents?: Machine.EventProtocol<"emitted", Emits>
1387
- readonly parentEvents?: Machine.EventProtocol<"public", ParentEvents>
1628
+ readonly parent?: ParentDeclaration
1388
1629
  readonly input?: Input
1389
1630
  readonly initial: unknown
1390
1631
  }
1391
- ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentEvents> => {
1632
+ ): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration> => {
1392
1633
  StateDefinition.validateStateDefinitions(config.states, "Machine.make")
1393
1634
  const self = Object.create(Proto)
1394
1635
  self.states = config.states
1395
1636
  self.events = config.events
1396
1637
  self.internalEvents = config.internalEvents ?? Protocol.makeEventProtocol("internal", [] as const)
1397
1638
  self.emittedEvents = config.emittedEvents ?? Protocol.makeEventProtocol("emitted", [] as const)
1398
- self.parentEvents = config.parentEvents ?? Protocol.makeEventProtocol("public", [] as const)
1639
+ self.parent = config.parent
1399
1640
  self.input = config.input
1400
1641
  self.id = config.id
1401
1642
  self.stateNodes = Topology.compileStateNodes(config.states)
@@ -1405,7 +1646,6 @@ export const make: Make = (<
1405
1646
  self.makeTargetBuilder = makeTargetBuilder(config.states, self.stateNodes)
1406
1647
  self.handlers = Object.create(null)
1407
1648
  self.handle = makeHandle(self)
1408
- self.invoke = makeBoundInvoke
1409
1649
  Protocol.setProtocol(self)
1410
1650
  return self
1411
1651
  }) as Make
@@ -1428,6 +1668,27 @@ export const events = <const Inputs extends ReadonlyArray<Machine.EventProtocolI
1428
1668
  flattenEventProtocolInputs("public", inputs)
1429
1669
  ) as Machine.EventProtocol<"public", Machine.EventProtocolInputSchemasOf<"public", Inputs>>
1430
1670
 
1671
+ const makeParent = <
1672
+ const Mode extends ParentMode,
1673
+ const Events extends ReadonlyArray<Machine.TaggedSchema>
1674
+ >(
1675
+ mode: Mode,
1676
+ events: Machine.EventProtocol<"public", Events>
1677
+ ): Parent<Mode, Events> => {
1678
+ if (!Protocol.isEventProtocol(events, "public")) {
1679
+ throw new Error("Machine parent declarations require a protocol created with Machine.events")
1680
+ }
1681
+ return Object.freeze({ [ParentTypeId]: ParentTypeId, mode, events })
1682
+ }
1683
+
1684
+ export const parent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
1685
+ events: Machine.EventProtocol<"public", Events>
1686
+ ): Parent<"required", Events> => makeParent("required", events)
1687
+
1688
+ export const optionalParent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
1689
+ events: Machine.EventProtocol<"public", Events>
1690
+ ): Parent<"optional", Events> => makeParent("optional", events)
1691
+
1431
1692
  export const internalEvents = <const Inputs extends ReadonlyArray<Machine.EventProtocolInput<"internal">>>(
1432
1693
  ...inputs: Inputs
1433
1694
  ): Machine.EventProtocol<"internal", Machine.EventProtocolInputSchemasOf<"internal", Inputs>> =>