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.
- package/README.md +76 -16
- package/dist/_virtual/_rolldown/runtime.js +6 -11
- package/dist/actor.d.ts +58 -72
- package/dist/actor.js +166 -32
- package/dist/cluster/entity-machine.d.ts +0 -1
- package/dist/cluster/entity-machine.js +6 -6
- package/dist/cluster/index.js +1 -2
- package/dist/cluster/to-entity.js +1 -3
- package/dist/errors.d.ts +12 -1
- package/dist/errors.js +8 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -3
- package/dist/inspection.js +1 -3
- package/dist/internal/inspection.js +1 -3
- package/dist/internal/transition.d.ts +26 -2
- package/dist/internal/transition.js +37 -10
- package/dist/internal/utils.d.ts +7 -2
- package/dist/internal/utils.js +1 -3
- package/dist/machine.d.ts +66 -4
- package/dist/machine.js +67 -31
- package/dist/persistence/adapter.js +1 -3
- package/dist/persistence/adapters/in-memory.js +1 -3
- package/dist/persistence/index.js +1 -2
- package/dist/persistence/persistent-actor.js +54 -19
- package/dist/persistence/persistent-machine.js +1 -3
- package/dist/schema.js +1 -3
- package/dist/slot.js +1 -3
- package/dist/testing.js +58 -6
- package/package.json +19 -18
- package/v3/dist/_virtual/_rolldown/runtime.js +13 -0
- package/{dist-v3 → v3/dist}/actor.d.ts +65 -78
- package/{dist-v3 → v3/dist}/actor.js +173 -37
- package/{dist-v3 → v3/dist}/cluster/entity-machine.d.ts +1 -2
- package/{dist-v3 → v3/dist}/cluster/entity-machine.js +9 -9
- package/{dist-v3 → v3/dist}/cluster/index.js +1 -2
- package/{dist-v3 → v3/dist}/cluster/to-entity.d.ts +1 -1
- package/{dist-v3 → v3/dist}/cluster/to-entity.js +2 -4
- package/v3/dist/errors.d.ts +76 -0
- package/{dist-v3 → v3/dist}/errors.js +9 -4
- package/v3/dist/index.d.ts +13 -0
- package/v3/dist/index.js +13 -0
- package/{dist-v3 → v3/dist}/inspection.d.ts +53 -8
- package/v3/dist/inspection.js +156 -0
- package/{dist-v3 → v3/dist}/internal/brands.d.ts +1 -1
- package/{dist-v3 → v3/dist}/internal/inspection.d.ts +1 -1
- package/v3/dist/internal/inspection.js +20 -0
- package/{dist-v3 → v3/dist}/internal/transition.d.ts +35 -11
- package/{dist-v3 → v3/dist}/internal/transition.js +47 -17
- package/{dist-v3 → v3/dist}/internal/utils.d.ts +9 -4
- package/{dist-v3 → v3/dist}/internal/utils.js +2 -4
- package/{dist-v3 → v3/dist}/machine.d.ts +86 -10
- package/{dist-v3 → v3/dist}/machine.js +130 -33
- package/{dist-v3 → v3/dist}/persistence/adapter.d.ts +18 -5
- package/{dist-v3 → v3/dist}/persistence/adapter.js +2 -4
- package/{dist-v3 → v3/dist}/persistence/adapters/in-memory.d.ts +1 -1
- package/{dist-v3 → v3/dist}/persistence/adapters/in-memory.js +2 -4
- package/{dist-v3 → v3/dist}/persistence/index.js +1 -2
- package/{dist-v3 → v3/dist}/persistence/persistent-actor.d.ts +7 -6
- package/{dist-v3 → v3/dist}/persistence/persistent-actor.js +59 -22
- package/{dist-v3 → v3/dist}/persistence/persistent-machine.d.ts +1 -1
- package/{dist-v3 → v3/dist}/persistence/persistent-machine.js +2 -4
- package/{dist-v3 → v3/dist}/schema.d.ts +1 -1
- package/{dist-v3 → v3/dist}/schema.js +6 -5
- package/{dist-v3 → v3/dist}/slot.d.ts +4 -3
- package/{dist-v3 → v3/dist}/slot.js +2 -4
- package/{dist-v3 → v3/dist}/testing.d.ts +14 -8
- package/{dist-v3 → v3/dist}/testing.js +61 -9
- package/dist-v3/_virtual/_rolldown/runtime.js +0 -18
- package/dist-v3/errors.d.ts +0 -27
- package/dist-v3/index.d.ts +0 -13
- package/dist-v3/index.js +0 -14
- package/dist-v3/inspection.js +0 -50
- package/dist-v3/internal/inspection.js +0 -15
- /package/{dist-v3 → v3/dist}/cluster/index.d.ts +0 -0
- /package/{dist-v3 → v3/dist}/internal/brands.js +0 -0
- /package/{dist-v3 → v3/dist}/persistence/index.d.ts +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Schema } from "effect";
|
|
1
|
+
import { Context, Effect, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
//#region src
|
|
3
|
+
//#region src/inspection.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Resolve a type param: if it's a Schema, extract `.Type`; otherwise use as-is.
|
|
6
6
|
*/
|
|
@@ -45,6 +45,18 @@ interface EffectEvent<S> {
|
|
|
45
45
|
readonly state: S;
|
|
46
46
|
readonly timestamp: number;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Event emitted when a task lifecycle phase occurs
|
|
50
|
+
*/
|
|
51
|
+
interface TaskEvent<S> {
|
|
52
|
+
readonly type: "@machine.task";
|
|
53
|
+
readonly actorId: string;
|
|
54
|
+
readonly state: S;
|
|
55
|
+
readonly taskName?: string;
|
|
56
|
+
readonly phase: "start" | "success" | "failure" | "interrupt";
|
|
57
|
+
readonly error?: string;
|
|
58
|
+
readonly timestamp: number;
|
|
59
|
+
}
|
|
48
60
|
/**
|
|
49
61
|
* Event emitted when a transition handler or spawn effect fails with a defect
|
|
50
62
|
*/
|
|
@@ -69,7 +81,7 @@ interface StopEvent<S> {
|
|
|
69
81
|
/**
|
|
70
82
|
* Union of all inspection events
|
|
71
83
|
*/
|
|
72
|
-
type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
|
|
84
|
+
type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | TaskEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
|
|
73
85
|
/**
|
|
74
86
|
* Convenience alias for untyped inspection events.
|
|
75
87
|
* Useful for general-purpose inspectors that don't need specific state/event types.
|
|
@@ -81,19 +93,23 @@ type AnyInspectionEvent = InspectionEvent<{
|
|
|
81
93
|
}, {
|
|
82
94
|
readonly _tag: string;
|
|
83
95
|
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Inspector handler — sync callback or Effect-returning callback.
|
|
98
|
+
*/
|
|
99
|
+
type InspectorHandler<S, E> = (event: InspectionEvent<S, E>) => void | Effect.Effect<void, never, never>;
|
|
84
100
|
/**
|
|
85
101
|
* Inspector interface for observing machine behavior
|
|
86
102
|
*/
|
|
87
103
|
interface Inspector<S, E> {
|
|
88
|
-
readonly onInspect:
|
|
104
|
+
readonly onInspect: InspectorHandler<S, E>;
|
|
89
105
|
}
|
|
90
106
|
/**
|
|
91
107
|
* Inspector service tag - optional service for machine introspection
|
|
92
108
|
* Uses `any` types to allow variance flexibility when providing the service
|
|
93
109
|
*/
|
|
94
|
-
declare const Inspector: any
|
|
110
|
+
declare const Inspector: Context.Tag<Inspector<any, any>, Inspector<any, any>>;
|
|
95
111
|
/**
|
|
96
|
-
* Create an inspector from a callback function.
|
|
112
|
+
* Create an inspector from a sync callback function.
|
|
97
113
|
*
|
|
98
114
|
* Type params accept either raw tagged types or Schema constructors:
|
|
99
115
|
* - `makeInspector(cb)` — defaults to `AnyInspectionEvent`
|
|
@@ -104,7 +120,36 @@ declare const makeInspector: <S = {
|
|
|
104
120
|
readonly _tag: string;
|
|
105
121
|
}, E = {
|
|
106
122
|
readonly _tag: string;
|
|
107
|
-
}>(onInspect:
|
|
123
|
+
}>(onInspect: InspectorHandler<ResolveType<S>, ResolveType<E>>) => Inspector<ResolveType<S>, ResolveType<E>>;
|
|
124
|
+
/**
|
|
125
|
+
* Create an inspector from an Effect-returning callback function.
|
|
126
|
+
*/
|
|
127
|
+
declare const makeInspectorEffect: <S = {
|
|
128
|
+
readonly _tag: string;
|
|
129
|
+
}, E = {
|
|
130
|
+
readonly _tag: string;
|
|
131
|
+
}>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void, never, never>) => Inspector<ResolveType<S>, ResolveType<E>>;
|
|
132
|
+
/**
|
|
133
|
+
* Combine multiple inspectors into one. All run concurrently per event.
|
|
134
|
+
* Individual inspector failures are swallowed.
|
|
135
|
+
*/
|
|
136
|
+
declare const combineInspectors: <S, E>(...inspectors: ReadonlyArray<Inspector<S, E>>) => Inspector<S, E>;
|
|
137
|
+
/**
|
|
138
|
+
* Options for the tracing inspector.
|
|
139
|
+
*/
|
|
140
|
+
interface TracingInspectorOptions<S, E> {
|
|
141
|
+
readonly spanName?: string | ((event: InspectionEvent<S, E>) => string);
|
|
142
|
+
readonly attributes?: (event: InspectionEvent<S, E>) => Readonly<Record<string, string | number | boolean>>;
|
|
143
|
+
readonly eventName?: (event: InspectionEvent<S, E>) => string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Inspector that emits OpenTelemetry spans and events for each inspection event.
|
|
147
|
+
*/
|
|
148
|
+
declare const tracingInspector: <S extends {
|
|
149
|
+
readonly _tag: string;
|
|
150
|
+
}, E extends {
|
|
151
|
+
readonly _tag: string;
|
|
152
|
+
}>(options?: TracingInspectorOptions<S, E>) => Inspector<S, E>;
|
|
108
153
|
/**
|
|
109
154
|
* Console inspector that logs events in a readable format
|
|
110
155
|
*/
|
|
@@ -122,4 +167,4 @@ declare const collectingInspector: <S extends {
|
|
|
122
167
|
readonly _tag: string;
|
|
123
168
|
}>(events: InspectionEvent<S, E>[]) => Inspector<S, E>;
|
|
124
169
|
//#endregion
|
|
125
|
-
export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, SpawnEvent, StopEvent, TransitionEvent, collectingInspector, consoleInspector, makeInspector };
|
|
170
|
+
export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Context, Effect, Option } from "effect";
|
|
2
|
+
//#region src/inspection.ts
|
|
3
|
+
/**
|
|
4
|
+
* Inspector service tag - optional service for machine introspection
|
|
5
|
+
* Uses `any` types to allow variance flexibility when providing the service
|
|
6
|
+
*/
|
|
7
|
+
const Inspector = Context.GenericTag("@effect/machine/Inspector");
|
|
8
|
+
/**
|
|
9
|
+
* Create an inspector from a sync callback function.
|
|
10
|
+
*
|
|
11
|
+
* Type params accept either raw tagged types or Schema constructors:
|
|
12
|
+
* - `makeInspector(cb)` — defaults to `AnyInspectionEvent`
|
|
13
|
+
* - `makeInspector<MyState, MyEvent>(cb)` — explicit tagged types
|
|
14
|
+
* - `makeInspector<typeof MyState, typeof MyEvent>(cb)` — schema constructors (auto-extracts `.Type`)
|
|
15
|
+
*/
|
|
16
|
+
const makeInspector = (onInspect) => ({ onInspect });
|
|
17
|
+
/**
|
|
18
|
+
* Create an inspector from an Effect-returning callback function.
|
|
19
|
+
*/
|
|
20
|
+
const makeInspectorEffect = (onInspect) => ({ onInspect });
|
|
21
|
+
/**
|
|
22
|
+
* Run an inspector handler, handling both sync and Effect returns.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
const inspectionEffect = (inspector, event) => {
|
|
26
|
+
const result = inspector.onInspect(event);
|
|
27
|
+
return Effect.isEffect(result) ? result : Effect.void;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Combine multiple inspectors into one. All run concurrently per event.
|
|
31
|
+
* Individual inspector failures are swallowed.
|
|
32
|
+
*/
|
|
33
|
+
const combineInspectors = (...inspectors) => ({ onInspect: (event) => Effect.forEach(inspectors, (inspector) => inspectionEffect(inspector, event).pipe(Effect.catchAllCause(() => Effect.void)), {
|
|
34
|
+
concurrency: "unbounded",
|
|
35
|
+
discard: true
|
|
36
|
+
}) });
|
|
37
|
+
const inspectionSpanName = (event) => {
|
|
38
|
+
switch (event.type) {
|
|
39
|
+
case "@machine.spawn": return `Machine.inspect ${event.initialState._tag}`;
|
|
40
|
+
case "@machine.event": return `Machine.inspect ${event.event._tag}`;
|
|
41
|
+
case "@machine.transition": return `Machine.inspect ${event.fromState._tag}->${event.toState._tag}`;
|
|
42
|
+
case "@machine.effect": return `Machine.inspect ${event.effectType}`;
|
|
43
|
+
case "@machine.task": return `Machine.inspect task:${event.phase}`;
|
|
44
|
+
case "@machine.error": return `Machine.inspect ${event.phase}`;
|
|
45
|
+
case "@machine.stop": return `Machine.inspect ${event.finalState._tag}`;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const inspectionTraceName = (event) => {
|
|
49
|
+
switch (event.type) {
|
|
50
|
+
case "@machine.spawn": return `machine.spawn ${event.initialState._tag}`;
|
|
51
|
+
case "@machine.event": return `machine.event ${event.event._tag}`;
|
|
52
|
+
case "@machine.transition": return `machine.transition ${event.fromState._tag}->${event.toState._tag}`;
|
|
53
|
+
case "@machine.effect": return `machine.effect ${event.effectType}`;
|
|
54
|
+
case "@machine.task": return `machine.task ${event.phase}${event.taskName === void 0 ? "" : ` ${event.taskName}`}`;
|
|
55
|
+
case "@machine.error": return `machine.error ${event.phase}`;
|
|
56
|
+
case "@machine.stop": return `machine.stop ${event.finalState._tag}`;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const inspectionAttributes = (event) => {
|
|
60
|
+
const shared = {
|
|
61
|
+
"machine.actor.id": event.actorId,
|
|
62
|
+
"machine.inspection.type": event.type
|
|
63
|
+
};
|
|
64
|
+
switch (event.type) {
|
|
65
|
+
case "@machine.spawn": return {
|
|
66
|
+
...shared,
|
|
67
|
+
"machine.state.initial": event.initialState._tag
|
|
68
|
+
};
|
|
69
|
+
case "@machine.event": return {
|
|
70
|
+
...shared,
|
|
71
|
+
"machine.state.current": event.state._tag,
|
|
72
|
+
"machine.event.tag": event.event._tag
|
|
73
|
+
};
|
|
74
|
+
case "@machine.transition": return {
|
|
75
|
+
...shared,
|
|
76
|
+
"machine.state.from": event.fromState._tag,
|
|
77
|
+
"machine.state.to": event.toState._tag,
|
|
78
|
+
"machine.event.tag": event.event._tag
|
|
79
|
+
};
|
|
80
|
+
case "@machine.effect": return {
|
|
81
|
+
...shared,
|
|
82
|
+
"machine.state.current": event.state._tag,
|
|
83
|
+
"machine.effect.kind": event.effectType
|
|
84
|
+
};
|
|
85
|
+
case "@machine.task": return {
|
|
86
|
+
...shared,
|
|
87
|
+
"machine.state.current": event.state._tag,
|
|
88
|
+
"machine.task.phase": event.phase,
|
|
89
|
+
...event.taskName === void 0 ? {} : { "machine.task.name": event.taskName }
|
|
90
|
+
};
|
|
91
|
+
case "@machine.error": return {
|
|
92
|
+
...shared,
|
|
93
|
+
"machine.phase": event.phase,
|
|
94
|
+
"machine.state.current": event.state._tag
|
|
95
|
+
};
|
|
96
|
+
case "@machine.stop": return {
|
|
97
|
+
...shared,
|
|
98
|
+
"machine.state.final": event.finalState._tag
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Inspector that emits OpenTelemetry spans and events for each inspection event.
|
|
104
|
+
*/
|
|
105
|
+
const tracingInspector = (options) => ({ onInspect: (event) => {
|
|
106
|
+
const spanName = typeof options?.spanName === "function" ? options.spanName(event) : options?.spanName;
|
|
107
|
+
const traceName = options?.eventName?.(event) ?? inspectionTraceName(event);
|
|
108
|
+
const attributes = {
|
|
109
|
+
...inspectionAttributes(event),
|
|
110
|
+
...options?.attributes?.(event) ?? {}
|
|
111
|
+
};
|
|
112
|
+
return Effect.gen(function* () {
|
|
113
|
+
const currentSpan = yield* Effect.option(Effect.currentSpan);
|
|
114
|
+
if (Option.isSome(currentSpan)) currentSpan.value.event(traceName, BigInt(event.timestamp) * 1000000n, {
|
|
115
|
+
actorId: event.actorId,
|
|
116
|
+
inspectionType: event.type
|
|
117
|
+
});
|
|
118
|
+
}).pipe(Effect.withSpan(spanName ?? inspectionSpanName(event), { attributes }));
|
|
119
|
+
} });
|
|
120
|
+
/**
|
|
121
|
+
* Console inspector that logs events in a readable format
|
|
122
|
+
*/
|
|
123
|
+
const consoleInspector = () => makeInspector((event) => {
|
|
124
|
+
const prefix = `[${event.actorId}]`;
|
|
125
|
+
switch (event.type) {
|
|
126
|
+
case "@machine.spawn":
|
|
127
|
+
console.log(prefix, "spawned →", event.initialState._tag);
|
|
128
|
+
break;
|
|
129
|
+
case "@machine.event":
|
|
130
|
+
console.log(prefix, "received", event.event._tag, "in", event.state._tag);
|
|
131
|
+
break;
|
|
132
|
+
case "@machine.transition":
|
|
133
|
+
console.log(prefix, event.fromState._tag, "→", event.toState._tag);
|
|
134
|
+
break;
|
|
135
|
+
case "@machine.effect":
|
|
136
|
+
console.log(prefix, event.effectType, "effect in", event.state._tag);
|
|
137
|
+
break;
|
|
138
|
+
case "@machine.task":
|
|
139
|
+
console.log(prefix, "task", event.phase, event.taskName ?? "<unnamed>", "in", event.state._tag);
|
|
140
|
+
break;
|
|
141
|
+
case "@machine.error":
|
|
142
|
+
console.log(prefix, "error in", event.phase, event.state._tag, "-", event.error);
|
|
143
|
+
break;
|
|
144
|
+
case "@machine.stop":
|
|
145
|
+
console.log(prefix, "stopped in", event.finalState._tag);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* Collecting inspector that stores events in an array for testing
|
|
151
|
+
*/
|
|
152
|
+
const collectingInspector = (events) => ({ onInspect: (event) => {
|
|
153
|
+
events.push(event);
|
|
154
|
+
} });
|
|
155
|
+
//#endregion
|
|
156
|
+
export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Clock, Effect } from "effect";
|
|
2
|
+
//#region src/internal/inspection.ts
|
|
3
|
+
/**
|
|
4
|
+
* Emit an inspection event with timestamp from Clock.
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
const emitWithTimestamp = Effect.fn("effect-machine.emitWithTimestamp")(function* (inspector, makeEvent) {
|
|
8
|
+
if (inspector === void 0) return;
|
|
9
|
+
const event = makeEvent(yield* Clock.currentTimeMillis);
|
|
10
|
+
const result = yield* Effect.sync(() => {
|
|
11
|
+
try {
|
|
12
|
+
return inspector.onInspect(event);
|
|
13
|
+
} catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
if (result !== void 0 && Effect.isEffect(result)) yield* result.pipe(Effect.catchAllCause(() => Effect.void));
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
export { emitWithTimestamp };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { EffectsDef, GuardsDef } from "../slot.js";
|
|
1
|
+
import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
|
|
2
2
|
import { BuiltMachine, Machine, MachineRef, SpawnEffect, Transition } from "../machine.js";
|
|
3
3
|
import { ActorSystem } from "../actor.js";
|
|
4
4
|
import { Cause, Effect, Scope } from "effect";
|
|
5
5
|
|
|
6
|
-
//#region src
|
|
6
|
+
//#region src/internal/transition.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* Result of executing a transition.
|
|
9
9
|
*/
|
|
@@ -29,7 +29,11 @@ declare const runTransitionHandler: <S extends {
|
|
|
29
29
|
readonly _tag: string;
|
|
30
30
|
}, E extends {
|
|
31
31
|
readonly _tag: string;
|
|
32
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, transition: Transition<S, E, GD, EFD, R>, state: S, event: E, self: MachineRef<E>, system: ActorSystem) => Effect.Effect<
|
|
32
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, transition: Transition<S, E, GD, EFD, R>, state: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
|
|
33
|
+
newState: S;
|
|
34
|
+
hasReply: boolean;
|
|
35
|
+
reply: unknown;
|
|
36
|
+
}, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
|
|
33
37
|
/**
|
|
34
38
|
* Execute a transition for a given state and event.
|
|
35
39
|
* Handles transition resolution, handler invocation, and guard/effect slot creation.
|
|
@@ -45,11 +49,13 @@ declare const executeTransition: <S extends {
|
|
|
45
49
|
readonly _tag: string;
|
|
46
50
|
}, E extends {
|
|
47
51
|
readonly _tag: string;
|
|
48
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, currentState: S, event: E, self: MachineRef<E>, system: ActorSystem) => Effect.Effect<{
|
|
52
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, currentState: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
|
|
49
53
|
newState: S;
|
|
50
54
|
transitioned: boolean;
|
|
51
55
|
reenter: boolean;
|
|
52
|
-
|
|
56
|
+
hasReply: boolean;
|
|
57
|
+
reply: unknown;
|
|
58
|
+
}, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
|
|
53
59
|
/**
|
|
54
60
|
* Optional hooks for event processing inspection/tracing.
|
|
55
61
|
*/
|
|
@@ -84,7 +90,22 @@ interface ProcessEventResult<S> {
|
|
|
84
90
|
readonly lifecycleRan: boolean;
|
|
85
91
|
/** Whether new state is final */
|
|
86
92
|
readonly isFinal: boolean;
|
|
93
|
+
/** Whether the handler provided a reply (structural, not value-based) */
|
|
94
|
+
readonly hasReply: boolean;
|
|
95
|
+
/** Domain reply value from handler (used by ask). Only meaningful when hasReply is true. */
|
|
96
|
+
readonly reply?: unknown;
|
|
97
|
+
/** Whether the event was postponed (buffered for retry after next state change) */
|
|
98
|
+
readonly postponed: boolean;
|
|
87
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if an event should be postponed in the current state.
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
declare const shouldPostpone: <S extends {
|
|
105
|
+
readonly _tag: string;
|
|
106
|
+
}, E extends {
|
|
107
|
+
readonly _tag: string;
|
|
108
|
+
}, R>(machine: Machine<S, E, R, any, any, any, any>, stateTag: string, eventTag: string) => boolean;
|
|
88
109
|
/**
|
|
89
110
|
* Process a single event through the machine.
|
|
90
111
|
*
|
|
@@ -103,13 +124,16 @@ declare const processEventCore: <S extends {
|
|
|
103
124
|
readonly _tag: string;
|
|
104
125
|
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, currentState: S, event: E, self: MachineRef<E>, stateScopeRef: {
|
|
105
126
|
current: Scope.CloseableScope;
|
|
106
|
-
}, system: ActorSystem, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
|
|
107
|
-
newState:
|
|
127
|
+
}, system: ActorSystem, actorId: string, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
|
|
128
|
+
newState: S;
|
|
108
129
|
previousState: S;
|
|
109
130
|
transitioned: boolean;
|
|
110
|
-
lifecycleRan:
|
|
131
|
+
lifecycleRan: boolean;
|
|
111
132
|
isFinal: boolean;
|
|
112
|
-
|
|
133
|
+
hasReply: boolean;
|
|
134
|
+
reply: unknown;
|
|
135
|
+
postponed: boolean;
|
|
136
|
+
}, never, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
|
|
113
137
|
/**
|
|
114
138
|
* Run spawn effects for a state (forked into state scope, auto-cancelled on state exit).
|
|
115
139
|
*
|
|
@@ -119,7 +143,7 @@ declare const runSpawnEffects: <S extends {
|
|
|
119
143
|
readonly _tag: string;
|
|
120
144
|
}, E extends {
|
|
121
145
|
readonly _tag: string;
|
|
122
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.CloseableScope, system: ActorSystem, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never,
|
|
146
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.CloseableScope, system: ActorSystem, actorId: string, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
|
|
123
147
|
/**
|
|
124
148
|
* Resolve which transition should fire for a given state and event.
|
|
125
149
|
* Uses indexed O(1) lookup. First matching transition wins.
|
|
@@ -157,4 +181,4 @@ declare const findSpawnEffects: <S extends {
|
|
|
157
181
|
readonly _tag: string;
|
|
158
182
|
}, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, GD, EFD>, stateTag: string) => ReadonlyArray<SpawnEffect<S, E, EFD, R>>;
|
|
159
183
|
//#endregion
|
|
160
|
-
export { ProcessEventError, ProcessEventHooks, ProcessEventResult, TransitionExecutionResult, executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler };
|
|
184
|
+
export { ProcessEventError, ProcessEventHooks, ProcessEventResult, TransitionExecutionResult, executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { INTERNAL_ENTER_EVENT, isEffect } from "./utils.js";
|
|
2
2
|
import { BuiltMachine } from "../machine.js";
|
|
3
3
|
import { Cause, Effect, Exit, Scope } from "effect";
|
|
4
|
-
|
|
5
|
-
//#region src-v3/internal/transition.ts
|
|
4
|
+
//#region src/internal/transition.ts
|
|
6
5
|
/**
|
|
7
6
|
* Transition execution and indexing.
|
|
8
7
|
*
|
|
@@ -23,8 +22,9 @@ import { Cause, Effect, Exit, Scope } from "effect";
|
|
|
23
22
|
*
|
|
24
23
|
* @internal
|
|
25
24
|
*/
|
|
26
|
-
const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self, system) {
|
|
25
|
+
const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self, system, actorId) {
|
|
27
26
|
const ctx = {
|
|
27
|
+
actorId,
|
|
28
28
|
state,
|
|
29
29
|
event,
|
|
30
30
|
self,
|
|
@@ -37,8 +37,18 @@ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(fu
|
|
|
37
37
|
guards,
|
|
38
38
|
effects
|
|
39
39
|
};
|
|
40
|
-
const
|
|
41
|
-
|
|
40
|
+
const raw = transition.handler(handlerCtx);
|
|
41
|
+
const resolved = isEffect(raw) ? yield* raw.pipe(Effect.provideService(machine.Context, ctx)) : raw;
|
|
42
|
+
if (resolved !== null && typeof resolved === "object" && "state" in resolved && "reply" in resolved && !("_tag" in resolved)) return {
|
|
43
|
+
newState: resolved.state,
|
|
44
|
+
hasReply: true,
|
|
45
|
+
reply: resolved.reply
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
newState: resolved,
|
|
49
|
+
hasReply: false,
|
|
50
|
+
reply: void 0
|
|
51
|
+
};
|
|
42
52
|
});
|
|
43
53
|
/**
|
|
44
54
|
* Execute a transition for a given state and event.
|
|
@@ -51,20 +61,33 @@ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(fu
|
|
|
51
61
|
*
|
|
52
62
|
* @internal
|
|
53
63
|
*/
|
|
54
|
-
const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self, system) {
|
|
64
|
+
const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self, system, actorId) {
|
|
55
65
|
const transition = resolveTransition(machine, currentState, event);
|
|
56
66
|
if (transition === void 0) return {
|
|
57
67
|
newState: currentState,
|
|
58
68
|
transitioned: false,
|
|
59
|
-
reenter: false
|
|
69
|
+
reenter: false,
|
|
70
|
+
hasReply: false,
|
|
71
|
+
reply: void 0
|
|
60
72
|
};
|
|
73
|
+
const { newState, hasReply, reply } = yield* runTransitionHandler(machine, transition, currentState, event, self, system, actorId);
|
|
61
74
|
return {
|
|
62
|
-
newState
|
|
75
|
+
newState,
|
|
63
76
|
transitioned: true,
|
|
64
|
-
reenter: transition.reenter === true
|
|
77
|
+
reenter: transition.reenter === true,
|
|
78
|
+
hasReply,
|
|
79
|
+
reply
|
|
65
80
|
};
|
|
66
81
|
});
|
|
67
82
|
/**
|
|
83
|
+
* Check if an event should be postponed in the current state.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
const shouldPostpone = (machine, stateTag, eventTag) => {
|
|
87
|
+
for (const rule of machine.postponeRules) if (rule.stateTag === stateTag && rule.eventTag === eventTag) return true;
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
68
91
|
* Process a single event through the machine.
|
|
69
92
|
*
|
|
70
93
|
* Handles:
|
|
@@ -76,8 +99,8 @@ const executeTransition = Effect.fn("effect-machine.executeTransition")(function
|
|
|
76
99
|
*
|
|
77
100
|
* @internal
|
|
78
101
|
*/
|
|
79
|
-
const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, system, hooks) {
|
|
80
|
-
const result = yield* executeTransition(machine, currentState, event, self, system).pipe(Effect.catchAllCause((cause) => {
|
|
102
|
+
const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, system, actorId, hooks) {
|
|
103
|
+
const result = yield* executeTransition(machine, currentState, event, self, system, actorId).pipe(Effect.catchAllCause((cause) => {
|
|
81
104
|
if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
|
|
82
105
|
const onError = hooks?.onError;
|
|
83
106
|
if (onError === void 0) return Effect.failCause(cause).pipe(Effect.orDie);
|
|
@@ -93,7 +116,10 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
93
116
|
previousState: currentState,
|
|
94
117
|
transitioned: false,
|
|
95
118
|
lifecycleRan: false,
|
|
96
|
-
isFinal: false
|
|
119
|
+
isFinal: false,
|
|
120
|
+
hasReply: false,
|
|
121
|
+
reply: void 0,
|
|
122
|
+
postponed: false
|
|
97
123
|
};
|
|
98
124
|
const newState = result.newState;
|
|
99
125
|
const runLifecycle = newState._tag !== currentState._tag || result.reenter;
|
|
@@ -102,14 +128,17 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
102
128
|
stateScopeRef.current = yield* Scope.make();
|
|
103
129
|
if (hooks?.onTransition !== void 0) yield* hooks.onTransition(currentState, newState, event);
|
|
104
130
|
if (hooks?.onSpawnEffect !== void 0) yield* hooks.onSpawnEffect(newState);
|
|
105
|
-
yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, hooks?.onError);
|
|
131
|
+
yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, actorId, hooks?.onError);
|
|
106
132
|
}
|
|
107
133
|
return {
|
|
108
134
|
newState,
|
|
109
135
|
previousState: currentState,
|
|
110
136
|
transitioned: true,
|
|
111
137
|
lifecycleRan: runLifecycle,
|
|
112
|
-
isFinal: machine.finalStates.has(newState._tag)
|
|
138
|
+
isFinal: machine.finalStates.has(newState._tag),
|
|
139
|
+
hasReply: result.hasReply,
|
|
140
|
+
reply: result.reply,
|
|
141
|
+
postponed: false
|
|
113
142
|
};
|
|
114
143
|
});
|
|
115
144
|
/**
|
|
@@ -117,9 +146,10 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
117
146
|
*
|
|
118
147
|
* @internal
|
|
119
148
|
*/
|
|
120
|
-
const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, onError) {
|
|
149
|
+
const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, actorId, onError) {
|
|
121
150
|
const spawnEffects = findSpawnEffects(machine, state._tag);
|
|
122
151
|
const ctx = {
|
|
152
|
+
actorId,
|
|
123
153
|
state,
|
|
124
154
|
event,
|
|
125
155
|
self,
|
|
@@ -129,6 +159,7 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
|
|
|
129
159
|
const reportError = onError;
|
|
130
160
|
for (const spawnEffect of spawnEffects) {
|
|
131
161
|
const effect = spawnEffect.handler({
|
|
162
|
+
actorId,
|
|
132
163
|
state,
|
|
133
164
|
event,
|
|
134
165
|
self,
|
|
@@ -233,6 +264,5 @@ const findTransitions = (input, stateTag, eventTag) => {
|
|
|
233
264
|
const findSpawnEffects = (machine, stateTag) => {
|
|
234
265
|
return getIndex(machine).spawn.get(stateTag) ?? [];
|
|
235
266
|
};
|
|
236
|
-
|
|
237
267
|
//#endregion
|
|
238
|
-
export { executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler };
|
|
268
|
+
export { executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActorSystem } from "../actor.js";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
|
|
4
|
-
//#region src
|
|
4
|
+
//#region src/internal/utils.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* Extracts _tag from a tagged union member
|
|
7
7
|
*/
|
|
@@ -23,10 +23,15 @@ type InstanceOf<C> = C extends ((...args: unknown[]) => infer R) ? R : never;
|
|
|
23
23
|
type TaggedConstructor<T extends {
|
|
24
24
|
readonly _tag: string;
|
|
25
25
|
}> = (args: Omit<T, "_tag">) => T;
|
|
26
|
+
/** Reply tuple returned from transition handlers for ask support */
|
|
27
|
+
interface TransitionReply<State> {
|
|
28
|
+
readonly state: State;
|
|
29
|
+
readonly reply: unknown;
|
|
30
|
+
}
|
|
26
31
|
/**
|
|
27
|
-
* Transition handler result - either a new state or Effect producing one
|
|
32
|
+
* Transition handler result - either a new state, reply tuple, or Effect producing one
|
|
28
33
|
*/
|
|
29
|
-
type TransitionResult<State, R> = State | Effect.Effect<State
|
|
34
|
+
type TransitionResult<State, R> = State | TransitionReply<State> | Effect.Effect<State | TransitionReply<State>, never, R>;
|
|
30
35
|
/**
|
|
31
36
|
* Internal event tags used for lifecycle effect contexts.
|
|
32
37
|
* Prefixed with $ to distinguish from user events.
|
|
@@ -57,4 +62,4 @@ declare const isEffect: (value: unknown) => value is Effect.Effect<unknown, unkn
|
|
|
57
62
|
*/
|
|
58
63
|
declare const stubSystem: ActorSystem;
|
|
59
64
|
//#endregion
|
|
60
|
-
export { ArgsOf, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, TagOf, TaggedConstructor, TransitionResult, getTag, isEffect, stubSystem };
|
|
65
|
+
export { ArgsOf, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, TagOf, TaggedConstructor, TransitionReply, TransitionResult, getTag, isEffect, stubSystem };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Effect, Stream } from "effect";
|
|
2
|
-
|
|
3
|
-
//#region src-v3/internal/utils.ts
|
|
2
|
+
//#region src/internal/utils.ts
|
|
4
3
|
/**
|
|
5
4
|
* Internal event tags used for lifecycle effect contexts.
|
|
6
5
|
* Prefixed with $ to distinguish from user events.
|
|
@@ -46,6 +45,5 @@ const stubSystem = {
|
|
|
46
45
|
restoreMany: () => Effect.die("restoreMany not supported in stub system"),
|
|
47
46
|
restoreAll: () => Effect.die("restoreAll not supported in stub system")
|
|
48
47
|
};
|
|
49
|
-
|
|
50
48
|
//#endregion
|
|
51
|
-
export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect, stubSystem };
|
|
49
|
+
export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect, stubSystem };
|