effect-machine 0.4.0 → 0.7.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 (70) hide show
  1. package/README.md +36 -0
  2. package/dist/actor.d.ts +40 -4
  3. package/dist/actor.js +89 -34
  4. package/dist/cluster/entity-machine.d.ts +2 -2
  5. package/dist/cluster/entity-machine.js +1 -1
  6. package/dist/cluster/to-entity.d.ts +5 -5
  7. package/dist/cluster/to-entity.js +2 -2
  8. package/dist/errors.d.ts +25 -40
  9. package/dist/errors.js +10 -10
  10. package/dist/index.d.ts +2 -2
  11. package/dist/inspection.d.ts +3 -3
  12. package/dist/inspection.js +2 -2
  13. package/dist/internal/brands.d.ts +3 -6
  14. package/dist/internal/inspection.js +5 -1
  15. package/dist/internal/transition.d.ts +2 -2
  16. package/dist/internal/transition.js +6 -6
  17. package/dist/internal/utils.js +11 -2
  18. package/dist/machine.d.ts +5 -5
  19. package/dist/machine.js +9 -5
  20. package/dist/persistence/adapter.d.ts +18 -21
  21. package/dist/persistence/adapter.js +4 -4
  22. package/dist/persistence/adapters/in-memory.js +4 -4
  23. package/dist/persistence/persistent-actor.js +23 -14
  24. package/dist/persistence/persistent-machine.d.ts +3 -3
  25. package/dist/schema.d.ts +4 -4
  26. package/dist/schema.js +2 -2
  27. package/dist/slot.d.ts +3 -3
  28. package/dist/slot.js +2 -2
  29. package/dist-v3/_virtual/_rolldown/runtime.js +18 -0
  30. package/dist-v3/actor.d.ts +291 -0
  31. package/dist-v3/actor.js +459 -0
  32. package/dist-v3/cluster/entity-machine.d.ts +90 -0
  33. package/dist-v3/cluster/entity-machine.js +80 -0
  34. package/dist-v3/cluster/index.d.ts +3 -0
  35. package/dist-v3/cluster/index.js +4 -0
  36. package/dist-v3/cluster/to-entity.d.ts +61 -0
  37. package/dist-v3/cluster/to-entity.js +53 -0
  38. package/dist-v3/errors.d.ts +27 -0
  39. package/dist-v3/errors.js +38 -0
  40. package/dist-v3/index.d.ts +13 -0
  41. package/dist-v3/index.js +14 -0
  42. package/dist-v3/inspection.d.ts +125 -0
  43. package/dist-v3/inspection.js +50 -0
  44. package/dist-v3/internal/brands.d.ts +40 -0
  45. package/dist-v3/internal/brands.js +0 -0
  46. package/dist-v3/internal/inspection.d.ts +11 -0
  47. package/dist-v3/internal/inspection.js +15 -0
  48. package/dist-v3/internal/transition.d.ts +160 -0
  49. package/dist-v3/internal/transition.js +238 -0
  50. package/dist-v3/internal/utils.d.ts +60 -0
  51. package/dist-v3/internal/utils.js +51 -0
  52. package/dist-v3/machine.d.ts +278 -0
  53. package/dist-v3/machine.js +317 -0
  54. package/dist-v3/persistence/adapter.d.ts +125 -0
  55. package/dist-v3/persistence/adapter.js +27 -0
  56. package/dist-v3/persistence/adapters/in-memory.d.ts +32 -0
  57. package/dist-v3/persistence/adapters/in-memory.js +176 -0
  58. package/dist-v3/persistence/index.d.ts +5 -0
  59. package/dist-v3/persistence/index.js +6 -0
  60. package/dist-v3/persistence/persistent-actor.d.ts +49 -0
  61. package/dist-v3/persistence/persistent-actor.js +367 -0
  62. package/dist-v3/persistence/persistent-machine.d.ts +105 -0
  63. package/dist-v3/persistence/persistent-machine.js +24 -0
  64. package/dist-v3/schema.d.ts +141 -0
  65. package/dist-v3/schema.js +165 -0
  66. package/dist-v3/slot.d.ts +130 -0
  67. package/dist-v3/slot.js +99 -0
  68. package/dist-v3/testing.d.ts +136 -0
  69. package/dist-v3/testing.js +138 -0
  70. package/package.json +29 -21
