@typeonce/effect-machine 0.20.0 → 0.22.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 (46) hide show
  1. package/README.md +115 -5
  2. package/dist/Machine.d.ts +188 -52
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +6 -5
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  7. package/dist/internal/machine/executionPlan.js +50 -6
  8. package/dist/internal/machine/executionPlan.js.map +1 -1
  9. package/dist/internal/machine/initialization.d.ts.map +1 -1
  10. package/dist/internal/machine/initialization.js +7 -2
  11. package/dist/internal/machine/initialization.js.map +1 -1
  12. package/dist/internal/machine/machine.d.ts.map +1 -1
  13. package/dist/internal/machine/machine.js +132 -22
  14. package/dist/internal/machine/machine.js.map +1 -1
  15. package/dist/internal/machine/planner.d.ts +16 -2
  16. package/dist/internal/machine/planner.d.ts.map +1 -1
  17. package/dist/internal/machine/planner.js +67 -13
  18. package/dist/internal/machine/planner.js.map +1 -1
  19. package/dist/internal/machine/topology.d.ts +19 -2
  20. package/dist/internal/machine/topology.d.ts.map +1 -1
  21. package/dist/internal/machine/topology.js +17 -2
  22. package/dist/internal/machine/topology.js.map +1 -1
  23. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  24. package/dist/internal/testing/machine/finiteModel.js +3 -3
  25. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  26. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  27. package/dist/internal/testing/machine/verification.js +17 -3
  28. package/dist/internal/testing/machine/verification.js.map +1 -1
  29. package/dist/testing/MachineTest.d.ts +5 -3
  30. package/dist/testing/MachineTest.d.ts.map +1 -1
  31. package/dist/testing/MachineTest.js +3 -3
  32. package/dist/testing/MachineTest.js.map +1 -1
  33. package/dist/unstable/reactivity/AtomMachine.d.ts +1 -1
  34. package/dist/unstable/reactivity/AtomMachine.js +1 -1
  35. package/docs/agent-guide.md +51 -3
  36. package/package.json +1 -1
  37. package/src/Machine.ts +532 -107
  38. package/src/internal/machine/executionPlan.ts +62 -6
  39. package/src/internal/machine/initialization.ts +10 -2
  40. package/src/internal/machine/machine.ts +188 -24
  41. package/src/internal/machine/planner.ts +86 -15
  42. package/src/internal/machine/topology.ts +40 -3
  43. package/src/internal/testing/machine/finiteModel.ts +7 -3
  44. package/src/internal/testing/machine/verification.ts +24 -3
  45. package/src/testing/MachineTest.ts +5 -3
  46. package/src/unstable/reactivity/AtomMachine.ts +1 -1
@@ -47,7 +47,15 @@ import {
47
47
  validateDeclaredTransitionTarget
48
48
  } from "./planner.js"
