@typeonce/effect-machine 0.16.0 → 0.18.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 +157 -150
- package/dist/Machine.d.ts +416 -592
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +55 -136
- 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/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +1 -1
- package/dist/internal/machine/invocation.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 +239 -35
- 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 +15 -15
- 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 +13 -13
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +229 -193
- package/package.json +1 -1
- package/src/Machine.ts +1686 -2234
- 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/invocation.ts +1 -1
- package/src/internal/machine/machine.ts +365 -48
- 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/exploration.ts +1 -1
- package/src/internal/testing/machine/finiteModel.ts +18 -21
- package/src/internal/testing/machine/trace.ts +1 -1
- package/src/internal/testing/machine/transitionCoverage.ts +2 -0
- package/src/internal/testing/machine/verification.ts +1 -1
- package/src/testing/MachineTest.ts +18 -15
- package/src/unstable/cluster/ClusterMachine.ts +8 -4
- package/src/unstable/reactivity/AtomMachine.ts +20 -13
|
@@ -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,8 +51,10 @@ 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")
|
|
57
|
+
const InvokeBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InvokeBuilderDescriptor")
|
|
54
58
|
const ChildMachineTypeId = "~effect/Machine/ChildMachine"
|
|
55
59
|
type IsAny<A> = 0 extends 1 & A ? true : false
|
|
56
60
|
type MachineRuntimeRequirement = internalRuntime.MachineRuntime
|
|
@@ -112,8 +116,6 @@ const Proto = {
|
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
const makeBoundInvoke = (config: unknown): unknown => config
|
|
116
|
-
|
|
117
119
|
const makeWithHandlers = (
|
|
118
120
|
self: Definition.Any,
|
|
119
121
|
handlers: Machine.StateConfigs<any, any, any, any, any, any, any>
|
|
@@ -123,7 +125,7 @@ const makeWithHandlers = (
|
|
|
123
125
|
machine.events = self.events
|
|
124
126
|
machine.internalEvents = self.internalEvents
|
|
125
127
|
machine.emittedEvents = self.emittedEvents
|
|
126
|
-
machine.
|
|
128
|
+
machine.parent = self.parent
|
|
127
129
|
machine.input = self.input
|
|
128
130
|
machine.id = self.id
|
|
129
131
|
machine.initial = self.initial
|
|
@@ -131,7 +133,6 @@ const makeWithHandlers = (
|
|
|
131
133
|
machine.stateNodes = self.stateNodes
|
|
132
134
|
machine.makeTargetBuilder = self.makeTargetBuilder
|
|
133
135
|
machine.handlers = handlers
|
|
134
|
-
machine.invoke = makeBoundInvoke
|
|
135
136
|
Protocol.copyProtocol(self, machine)
|
|
136
137
|
return machine
|
|
137
138
|
}
|
|
@@ -139,6 +140,7 @@ const makeWithHandlers = (
|
|
|
139
140
|
type DefinitionBranch = {
|
|
140
141
|
readonly target: (selector: unknown) => unknown
|
|
141
142
|
readonly resolve?: (context: any, enqueue: unknown) => unknown
|
|
143
|
+
readonly declinable?: boolean
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
type CapturedBranch = DefinitionBranch & {
|
|
@@ -156,6 +158,189 @@ type CapturedNamedBranch = {
|
|
|
156
158
|
readonly selection: Topology.TargetSelection
|
|
157
159
|
}
|
|
158
160
|
|
|
161
|
+
const TransitionBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/TransitionBuilderDescriptor")
|
|
162
|
+
|
|
163
|
+
type DirectTransitionDescriptor = {
|
|
164
|
+
readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId
|
|
165
|
+
readonly type: "direct"
|
|
166
|
+
readonly selection: Topology.TargetSelection
|
|
167
|
+
readonly resolve?: (context: any, enqueue: unknown) => unknown
|
|
168
|
+
readonly reenter: boolean
|
|
169
|
+
readonly declinable: boolean
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
type BranchesTransitionDescriptor = {
|
|
173
|
+
readonly [TransitionBuilderDescriptorTypeId]: typeof TransitionBuilderDescriptorTypeId
|
|
174
|
+
readonly type: "branches"
|
|
175
|
+
readonly declarations: unknown
|
|
176
|
+
readonly resolve: (context: any, enqueue: unknown) => unknown
|
|
177
|
+
readonly reenter: boolean
|
|
178
|
+
readonly declinable: boolean
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
type TransitionBuilderDescriptor = DirectTransitionDescriptor | BranchesTransitionDescriptor
|
|
182
|
+
|
|
183
|
+
const InitialBuilderDescriptorTypeId: unique symbol = Symbol("effect/Machine/InitialBuilderDescriptor")
|
|
184
|
+
|
|
185
|
+
type InitialBuilderDescriptor = {
|
|
186
|
+
readonly [InitialBuilderDescriptorTypeId]: typeof InitialBuilderDescriptorTypeId
|
|
187
|
+
readonly selection: Topology.TargetSelection
|
|
188
|
+
readonly resolve: (context: any) => unknown
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const plainTargetSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
|
|
192
|
+
selection.kind === "none"
|
|
193
|
+
? Topology.noneTargetSelection
|
|
194
|
+
: Topology.makeTargetSelection(selection.kind, selection.path, selection.scope)
|
|
195
|
+
|
|
196
|
+
const transitionOptions = (options: unknown): { readonly reenter: boolean; readonly declinable: boolean } => {
|
|
197
|
+
const configuration = typeof options === "object" && options !== null
|
|
198
|
+
? options as { readonly reenter?: unknown; readonly declinable?: unknown }
|
|
199
|
+
: {}
|
|
200
|
+
return {
|
|
201
|
+
reenter: configuration.reenter === true,
|
|
202
|
+
declinable: configuration.declinable === true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const makeDirectTransitionDescriptor = (
|
|
207
|
+
selection: Topology.TargetSelection,
|
|
208
|
+
resolve: ((context: any, enqueue: unknown) => unknown) | undefined,
|
|
209
|
+
options: unknown
|
|
210
|
+
): DirectTransitionDescriptor => {
|
|
211
|
+
const shared: Omit<DirectTransitionDescriptor, "resolve"> = {
|
|
212
|
+
[TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
|
|
213
|
+
type: "direct",
|
|
214
|
+
selection: plainTargetSelection(selection),
|
|
215
|
+
...transitionOptions(options)
|
|
216
|
+
}
|
|
217
|
+
return resolve === undefined
|
|
218
|
+
? Object.freeze(shared)
|
|
219
|
+
: Object.freeze({ ...shared, resolve })
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const decorateTransitionSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
|
|
223
|
+
Object.freeze({
|
|
224
|
+
...selection,
|
|
225
|
+
resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
|
|
226
|
+
makeDirectTransitionDescriptor(selection, resolve, options),
|
|
227
|
+
reenter: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true })
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
|
|
231
|
+
|
|
232
|
+
const makeInitialBuilderDescriptor = (
|
|
233
|
+
selection: Topology.TargetSelection,
|
|
234
|
+
resolve: (context: any) => unknown
|
|
235
|
+
): InitialBuilderDescriptor =>
|
|
236
|
+
Object.freeze({
|
|
237
|
+
[InitialBuilderDescriptorTypeId]: InitialBuilderDescriptorTypeId,
|
|
238
|
+
selection: plainTargetSelection(selection),
|
|
239
|
+
resolve
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const decorateInitialSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
|
|
243
|
+
Object.freeze({
|
|
244
|
+
...selection,
|
|
245
|
+
resolve: (resolve: (context: any) => unknown) => makeInitialBuilderDescriptor(selection, resolve)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const decorateInitialSelectorNode = (node: unknown): unknown => {
|
|
249
|
+
if (Topology.isTargetSelection(node)) return decorateInitialSelection(node)
|
|
250
|
+
if (typeof node === "function") {
|
|
251
|
+
const wrapped = ((...args: ReadonlyArray<unknown>) => decorateInitialSelection(node(...args))) as
|
|
252
|
+
& ((...args: ReadonlyArray<unknown>) => unknown)
|
|
253
|
+
& Record<string, unknown>
|
|
254
|
+
for (const key of Object.keys(node)) {
|
|
255
|
+
wrapped[key] = decorateInitialSelectorNode((node as unknown as Record<string, unknown>)[key])
|
|
256
|
+
}
|
|
257
|
+
return Object.freeze(wrapped)
|
|
258
|
+
}
|
|
259
|
+
if (typeof node === "object" && node !== null) {
|
|
260
|
+
const wrapped: Record<string, unknown> = {}
|
|
261
|
+
for (const key of Object.keys(node)) {
|
|
262
|
+
wrapped[key] = decorateInitialSelectorNode((node as Record<string, unknown>)[key])
|
|
263
|
+
}
|
|
264
|
+
return Object.freeze(wrapped)
|
|
265
|
+
}
|
|
266
|
+
return node
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const decorateTransitionSelectorNode = (node: unknown): unknown => {
|
|
270
|
+
if (Topology.isTargetSelection(node)) {
|
|
271
|
+
return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
|
|
272
|
+
}
|
|
273
|
+
if (typeof node === "function") {
|
|
274
|
+
const wrapped = ((...args: ReadonlyArray<unknown>) => decorateTransitionSelection(node(...args))) as
|
|
275
|
+
& ((...args: ReadonlyArray<unknown>) => unknown)
|
|
276
|
+
& Record<string, unknown>
|
|
277
|
+
for (const key of Object.keys(node)) {
|
|
278
|
+
wrapped[key] = decorateTransitionSelectorNode((node as unknown as Record<string, unknown>)[key])
|
|
279
|
+
}
|
|
280
|
+
return Object.freeze(wrapped)
|
|
281
|
+
}
|
|
282
|
+
if (typeof node === "object" && node !== null) {
|
|
283
|
+
const wrapped: Record<string, unknown> = {}
|
|
284
|
+
for (const key of Object.keys(node)) {
|
|
285
|
+
wrapped[key] = decorateTransitionSelectorNode((node as Record<string, unknown>)[key])
|
|
286
|
+
}
|
|
287
|
+
return Object.freeze(wrapped)
|
|
288
|
+
}
|
|
289
|
+
return node
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const makeTransitionSelector = (
|
|
293
|
+
stateNodes: Machine.StateNodes,
|
|
294
|
+
source: string
|
|
295
|
+
): unknown => {
|
|
296
|
+
const selector = {
|
|
297
|
+
...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record<string, unknown>
|
|
298
|
+
}
|
|
299
|
+
selector.branches = (declarations: unknown) =>
|
|
300
|
+
Object.freeze({
|
|
301
|
+
resolve: (
|
|
302
|
+
resolve: (context: any, enqueue: unknown) => unknown,
|
|
303
|
+
options?: unknown
|
|
304
|
+
): BranchesTransitionDescriptor =>
|
|
305
|
+
Object.freeze({
|
|
306
|
+
[TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
|
|
307
|
+
type: "branches",
|
|
308
|
+
declarations,
|
|
309
|
+
resolve,
|
|
310
|
+
...transitionOptions(options)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
return Object.freeze(selector)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const normalizeTransitionBuilder = (
|
|
317
|
+
transition: (selector: unknown) => unknown,
|
|
318
|
+
stateNodes: Machine.StateNodes,
|
|
319
|
+
path: string
|
|
320
|
+
): unknown => {
|
|
321
|
+
const result = transition(makeTransitionSelector(stateNodes, path))
|
|
322
|
+
if (Topology.isTargetSelection(result)) {
|
|
323
|
+
const selection = plainTargetSelection(result)
|
|
324
|
+
return { target: () => selection }
|
|
325
|
+
}
|
|
326
|
+
if (!hasProperty(result, TransitionBuilderDescriptorTypeId)) return result
|
|
327
|
+
const descriptor = result as TransitionBuilderDescriptor
|
|
328
|
+
if (descriptor.type === "branches") {
|
|
329
|
+
return {
|
|
330
|
+
branches: () => descriptor.declarations,
|
|
331
|
+
resolve: descriptor.resolve,
|
|
332
|
+
reenter: descriptor.reenter,
|
|
333
|
+
declinable: descriptor.declinable
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
target: () => descriptor.selection,
|
|
338
|
+
resolve: descriptor.resolve,
|
|
339
|
+
reenter: descriptor.reenter,
|
|
340
|
+
declinable: descriptor.declinable
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
159
344
|
const transitionTargetSelection = (
|
|
160
345
|
selection: Topology.TargetSelection
|
|
161
346
|
): Machine.TransitionTargetSelection =>
|
|
@@ -172,6 +357,12 @@ const makeSelectionMethod = (
|
|
|
172
357
|
): () => Topology.TargetSelection =>
|
|
173
358
|
() => Topology.makeTargetSelection(kind, path, scope)
|
|
174
359
|
|
|
360
|
+
const makeSelectionValue = (
|
|
361
|
+
kind: Topology.TargetSelectionKind,
|
|
362
|
+
path: string | undefined,
|
|
363
|
+
scope: Topology.TargetSelectionScope
|
|
364
|
+
): Topology.TargetSelection => Topology.makeTargetSelection(kind, path, scope)
|
|
365
|
+
|
|
175
366
|
const addSelectionChildren = (
|
|
176
367
|
builder: Record<string, unknown>,
|
|
177
368
|
stateNodes: Machine.StateNodes,
|
|
@@ -194,7 +385,7 @@ const makeSelectionNode = (
|
|
|
194
385
|
const method = makeSelectionMethod(kind, path, scope) as unknown as Record<string, unknown>
|
|
195
386
|
if (node.type !== "atomic" && node.type !== "final" && node.type !== "choice" && node.type !== "history") {
|
|
196
387
|
Object.defineProperty(method, "initial", {
|
|
197
|
-
value:
|
|
388
|
+
value: makeSelectionValue("initial", path, scope),
|
|
198
389
|
enumerable: true
|
|
199
390
|
})
|
|
200
391
|
if (scope === "local" || scope === "branch") {
|
|
@@ -212,7 +403,7 @@ const makeHistorySelectionTree = (
|
|
|
212
403
|
for (const node of stateNodes.byPath.values()) {
|
|
213
404
|
if (node.parent !== parent) continue
|
|
214
405
|
if (node.type === "history") {
|
|
215
|
-
builder[node.key] =
|
|
406
|
+
builder[node.key] = makeSelectionValue("history", node.path, "full")
|
|
216
407
|
} else if (node.type !== "choice") {
|
|
217
408
|
const children = makeHistorySelectionTree(stateNodes, node.path)
|
|
218
409
|
if (Object.keys(children).length > 0) builder[node.key] = children
|
|
@@ -239,12 +430,12 @@ const makeTargetSelector = (
|
|
|
239
430
|
if (localScope !== undefined) {
|
|
240
431
|
const localScopeNode = getTargetBuilderNode(stateNodes, localScope)
|
|
241
432
|
if (localScopeNode.schema !== undefined) {
|
|
242
|
-
local.with =
|
|
433
|
+
local.with = makeSelectionValue("state", localScope, "local")
|
|
243
434
|
}
|
|
244
435
|
addSelectionChildren(local, stateNodes, localScope, "local")
|
|
245
436
|
}
|
|
246
437
|
return {
|
|
247
|
-
none:
|
|
438
|
+
none: Topology.noneTargetSelection,
|
|
248
439
|
local,
|
|
249
440
|
branch,
|
|
250
441
|
full,
|
|
@@ -309,7 +500,8 @@ const getSelectionBuilder = (
|
|
|
309
500
|
return builder
|
|
310
501
|
}
|
|
311
502
|
|
|
312
|
-
const constructSelectedTarget = (builder: any): unknown =>
|
|
503
|
+
const constructSelectedTarget = (builder: any): unknown =>
|
|
504
|
+
typeof builder?.from === "function" ? builder.from() : builder()
|
|
313
505
|
|
|
314
506
|
const validateResolvedSelection = (
|
|
315
507
|
result: unknown,
|
|
@@ -352,7 +544,14 @@ const runCapturedBranch = (
|
|
|
352
544
|
const resolverContext = { ...context }
|
|
353
545
|
if (branch.selection.kind === "none") delete resolverContext.target
|
|
354
546
|
else resolverContext.target = selectedTarget
|
|
547
|
+
if (branch.declinable === true) resolverContext.decline = Topology.makeDeclined
|
|
355
548
|
const resolved = branch.resolve(resolverContext, enqueue)
|
|
549
|
+
if (Topology.isDeclined(resolved)) {
|
|
550
|
+
if (branch.declinable !== true) {
|
|
551
|
+
throw new Error(`Machine transition for state "${source}" returned decline without declaring declinable: true`)
|
|
552
|
+
}
|
|
553
|
+
return resolved
|
|
554
|
+
}
|
|
356
555
|
validateResolvedSelection(resolved, branch.selection, stateNodes)
|
|
357
556
|
return resolved === undefined ? constructSelectedTarget(selectedTarget) : resolved
|
|
358
557
|
}
|
|
@@ -394,7 +593,7 @@ const captureNamedBranches = (
|
|
|
394
593
|
if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
|
|
395
594
|
throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
|
|
396
595
|
}
|
|
397
|
-
return Object.freeze({ key, title: title ?? key, selection: target })
|
|
596
|
+
return Object.freeze({ key, title: title ?? key, selection: plainTargetSelection(target) })
|
|
398
597
|
}))
|
|
399
598
|
}
|
|
400
599
|
|
|
@@ -474,17 +673,24 @@ const validateSelectedBranchResult = (
|
|
|
474
673
|
}
|
|
475
674
|
|
|
476
675
|
const captureTransition = (
|
|
477
|
-
|
|
676
|
+
rawTransition: unknown,
|
|
478
677
|
stateNodes: Machine.StateNodes,
|
|
479
678
|
path: string,
|
|
480
679
|
trigger: PropertyKey
|
|
481
680
|
): unknown => {
|
|
681
|
+
if (typeof rawTransition !== "function") {
|
|
682
|
+
throw new Error(
|
|
683
|
+
`Machine transition for state "${path}" on "${String(trigger)}" must be a target-first callback`
|
|
684
|
+
)
|
|
685
|
+
}
|
|
686
|
+
const transition = normalizeTransitionBuilder(rawTransition as (selector: unknown) => unknown, stateNodes, path)
|
|
482
687
|
if (typeof transition !== "object" || transition === null) {
|
|
483
688
|
throw new Error(`Machine transition for state "${path}" on "${String(trigger)}" must be an object`)
|
|
484
689
|
}
|
|
485
690
|
const definition = transition as Record<PropertyKey, unknown>
|
|
486
691
|
const selector = makeTargetSelector(stateNodes, path)
|
|
487
692
|
const reenter = definition.reenter === true
|
|
693
|
+
const declinable = definition.declinable === true
|
|
488
694
|
if (hasProperty(definition, "branches")) {
|
|
489
695
|
const branching = definition as { readonly branches: unknown; readonly resolve?: unknown }
|
|
490
696
|
if (typeof branching.branches !== "function" || typeof branching.resolve !== "function") {
|
|
@@ -499,7 +705,18 @@ const captureTransition = (
|
|
|
499
705
|
const resolverContext = { ...context }
|
|
500
706
|
delete resolverContext.target
|
|
501
707
|
resolverContext.select = makeBranchSelectors(context, branches, owner, stateNodes, path)
|
|
708
|
+
if (declinable) resolverContext.decline = Topology.makeDeclined
|
|
502
709
|
const selected = resolve(resolverContext, enqueue)
|
|
710
|
+
if (Topology.isDeclined(selected)) {
|
|
711
|
+
if (!declinable) {
|
|
712
|
+
throw new Error(
|
|
713
|
+
`Machine branching transition for state "${path}" on "${
|
|
714
|
+
String(trigger)
|
|
715
|
+
}" returned decline without declaring declinable: true`
|
|
716
|
+
)
|
|
717
|
+
}
|
|
718
|
+
return { result: selected, branchIndex: -1, branchKey: undefined }
|
|
719
|
+
}
|
|
503
720
|
if (!Topology.isSelectedBranch(selected) || selected.owner !== owner) {
|
|
504
721
|
throw new Error(
|
|
505
722
|
`Machine branching transition for state "${path}" on "${String(trigger)}" must select one declared branch`
|
|
@@ -518,6 +735,7 @@ const captureTransition = (
|
|
|
518
735
|
}
|
|
519
736
|
return {
|
|
520
737
|
reenter,
|
|
738
|
+
declinable,
|
|
521
739
|
targets: [
|
|
522
740
|
...new Set(
|
|
523
741
|
branches.flatMap((branch) => branch.selection.path === undefined ? [] : [branch.selection.path])
|
|
@@ -544,6 +762,7 @@ const captureTransition = (
|
|
|
544
762
|
})
|
|
545
763
|
return {
|
|
546
764
|
reenter,
|
|
765
|
+
declinable,
|
|
547
766
|
targets: branch.selection.path === undefined ? [] : [branch.selection.path],
|
|
548
767
|
branches: [{
|
|
549
768
|
type: "direct" as const,
|
|
@@ -570,20 +789,75 @@ const captureEventHandlers = (
|
|
|
570
789
|
return captured
|
|
571
790
|
}
|
|
572
791
|
|
|
792
|
+
interface InvokeBuilderDescriptor {
|
|
793
|
+
readonly [InvokeBuilderDescriptorTypeId]: typeof InvokeBuilderDescriptorTypeId
|
|
794
|
+
readonly config: Readonly<Record<PropertyKey, unknown>>
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
type InvokeBuilderChannel = "onDone" | "onFailure" | "onElement" | "onSnapshot"
|
|
798
|
+
|
|
799
|
+
const makeInvokeBuilder = (
|
|
800
|
+
config: Readonly<Record<PropertyKey, unknown>>,
|
|
801
|
+
channels: ReadonlyArray<InvokeBuilderChannel>
|
|
802
|
+
): InvokeBuilderDescriptor => {
|
|
803
|
+
const builder: Record<PropertyKey, unknown> = {
|
|
804
|
+
[InvokeBuilderDescriptorTypeId]: InvokeBuilderDescriptorTypeId as typeof InvokeBuilderDescriptorTypeId,
|
|
805
|
+
config
|
|
806
|
+
}
|
|
807
|
+
for (const channel of channels) {
|
|
808
|
+
if (!hasProperty(config, channel)) {
|
|
809
|
+
builder[channel] = (handler: unknown) => makeInvokeBuilder({ ...config, [channel]: handler }, channels)
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return Object.freeze(builder) as unknown as InvokeBuilderDescriptor
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const invokeSelector = Object.freeze({
|
|
816
|
+
effect: (id: string, effect: unknown) => makeInvokeBuilder({ id, effect }, ["onDone", "onFailure"]),
|
|
817
|
+
stream: (id: string, stream: unknown) => makeInvokeBuilder({ id, stream }, ["onElement", "onDone", "onFailure"]),
|
|
818
|
+
timer: (id: string, after: unknown) => makeInvokeBuilder({ id, after }, ["onDone"]),
|
|
819
|
+
logic: (id: string, options: Readonly<Record<PropertyKey, unknown>>) =>
|
|
820
|
+
makeInvokeBuilder({ id, ...options }, ["onSnapshot", "onDone", "onFailure"]),
|
|
821
|
+
child: (child: unknown, options?: Readonly<Record<PropertyKey, unknown>>) =>
|
|
822
|
+
makeInvokeBuilder(options === undefined ? { child } : { child, ...options }, [
|
|
823
|
+
"onSnapshot",
|
|
824
|
+
"onDone",
|
|
825
|
+
"onFailure"
|
|
826
|
+
])
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
const invokeBuilderConfig = (value: unknown, path: string): Readonly<Record<PropertyKey, unknown>> => {
|
|
830
|
+
if (
|
|
831
|
+
typeof value !== "object" || value === null ||
|
|
832
|
+
!hasProperty(value, InvokeBuilderDescriptorTypeId) ||
|
|
833
|
+
value[InvokeBuilderDescriptorTypeId] !== InvokeBuilderDescriptorTypeId
|
|
834
|
+
) {
|
|
835
|
+
throw new Error(`Machine invocation for state "${path}" must be constructed from its source selector`)
|
|
836
|
+
}
|
|
837
|
+
return (value as unknown as InvokeBuilderDescriptor).config
|
|
838
|
+
}
|
|
839
|
+
|
|
573
840
|
const captureInvokeDefinition = (
|
|
574
841
|
invoke: unknown,
|
|
575
842
|
stateNodes: Machine.StateNodes,
|
|
576
843
|
path: string
|
|
577
844
|
): unknown => {
|
|
578
|
-
if (
|
|
579
|
-
|
|
580
|
-
const captured = { ...(invoke as Record<PropertyKey, unknown>) }
|
|
581
|
-
for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
|
|
582
|
-
if (captured[key] !== undefined) {
|
|
583
|
-
captured[key] = captureTransition(captured[key], stateNodes, path, key)
|
|
584
|
-
}
|
|
845
|
+
if (typeof invoke !== "function") {
|
|
846
|
+
throw new Error(`Machine invocation for state "${path}" must be a source-first callback`)
|
|
585
847
|
}
|
|
586
|
-
|
|
848
|
+
const authored = invoke(invokeSelector)
|
|
849
|
+
const definitions = Array.isArray(authored) ? authored : [authored]
|
|
850
|
+
const capturedDefinitions = definitions.map((definition) => {
|
|
851
|
+
const captured = { ...invokeBuilderConfig(definition, path) }
|
|
852
|
+
for (const key of ["onElement", "onDone", "onFailure", "onSnapshot"] as const) {
|
|
853
|
+
if (captured[key] !== undefined) {
|
|
854
|
+
captured[key] = captureTransition(captured[key], stateNodes, path, key)
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
return captured
|
|
858
|
+
})
|
|
859
|
+
if (Array.isArray(authored)) return capturedDefinitions
|
|
860
|
+
return capturedDefinitions[0]
|
|
587
861
|
}
|
|
588
862
|
|
|
589
863
|
const flattenHandlers = (
|
|
@@ -1185,10 +1459,12 @@ const makeInitialSelector = (stateNodes: Machine.StateNodes): unknown => {
|
|
|
1185
1459
|
const selector: Record<string, unknown> = {}
|
|
1186
1460
|
for (const node of stateNodes.byPath.values()) {
|
|
1187
1461
|
if (node.parent === undefined && node.type !== "history" && node.type !== "choice") {
|
|
1188
|
-
selector[node.key] =
|
|
1462
|
+
selector[node.key] = node.type === "atomic" || node.type === "final"
|
|
1463
|
+
? makeSelectionMethod("state", node.path, "initial")
|
|
1464
|
+
: Object.freeze({ initial: makeSelectionValue("initial", node.path, "initial") })
|
|
1189
1465
|
}
|
|
1190
1466
|
}
|
|
1191
|
-
return selector
|
|
1467
|
+
return Object.freeze(selector)
|
|
1192
1468
|
}
|
|
1193
1469
|
|
|
1194
1470
|
const getInitialSelectionBuilder = (
|
|
@@ -1207,15 +1483,37 @@ const getInitialSelectionBuilder = (
|
|
|
1207
1483
|
}
|
|
1208
1484
|
|
|
1209
1485
|
const captureInitialBranch = (
|
|
1210
|
-
|
|
1211
|
-
|
|
1486
|
+
definition: unknown,
|
|
1487
|
+
stateNodes: Machine.StateNodes,
|
|
1212
1488
|
initialBuilder: Record<string, any>
|
|
1213
|
-
):
|
|
1214
|
-
|
|
1215
|
-
|
|
1489
|
+
): {
|
|
1490
|
+
readonly selection: Topology.TargetSelection
|
|
1491
|
+
readonly resolve?: (context: any) => unknown
|
|
1492
|
+
readonly builder: (...args: ReadonlyArray<any>) => unknown
|
|
1493
|
+
} => {
|
|
1494
|
+
if (typeof definition !== "function") {
|
|
1495
|
+
throw new Error("Machine initial definition must be a target-first callback")
|
|
1496
|
+
}
|
|
1497
|
+
const result = definition(decorateInitialSelectorNode(makeInitialSelector(stateNodes)))
|
|
1498
|
+
let selection: Topology.TargetSelection
|
|
1499
|
+
let resolve: ((context: any) => unknown) | undefined
|
|
1500
|
+
if (Topology.isTargetSelection(result)) {
|
|
1501
|
+
selection = plainTargetSelection(result)
|
|
1502
|
+
} else if (hasProperty(result, InitialBuilderDescriptorTypeId)) {
|
|
1503
|
+
const descriptor = result as InitialBuilderDescriptor
|
|
1504
|
+
selection = descriptor.selection
|
|
1505
|
+
resolve = descriptor.resolve
|
|
1506
|
+
} else {
|
|
1507
|
+
throw new Error("Machine initial definition must select exactly one target")
|
|
1508
|
+
}
|
|
1509
|
+
if (selection.kind !== "state" && selection.kind !== "initial") {
|
|
1216
1510
|
throw new Error("Machine initial target must select a top-level state or its declared initial entry")
|
|
1217
1511
|
}
|
|
1218
|
-
|
|
1512
|
+
const captured = {
|
|
1513
|
+
selection,
|
|
1514
|
+
builder: getInitialSelectionBuilder(initialBuilder, selection)
|
|
1515
|
+
}
|
|
1516
|
+
return resolve === undefined ? Object.freeze(captured) : Object.freeze({ ...captured, resolve })
|
|
1219
1517
|
}
|
|
1220
1518
|
|
|
1221
1519
|
const validateInitialSelection = (result: unknown, selection: Topology.TargetSelection): void => {
|
|
@@ -1237,17 +1535,13 @@ const compileInitial = (
|
|
|
1237
1535
|
readonly initial: (input?: unknown) => unknown
|
|
1238
1536
|
readonly definition: Machine.InitialDefinition
|
|
1239
1537
|
} => {
|
|
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
1538
|
const initialBuilder = makeSnapshotBuilder(states, { mode: "initial", prefix: "" }) as Record<string, any>
|
|
1245
|
-
const branch = captureInitialBranch(definition,
|
|
1539
|
+
const branch = captureInitialBranch(definition, stateNodes, initialBuilder)
|
|
1246
1540
|
return {
|
|
1247
1541
|
initial: (input?: unknown) => {
|
|
1248
1542
|
const result = branch.resolve === undefined
|
|
1249
|
-
? branch.builder
|
|
1250
|
-
: branch.resolve({ input, target: branch.builder }
|
|
1543
|
+
? constructSelectedTarget(branch.builder)
|
|
1544
|
+
: branch.resolve({ input, target: branch.builder })
|
|
1251
1545
|
validateInitialSelection(result, branch.selection)
|
|
1252
1546
|
return result
|
|
1253
1547
|
},
|
|
@@ -1297,7 +1591,7 @@ type MakeConfig<
|
|
|
1297
1591
|
InitialE,
|
|
1298
1592
|
InitialR,
|
|
1299
1593
|
InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1300
|
-
|
|
1594
|
+
ParentDeclaration extends Parent.Any | undefined
|
|
1301
1595
|
> = {
|
|
1302
1596
|
readonly id?: string
|
|
1303
1597
|
readonly states: States & DefineStateTreeInput<NoInfer<States>>
|
|
@@ -1311,7 +1605,7 @@ type MakeConfig<
|
|
|
1311
1605
|
NoInfer<InternalEvents>
|
|
1312
1606
|
>
|
|
1313
1607
|
readonly emittedEvents?: Machine.EventProtocol<"emitted", Emits>
|
|
1314
|
-
readonly
|
|
1608
|
+
readonly parent?: ParentDeclaration
|
|
1315
1609
|
readonly input?: Input
|
|
1316
1610
|
readonly initial: unknown
|
|
1317
1611
|
}
|
|
@@ -1324,7 +1618,7 @@ type MakeResult<
|
|
|
1324
1618
|
InitialE,
|
|
1325
1619
|
InitialR,
|
|
1326
1620
|
InternalEvents extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1327
|
-
|
|
1621
|
+
ParentDeclaration extends Parent.Any | undefined
|
|
1328
1622
|
> = Definition<
|
|
1329
1623
|
States,
|
|
1330
1624
|
readonly [...InputEvents, ...InternalEvents],
|
|
@@ -1335,7 +1629,7 @@ type MakeResult<
|
|
|
1335
1629
|
Machine.TerminalOutput<States>,
|
|
1336
1630
|
Emits,
|
|
1337
1631
|
InputEvents,
|
|
1338
|
-
|
|
1632
|
+
Machine.ParentEventsOf<ParentDeclaration>
|
|
1339
1633
|
>
|
|
1340
1634
|
|
|
1341
1635
|
interface Make {
|
|
@@ -1347,11 +1641,11 @@ interface Make {
|
|
|
1347
1641
|
InitialE = never,
|
|
1348
1642
|
InitialR = never,
|
|
1349
1643
|
const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1350
|
-
const
|
|
1644
|
+
const ParentDeclaration extends Parent.Any | undefined = undefined
|
|
1351
1645
|
>(
|
|
1352
|
-
config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents,
|
|
1646
|
+
config: MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>,
|
|
1353
1647
|
..._validation: ValidateDefinedStates<NoInfer<States>>
|
|
1354
|
-
): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents,
|
|
1648
|
+
): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>
|
|
1355
1649
|
<
|
|
1356
1650
|
const States extends Machine.StateSchemas,
|
|
1357
1651
|
const InputEvents extends ReadonlyArray<Machine.TaggedSchema>,
|
|
@@ -1360,10 +1654,13 @@ interface Make {
|
|
|
1360
1654
|
InitialE = never,
|
|
1361
1655
|
InitialR = never,
|
|
1362
1656
|
const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1363
|
-
const
|
|
1657
|
+
const ParentDeclaration extends Parent.Any | undefined = undefined
|
|
1364
1658
|
>(
|
|
1365
1659
|
config:
|
|
1366
|
-
& Omit<
|
|
1660
|
+
& Omit<
|
|
1661
|
+
MakeConfig<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration>,
|
|
1662
|
+
"states"
|
|
1663
|
+
>
|
|
1367
1664
|
& { readonly states: InvalidDefinedStateTreeInput<States> }
|
|
1368
1665
|
): never
|
|
1369
1666
|
}
|
|
@@ -1376,7 +1673,7 @@ export const make: Make = (<
|
|
|
1376
1673
|
InitialE = never,
|
|
1377
1674
|
InitialR = never,
|
|
1378
1675
|
const InternalEvents extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1379
|
-
const
|
|
1676
|
+
const ParentDeclaration extends Parent.Any | undefined = undefined
|
|
1380
1677
|
>(
|
|
1381
1678
|
config: {
|
|
1382
1679
|
readonly id?: string
|
|
@@ -1384,18 +1681,18 @@ export const make: Make = (<
|
|
|
1384
1681
|
readonly events: Machine.EventProtocol<"public", InputEvents>
|
|
1385
1682
|
readonly internalEvents?: Machine.EventProtocol<"internal", InternalEvents>
|
|
1386
1683
|
readonly emittedEvents?: Machine.EventProtocol<"emitted", Emits>
|
|
1387
|
-
readonly
|
|
1684
|
+
readonly parent?: ParentDeclaration
|
|
1388
1685
|
readonly input?: Input
|
|
1389
1686
|
readonly initial: unknown
|
|
1390
1687
|
}
|
|
1391
|
-
): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents,
|
|
1688
|
+
): MakeResult<States, InputEvents, Emits, Input, InitialE, InitialR, InternalEvents, ParentDeclaration> => {
|
|
1392
1689
|
StateDefinition.validateStateDefinitions(config.states, "Machine.make")
|
|
1393
1690
|
const self = Object.create(Proto)
|
|
1394
1691
|
self.states = config.states
|
|
1395
1692
|
self.events = config.events
|
|
1396
1693
|
self.internalEvents = config.internalEvents ?? Protocol.makeEventProtocol("internal", [] as const)
|
|
1397
1694
|
self.emittedEvents = config.emittedEvents ?? Protocol.makeEventProtocol("emitted", [] as const)
|
|
1398
|
-
self.
|
|
1695
|
+
self.parent = config.parent
|
|
1399
1696
|
self.input = config.input
|
|
1400
1697
|
self.id = config.id
|
|
1401
1698
|
self.stateNodes = Topology.compileStateNodes(config.states)
|
|
@@ -1405,7 +1702,6 @@ export const make: Make = (<
|
|
|
1405
1702
|
self.makeTargetBuilder = makeTargetBuilder(config.states, self.stateNodes)
|
|
1406
1703
|
self.handlers = Object.create(null)
|
|
1407
1704
|
self.handle = makeHandle(self)
|
|
1408
|
-
self.invoke = makeBoundInvoke
|
|
1409
1705
|
Protocol.setProtocol(self)
|
|
1410
1706
|
return self
|
|
1411
1707
|
}) as Make
|
|
@@ -1428,6 +1724,27 @@ export const events = <const Inputs extends ReadonlyArray<Machine.EventProtocolI
|
|
|
1428
1724
|
flattenEventProtocolInputs("public", inputs)
|
|
1429
1725
|
) as Machine.EventProtocol<"public", Machine.EventProtocolInputSchemasOf<"public", Inputs>>
|
|
1430
1726
|
|
|
1727
|
+
const makeParent = <
|
|
1728
|
+
const Mode extends ParentMode,
|
|
1729
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1730
|
+
>(
|
|
1731
|
+
mode: Mode,
|
|
1732
|
+
events: Machine.EventProtocol<"public", Events>
|
|
1733
|
+
): Parent<Mode, Events> => {
|
|
1734
|
+
if (!Protocol.isEventProtocol(events, "public")) {
|
|
1735
|
+
throw new Error("Machine parent declarations require a protocol created with Machine.events")
|
|
1736
|
+
}
|
|
1737
|
+
return Object.freeze({ [ParentTypeId]: ParentTypeId, mode, events })
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
export const parent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
1741
|
+
events: Machine.EventProtocol<"public", Events>
|
|
1742
|
+
): Parent<"required", Events> => makeParent("required", events)
|
|
1743
|
+
|
|
1744
|
+
export const optionalParent = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
1745
|
+
events: Machine.EventProtocol<"public", Events>
|
|
1746
|
+
): Parent<"optional", Events> => makeParent("optional", events)
|
|
1747
|
+
|
|
1431
1748
|
export const internalEvents = <const Inputs extends ReadonlyArray<Machine.EventProtocolInput<"internal">>>(
|
|
1432
1749
|
...inputs: Inputs
|
|
1433
1750
|
): Machine.EventProtocol<"internal", Machine.EventProtocolInputSchemasOf<"internal", Inputs>> =>
|