@typeonce/effect-machine 0.8.0 → 0.10.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 (108) hide show
  1. package/README.md +180 -40
  2. package/dist/Machine.d.ts +659 -481
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +86 -222
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +5 -12
  7. package/dist/internal/machine/activities.d.ts.map +1 -1
  8. package/dist/internal/machine/activities.js +47 -17
  9. package/dist/internal/machine/activities.js.map +1 -1
  10. package/dist/internal/machine/atom.d.ts +6 -2
  11. package/dist/internal/machine/atom.d.ts.map +1 -1
  12. package/dist/internal/machine/atom.js +5 -0
  13. package/dist/internal/machine/atom.js.map +1 -1
  14. package/dist/internal/machine/cluster.d.ts.map +1 -1
  15. package/dist/internal/machine/cluster.js +2 -1
  16. package/dist/internal/machine/cluster.js.map +1 -1
  17. package/dist/internal/machine/command.js +2 -2
  18. package/dist/internal/machine/command.js.map +1 -1
  19. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  20. package/dist/internal/machine/commandRuntime.js +6 -3
  21. package/dist/internal/machine/commandRuntime.js.map +1 -1
  22. package/dist/internal/machine/configuration.d.ts +8 -1
  23. package/dist/internal/machine/configuration.d.ts.map +1 -1
  24. package/dist/internal/machine/configuration.js +9 -4
  25. package/dist/internal/machine/configuration.js.map +1 -1
  26. package/dist/internal/machine/errors.d.ts +1 -1
  27. package/dist/internal/machine/errors.d.ts.map +1 -1
  28. package/dist/internal/machine/executionPlan.d.ts +3 -3
  29. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  30. package/dist/internal/machine/executionPlan.js +74 -24
  31. package/dist/internal/machine/executionPlan.js.map +1 -1
  32. package/dist/internal/machine/invocation.d.ts +10 -2
  33. package/dist/internal/machine/invocation.d.ts.map +1 -1
  34. package/dist/internal/machine/invocation.js +94 -34
  35. package/dist/internal/machine/invocation.js.map +1 -1
  36. package/dist/internal/machine/invocationEvent.d.ts +39 -0
  37. package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
  38. package/dist/internal/machine/invocationEvent.js +43 -0
  39. package/dist/internal/machine/invocationEvent.js.map +1 -0
  40. package/dist/internal/machine/machine.d.ts +16 -65
  41. package/dist/internal/machine/machine.d.ts.map +1 -1
  42. package/dist/internal/machine/machine.js +18 -73
  43. package/dist/internal/machine/machine.js.map +1 -1
  44. package/dist/internal/machine/planner.d.ts +4 -2
  45. package/dist/internal/machine/planner.d.ts.map +1 -1
  46. package/dist/internal/machine/planner.js +104 -24
  47. package/dist/internal/machine/planner.js.map +1 -1
  48. package/dist/internal/machine/process.d.ts.map +1 -1
  49. package/dist/internal/machine/process.js +8 -8
  50. package/dist/internal/machine/process.js.map +1 -1
  51. package/dist/internal/machine/protocol.d.ts +8 -5
  52. package/dist/internal/machine/protocol.d.ts.map +1 -1
  53. package/dist/internal/machine/protocol.js +207 -15
  54. package/dist/internal/machine/protocol.js.map +1 -1
  55. package/dist/internal/machine/runtime.d.ts +8 -2
  56. package/dist/internal/machine/runtime.d.ts.map +1 -1
  57. package/dist/internal/machine/runtime.js +86 -8
  58. package/dist/internal/machine/runtime.js.map +1 -1
  59. package/dist/internal/machine/symbols.d.ts +2 -0
  60. package/dist/internal/machine/symbols.d.ts.map +1 -1
  61. package/dist/internal/machine/symbols.js +2 -0
  62. package/dist/internal/machine/symbols.js.map +1 -1
  63. package/dist/internal/machine/topology.d.ts +7 -0
  64. package/dist/internal/machine/topology.d.ts.map +1 -1
  65. package/dist/internal/machine/topology.js +29 -0
  66. package/dist/internal/machine/topology.js.map +1 -1
  67. package/dist/internal/testing/machine/finiteModel.js +1 -1
  68. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  69. package/dist/internal/testing/machine/runtime.d.ts.map +1 -1
  70. package/dist/internal/testing/machine/runtime.js +2 -1
  71. package/dist/internal/testing/machine/runtime.js.map +1 -1
  72. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  73. package/dist/internal/testing/machine/verification.js +5 -3
  74. package/dist/internal/testing/machine/verification.js.map +1 -1
  75. package/dist/testing/MachineTest.d.ts +7 -7
  76. package/dist/testing/MachineTest.js +7 -7
  77. package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
  78. package/dist/unstable/cluster/ClusterMachine.js +1 -1
  79. package/dist/unstable/reactivity/AtomMachine.d.ts +25 -8
  80. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  81. package/dist/unstable/reactivity/AtomMachine.js +18 -3
  82. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  83. package/docs/agent-guide.md +242 -122
  84. package/package.json +2 -2
  85. package/src/Machine.ts +1453 -812
  86. package/src/internal/machine/activities.ts +48 -33
  87. package/src/internal/machine/atom.ts +38 -13
  88. package/src/internal/machine/cluster.ts +2 -1
  89. package/src/internal/machine/command.ts +2 -2
  90. package/src/internal/machine/commandRuntime.ts +9 -4
  91. package/src/internal/machine/configuration.ts +23 -5
  92. package/src/internal/machine/errors.ts +1 -1
  93. package/src/internal/machine/executionPlan.ts +102 -28
  94. package/src/internal/machine/invocation.ts +182 -50
  95. package/src/internal/machine/invocationEvent.ts +72 -0
  96. package/src/internal/machine/machine.ts +80 -387
  97. package/src/internal/machine/planner.ts +127 -32
  98. package/src/internal/machine/process.ts +18 -8
  99. package/src/internal/machine/protocol.ts +313 -25
  100. package/src/internal/machine/runtime.ts +135 -9
  101. package/src/internal/machine/symbols.ts +3 -0
  102. package/src/internal/machine/topology.ts +38 -0
  103. package/src/internal/testing/machine/finiteModel.ts +1 -1
  104. package/src/internal/testing/machine/runtime.ts +2 -1
  105. package/src/internal/testing/machine/verification.ts +5 -3
  106. package/src/testing/MachineTest.ts +7 -7
  107. package/src/unstable/cluster/ClusterMachine.ts +1 -1
  108. package/src/unstable/reactivity/AtomMachine.ts +45 -13
