effect-machine 0.3.1 → 0.4.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 (62) hide show
  1. package/README.md +24 -0
  2. package/dist/_virtual/_rolldown/runtime.js +18 -0
  3. package/dist/actor.d.ts +256 -0
  4. package/dist/actor.js +402 -0
  5. package/dist/cluster/entity-machine.d.ts +90 -0
  6. package/dist/cluster/entity-machine.js +80 -0
  7. package/dist/cluster/index.d.ts +3 -0
  8. package/dist/cluster/index.js +4 -0
  9. package/dist/cluster/to-entity.d.ts +64 -0
  10. package/dist/cluster/to-entity.js +53 -0
  11. package/dist/errors.d.ts +61 -0
  12. package/dist/errors.js +38 -0
  13. package/dist/index.d.ts +13 -0
  14. package/dist/index.js +14 -0
  15. package/dist/inspection.d.ts +125 -0
  16. package/dist/inspection.js +50 -0
  17. package/dist/internal/brands.d.ts +40 -0
  18. package/dist/internal/brands.js +0 -0
  19. package/dist/internal/inspection.d.ts +11 -0
  20. package/dist/internal/inspection.js +15 -0
  21. package/dist/internal/transition.d.ts +160 -0
  22. package/dist/internal/transition.js +238 -0
  23. package/dist/internal/utils.d.ts +60 -0
  24. package/dist/internal/utils.js +46 -0
  25. package/dist/machine.d.ts +278 -0
  26. package/dist/machine.js +317 -0
  27. package/{src/persistence/adapter.ts → dist/persistence/adapter.d.ts} +40 -72
  28. package/dist/persistence/adapter.js +27 -0
  29. package/dist/persistence/adapters/in-memory.d.ts +32 -0
  30. package/dist/persistence/adapters/in-memory.js +176 -0
  31. package/dist/persistence/index.d.ts +5 -0
  32. package/dist/persistence/index.js +6 -0
  33. package/dist/persistence/persistent-actor.d.ts +50 -0
  34. package/dist/persistence/persistent-actor.js +358 -0
  35. package/{src/persistence/persistent-machine.ts → dist/persistence/persistent-machine.d.ts} +28 -54
  36. package/dist/persistence/persistent-machine.js +24 -0
  37. package/dist/schema.d.ts +141 -0
  38. package/dist/schema.js +165 -0
  39. package/dist/slot.d.ts +130 -0
  40. package/dist/slot.js +99 -0
  41. package/dist/testing.d.ts +142 -0
  42. package/dist/testing.js +138 -0
  43. package/package.json +28 -14
  44. package/src/actor.ts +0 -1058
  45. package/src/cluster/entity-machine.ts +0 -201
  46. package/src/cluster/index.ts +0 -43
  47. package/src/cluster/to-entity.ts +0 -99
  48. package/src/errors.ts +0 -64
  49. package/src/index.ts +0 -105
  50. package/src/inspection.ts +0 -178
  51. package/src/internal/brands.ts +0 -51
  52. package/src/internal/inspection.ts +0 -18
  53. package/src/internal/transition.ts +0 -489
  54. package/src/internal/utils.ts +0 -80
  55. package/src/machine.ts +0 -836
  56. package/src/persistence/adapters/in-memory.ts +0 -294
  57. package/src/persistence/index.ts +0 -24
  58. package/src/persistence/persistent-actor.ts +0 -791
  59. package/src/schema.ts +0 -362
  60. package/src/slot.ts +0 -281
  61. package/src/testing.ts +0 -284
  62. package/tsconfig.json +0 -65