49
49
  import { decodeEmitSync, decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js"
50
- import { isInitialTarget, isNoTarget, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
50
+ import {
51
+ isCombinedTarget,
52
+ isInitialTarget,
53
+ isNoTarget,
54
+ isSnapshot,
55
+ isStateUpdate,
56
+ isTarget,
57
+ TargetSnapshotTypeId
58
+ } from "./topology.js"
51
59
 
52
60
  interface IndexedExecutionDescriptor {
53
61
  readonly flat: boolean
@@ -548,7 +556,28 @@ const collectIndexedEvaluatedTransition = (
548
556
  selection.context,
549
557
  selection.transition.evaluate
550
558
  )
551
- const unresolvedTarget = transitionResult.state
559
+ const combined = isCombinedTarget(transitionResult.state) ? transitionResult.state : undefined
560
+ const transitionState = combined?.target ?? transitionResult.state
561
+ const stateUpdate = combined?.update ?? (isStateUpdate(transitionState) ? transitionState : undefined)
562
+ const update = stateUpdate !== undefined
563
+ ? (() => {
564
+ const index = descriptor.indexByPath.get(stateUpdate.path)
565
+ const node = index === undefined ? undefined : descriptor.nodes[index]
566
+ if (
567
+ index === undefined || node === undefined || state.active[index] !== 1 || node.schema === undefined ||
568
+ (node.type !== "compound" && node.type !== "parallel")
569
+ ) {
570
+ throw new Error(
571
+ `Machine state update owner "${stateUpdate.path}" must be an active valued compound or parallel state`
572
+ )
573
+ }
574
+ return {
575
+ path: node.path,
576
+ value: decodeStateValueSync(machine, node, stateUpdate.value)
577
+ }
578
+ })()
579
+ : undefined
580
+ const unresolvedTarget = transitionState === undefined || isStateUpdate(transitionState) ? undefined : transitionState
552
581
  validateDeclaredTransitionTarget(
553
582
  selection.sourcePath,
554
583
  selection.trigger,
@@ -567,10 +596,24 @@ const collectIndexedEvaluatedTransition = (
567
596
  if (target !== undefined && !isTarget(target) && !isSnapshot(target)) {
568
597
  throw new Error("Machine expected indexed transition target to be a snapshot or target builder result")
569
598
  }
570
- const next = target === undefined
571
- ? state
599
+ let next = target === undefined
600
+ ? update === undefined ? state : (() => {
601
+ const next = copyOwnedIndexedState(state)
602
+ next.values[descriptor.indexByPath.get(update.path)!] = update.value
603
+ return next
604
+ })()
572
605
  : normalizeIndexedTargetStateSync(machine, descriptor, state, target as any, selection.leafIndex)
606
+ if (combined !== undefined && update !== undefined) {
607
+ const ownerIndex = descriptor.indexByPath.get(update.path)!
608
+ if (next.active[ownerIndex] !== 1) {
609
+ throw new Error(`Machine combined target exited its updating owner "${update.path}"`)
610
+ }
611
+ const values = next.values.slice()
612
+ values[ownerIndex] = update.value
613
+ next = { ...next, values }
614
+ }
573
615
  const changed = selection.transition.reenter || !hasSameIndexedActive(state, next)
616
+ const stabilize = changed || update !== undefined
574
617
  if (!changed) {
575
618
  return {
576
619
  selection,
@@ -578,11 +621,13 @@ const collectIndexedEvaluatedTransition = (
578
621
  branchKey: transitionResult.branchKey,
579
622
  unresolvedTarget: unresolvedTarget as any,
580
623
  target: target as any,
624
+ update,
581
625
  next,
582
626
  commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
583
627
  raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
584
628
  emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
585
629
  changed: false,
630
+ stabilize,
586
631
  exitPaths: [],
587
632
  entryPaths: [],
588
633
  choiceTransitions: initialResolution?.transitions ?? []
@@ -603,11 +648,13 @@ const collectIndexedEvaluatedTransition = (
603
648
  branchKey: transitionResult.branchKey,
604
649
  unresolvedTarget: unresolvedTarget as any,
605
650
  target: target as any,
651
+ update,
606
652
  next,
607
653
  commands: [...transitionResult.commands, ...(initialResolution?.commands ?? [])],
608
654
  raisedEvents: [...transitionResult.raisedEvents, ...(initialResolution?.raisedEvents ?? [])],
609
655
  emittedEvents: [...transitionResult.emittedEvents, ...(initialResolution?.emittedEvents ?? [])],
610
656
  changed: true,
657
+ stabilize,
611
658
  exitPaths: getExitPaths(machine, activeConfigurationFromIndexedState(descriptor, state), boundary),
612
659
  entryPaths: getEntryPaths(machine, activeConfigurationFromIndexedState(descriptor, next), boundary),
613
660
  choiceTransitions: initialResolution?.transitions ?? []
@@ -629,7 +676,8 @@ const indexedMicrostep = (
629
676
  branchIndex: transition.branchIndex,
630
677
  branchKey: transition.branchKey,
631
678
  target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
632
- resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
679
+ resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target),
680
+ updates: transition.update === undefined ? [] : [transition.update.path]
633
681
  })
634
682
  if (selections.length === 1) {
635
683
  const transition = collectIndexedEvaluatedTransition(machine, descriptor, state, selections[0]!)
@@ -663,6 +711,13 @@ const indexedMicrostep = (
663
711
  if (transitions.length === 1) {
664
712
  next = transitions[0]!.next
665
713
  } else {
714
+ for (const transition of transitions) {
715
+ if (transition.update !== undefined) {
716
+ const values = next.values.slice()
717
+ values[descriptor.indexByPath.get(transition.update.path)!] = transition.update.value
718
+ next = { ...next, values }
719
+ }
720
+ }
666
721
  const applicationOrder = [
667
722
  ...transitions.filter((transition) => !transition.changed),
668
723
  ...transitions.filter((transition) => transition.changed)
@@ -815,7 +870,8 @@ const planIndexedFlatState = (
815
870
  branchIndex: transitionResult.branchIndex,
816
871
  branchKey: transitionResult.branchKey,
817
872
  target: target === undefined ? undefined : getTargetNodePath(target as any),
818
- resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any)
873
+ resolvedTarget: target === undefined ? undefined : getTargetNodePath(target as any),
874
+ updates: []
819
875
  }]
820
876
  }
821
877
  : undefined),
@@ -14,11 +14,19 @@ const getNode = (machine: Machine.Any, path: string): Machine.StateNode => {
14
14
  }
15
15
 
16
16
  const withFrom = <Method extends (value: unknown) => unknown>(method: Method) => {
17
- Object.defineProperty(method, "from", {
17
+ const builder = {}
18
+ Object.defineProperty(builder, "decoded", {
19
+ value: method,
20
+ enumerable: false
21
+ })
22
+ Object.defineProperty(builder, "from", {
18
23
  value: (...args: ReadonlyArray<unknown>) => method(Topology.makeStateInput(args.length === 0 ? {} : args[0])),
19
24
  enumerable: false
20
25
  })
21
- return method as Method & { readonly from: (...args: ReadonlyArray<unknown>) => unknown }
26
+ return builder as {
27
+ readonly decoded: Method
28
+ readonly from: (...args: ReadonlyArray<unknown>) => unknown
29
+ }
22
30
  }
23
31
 
24
32
  const makeCompletion = (values: Readonly<Record<string, unknown>>): object => {
@@ -191,7 +191,7 @@ type InitialBuilderDescriptor = {
191
191
  const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
192
192
  selection.kind === "none"
193
193
  ? Topology.noneTargetSelection
194
- : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope)
194
+ : Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, selection.updatePath)
195
195
 
196
196
  const transitionOptions = (options: unknown): { readonly reenter: boolean; readonly declinable: boolean } => {
197
197
  const configuration = typeof options === "object" && options !== null
@@ -224,9 +224,33 @@ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topol
224
224
  ...selection,
225
225
  resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
226
226
  makeDirectTransitionDescriptor(selection, resolve, options),
227
- reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true })
227
+ reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true }),
228
+ updating: (owner: unknown) => {
229
+ if (typeof owner !== "function") {
230
+ throw new Error("Machine updating owner must be a state selector")
231
+ }
232
+ const ownerSelection = owner()
233
+ if (
234
+ !Topology.isTargetSelection(ownerSelection) || ownerSelection.kind !== "state" ||
235
+ ownerSelection.scope !== "branch"
236
+ ) {
237
+ throw new Error("Machine updating owner must be addressed by one branch state selector")
238
+ }
239
+ return decorateTransitionSelection(
240
+ Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, ownerSelection.path)
241
+ )
242
+ }
228
243
  })
229
244
 
245
+ const decorateStateUpdateSelection = (selection: Topology.TargetSelection): Topology.TargetSelection => {
246
+ const update = (
247
+ resolve: (context: any, enqueue: unknown) => unknown,
248
+ options?: unknown
249
+ ) => makeDirectTransitionDescriptor(selection, resolve, options)
250
+ Object.assign(update, selection)
251
+ return Object.freeze(update) as unknown as Topology.TargetSelection
252
+ }
253
+
230
254
  const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
231
255
 
232
256
  const makeInitialBuilderDescriptor = (
@@ -268,6 +292,7 @@ const decorateInitialSelectorNode = (node: unknown): unknown => {
268
292
 
269
293
  const decorateTransitionSelectorNode = (node: unknown): unknown => {
270
294
  if (Topology.isTargetSelection(node)) {
295
+ if (node.kind === "update") return decorateStateUpdateSelection(node)
271
296
  return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
272
297
  }
273
298
  if (typeof node === "function") {
@@ -350,6 +375,11 @@ const transitionTargetSelection = (
350
375
  scope: selection.scope
351
376
  })
352
377
 
378
+ const selectionUpdates = (selection: Topology.TargetSelection): ReadonlyArray<string> =>
379
+ selection.updatePath === undefined ?
380
+ selection.kind === "update" && selection.path !== undefined ? [selection.path] : []
381
+ : [selection.updatePath]
382
+
353
383
  const makeSelectionMethod = (
354
384
  kind: Topology.TargetSelectionKind,
355
385
  path: string | undefined,
@@ -363,22 +393,29 @@ const makeSelectionValue = (
363
393
  scope: Topology.TargetSelectionScope
364
394
  ): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope)
365
395
 
396
+ const makeStateUpdateSelection = (
397
+ path: string,
398
+ scope: "local" | "branch"
399
+ ): Topology.TargetSelection => Topology.makeTargetSelection("update", path, scope)
400
+
366
401
  const addSelectionChildren = (
367
402
  builder: Record<string, unknown>,
368
403
  stateNodes: Machine.StateNodes,
369
404
  parent: string,
370
- scope: "local" | "branch"
405
+ scope: "local" | "branch",
406
+ source?: string
371
407
  ): void => {
372
408
  for (const node of stateNodes.byPath.values()) {
373
409
  if (node.parent !== parent || node.type === "history") continue
374
- builder[node.key] = makeSelectionNode(stateNodes, node.path, scope)
410
+ builder[node.key] = makeSelectionNode(stateNodes, node.path, scope, source)
375
411
  }
376
412
  }
377
413
 
378
414
  const makeSelectionNode = (
379
415
  stateNodes: Machine.StateNodes,
380
416
  path: string,
381
- scope: Topology.TargetSelectionScope
417
+ scope: Topology.TargetSelectionScope,
418
+ source?: string
382
419
  ): unknown => {
383
420
  const node = getTargetBuilderNode(stateNodes, path)
384
421
  const kind: Topology.TargetSelectionKind = node.type === "choice" ? "choice" : "state"
@@ -389,7 +426,17 @@ const makeSelectionNode = (
389
426
  enumerable: true
390
427
  })
391
428
  if (scope === "local" || scope === "branch") {
392
- addSelectionChildren(method, stateNodes, path, scope)
429
+ addSelectionChildren(method, stateNodes, path, scope, source)
430
+ }
431
+ if (
432
+ scope === "branch" && source !== undefined && node.schema !== undefined &&
433
+ (source === path || source.startsWith(`${path}.`)) &&
434
+ getTargetBuilderNode(stateNodes, source).type !== "choice"
435
+ ) {
436
+ Object.defineProperty(method, "update", {
437
+ value: makeStateUpdateSelection(path, "branch"),
438
+ enumerable: true
439
+ })
393
440
  }
394
441
  }
395
442
  return method
@@ -424,13 +471,16 @@ const makeTargetSelector = (
424
471
  }
425
472
  const branch: Record<string, unknown> = {}
426
473
  const root = getTargetBuilderNode(stateNodes, source.split(".")[0]!)
427
- branch[root.key] = makeSelectionNode(stateNodes, root.path, "branch")
474
+ branch[root.key] = makeSelectionNode(stateNodes, root.path, "branch", source)
428
475
  const local: Record<string, unknown> = {}
429
476
  const localScope = getLocalTargetScope(stateNodes, source)
430
477
  if (localScope !== undefined) {
431
478
  const localScopeNode = getTargetBuilderNode(stateNodes, localScope)
432
479
  if (localScopeNode.schema !== undefined) {
433
480
  local.with = makeSelectionValue("state", localScope, "local")
481
+ if (getTargetBuilderNode(stateNodes, source).type !== "choice") {
482
+ local.update = makeStateUpdateSelection(localScope, "local")
483
+ }
434
484
  }
435
485
  addSelectionChildren(local, stateNodes, localScope, "local")
436
486
  }
@@ -446,6 +496,7 @@ const makeTargetSelector = (
446
496
  const captureDefinitionBranch = (
447
497
  branch: unknown,
448
498
  selector: unknown,
499
+ stateNodes: Machine.StateNodes,
449
500
  path: string,
450
501
  trigger: PropertyKey
451
502
  ): CapturedBranch => {
@@ -459,9 +510,58 @@ const captureDefinitionBranch = (
459
510
  if (!Topology.isTargetSelection(selection)) {
460
511
  throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must select exactly one target`)
461
512
  }
513
+ if (selection.updatePath !== undefined) {
514
+ const owner = stateNodes.byPath.get(selection.updatePath)
515
+ if (
516
+ selection.kind !== "state" || (selection.scope !== "local" && selection.scope !== "branch") ||
517
+ selection.path === undefined || owner === undefined || owner.schema === undefined ||
518
+ (owner.type !== "compound" && owner.type !== "parallel") ||
519
+ !Configuration.isDescendantOf(path, owner.path) ||
520
+ !Configuration.isDescendantOf(selection.path, owner.path)
521
+ ) {
522
+ throw new Error(
523
+ `Machine updating owner "${selection.updatePath}" must be a valued ancestor retained by source "${path}" and target "${selection.path}"`
524
+ )
525
+ }
526
+ }
462
527
  return { ...(branch as DefinitionBranch), selection }
463
528
  }
464
529
 
530
+ const makeUpdatingConstruction = (
531
+ target: unknown,
532
+ ownerPath: string
533
+ ): { readonly update: (update: unknown) => Topology.CombinedTarget } =>
534
+ Object.freeze({
535
+ update: (update: unknown) => {
536
+ if (!Topology.isStateUpdate(update) || update.path !== ownerPath) {
537
+ throw new Error(`Machine combined target must update its declared owner "${ownerPath}"`)
538
+ }
539
+ return Topology.makeCombinedTarget(target, update)
540
+ }
541
+ })
542
+
543
+ const makeUpdatingTargetBuilder = (
544
+ builder: unknown,
545
+ ownerPath: string
546
+ ): unknown => {
547
+ if (typeof builder !== "object" || builder === null) {
548
+ throw new Error("Machine combined target requires a state construction builder")
549
+ }
550
+ const updating: Record<PropertyKey, unknown> = {}
551
+ for (const property of Reflect.ownKeys(builder)) {
552
+ const descriptor = Object.getOwnPropertyDescriptor(builder, property)
553
+ if (descriptor === undefined) continue
554
+ if (
555
+ (property === "from" || property === "decoded") && "value" in descriptor && typeof descriptor.value === "function"
556
+ ) {
557
+ const construct = descriptor.value
558
+ descriptor.value = (...args: ReadonlyArray<unknown>) => makeUpdatingConstruction(construct(...args), ownerPath)
559
+ }
560
+ Object.defineProperty(updating, property, descriptor)
561
+ }
562
+ return Object.freeze(updating)
563
+ }
564
+
465
565
  const getSelectionBuilder = (
466
566
  target: Record<string, any>,
467
567
  selection: Topology.TargetSelection,
@@ -469,6 +569,13 @@ const getSelectionBuilder = (
469
569
  source: string
470
570
  ): unknown => {
471
571
  if (selection.kind === "none") return target.none
572
+ if (selection.kind === "update") {
573
+ return withFrom(
574
+ (value: unknown) => Topology.makeStateUpdate(selection.path!, value),
575
+ "leaf",
576
+ true
577
+ )
578
+ }
472
579
  let builder: any
473
580
  let parts = selection.path!.split(".")
474
581
  if (selection.kind === "history") {
@@ -497,7 +604,7 @@ const getSelectionBuilder = (
497
604
  ) {
498
605
  throw new Error(`Machine could not construct selected transition target "${selection.path}"`)
499
606
  }
500
- return builder
607
+ return selection.updatePath === undefined ? builder : makeUpdatingTargetBuilder(builder, selection.updatePath)
501
608
  }
502
609
 
503
610
  const constructSelectedTarget = (builder: any): unknown =>
@@ -514,10 +621,24 @@ const validateResolvedSelection = (
514
621
  }
515
622
  return
516
623
  }
624
+ if (selection.kind === "update") {
625
+ if (!Topology.isStateUpdate(result) || result.path !== selection.path) {
626
+ throw new Error(`Machine state update for "${selection.path}" must return its selected update builder`)
627
+ }
628
+ return
629
+ }
630
+ if (selection.updatePath !== undefined) {
631
+ if (!Topology.isCombinedTarget(result) || result.update.path !== selection.updatePath) {
632
+ throw new Error(`Machine target updating "${selection.updatePath}" must return target construction .update(...)`)
633
+ }
634
+ } else if (Topology.isCombinedTarget(result)) {
635
+ throw new Error("Machine combined target requires an updating owner declaration")
636
+ }
517
637
  if (result === undefined) return
518
- const resultPath = typeof result === "object" && result !== null && hasProperty(result, "path") &&
519
- typeof result.path === "string"
520
- ? result.path
638
+ const target = Topology.isCombinedTarget(result) ? result.target : result
639
+ const resultPath = typeof target === "object" && target !== null && hasProperty(target, "path") &&
640
+ typeof target.path === "string"
641
+ ? target.path
521
642
  : undefined
522
643
  const selectedNode = selection.path === undefined ? undefined : stateNodes.byPath.get(selection.path)
523
644
  const acceptsDescendant = (selection.scope === "local" || selection.scope === "branch") &&
@@ -544,6 +665,26 @@ const runCapturedBranch = (
544
665
  const resolverContext = { ...context }
545
666
  if (branch.selection.kind === "none") delete resolverContext.target
546
667
  else resolverContext.target = selectedTarget
668
+ if (branch.selection.kind === "update") {
669
+ const ownerPath = branch.selection.path!
670
+ delete resolverContext.target
671
+ resolverContext.current = context.ancestors[ownerPath] ?? context.state
672
+ resolverContext.owner = getSelectionBuilder(
673
+ context.target,
674
+ makeStateUpdateSelection(ownerPath, branch.selection.scope === "local" ? "local" : "branch"),
675
+ stateNodes,
676
+ source
677
+ )
678
+ } else if (branch.selection.updatePath !== undefined) {
679
+ const ownerPath = branch.selection.updatePath
680
+ resolverContext.current = context.ancestors[ownerPath]
681
+ resolverContext.owner = getSelectionBuilder(
682
+ context.target,
683
+ makeStateUpdateSelection(ownerPath, "branch"),
684
+ stateNodes,
685
+ source
686
+ )
687
+ }
547
688
  if (branch.declinable === true) resolverContext.decline = Topology.makeDeclined
548
689
  const resolved = branch.resolve(resolverContext, enqueue)
549
690
  if (Topology.isDeclined(resolved)) {
@@ -556,6 +697,9 @@ const runCapturedBranch = (
556
697
  return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
557
698
  }
558
699
 
700
+ const topologyTargetPath = (selection: Topology.TargetSelection): string | undefined =>
701
+ selection.kind === "update" ? undefined : selection.path
702
+
559
703
  const isArrayIndexKey = (key: string): boolean => {
560
704
  const index = Number(key)
561
705
  return Number.isInteger(index) && index >= 0 && index < 0xffff_ffff && String(index) === key
@@ -590,6 +734,9 @@ const captureNamedBranches = (
590
734
  if (!Topology.isTargetSelection(target)) {
591
735
  throw new Error(`Machine transition branch "${key}" must select exactly one target`)
592
736
  }
737
+ if (target.updatePath !== undefined) {
738
+ throw new Error(`Machine transition branch "${key}" cannot declare an updating target`)
739
+ }
593
740
  if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
594
741
  throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
595
742
  }
@@ -738,7 +885,9 @@ const captureTransition = (
738
885
  declinable,
739
886
  targets: [
740
887
  ...new Set(
741
- branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
888
+ branches.flatMap((branch) =>
889
+ topologyTargetPath(branch.selection) === undefined ? [] : [branch.selection.path!]
890
+ )
742
891
  )
743
892
  ],
744
893
  branches: branches.map((branch) =>
@@ -746,15 +895,16 @@ const captureTransition = (
746
895
  type: "branch" as const,
747
896
  key: branch.key,
748
897
  title: branch.title,
749
- target: branch.selection.path,
750
- selection: transitionTargetSelection(branch.selection)
898
+ target: topologyTargetPath(branch.selection),
899
+ selection: transitionTargetSelection(branch.selection),
900
+ updates: selectionUpdates(branch.selection)
751
901
  })
752
902
  ),
753
903
  evaluate,
754
904
  transition: (context: Record<string, any>, enqueue: unknown) => evaluate(context, enqueue).result
755
905
  }
756
906
  }
757
- const branch = captureDefinitionBranch(transition, selector, path, trigger)
907
+ const branch = captureDefinitionBranch(transition, selector, stateNodes, path, trigger)
758
908
  const evaluate = (context: Record<string, any>, enqueue: unknown) => ({
759
909
  result: runCapturedBranch(branch, context, enqueue, stateNodes, path),
760
910
  branchIndex: 0,
@@ -763,11 +913,12 @@ const captureTransition = (
763
913
  return {
764
914
  reenter,
765
915
  declinable,
766
- targets: branch.selection.path === undefined ? [] : [branch.selection.path],
916
+ targets: topologyTargetPath(branch.selection) === undefined ? [] : [branch.selection.path!],
767
917
  branches: [{
768
918
  type: "direct" as const,
769
- target: branch.selection.path,
770
- selection: transitionTargetSelection(branch.selection)
919
+ target: topologyTargetPath(branch.selection),
920
+ selection: transitionTargetSelection(branch.selection),
921
+ updates: selectionUpdates(branch.selection)
771
922
  }],
772
923
  evaluate,
773
924
  transition: (context: Record<string, any>, enqueue: unknown) => evaluate(context, enqueue).result
@@ -974,8 +1125,18 @@ const withFrom = <Method extends (value: unknown, ...args: ReadonlyArray<any>) =
974
1125
  method: Method,
975
1126
  kind: FromMethodKind,
976
1127
  valued: boolean
977
- ): Method & { readonly from: (...args: ReadonlyArray<any>) => unknown } => {
978
- Object.defineProperty(method, "from", {
1128
+ ): {
1129
+ readonly decoded?: Method
1130
+ readonly from: (...args: ReadonlyArray<any>) => unknown
1131
+ } => {
1132
+ const builder: Record<string, unknown> = {}
1133
+ if (valued) {
1134
+ Object.defineProperty(builder, "decoded", {
1135
+ value: method,
1136
+ enumerable: false
1137
+ })
1138
+ }
1139
+ Object.defineProperty(builder, "from", {
979
1140
  value: (...args: ReadonlyArray<any>) => {
980
1141
  if (!valued) {
981
1142
  return method(undefined, ...args)
@@ -987,7 +1148,10 @@ const withFrom = <Method extends (value: unknown, ...args: ReadonlyArray<any>) =
987
1148
  },
988
1149
  enumerable: false
989
1150
  })
990
- return method as Method & { readonly from: (...args: ReadonlyArray<any>) => unknown }
1151
+ return builder as {
1152
+ readonly decoded?: Method
1153
+ readonly from: (...args: ReadonlyArray<any>) => unknown
1154
+ }
991
1155
  }
992
1156
 
993
1157
  const withInitial = <Builder extends object>(
@@ -1470,13 +1634,13 @@ const makeInitialSelector = (stateNodes: Machine.StateNodes): unknown => {
1470
1634
  const getInitialSelectionBuilder = (
1471
1635
  initialBuilder: Record<string, any>,
1472
1636
  selection: Topology.TargetSelection
1473
- ): (...args: ReadonlyArray<any>) => unknown => {
1637
+ ): Record<string, any> => {
1474
1638
  const path = selection.path
1475
1639
  if (path === undefined || path.includes(".")) {
1476
1640
  throw new Error("Machine initial target must select one top-level state")
1477
1641
  }
1478
1642
  const builder = initialBuilder[path]
1479
- if (typeof builder !== "function") {
1643
+ if (typeof builder !== "object" || builder === null || typeof builder.from !== "function") {
1480
1644
  throw new Error(`Machine could not construct selected initial state "${path}"`)
1481
1645
  }
1482
1646
  return builder
@@ -1489,7 +1653,7 @@ const captureInitialBranch = (
1489
1653
  ): {
1490
1654
  readonly selection: Topology.TargetSelection
1491
1655
  readonly resolve?: (context: any) => unknown
1492
- readonly builder: (...args: ReadonlyArray<any>) => unknown
1656
+ readonly builder: Record<string, any>
1493
1657
  } => {
1494
1658
  if (typeof definition !== "function") {
1495
1659
  throw new Error("Machine initial definition must be a target-first callback")