@@ -9,12 +9,13 @@ import * as Effect from "effect/Effect"
9
9
  import { hasProperty } from "effect/Predicate"
10
10
  import * as Result from "effect/Result"
11
11
  import * as Schema from "effect/Schema"
12
+ import * as SchemaAST from "effect/SchemaAST"
12
13
  import type { Machine } from "../../Machine.js"
13
14
  import { MachineSchemaDecodeError } from "./errors.js"
14
15
  import { getStateNodeSchema, isStateInput } from "./topology.js"
15
16
 
16
17
  export interface DecodeBoundaryOptions {
17
- readonly boundary: "input" | "event" | "emit" | "state" | "output" | "history" | "configuration"
18
+ readonly boundary: "input" | "event" | "emission" | "state" | "output" | "history" | "configuration"
18
19
  readonly state?: string
19
20
  readonly event?: string
20
21
  }
@@ -24,8 +25,33 @@ interface MachineProtocolSchemas {
24
25
  readonly emit: Schema.Top
25
26
  readonly eventConstructors: ReadonlySet<object>
26
27
  readonly trustedEvents: WeakSet<object>
28
+ readonly emissionConstructors: ReadonlySet<object>
29
+ readonly trustedEmissions: WeakSet<object>
27
30
  }
28
31
 