@@ -1,294 +0,0 @@
1
- import { Effect, Layer, Option, Ref, Schema } from "effect";
2
-
3
- import type { ActorMetadata, PersistedEvent, PersistenceAdapter, Snapshot } from "../adapter.js";
4
- import { PersistenceAdapterTag, PersistenceError, VersionConflictError } from "../adapter.js";
5
-
6
- /**
7
- * In-memory storage for a single actor
8
- */
9
- interface ActorStorage {
10
- snapshot: Option.Option<{
11
- readonly data: unknown;
12
- readonly version: number;
13
- readonly timestamp: number;
14
- }>;
15
- events: Array<{ readonly data: unknown; readonly version: number; readonly timestamp: number }>;
16
- }
17
-
18
- /**
19
- * Create an in-memory persistence adapter.
20
- * Useful for testing and development.
21
- */
22
- const make = Effect.gen(function* () {
23
- const storage = yield* Ref.make(new Map<string, ActorStorage>());
24
- const registry = yield* Ref.make(new Map<string, ActorMetadata>());
25
-
26
- const getOrCreateStorage = Effect.fn("effect-machine.persistence.inMemory.getOrCreateStorage")(
27
- function* (id: string) {
28
- return yield* Ref.modify(storage, (map) => {
29
- const existing = map.get(id);
30
- if (existing !== undefined) {
31
- return [existing, map];
32
- }
33
- const newStorage: ActorStorage = {
34
- snapshot: Option.none(),
35
- events: [],
36
- };
37
- const newMap = new Map(map);
38
- newMap.set(id, newStorage);
39
- return [newStorage, newMap];
40
- });
41
- },
42
- );
43
-
44
- const updateStorage = Effect.fn("effect-machine.persistence.inMemory.updateStorage")(function* (
45
- id: string,
46
- update: (storage: ActorStorage) => ActorStorage,
47
- ) {
48
- yield* Ref.update(storage, (map) => {
49
- const existing = map.get(id);
50
- if (existing === undefined) {
51
- return map;
52
- }
53
- const newMap = new Map(map);
54
- newMap.set(id, update(existing));
55
- return newMap;
56
- });
57
- });
58
-
59
- const saveSnapshot = Effect.fn("effect-machine.persistence.inMemory.saveSnapshot")(function* <
60
- S,
61
- SI,
62
- >(id: string, snapshot: Snapshot<S>, schema: Schema.Schema<S, SI, never>) {
63
- const actorStorage = yield* getOrCreateStorage(id);
64
-
65
- // Optimistic locking: check version
66
- // Reject only if trying to save an older version (strict <)
67
- // Same-version saves are idempotent (allow retries/multiple callers)
68
- if (Option.isSome(actorStorage.snapshot)) {
69
- const existingVersion = actorStorage.snapshot.value.version;
70
- if (snapshot.version < existingVersion) {
71
- return yield* new VersionConflictError({
72
- actorId: id,
73
- expectedVersion: existingVersion,
74
- actualVersion: snapshot.version,
75
- });
76
- }
77
- }
78
-
79
- // Encode state using schema
80
- const encoded = yield* Schema.encode(schema)(snapshot.state).pipe(
81
- Effect.mapError(
82
- (cause) =>
83
- new PersistenceError({
84
- operation: "saveSnapshot",
85
- actorId: id,
86
- cause,
87
- message: "Failed to encode state",
88
- }),
89
- ),
90
- );
91
-
92
- yield* updateStorage(id, (s) => ({
93
- ...s,
94
- snapshot: Option.some({
95
- data: encoded,
96
- version: snapshot.version,
97
- timestamp: snapshot.timestamp,
98
- }),
99
- }));
100
- });
101
-
102
- const loadSnapshot = Effect.fn("effect-machine.persistence.inMemory.loadSnapshot")(function* <
103
- S,
104
- SI,
105
- >(id: string, schema: Schema.Schema<S, SI, never>) {
106
- const actorStorage = yield* getOrCreateStorage(id);
107
-
108
- if (Option.isNone(actorStorage.snapshot)) {
109
- return Option.none();
110
- }
111
-
112
- const stored = actorStorage.snapshot.value;
113
-
114
- // Decode state using schema
115
- const decoded = yield* Schema.decode(schema)(stored.data as SI).pipe(
116
- Effect.mapError(
117
- (cause) =>
118
- new PersistenceError({
119
- operation: "loadSnapshot",
120
- actorId: id,
121
- cause,
122
- message: "Failed to decode state",
123
- }),
124
- ),
125
- );
126
-
127
- return Option.some({
128
- state: decoded,
129
- version: stored.version,
130
- timestamp: stored.timestamp,
131
- });
132
- });
133
-
134
- const appendEvent = Effect.fn("effect-machine.persistence.inMemory.appendEvent")(function* <
135
- E,
136
- EI,
137
- >(id: string, event: PersistedEvent<E>, schema: Schema.Schema<E, EI, never>) {
138
- yield* getOrCreateStorage(id);
139
-
140
- // Encode event using schema
141
- const encoded = yield* Schema.encode(schema)(event.event).pipe(
142
- Effect.mapError(
143
- (cause) =>
144
- new PersistenceError({
145
- operation: "appendEvent",
146
- actorId: id,
147
- cause,
148
- message: "Failed to encode event",
149
- }),
150
- ),
151
- );
152
-
153
- yield* updateStorage(id, (s) => ({
154
- ...s,
155
- events: [
156
- ...s.events,
157
- {
158
- data: encoded,
159
- version: event.version,
160
- timestamp: event.timestamp,
161
- },
162
- ],
163
- }));
164
- });
165
-
166
- const loadEvents = Effect.fn("effect-machine.persistence.inMemory.loadEvents")(function* <E, EI>(
167
- id: string,
168
- schema: Schema.Schema<E, EI, never>,
169
- afterVersion?: number,
170
- ) {
171
- const actorStorage = yield* getOrCreateStorage(id);
172
-
173
- // Single pass - skip filtered events inline instead of creating intermediate array
174
- const decoded: PersistedEvent<E>[] = [];
175
- for (const stored of actorStorage.events) {
176
- if (afterVersion !== undefined && stored.version <= afterVersion) continue;
177
-
178
- const event = yield* Schema.decode(schema)(stored.data as EI).pipe(
179
- Effect.mapError(
180
- (cause) =>
181
- new PersistenceError({
182
- operation: "loadEvents",
183
- actorId: id,
184
- cause,
185
- message: "Failed to decode event",
186
- }),
187
- ),
188
- );
189
- decoded.push({
190
- event,
191
- version: stored.version,
192
- timestamp: stored.timestamp,
193
- });
194
- }
195
-
196
- return decoded;
197
- });
198
-
199
- const deleteActor = Effect.fn("effect-machine.persistence.inMemory.deleteActor")(function* (
200
- id: string,
201
- ) {
202
- yield* Ref.update(storage, (map) => {
203
- const newMap = new Map(map);
204
- newMap.delete(id);
205
- return newMap;
206
- });
207
- // Also delete metadata
208
- yield* Ref.update(registry, (map) => {
209
- const newMap = new Map(map);
210
- newMap.delete(id);
211
- return newMap;
212
- });
213
- });
214
-
215
- const listActors = Effect.fn("effect-machine.persistence.inMemory.listActors")(function* () {
216
- const map = yield* Ref.get(registry);
217
- return Array.from(map.values());
218
- });
219
-
220
- const saveMetadata = Effect.fn("effect-machine.persistence.inMemory.saveMetadata")(function* (
221
- metadata: ActorMetadata,
222
- ) {
223
- yield* Ref.update(registry, (map) => {
224
- const newMap = new Map(map);
225
- newMap.set(metadata.id, metadata);
226
- return newMap;
227
- });
228
- });
229
-
230
- const deleteMetadata = Effect.fn("effect-machine.persistence.inMemory.deleteMetadata")(function* (
231
- id: string,
232
- ) {
233
- yield* Ref.update(registry, (map) => {
234
- const newMap = new Map(map);
235
- newMap.delete(id);
236
- return newMap;
237
- });
238
- });
239
-
240
- const loadMetadata = Effect.fn("effect-machine.persistence.inMemory.loadMetadata")(function* (
241
- id: string,
242
- ) {
243
- const map = yield* Ref.get(registry);
244
- const meta = map.get(id);
245
- return meta !== undefined ? Option.some(meta) : Option.none();
246
- });
247
-
248
- const adapter: PersistenceAdapter = {
249
- saveSnapshot,
250
- loadSnapshot,
251
- appendEvent,
252
- loadEvents,
253
- deleteActor,
254
-
255
- // Registry methods for actor discovery
256
- listActors,
257
- saveMetadata,
258
- deleteMetadata,
259
- loadMetadata,
260
- };
261
-
262
- return adapter;
263
- }).pipe(Effect.withSpan("effect-machine.persistence.inMemory.make"));
264
-
265
- /**
266
- * Create an in-memory persistence adapter effect.
267
- * Returns the adapter directly for custom layer composition.
268
- */
269
- export const makeInMemoryPersistenceAdapter = make;
270
-
271
- /**
272
- * In-memory persistence adapter layer.
273
- * Data is not persisted across process restarts.
274
- *
275
- * NOTE: Each `Effect.provide(InMemoryPersistenceAdapter)` creates a NEW adapter
276
- * with empty storage. For tests that need persistent storage across multiple
277
- * runPromise calls, use `makeInMemoryPersistenceAdapter` with a shared scope.
278
- *
279
- * @example
280
- * ```ts
281
- * const program = Effect.gen(function* () {
282
- * const system = yield* ActorSystemService;
283
- * const actor = yield* system.spawn("my-actor", persistentMachine);
284
- * // ...
285
- * }).pipe(
286
- * Effect.provide(InMemoryPersistenceAdapter),
287
- * Effect.provide(ActorSystemDefault),
288
- * );
289
- * ```
290
- */
291
- export const InMemoryPersistenceAdapter: Layer.Layer<PersistenceAdapterTag> = Layer.effect(
292
- PersistenceAdapterTag,
293
- make,
294
- );
@@ -1,24 +0,0 @@
1
- // Core types
2
- export type {
3
- ActorMetadata,
4
- PersistedEvent,
5
- PersistenceAdapter,
6
- RestoreFailure,
7
- RestoreResult,
8
- Snapshot,
9
- } from "./adapter.js";
10
- export { PersistenceAdapterTag, PersistenceError, VersionConflictError } from "./adapter.js";
11
-
12
- // Persistent machine
13
- export type { PersistenceConfig, PersistentMachine } from "./persistent-machine.js";
14
- export { isPersistentMachine, persist } from "./persistent-machine.js";
15
-
16
- // Persistent actor
17
- export type { PersistentActorRef } from "./persistent-actor.js";
18
- export { createPersistentActor, restorePersistentActor } from "./persistent-actor.js";
19
-
20
- // Adapters
21
- export {
22
- InMemoryPersistenceAdapter,
23
- makeInMemoryPersistenceAdapter,
24
- } from "./adapters/in-memory.js";