@typeonce/effect-machine 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +21 -0
  2. package/dist/Machine.d.ts +159 -140
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +3 -1
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +8 -2
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js.map +1 -1
  9. package/dist/internal/machine/configuration.d.ts.map +1 -1
  10. package/dist/internal/machine/configuration.js +103 -20
  11. package/dist/internal/machine/configuration.js.map +1 -1
  12. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  13. package/dist/internal/machine/executionPlan.js +6 -0
  14. package/dist/internal/machine/executionPlan.js.map +1 -1
  15. package/dist/internal/machine/invocation.js +1 -1
  16. package/dist/internal/machine/invocation.js.map +1 -1
  17. package/dist/internal/machine/machine.d.ts.map +1 -1
  18. package/dist/internal/machine/machine.js +22 -16
  19. package/dist/internal/machine/machine.js.map +1 -1
  20. package/dist/internal/machine/planner.d.ts +10 -0
  21. package/dist/internal/machine/planner.d.ts.map +1 -1
  22. package/dist/internal/machine/planner.js +45 -35
  23. package/dist/internal/machine/planner.js.map +1 -1
  24. package/dist/internal/machine/serialization.d.ts.map +1 -1
  25. package/dist/internal/machine/serialization.js +53 -21
  26. package/dist/internal/machine/serialization.js.map +1 -1
  27. package/dist/internal/machine/stateDefinition.d.ts +4 -4
  28. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  29. package/dist/internal/machine/stateDefinition.js +18 -11
  30. package/dist/internal/machine/stateDefinition.js.map +1 -1
  31. package/dist/internal/machine/topology.d.ts +5 -5
  32. package/dist/internal/machine/topology.d.ts.map +1 -1
  33. package/dist/internal/machine/topology.js +16 -13
  34. package/dist/internal/machine/topology.js.map +1 -1
  35. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  36. package/dist/internal/testing/machine/verification.js +10 -3
  37. package/dist/internal/testing/machine/verification.js.map +1 -1
  38. package/dist/unstable/reactivity/AtomMachine.d.ts +8 -2
  39. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  40. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  41. package/docs/agent-guide.md +56 -28
  42. package/package.json +2 -2
  43. package/src/Machine.ts +371 -296
  44. package/src/internal/machine/atom.ts +10 -3
  45. package/src/internal/machine/configuration.ts +102 -23
  46. package/src/internal/machine/executionPlan.ts +6 -0
  47. package/src/internal/machine/invocation.ts +1 -1
  48. package/src/internal/machine/machine.ts +85 -57
  49. package/src/internal/machine/planner.ts +49 -38
  50. package/src/internal/machine/serialization.ts +56 -31
  51. package/src/internal/machine/stateDefinition.ts +22 -11
  52. package/src/internal/machine/topology.ts +21 -18
  53. package/src/internal/testing/machine/verification.ts +13 -3
  54. package/src/unstable/reactivity/AtomMachine.ts +9 -2