32
+ interface EventProtocolDefinition {
33
+ readonly kind: Machine.EventProtocolKind
34
+ readonly schemas: ReadonlyArray<Machine.TaggedSchema>
35
+ }
36
+
37
+ const eventProtocolDefinitions = new WeakMap<object, EventProtocolDefinition>()
38
+
39
+ export const EventConstructionTypeId: unique symbol = Symbol("effect/Machine/EventConstruction")
40
+
41
+ interface EventConstruction {
42
+ readonly [EventConstructionTypeId]: typeof EventConstructionTypeId
43
+ readonly _tag: PropertyKey
44
+ }
45
+
46
+ interface EventConstructionDefinition {
47
+ readonly kind: Machine.EventProtocolKind
48
+ readonly schema: Machine.TaggedSchema
49
+ readonly input: unknown
50
+ readonly inputError?: unknown
51
+ }
52
+
53
+ const eventConstructionDefinitions = new WeakMap<object, EventConstructionDefinition>()
54
+
29
55
  type BoundaryDecoder = (value: unknown) => Effect.Effect<unknown, Schema.SchemaError, unknown>
30
56
 
31
57
  type BoundaryResultDecoder = (value: unknown) => Result.Result<unknown, Schema.SchemaError>
@@ -95,13 +121,59 @@ const collectEventConstructors = (
95
121
  return constructors
96
122
  }
97
123
 
124
+ const getEventProtocolDefinition = (
125
+ protocol: Machine.EventProtocol.Any,
126
+ expectedKind?: Machine.EventProtocolKind
127
+ ): EventProtocolDefinition => {
128
+ const definition = eventProtocolDefinitions.get(protocol as object)
129
+ if (definition === undefined) {
130
+ throw new Error(
131
+ "Machine expected an event protocol created with Machine.events, Machine.internalEvents, or Machine.emittedEvents"
132
+ )
133
+ }
134
+ if (expectedKind !== undefined && definition.kind !== expectedKind) {
135
+ throw new Error(`Machine expected a ${expectedKind} event protocol`)
136
+ }
137
+ return definition
138
+ }
139
+
140
+ export const isEventProtocol = <Kind extends Machine.EventProtocolKind>(
141
+ value: unknown,
142
+ kind: Kind
143
+ ): value is Machine.EventProtocol.Any<Kind> =>
144
+ typeof value === "object" && value !== null && eventProtocolDefinitions.get(value)?.kind === kind
145
+
146
+ export const eventProtocolSchemas = <Schemas extends ReadonlyArray<Machine.TaggedSchema>>(
147
+ protocol: Machine.EventProtocol<Machine.EventProtocolKind, Schemas>
148
+ ): Schemas => getEventProtocolDefinition(protocol).schemas as Schemas
149
+
150
+ export const inputEventSchemas = (machine: Machine.Any): ReadonlyArray<Machine.TaggedSchema> =>
151
+ getEventProtocolDefinition(machine.events, "public").schemas
152
+
153
+ export const internalEventSchemas = (machine: Machine.Any): ReadonlyArray<Machine.TaggedSchema> =>
154
+ getEventProtocolDefinition(machine.internalEvents, "internal").schemas
155
+
156
+ export const emittedEventSchemas = (machine: Machine.Any): ReadonlyArray<Machine.TaggedSchema> =>
157
+ getEventProtocolDefinition(machine.emittedEvents, "emitted").schemas
158
+
98
159
  export const setProtocol = (machine: Machine.Any): void => {
99
- const events = [...machine.events, ...machine.internalEvents]
160
+ const inputEvents = inputEventSchemas(machine)
161
+ const localEvents = internalEventSchemas(machine)
162
+ const emittedEvents = emittedEventSchemas(machine)
163
+ const publicTags = new Set(Reflect.ownKeys(machine.events))
164
+ for (const tag of Reflect.ownKeys(machine.internalEvents)) {
165
+ if (publicTags.has(tag)) {
166
+ throw new Error(`Public and internal machine event tags must be disjoint: ${String(tag)}`)
167
+ }
168
+ }
169
+ const events = [...inputEvents, ...localEvents]
100
170
  setProtocolSchemas(machine, {
101
171
  event: Schema.Union(events),
102
- emit: Schema.Union(machine.emits),
172
+ emit: Schema.Union(emittedEvents),
103
173
  eventConstructors: collectEventConstructors(events),
104
- trustedEvents: new WeakSet()
174
+ trustedEvents: new WeakSet(),
175
+ emissionConstructors: collectEventConstructors(emittedEvents),
176
+ trustedEmissions: new WeakSet()
105
177
  })
106
178
  }
