@typeonce/effect-machine 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -22
- package/dist/Machine.d.ts +71 -34
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +50 -43
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/docs/root-api.md +45 -0
- package/package.json +1 -1
- package/src/Machine.ts +266 -92
- package/src/internal/machine/machine.ts +88 -57
- package/src/internal/testing/machine/finiteModel.ts +1 -1
|
@@ -140,7 +140,7 @@ type DirectTransitionDescriptor = {
|
|
|
140
140
|
readonly type: "direct"
|
|
141
141
|
readonly selection: Topology.TargetSelection
|
|
142
142
|
readonly resolver?: (context: any, enqueue: unknown) => unknown
|
|
143
|
-
readonly
|
|
143
|
+
readonly reenterSource: boolean
|
|
144
144
|
readonly declinable: boolean
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -149,7 +149,7 @@ type BranchesTransitionDescriptor = {
|
|
|
149
149
|
readonly type: "branches"
|
|
150
150
|
readonly declarations: unknown
|
|
151
151
|
readonly resolve: (context: any, enqueue: unknown) => unknown
|
|
152
|
-
readonly
|
|
152
|
+
readonly reenterSource: boolean
|
|
153
153
|
readonly declinable: boolean
|
|
154
154
|
}
|
|
155
155
|
|
|
@@ -168,25 +168,27 @@ const plainTargetSelection = (selection: Topology.TargetSelection): Topology.Tar
|
|
|
168
168
|
? Topology.noneTargetSelection
|
|
169
169
|
: Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, selection.updatePath)
|
|
170
170
|
|
|
171
|
-
const transitionOptions = (options: unknown): { readonly
|
|
171
|
+
const transitionOptions = (options: unknown): { readonly declinable: boolean } => {
|
|
172
172
|
const configuration = typeof options === "object" && options !== null
|
|
173
|
-
? options as { readonly
|
|
173
|
+
? options as { readonly declinable?: unknown }
|
|
174
174
|
: {}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
declinable: configuration.declinable === true
|
|
175
|
+
if (hasProperty(configuration, "reenter")) {
|
|
176
|
+
throw new Error("Use .reenter() before construction instead of the resolver reenter option")
|
|
178
177
|
}
|
|
178
|
+
return { declinable: configuration.declinable === true }
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
const makeDirectTransitionDescriptor = (
|
|
182
182
|
selection: Topology.TargetSelection,
|
|
183
183
|
resolve: ((context: any, enqueue: unknown) => unknown) | undefined,
|
|
184
|
-
options: unknown
|
|
184
|
+
options: unknown,
|
|
185
|
+
reenterSource = false
|
|
185
186
|
): DirectTransitionDescriptor => {
|
|
186
187
|
const shared: Omit<DirectTransitionDescriptor, "resolver"> = {
|
|
187
188
|
[TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
|
|
188
189
|
type: "direct",
|
|
189
190
|
selection: plainTargetSelection(selection),
|
|
191
|
+
reenterSource,
|
|
190
192
|
...transitionOptions(options)
|
|
191
193
|
}
|
|
192
194
|
return resolve === undefined
|
|
@@ -194,34 +196,67 @@ const makeDirectTransitionDescriptor = (
|
|
|
194
196
|
: Object.freeze({ ...shared, resolver: resolve })
|
|
195
197
|
}
|
|
196
198
|
|
|
197
|
-
|
|
199
|
+
// The authored callback crosses an erased schema boundary here. Each builder
|
|
200
|
+
// retains its construction mode, and planning validates both resulting values.
|
|
201
|
+
const constructSelectionValue = (
|
|
202
|
+
selection: Topology.TargetSelection,
|
|
203
|
+
context: any,
|
|
204
|
+
method: "from" | "decoded",
|
|
205
|
+
value: unknown
|
|
206
|
+
): unknown => {
|
|
207
|
+
if (selection.kind === "update") return context.owner[method](value)
|
|
208
|
+
if (selection.updatePath === undefined) return context.target[method](value)
|
|
209
|
+
const values = value as { readonly target: unknown; readonly update: unknown }
|
|
210
|
+
return context.target[method](values.target).update(context.owner[method](values.update))
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const guardedSelection = (
|
|
214
|
+
selection: Topology.TargetSelection,
|
|
215
|
+
predicate: (context: any) => boolean,
|
|
216
|
+
reenterSource: boolean
|
|
217
|
+
): object => {
|
|
198
218
|
const wrap = (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
|
|
199
219
|
makeDirectTransitionDescriptor(
|
|
200
220
|
selection,
|
|
201
221
|
(context, enqueue) => predicate(context) ? resolve(context, enqueue) : Topology.makeDeclined(),
|
|
202
|
-
{ ...transitionOptions(options), declinable: true }
|
|
222
|
+
{ ...transitionOptions(options), declinable: true },
|
|
223
|
+
reenterSource
|
|
203
224
|
)
|
|
204
225
|
return Object.freeze({
|
|
205
|
-
...wrap((
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
226
|
+
...wrap((context) => selection.kind === "none" ? undefined : constructSelectedTarget(context.target)),
|
|
227
|
+
from: (value: (context: any) => unknown) =>
|
|
228
|
+
wrap((context) => constructSelectionValue(selection, context, "from", value(context))),
|
|
229
|
+
decoded: (value: (context: any) => unknown) =>
|
|
230
|
+
wrap((context) => constructSelectionValue(selection, context, "decoded", value(context))),
|
|
210
231
|
resolve: wrap
|
|
211
232
|
})
|
|
212
233
|
}
|
|
213
234
|
|
|
214
|
-
const decorateTransitionSelection = (
|
|
235
|
+
const decorateTransitionSelection = (
|
|
236
|
+
selection: Topology.TargetSelection,
|
|
237
|
+
reenterSource = false
|
|
238
|
+
): Topology.TargetSelection =>
|
|
215
239
|
Object.freeze({
|
|
216
240
|
...selection,
|
|
217
|
-
|
|
241
|
+
...(reenterSource ? makeDirectTransitionDescriptor(selection, undefined, undefined, true) : {}),
|
|
242
|
+
guard: (predicate: (context: any) => boolean) => guardedSelection(selection, predicate, reenterSource),
|
|
218
243
|
from: (value: (context: any) => unknown) =>
|
|
219
|
-
makeDirectTransitionDescriptor(
|
|
244
|
+
makeDirectTransitionDescriptor(
|
|
245
|
+
selection,
|
|
246
|
+
(context) => constructSelectionValue(selection, context, "from", value(context)),
|
|
247
|
+
undefined,
|
|
248
|
+
reenterSource
|
|
249
|
+
),
|
|
220
250
|
decoded: (value: (context: any) => unknown) =>
|
|
221
|
-
makeDirectTransitionDescriptor(
|
|
251
|
+
makeDirectTransitionDescriptor(
|
|
252
|
+
selection,
|
|
253
|
+
(context) => constructSelectionValue(selection, context, "decoded", value(context)),
|
|
254
|
+
undefined,
|
|
255
|
+
reenterSource
|
|
256
|
+
),
|
|
222
257
|
resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
|
|
223
|
-
makeDirectTransitionDescriptor(selection, resolve, options),
|
|
224
|
-
reenter: () =>
|
|
258
|
+
makeDirectTransitionDescriptor(selection, resolve, options, reenterSource),
|
|
259
|
+
reenter: () => decorateTransitionSelection(selection, true),
|
|
225
260
|
updating: (owner: unknown) => {
|
|
226
261
|
if (typeof owner !== "function") {
|
|
227
262
|
throw new Error("Machine updating owner must be a state selector")
|
|
@@ -234,22 +269,12 @@ const decorateTransitionSelection = (selection: Topology.TargetSelection): Topol
|
|
|
234
269
|
throw new Error("Machine updating owner must be addressed by one branch state selector")
|
|
235
270
|
}
|
|
236
271
|
return decorateTransitionSelection(
|
|
237
|
-
Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, ownerSelection.path)
|
|
272
|
+
Topology.makeTargetSelection(selection.kind, selection.path, selection.scope, ownerSelection.path),
|
|
273
|
+
reenterSource
|
|
238
274
|
)
|
|
239
275
|
}
|
|
240
276
|
})
|
|
241
277
|
|
|
242
|
-
const decorateStateUpdateSelection = (selection: Topology.TargetSelection): Topology.TargetSelection =>
|
|
243
|
-
Object.freeze({
|
|
244
|
-
...selection,
|
|
245
|
-
from: (value: (context: any) => unknown) =>
|
|
246
|
-
makeDirectTransitionDescriptor(selection, (context) => context.owner.from(value(context)), undefined),
|
|
247
|
-
decoded: (value: (context: any) => unknown) =>
|
|
248
|
-
makeDirectTransitionDescriptor(selection, (context) => context.owner.decoded(value(context)), undefined),
|
|
249
|
-
resolve: (resolve: (context: any, enqueue: unknown) => unknown, options?: unknown) =>
|
|
250
|
-
makeDirectTransitionDescriptor(selection, resolve, options)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
278
|
const noneTransitionSelection = decorateTransitionSelection(Topology.noneTargetSelection)
|
|
254
279
|
|
|
255
280
|
const makeInitialBuilderDescriptor = (
|
|
@@ -301,7 +326,6 @@ const decorateTransitionSelectorNode = (node: unknown): unknown => {
|
|
|
301
326
|
initial: decorateTransitionSelection(node.initial as Topology.TargetSelection)
|
|
302
327
|
})
|
|
303
328
|
}
|
|
304
|
-
if (node.kind === "update") return decorateStateUpdateSelection(node)
|
|
305
329
|
return node === Topology.noneTargetSelection ? noneTransitionSelection : decorateTransitionSelection(node)
|
|
306
330
|
}
|
|
307
331
|
if (typeof node === "function") {
|
|
@@ -323,29 +347,31 @@ const decorateTransitionSelectorNode = (node: unknown): unknown => {
|
|
|
323
347
|
return node
|
|
324
348
|
}
|
|
325
349
|
|
|
350
|
+
const decorateBranches = (declarations: unknown, reenterSource = false): object =>
|
|
351
|
+
Object.freeze({
|
|
352
|
+
reenter: () => decorateBranches(declarations, true),
|
|
353
|
+
resolve: (
|
|
354
|
+
resolve: (context: any, enqueue: unknown) => unknown,
|
|
355
|
+
options?: unknown
|
|
356
|
+
): BranchesTransitionDescriptor =>
|
|
357
|
+
Object.freeze({
|
|
358
|
+
[TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
|
|
359
|
+
type: "branches",
|
|
360
|
+
declarations,
|
|
361
|
+
resolve,
|
|
362
|
+
reenterSource,
|
|
363
|
+
...transitionOptions(options)
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
326
367
|
const makeTransitionSelector = (
|
|
327
368
|
stateNodes: Machine.StateNodes,
|
|
328
369
|
source: string
|
|
329
|
-
): unknown =>
|
|
330
|
-
|
|
331
|
-
...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record<string, unknown
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
Object.freeze({
|
|
335
|
-
resolve: (
|
|
336
|
-
resolve: (context: any, enqueue: unknown) => unknown,
|
|
337
|
-
options?: unknown
|
|
338
|
-
): BranchesTransitionDescriptor =>
|
|
339
|
-
Object.freeze({
|
|
340
|
-
[TransitionBuilderDescriptorTypeId]: TransitionBuilderDescriptorTypeId,
|
|
341
|
-
type: "branches",
|
|
342
|
-
declarations,
|
|
343
|
-
resolve,
|
|
344
|
-
...transitionOptions(options)
|
|
345
|
-
})
|
|
346
|
-
})
|
|
347
|
-
return Object.freeze(selector)
|
|
348
|
-
}
|
|
370
|
+
): unknown =>
|
|
371
|
+
Object.freeze({
|
|
372
|
+
...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record<string, unknown>,
|
|
373
|
+
branches: (declarations: unknown) => decorateBranches(declarations)
|
|
374
|
+
})
|
|
349
375
|
|
|
350
376
|
const normalizeTransitionBuilder = (
|
|
351
377
|
transition: (selector: unknown) => unknown,
|
|
@@ -353,7 +379,7 @@ const normalizeTransitionBuilder = (
|
|
|
353
379
|
path: string
|
|
354
380
|
): unknown => {
|
|
355
381
|
const result = transition(makeTransitionSelector(stateNodes, path))
|
|
356
|
-
if (Topology.isTargetSelection(result)) {
|
|
382
|
+
if (Topology.isTargetSelection(result) && !hasProperty(result, TransitionBuilderDescriptorTypeId)) {
|
|
357
383
|
const selection = plainTargetSelection(result)
|
|
358
384
|
return { target: () => selection }
|
|
359
385
|
}
|
|
@@ -363,14 +389,14 @@ const normalizeTransitionBuilder = (
|
|
|
363
389
|
return {
|
|
364
390
|
branches: () => descriptor.declarations,
|
|
365
391
|
resolve: descriptor.resolve,
|
|
366
|
-
reenter: descriptor.
|
|
392
|
+
reenter: descriptor.reenterSource,
|
|
367
393
|
declinable: descriptor.declinable
|
|
368
394
|
}
|
|
369
395
|
}
|
|
370
396
|
return {
|
|
371
397
|
target: () => descriptor.selection,
|
|
372
398
|
resolve: descriptor.resolver,
|
|
373
|
-
reenter: descriptor.
|
|
399
|
+
reenter: descriptor.reenterSource,
|
|
374
400
|
declinable: descriptor.declinable
|
|
375
401
|
}
|
|
376
402
|
}
|
|
@@ -753,6 +779,11 @@ const captureNamedBranches = (
|
|
|
753
779
|
if (target.updatePath !== undefined) {
|
|
754
780
|
throw new Error(`Machine transition branch "${key}" cannot declare an updating target`)
|
|
755
781
|
}
|
|
782
|
+
if (hasProperty(target, TransitionBuilderDescriptorTypeId)) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
`Machine transition branch "${key}" requires a topology selection; apply .reenter() to .branches(...)`
|
|
785
|
+
)
|
|
786
|
+
}
|
|
756
787
|
if (title !== undefined && (typeof title !== "string" || title.length === 0)) {
|
|
757
788
|
throw new Error(`Machine transition branch "${key}" title must be a non-empty string`)
|
|
758
789
|
}
|
|
@@ -1442,7 +1442,7 @@ const makeHandlers = (
|
|
|
1442
1442
|
? () => undefined
|
|
1443
1443
|
: ({ target }: { readonly target: any }) =>
|
|
1444
1444
|
resolveDefinitionTarget(target, path, transition.target!, byPath, transition.targetValue)
|
|
1445
|
-
return
|
|
1445
|
+
return ("reenter" in transition && transition.reenter ? selected.reenter() : selected).resolve(resolve)
|
|
1446
1446
|
}
|
|
1447
1447
|
if (transition.trigger.type === "event") on[transition.trigger.event] = config
|
|
1448
1448
|
else if (transition.trigger.type === "always") always = config
|