effect-machine 0.11.0 → 0.12.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/dist/actor.d.ts +10 -4
- package/dist/actor.js +33 -5
- package/dist/cluster/adapters/in-memory.d.ts +28 -0
- package/dist/cluster/adapters/in-memory.js +79 -0
- package/dist/cluster/entity-actor-ref.d.ts +56 -0
- package/dist/cluster/entity-actor-ref.js +33 -0
- package/dist/cluster/entity-machine.d.ts +31 -49
- package/dist/cluster/entity-machine.js +167 -52
- package/dist/cluster/index.d.ts +5 -2
- package/dist/cluster/index.js +4 -1
- package/dist/cluster/persistence.d.ts +49 -0
- package/dist/cluster/persistence.js +18 -0
- package/dist/cluster/to-entity.d.ts +9 -3
- package/dist/cluster/to-entity.js +16 -4
- package/dist/errors.d.ts +12 -1
- package/dist/errors.js +8 -1
- package/dist/index.d.ts +3 -2
- package/dist/internal/brands.d.ts +14 -1
- package/dist/internal/runtime.d.ts +67 -0
- package/dist/internal/runtime.js +248 -0
- package/dist/internal/transition.d.ts +5 -0
- package/dist/internal/transition.js +15 -3
- package/dist/internal/utils.d.ts +42 -6
- package/dist/internal/utils.js +27 -1
- package/dist/machine.d.ts +26 -13
- package/dist/machine.js +14 -3
- package/dist/schema.d.ts +35 -34
- package/dist/schema.js +32 -3
- package/dist/testing.js +4 -2
- package/package.json +3 -3
- package/v3/dist/actor.d.ts +4 -3
- package/v3/dist/actor.js +15 -3
- package/v3/dist/cluster/adapters/in-memory.d.ts +15 -0
- package/v3/dist/cluster/adapters/in-memory.js +62 -0
- package/v3/dist/cluster/entity-actor-ref.d.ts +49 -0
- package/v3/dist/cluster/entity-actor-ref.js +19 -0
- package/v3/dist/cluster/entity-machine.d.ts +34 -49
- package/v3/dist/cluster/entity-machine.js +134 -50
- package/v3/dist/cluster/index.d.ts +5 -2
- package/v3/dist/cluster/index.js +4 -1
- package/v3/dist/cluster/persistence.d.ts +48 -0
- package/v3/dist/cluster/persistence.js +14 -0
- package/v3/dist/cluster/to-entity.d.ts +5 -2
- package/v3/dist/cluster/to-entity.js +12 -4
- package/v3/dist/errors.d.ts +16 -1
- package/v3/dist/errors.js +8 -1
- package/v3/dist/index.d.ts +3 -2
- package/v3/dist/internal/brands.d.ts +15 -1
- package/v3/dist/internal/runtime.d.ts +65 -0
- package/v3/dist/internal/runtime.js +236 -0
- package/v3/dist/internal/transition.d.ts +5 -0
- package/v3/dist/internal/transition.js +15 -3
- package/v3/dist/internal/utils.d.ts +42 -6
- package/v3/dist/internal/utils.js +27 -1
- package/v3/dist/machine.d.ts +19 -13
- package/v3/dist/machine.js +10 -2
- package/v3/dist/schema.d.ts +35 -34
- package/v3/dist/schema.js +29 -3
|
@@ -1,80 +1,164 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stubSystem } from "../internal/utils.js";
|
|
2
|
+
import { BuiltMachine, replay } from "../machine.js";
|
|
2
3
|
import { ActorSystem } from "../actor.js";
|
|
3
|
-
import {
|
|
4
|
+
import { createRuntime } from "../internal/runtime.js";
|
|
5
|
+
import { PersistenceAdapter } from "./persistence.js";
|
|
6
|
+
import { Effect, Option, Ref } from "effect";
|
|
4
7
|
import { Entity } from "@effect/cluster";
|
|
5
8
|
//#region src/cluster/entity-machine.ts
|
|
6
9
|
/**
|
|
7
10
|
* EntityMachine adapter - wires a machine to a cluster Entity layer.
|
|
8
11
|
*
|
|
12
|
+
* Uses the runtime kernel for a single serialized event loop per entity.
|
|
13
|
+
* All events (external RPCs + internal self.send) go through the
|
|
14
|
+
* runtime kernel's single queue — no split-mailbox race.
|
|
15
|
+
*
|
|
16
|
+
* v3 uses `entity.toLayer` with handler objects (no `toLayerQueue`/`toLayerMailbox`).
|
|
17
|
+
* Supports opt-in persistence (snapshot or journal strategy).
|
|
18
|
+
*
|
|
9
19
|
* @module
|
|
10
20
|
*/
|
|
11
21
|
/**
|
|
12
|
-
* Process a single event through the machine using shared core.
|
|
13
|
-
* Returns the new state after processing.
|
|
14
|
-
*/
|
|
15
|
-
const processEvent = Effect.fn("effect-machine.cluster.processEvent")(function* (machine, stateRef, event, self, stateScopeRef, system, hooks) {
|
|
16
|
-
const result = yield* processEventCore(machine, yield* Ref.get(stateRef), event, self, stateScopeRef, system, "*", hooks);
|
|
17
|
-
if (result.transitioned) yield* Ref.set(stateRef, result.newState);
|
|
18
|
-
return result.newState;
|
|
19
|
-
});
|
|
20
|
-
/**
|
|
21
22
|
* Create an Entity layer that wires a machine to handle RPC calls.
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
* - Maintains state via Ref per entity instance
|
|
25
|
-
* - Resolves transitions using the indexed lookup
|
|
26
|
-
* - Evaluates guards in registration order
|
|
27
|
-
* - Runs lifecycle effects (onEnter/spawn)
|
|
28
|
-
* - Processes internal events from spawn effects
|
|
24
|
+
* v3: Uses `Entity.toLayer` with handler objects backed by the runtime kernel.
|
|
29
25
|
*
|
|
30
26
|
* @example
|
|
31
27
|
* ```ts
|
|
32
|
-
* const OrderEntity = toEntity(orderMachine, {
|
|
33
|
-
* type: "Order",
|
|
34
|
-
* stateSchema: OrderState,
|
|
35
|
-
* eventSchema: OrderEvent,
|
|
36
|
-
* })
|
|
28
|
+
* const OrderEntity = toEntity(orderMachine, { type: "Order" })
|
|
37
29
|
*
|
|
38
30
|
* const OrderEntityLayer = EntityMachine.layer(OrderEntity, orderMachine, {
|
|
39
31
|
* initializeState: (entityId) => OrderState.Pending({ orderId: entityId }),
|
|
40
32
|
* })
|
|
41
|
-
*
|
|
42
|
-
* // Use in cluster
|
|
43
|
-
* const program = Effect.gen(function* () {
|
|
44
|
-
* const client = yield* ShardingClient.client(OrderEntity)
|
|
45
|
-
* yield* client.Send("order-123", { event: OrderEvent.Ship({ trackingId: "abc" }) })
|
|
46
|
-
* })
|
|
47
33
|
* ```
|
|
48
34
|
*/
|
|
49
35
|
const EntityMachine = { layer: (entity, machine, options) => {
|
|
50
|
-
const
|
|
36
|
+
const persistence = options?.persistence;
|
|
37
|
+
const build = Effect.gen(function* () {
|
|
51
38
|
const entityId = yield* Effect.serviceOption(Entity.CurrentAddress).pipe(Effect.map((opt) => opt._tag === "Some" ? opt.value.entityId : ""));
|
|
52
|
-
const initialState = options?.initializeState !== void 0 ? options.initializeState(entityId) : machine.initial;
|
|
53
39
|
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
40
|
+
const system = Option.isSome(existingSystem) ? existingSystem.value : stubSystem;
|
|
41
|
+
const persistCtx = yield* hydratePersistence(persistence, entity, entityId, machine, options?.initializeState);
|
|
42
|
+
const initialState = persistCtx.hydratedState ?? (options?.initializeState !== void 0 ? options.initializeState(entityId) : void 0);
|
|
43
|
+
const machineWithState = initialState !== void 0 ? Object.create(machine, { initial: {
|
|
44
|
+
value: initialState,
|
|
45
|
+
enumerable: true
|
|
46
|
+
} }) : machine;
|
|
47
|
+
const versionRef = yield* Ref.make(persistCtx.initialVersion);
|
|
48
|
+
const runtime = yield* createRuntime(machineWithState, system, {
|
|
49
|
+
actorId: entityId,
|
|
50
|
+
hooks: options?.hooks
|
|
59
51
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
52
|
+
if (persistCtx.adapter !== void 0) {
|
|
53
|
+
const { adapter: pAdapter, key } = persistCtx;
|
|
54
|
+
yield* Effect.addFinalizer(() => Effect.gen(function* () {
|
|
55
|
+
const state = yield* runtime.getState;
|
|
56
|
+
const version = yield* Ref.get(versionRef);
|
|
57
|
+
yield* pAdapter.saveSnapshot(key, {
|
|
58
|
+
state,
|
|
59
|
+
version,
|
|
60
|
+
timestamp: Date.now()
|
|
61
|
+
});
|
|
62
|
+
}).pipe(Effect.catchAll(() => Effect.void)));
|
|
63
|
+
}
|
|
64
|
+
const hasPersistence = persistCtx.adapter !== void 0;
|
|
65
|
+
const journalCtx = hasPersistence && (persistence?.strategy ?? "snapshot") === "journal" ? {
|
|
66
|
+
adapter: persistCtx.adapter,
|
|
67
|
+
key: persistCtx.key
|
|
68
|
+
} : void 0;
|
|
72
69
|
return entity.of({
|
|
73
|
-
Send: (envelope) =>
|
|
74
|
-
|
|
70
|
+
Send: (envelope) => Effect.gen(function* () {
|
|
71
|
+
yield* runtime.sendWait(envelope.payload.event);
|
|
72
|
+
if (journalCtx !== void 0) yield* persistEvent(journalCtx.adapter, journalCtx.key, versionRef, envelope.payload.event);
|
|
73
|
+
else if (hasPersistence) yield* Ref.update(versionRef, (v) => v + 1);
|
|
74
|
+
return yield* runtime.getState;
|
|
75
|
+
}),
|
|
76
|
+
Ask: (envelope) => Effect.gen(function* () {
|
|
77
|
+
const reply = yield* runtime.ask(envelope.payload.event);
|
|
78
|
+
if (journalCtx !== void 0) yield* persistEvent(journalCtx.adapter, journalCtx.key, versionRef, envelope.payload.event);
|
|
79
|
+
else if (hasPersistence) yield* Ref.update(versionRef, (v) => v + 1);
|
|
80
|
+
return reply;
|
|
81
|
+
}),
|
|
82
|
+
GetState: () => runtime.getState
|
|
75
83
|
});
|
|
76
84
|
});
|
|
77
|
-
|
|
85
|
+
const clusterOptions = {};
|
|
86
|
+
if (options?.maxIdleTime !== void 0) clusterOptions.maxIdleTime = options.maxIdleTime;
|
|
87
|
+
if (options?.concurrency !== void 0) clusterOptions.concurrency = options.concurrency;
|
|
88
|
+
if (options?.mailboxCapacity !== void 0) clusterOptions.mailboxCapacity = options.mailboxCapacity;
|
|
89
|
+
if (options?.disableFatalDefects !== void 0) clusterOptions.disableFatalDefects = options.disableFatalDefects;
|
|
90
|
+
if (options?.defectRetryPolicy !== void 0) clusterOptions.defectRetryPolicy = options.defectRetryPolicy;
|
|
91
|
+
return entity.toLayer(build.pipe(Effect.orDie), Object.keys(clusterOptions).length > 0 ? clusterOptions : void 0);
|
|
78
92
|
} };
|
|
93
|
+
const noPersistence = {
|
|
94
|
+
adapter: void 0,
|
|
95
|
+
key: void 0,
|
|
96
|
+
hydratedState: void 0,
|
|
97
|
+
initialVersion: 0
|
|
98
|
+
};
|
|
99
|
+
/** Load snapshot/journal and compute hydrated state. */
|
|
100
|
+
const hydratePersistence = (persistence, entityDef, entityId, machine, initializeState) => Effect.gen(function* () {
|
|
101
|
+
if (persistence === void 0) return noPersistence;
|
|
102
|
+
const adapter = yield* PersistenceAdapter;
|
|
103
|
+
const key = {
|
|
104
|
+
entityType: persistence.machineType ?? entityDef.type,
|
|
105
|
+
entityId
|
|
106
|
+
};
|
|
107
|
+
const maybeSnapshot = yield* adapter.loadSnapshot(key);
|
|
108
|
+
if ((persistence.strategy ?? "snapshot") === "journal") {
|
|
109
|
+
const baseState = Option.isSome(maybeSnapshot) ? maybeSnapshot.value.state : initializeState !== void 0 ? initializeState(entityId) : machine.initial;
|
|
110
|
+
const snapshotVersion = Option.isSome(maybeSnapshot) ? maybeSnapshot.value.version : 0;
|
|
111
|
+
const events = yield* adapter.loadEvents(key, snapshotVersion);
|
|
112
|
+
if (events.length > 0) {
|
|
113
|
+
const eventValues = events.map((e) => e.event);
|
|
114
|
+
const hydratedState = yield* replay(new BuiltMachine(machine), eventValues, { from: baseState });
|
|
115
|
+
const lastEvent = events[events.length - 1];
|
|
116
|
+
return {
|
|
117
|
+
adapter,
|
|
118
|
+
key,
|
|
119
|
+
hydratedState,
|
|
120
|
+
initialVersion: lastEvent !== void 0 ? lastEvent.version : snapshotVersion
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
adapter,
|
|
125
|
+
key,
|
|
126
|
+
hydratedState: Option.isSome(maybeSnapshot) ? maybeSnapshot.value.state : void 0,
|
|
127
|
+
initialVersion: snapshotVersion
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (Option.isSome(maybeSnapshot)) return {
|
|
131
|
+
adapter,
|
|
132
|
+
key,
|
|
133
|
+
hydratedState: maybeSnapshot.value.state,
|
|
134
|
+
initialVersion: maybeSnapshot.value.version
|
|
135
|
+
};
|
|
136
|
+
return {
|
|
137
|
+
adapter,
|
|
138
|
+
key,
|
|
139
|
+
hydratedState: void 0,
|
|
140
|
+
initialVersion: 0
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
/**
|
|
144
|
+
* Append a single event to the journal, incrementing version.
|
|
145
|
+
*
|
|
146
|
+
* On failure: defects the entity activation. The cluster's defectRetryPolicy
|
|
147
|
+
* restarts the entity from the last consistent snapshot.
|
|
148
|
+
*/
|
|
149
|
+
const persistEvent = (adapter, key, versionRef, event) => Effect.gen(function* () {
|
|
150
|
+
const expectedVersion = yield* Ref.get(versionRef);
|
|
151
|
+
const newVersion = expectedVersion + 1;
|
|
152
|
+
const persisted = {
|
|
153
|
+
event,
|
|
154
|
+
version: newVersion,
|
|
155
|
+
timestamp: Date.now()
|
|
156
|
+
};
|
|
157
|
+
yield* adapter.appendEvents(key, [persisted], expectedVersion);
|
|
158
|
+
yield* Ref.set(versionRef, newVersion);
|
|
159
|
+
}).pipe(Effect.tapError((error) => Effect.logWarning("Journal append failed, defecting entity", {
|
|
160
|
+
key,
|
|
161
|
+
error
|
|
162
|
+
})), Effect.orDie);
|
|
79
163
|
//#endregion
|
|
80
164
|
export { EntityMachine };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { EntityPersistenceConfig, PersistedEvent, PersistenceAdapter, PersistenceKey, Snapshot } from "./persistence.js";
|
|
2
|
+
import { makeInMemoryPersistenceAdapter } from "./adapters/in-memory.js";
|
|
3
|
+
import { EntityActorRef, makeEntityActorRef } from "./entity-actor-ref.js";
|
|
1
4
|
import { EntityMachine, EntityMachineOptions } from "./entity-machine.js";
|
|
2
|
-
import { ToEntityOptions, toEntity } from "./to-entity.js";
|
|
3
|
-
export { EntityMachine, type EntityMachineOptions, type ToEntityOptions, toEntity };
|
|
5
|
+
import { EntityRpcs, ToEntityOptions, toEntity } from "./to-entity.js";
|
|
6
|
+
export { type EntityActorRef, EntityMachine, type EntityMachineOptions, type EntityPersistenceConfig, type EntityRpcs, type PersistedEvent, PersistenceAdapter, type PersistenceAdapter as PersistenceAdapterInterface, type PersistenceKey, type Snapshot, type ToEntityOptions, makeEntityActorRef, makeInMemoryPersistenceAdapter, toEntity };
|
package/v3/dist/cluster/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { makeEntityActorRef } from "./entity-actor-ref.js";
|
|
2
|
+
import { PersistenceAdapter } from "./persistence.js";
|
|
1
3
|
import { EntityMachine } from "./entity-machine.js";
|
|
2
4
|
import { toEntity } from "./to-entity.js";
|
|
3
|
-
|
|
5
|
+
import { makeInMemoryPersistenceAdapter } from "./adapters/in-memory.js";
|
|
6
|
+
export { EntityMachine, PersistenceAdapter, makeEntityActorRef, makeInMemoryPersistenceAdapter, toEntity };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PersistenceError, VersionConflictError } from "../errors.js";
|
|
2
|
+
import { Context, Effect, Option, Schedule } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/cluster/persistence.d.ts
|
|
5
|
+
/** Namespaced key preventing cross-type collisions (e.g. Order/123 vs User/123). */
|
|
6
|
+
interface PersistenceKey {
|
|
7
|
+
readonly entityType: string;
|
|
8
|
+
readonly entityId: string;
|
|
9
|
+
}
|
|
10
|
+
/** Stored state snapshot with version and timestamp. */
|
|
11
|
+
interface Snapshot<S> {
|
|
12
|
+
readonly state: S;
|
|
13
|
+
readonly version: number;
|
|
14
|
+
readonly timestamp: number;
|
|
15
|
+
}
|
|
16
|
+
/** Stored event with version and timestamp. */
|
|
17
|
+
interface PersistedEvent<E> {
|
|
18
|
+
readonly event: E;
|
|
19
|
+
readonly version: number;
|
|
20
|
+
readonly timestamp: number;
|
|
21
|
+
}
|
|
22
|
+
/** Persistence configuration for EntityMachineOptions. */
|
|
23
|
+
interface EntityPersistenceConfig {
|
|
24
|
+
/** Persistence strategy. Default: "snapshot". */
|
|
25
|
+
readonly strategy?: "snapshot" | "journal";
|
|
26
|
+
/**
|
|
27
|
+
* Schedule controlling snapshot frequency.
|
|
28
|
+
* Default: save on every state change.
|
|
29
|
+
*/
|
|
30
|
+
readonly snapshotSchedule?: Schedule.Schedule<any, any>;
|
|
31
|
+
/** Override entityType in the persistence key (defaults to Entity.type). */
|
|
32
|
+
readonly machineType?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Storage backend for entity state persistence. */
|
|
35
|
+
interface PersistenceAdapter {
|
|
36
|
+
/** Save a state snapshot. Fails with VersionConflictError if version is stale. */
|
|
37
|
+
readonly saveSnapshot: (key: PersistenceKey, snapshot: Snapshot<unknown>) => Effect.Effect<void, PersistenceError | VersionConflictError>;
|
|
38
|
+
/** Load the latest snapshot, or None if no snapshot exists. */
|
|
39
|
+
readonly loadSnapshot: (key: PersistenceKey) => Effect.Effect<Option.Option<Snapshot<unknown>>, PersistenceError>;
|
|
40
|
+
/** Append events to the journal. Fails with VersionConflictError if expectedVersion doesn't match. */
|
|
41
|
+
readonly appendEvents: (key: PersistenceKey, events: ReadonlyArray<PersistedEvent<unknown>>, expectedVersion: number) => Effect.Effect<void, PersistenceError | VersionConflictError>;
|
|
42
|
+
/** Load events from the journal, optionally after a given version. */
|
|
43
|
+
readonly loadEvents: (key: PersistenceKey, afterVersion?: number) => Effect.Effect<ReadonlyArray<PersistedEvent<unknown>>, PersistenceError>;
|
|
44
|
+
}
|
|
45
|
+
/** Service tag for PersistenceAdapter — resolve from context for shared infra. */
|
|
46
|
+
declare const PersistenceAdapter: Context.Tag<PersistenceAdapter, PersistenceAdapter>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { EntityPersistenceConfig, PersistedEvent, PersistenceAdapter, PersistenceKey, Snapshot };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
//#region src/cluster/persistence.ts
|
|
3
|
+
/**
|
|
4
|
+
* Entity persistence types and adapter interface (v3).
|
|
5
|
+
*
|
|
6
|
+
* Provides snapshot and event journal persistence for entity-machine
|
|
7
|
+
* state across deactivation/reactivation cycles.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/** Service tag for PersistenceAdapter — resolve from context for shared infra. */
|
|
12
|
+
const PersistenceAdapter = Context.GenericTag("@effect-machine/cluster/PersistenceAdapter");
|
|
13
|
+
//#endregion
|
|
14
|
+
export { PersistenceAdapter };
|
|
@@ -15,12 +15,15 @@ interface ToEntityOptions {
|
|
|
15
15
|
/**
|
|
16
16
|
* Default RPC protocol for entity machines.
|
|
17
17
|
*
|
|
18
|
-
* - `Send` - Send event to machine, returns new state
|
|
18
|
+
* - `Send` - Send event to machine (fire-and-forget), returns new state
|
|
19
|
+
* - `Ask` - Send event and get domain reply (typed via Event.reply() schemas)
|
|
19
20
|
* - `GetState` - Get current state
|
|
20
21
|
*/
|
|
21
22
|
type EntityRpcs<StateSchema extends Schema.Schema.Any, EventSchema extends Schema.Schema.Any> = readonly [Rpc.Rpc<"Send", Schema.Struct<{
|
|
22
23
|
readonly event: EventSchema;
|
|
23
|
-
}>, StateSchema, typeof Schema.Never, never>, Rpc.Rpc<"
|
|
24
|
+
}>, StateSchema, typeof Schema.Never, never>, Rpc.Rpc<"Ask", Schema.Struct<{
|
|
25
|
+
readonly event: EventSchema;
|
|
26
|
+
}>, typeof Schema.Unknown, typeof Schema.Never, never>, Rpc.Rpc<"GetState", typeof Schema.Void, StateSchema, typeof Schema.Never, never>];
|
|
24
27
|
/**
|
|
25
28
|
* Generate an Entity definition from a machine.
|
|
26
29
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MissingSchemaError } from "../errors.js";
|
|
2
|
+
import { Schema } from "effect";
|
|
2
3
|
import { Entity } from "@effect/cluster";
|
|
3
4
|
import { Rpc } from "@effect/rpc";
|
|
4
5
|
//#region src/cluster/to-entity.ts
|
|
@@ -42,10 +43,17 @@ const toEntity = (machine, options) => {
|
|
|
42
43
|
const stateSchema = machine.stateSchema;
|
|
43
44
|
const eventSchema = machine.eventSchema;
|
|
44
45
|
if (stateSchema === void 0 || eventSchema === void 0) throw new MissingSchemaError({ operation: "toEntity" });
|
|
45
|
-
return Entity.make(options.type, [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
return Entity.make(options.type, [
|
|
47
|
+
Rpc.make("Send", {
|
|
48
|
+
payload: { event: eventSchema },
|
|
49
|
+
success: stateSchema
|
|
50
|
+
}),
|
|
51
|
+
Rpc.make("Ask", {
|
|
52
|
+
payload: { event: eventSchema },
|
|
53
|
+
success: Schema.Unknown
|
|
54
|
+
}),
|
|
55
|
+
Rpc.make("GetState", { success: stateSchema })
|
|
56
|
+
]);
|
|
49
57
|
};
|
|
50
58
|
//#endregion
|
|
51
59
|
export { toEntity };
|
package/v3/dist/errors.d.ts
CHANGED
|
@@ -72,5 +72,20 @@ declare const NoReplyError_base: Schema.TaggedErrorClass<NoReplyError, "NoReplyE
|
|
|
72
72
|
}>;
|
|
73
73
|
/** ask() was used but the transition handler did not call reply */
|
|
74
74
|
declare class NoReplyError extends NoReplyError_base {}
|
|
75
|
+
declare const PersistenceError_base: Schema.TaggedErrorClass<PersistenceError, "PersistenceError", {
|
|
76
|
+
readonly _tag: Schema.tag<"PersistenceError">;
|
|
77
|
+
} & {
|
|
78
|
+
message: typeof Schema.String;
|
|
79
|
+
}>;
|
|
80
|
+
/** Persistence adapter operation failed */
|
|
81
|
+
declare class PersistenceError extends PersistenceError_base {}
|
|
82
|
+
declare const VersionConflictError_base: Schema.TaggedErrorClass<VersionConflictError, "VersionConflictError", {
|
|
83
|
+
readonly _tag: Schema.tag<"VersionConflictError">;
|
|
84
|
+
} & {
|
|
85
|
+
expected: typeof Schema.Number;
|
|
86
|
+
actual: typeof Schema.Number;
|
|
87
|
+
}>;
|
|
88
|
+
/** Optimistic locking failure — stored version doesn't match expected */
|
|
89
|
+
declare class VersionConflictError extends VersionConflictError_base {}
|
|
75
90
|
//#endregion
|
|
76
|
-
export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
|
|
91
|
+
export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError, VersionConflictError };
|
package/v3/dist/errors.js
CHANGED
|
@@ -39,5 +39,12 @@ var NoReplyError = class extends Schema.TaggedError()("NoReplyError", {
|
|
|
39
39
|
actorId: Schema.String,
|
|
40
40
|
eventTag: Schema.String
|
|
41
41
|
}) {};
|
|
42
|
+
/** Persistence adapter operation failed */
|
|
43
|
+
var PersistenceError = class extends Schema.TaggedError()("PersistenceError", { message: Schema.String }) {};
|
|
44
|
+
/** Optimistic locking failure — stored version doesn't match expected */
|
|
45
|
+
var VersionConflictError = class extends Schema.TaggedError()("VersionConflictError", {
|
|
46
|
+
expected: Schema.Number,
|
|
47
|
+
actual: Schema.Number
|
|
48
|
+
}) {};
|
|
42
49
|
//#endregion
|
|
43
|
-
export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
|
|
50
|
+
export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError, VersionConflictError };
|
package/v3/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { EffectHandlers, EffectSlot, EffectSlots, EffectsDef, EffectsSchema, GuardHandlers, GuardSlot, GuardSlots, GuardsDef, GuardsSchema, MachineContext, Slot } from "./slot.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ReplyResult } from "./internal/utils.js";
|
|
3
|
+
import { Event, MachineEventSchema, MachineStateSchema, ReplyFields, State } from "./schema.js";
|
|
3
4
|
import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError } from "./errors.js";
|
|
4
5
|
import { ProcessEventResult } from "./internal/transition.js";
|
|
5
6
|
import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, ProvideHandlers, SpawnEffect, StateHandlerContext, TaskOptions, Transition, machine_d_exports } from "./machine.js";
|
|
6
7
|
import { ActorRef, ActorRefSync, ActorSystem, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
|
|
7
8
|
import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
|
|
8
9
|
import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
|
|
9
|
-
export { type ActorRef, type ActorRefSync, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type BuiltMachine, DuplicateActorError, type EffectEvent, type EffectSlots, type EffectsDef, type EffectsSchema, type ErrorEvent, Event, type EventReceivedEvent, type GuardHandlers, type GuardSlot, type GuardSlots, type GuardsDef, type GuardsSchema, type HandlerContext, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, NoReplyError, type ProcessEventResult, type ProvideHandlers, ProvisionValidationError, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TracingInspectorOptions, type Transition, type TransitionEvent, type TransitionInfo, UnprovidedSlotsError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
|
|
10
|
+
export { type ActorRef, type ActorRefSync, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type BuiltMachine, DuplicateActorError, type EffectEvent, type EffectSlots, type EffectsDef, type EffectsSchema, type ErrorEvent, Event, type EventReceivedEvent, type GuardHandlers, type GuardSlot, type GuardSlots, type GuardsDef, type GuardsSchema, type HandlerContext, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, NoReplyError, type ProcessEventResult, type ProvideHandlers, ProvisionValidationError, type ReplyFields, type ReplyResult, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TracingInspectorOptions, type Transition, type TransitionEvent, type TransitionInfo, UnprovidedSlotsError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
|
|
@@ -29,6 +29,20 @@ type FullStateBrand<D extends Record<string, unknown>> = StateBrand & SchemaIdBr
|
|
|
29
29
|
* Full event brand: combines base event brand with schema-specific brand
|
|
30
30
|
*/
|
|
31
31
|
type FullEventBrand<D extends Record<string, unknown>> = EventBrand & SchemaIdBrand<D>;
|
|
32
|
+
/**
|
|
33
|
+
* Brand that carries the reply type for an event variant.
|
|
34
|
+
* Present only on events defined with Event.reply().
|
|
35
|
+
*/
|
|
36
|
+
declare const ReplyTypeId: unique symbol;
|
|
37
|
+
type ReplyTypeId = typeof ReplyTypeId;
|
|
38
|
+
interface ReplyTypeBrand<R> extends Brand.Brand<ReplyTypeId> {
|
|
39
|
+
readonly _ReplyType: R;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract the reply type from a branded event value.
|
|
43
|
+
* Returns `never` if the event has no reply schema.
|
|
44
|
+
*/
|
|
45
|
+
type ExtractReply<E> = E extends ReplyTypeBrand<infer R> ? R : never;
|
|
32
46
|
/**
|
|
33
47
|
* Value or constructor for a tagged type.
|
|
34
48
|
* Accepts both plain values (empty structs) and constructor functions (non-empty structs).
|
|
@@ -37,4 +51,4 @@ type TaggedOrConstructor<T extends {
|
|
|
37
51
|
readonly _tag: string;
|
|
38
52
|
}> = T | ((...args: never[]) => T);
|
|
39
53
|
//#endregion
|
|
40
|
-
export { BrandedEvent, BrandedState, EventBrand, EventTypeId, FullEventBrand, FullStateBrand, SchemaIdBrand, StateBrand, StateTypeId, TaggedOrConstructor };
|
|
54
|
+
export { BrandedEvent, BrandedState, EventBrand, EventTypeId, ExtractReply, FullEventBrand, FullStateBrand, ReplyTypeBrand, ReplyTypeId, SchemaIdBrand, StateBrand, StateTypeId, TaggedOrConstructor };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
|
|
2
|
+
import { NoReplyError } from "../errors.js";
|
|
3
|
+
import { ProcessEventHooks } from "./transition.js";
|
|
4
|
+
import { Machine, MachineRef } from "../machine.js";
|
|
5
|
+
import { ActorSystem } from "../actor.js";
|
|
6
|
+
import { Deferred, Effect, Queue, Scope } from "effect";
|
|
7
|
+
|
|
8
|
+
//#region src/internal/runtime.d.ts
|
|
9
|
+
/** @internal */
|
|
10
|
+
type RuntimeQueuedEvent<E> = {
|
|
11
|
+
readonly _tag: "send";
|
|
12
|
+
readonly event: E;
|
|
13
|
+
} | {
|
|
14
|
+
readonly _tag: "sendWait";
|
|
15
|
+
readonly event: E;
|
|
16
|
+
readonly done: Deferred.Deferred<void>;
|
|
17
|
+
} | {
|
|
18
|
+
readonly _tag: "ask";
|
|
19
|
+
readonly event: E;
|
|
20
|
+
readonly reply: Deferred.Deferred<unknown, NoReplyError>;
|
|
21
|
+
};
|
|
22
|
+
/** @internal */
|
|
23
|
+
interface RuntimeHandle<S, E> {
|
|
24
|
+
/** Enqueue a fire-and-forget event */
|
|
25
|
+
readonly send: (event: E) => Effect.Effect<void>;
|
|
26
|
+
/** Enqueue event and wait for processing to complete (for RPC Send) */
|
|
27
|
+
readonly sendWait: (event: E) => Effect.Effect<void>;
|
|
28
|
+
/** Enqueue an ask event, returns the reply value */
|
|
29
|
+
readonly ask: (event: E) => Effect.Effect<unknown, NoReplyError>;
|
|
30
|
+
/** Get current state */
|
|
31
|
+
readonly getState: Effect.Effect<S>;
|
|
32
|
+
/** Whether the runtime has stopped (final state reached) */
|
|
33
|
+
readonly isStopped: Effect.Effect<boolean>;
|
|
34
|
+
/** Stop the runtime (interrupt event loop, clean up) */
|
|
35
|
+
readonly stop: Effect.Effect<void>;
|
|
36
|
+
}
|
|
37
|
+
/** @internal */
|
|
38
|
+
interface RuntimeConfig<S, E> {
|
|
39
|
+
readonly actorId: string;
|
|
40
|
+
readonly hooks?: ProcessEventHooks<S, E>;
|
|
41
|
+
/**
|
|
42
|
+
* Custom queue factory. Default: `Queue.unbounded()`.
|
|
43
|
+
* Use `Queue.sliding(n)` or `Queue.dropping(n)` for bounded queues.
|
|
44
|
+
*/
|
|
45
|
+
readonly queueFactory?: Effect.Effect<Queue.Queue<RuntimeQueuedEvent<E>>>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Create a runtime for a machine. Returns a handle for sending events
|
|
49
|
+
* and querying state. The runtime owns:
|
|
50
|
+
* - Single event queue (all events serialized)
|
|
51
|
+
* - Event loop fiber
|
|
52
|
+
* - Postpone buffer
|
|
53
|
+
* - Background effects
|
|
54
|
+
* - State scope (spawn effects)
|
|
55
|
+
* - Final state detection
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
*/
|
|
59
|
+
declare const createRuntime: <S extends {
|
|
60
|
+
readonly _tag: string;
|
|
61
|
+
}, E extends {
|
|
62
|
+
readonly _tag: string;
|
|
63
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, any, any, GD, EFD>, system: ActorSystem, config: RuntimeConfig<S, E>) => Effect.Effect<RuntimeHandle<S, E>, never, Scope.Scope | Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
|
|
64
|
+
//#endregion
|
|
65
|
+
export { RuntimeConfig, RuntimeHandle, RuntimeQueuedEvent, createRuntime };
|