107
179
 
@@ -111,6 +183,116 @@ export const copyProtocol = (source: Machine.Any, target: Machine.Any): void =>
111
183
  export const getEventName = (event: unknown): string | undefined =>
112
184
  hasProperty(event, "_tag") ? String(event._tag) : undefined
113
185
 
186
+ const makeEventConstruction = (
187
+ kind: Machine.EventProtocolKind,
188
+ schema: Machine.TaggedSchema,
189
+ tag: PropertyKey,
190
+ input: unknown
191
+ ): EventConstruction => {
192
+ let ownedInput = input
193
+ let inputError: unknown
194
+ if (typeof input === "object" && input !== null) {
195
+ try {
196
+ ownedInput = { ...input }
197
+ } catch (cause) {
198
+ ownedInput = undefined
199
+ inputError = cause
200
+ }
201
+ }
202
+ const construction: EventConstruction = {
203
+ [EventConstructionTypeId]: EventConstructionTypeId,
204
+ _tag: tag
205
+ }
206
+ eventConstructionDefinitions.set(construction, {
207
+ kind,
208
+ schema,
209
+ input: ownedInput,
210
+ ...(inputError === undefined ? {} : { inputError })
211
+ })
212
+ return Object.freeze(construction)
213
+ }
214
+
215
+ const isEventConstruction = (value: unknown): value is EventConstruction =>
216
+ typeof value === "object" && value !== null && eventConstructionDefinitions.has(value)
217
+
218
+ const eventConstructors = (
219
+ kind: Machine.EventProtocolKind,
220
+ schemas: ReadonlyArray<Machine.TaggedSchema>
221
+ ): Readonly<Record<PropertyKey, (...args: ReadonlyArray<unknown>) => EventConstruction>> => {
222
+ const leaves: Array<Machine.TaggedSchema> = []
223
+ const collect = (schema: Machine.TaggedSchema): void => {
224
+ if (hasProperty(schema, "cases") && typeof schema.cases === "object" && schema.cases !== null) {
225
+ for (const candidate of Reflect.ownKeys(schema.cases)) {
226
+ collect(Reflect.get(schema.cases, candidate) as Machine.TaggedSchema)
227
+ }
228
+ return
229
+ }
230
+ if (hasProperty(schema, "members") && Array.isArray(schema.members)) {
231
+ for (const member of schema.members) collect(member as Machine.TaggedSchema)
232
+ return
233
+ }
234
+ leaves.push(schema)
235
+ }
236
+ for (const schema of schemas) collect(schema)
237
+ const constructors = Object.create(null) as Record<
238
+ PropertyKey,
239
+ (...args: ReadonlyArray<unknown>) => EventConstruction
240
+ >
241
+ const tags = (ast: SchemaAST.AST): ReadonlyArray<PropertyKey> => {
242
+ if (SchemaAST.isLiteral(ast)) {
243
+ return typeof ast.literal === "string" || typeof ast.literal === "number" ? [ast.literal] : []
244
+ }
245
+ if (SchemaAST.isUniqueSymbol(ast)) return [ast.symbol]
246
+ if (SchemaAST.isEnum(ast)) return ast.enums.map(([, value]) => value)
247
+ if (SchemaAST.isUnion(ast)) return ast.types.flatMap(tags)
248
+ if (SchemaAST.isSuspend(ast)) return tags(ast.thunk())
249
+ return []
250
+ }
251
+ const schemaTags = (schema: Machine.TaggedSchema): ReadonlyArray<PropertyKey> => {
252
+ const ast = SchemaAST.toType(schema.ast)
253
+ if (SchemaAST.isObjects(ast)) {
254
+ const tag = ast.propertySignatures.find(({ name }) => name === "_tag")
255
+ const discriminants = tag === undefined ? [] : tags(tag.type)
256
+ if (discriminants.length > 0) return discriminants
257
+ }
258
+ try {
259
+ return Schema.Union([schema]).pipe(Schema.toTaggedUnion("_tag")).discriminants
260
+ } catch {
261
+ return []
262
+ }
263
+ }
264
+ for (const schema of leaves) {
265
+ const discriminants = schemaTags(schema)
266
+ if (discriminants.length === 0) {
267
+ continue
268
+ }
269
+ for (const tag of discriminants) {
270
+ if (hasProperty(constructors, tag)) {
271
+ throw new Error(`Duplicate machine event constructor tag: ${String(tag)}`)
272
+ }
273
+ Object.defineProperty(constructors, tag, {
274
+ value: (...args: ReadonlyArray<unknown>) =>
275
+ makeEventConstruction(kind, schema, tag, args.length === 0 ? {} : args[0]),
276
+ enumerable: true
277
+ })
278
+ }
279
+ }
280
+ return constructors
281
+ }
282
+
283
+ export const makeEventProtocol = <
284
+ Kind extends Machine.EventProtocolKind,
285
+ Schemas extends ReadonlyArray<Machine.TaggedSchema>
286
+ >(
287
+ kind: Kind,
288
+ schemas: Schemas
289
+ ): Machine.EventProtocol<Kind, Schemas> => {
290
+ const ownedSchemas = Object.freeze(Array.from(schemas)) as unknown as Schemas
291
+ const protocol = eventConstructors(kind, ownedSchemas) as Machine.EventProtocol<Kind, Schemas>
292
+ eventProtocolDefinitions.set(protocol as object, { kind, schemas: ownedSchemas })
293
+ return Object.freeze(protocol) as Machine.EventProtocol<Kind, Schemas>
294
+ }
295
+
114
296
  export const decodeBoundary = <A>(
115
297
  machine: Machine.Any,
116
298
  schema: Schema.Top,
@@ -172,26 +354,106 @@ const makeBoundarySync = <A>(
172
354
  }
173
355
  }
