@voidhash/mimic-effect 1.0.0-beta.16 → 1.0.0-beta.17

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 (99) hide show
  1. package/dist/ColdStorage.cjs +1 -1
  2. package/dist/ColdStorage.d.cts +2 -2
  3. package/dist/ColdStorage.d.cts.map +1 -1
  4. package/dist/ColdStorage.d.mts +2 -2
  5. package/dist/ColdStorage.d.mts.map +1 -1
  6. package/dist/ColdStorage.mjs +2 -2
  7. package/dist/ColdStorage.mjs.map +1 -1
  8. package/dist/DocumentInstance.cjs +13 -13
  9. package/dist/DocumentInstance.mjs +13 -13
  10. package/dist/DocumentInstance.mjs.map +1 -1
  11. package/dist/Errors.d.cts +8 -8
  12. package/dist/Errors.d.cts.map +1 -1
  13. package/dist/Errors.d.mts +8 -8
  14. package/dist/Errors.d.mts.map +1 -1
  15. package/dist/HotStorage.cjs +1 -1
  16. package/dist/HotStorage.d.cts +2 -2
  17. package/dist/HotStorage.d.mts +2 -2
  18. package/dist/HotStorage.mjs +2 -2
  19. package/dist/HotStorage.mjs.map +1 -1
  20. package/dist/Metrics.cjs +6 -6
  21. package/dist/Metrics.d.cts +21 -23
  22. package/dist/Metrics.d.cts.map +1 -1
  23. package/dist/Metrics.d.mts +21 -23
  24. package/dist/Metrics.d.mts.map +1 -1
  25. package/dist/Metrics.mjs +7 -7
  26. package/dist/Metrics.mjs.map +1 -1
  27. package/dist/MimicAuthService.cjs +1 -1
  28. package/dist/MimicAuthService.d.cts +2 -2
  29. package/dist/MimicAuthService.d.cts.map +1 -1
  30. package/dist/MimicAuthService.d.mts +2 -2
  31. package/dist/MimicAuthService.d.mts.map +1 -1
  32. package/dist/MimicAuthService.mjs +2 -2
  33. package/dist/MimicAuthService.mjs.map +1 -1
  34. package/dist/MimicClusterServerEngine.cjs +38 -41
  35. package/dist/MimicClusterServerEngine.d.cts +1 -1
  36. package/dist/MimicClusterServerEngine.d.mts +1 -1
  37. package/dist/MimicClusterServerEngine.mjs +31 -34
  38. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  39. package/dist/MimicServer.cjs +23 -23
  40. package/dist/MimicServer.d.cts +3 -3
  41. package/dist/MimicServer.d.cts.map +1 -1
  42. package/dist/MimicServer.d.mts +3 -3
  43. package/dist/MimicServer.d.mts.map +1 -1
  44. package/dist/MimicServer.mjs +22 -22
  45. package/dist/MimicServer.mjs.map +1 -1
  46. package/dist/MimicServerEngine.cjs +13 -13
  47. package/dist/MimicServerEngine.d.cts +2 -2
  48. package/dist/MimicServerEngine.d.mts +2 -2
  49. package/dist/MimicServerEngine.mjs +14 -14
  50. package/dist/MimicServerEngine.mjs.map +1 -1
  51. package/dist/PresenceManager.cjs +4 -4
  52. package/dist/PresenceManager.d.cts +2 -2
  53. package/dist/PresenceManager.d.mts +2 -2
  54. package/dist/PresenceManager.mjs +5 -5
  55. package/dist/PresenceManager.mjs.map +1 -1
  56. package/dist/Types.d.cts +1 -1
  57. package/dist/Types.d.mts +1 -1
  58. package/dist/testing/ColdStorageTestSuite.cjs +3 -3
  59. package/dist/testing/ColdStorageTestSuite.mjs +3 -3
  60. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -1
  61. package/dist/testing/HotStorageTestSuite.cjs +13 -13
  62. package/dist/testing/HotStorageTestSuite.mjs +13 -13
  63. package/dist/testing/HotStorageTestSuite.mjs.map +1 -1
  64. package/dist/testing/StorageIntegrationTestSuite.cjs +3 -3
  65. package/dist/testing/StorageIntegrationTestSuite.mjs +3 -3
  66. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -1
  67. package/dist/testing/types.d.cts +1 -1
  68. package/dist/testing/types.d.cts.map +1 -1
  69. package/dist/testing/types.d.mts +3 -3
  70. package/dist/testing/types.d.mts.map +1 -1
  71. package/package.json +17 -21
  72. package/src/ColdStorage.ts +4 -5
  73. package/src/DocumentInstance.ts +13 -13
  74. package/src/HotStorage.ts +3 -3
  75. package/src/Metrics.ts +22 -16
  76. package/src/MimicAuthService.ts +3 -3
  77. package/src/MimicClusterServerEngine.ts +35 -35
  78. package/src/MimicServer.ts +26 -30
  79. package/src/MimicServerEngine.ts +15 -15
  80. package/src/PresenceManager.ts +6 -6
  81. package/src/Types.ts +1 -1
  82. package/src/testing/ColdStorageTestSuite.ts +3 -3
  83. package/src/testing/HotStorageTestSuite.ts +17 -17
  84. package/src/testing/StorageIntegrationTestSuite.ts +3 -3
  85. package/.turbo/turbo-build.log +0 -154
  86. package/tests/ColdStorage.test.ts +0 -24
  87. package/tests/DocumentInstance.test.ts +0 -669
  88. package/tests/HotStorage.test.ts +0 -24
  89. package/tests/MimicAuthService.test.ts +0 -153
  90. package/tests/MimicClusterServerEngine.test.ts +0 -587
  91. package/tests/MimicServer.test.ts +0 -142
  92. package/tests/MimicServerEngine.test.ts +0 -547
  93. package/tests/PresenceManager.test.ts +0 -380
  94. package/tests/Protocol.test.ts +0 -190
  95. package/tests/StorageIntegration.test.ts +0 -259
  96. package/tsconfig.build.json +0 -24
  97. package/tsconfig.json +0 -8
  98. package/tsdown.config.ts +0 -18
  99. package/vitest.mts +0 -11
