effect-machine 0.10.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.
Files changed (81) hide show
  1. package/README.md +65 -62
  2. package/dist/actor.d.ts +35 -97
  3. package/dist/actor.js +69 -98
  4. package/dist/cluster/adapters/in-memory.d.ts +28 -0
  5. package/dist/cluster/adapters/in-memory.js +79 -0
  6. package/dist/cluster/entity-actor-ref.d.ts +56 -0
  7. package/dist/cluster/entity-actor-ref.js +33 -0
  8. package/dist/cluster/entity-machine.d.ts +31 -49
  9. package/dist/cluster/entity-machine.js +167 -52
  10. package/dist/cluster/index.d.ts +5 -2
  11. package/dist/cluster/index.js +4 -1
  12. package/dist/cluster/persistence.d.ts +49 -0
  13. package/dist/cluster/persistence.js +18 -0
  14. package/dist/cluster/to-entity.d.ts +9 -3
  15. package/dist/cluster/to-entity.js +16 -4
  16. package/dist/errors.d.ts +12 -1
  17. package/dist/errors.js +8 -1
  18. package/dist/index.d.ts +5 -8
  19. package/dist/index.js +1 -6
  20. package/dist/internal/brands.d.ts +14 -1
  21. package/dist/internal/runtime.d.ts +67 -0
  22. package/dist/internal/runtime.js +248 -0
  23. package/dist/internal/transition.d.ts +6 -1
  24. package/dist/internal/transition.js +16 -4
  25. package/dist/internal/utils.d.ts +42 -6
  26. package/dist/internal/utils.js +28 -6
  27. package/dist/machine.d.ts +55 -46
  28. package/dist/machine.js +74 -13
  29. package/dist/schema.d.ts +35 -34
  30. package/dist/schema.js +32 -3
  31. package/dist/testing.js +4 -2
  32. package/package.json +4 -4
  33. package/v3/dist/actor.d.ts +29 -96
  34. package/v3/dist/actor.js +52 -97
  35. package/v3/dist/cluster/adapters/in-memory.d.ts +15 -0
  36. package/v3/dist/cluster/adapters/in-memory.js +62 -0
  37. package/v3/dist/cluster/entity-actor-ref.d.ts +49 -0
  38. package/v3/dist/cluster/entity-actor-ref.js +19 -0
  39. package/v3/dist/cluster/entity-machine.d.ts +34 -49
  40. package/v3/dist/cluster/entity-machine.js +134 -50
  41. package/v3/dist/cluster/index.d.ts +5 -2
  42. package/v3/dist/cluster/index.js +4 -1
  43. package/v3/dist/cluster/persistence.d.ts +48 -0
  44. package/v3/dist/cluster/persistence.js +14 -0
  45. package/v3/dist/cluster/to-entity.d.ts +5 -2
  46. package/v3/dist/cluster/to-entity.js +12 -4
  47. package/v3/dist/errors.d.ts +16 -1
  48. package/v3/dist/errors.js +8 -1
  49. package/v3/dist/index.d.ts +5 -8
  50. package/v3/dist/index.js +1 -6
  51. package/v3/dist/internal/brands.d.ts +15 -1
  52. package/v3/dist/internal/runtime.d.ts +65 -0
  53. package/v3/dist/internal/runtime.js +236 -0
  54. package/v3/dist/internal/transition.d.ts +5 -0
  55. package/v3/dist/internal/transition.js +15 -3
  56. package/v3/dist/internal/utils.d.ts +42 -6
  57. package/v3/dist/internal/utils.js +28 -6
  58. package/v3/dist/machine.d.ts +48 -46
  59. package/v3/dist/machine.js +71 -13
  60. package/v3/dist/schema.d.ts +35 -34
  61. package/v3/dist/schema.js +29 -3
  62. package/dist/persistence/adapter.d.ts +0 -135
  63. package/dist/persistence/adapter.js +0 -25
  64. package/dist/persistence/adapters/in-memory.d.ts +0 -32
  65. package/dist/persistence/adapters/in-memory.js +0 -174
  66. package/dist/persistence/index.d.ts +0 -5
  67. package/dist/persistence/index.js +0 -5
  68. package/dist/persistence/persistent-actor.d.ts +0 -50
  69. package/dist/persistence/persistent-actor.js +0 -404
  70. package/dist/persistence/persistent-machine.d.ts +0 -105
  71. package/dist/persistence/persistent-machine.js +0 -22
  72. package/v3/dist/persistence/adapter.d.ts +0 -138
  73. package/v3/dist/persistence/adapter.js +0 -25
  74. package/v3/dist/persistence/adapters/in-memory.d.ts +0 -32
  75. package/v3/dist/persistence/adapters/in-memory.js +0 -174
  76. package/v3/dist/persistence/index.d.ts +0 -5
  77. package/v3/dist/persistence/index.js +0 -5
  78. package/v3/dist/persistence/persistent-actor.d.ts +0 -50
  79. package/v3/dist/persistence/persistent-actor.js +0 -404
  80. package/v3/dist/persistence/persistent-machine.d.ts +0 -105
  81. package/v3/dist/persistence/persistent-machine.js +0 -22