174
356
 
175
- /** Constructs an event through one of the machine protocol's own schemas and
176
- * records the decoded value as trusted by that protocol. Machine clones share
177
- * the protocol record, while unrelated machines retain independent trust. */
178
- export const makeEvent = <Schema extends Machine.TaggedSchema>(
357
+ const eventConstructionProtocolError = (
179
358
  machine: Machine.Any,
180
- schema: Schema,
181
- input: unknown
182
- ): Schema["Type"] => {
183
- const protocol = getProtocolSchemas(machine)
184
- if (!protocol.eventConstructors.has(schema as object)) {
185
- throw new Error("Machine.event expected a schema from the machine event protocol")
359
+ construction: EventConstruction,
360
+ boundary: "event" | "emission"
361
+ ): MachineSchemaDecodeError =>
362
+ new MachineSchemaDecodeError({
363
+ machineId: machine.id,
364
+ boundary,
365
+ event: String(construction._tag),
366
+ cause: Cause.die(new Error(`Constructed ${boundary} schema does not belong to the machine ${boundary} protocol`))
367
+ })
368
+
369
+ const eventConstructionInput = (
370
+ construction: EventConstruction,
371
+ definition: EventConstructionDefinition
372
+ ): unknown => {
373
+ if (definition.inputError !== undefined) throw definition.inputError
374
+ return typeof definition.input === "object" && definition.input !== null
375
+ ? { ...definition.input, _tag: construction._tag }
376
+ : { _tag: construction._tag }
377
+ }
378
+
379
+ const eventConstructionInputError = (
380
+ machine: Machine.Any,
381
+ construction: EventConstruction,
382
+ boundary: "event" | "emission",
383
+ cause: unknown
384
+ ): MachineSchemaDecodeError =>
385
+ new MachineSchemaDecodeError({
386
+ machineId: machine.id,
387
+ boundary,
388
+ event: String(construction._tag),
389
+ cause: Cause.die(cause)
390
+ })
391
+
392
+ const decodeEventConstruction = (
393
+ machine: Machine.Any,
394
+ protocol: MachineProtocolSchemas,
395
+ construction: EventConstruction,
396
+ boundary: "event" | "emission"
397
+ ): Effect.Effect<unknown, MachineSchemaDecodeError> => {
398
+ const definition = eventConstructionDefinitions.get(construction)
399
+ const constructors = boundary === "event" ? protocol.eventConstructors : protocol.emissionConstructors
400
+ if (
401
+ definition === undefined ||
402
+ (boundary === "emission" ? definition.kind !== "emitted" : definition.kind === "emitted") ||
403
+ !constructors.has(definition.schema as object)
404
+ ) {
405
+ return Effect.fail(eventConstructionProtocolError(machine, construction, boundary))
186
406
  }
187
- const inputName = getEventName(input)
188
- const event = makeBoundarySync<Schema["Type"]>(
189
- machine,
190
- schema,
191
- input,
192
- inputName === undefined ? { boundary: "event" } : { boundary: "event", event: inputName }
407
+ return Effect.try({
408
+ try: () => eventConstructionInput(construction, definition),
409
+ catch: (cause) => eventConstructionInputError(machine, construction, boundary, cause)
410
+ }).pipe(
411
+ Effect.flatMap((input) =>
412
+ definition.schema.makeEffect(input as never).pipe(
413
+ Effect.mapError((cause) =>
414
+ new MachineSchemaDecodeError({
415
+ machineId: machine.id,
416
+ boundary,
417
+ event: String(construction._tag),
418
+ cause: new Schema.SchemaError(cause)
419
+ })
420
+ )
421
+ )
422
+ ),
423
+ Effect.tap((event) =>
424
+ Effect.sync(() =>
425
+ (boundary === "event" ? protocol.trustedEvents : protocol.trustedEmissions).add(event as object)
426
+ )
427
+ )
193
428
  )
194
- protocol.trustedEvents.add(event as object)
429
+ }
430
+
431
+ const decodeEventConstructionSync = (
432
+ machine: Machine.Any,
433
+ protocol: MachineProtocolSchemas,
434
+ construction: EventConstruction,
435
+ boundary: "event" | "emission"
436
+ ): unknown => {
437
+ const definition = eventConstructionDefinitions.get(construction)
438
+ const constructors = boundary === "event" ? protocol.eventConstructors : protocol.emissionConstructors
439
+ if (
440
+ definition === undefined ||
441
+ (boundary === "emission" ? definition.kind !== "emitted" : definition.kind === "emitted") ||
442
+ !constructors.has(definition.schema as object)
443
+ ) {
444
+ throw eventConstructionProtocolError(machine, construction, boundary)
445
+ }
446
+ let input: unknown
447
+ try {
448
+ input = eventConstructionInput(construction, definition)
449
+ } catch (cause) {
450
+ throw eventConstructionInputError(machine, construction, boundary, cause)
451
+ }
452
+ const event = makeBoundarySync(machine, definition.schema, input, {
453
+ boundary,
454
+ event: String(construction._tag)
455
+ })
456
+ ;(boundary === "event" ? protocol.trustedEvents : protocol.trustedEmissions).add(event as object)
195
457
  return event
196
458
  }
197
459
 
@@ -210,6 +472,12 @@ export const decodeEvent = <const Events extends ReadonlyArray<Machine.TaggedSch
210
472
  event: unknown
211
473
  ): Effect.Effect<Machine.EventOf<Events>, MachineSchemaDecodeError> => {
212
474
  const protocol = getProtocolSchemas(machine)
475
+ if (isEventConstruction(event)) {
476
+ return decodeEventConstruction(machine, protocol, event, "event") as Effect.Effect<
477
+ Machine.EventOf<Events>,
478
+ MachineSchemaDecodeError
479
+ >
480
+ }
213
481
  if (isTrustedEvent(protocol, event)) {
214
482
  return Effect.succeed(event as Machine.EventOf<Events>)
215
483
  }
@@ -227,6 +495,9 @@ export const decodeEventSync = <const Events extends ReadonlyArray<Machine.Tagge
227
495
  event: unknown
228
496
  ): Machine.EventOf<Events> => {
229
497
  const protocol = getProtocolSchemas(machine)
498
+ if (isEventConstruction(event)) {
499
+ return decodeEventConstructionSync(machine, protocol, event, "event") as Machine.EventOf<Events>
500
+ }
230
501
  if (isTrustedEvent(protocol, event)) {
231
502
  return event as Machine.EventOf<Events>
232
503
  }
@@ -243,12 +514,22 @@ export const decodeEmit = <const Emits extends ReadonlyArray<Machine.TaggedSchem
243
514
  machine: Machine.Any,
244
515
  event: unknown
245
516
  ): Effect.Effect<Machine.EmitOf<Emits>, MachineSchemaDecodeError> => {
517
+ const protocol = getProtocolSchemas(machine)
518
+ if (isEventConstruction(event)) {
519
+ return decodeEventConstruction(machine, protocol, event, "emission") as Effect.Effect<
520
+ Machine.EmitOf<Emits>,
521
+ MachineSchemaDecodeError
522
+ >
523
+ }
524
+ if (typeof event === "object" && event !== null && protocol.trustedEmissions.has(event)) {
525
+ return Effect.succeed(event as Machine.EmitOf<Emits>)
526
+ }
246
527
  const eventName = getEventName(event)
247
528
  return decodeBoundary<Machine.EmitOf<Emits>>(
248
529
  machine,
249
- getProtocolSchemas(machine).emit,
530
+ protocol.emit,
250
531
  event,
251
- eventName === undefined ? { boundary: "emit" } : { boundary: "emit", event: eventName }
532
+ eventName === undefined ? { boundary: "emission" } : { boundary: "emission", event: eventName }
252
533
  )
253
534
  }
254
535
 
@@ -256,12 +537,19 @@ export const decodeEmitSync = <const Emits extends ReadonlyArray<Machine.TaggedS
256
537
  machine: Machine.Any,
257
538
  event: unknown
258
539
  ): Machine.EmitOf<Emits> => {
540
+ const protocol = getProtocolSchemas(machine)
541
+ if (isEventConstruction(event)) {
542
+ return decodeEventConstructionSync(machine, protocol, event, "emission") as Machine.EmitOf<Emits>
543
+ }
544
+ if (typeof event === "object" && event !== null && protocol.trustedEmissions.has(event)) {
545
+ return event as Machine.EmitOf<Emits>
546
+ }
259
547
  const eventName = getEventName(event)
260
548
  return decodeBoundarySync<Machine.EmitOf<Emits>>(
261
549
  machine,
262
- getProtocolSchemas(machine).emit,
550
+ protocol.emit,
263
551
  event,
264
- eventName === undefined ? { boundary: "emit" } : { boundary: "emit", event: eventName }
552
+ eventName === undefined ? { boundary: "emission" } : { boundary: "emission", event: eventName }
265
553
  )
266
554
  }
267
555