@@ -1,24 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { Effect } from "effect";
3
- import { ColdStorage, ColdStorageTag } from "../src/ColdStorage";
4
- import { ColdStorageTestSuite } from "../src/testing";
5
-
6
- describe("ColdStorage", () => {
7
- describe("InMemory", () => {
8
- // Use the test suite utilities for comprehensive testing
9
- const layer = ColdStorage.InMemory.make();
10
-
11
- // Run all test suite tests
12
- for (const test of ColdStorageTestSuite.makeTests()) {
13
- it(`[${test.category}] ${test.name}`, () =>
14
- Effect.runPromise(test.run.pipe(Effect.provide(layer)))
15
- );
16
- }
17
- });
18
-
19
- describe("Tag", () => {
20
- it("should have correct identifier", () => {
21
- expect(ColdStorageTag.key).toBe("@voidhash/mimic-effect/ColdStorage");
22
- });
23
- });
24
- });
@@ -1,669 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { Effect, Duration, Stream, Fiber } from "effect";
3
- import { Primitive, Document, Transaction } from "@voidhash/mimic";
4
- import { DocumentInstance, type SubmitResult } from "../src/DocumentInstance";
5
- import { ColdStorage, ColdStorageTag } from "../src/ColdStorage";
6
- import { HotStorage, HotStorageTag } from "../src/HotStorage";
7
-
8
- // =============================================================================
9
- // Test Schema
10
- // =============================================================================
11
-
12
- const TestSchema = Primitive.Struct({
13
- title: Primitive.String().default(""),
14
- count: Primitive.Number().default(0),
15
- });
16
-
17
- // =============================================================================
18
- // Helper Functions
19
- // =============================================================================
20
-
21
- const createValidTransaction = (
22
- id: string,
23
- title: string
24
- ): Transaction.Transaction => {
25
- const doc = Document.make(TestSchema);
26
- doc.transaction((root) => {
27
- root.title.set(title);
28
- });
29
- const tx = doc.flush();
30
- return { ...tx, id };
31
- };
32
-
33
- const createEmptyTransaction = (id: string): Transaction.Transaction => ({
34
- id,
35
- ops: [],
36
- timestamp: Date.now(),
37
- });
38
-
39
- // =============================================================================
40
- // Test Config Factory
41
- // =============================================================================
42
-
43
- const makeTestConfig = (options?: {
44
- initial?: { title?: string; count?: number };
45
- }) => ({
46
- schema: TestSchema,
47
- initial: options?.initial,
48
- maxTransactionHistory: 100,
49
- snapshot: {
50
- interval: Duration.minutes(5),
51
- transactionThreshold: 100,
52
- },
53
- });
54
-
55
- // =============================================================================
56
- // DocumentInstance Tests
57
- // =============================================================================
58
-
59
- describe("DocumentInstance", () => {
60
- describe("make", () => {
61
- it("should create a new document instance", async () => {
62
- const result = await Effect.runPromise(
63
- Effect.gen(function* () {
64
- const coldStorage = yield* ColdStorageTag;
65
- const hotStorage = yield* HotStorageTag;
66
-
67
- const instance = yield* DocumentInstance.make(
68
- "doc-1",
69
- makeTestConfig(),
70
- coldStorage,
71
- hotStorage
72
- );
73
-
74
- return {
75
- hasDocument: instance.document !== undefined,
76
- hasPubsub: instance.pubsub !== undefined,
77
- hasSubmit: typeof instance.submit === "function",
78
- hasGetSnapshot: typeof instance.getSnapshot === "function",
79
- hasSaveSnapshot: typeof instance.saveSnapshot === "function",
80
- };
81
- }).pipe(
82
- Effect.provide(ColdStorage.InMemory.make()),
83
- Effect.provide(HotStorage.InMemory.make())
84
- )
85
- );
86
-
87
- expect(result.hasDocument).toBe(true);
88
- expect(result.hasPubsub).toBe(true);
89
- expect(result.hasSubmit).toBe(true);
90
- expect(result.hasGetSnapshot).toBe(true);
91
- expect(result.hasSaveSnapshot).toBe(true);
92
- });
93
-
94
- it("should initialize with default state when no initial provided", async () => {
95
- const result = await Effect.runPromise(
96
- Effect.gen(function* () {
97
- const coldStorage = yield* ColdStorageTag;
98
- const hotStorage = yield* HotStorageTag;
99
-
100
- const instance = yield* DocumentInstance.make(
101
- "doc-default",
102
- makeTestConfig(),
103
- coldStorage,
104
- hotStorage
105
- );
106
-
107
- return instance.getSnapshot();
108
- }).pipe(
109
- Effect.provide(ColdStorage.InMemory.make()),
110
- Effect.provide(HotStorage.InMemory.make())
111
- )
112
- );
113
-
114
- expect(result.version).toBe(0);
115
- expect(result.state).toEqual({ title: "", count: 0 });
116
- });
117
-
118
- it("should use initial state for new documents", async () => {
119
- const result = await Effect.runPromise(
120
- Effect.gen(function* () {
121
- const coldStorage = yield* ColdStorageTag;
122
- const hotStorage = yield* HotStorageTag;
123
-
124
- const instance = yield* DocumentInstance.make(
125
- "doc-initial",
126
- makeTestConfig({ initial: { title: "Initial Title", count: 42 } }),
127
- coldStorage,
128
- hotStorage
129
- );
130
-
131
- return instance.getSnapshot();
132
- }).pipe(
133
- Effect.provide(ColdStorage.InMemory.make()),
134
- Effect.provide(HotStorage.InMemory.make())
135
- )
136
- );
137
-
138
- expect(result.version).toBe(0);
139
- expect(result.state).toEqual({ title: "Initial Title", count: 42 });
140
- });
141
-
142
- it("should restore from cold storage if document exists", async () => {
143
- const result = await Effect.runPromise(
144
- Effect.gen(function* () {
145
- const coldStorage = yield* ColdStorageTag;
146
- const hotStorage = yield* HotStorageTag;
147
-
148
- // Pre-populate cold storage
149
- yield* coldStorage.save("doc-restore", {
150
- state: { title: "Restored", count: 100 },
151
- version: 5,
152
- schemaVersion: 1,
153
- savedAt: Date.now(),
154
- });
155
-
156
- const instance = yield* DocumentInstance.make(
157
- "doc-restore",
158
- makeTestConfig(),
159
- coldStorage,
160
- hotStorage
161
- );
162
-
163
- return instance.getSnapshot();
164
- }).pipe(
165
- Effect.provide(ColdStorage.InMemory.make()),
166
- Effect.provide(HotStorage.InMemory.make())
167
- )
168
- );
169
-
170
- expect(result.version).toBe(5);
171
- expect(result.state).toEqual({ title: "Restored", count: 100 });
172
- });
173
- });
174
-
175
- describe("submit", () => {
176
- it("should accept valid transactions", async () => {
177
- const result = await Effect.runPromise(
178
- Effect.gen(function* () {
179
- const coldStorage = yield* ColdStorageTag;
180
- const hotStorage = yield* HotStorageTag;
181
-
182
- const instance = yield* DocumentInstance.make(
183
- "doc-submit-1",
184
- makeTestConfig(),
185
- coldStorage,
186
- hotStorage
187
- );
188
-
189
- const tx = createValidTransaction("tx-1", "Hello World");
190
- return yield* instance.submit(tx);
191
- }).pipe(
192
- Effect.provide(ColdStorage.InMemory.make()),
193
- Effect.provide(HotStorage.InMemory.make())
194
- )
195
- );
196
-
197
- expect(result.success).toBe(true);
198
- if (result.success) {
199
- expect(result.version).toBe(1);
200
- }
201
- });
202
-
203
- it("should reject empty transactions", async () => {
204
- const result = await Effect.runPromise(
205
- Effect.gen(function* () {
206
- const coldStorage = yield* ColdStorageTag;
207
- const hotStorage = yield* HotStorageTag;
208
-
209
- const instance = yield* DocumentInstance.make(
210
- "doc-submit-empty",
211
- makeTestConfig(),
212
- coldStorage,
213
- hotStorage
214
- );
215
-
216
- const tx = createEmptyTransaction("tx-empty");
217
- return yield* instance.submit(tx);
218
- }).pipe(
219
- Effect.provide(ColdStorage.InMemory.make()),
220
- Effect.provide(HotStorage.InMemory.make())
221
- )
222
- );
223
-
224
- expect(result.success).toBe(false);
225
- if (!result.success) {
226
- expect(result.reason).toBe("Transaction is empty");
227
- }
228
- });
229
-
230
- it("should reject duplicate transactions", async () => {
231
- const result = await Effect.runPromise(
232
- Effect.gen(function* () {
233
- const coldStorage = yield* ColdStorageTag;
234
- const hotStorage = yield* HotStorageTag;
235
-
236
- const instance = yield* DocumentInstance.make(
237
- "doc-submit-dup",
238
- makeTestConfig(),
239
- coldStorage,
240
- hotStorage
241
- );
242
-
243
- const tx = createValidTransaction("tx-dup", "First");
244
-
245
- const first = yield* instance.submit(tx);
246
- const second = yield* instance.submit(tx);
247
-
248
- return { first, second };
249
- }).pipe(
250
- Effect.provide(ColdStorage.InMemory.make()),
251
- Effect.provide(HotStorage.InMemory.make())
252
- )
253
- );
254
-
255
- expect(result.first.success).toBe(true);
256
- expect(result.second.success).toBe(false);
257
- if (!result.second.success) {
258
- expect(result.second.reason).toBe(
259
- "Transaction has already been processed"
260
- );
261
- }
262
- });
263
-
264
- it("should increment version with each successful transaction", async () => {
265
- const result = await Effect.runPromise(
266
- Effect.gen(function* () {
267
- const coldStorage = yield* ColdStorageTag;
268
- const hotStorage = yield* HotStorageTag;
269
-
270
- const instance = yield* DocumentInstance.make(
271
- "doc-submit-versions",
272
- makeTestConfig(),
273
- coldStorage,
274
- hotStorage
275
- );
276
-
277
- const tx1 = createValidTransaction("tx-1", "One");
278
- const tx2 = createValidTransaction("tx-2", "Two");
279
- const tx3 = createValidTransaction("tx-3", "Three");
280
-
281
- const r1 = yield* instance.submit(tx1);
282
- const r2 = yield* instance.submit(tx2);
283
- const r3 = yield* instance.submit(tx3);
284
-
285
- return { r1, r2, r3 };
286
- }).pipe(
287
- Effect.provide(ColdStorage.InMemory.make()),
288
- Effect.provide(HotStorage.InMemory.make())
289
- )
290
- );
291
-
292
- expect(result.r1.success).toBe(true);
293
- expect(result.r2.success).toBe(true);
294
- expect(result.r3.success).toBe(true);
295
-
296
- if (result.r1.success && result.r2.success && result.r3.success) {
297
- expect(result.r1.version).toBe(1);
298
- expect(result.r2.version).toBe(2);
299
- expect(result.r3.version).toBe(3);
300
- }
301
- });
302
-
303
- it("should persist transactions to WAL", async () => {
304
- const result = await Effect.runPromise(
305
- Effect.gen(function* () {
306
- const coldStorage = yield* ColdStorageTag;
307
- const hotStorage = yield* HotStorageTag;
308
-
309
- const instance = yield* DocumentInstance.make(
310
- "doc-wal",
311
- makeTestConfig(),
312
- coldStorage,
313
- hotStorage
314
- );
315
-
316
- const tx1 = createValidTransaction("tx-1", "First");
317
- const tx2 = createValidTransaction("tx-2", "Second");
318
-
319
- yield* instance.submit(tx1);
320
- yield* instance.submit(tx2);
321
-
322
- // Check WAL entries
323
- const entries = yield* hotStorage.getEntries("doc-wal", 0);
324
- return entries;
325
- }).pipe(
326
- Effect.provide(ColdStorage.InMemory.make()),
327
- Effect.provide(HotStorage.InMemory.make())
328
- )
329
- );
330
-
331
- expect(result.length).toBe(2);
332
- expect(result[0]!.version).toBe(1);
333
- expect(result[1]!.version).toBe(2);
334
- });
335
- });
336
-
337
- describe("getSnapshot", () => {
338
- it("should return current state after transactions", async () => {
339
- const result = await Effect.runPromise(
340
- Effect.gen(function* () {
341
- const coldStorage = yield* ColdStorageTag;
342
- const hotStorage = yield* HotStorageTag;
343
-
344
- const instance = yield* DocumentInstance.make(
345
- "doc-snapshot",
346
- makeTestConfig({ initial: { title: "Initial" } }),
347
- coldStorage,
348
- hotStorage
349
- );
350
-
351
- const tx = createValidTransaction("tx-1", "Updated Title");
352
- yield* instance.submit(tx);
353
-
354
- return instance.getSnapshot();
355
- }).pipe(
356
- Effect.provide(ColdStorage.InMemory.make()),
357
- Effect.provide(HotStorage.InMemory.make())
358
- )
359
- );
360
-
361
- expect(result.version).toBe(1);
362
- expect((result.state as { title: string }).title).toBe("Updated Title");
363
- });
364
- });
365
-
366
- describe("getVersion", () => {
367
- it("should return current version", async () => {
368
- const result = await Effect.runPromise(
369
- Effect.gen(function* () {
370
- const coldStorage = yield* ColdStorageTag;
371
- const hotStorage = yield* HotStorageTag;
372
-
373
- const instance = yield* DocumentInstance.make(
374
- "doc-version",
375
- makeTestConfig(),
376
- coldStorage,
377
- hotStorage
378
- );
379
-
380
- const v0 = instance.getVersion();
381
-
382
- const tx1 = createValidTransaction("tx-1", "First");
383
- yield* instance.submit(tx1);
384
- const v1 = instance.getVersion();
385
-
386
- const tx2 = createValidTransaction("tx-2", "Second");
387
- yield* instance.submit(tx2);
388
- const v2 = instance.getVersion();
389
-
390
- return { v0, v1, v2 };
391
- }).pipe(
392
- Effect.provide(ColdStorage.InMemory.make()),
393
- Effect.provide(HotStorage.InMemory.make())
394
- )
395
- );
396
-
397
- expect(result.v0).toBe(0);
398
- expect(result.v1).toBe(1);
399
- expect(result.v2).toBe(2);
400
- });
401
- });
402
-
403
- describe("touch", () => {
404
- it("should update activity time without error", async () => {
405
- const result = await Effect.runPromise(
406
- Effect.gen(function* () {
407
- const coldStorage = yield* ColdStorageTag;
408
- const hotStorage = yield* HotStorageTag;
409
-
410
- const instance = yield* DocumentInstance.make(
411
- "doc-touch",
412
- makeTestConfig(),
413
- coldStorage,
414
- hotStorage
415
- );
416
-
417
- yield* instance.touch();
418
- return true;
419
- }).pipe(
420
- Effect.provide(ColdStorage.InMemory.make()),
421
- Effect.provide(HotStorage.InMemory.make())
422
- )
423
- );
424
-
425
- expect(result).toBe(true);
426
- });
427
- });
428
-
429
- describe("pubsub broadcasts", () => {
430
- it("should broadcast transactions to subscribers", async () => {
431
- const result = await Effect.runPromise(
432
- Effect.gen(function* () {
433
- const coldStorage = yield* ColdStorageTag;
434
- const hotStorage = yield* HotStorageTag;
435
-
436
- const instance = yield* DocumentInstance.make(
437
- "doc-broadcast",
438
- makeTestConfig(),
439
- coldStorage,
440
- hotStorage
441
- );
442
-
443
- // Subscribe to broadcasts
444
- const broadcastStream = Stream.fromPubSub(instance.pubsub);
445
-
446
- // Start collecting in background
447
- const collectFiber = yield* Effect.fork(
448
- broadcastStream.pipe(Stream.take(1), Stream.runCollect)
449
- );
450
-
451
- // Wait a bit for subscription to be ready
452
- yield* Effect.sleep(50);
453
-
454
- // Submit a transaction
455
- const tx = createValidTransaction("tx-broadcast", "Broadcast Test");
456
- yield* instance.submit(tx);
457
-
458
- // Wait for broadcast
459
- const broadcasts = yield* Fiber.join(collectFiber).pipe(
460
- Effect.timeout(2000)
461
- );
462
-
463
- return broadcasts;
464
- }).pipe(
465
- Effect.provide(ColdStorage.InMemory.make()),
466
- Effect.provide(HotStorage.InMemory.make())
467
- )
468
- );
469
-
470
- expect(result).toBeDefined();
471
- if (result) {
472
- const broadcasts = Array.from(result);
473
- expect(broadcasts.length).toBe(1);
474
- expect(broadcasts[0]!.type).toBe("transaction");
475
- }
476
- });
477
- });
478
-
479
- describe("getSnapshotTracking", () => {
480
- it("should track snapshot state", async () => {
481
- const result = await Effect.runPromise(
482
- Effect.gen(function* () {
483
- const coldStorage = yield* ColdStorageTag;
484
- const hotStorage = yield* HotStorageTag;
485
-
486
- const instance = yield* DocumentInstance.make(
487
- "doc-tracking",
488
- makeTestConfig(),
489
- coldStorage,
490
- hotStorage
491
- );
492
-
493
- const initialTracking = yield* instance.getSnapshotTracking;
494
-
495
- // Submit some transactions
496
- yield* instance.submit(createValidTransaction("tx-1", "One"));
497
- yield* instance.submit(createValidTransaction("tx-2", "Two"));
498
-
499
- const afterTracking = yield* instance.getSnapshotTracking;
500
-
501
- return { initialTracking, afterTracking };
502
- }).pipe(
503
- Effect.provide(ColdStorage.InMemory.make()),
504
- Effect.provide(HotStorage.InMemory.make())
505
- )
506
- );
507
-
508
- expect(result.initialTracking.transactionsSinceSnapshot).toBe(0);
509
- expect(result.afterTracking.transactionsSinceSnapshot).toBe(2);
510
- });
511
- });
512
-
513
- describe("saveSnapshot", () => {
514
- it("should save snapshot to cold storage", async () => {
515
- const result = await Effect.runPromise(
516
- Effect.gen(function* () {
517
- const coldStorage = yield* ColdStorageTag;
518
- const hotStorage = yield* HotStorageTag;
519
-
520
- const instance = yield* DocumentInstance.make(
521
- "doc-save-snapshot",
522
- makeTestConfig(),
523
- coldStorage,
524
- hotStorage
525
- );
526
-
527
- // Submit some transactions
528
- yield* instance.submit(createValidTransaction("tx-1", "Final Title"));
529
-
530
- // Save snapshot manually
531
- yield* instance.saveSnapshot();
532
-
533
- // Verify cold storage was updated
534
- const stored = yield* coldStorage.load("doc-save-snapshot");
535
- return stored;
536
- }).pipe(
537
- Effect.provide(ColdStorage.InMemory.make()),
538
- Effect.provide(HotStorage.InMemory.make())
539
- )
540
- );
541
-
542
- expect(result).toBeDefined();
543
- expect(result!.version).toBe(1);
544
- expect((result!.state as { title: string }).title).toBe("Final Title");
545
- });
546
-
547
- it("should be idempotent when called multiple times at same version", async () => {
548
- const result = await Effect.runPromise(
549
- Effect.gen(function* () {
550
- const coldStorage = yield* ColdStorageTag;
551
- const hotStorage = yield* HotStorageTag;
552
-
553
- const instance = yield* DocumentInstance.make(
554
- "doc-idempotent-snapshot",
555
- makeTestConfig(),
556
- coldStorage,
557
- hotStorage
558
- );
559
-
560
- yield* instance.submit(createValidTransaction("tx-1", "Test"));
561
-
562
- // Save snapshot multiple times
563
- yield* instance.saveSnapshot();
564
- yield* instance.saveSnapshot();
565
- yield* instance.saveSnapshot();
566
-
567
- return true;
568
- }).pipe(
569
- Effect.provide(ColdStorage.InMemory.make()),
570
- Effect.provide(HotStorage.InMemory.make())
571
- )
572
- );
573
-
574
- expect(result).toBe(true);
575
- });
576
- });
577
-
578
- describe("WAL replay", () => {
579
- it("should replay WAL entries on restore", async () => {
580
- const result = await Effect.runPromise(
581
- Effect.gen(function* () {
582
- const coldStorage = yield* ColdStorageTag;
583
- const hotStorage = yield* HotStorageTag;
584
-
585
- // Pre-populate cold storage with base state
586
- yield* coldStorage.save("doc-wal-replay", {
587
- state: { title: "Base", count: 0 },
588
- version: 0,
589
- schemaVersion: 1,
590
- savedAt: Date.now(),
591
- });
592
-
593
- // Pre-populate WAL with entries
594
- const tx1 = createValidTransaction("tx-1", "After WAL 1");
595
- yield* hotStorage.append("doc-wal-replay", {
596
- transaction: tx1,
597
- version: 1,
598
- timestamp: Date.now(),
599
- });
600
-
601
- const tx2 = createValidTransaction("tx-2", "After WAL 2");
602
- yield* hotStorage.append("doc-wal-replay", {
603
- transaction: tx2,
604
- version: 2,
605
- timestamp: Date.now(),
606
- });
607
-
608
- // Create instance - should replay WAL
609
- const instance = yield* DocumentInstance.make(
610
- "doc-wal-replay",
611
- makeTestConfig(),
612
- coldStorage,
613
- hotStorage
614
- );
615
-
616
- return instance.getSnapshot();
617
- }).pipe(
618
- Effect.provide(ColdStorage.InMemory.make()),
619
- Effect.provide(HotStorage.InMemory.make())
620
- )
621
- );
622
-
623
- // Version should reflect replayed WAL
624
- expect(result.version).toBe(2);
625
- expect((result.state as { title: string }).title).toBe("After WAL 2");
626
- });
627
- });
628
-
629
- describe("initial state function", () => {
630
- it("should support initial state as effect function", async () => {
631
- const result = await Effect.runPromise(
632
- Effect.gen(function* () {
633
- const coldStorage = yield* ColdStorageTag;
634
- const hotStorage = yield* HotStorageTag;
635
-
636
- const instance = yield* DocumentInstance.make(
637
- "doc-dynamic-initial",
638
- {
639
- schema: TestSchema,
640
- initial: (ctx) =>
641
- Effect.succeed({
642
- title: `Document: ${ctx.documentId}`,
643
- count: 100,
644
- }),
645
- maxTransactionHistory: 100,
646
- snapshot: {
647
- interval: Duration.minutes(5),
648
- transactionThreshold: 100,
649
- },
650
- },
651
- coldStorage,
652
- hotStorage
653
- );
654
-
655
- return instance.getSnapshot();
656
- }).pipe(
657
- Effect.provide(ColdStorage.InMemory.make()),
658
- Effect.provide(HotStorage.InMemory.make())
659
- )
660
- );
661
-
662
- expect(result.version).toBe(0);
663
- expect(result.state).toEqual({
664
- title: "Document: doc-dynamic-initial",
665
- count: 100,
666
- });
667
- });
668
- });
669
- });