@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.
@@ -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 reenter: boolean
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 reenter: boolean
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 reenter: boolean; readonly declinable: boolean } => {
171
+ const transitionOptions = (options: unknown): { readonly declinable: boolean } => {
172
172
  const configuration = typeof options === "object" && options !== null
173
- ? options as { readonly reenter?: unknown; readonly declinable?: unknown }
173
+ ? options as { readonly declinable?: unknown }
174
174
  : {}
175
- return {
176
- reenter: configuration.reenter === true,
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
- const guardedSelection = (selection: Topology.TargetSelection, predicate: (context: any) => boolean): object => {
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
- context
207
- ) => (selection.kind === "none" ? Topology.makeNoTarget() : constructSelectedTarget(context.target))),
208
- from: (value: (context: any) => unknown) => wrap((context) => context.target.from(value(context))),
209
- decoded: (value: (context: any) => unknown) => wrap((context) => context.target.decoded(value(context))),
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 = (selection: Topology.TargetSelection): Topology.TargetSelection =>
235
+ const decorateTransitionSelection = (
236
+ selection: Topology.TargetSelection,
237
+ reenterSource = false
238
+ ): Topology.TargetSelection =>
215
239
  Object.freeze({
216
240
  ...selection,
217
- guard: (predicate: (context: any) => boolean) => guardedSelection(selection, predicate),
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(selection, (context) => context.target.from(value(context)), undefined),
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(selection, (context) => context.target.decoded(value(context)), undefined),
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: () => makeDirectTransitionDescriptor(selection, undefined, { reenter: true }),
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
- const selector = {
331
- ...decorateTransitionSelectorNode(makeTargetSelector(stateNodes, source)) as Record<string, unknown>
332
- }
333
- selector.branches = (declarations: unknown) =>
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.reenter,
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.reenter,
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 selected.resolve(resolve, "reenter" in transition ? { reenter: transition.reenter } : undefined)
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