@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.
- package/README.md +98 -106
- package/dist/Machine.d.ts +324 -367
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +61 -96
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +7 -7
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts +2 -2
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js +6 -5
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +10 -3
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +8 -6
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +191 -25
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +19 -4
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +56 -20
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +14 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +9 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +16 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +15 -20
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.js +2 -0
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +11 -11
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +5 -8
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +2 -2
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +12 -12
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +144 -147
- package/package.json +1 -1
- package/src/Machine.ts +1118 -1178
- package/src/internal/machine/atom.ts +20 -15
- package/src/internal/machine/cluster.ts +14 -9
- package/src/internal/machine/executionPlan.ts +8 -3
- package/src/internal/machine/machine.ts +301 -40
- package/src/internal/machine/planner.ts +89 -27
- package/src/internal/machine/stateDefinition.ts +39 -0
- package/src/internal/machine/topology.ts +28 -0
- package/src/internal/testing/machine/finiteModel.ts +18 -21
- package/src/internal/testing/machine/transitionCoverage.ts +2 -0
- package/src/testing/MachineTest.ts +14 -11
- package/src/unstable/cluster/ClusterMachine.ts +8 -4
- 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.
|
|
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:
|
|
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] =
|
|
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 =
|
|
432
|
+
local.with = makeSelectionValue("state", localScope, "local")
|
|
243
433
|
}
|
|
244
434
|
addSelectionChildren(local, stateNodes, localScope, "local")
|
|
245
435
|
}
|
|
246
436
|
return {
|
|
247
|
-
none:
|
|
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 =>
|
|
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
|
-
|
|
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] =
|
|
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
|
-
|
|
1211
|
-
|
|
1430
|
+
definition: unknown,
|
|
1431
|
+
stateNodes: Machine.StateNodes,
|
|
1212
1432
|
initialBuilder: Record<string, any>
|
|
1213
|
-
):
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
|
|
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,
|
|
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 }
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1588
|
+
const ParentDeclaration extends Parent.Any | undefined = undefined
|
|
1351
1589
|
>(
|
|
1352
|
-
config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents,
|
|
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,
|
|
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
|
|
1601
|
+
const ParentDeclaration extends Parent.Any | undefined = undefined
|
|
1364
1602
|
>(
|
|
1365
1603
|
config:
|
|
1366
|
-
& Omit<
|
|
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
|
|
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
|
|
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,
|
|
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.
|
|
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>> =>
|