@@ -1,80 +1,164 @@
1
- import { processEventCore, runSpawnEffects } from "../internal/transition.js";
1
+ import { stubSystem } from "../internal/utils.js";
2
+ import { BuiltMachine, replay } from "../machine.js";
2
3
  import { ActorSystem } from "../actor.js";
3
- import { Effect, Option, Queue, Ref, Scope } from "effect";
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
- * The layer:
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 layer = Effect.fn("effect-machine.cluster.layer")(function* () {
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
- if (Option.isNone(existingSystem)) return yield* Effect.die("EntityMachine requires ActorSystem in context");
55
- const system = existingSystem.value;
56
- const internalQueue = yield* Queue.unbounded();
57
- const clusterSend = Effect.fn("effect-machine.cluster.self.send")(function* (event) {
58
- yield* Queue.offer(internalQueue, event);
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
- const self = {
61
- send: clusterSend,
62
- cast: clusterSend,
63
- spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
64
- };
65
- const stateRef = yield* Ref.make(initialState);
66
- const stateScopeRef = { current: yield* Scope.make() };
67
- yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, system, entityId, options?.hooks?.onError);
68
- const runInternalEvent = Effect.fn("effect-machine.cluster.internalEvent")(function* () {
69
- yield* processEvent(machine, stateRef, yield* Queue.take(internalQueue), self, stateScopeRef, system, options?.hooks);
70
- });
71
- yield* Effect.forkScoped(Effect.forever(runInternalEvent()));
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) => processEvent(machine, stateRef, envelope.payload.event, self, stateScopeRef, system, options?.hooks),
74
- GetState: () => Ref.get(stateRef)
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
- return entity.toLayer(layer());
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 };
@@ -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
- export { EntityMachine, toEntity };
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<"GetState", typeof Schema.Void, StateSchema, typeof Schema.Never, never>];
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, [Rpc.make("Send", {
46
- payload: { event: eventSchema },
47
- success: stateSchema
48
- }), Rpc.make("GetState", { success: stateSchema })]);
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 };
@@ -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 };
@@ -1,13 +1,10 @@
1
1
  import { EffectHandlers, EffectSlot, EffectSlots, EffectsDef, EffectsSchema, GuardHandlers, GuardSlot, GuardSlots, GuardsDef, GuardsSchema, MachineContext, Slot } from "./slot.js";
2
- import { Event, MachineEventSchema, MachineStateSchema, State } from "./schema.js";
3
- import { PersistenceConfig, PersistentMachine, isPersistentMachine } from "./persistence/persistent-machine.js";
2
+ import { ReplyResult } from "./internal/utils.js";
3
+ import { Event, MachineEventSchema, MachineStateSchema, ReplyFields, State } from "./schema.js";
4
4
  import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError } from "./errors.js";
5
5
  import { ProcessEventResult } from "./internal/transition.js";
6
- import { PersistentActorRef, createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
7
- import { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTag, PersistenceError, RestoreFailure, RestoreResult, Snapshot, VersionConflictError } from "./persistence/adapter.js";
8
- import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./persistence/adapters/in-memory.js";
9
- import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, PersistOptions, ProvideHandlers, SpawnEffect, StateHandlerContext, TaskOptions, Transition, machine_d_exports } from "./machine.js";
10
- import { ActorRef, ActorRefSync, ActorSystem, Default, SystemEvent, SystemEventListener } from "./actor.js";
6
+ import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, ProvideHandlers, SpawnEffect, StateHandlerContext, TaskOptions, Transition, machine_d_exports } from "./machine.js";
7
+ import { ActorRef, ActorRefSync, ActorSystem, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
11
8
  import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
12
9
  import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
13
- export { type ActorMetadata, 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, InMemoryPersistenceAdapter, 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 PersistOptions, type PersistedEvent, type PersistenceAdapter, PersistenceAdapterTag, type PersistenceConfig, PersistenceError, type PersistentActorRef, type PersistentMachine, type ProcessEventResult, type ProvideHandlers, ProvisionValidationError, type RestoreFailure, type RestoreResult, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type Snapshot, 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, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, makeInspectorEffect, restorePersistentActor, 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 };
package/v3/dist/index.js CHANGED
@@ -1,13 +1,8 @@
1
1
  import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
2
2
  import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError } from "./errors.js";
3
- import { isPersistentMachine } from "./persistence/persistent-machine.js";
4
3
  import { Slot } from "./slot.js";
5
4
  import { machine_exports } from "./machine.js";
6
- import { PersistenceAdapterTag, PersistenceError, VersionConflictError } from "./persistence/adapter.js";
7
- import { createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
8
5
  import { ActorSystem, Default } from "./actor.js";
9
6
  import { Event, State } from "./schema.js";
10
7
  import { assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
11
- import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./persistence/adapters/in-memory.js";
12
- import "./persistence/index.js";
13
- export { ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, InMemoryPersistenceAdapter, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceAdapterTag, PersistenceError, ProvisionValidationError, Slot, SlotProvisionError, State, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, makeInspectorEffect, restorePersistentActor, simulate, tracingInspector };
8
+ export { ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, Slot, SlotProvisionError, State, 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 };