@voidhash/mimic-effect 1.0.0-beta.1 → 1.0.0-beta.10

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 (142) hide show
  1. package/.turbo/turbo-build.log +116 -74
  2. package/dist/ColdStorage.cjs +9 -5
  3. package/dist/ColdStorage.d.cts.map +1 -1
  4. package/dist/ColdStorage.d.mts.map +1 -1
  5. package/dist/ColdStorage.mjs +9 -5
  6. package/dist/ColdStorage.mjs.map +1 -1
  7. package/dist/DocumentInstance.cjs +263 -0
  8. package/dist/DocumentInstance.d.cts +78 -0
  9. package/dist/DocumentInstance.d.cts.map +1 -0
  10. package/dist/DocumentInstance.d.mts +78 -0
  11. package/dist/DocumentInstance.d.mts.map +1 -0
  12. package/dist/DocumentInstance.mjs +264 -0
  13. package/dist/DocumentInstance.mjs.map +1 -0
  14. package/dist/Errors.cjs +10 -1
  15. package/dist/Errors.d.cts +18 -3
  16. package/dist/Errors.d.cts.map +1 -1
  17. package/dist/Errors.d.mts +18 -3
  18. package/dist/Errors.d.mts.map +1 -1
  19. package/dist/Errors.mjs +9 -1
  20. package/dist/Errors.mjs.map +1 -1
  21. package/dist/HotStorage.cjs +39 -12
  22. package/dist/HotStorage.d.cts +17 -1
  23. package/dist/HotStorage.d.cts.map +1 -1
  24. package/dist/HotStorage.d.mts +17 -1
  25. package/dist/HotStorage.d.mts.map +1 -1
  26. package/dist/HotStorage.mjs +39 -12
  27. package/dist/HotStorage.mjs.map +1 -1
  28. package/dist/Metrics.cjs +29 -1
  29. package/dist/Metrics.d.cts +5 -0
  30. package/dist/Metrics.d.cts.map +1 -1
  31. package/dist/Metrics.d.mts +5 -0
  32. package/dist/Metrics.d.mts.map +1 -1
  33. package/dist/Metrics.mjs +26 -1
  34. package/dist/Metrics.mjs.map +1 -1
  35. package/dist/MimicClusterServerEngine.cjs +44 -139
  36. package/dist/MimicClusterServerEngine.d.cts.map +1 -1
  37. package/dist/MimicClusterServerEngine.d.mts +1 -1
  38. package/dist/MimicClusterServerEngine.d.mts.map +1 -1
  39. package/dist/MimicClusterServerEngine.mjs +46 -141
  40. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  41. package/dist/MimicServer.cjs +20 -20
  42. package/dist/MimicServer.d.cts.map +1 -1
  43. package/dist/MimicServer.d.mts.map +1 -1
  44. package/dist/MimicServer.mjs +20 -20
  45. package/dist/MimicServer.mjs.map +1 -1
  46. package/dist/MimicServerEngine.cjs +92 -11
  47. package/dist/MimicServerEngine.d.cts +12 -4
  48. package/dist/MimicServerEngine.d.cts.map +1 -1
  49. package/dist/MimicServerEngine.d.mts +12 -4
  50. package/dist/MimicServerEngine.d.mts.map +1 -1
  51. package/dist/MimicServerEngine.mjs +94 -13
  52. package/dist/MimicServerEngine.mjs.map +1 -1
  53. package/dist/PresenceManager.cjs +5 -5
  54. package/dist/PresenceManager.d.cts.map +1 -1
  55. package/dist/PresenceManager.d.mts.map +1 -1
  56. package/dist/PresenceManager.mjs +5 -5
  57. package/dist/PresenceManager.mjs.map +1 -1
  58. package/dist/Protocol.d.cts +1 -1
  59. package/dist/Protocol.d.mts +1 -1
  60. package/dist/Types.d.cts +9 -2
  61. package/dist/Types.d.cts.map +1 -1
  62. package/dist/Types.d.mts +9 -2
  63. package/dist/Types.d.mts.map +1 -1
  64. package/dist/index.cjs +5 -6
  65. package/dist/index.d.cts +3 -3
  66. package/dist/index.d.mts +3 -3
  67. package/dist/index.mjs +3 -3
  68. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  69. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  70. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  71. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  72. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  73. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  74. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  75. package/dist/testing/FailingStorage.cjs +162 -0
  76. package/dist/testing/FailingStorage.d.cts +43 -0
  77. package/dist/testing/FailingStorage.d.cts.map +1 -0
  78. package/dist/testing/FailingStorage.d.mts +43 -0
  79. package/dist/testing/FailingStorage.d.mts.map +1 -0
  80. package/dist/testing/FailingStorage.mjs +163 -0
  81. package/dist/testing/FailingStorage.mjs.map +1 -0
  82. package/dist/testing/HotStorageTestSuite.cjs +820 -0
  83. package/dist/testing/HotStorageTestSuite.d.cts +42 -0
  84. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  85. package/dist/testing/HotStorageTestSuite.d.mts +42 -0
  86. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  87. package/dist/testing/HotStorageTestSuite.mjs +820 -0
  88. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  89. package/dist/testing/StorageIntegrationTestSuite.cjs +487 -0
  90. package/dist/testing/StorageIntegrationTestSuite.d.cts +37 -0
  91. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  92. package/dist/testing/StorageIntegrationTestSuite.d.mts +37 -0
  93. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  94. package/dist/testing/StorageIntegrationTestSuite.mjs +487 -0
  95. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  96. package/dist/testing/assertions.cjs +117 -0
  97. package/dist/testing/assertions.mjs +112 -0
  98. package/dist/testing/assertions.mjs.map +1 -0
  99. package/dist/testing/index.cjs +14 -0
  100. package/dist/testing/index.d.cts +6 -0
  101. package/dist/testing/index.d.mts +6 -0
  102. package/dist/testing/index.mjs +7 -0
  103. package/dist/testing/types.cjs +15 -0
  104. package/dist/testing/types.d.cts +90 -0
  105. package/dist/testing/types.d.cts.map +1 -0
  106. package/dist/testing/types.d.mts +90 -0
  107. package/dist/testing/types.d.mts.map +1 -0
  108. package/dist/testing/types.mjs +16 -0
  109. package/dist/testing/types.mjs.map +1 -0
  110. package/package.json +8 -3
  111. package/src/ColdStorage.ts +21 -12
  112. package/src/DocumentInstance.ts +527 -0
  113. package/src/Errors.ts +15 -1
  114. package/src/HotStorage.ts +115 -24
  115. package/src/Metrics.ts +30 -0
  116. package/src/MimicClusterServerEngine.ts +120 -275
  117. package/src/MimicServer.ts +83 -75
  118. package/src/MimicServerEngine.ts +230 -30
  119. package/src/PresenceManager.ts +44 -34
  120. package/src/Types.ts +9 -2
  121. package/src/index.ts +5 -35
  122. package/src/testing/ColdStorageTestSuite.ts +589 -0
  123. package/src/testing/FailingStorage.ts +338 -0
  124. package/src/testing/HotStorageTestSuite.ts +1105 -0
  125. package/src/testing/StorageIntegrationTestSuite.ts +736 -0
  126. package/src/testing/assertions.ts +188 -0
  127. package/src/testing/index.ts +83 -0
  128. package/src/testing/types.ts +100 -0
  129. package/tests/ColdStorage.test.ts +8 -120
  130. package/tests/DocumentInstance.test.ts +669 -0
  131. package/tests/HotStorage.test.ts +7 -126
  132. package/tests/StorageIntegration.test.ts +259 -0
  133. package/tsdown.config.ts +1 -1
  134. package/dist/DocumentManager.cjs +0 -229
  135. package/dist/DocumentManager.d.cts +0 -59
  136. package/dist/DocumentManager.d.cts.map +0 -1
  137. package/dist/DocumentManager.d.mts +0 -59
  138. package/dist/DocumentManager.d.mts.map +0 -1
  139. package/dist/DocumentManager.mjs +0 -227
  140. package/dist/DocumentManager.mjs.map +0 -1
  141. package/src/DocumentManager.ts +0 -506
  142. package/tests/DocumentManager.test.ts +0 -335