@@ -18,7 +18,6 @@ import {
18
18
  configurationFromHistoryRecord,
19
19
  getActiveLeafPathFrom,
20
20
  getActiveLeafPaths,
21
- getActiveValue,
22
21
  getHistoryRecord,
23
22
  getInitialEntryPaths,
24
23
  getLeafPath,
@@ -201,66 +200,76 @@ const completeHistoryConfiguration = (
201
200
  if (node.initial === undefined) {
202
201
  throw new Error(`Machine shallow history expected compound state "${path}" to have an initial child`)
203
202
  }
204
- const initializer = machine.handlers[path]?.initial
205
- if (initializer === undefined) {
206
- throw new Error(`Machine shallow history requires an initial value implementation for state "${path}"`)
207
- }
203
+ const child = getNode(machine, node.initial)
208
204
  const current = {
209
205
  active,
210
206
  values,
211
207
  outputs: configuration.outputs,
212
208
  history: configuration.history
213
209
  } as ActiveConfiguration
214
- const initialized = collectStateInitializer(machine, initializer, {
215
- state: getActiveValue(current, path),
216
- parent: getParentValue(machine, current, path),
217
- parents: getParentValues(machine, current, path),
218
- event
219
- })
220
- const child = getNode(machine, node.initial)
221
210
  active.add(child.path)
222
- values.set(child.path, decodeStateValueSync(machine, child, initialized.value))
223
- commands.push(...initialized.commands)
224
- raisedEvents.push(...initialized.raisedEvents)
225
- emittedEvents.push(...initialized.emittedEvents)
211
+ if (child.schema !== undefined) {
212
+ const initializer = machine.handlers[path]?.initial
213
+ if (initializer === undefined) {
214
+ throw new Error(`Machine shallow history requires an initial value implementation for state "${path}"`)
215
+ }
216
+ const initialized = collectStateInitializer(machine, initializer, {
217
+ state: current.values.get(path),
218
+ parent: getParentValue(machine, current, path),
219
+ parents: getParentValues(machine, current, path),
220
+ event
221
+ })
222
+ values.set(child.path, decodeStateValueSync(machine, child, initialized.value))
223
+ commands.push(...initialized.commands)
224
+ raisedEvents.push(...initialized.raisedEvents)
225
+ emittedEvents.push(...initialized.emittedEvents)
226
+ }
226
227
  changed = true
227
228
  }
228
229
  if (node.type === "parallel") {
229
230
  const missing = node.children.filter((childPath) => !active.has(childPath))
230
231
  if (missing.length > 0) {
231
- const initializer = machine.handlers[path]?.initial
232
- if (initializer === undefined) {
233
- throw new Error(`Machine shallow history requires an initial value implementation for state "${path}"`)
234
- }
232
+ const valuedMissing = missing.filter((childPath) => getNode(machine, childPath).schema !== undefined)
235
233
  const current = {
236
234
  active,
237
235
  values,
238
236
  outputs: configuration.outputs,
239
237
  history: configuration.history
240
238
  } as ActiveConfiguration
241
- const initialized = collectStateInitializer(machine, initializer, {
242
- state: getActiveValue(current, path),
239
+ const initializer = valuedMissing.length === 0 ? undefined : machine.handlers[path]?.initial
240
+ if (valuedMissing.length > 0 && initializer === undefined) {
241
+ throw new Error(`Machine shallow history requires an initial value implementation for state "${path}"`)
242
+ }
243
+ const initialized = initializer === undefined ? undefined : collectStateInitializer(machine, initializer, {
244
+ state: current.values.get(path),
243
245
  parent: getParentValue(machine, current, path),
244
246
  parents: getParentValues(machine, current, path),
245
247
  event
246
248
  })
247
- if (typeof initialized.value !== "object" || initialized.value === null) {
249
+ if (initialized !== undefined && (typeof initialized.value !== "object" || initialized.value === null)) {
248
250
  throw new Error(`Machine parallel state initializer for "${path}" must return its region values`)
249
251
  }
250
252
  for (const childPath of missing) {
251
253
  const child = getNode(machine, childPath)
252
- if (!Object.prototype.hasOwnProperty.call(initialized.value, child.key)) {
253
- throw new Error(`Machine parallel state initializer for "${path}" must return region "${child.key}"`)
254
- }
255
254
  active.add(child.path)
256
- values.set(
257
- child.path,
258
- decodeStateValueSync(machine, child, (initialized.value as Record<string, unknown>)[child.key])
259
- )
255
+ if (child.schema !== undefined) {
256
+ if (
257
+ initialized === undefined ||
258
+ !Object.prototype.hasOwnProperty.call(initialized.value as object, child.key)
259
+ ) {
260
+ throw new Error(`Machine parallel state initializer for "${path}" must return region "${child.key}"`)
261
+ }
262
+ values.set(
263
+ child.path,
264
+ decodeStateValueSync(machine, child, (initialized.value as Record<string, unknown>)[child.key])
265
+ )
266
+ }
267
+ }
268
+ if (initialized !== undefined) {
269
+ commands.push(...initialized.commands)
270
+ raisedEvents.push(...initialized.raisedEvents)
271
+ emittedEvents.push(...initialized.emittedEvents)
260
272
  }
261
- commands.push(...initialized.commands)
262
- raisedEvents.push(...initialized.raisedEvents)
263
- emittedEvents.push(...initialized.emittedEvents)
264
273
  changed = true
265
274
  }
266
275
  }
@@ -495,7 +504,7 @@ const makeStateActionContext = <
495
504
  path: string,
496
505
  event: Machine.LifecycleEvent<Events>
497
506
  ): Machine.StateActionContext<States, Events, Emits, StateId> => ({
498
- state: getActiveValue(configuration, path) as Machine.StateByIdentifier<States, StateId>,
507
+ state: configuration.values.get(path) as Machine.StateByIdentifier<States, StateId>,
499
508
  parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<States, StateId>,
500
509
  parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<States, StateId>,
501
510
  event
@@ -514,7 +523,7 @@ const makeTransitionContext = <
514
523
  event: Machine.EventByTag<Events, EventTag>,
515
524
  snapshot: Machine.Snapshot<States>
516
525
  ): Machine.HandlerContext<States, Events, Emits, StateId, EventTag, any, any> => ({
517
- state: getActiveValue(configuration, path) as Machine.StateByIdentifier<States, StateId>,
526
+ state: configuration.values.get(path) as Machine.StateByIdentifier<States, StateId>,
518
527
  parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<States, StateId>,
519
528
  parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<States, StateId>,
520
529
  event,
@@ -535,7 +544,7 @@ const makeDoneContext = <
535
544
  output: unknown,
536
545
  snapshot: Machine.Snapshot<States>
537
546
  ): Machine.DoneContext<States, Events, Emits, StateId> => ({
538
- state: getActiveValue(configuration, path) as Machine.StateByIdentifier<States, StateId>,
547
+ state: configuration.values.get(path) as Machine.StateByIdentifier<States, StateId>,
539
548
  parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<States, StateId>,
540
549
  parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<States, StateId>,
541
550
  event,
@@ -629,7 +638,7 @@ const selectAlwaysTransitions = <
629
638
  Machine.AlwaysContext<States, Events, Emits, Machine.StateIdentifier<States>>
630
639
  >,
631
640
  context: {
632
- state: getActiveValue(configuration, path) as Machine.StateByIdentifier<
641
+ state: configuration.values.get(path) as Machine.StateByIdentifier<
633
642
  States,
634
643
  Machine.StateIdentifier<States>
635
644
  >,
@@ -919,7 +928,9 @@ const choicesFromTarget = (
919
928
  return
920
929
  }
921
930
  if (typeof current !== "object" || current === null || !("path" in current) || !("value" in current)) return
922
- values[String(current.path)] = current.value
931
+ if (current.value !== undefined) {
932
+ values[String(current.path)] = current.value
933
+ }
923
934
  if ("state" in current) visit(current.state)
924
935
  if ("states" in current && typeof current.states === "object" && current.states !== null) {
925
936
  for (const state of Object.values(current.states)) visit(state)
@@ -31,7 +31,7 @@ const EncodedSnapshotSchema = Schema.Struct({
31
31
  _tag: Schema.Literal("MachineSnapshot"),
32
32
  active: Schema.Array(Schema.Struct({
33
33
  path: Schema.String,
34
- value: Schema.Unknown
34
+ value: Schema.optional(Schema.Unknown)
35
35
  })),
36
36
  completed: Schema.optional(Schema.Array(Schema.Struct({
37
37
  path: Schema.String,
@@ -220,13 +220,17 @@ export const encodeSnapshot = (
220
220
  const path of Array.from(configuration.active).sort((left, right) => compareDocumentOrder(machine, left, right))
221
221
  ) {
222
222
  const node = getNode(machine, path)
223
- active.push({
224
- path,
225
- value: yield* encodeBoundary(machine, getStateNodeSchema(node), getActiveValue(configuration, path), {
226
- boundary: "state",
227
- state: path
223
+ if (node.schema === undefined) {
224
+ active.push({ path })
225
+ } else {
226
+ active.push({
227
+ path,
228
+ value: yield* encodeBoundary(machine, getStateNodeSchema(node), getActiveValue(configuration, path), {
229
+ boundary: "state",
230
+ state: path
231
+ })
228
232
  })
229
- })
233
+ }
230
234
  }
231
235
 
232
236
  const completed: Array<Machine.EncodedSnapshotCompletion> = []
@@ -290,7 +294,6 @@ export const encodeSnapshot = (
290
294
  const stateNode = machine.stateNodes.byPath.get(path)
291
295
  if (
292
296
  stateNode === undefined || stateNode.type === "history" || stateNode.type === "choice" ||
293
- !record.values.has(path) ||
294
297
  !(isPathInSubtree(path, record.parent) || getPathToRoot(machine, record.parent).includes(path))
295
298
  ) {
296
299
  return yield* Effect.fail(
@@ -302,14 +305,23 @@ export const encodeSnapshot = (
302
305
  })
303
306
  )
304
307
  }
305
- encodedValues[path] = yield* encodeBoundary(
306
- machine,
307
- getStateNodeSchema(stateNode),
308
- record.values.get(path),
309
- { boundary: "history", state: path }
310
- )
308
+ if (stateNode.schema === undefined) {
309
+ if (record.values.has(path)) {
310
+ throw new Error(`Machine history record contains a value for structural state "${path}"`)
311
+ }
312
+ } else {
313
+ if (!record.values.has(path)) {
314
+ throw new Error(`Machine history record omits value for "${path}"`)
315
+ }
316
+ encodedValues[path] = yield* encodeBoundary(
317
+ machine,
318
+ getStateNodeSchema(stateNode),
319
+ record.values.get(path),
320
+ { boundary: "history", state: path }
321
+ )
322
+ }
311
323
  }
312
- if (record.values.size !== record.active.size) {
324
+ if (Object.keys(encodedValues).length !== record.values.size) {
313
325
  return yield* Effect.fail(
314
326
  new MachineSchemaEncodeError({
315
327
  machineId: machine.id,
@@ -356,13 +368,19 @@ export const decodeSnapshot = (
356
368
  }
357
369
  const node = getNode(machine, entry.path)
358
370
  active.add(entry.path)
359
- values.set(
360
- entry.path,
361
- yield* decodeEncodedBoundary(machine, getStateNodeSchema(node), entry.value, {
362
- boundary: "state",
363
- state: entry.path
364
- })
365
- )
371
+ const hasValue = Object.prototype.hasOwnProperty.call(entry, "value")
372
+ if (node.schema === undefined) {
373
+ if (hasValue) throw new Error(`Machine encoded snapshot contains a value for structural state "${entry.path}"`)
374
+ } else {
375
+ if (!hasValue) throw new Error(`Machine encoded snapshot omits value for state "${entry.path}"`)
376
+ values.set(
377
+ entry.path,
378
+ yield* decodeEncodedBoundary(machine, getStateNodeSchema(node), entry.value, {
379
+ boundary: "state",
380
+ state: entry.path
381
+ })
382
+ )
383
+ }
366
384
  }
367
385
 
368
386
  const history = new Map<string, HistoryRecord>()
@@ -397,7 +415,6 @@ export const decodeSnapshot = (
397
415
  const stateNode = machine.stateNodes.byPath.get(path)
398
416
  if (
399
417
  stateNode === undefined || stateNode.type === "history" || stateNode.type === "choice" ||
400
- !Object.prototype.hasOwnProperty.call(encodedRecord.values, path) ||
401
418
  !(isPathInSubtree(path, historyNode.parent) || getPathToRoot(machine, historyNode.parent).includes(path))
402
419
  ) {
403
420
  return yield* Effect.fail(
@@ -410,15 +427,23 @@ export const decodeSnapshot = (
410
427
  )
411
428
  }
412
429
  rememberedActive.add(path)
413
- rememberedValues.set(
414
- path,
415
- yield* decodeEncodedBoundary(machine, getStateNodeSchema(stateNode), encodedRecord.values[path], {
416
- boundary: "history",
417
- state: path
418
- })
419
- )
430
+ const hasValue = Object.prototype.hasOwnProperty.call(encodedRecord.values, path)
431
+ if (stateNode.schema === undefined) {
432
+ if (hasValue) {
433
+ throw new Error(`Machine encoded history contains a value for structural state "${path}"`)
434
+ }
435
+ } else {
436
+ if (!hasValue) throw new Error(`Machine encoded history omits value for state "${path}"`)
437
+ rememberedValues.set(
438
+ path,
439
+ yield* decodeEncodedBoundary(machine, getStateNodeSchema(stateNode), encodedRecord.values[path], {
440
+ boundary: "history",
441
+ state: path
442
+ })
443
+ )
444
+ }
420
445
  }
421
- if (Object.keys(encodedRecord.values).length !== rememberedActive.size) {
446
+ if (Object.keys(encodedRecord.values).length !== rememberedValues.size) {
422
447
  return yield* Effect.fail(
423
448
  new MachineSchemaDecodeError({
424
449
  machineId: machine.id,
@@ -2,10 +2,10 @@ import * as Schema from "effect/Schema"
2
2
  import * as SchemaAST from "effect/SchemaAST"
3
3
 
4
4
  export const StateNodePropertyPolicy = {
5
- atomic: ["schema", "type"],
6
- final: ["schema", "type", "output"],
7
- compound: ["schema", "type", "initial", "states"],
8
- parallel: ["schema", "type", "output", "states"],
5
+ atomic: ["schema", "type", "annotations"],
6
+ final: ["schema", "type", "output", "annotations"],
7
+ compound: ["schema", "type", "initial", "states", "annotations"],
8
+ parallel: ["schema", "type", "output", "states", "annotations"],
9
9
  history: ["type", "history", "annotations"],
10
10
  choice: ["type", "annotations"]
11
11
  } as const
@@ -152,18 +152,22 @@ const assertAllowedProperties = (
152
152
  }
153
153
  }
154
154
 
155
- const validatePseudoAnnotations = (
155
+ const validateSchemaLessAnnotations = (
156
156
  boundary: StateDefinitionBoundary,
157
157
  path: string,
158
158
  annotations: unknown
159
159
  ): void => {
160
- assertPlainRecord(boundary, `${path}.annotations`, annotations, "pseudo-state annotations")
160
+ assertPlainRecord(boundary, `${path}.annotations`, annotations, "schema-less state annotations")
161
161
  for (const property of Reflect.ownKeys(annotations)) {
162
162
  if (!(PseudoStateAnnotationProperties as ReadonlyArray<PropertyKey>).includes(property)) {
163
- fail(boundary, `${path}.annotations`, `pseudo-state annotations cannot declare property "${String(property)}"`)
163
+ fail(
164
+ boundary,
165
+ `${path}.annotations`,
166
+ `schema-less state annotations cannot declare property "${String(property)}"`
167
+ )
164
168
  }
165
169
  if (typeof annotations[property] !== "string") {
166
- fail(boundary, `${path}.annotations.${String(property)}`, "pseudo-state annotation values must be strings")
170
+ fail(boundary, `${path}.annotations.${String(property)}`, "schema-less state annotation values must be strings")
167
171
  }
168
172
  }
169
173
  }
@@ -211,7 +215,7 @@ const validateStateTree = (
211
215
  fail(boundary, path, `${kind} states must be declared below an active parent state`)
212
216
  }
213
217
  if (hasOwn(node, "annotations")) {
214
- validatePseudoAnnotations(boundary, path, node.annotations)
218
+ validateSchemaLessAnnotations(boundary, path, node.annotations)
215
219
  }
216
220
  if (kind === "history" && hasOwn(node, "history") && node.history !== "shallow" && node.history !== "deep") {
217
221
  fail(boundary, `${path}.history`, "history must be \"shallow\" or \"deep\"")
@@ -219,8 +223,15 @@ const validateStateTree = (
219
223
  continue
220
224
  }
221
225
 
222
- if (!hasOwn(node, "schema") || !isTaggedSchema(node.schema)) {
223
- fail(boundary, `${path}.schema`, "active states must declare an Effect Schema with a required PropertyKey _tag")
226
+ const valued = hasOwn(node, "schema")
227
+ if (valued && !isTaggedSchema(node.schema)) {
228
+ fail(boundary, `${path}.schema`, "state schemas must decode to an object with a required PropertyKey _tag")
229
+ }
230
+ if (valued && hasOwn(node, "annotations")) {
231
+ fail(boundary, `${path}.annotations`, "schema-backed states must declare annotations on their schema")
232
+ }
233
+ if (!valued && hasOwn(node, "annotations")) {
234
+ validateSchemaLessAnnotations(boundary, path, node.annotations)
224
235
  }
225
236
  if (kind === "atomic" && hasOwn(node, "type") && node.type !== "active") {
226
237
  fail(boundary, `${path}.type`, "atomic state type must be \"active\"")
@@ -70,7 +70,7 @@ interface NormalizedStateNodeDefinitionBase {
70
70
  type NormalizedStateNodeDefinition =
71
71
  | (NormalizedStateNodeDefinitionBase & {
72
72
  readonly type: "atomic"
73
- readonly schema: Machine.TaggedSchema
73
+ readonly schema: Machine.TaggedSchema | undefined
74
74
  readonly output: undefined
75
75
  readonly history: undefined
76
76
  readonly initial: undefined
@@ -78,7 +78,7 @@ type NormalizedStateNodeDefinition =
78
78
  })
79
79
  | (NormalizedStateNodeDefinitionBase & {
80
80
  readonly type: "compound"
81
- readonly schema: Machine.TaggedSchema
81
+ readonly schema: Machine.TaggedSchema | undefined
82
82
  readonly output: undefined
83
83
  readonly history: undefined
84
84
  readonly initial: string
@@ -86,7 +86,7 @@ type NormalizedStateNodeDefinition =
86
86
  })
87
87
  | (NormalizedStateNodeDefinitionBase & {
88
88
  readonly type: "parallel"
89
- readonly schema: Machine.TaggedSchema
89
+ readonly schema: Machine.TaggedSchema | undefined
90
90
  readonly output: Schema.Top | undefined
91
91
  readonly history: undefined
92
92
  readonly initial: undefined
@@ -94,7 +94,7 @@ type NormalizedStateNodeDefinition =
94
94
  })
95
95
  | (NormalizedStateNodeDefinitionBase & {
96
96
  readonly type: "final"
97
- readonly schema: Machine.TaggedSchema
97
+ readonly schema: Machine.TaggedSchema | undefined
98
98
  readonly output: Schema.Top | undefined
99
99
  readonly history: undefined
100
100
  readonly initial: undefined
@@ -154,9 +154,10 @@ export const getStateNodeDefinition = (
154
154
  states: undefined
155
155
  }
156
156
  }
157
- if (!hasProperty(definition, "schema") || !Schema.isSchema(definition.schema)) {
158
- throw new Error(`Machine.make expected state "${path}" to be a tagged schema or state node config`)
159
- }
157
+ const schema = hasProperty(definition, "schema") && Schema.isSchema(definition.schema)
158
+ ? definition.schema as Machine.TaggedSchema
159
+ : undefined
160
+ const annotations = schema === undefined ? definition.annotations : Schema.resolveAnnotations(schema)
160
161
  if (definition.type === "parallel" && !hasProperty(definition, "states")) {
161
162
  throw new Error(`Machine.make expected parallel state "${path}" to declare child regions`)
162
163
  }
@@ -167,9 +168,9 @@ export const getStateNodeDefinition = (
167
168
  }
168
169
  if (definition.type === "parallel") {
169
170
  return {
170
- schema: definition.schema,
171
+ schema,
171
172
  output: Schema.isSchema(definition.output) ? definition.output : undefined,
172
- annotations: Schema.resolveAnnotations(definition.schema),
173
+ annotations,
173
174
  type: "parallel",
174
175
  history: undefined,
175
176
  initial: undefined,
@@ -180,9 +181,9 @@ export const getStateNodeDefinition = (
180
181
  throw new Error(`Machine.make expected compound state "${path}" to declare an initial child`)
181
182
  }
182
183
  return {
183
- schema: definition.schema,
184
+ schema,
184
185
  output: undefined,
185
- annotations: Schema.resolveAnnotations(definition.schema),
186
+ annotations,
186
187
  type: "compound",
187
188
  history: undefined,
188
189
  initial: definition.initial,
@@ -192,18 +193,18 @@ export const getStateNodeDefinition = (
192
193
  const output = Schema.isSchema(definition.output) ? definition.output : undefined
193
194
  return definition.type === "final"
194
195
  ? {
195
- schema: definition.schema,
196
+ schema,
196
197
  output,
197
- annotations: Schema.resolveAnnotations(definition.schema),
198
+ annotations,
198
199
  type: "final",
199
200
  history: undefined,
200
201
  initial: undefined,
201
202
  states: undefined
202
203
  }
203
204
  : {
204
- schema: definition.schema,
205
+ schema,
205
206
  output: undefined,
206
- annotations: Schema.resolveAnnotations(definition.schema),
207
+ annotations,
207
208
  type: "atomic",
208
209
  history: undefined,
209
210
  initial: undefined,
@@ -405,7 +406,7 @@ export const makeTarget = <
405
406
  readonly snapshot?: Machine.SnapshotByIdentifier<States, StateId>
406
407
  readonly values?: Partial<
407
408
  {
408
- readonly [AncestorStateId in Machine.StateIdentifier<States>]: Machine.StateByIdentifier<
409
+ readonly [AncestorStateId in Machine.ValuedStateIdentifier<States>]: Machine.StateByIdentifier<
409
410
  States,
410
411
  AncestorStateId
411
412
  >
@@ -445,7 +446,9 @@ export const getSnapshotByPath = (
445
446
  return Option.none()
446
447
  }
447
448
  if (parents !== undefined) {
448
- parents[snapshot.path] = snapshot.value
449
+ if (snapshot.value !== undefined) {
450
+ parents[snapshot.path] = snapshot.value
451
+ }
449
452
  }
450
453
  if (hasProperty(snapshot, "state") && isSnapshot(snapshot.state)) {
451
454
  return getSnapshotByPath(snapshot.state, path, parents)
@@ -473,7 +476,7 @@ export const getNode = (machine: Machine.Any, path: string): Machine.StateNode =
473
476
 
474
477
  export const getStateNodeSchema = (node: Machine.StateNode): Machine.TaggedSchema => {
475
478
  if (node.schema === undefined) {
476
- throw new Error(`Machine pseudo-state "${node.path}" has no active value schema`)
479
+ throw new Error(`Machine state "${node.path}" has no value schema`)
477
480
  }
478
481
  return node.schema
479
482
  }
@@ -1114,7 +1114,9 @@ export const verify = <M extends AnyMachine>(
1114
1114
  }
1115
1115
  return
1116
1116
  }
1117
- if (reportConfiguration && !schemaMatches(node.schema, current.value)) {
1117
+ if (reportConfiguration && node.schema === undefined && current.value !== undefined) {
1118
+ add("configuration.schema", location, `${label} structural state "${path}" contains a value`, path)
1119
+ } else if (reportConfiguration && node.schema !== undefined && !schemaMatches(node.schema, current.value)) {
1118
1120
  add("configuration.schema", location, `${label} value for "${path}" does not match its schema`, path)
1119
1121
  }
1120
1122
 
@@ -1284,9 +1286,17 @@ export const verify = <M extends AnyMachine>(
1284
1286
  path
1285
1287
  )
1286
1288
  }
1287
- if (!hasOwn(entry.values, path)) {
1289
+ const hasValue = hasOwn(entry.values, path)
1290
+ if (node.schema === undefined && hasValue) {
1291
+ add(
1292
+ "history.value",
1293
+ location,
1294
+ `${label} history record "${historyPath}" values structural state "${path}"`,
1295
+ path
1296
+ )
1297
+ } else if (node.schema !== undefined && !hasValue) {
1288
1298
  add("history.value", location, `${label} history record "${historyPath}" omits value for "${path}"`, path)
1289
- } else if (!schemaMatches(node.schema, entry.values[path])) {
1299
+ } else if (node.schema !== undefined && !schemaMatches(node.schema, entry.values[path])) {
1290
1300
  add(
1291
1301
  "history.value",
1292
1302
  location,
@@ -291,6 +291,13 @@ type SnapshotIdentifier<State> = SnapshotNode<State> extends infer Node ?
291
291
  Node extends { readonly path: infer Path extends string } ? Path : never
292
292
  : never
293
293
 
294
+ type ValuedSnapshotIdentifier<State> = SnapshotNode<State> extends infer Node ?
295
+ Node extends { readonly path: infer Path extends string; readonly value: infer Value } ?
296
+ [Value] extends [undefined] ? never
297
+ : Path
298
+ : never
299
+ : never
300
+
294
301
  type SnapshotValueByIdentifier<State, Path extends SnapshotIdentifier<State>> = SnapshotNode<State> extends infer Node ?
295
302
  Node extends { readonly path: Path; readonly value: infer Value } ? Value : never
296
303
  : never
@@ -338,7 +345,7 @@ export const select: <
338
345
  Error,
339
346
  Output,
340
347
  StartError,
341
- const Path extends SnapshotIdentifier<State>
348
+ const Path extends ValuedSnapshotIdentifier<State>
342
349
  >(self: MachineAtom<State, Event, Error, Output, StartError>, path: Path) => Atom.Atom<
343
350
  AsyncResult.AsyncResult<Option.Option<SnapshotValueByIdentifier<State, Path>>, StartError | Error>
344
351
  > = internal.select
@@ -384,7 +391,7 @@ export const selectSnapshot: <
384
391
  export const selectChild: <
385
392
  Child extends Machine.ChildMachine.Any,
386
393
  StartError,
387
- const Path extends SnapshotIdentifier<ChildState<Child>>
394
+ const Path extends ValuedSnapshotIdentifier<ChildState<Child>>
388
395
  >(self: ChildMachineAtom<Child, StartError>, path: Path) => Atom.Atom<
389
396
  AsyncResult.AsyncResult<
390
397
  Option.Option<SnapshotValueByIdentifier<ChildState<Child>, Path>>,