@@ -0,0 +1,317 @@
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { getTag } from "./internal/utils.js";
3
+ import { ProvisionValidationError, SlotProvisionError } from "./errors.js";
4
+ import { persist } from "./persistence/persistent-machine.js";
5
+ import { MachineContextTag } from "./slot.js";
6
+ import { findTransitions, invalidateIndex } from "./internal/transition.js";
7
+ import { createActor } from "./actor.js";
8
+ import { Cause, Effect, Exit, Option, Scope } from "effect";
9
+
10
+ //#region src-v3/machine.ts
11
+ var machine_exports = /* @__PURE__ */ __exportAll({
12
+ BuiltMachine: () => BuiltMachine,
13
+ Machine: () => Machine,
14
+ findTransitions: () => findTransitions,
15
+ make: () => make,
16
+ spawn: () => spawn
17
+ });
18
+ /**
19
+ * A finalized machine ready for spawning.
20
+ *
21
+ * Created by calling `.build()` on a `Machine`. This is the only type
22
+ * accepted by `Machine.spawn` and `ActorSystem.spawn` (regular overload).
23
+ * Testing utilities (`simulate`, `createTestHarness`, etc.) still accept `Machine`.
24
+ */
25
+ var BuiltMachine = class {
26
+ /** @internal */
27
+ _inner;
28
+ /** @internal */
29
+ constructor(machine) {
30
+ this._inner = machine;
31
+ }
32
+ get initial() {
33
+ return this._inner.initial;
34
+ }
35
+ persist(config) {
36
+ return this._inner.persist(config);
37
+ }
38
+ };
39
+ /**
40
+ * Machine definition with fluent builder API.
41
+ *
42
+ * Type parameters:
43
+ * - `State`: The state union type
44
+ * - `Event`: The event union type
45
+ * - `R`: Effect requirements
46
+ * - `_SD`: State schema definition (for compile-time validation)
47
+ * - `_ED`: Event schema definition (for compile-time validation)
48
+ * - `GD`: Guard definitions
49
+ * - `EFD`: Effect definitions
50
+ */
51
+ var Machine = class Machine {
52
+ initial;
53
+ /** @internal */ _transitions;
54
+ /** @internal */ _spawnEffects;
55
+ /** @internal */ _backgroundEffects;
56
+ /** @internal */ _finalStates;
57
+ /** @internal */ _guardsSchema;
58
+ /** @internal */ _effectsSchema;
59
+ /** @internal */ _guardHandlers;
60
+ /** @internal */ _effectHandlers;
61
+ /** @internal */ _slots;
62
+ stateSchema;
63
+ eventSchema;
64
+ /**
65
+ * Context tag for accessing machine state/event/self in slot handlers.
66
+ * Uses shared module-level tag for all machines.
67
+ */
68
+ Context = MachineContextTag;
69
+ get transitions() {
70
+ return this._transitions;
71
+ }
72
+ get spawnEffects() {
73
+ return this._spawnEffects;
74
+ }
75
+ get backgroundEffects() {
76
+ return this._backgroundEffects;
77
+ }
78
+ get finalStates() {
79
+ return this._finalStates;
80
+ }
81
+ get guardsSchema() {
82
+ return this._guardsSchema;
83
+ }
84
+ get effectsSchema() {
85
+ return this._effectsSchema;
86
+ }
87
+ /** @internal */
88
+ constructor(initial, stateSchema, eventSchema, guardsSchema, effectsSchema) {
89
+ this.initial = initial;
90
+ this._transitions = [];
91
+ this._spawnEffects = [];
92
+ this._backgroundEffects = [];
93
+ this._finalStates = /* @__PURE__ */ new Set();
94
+ this._guardsSchema = guardsSchema;
95
+ this._effectsSchema = effectsSchema;
96
+ this._guardHandlers = /* @__PURE__ */ new Map();
97
+ this._effectHandlers = /* @__PURE__ */ new Map();
98
+ this.stateSchema = stateSchema;
99
+ this.eventSchema = eventSchema;
100
+ this._slots = {
101
+ guards: this._guardsSchema !== void 0 ? this._guardsSchema._createSlots((name, params) => Effect.flatMap(Effect.serviceOptional(this.Context).pipe(Effect.orDie), (ctx) => {
102
+ const handler = this._guardHandlers.get(name);
103
+ if (handler === void 0) return Effect.die(new SlotProvisionError({
104
+ slotName: name,
105
+ slotType: "guard"
106
+ }));
107
+ const result = handler(params, ctx);
108
+ return typeof result === "boolean" ? Effect.succeed(result) : result;
109
+ })) : {},
110
+ effects: this._effectsSchema !== void 0 ? this._effectsSchema._createSlots((name, params) => Effect.flatMap(Effect.serviceOptional(this.Context).pipe(Effect.orDie), (ctx) => {
111
+ const handler = this._effectHandlers.get(name);
112
+ if (handler === void 0) return Effect.die(new SlotProvisionError({
113
+ slotName: name,
114
+ slotType: "effect"
115
+ }));
116
+ return handler(params, ctx);
117
+ })) : {}
118
+ };
119
+ }
120
+ on(stateOrStates, event, handler) {
121
+ const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
122
+ for (const s of states) this.addTransition(s, event, handler, false);
123
+ return this;
124
+ }
125
+ reenter(stateOrStates, event, handler) {
126
+ const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
127
+ for (const s of states) this.addTransition(s, event, handler, true);
128
+ return this;
129
+ }
130
+ /**
131
+ * Register a wildcard transition that fires from any state when no specific transition matches.
132
+ * Specific `.on()` transitions always take priority over `.onAny()`.
133
+ */
134
+ onAny(event, handler) {
135
+ const transition = {
136
+ stateTag: "*",
137
+ eventTag: getTag(event),
138
+ handler,
139
+ reenter: false
140
+ };
141
+ this._transitions.push(transition);
142
+ invalidateIndex(this);
143
+ return this;
144
+ }
145
+ /** @internal */
146
+ addTransition(state, event, handler, reenter) {
147
+ const transition = {
148
+ stateTag: getTag(state),
149
+ eventTag: getTag(event),
150
+ handler,
151
+ reenter
152
+ };
153
+ this._transitions.push(transition);
154
+ invalidateIndex(this);
155
+ return this;
156
+ }
157
+ /**
158
+ * State-scoped effect that is forked on state entry and automatically cancelled on state exit.
159
+ * Use effect slots defined via `Slot.Effects` for the actual work.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const MyEffects = Slot.Effects({
164
+ * fetchData: { url: Schema.String },
165
+ * });
166
+ *
167
+ * machine
168
+ * .spawn(State.Loading, ({ effects, state }) => effects.fetchData({ url: state.url }))
169
+ * .build({
170
+ * fetchData: ({ url }, { self }) =>
171
+ * Effect.gen(function* () {
172
+ * yield* Effect.addFinalizer(() => Effect.log("Leaving Loading"));
173
+ * const data = yield* Http.get(url);
174
+ * yield* self.send(Event.Loaded({ data }));
175
+ * }),
176
+ * });
177
+ * ```
178
+ */
179
+ spawn(state, handler) {
180
+ const stateTag = getTag(state);
181
+ this._spawnEffects.push({
182
+ stateTag,
183
+ handler
184
+ });
185
+ invalidateIndex(this);
186
+ return this;
187
+ }
188
+ /**
189
+ * State-scoped task that runs on entry and sends success/failure events.
190
+ * Interrupts do not emit failure events.
191
+ */
192
+ task(state, run, options) {
193
+ const handler = Effect.fn("effect-machine.task")(function* (ctx) {
194
+ const exit = yield* Effect.exit(run(ctx));
195
+ if (Exit.isSuccess(exit)) {
196
+ yield* ctx.self.send(options.onSuccess(exit.value, ctx));
197
+ yield* Effect.yieldNow();
198
+ return;
199
+ }
200
+ const cause = exit.cause;
201
+ if (Cause.isInterruptedOnly(cause)) return;
202
+ if (options.onFailure !== void 0) {
203
+ yield* ctx.self.send(options.onFailure(cause, ctx));
204
+ yield* Effect.yieldNow();
205
+ return;
206
+ }
207
+ return yield* Effect.failCause(cause).pipe(Effect.orDie);
208
+ });
209
+ return this.spawn(state, handler);
210
+ }
211
+ /**
212
+ * Machine-lifetime effect that is forked on actor spawn and runs until the actor stops.
213
+ * Use effect slots defined via `Slot.Effects` for the actual work.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * const MyEffects = Slot.Effects({
218
+ * heartbeat: {},
219
+ * });
220
+ *
221
+ * machine
222
+ * .background(({ effects }) => effects.heartbeat())
223
+ * .build({
224
+ * heartbeat: (_, { self }) =>
225
+ * Effect.forever(
226
+ * Effect.sleep("30 seconds").pipe(Effect.andThen(self.send(Event.Ping)))
227
+ * ),
228
+ * });
229
+ * ```
230
+ */
231
+ background(handler) {
232
+ this._backgroundEffects.push({ handler });
233
+ return this;
234
+ }
235
+ final(state) {
236
+ const stateTag = getTag(state);
237
+ this._finalStates.add(stateTag);
238
+ return this;
239
+ }
240
+ /**
241
+ * Finalize the machine. Returns a `BuiltMachine` — the only type accepted by `Machine.spawn`.
242
+ *
243
+ * - Machines with slots: pass implementations as the first argument.
244
+ * - Machines without slots: call with no arguments.
245
+ */
246
+ build(...args) {
247
+ const handlers = args[0];
248
+ if (handlers !== void 0) {
249
+ const requiredSlots = /* @__PURE__ */ new Set();
250
+ if (this._guardsSchema !== void 0) for (const name of Object.keys(this._guardsSchema.definitions)) requiredSlots.add(name);
251
+ if (this._effectsSchema !== void 0) for (const name of Object.keys(this._effectsSchema.definitions)) requiredSlots.add(name);
252
+ const providedSlots = new Set(Object.keys(handlers));
253
+ const missing = [];
254
+ const extra = [];
255
+ for (const name of requiredSlots) if (!providedSlots.has(name)) missing.push(name);
256
+ for (const name of providedSlots) if (!requiredSlots.has(name)) extra.push(name);
257
+ if (missing.length > 0 || extra.length > 0) throw new ProvisionValidationError({
258
+ missing,
259
+ extra
260
+ });
261
+ const result = new Machine(this.initial, this.stateSchema, this.eventSchema, this._guardsSchema, this._effectsSchema);
262
+ result._transitions = [...this._transitions];
263
+ result._finalStates = new Set(this._finalStates);
264
+ result._spawnEffects = [...this._spawnEffects];
265
+ result._backgroundEffects = [...this._backgroundEffects];
266
+ const anyHandlers = handlers;
267
+ if (this._guardsSchema !== void 0) for (const name of Object.keys(this._guardsSchema.definitions)) result._guardHandlers.set(name, anyHandlers[name]);
268
+ if (this._effectsSchema !== void 0) for (const name of Object.keys(this._effectsSchema.definitions)) result._effectHandlers.set(name, anyHandlers[name]);
269
+ return new BuiltMachine(result);
270
+ }
271
+ return new BuiltMachine(this);
272
+ }
273
+ /** @internal Persist from raw Machine — prefer BuiltMachine.persist() */
274
+ persist(config) {
275
+ return persist(config)(this);
276
+ }
277
+ static make(config) {
278
+ return new Machine(config.initial, config.state, config.event, config.guards, config.effects);
279
+ }
280
+ };
281
+ const make = Machine.make;
282
+ /**
283
+ * Spawn an actor directly without ActorSystem ceremony.
284
+ * Accepts only `BuiltMachine` (call `.build()` first).
285
+ *
286
+ * **Single actor, no registry.** Caller manages lifetime via `actor.stop`.
287
+ * If a `Scope` exists in context, cleanup attaches automatically on scope close.
288
+ *
289
+ * For registry, lookup by ID, persistence, or multi-actor coordination,
290
+ * use `ActorSystemService` / `system.spawn` instead.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * // Fire-and-forget — caller manages lifetime
295
+ * const actor = yield* Machine.spawn(machine.build());
296
+ * yield* actor.send(Event.Start);
297
+ * yield* actor.awaitFinal;
298
+ * yield* actor.stop;
299
+ *
300
+ * // Scope-aware — auto-cleans up on scope close
301
+ * yield* Effect.scoped(Effect.gen(function* () {
302
+ * const actor = yield* Machine.spawn(machine.build());
303
+ * yield* actor.send(Event.Start);
304
+ * // actor.stop called automatically when scope closes
305
+ * }));
306
+ * ```
307
+ */
308
+ const spawnImpl = Effect.fn("effect-machine.spawn")(function* (built, id) {
309
+ const actor = yield* createActor(id ?? `actor-${Math.random().toString(36).slice(2)}`, built._inner);
310
+ const maybeScope = yield* Effect.serviceOption(Scope.Scope);
311
+ if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
312
+ return actor;
313
+ });
314
+ const spawn = spawnImpl;
315
+
316
+ //#endregion
317
+ export { BuiltMachine, Machine, findTransitions, machine_exports, make, spawn };
@@ -0,0 +1,125 @@
1
+ import { DuplicateActorError } from "../errors.js";
2
+ import { PersistentActorRef } from "./persistent-actor.js";
3
+ import { Effect, Option, Schema } from "effect";
4
+
5
+ //#region src-v3/persistence/adapter.d.ts
6
+ /**
7
+ * Metadata for a persisted actor.
8
+ * Used for discovery and filtering during bulk restore.
9
+ */
10
+ interface ActorMetadata {
11
+ readonly id: string;
12
+ /** User-provided identifier for the machine type */
13
+ readonly machineType: string;
14
+ readonly createdAt: number;
15
+ readonly lastActivityAt: number;
16
+ readonly version: number;
17
+ /** Current state _tag value */
18
+ readonly stateTag: string;
19
+ }
20
+ /**
21
+ * Result of a bulk restore operation.
22
+ * Contains both successfully restored actors and failures.
23
+ */
24
+ interface RestoreResult<S extends {
25
+ readonly _tag: string;
26
+ }, E extends {
27
+ readonly _tag: string;
28
+ }, R = never> {
29
+ readonly restored: ReadonlyArray<PersistentActorRef<S, E, R>>;
30
+ readonly failed: ReadonlyArray<RestoreFailure>;
31
+ }
32
+ /**
33
+ * A single restore failure with actor ID and error details.
34
+ */
35
+ interface RestoreFailure {
36
+ readonly id: string;
37
+ readonly error: PersistenceError | DuplicateActorError;
38
+ }
39
+ /**
40
+ * Snapshot of actor state at a point in time
41
+ */
42
+ interface Snapshot<S> {
43
+ readonly state: S;
44
+ readonly version: number;
45
+ readonly timestamp: number;
46
+ }
47
+ /**
48
+ * Persisted event with metadata
49
+ */
50
+ interface PersistedEvent<E> {
51
+ readonly event: E;
52
+ readonly version: number;
53
+ readonly timestamp: number;
54
+ }
55
+ /**
56
+ * Adapter for persisting actor state and events.
57
+ *
58
+ * Implementations handle serialization and storage of snapshots and event journals.
59
+ * Schema parameters ensure type-safe serialization/deserialization.
60
+ * Schemas must have no context requirements (use Schema<S, SI, never>).
61
+ */
62
+ interface PersistenceAdapter {
63
+ /**
64
+ * Save a snapshot of actor state.
65
+ * Implementations should use optimistic locking — fail if version mismatch.
66
+ */
67
+ readonly saveSnapshot: <S, SI>(id: string, snapshot: Snapshot<S>, schema: Schema.Schema<S, SI, never>) => Effect.Effect<void, PersistenceError | VersionConflictError>;
68
+ /**
69
+ * Load the latest snapshot for an actor.
70
+ * Returns None if no snapshot exists.
71
+ */
72
+ readonly loadSnapshot: <S, SI>(id: string, schema: Schema.Schema<S, SI, never>) => Effect.Effect<Option.Option<Snapshot<S>>, PersistenceError>;
73
+ /**
74
+ * Append an event to the actor's event journal.
75
+ */
76
+ readonly appendEvent: <E, EI>(id: string, event: PersistedEvent<E>, schema: Schema.Schema<E, EI, never>) => Effect.Effect<void, PersistenceError>;
77
+ /**
78
+ * Load events from the journal, optionally after a specific version.
79
+ */
80
+ readonly loadEvents: <E, EI>(id: string, schema: Schema.Schema<E, EI, never>, afterVersion?: number) => Effect.Effect<ReadonlyArray<PersistedEvent<E>>, PersistenceError>;
81
+ /**
82
+ * Delete all persisted data for an actor (snapshot + events).
83
+ */
84
+ readonly deleteActor: (id: string) => Effect.Effect<void, PersistenceError>;
85
+ /**
86
+ * List all persisted actor metadata.
87
+ * Optional — adapters without registry support can omit this.
88
+ */
89
+ readonly listActors?: () => Effect.Effect<ReadonlyArray<ActorMetadata>, PersistenceError>;
90
+ /**
91
+ * Save or update actor metadata.
92
+ * Called on spawn and state transitions.
93
+ * Optional — adapters without registry support can omit this.
94
+ */
95
+ readonly saveMetadata?: (metadata: ActorMetadata) => Effect.Effect<void, PersistenceError>;
96
+ /**
97
+ * Delete actor metadata.
98
+ * Called when actor is deleted.
99
+ * Optional — adapters without registry support can omit this.
100
+ */
101
+ readonly deleteMetadata?: (id: string) => Effect.Effect<void, PersistenceError>;
102
+ /**
103
+ * Load metadata for a specific actor by ID.
104
+ * Returns None if no metadata exists.
105
+ * Optional — adapters without registry support can omit this.
106
+ */
107
+ readonly loadMetadata?: (id: string) => Effect.Effect<Option.Option<ActorMetadata>, PersistenceError>;
108
+ }
109
+ declare const PersistenceError_base: any;
110
+ /**
111
+ * Error type for persistence operations
112
+ */
113
+ declare class PersistenceError extends PersistenceError_base {}
114
+ declare const VersionConflictError_base: any;
115
+ /**
116
+ * Version conflict error — snapshot version doesn't match expected
117
+ */
118
+ declare class VersionConflictError extends VersionConflictError_base {}
119
+ declare const PersistenceAdapterTag_base: any;
120
+ /**
121
+ * PersistenceAdapter service tag
122
+ */
123
+ declare class PersistenceAdapterTag extends PersistenceAdapterTag_base {}
124
+ //#endregion
125
+ export { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTag, PersistenceError, RestoreFailure, RestoreResult, Snapshot, VersionConflictError };
@@ -0,0 +1,27 @@
1
+ import { Context, Schema } from "effect";
2
+
3
+ //#region src-v3/persistence/adapter.ts
4
+ /**
5
+ * Error type for persistence operations
6
+ */
7
+ var PersistenceError = class extends Schema.TaggedError()("PersistenceError", {
8
+ operation: Schema.String,
9
+ actorId: Schema.String,
10
+ cause: Schema.optional(Schema.Unknown),
11
+ message: Schema.optional(Schema.String)
12
+ }) {};
13
+ /**
14
+ * Version conflict error — snapshot version doesn't match expected
15
+ */
16
+ var VersionConflictError = class extends Schema.TaggedError()("VersionConflictError", {
17
+ actorId: Schema.String,
18
+ expectedVersion: Schema.Number,
19
+ actualVersion: Schema.Number
20
+ }) {};
21
+ /**
22
+ * PersistenceAdapter service tag
23
+ */
24
+ var PersistenceAdapterTag = class extends Context.Tag("effect-machine/src/persistence/adapter/PersistenceAdapterTag")() {};
25
+
26
+ //#endregion
27
+ export { PersistenceAdapterTag, PersistenceError, VersionConflictError };
@@ -0,0 +1,32 @@
1
+ import { PersistenceAdapter, PersistenceAdapterTag } from "../adapter.js";
2
+ import { Effect, Layer } from "effect";
3
+
4
+ //#region src-v3/persistence/adapters/in-memory.d.ts
5
+ /**
6
+ * Create an in-memory persistence adapter effect.
7
+ * Returns the adapter directly for custom layer composition.
8
+ */
9
+ declare const makeInMemoryPersistenceAdapter: Effect.Effect<PersistenceAdapter, never, never>;
10
+ /**
11
+ * In-memory persistence adapter layer.
12
+ * Data is not persisted across process restarts.
13
+ *
14
+ * NOTE: Each `Effect.provide(InMemoryPersistenceAdapter)` creates a NEW adapter
15
+ * with empty storage. For tests that need persistent storage across multiple
16
+ * runPromise calls, use `makeInMemoryPersistenceAdapter` with a shared scope.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const program = Effect.gen(function* () {
21
+ * const system = yield* ActorSystemService;
22
+ * const actor = yield* system.spawn("my-actor", persistentMachine);
23
+ * // ...
24
+ * }).pipe(
25
+ * Effect.provide(InMemoryPersistenceAdapter),
26
+ * Effect.provide(ActorSystemDefault),
27
+ * );
28
+ * ```
29
+ */
30
+ declare const InMemoryPersistenceAdapter: Layer.Layer<PersistenceAdapterTag>;
31
+ //#endregion
32
+ export { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter };
@@ -0,0 +1,176 @@
1
+ import { PersistenceAdapterTag, PersistenceError, VersionConflictError } from "../adapter.js";
2
+ import { Effect, Layer, Option, Ref, Schema } from "effect";
3
+
4
+ //#region src-v3/persistence/adapters/in-memory.ts
5
+ /**
6
+ * Create an in-memory persistence adapter.
7
+ * Useful for testing and development.
8
+ */
9
+ const make = Effect.gen(function* () {
10
+ const storage = yield* Ref.make(/* @__PURE__ */ new Map());
11
+ const registry = yield* Ref.make(/* @__PURE__ */ new Map());
12
+ const getOrCreateStorage = Effect.fn("effect-machine.persistence.inMemory.getOrCreateStorage")(function* (id) {
13
+ return yield* Ref.modify(storage, (map) => {
14
+ const existing = map.get(id);
15
+ if (existing !== void 0) return [existing, map];
16
+ const newStorage = {
17
+ snapshot: Option.none(),
18
+ events: []
19
+ };
20
+ const newMap = new Map(map);
21
+ newMap.set(id, newStorage);
22
+ return [newStorage, newMap];
23
+ });
24
+ });
25
+ const updateStorage = Effect.fn("effect-machine.persistence.inMemory.updateStorage")(function* (id, update) {
26
+ yield* Ref.update(storage, (map) => {
27
+ const existing = map.get(id);
28
+ if (existing === void 0) return map;
29
+ const newMap = new Map(map);
30
+ newMap.set(id, update(existing));
31
+ return newMap;
32
+ });
33
+ });
34
+ return {
35
+ saveSnapshot: Effect.fn("effect-machine.persistence.inMemory.saveSnapshot")(function* (id, snapshot, schema) {
36
+ const actorStorage = yield* getOrCreateStorage(id);
37
+ if (Option.isSome(actorStorage.snapshot)) {
38
+ const existingVersion = actorStorage.snapshot.value.version;
39
+ if (snapshot.version < existingVersion) return yield* new VersionConflictError({
40
+ actorId: id,
41
+ expectedVersion: existingVersion,
42
+ actualVersion: snapshot.version
43
+ });
44
+ }
45
+ const encoded = yield* Schema.encode(schema)(snapshot.state).pipe(Effect.mapError((cause) => new PersistenceError({
46
+ operation: "saveSnapshot",
47
+ actorId: id,
48
+ cause,
49
+ message: "Failed to encode state"
50
+ })));
51
+ yield* updateStorage(id, (s) => ({
52
+ ...s,
53
+ snapshot: Option.some({
54
+ data: encoded,
55
+ version: snapshot.version,
56
+ timestamp: snapshot.timestamp
57
+ })
58
+ }));
59
+ }),
60
+ loadSnapshot: Effect.fn("effect-machine.persistence.inMemory.loadSnapshot")(function* (id, schema) {
61
+ const actorStorage = yield* getOrCreateStorage(id);
62
+ if (Option.isNone(actorStorage.snapshot)) return Option.none();
63
+ const stored = actorStorage.snapshot.value;
64
+ const decoded = yield* Schema.decode(schema)(stored.data).pipe(Effect.mapError((cause) => new PersistenceError({
65
+ operation: "loadSnapshot",
66
+ actorId: id,
67
+ cause,
68
+ message: "Failed to decode state"
69
+ })));
70
+ return Option.some({
71
+ state: decoded,
72
+ version: stored.version,
73
+ timestamp: stored.timestamp
74
+ });
75
+ }),
76
+ appendEvent: Effect.fn("effect-machine.persistence.inMemory.appendEvent")(function* (id, event, schema) {
77
+ yield* getOrCreateStorage(id);
78
+ const encoded = yield* Schema.encode(schema)(event.event).pipe(Effect.mapError((cause) => new PersistenceError({
79
+ operation: "appendEvent",
80
+ actorId: id,
81
+ cause,
82
+ message: "Failed to encode event"
83
+ })));
84
+ yield* updateStorage(id, (s) => ({
85
+ ...s,
86
+ events: [...s.events, {
87
+ data: encoded,
88
+ version: event.version,
89
+ timestamp: event.timestamp
90
+ }]
91
+ }));
92
+ }),
93
+ loadEvents: Effect.fn("effect-machine.persistence.inMemory.loadEvents")(function* (id, schema, afterVersion) {
94
+ const actorStorage = yield* getOrCreateStorage(id);
95
+ const decoded = [];
96
+ for (const stored of actorStorage.events) {
97
+ if (afterVersion !== void 0 && stored.version <= afterVersion) continue;
98
+ const event = yield* Schema.decode(schema)(stored.data).pipe(Effect.mapError((cause) => new PersistenceError({
99
+ operation: "loadEvents",
100
+ actorId: id,
101
+ cause,
102
+ message: "Failed to decode event"
103
+ })));
104
+ decoded.push({
105
+ event,
106
+ version: stored.version,
107
+ timestamp: stored.timestamp
108
+ });
109
+ }
110
+ return decoded;
111
+ }),
112
+ deleteActor: Effect.fn("effect-machine.persistence.inMemory.deleteActor")(function* (id) {
113
+ yield* Ref.update(storage, (map) => {
114
+ const newMap = new Map(map);
115
+ newMap.delete(id);
116
+ return newMap;
117
+ });
118
+ yield* Ref.update(registry, (map) => {
119
+ const newMap = new Map(map);
120
+ newMap.delete(id);
121
+ return newMap;
122
+ });
123
+ }),
124
+ listActors: Effect.fn("effect-machine.persistence.inMemory.listActors")(function* () {
125
+ const map = yield* Ref.get(registry);
126
+ return Array.from(map.values());
127
+ }),
128
+ saveMetadata: Effect.fn("effect-machine.persistence.inMemory.saveMetadata")(function* (metadata) {
129
+ yield* Ref.update(registry, (map) => {
130
+ const newMap = new Map(map);
131
+ newMap.set(metadata.id, metadata);
132
+ return newMap;
133
+ });
134
+ }),
135
+ deleteMetadata: Effect.fn("effect-machine.persistence.inMemory.deleteMetadata")(function* (id) {
136
+ yield* Ref.update(registry, (map) => {
137
+ const newMap = new Map(map);
138
+ newMap.delete(id);
139
+ return newMap;
140
+ });
141
+ }),
142
+ loadMetadata: Effect.fn("effect-machine.persistence.inMemory.loadMetadata")(function* (id) {
143
+ const meta = (yield* Ref.get(registry)).get(id);
144
+ return meta !== void 0 ? Option.some(meta) : Option.none();
145
+ })
146
+ };
147
+ }).pipe(Effect.withSpan("effect-machine.persistence.inMemory.make"));
148
+ /**
149
+ * Create an in-memory persistence adapter effect.
150
+ * Returns the adapter directly for custom layer composition.
151
+ */
152
+ const makeInMemoryPersistenceAdapter = make;
153
+ /**
154
+ * In-memory persistence adapter layer.
155
+ * Data is not persisted across process restarts.
156
+ *
157
+ * NOTE: Each `Effect.provide(InMemoryPersistenceAdapter)` creates a NEW adapter
158
+ * with empty storage. For tests that need persistent storage across multiple
159
+ * runPromise calls, use `makeInMemoryPersistenceAdapter` with a shared scope.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const program = Effect.gen(function* () {
164
+ * const system = yield* ActorSystemService;
165
+ * const actor = yield* system.spawn("my-actor", persistentMachine);
166
+ * // ...
167
+ * }).pipe(
168
+ * Effect.provide(InMemoryPersistenceAdapter),
169
+ * Effect.provide(ActorSystemDefault),
170
+ * );
171
+ * ```
172
+ */
173
+ const InMemoryPersistenceAdapter = Layer.effect(PersistenceAdapterTag, make);
174
+
175
+ //#endregion
176
+ export { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter };
@@ -0,0 +1,5 @@
1
+ import { PersistenceConfig, PersistentMachine, isPersistentMachine, persist } from "./persistent-machine.js";
2
+ import { PersistentActorRef, createPersistentActor, restorePersistentActor } from "./persistent-actor.js";
3
+ import { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTag, PersistenceError, RestoreFailure, RestoreResult, Snapshot, VersionConflictError } from "./adapter.js";
4
+ import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./adapters/in-memory.js";
5
+ export { type ActorMetadata, InMemoryPersistenceAdapter, type PersistedEvent, type PersistenceAdapter, PersistenceAdapterTag, type PersistenceConfig, PersistenceError, type PersistentActorRef, type PersistentMachine, type RestoreFailure, type RestoreResult, type Snapshot, VersionConflictError, createPersistentActor, isPersistentMachine, makeInMemoryPersistenceAdapter, persist, restorePersistentActor };
@@ -0,0 +1,6 @@
1
+ import { isPersistentMachine, persist } from "./persistent-machine.js";
2
+ import { PersistenceAdapterTag, PersistenceError, VersionConflictError } from "./adapter.js";
3
+ import { createPersistentActor, restorePersistentActor } from "./persistent-actor.js";
4
+ import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./adapters/in-memory.js";
5
+
6
+ export { InMemoryPersistenceAdapter, PersistenceAdapterTag, PersistenceError, VersionConflictError, createPersistentActor, isPersistentMachine, makeInMemoryPersistenceAdapter, persist, restorePersistentActor };