@@ -0,0 +1,589 @@
1
+ /**
2
+ * @voidhash/mimic-effect/testing - ColdStorage Test Suite
3
+ *
4
+ * Comprehensive test suite for ColdStorage adapter implementations.
5
+ * These tests verify that an adapter correctly implements the ColdStorage interface
6
+ * and can reliably store/retrieve document snapshots without data loss.
7
+ */
8
+ import { Effect } from "effect";
9
+ import { ColdStorageTag } from "../ColdStorage";
10
+ import type { ColdStorageError } from "../Errors";
11
+ import type { StoredDocument } from "../Types";
12
+ import type { StorageTestCase, TestResults } from "./types";
13
+ import { TestError } from "./types";
14
+ import {
15
+ assertEqual,
16
+ assertUndefined,
17
+ assertDefined,
18
+ } from "./assertions";
19
+
20
+ /**
21
+ * Error type for ColdStorage tests - can be either a TestError or a ColdStorageError
22
+ */
23
+ export type ColdStorageTestError = TestError | ColdStorageError;
24
+
25
+ // =============================================================================
26
+ // Categories
27
+ // =============================================================================
28
+
29
+ export const Categories = {
30
+ BasicOperations: "Basic Operations",
31
+ DataIntegrity: "Data Integrity",
32
+ VersionHandling: "Version Handling",
33
+ TimestampHandling: "Timestamp Handling",
34
+ DocumentIdEdgeCases: "Document ID Edge Cases",
35
+ DocumentIsolation: "Document Isolation",
36
+ ConsistencyGuarantees: "Consistency Guarantees",
37
+ } as const;
38
+
39
+ // =============================================================================
40
+ // Helper Functions
41
+ // =============================================================================
42
+
43
+ const makeDoc = (overrides: Partial<StoredDocument> = {}): StoredDocument => ({
44
+ state: { title: "Test Document" },
45
+ version: 1,
46
+ schemaVersion: 1,
47
+ savedAt: Date.now(),
48
+ ...overrides,
49
+ });
50
+
51
+ const generateLargeState = (sizeKB: number): Record<string, unknown> => {
52
+ const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
53
+ const targetBytes = sizeKB * 1024;
54
+ let content = "";
55
+ while (content.length < targetBytes) {
56
+ content += chars[Math.floor(Math.random() * chars.length)];
57
+ }
58
+ return { content, padding: Array(100).fill("x") };
59
+ };
60
+
61
+ // =============================================================================
62
+ // Test Definitions
63
+ // =============================================================================
64
+
65
+ const tests: StorageTestCase<ColdStorageTestError, ColdStorageTag>[] = [
66
+ // ---------------------------------------------------------------------------
67
+ // Basic Operations
68
+ // ---------------------------------------------------------------------------
69
+ {
70
+ name: "load returns undefined for non-existent document",
71
+ category: Categories.BasicOperations,
72
+ run: Effect.gen(function* () {
73
+ const storage = yield* ColdStorageTag;
74
+ const result = yield* storage.load("non-existent-doc-id-12345");
75
+ yield* assertUndefined(result, "Expected undefined for non-existent document");
76
+ }),
77
+ },
78
+
79
+ {
80
+ name: "save then load returns exact same document",
81
+ category: Categories.BasicOperations,
82
+ run: Effect.gen(function* () {
83
+ const storage = yield* ColdStorageTag;
84
+ const doc = makeDoc();
85
+ yield* storage.save("test-doc-1", doc);
86
+ const loaded = yield* storage.load("test-doc-1");
87
+ yield* assertEqual(loaded, doc, "Loaded document should match saved document");
88
+ }),
89
+ },
90
+
91
+ {
92
+ name: "save overwrites existing document",
93
+ category: Categories.BasicOperations,
94
+ run: Effect.gen(function* () {
95
+ const storage = yield* ColdStorageTag;
96
+ const doc1 = makeDoc({ state: { title: "First" }, version: 1 });
97
+ const doc2 = makeDoc({ state: { title: "Second" }, version: 2 });
98
+ yield* storage.save("test-doc-2", doc1);
99
+ yield* storage.save("test-doc-2", doc2);
100
+ const loaded = yield* storage.load("test-doc-2");
101
+ yield* assertEqual(loaded, doc2, "Should return the second (overwritten) document");
102
+ }),
103
+ },
104
+
105
+ {
106
+ name: "delete removes document",
107
+ category: Categories.BasicOperations,
108
+ run: Effect.gen(function* () {
109
+ const storage = yield* ColdStorageTag;
110
+ const doc = makeDoc();
111
+ yield* storage.save("test-doc-3", doc);
112
+ yield* storage.delete("test-doc-3");
113
+ const loaded = yield* storage.load("test-doc-3");
114
+ yield* assertUndefined(loaded, "Document should be undefined after deletion");
115
+ }),
116
+ },
117
+
118
+ {
119
+ name: "delete on non-existent document does not error",
120
+ category: Categories.BasicOperations,
121
+ run: Effect.gen(function* () {
122
+ const storage = yield* ColdStorageTag;
123
+ yield* storage.delete("definitely-non-existent-doc-xyz");
124
+ }),
125
+ },
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Data Integrity & Serialization
129
+ // ---------------------------------------------------------------------------
130
+ {
131
+ name: "all StoredDocument fields are preserved",
132
+ category: Categories.DataIntegrity,
133
+ run: Effect.gen(function* () {
134
+ const storage = yield* ColdStorageTag;
135
+ const doc: StoredDocument = {
136
+ state: { nested: { value: 42 } },
137
+ version: 123,
138
+ schemaVersion: 2,
139
+ savedAt: 1704067200000,
140
+ };
141
+ yield* storage.save("fields-test", doc);
142
+ const loaded = yield* storage.load("fields-test");
143
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
144
+ yield* assertEqual(definedLoaded.state, doc.state, "state should be preserved");
145
+ yield* assertEqual(definedLoaded.version, doc.version, "version should be preserved");
146
+ yield* assertEqual(definedLoaded.schemaVersion, doc.schemaVersion, "schemaVersion should be preserved");
147
+ yield* assertEqual(definedLoaded.savedAt, doc.savedAt, "savedAt should be preserved");
148
+ }),
149
+ },
150
+
151
+ {
152
+ name: "complex nested state objects survive roundtrip",
153
+ category: Categories.DataIntegrity,
154
+ run: Effect.gen(function* () {
155
+ const storage = yield* ColdStorageTag;
156
+ const complexState = {
157
+ level1: {
158
+ level2: {
159
+ level3: {
160
+ value: "deep",
161
+ array: [1, 2, { nested: true }],
162
+ },
163
+ },
164
+ sibling: "value",
165
+ },
166
+ topLevel: 42,
167
+ };
168
+ const doc = makeDoc({ state: complexState });
169
+ yield* storage.save("complex-state", doc);
170
+ const loaded = yield* storage.load("complex-state");
171
+ yield* assertEqual(loaded, doc, "Complex nested state should survive roundtrip");
172
+ }),
173
+ },
174
+
175
+ {
176
+ name: "arrays in state survive roundtrip",
177
+ category: Categories.DataIntegrity,
178
+ run: Effect.gen(function* () {
179
+ const storage = yield* ColdStorageTag;
180
+ const doc = makeDoc({
181
+ state: {
182
+ numbers: [1, 2, 3, 4, 5],
183
+ strings: ["a", "b", "c"],
184
+ mixed: [1, "two", { three: 3 }, [4]],
185
+ empty: [],
186
+ },
187
+ });
188
+ yield* storage.save("arrays-test", doc);
189
+ const loaded = yield* storage.load("arrays-test");
190
+ yield* assertEqual(loaded, doc, "Arrays should survive roundtrip");
191
+ }),
192
+ },
193
+
194
+ {
195
+ name: "null values in state survive roundtrip",
196
+ category: Categories.DataIntegrity,
197
+ run: Effect.gen(function* () {
198
+ const storage = yield* ColdStorageTag;
199
+ const doc = makeDoc({
200
+ state: {
201
+ nullValue: null,
202
+ nested: { alsoNull: null },
203
+ array: [null, 1, null],
204
+ },
205
+ });
206
+ yield* storage.save("null-test", doc);
207
+ const loaded = yield* storage.load("null-test");
208
+ yield* assertEqual(loaded, doc, "null values should survive roundtrip");
209
+ }),
210
+ },
211
+
212
+ {
213
+ name: "empty object survives roundtrip",
214
+ category: Categories.DataIntegrity,
215
+ run: Effect.gen(function* () {
216
+ const storage = yield* ColdStorageTag;
217
+ const doc = makeDoc({ state: {} });
218
+ yield* storage.save("empty-obj", doc);
219
+ const loaded = yield* storage.load("empty-obj");
220
+ yield* assertEqual(loaded, doc, "Empty object should survive roundtrip");
221
+ }),
222
+ },
223
+
224
+ {
225
+ name: "empty array survives roundtrip",
226
+ category: Categories.DataIntegrity,
227
+ run: Effect.gen(function* () {
228
+ const storage = yield* ColdStorageTag;
229
+ const doc = makeDoc({ state: [] });
230
+ yield* storage.save("empty-arr", doc);
231
+ const loaded = yield* storage.load("empty-arr");
232
+ yield* assertEqual(loaded, doc, "Empty array should survive roundtrip");
233
+ }),
234
+ },
235
+
236
+ {
237
+ name: "unicode strings in state survive roundtrip",
238
+ category: Categories.DataIntegrity,
239
+ run: Effect.gen(function* () {
240
+ const storage = yield* ColdStorageTag;
241
+ const doc = makeDoc({
242
+ state: {
243
+ emoji: "Hello! How are you?",
244
+ chinese: "Chinese Characters",
245
+ arabic: "Arabic Letters",
246
+ special: "Combined: Cafe",
247
+ },
248
+ });
249
+ yield* storage.save("unicode-test", doc);
250
+ const loaded = yield* storage.load("unicode-test");
251
+ yield* assertEqual(loaded, doc, "Unicode strings should survive roundtrip");
252
+ }),
253
+ },
254
+
255
+ {
256
+ name: "large state objects (100KB+) survive roundtrip",
257
+ category: Categories.DataIntegrity,
258
+ run: Effect.gen(function* () {
259
+ const storage = yield* ColdStorageTag;
260
+ const largeState = generateLargeState(100);
261
+ const doc = makeDoc({ state: largeState });
262
+ yield* storage.save("large-doc", doc);
263
+ const loaded = yield* storage.load("large-doc");
264
+ yield* assertEqual(loaded, doc, "Large documents should survive roundtrip");
265
+ }),
266
+ },
267
+
268
+ {
269
+ name: "special JSON characters survive roundtrip",
270
+ category: Categories.DataIntegrity,
271
+ run: Effect.gen(function* () {
272
+ const storage = yield* ColdStorageTag;
273
+ const doc = makeDoc({
274
+ state: {
275
+ quotes: 'He said "hello"',
276
+ backslash: "path\\to\\file",
277
+ newline: "line1\nline2",
278
+ tab: "col1\tcol2",
279
+ mixed: 'All: "\\\n\t',
280
+ },
281
+ });
282
+ yield* storage.save("special-chars", doc);
283
+ const loaded = yield* storage.load("special-chars");
284
+ yield* assertEqual(loaded, doc, "Special JSON characters should survive roundtrip");
285
+ }),
286
+ },
287
+
288
+ // ---------------------------------------------------------------------------
289
+ // Version Number Edge Cases
290
+ // ---------------------------------------------------------------------------
291
+ {
292
+ name: "version 0 is preserved correctly",
293
+ category: Categories.VersionHandling,
294
+ run: Effect.gen(function* () {
295
+ const storage = yield* ColdStorageTag;
296
+ const doc = makeDoc({ version: 0 });
297
+ yield* storage.save("version-0", doc);
298
+ const loaded = yield* storage.load("version-0");
299
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
300
+ yield* assertEqual(definedLoaded.version, 0, "Version 0 should be preserved");
301
+ }),
302
+ },
303
+
304
+ {
305
+ name: "version 1 is preserved correctly",
306
+ category: Categories.VersionHandling,
307
+ run: Effect.gen(function* () {
308
+ const storage = yield* ColdStorageTag;
309
+ const doc = makeDoc({ version: 1 });
310
+ yield* storage.save("version-1", doc);
311
+ const loaded = yield* storage.load("version-1");
312
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
313
+ yield* assertEqual(definedLoaded.version, 1, "Version 1 should be preserved");
314
+ }),
315
+ },
316
+
317
+ {
318
+ name: "large version numbers are preserved",
319
+ category: Categories.VersionHandling,
320
+ run: Effect.gen(function* () {
321
+ const storage = yield* ColdStorageTag;
322
+ const largeVersion = Number.MAX_SAFE_INTEGER;
323
+ const doc = makeDoc({ version: largeVersion });
324
+ yield* storage.save("large-version", doc);
325
+ const loaded = yield* storage.load("large-version");
326
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
327
+ yield* assertEqual(
328
+ definedLoaded.version,
329
+ largeVersion,
330
+ "Large version number should be preserved exactly"
331
+ );
332
+ }),
333
+ },
334
+
335
+ {
336
+ name: "schema version is preserved correctly",
337
+ category: Categories.VersionHandling,
338
+ run: Effect.gen(function* () {
339
+ const storage = yield* ColdStorageTag;
340
+ const doc = makeDoc({ schemaVersion: 42 });
341
+ yield* storage.save("schema-version", doc);
342
+ const loaded = yield* storage.load("schema-version");
343
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
344
+ yield* assertEqual(definedLoaded.schemaVersion, 42, "Schema version should be preserved");
345
+ }),
346
+ },
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // Timestamp Handling
350
+ // ---------------------------------------------------------------------------
351
+ {
352
+ name: "savedAt timestamp is preserved exactly",
353
+ category: Categories.TimestampHandling,
354
+ run: Effect.gen(function* () {
355
+ const storage = yield* ColdStorageTag;
356
+ const timestamp = 1704067200000;
357
+ const doc = makeDoc({ savedAt: timestamp });
358
+ yield* storage.save("timestamp-exact", doc);
359
+ const loaded = yield* storage.load("timestamp-exact");
360
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
361
+ yield* assertEqual(definedLoaded.savedAt, timestamp, "savedAt should be preserved exactly");
362
+ }),
363
+ },
364
+
365
+ {
366
+ name: "timestamp 0 is preserved correctly",
367
+ category: Categories.TimestampHandling,
368
+ run: Effect.gen(function* () {
369
+ const storage = yield* ColdStorageTag;
370
+ const doc = makeDoc({ savedAt: 0 });
371
+ yield* storage.save("timestamp-0", doc);
372
+ const loaded = yield* storage.load("timestamp-0");
373
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
374
+ yield* assertEqual(definedLoaded.savedAt, 0, "Timestamp 0 should be preserved");
375
+ }),
376
+ },
377
+
378
+ {
379
+ name: "recent timestamps are preserved correctly",
380
+ category: Categories.TimestampHandling,
381
+ run: Effect.gen(function* () {
382
+ const storage = yield* ColdStorageTag;
383
+ const now = Date.now();
384
+ const doc = makeDoc({ savedAt: now });
385
+ yield* storage.save("timestamp-recent", doc);
386
+ const loaded = yield* storage.load("timestamp-recent");
387
+ const definedLoaded = yield* assertDefined(loaded, "Document should exist");
388
+ yield* assertEqual(definedLoaded.savedAt, now, "Recent timestamp should be preserved");
389
+ }),
390
+ },
391
+
392
+ // ---------------------------------------------------------------------------
393
+ // Document ID Edge Cases
394
+ // ---------------------------------------------------------------------------
395
+ {
396
+ name: "long documentId (1000+ chars) works",
397
+ category: Categories.DocumentIdEdgeCases,
398
+ run: Effect.gen(function* () {
399
+ const storage = yield* ColdStorageTag;
400
+ const longId = "x".repeat(1000);
401
+ const doc = makeDoc();
402
+ yield* storage.save(longId, doc);
403
+ const loaded = yield* storage.load(longId);
404
+ yield* assertEqual(loaded, doc, "Long documentId should work correctly");
405
+ }),
406
+ },
407
+
408
+ {
409
+ name: "unicode documentId works",
410
+ category: Categories.DocumentIdEdgeCases,
411
+ run: Effect.gen(function* () {
412
+ const storage = yield* ColdStorageTag;
413
+ const unicodeId = "doc-test-id";
414
+ const doc = makeDoc();
415
+ yield* storage.save(unicodeId, doc);
416
+ const loaded = yield* storage.load(unicodeId);
417
+ yield* assertEqual(loaded, doc, "Unicode documentId should work correctly");
418
+ }),
419
+ },
420
+
421
+ {
422
+ name: "documentId with special chars works",
423
+ category: Categories.DocumentIdEdgeCases,
424
+ run: Effect.gen(function* () {
425
+ const storage = yield* ColdStorageTag;
426
+ const specialId = "doc/path:to.file";
427
+ const doc = makeDoc();
428
+ yield* storage.save(specialId, doc);
429
+ const loaded = yield* storage.load(specialId);
430
+ yield* assertEqual(loaded, doc, "DocumentId with special chars should work");
431
+ }),
432
+ },
433
+
434
+ {
435
+ name: "documentId with spaces works",
436
+ category: Categories.DocumentIdEdgeCases,
437
+ run: Effect.gen(function* () {
438
+ const storage = yield* ColdStorageTag;
439
+ const spacedId = "doc with spaces";
440
+ const doc = makeDoc();
441
+ yield* storage.save(spacedId, doc);
442
+ const loaded = yield* storage.load(spacedId);
443
+ yield* assertEqual(loaded, doc, "DocumentId with spaces should work");
444
+ }),
445
+ },
446
+
447
+ // ---------------------------------------------------------------------------
448
+ // Document Isolation
449
+ // ---------------------------------------------------------------------------
450
+ {
451
+ name: "different documents are stored independently",
452
+ category: Categories.DocumentIsolation,
453
+ run: Effect.gen(function* () {
454
+ const storage = yield* ColdStorageTag;
455
+ const doc1 = makeDoc({ state: { id: 1 }, version: 1 });
456
+ const doc2 = makeDoc({ state: { id: 2 }, version: 2 });
457
+ yield* storage.save("iso-doc-1", doc1);
458
+ yield* storage.save("iso-doc-2", doc2);
459
+ const loaded1 = yield* storage.load("iso-doc-1");
460
+ const loaded2 = yield* storage.load("iso-doc-2");
461
+ yield* assertEqual(loaded1, doc1, "First document should be unchanged");
462
+ yield* assertEqual(loaded2, doc2, "Second document should be unchanged");
463
+ }),
464
+ },
465
+
466
+ {
467
+ name: "deleting one document does not affect others",
468
+ category: Categories.DocumentIsolation,
469
+ run: Effect.gen(function* () {
470
+ const storage = yield* ColdStorageTag;
471
+ const doc1 = makeDoc({ state: { id: 1 } });
472
+ const doc2 = makeDoc({ state: { id: 2 } });
473
+ yield* storage.save("del-iso-1", doc1);
474
+ yield* storage.save("del-iso-2", doc2);
475
+ yield* storage.delete("del-iso-1");
476
+ const loaded1 = yield* storage.load("del-iso-1");
477
+ const loaded2 = yield* storage.load("del-iso-2");
478
+ yield* assertUndefined(loaded1, "Deleted document should be undefined");
479
+ yield* assertEqual(loaded2, doc2, "Other document should be unchanged");
480
+ }),
481
+ },
482
+
483
+ {
484
+ name: "updating one document does not affect others",
485
+ category: Categories.DocumentIsolation,
486
+ run: Effect.gen(function* () {
487
+ const storage = yield* ColdStorageTag;
488
+ const doc1v1 = makeDoc({ state: { id: 1, v: 1 }, version: 1 });
489
+ const doc1v2 = makeDoc({ state: { id: 1, v: 2 }, version: 2 });
490
+ const doc2 = makeDoc({ state: { id: 2 } });
491
+ yield* storage.save("upd-iso-1", doc1v1);
492
+ yield* storage.save("upd-iso-2", doc2);
493
+ yield* storage.save("upd-iso-1", doc1v2);
494
+ const loaded1 = yield* storage.load("upd-iso-1");
495
+ const loaded2 = yield* storage.load("upd-iso-2");
496
+ yield* assertEqual(loaded1, doc1v2, "Updated document should have new value");
497
+ yield* assertEqual(loaded2, doc2, "Other document should be unchanged");
498
+ }),
499
+ },
500
+
501
+ // ---------------------------------------------------------------------------
502
+ // Consistency Guarantees
503
+ // ---------------------------------------------------------------------------
504
+ {
505
+ name: "multiple saves to same doc, load returns latest",
506
+ category: Categories.ConsistencyGuarantees,
507
+ run: Effect.gen(function* () {
508
+ const storage = yield* ColdStorageTag;
509
+ const doc1 = makeDoc({ state: { v: 1 }, version: 1 });
510
+ const doc2 = makeDoc({ state: { v: 2 }, version: 2 });
511
+ const doc3 = makeDoc({ state: { v: 3 }, version: 3 });
512
+ yield* storage.save("multi-save", doc1);
513
+ yield* storage.save("multi-save", doc2);
514
+ yield* storage.save("multi-save", doc3);
515
+ const loaded = yield* storage.load("multi-save");
516
+ yield* assertEqual(loaded, doc3, "Should return the latest saved document");
517
+ }),
518
+ },
519
+
520
+ {
521
+ name: "save-delete-save sequence works correctly",
522
+ category: Categories.ConsistencyGuarantees,
523
+ run: Effect.gen(function* () {
524
+ const storage = yield* ColdStorageTag;
525
+ const doc1 = makeDoc({ state: { phase: "first" }, version: 1 });
526
+ const doc2 = makeDoc({ state: { phase: "second" }, version: 2 });
527
+ yield* storage.save("sds-test", doc1);
528
+ yield* storage.delete("sds-test");
529
+ yield* storage.save("sds-test", doc2);
530
+ const loaded = yield* storage.load("sds-test");
531
+ yield* assertEqual(loaded, doc2, "Should return document saved after delete");
532
+ }),
533
+ },
534
+ ];
535
+
536
+ // =============================================================================
537
+ // Exports
538
+ // =============================================================================
539
+
540
+ /**
541
+ * Get all ColdStorage test cases.
542
+ *
543
+ * @returns Array of test cases that require ColdStorageTag
544
+ */
545
+ export const makeTests = (): StorageTestCase<
546
+ ColdStorageTestError,
547
+ ColdStorageTag
548
+ >[] => tests;
549
+
550
+ /**
551
+ * Run all tests and collect results.
552
+ *
553
+ * @returns Effect that produces TestResults
554
+ */
555
+ export const runAll = (): Effect.Effect<
556
+ TestResults<ColdStorageTestError, ColdStorageTag>,
557
+ never,
558
+ ColdStorageTag
559
+ > =>
560
+ Effect.gen(function* () {
561
+ const passed: StorageTestCase<ColdStorageTestError, ColdStorageTag>[] = [];
562
+ const failed: Array<{
563
+ test: StorageTestCase<ColdStorageTestError, ColdStorageTag>;
564
+ error: ColdStorageTestError;
565
+ }> = [];
566
+
567
+ for (const test of tests) {
568
+ const result = yield* Effect.either(test.run);
569
+ if (result._tag === "Right") {
570
+ passed.push(test);
571
+ } else {
572
+ failed.push({ test, error: result.left });
573
+ }
574
+ }
575
+
576
+ return {
577
+ passed,
578
+ failed,
579
+ total: tests.length,
580
+ passCount: passed.length,
581
+ failCount: failed.length,
582
+ };
583
+ });
584
+
585
+ export const ColdStorageTestSuite = {
586
+ Categories,
587
+ makeTests,
588
+ runAll,
589
+ };