@voidhash/mimic-effect 0.0.9 → 1.0.0-beta.2

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 (227) hide show
  1. package/.turbo/turbo-build.log +136 -90
  2. package/README.md +385 -0
  3. package/dist/ColdStorage.cjs +60 -0
  4. package/dist/ColdStorage.d.cts +53 -0
  5. package/dist/ColdStorage.d.cts.map +1 -0
  6. package/dist/ColdStorage.d.mts +53 -0
  7. package/dist/ColdStorage.d.mts.map +1 -0
  8. package/dist/ColdStorage.mjs +60 -0
  9. package/dist/ColdStorage.mjs.map +1 -0
  10. package/dist/DocumentManager.cjs +263 -82
  11. package/dist/DocumentManager.d.cts +44 -22
  12. package/dist/DocumentManager.d.cts.map +1 -1
  13. package/dist/DocumentManager.d.mts +44 -22
  14. package/dist/DocumentManager.d.mts.map +1 -1
  15. package/dist/DocumentManager.mjs +259 -67
  16. package/dist/DocumentManager.mjs.map +1 -1
  17. package/dist/Errors.cjs +54 -0
  18. package/dist/Errors.d.cts +96 -0
  19. package/dist/Errors.d.cts.map +1 -0
  20. package/dist/Errors.d.mts +96 -0
  21. package/dist/Errors.d.mts.map +1 -0
  22. package/dist/Errors.mjs +48 -0
  23. package/dist/Errors.mjs.map +1 -0
  24. package/dist/HotStorage.cjs +100 -0
  25. package/dist/HotStorage.d.cts +70 -0
  26. package/dist/HotStorage.d.cts.map +1 -0
  27. package/dist/HotStorage.d.mts +70 -0
  28. package/dist/HotStorage.d.mts.map +1 -0
  29. package/dist/HotStorage.mjs +100 -0
  30. package/dist/HotStorage.mjs.map +1 -0
  31. package/dist/Metrics.cjs +143 -0
  32. package/dist/Metrics.d.cts +31 -0
  33. package/dist/Metrics.d.cts.map +1 -0
  34. package/dist/Metrics.d.mts +31 -0
  35. package/dist/Metrics.d.mts.map +1 -0
  36. package/dist/Metrics.mjs +126 -0
  37. package/dist/Metrics.mjs.map +1 -0
  38. package/dist/MimicAuthService.cjs +61 -45
  39. package/dist/MimicAuthService.d.cts +61 -48
  40. package/dist/MimicAuthService.d.cts.map +1 -1
  41. package/dist/MimicAuthService.d.mts +61 -48
  42. package/dist/MimicAuthService.d.mts.map +1 -1
  43. package/dist/MimicAuthService.mjs +60 -36
  44. package/dist/MimicAuthService.mjs.map +1 -1
  45. package/dist/MimicClusterServerEngine.cjs +521 -0
  46. package/dist/MimicClusterServerEngine.d.cts +17 -0
  47. package/dist/MimicClusterServerEngine.d.cts.map +1 -0
  48. package/dist/MimicClusterServerEngine.d.mts +17 -0
  49. package/dist/MimicClusterServerEngine.d.mts.map +1 -0
  50. package/dist/MimicClusterServerEngine.mjs +523 -0
  51. package/dist/MimicClusterServerEngine.mjs.map +1 -0
  52. package/dist/MimicServer.cjs +205 -96
  53. package/dist/MimicServer.d.cts +9 -110
  54. package/dist/MimicServer.d.cts.map +1 -1
  55. package/dist/MimicServer.d.mts +9 -110
  56. package/dist/MimicServer.d.mts.map +1 -1
  57. package/dist/MimicServer.mjs +206 -90
  58. package/dist/MimicServer.mjs.map +1 -1
  59. package/dist/MimicServerEngine.cjs +97 -0
  60. package/dist/MimicServerEngine.d.cts +78 -0
  61. package/dist/MimicServerEngine.d.cts.map +1 -0
  62. package/dist/MimicServerEngine.d.mts +78 -0
  63. package/dist/MimicServerEngine.d.mts.map +1 -0
  64. package/dist/MimicServerEngine.mjs +97 -0
  65. package/dist/MimicServerEngine.mjs.map +1 -0
  66. package/dist/PresenceManager.cjs +75 -91
  67. package/dist/PresenceManager.d.cts +17 -66
  68. package/dist/PresenceManager.d.cts.map +1 -1
  69. package/dist/PresenceManager.d.mts +17 -66
  70. package/dist/PresenceManager.d.mts.map +1 -1
  71. package/dist/PresenceManager.mjs +74 -78
  72. package/dist/PresenceManager.mjs.map +1 -1
  73. package/dist/Protocol.cjs +146 -0
  74. package/dist/Protocol.d.cts +203 -0
  75. package/dist/Protocol.d.cts.map +1 -0
  76. package/dist/Protocol.d.mts +203 -0
  77. package/dist/Protocol.d.mts.map +1 -0
  78. package/dist/Protocol.mjs +132 -0
  79. package/dist/Protocol.mjs.map +1 -0
  80. package/dist/Types.d.cts +172 -0
  81. package/dist/Types.d.cts.map +1 -0
  82. package/dist/Types.d.mts +172 -0
  83. package/dist/Types.d.mts.map +1 -0
  84. package/dist/_virtual/rolldown_runtime.cjs +1 -25
  85. package/dist/_virtual/rolldown_runtime.mjs +4 -1
  86. package/dist/index.cjs +37 -75
  87. package/dist/index.d.cts +13 -12
  88. package/dist/index.d.mts +13 -12
  89. package/dist/index.mjs +12 -12
  90. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  91. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  92. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  93. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  94. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  95. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  96. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  97. package/dist/testing/FailingStorage.cjs +135 -0
  98. package/dist/testing/FailingStorage.d.cts +43 -0
  99. package/dist/testing/FailingStorage.d.cts.map +1 -0
  100. package/dist/testing/FailingStorage.d.mts +43 -0
  101. package/dist/testing/FailingStorage.d.mts.map +1 -0
  102. package/dist/testing/FailingStorage.mjs +136 -0
  103. package/dist/testing/FailingStorage.mjs.map +1 -0
  104. package/dist/testing/HotStorageTestSuite.cjs +585 -0
  105. package/dist/testing/HotStorageTestSuite.d.cts +40 -0
  106. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  107. package/dist/testing/HotStorageTestSuite.d.mts +40 -0
  108. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  109. package/dist/testing/HotStorageTestSuite.mjs +585 -0
  110. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  111. package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
  112. package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
  113. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  114. package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
  115. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  116. package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
  117. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  118. package/dist/testing/assertions.cjs +114 -0
  119. package/dist/testing/assertions.mjs +109 -0
  120. package/dist/testing/assertions.mjs.map +1 -0
  121. package/dist/testing/index.cjs +14 -0
  122. package/dist/testing/index.d.cts +6 -0
  123. package/dist/testing/index.d.mts +6 -0
  124. package/dist/testing/index.mjs +7 -0
  125. package/dist/testing/types.cjs +15 -0
  126. package/dist/testing/types.d.cts +90 -0
  127. package/dist/testing/types.d.cts.map +1 -0
  128. package/dist/testing/types.d.mts +90 -0
  129. package/dist/testing/types.d.mts.map +1 -0
  130. package/dist/testing/types.mjs +16 -0
  131. package/dist/testing/types.mjs.map +1 -0
  132. package/package.json +18 -3
  133. package/src/ColdStorage.ts +136 -0
  134. package/src/DocumentManager.ts +550 -190
  135. package/src/Errors.ts +114 -0
  136. package/src/HotStorage.ts +239 -0
  137. package/src/Metrics.ts +187 -0
  138. package/src/MimicAuthService.ts +126 -64
  139. package/src/MimicClusterServerEngine.ts +946 -0
  140. package/src/MimicServer.ts +448 -195
  141. package/src/MimicServerEngine.ts +276 -0
  142. package/src/PresenceManager.ts +169 -240
  143. package/src/Protocol.ts +350 -0
  144. package/src/Types.ts +231 -0
  145. package/src/index.ts +57 -23
  146. package/src/testing/ColdStorageTestSuite.ts +589 -0
  147. package/src/testing/FailingStorage.ts +286 -0
  148. package/src/testing/HotStorageTestSuite.ts +762 -0
  149. package/src/testing/StorageIntegrationTestSuite.ts +504 -0
  150. package/src/testing/assertions.ts +181 -0
  151. package/src/testing/index.ts +83 -0
  152. package/src/testing/types.ts +100 -0
  153. package/tests/ColdStorage.test.ts +24 -0
  154. package/tests/DocumentManager.test.ts +158 -287
  155. package/tests/HotStorage.test.ts +24 -0
  156. package/tests/MimicAuthService.test.ts +102 -134
  157. package/tests/MimicClusterServerEngine.test.ts +587 -0
  158. package/tests/MimicServer.test.ts +90 -226
  159. package/tests/MimicServerEngine.test.ts +521 -0
  160. package/tests/PresenceManager.test.ts +22 -63
  161. package/tests/Protocol.test.ts +190 -0
  162. package/tests/StorageIntegration.test.ts +259 -0
  163. package/tsconfig.json +1 -1
  164. package/tsdown.config.ts +1 -1
  165. package/dist/DocumentProtocol.cjs +0 -94
  166. package/dist/DocumentProtocol.d.cts +0 -113
  167. package/dist/DocumentProtocol.d.cts.map +0 -1
  168. package/dist/DocumentProtocol.d.mts +0 -113
  169. package/dist/DocumentProtocol.d.mts.map +0 -1
  170. package/dist/DocumentProtocol.mjs +0 -89
  171. package/dist/DocumentProtocol.mjs.map +0 -1
  172. package/dist/MimicConfig.cjs +0 -60
  173. package/dist/MimicConfig.d.cts +0 -141
  174. package/dist/MimicConfig.d.cts.map +0 -1
  175. package/dist/MimicConfig.d.mts +0 -141
  176. package/dist/MimicConfig.d.mts.map +0 -1
  177. package/dist/MimicConfig.mjs +0 -50
  178. package/dist/MimicConfig.mjs.map +0 -1
  179. package/dist/MimicDataStorage.cjs +0 -83
  180. package/dist/MimicDataStorage.d.cts +0 -113
  181. package/dist/MimicDataStorage.d.cts.map +0 -1
  182. package/dist/MimicDataStorage.d.mts +0 -113
  183. package/dist/MimicDataStorage.d.mts.map +0 -1
  184. package/dist/MimicDataStorage.mjs +0 -74
  185. package/dist/MimicDataStorage.mjs.map +0 -1
  186. package/dist/WebSocketHandler.cjs +0 -365
  187. package/dist/WebSocketHandler.d.cts +0 -34
  188. package/dist/WebSocketHandler.d.cts.map +0 -1
  189. package/dist/WebSocketHandler.d.mts +0 -34
  190. package/dist/WebSocketHandler.d.mts.map +0 -1
  191. package/dist/WebSocketHandler.mjs +0 -355
  192. package/dist/WebSocketHandler.mjs.map +0 -1
  193. package/dist/auth/NoAuth.cjs +0 -43
  194. package/dist/auth/NoAuth.d.cts +0 -22
  195. package/dist/auth/NoAuth.d.cts.map +0 -1
  196. package/dist/auth/NoAuth.d.mts +0 -22
  197. package/dist/auth/NoAuth.d.mts.map +0 -1
  198. package/dist/auth/NoAuth.mjs +0 -36
  199. package/dist/auth/NoAuth.mjs.map +0 -1
  200. package/dist/errors.cjs +0 -74
  201. package/dist/errors.d.cts +0 -89
  202. package/dist/errors.d.cts.map +0 -1
  203. package/dist/errors.d.mts +0 -89
  204. package/dist/errors.d.mts.map +0 -1
  205. package/dist/errors.mjs +0 -67
  206. package/dist/errors.mjs.map +0 -1
  207. package/dist/storage/InMemoryDataStorage.cjs +0 -57
  208. package/dist/storage/InMemoryDataStorage.d.cts +0 -19
  209. package/dist/storage/InMemoryDataStorage.d.cts.map +0 -1
  210. package/dist/storage/InMemoryDataStorage.d.mts +0 -19
  211. package/dist/storage/InMemoryDataStorage.d.mts.map +0 -1
  212. package/dist/storage/InMemoryDataStorage.mjs +0 -48
  213. package/dist/storage/InMemoryDataStorage.mjs.map +0 -1
  214. package/src/DocumentProtocol.ts +0 -112
  215. package/src/MimicConfig.ts +0 -211
  216. package/src/MimicDataStorage.ts +0 -157
  217. package/src/WebSocketHandler.ts +0 -735
  218. package/src/auth/NoAuth.ts +0 -46
  219. package/src/errors.ts +0 -113
  220. package/src/storage/InMemoryDataStorage.ts +0 -66
  221. package/tests/DocumentProtocol.test.ts +0 -113
  222. package/tests/InMemoryDataStorage.test.ts +0 -190
  223. package/tests/MimicConfig.test.ts +0 -290
  224. package/tests/MimicDataStorage.test.ts +0 -190
  225. package/tests/NoAuth.test.ts +0 -94
  226. package/tests/WebSocketHandler.test.ts +0 -321
  227. package/tests/errors.test.ts +0 -77
@@ -1,20 +1,13 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import * as Effect from "effect/Effect";
3
- import * as Stream from "effect/Stream";
4
- import * as Chunk from "effect/Chunk";
5
- import * as Fiber from "effect/Fiber";
6
- import * as PresenceManager from "../src/PresenceManager";
7
-
8
- // =============================================================================
9
- // PresenceManager Tests
10
- // =============================================================================
2
+ import { Effect, Stream, Chunk, Fiber } from "effect";
3
+ import { PresenceManager, PresenceManagerTag } from "../src/PresenceManager";
11
4
 
12
5
  describe("PresenceManager", () => {
13
6
  describe("getSnapshot", () => {
14
7
  it("should return empty snapshot for unknown document", async () => {
15
8
  const result = await Effect.runPromise(
16
9
  Effect.gen(function* () {
17
- const pm = yield* PresenceManager.PresenceManagerTag;
10
+ const pm = yield* PresenceManagerTag;
18
11
  return yield* pm.getSnapshot("unknown-doc");
19
12
  }).pipe(Effect.provide(PresenceManager.layer))
20
13
  );
@@ -22,10 +15,10 @@ describe("PresenceManager", () => {
22
15
  expect(result.presences).toEqual({});
23
16
  });
24
17
 
25
- it("should return existing presences after set", async () => {
18
+ it("should return presences after set", async () => {
26
19
  const result = await Effect.runPromise(
27
20
  Effect.gen(function* () {
28
- const pm = yield* PresenceManager.PresenceManagerTag;
21
+ const pm = yield* PresenceManagerTag;
29
22
 
30
23
  yield* pm.set("doc-1", "conn-1", {
31
24
  data: { x: 10, y: 20 },
@@ -44,7 +37,7 @@ describe("PresenceManager", () => {
44
37
  it("should return multiple presences", async () => {
45
38
  const result = await Effect.runPromise(
46
39
  Effect.gen(function* () {
47
- const pm = yield* PresenceManager.PresenceManagerTag;
40
+ const pm = yield* PresenceManagerTag;
48
41
 
49
42
  yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
50
43
  yield* pm.set("doc-1", "conn-2", {
@@ -71,7 +64,7 @@ describe("PresenceManager", () => {
71
64
  it("should store presence entry", async () => {
72
65
  const result = await Effect.runPromise(
73
66
  Effect.gen(function* () {
74
- const pm = yield* PresenceManager.PresenceManagerTag;
67
+ const pm = yield* PresenceManagerTag;
75
68
 
76
69
  yield* pm.set("doc-1", "conn-1", {
77
70
  data: { cursor: { x: 100, y: 200 } },
@@ -89,7 +82,7 @@ describe("PresenceManager", () => {
89
82
  it("should update existing presence entry", async () => {
90
83
  const result = await Effect.runPromise(
91
84
  Effect.gen(function* () {
92
- const pm = yield* PresenceManager.PresenceManagerTag;
85
+ const pm = yield* PresenceManagerTag;
93
86
 
94
87
  yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
95
88
  yield* pm.set("doc-1", "conn-1", { data: { x: 100, y: 200 } });
@@ -105,7 +98,7 @@ describe("PresenceManager", () => {
105
98
  const result = await Effect.runPromise(
106
99
  Effect.scoped(
107
100
  Effect.gen(function* () {
108
- const pm = yield* PresenceManager.PresenceManagerTag;
101
+ const pm = yield* PresenceManagerTag;
109
102
 
110
103
  // Subscribe first
111
104
  const eventStream = yield* pm.subscribe("doc-1");
@@ -146,7 +139,7 @@ describe("PresenceManager", () => {
146
139
  it("should remove presence entry", async () => {
147
140
  const result = await Effect.runPromise(
148
141
  Effect.gen(function* () {
149
- const pm = yield* PresenceManager.PresenceManagerTag;
142
+ const pm = yield* PresenceManagerTag;
150
143
 
151
144
  yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
152
145
  yield* pm.remove("doc-1", "conn-1");
@@ -162,7 +155,7 @@ describe("PresenceManager", () => {
162
155
  await expect(
163
156
  Effect.runPromise(
164
157
  Effect.gen(function* () {
165
- const pm = yield* PresenceManager.PresenceManagerTag;
158
+ const pm = yield* PresenceManagerTag;
166
159
  yield* pm.remove("doc-1", "non-existent-conn");
167
160
  }).pipe(Effect.provide(PresenceManager.layer))
168
161
  )
@@ -173,7 +166,7 @@ describe("PresenceManager", () => {
173
166
  await expect(
174
167
  Effect.runPromise(
175
168
  Effect.gen(function* () {
176
- const pm = yield* PresenceManager.PresenceManagerTag;
169
+ const pm = yield* PresenceManagerTag;
177
170
  yield* pm.remove("non-existent-doc", "conn-1");
178
171
  }).pipe(Effect.provide(PresenceManager.layer))
179
172
  )
@@ -184,7 +177,7 @@ describe("PresenceManager", () => {
184
177
  const result = await Effect.runPromise(
185
178
  Effect.scoped(
186
179
  Effect.gen(function* () {
187
- const pm = yield* PresenceManager.PresenceManagerTag;
180
+ const pm = yield* PresenceManagerTag;
188
181
 
189
182
  // Set presence first
190
183
  yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
@@ -222,7 +215,7 @@ describe("PresenceManager", () => {
222
215
  const result = await Effect.runPromise(
223
216
  Effect.scoped(
224
217
  Effect.gen(function* () {
225
- const pm = yield* PresenceManager.PresenceManagerTag;
218
+ const pm = yield* PresenceManagerTag;
226
219
 
227
220
  // Subscribe to doc that has no presences
228
221
  const eventStream = yield* pm.subscribe("doc-1");
@@ -257,7 +250,7 @@ describe("PresenceManager", () => {
257
250
  const result = await Effect.runPromise(
258
251
  Effect.scoped(
259
252
  Effect.gen(function* () {
260
- const pm = yield* PresenceManager.PresenceManagerTag;
253
+ const pm = yield* PresenceManagerTag;
261
254
 
262
255
  const eventStream = yield* pm.subscribe("doc-1");
263
256
 
@@ -285,7 +278,7 @@ describe("PresenceManager", () => {
285
278
  const result = await Effect.runPromise(
286
279
  Effect.scoped(
287
280
  Effect.gen(function* () {
288
- const pm = yield* PresenceManager.PresenceManagerTag;
281
+ const pm = yield* PresenceManagerTag;
289
282
 
290
283
  // Set up initial presence
291
284
  yield* pm.set("doc-1", "conn-1", { data: { x: 10 } });
@@ -319,7 +312,7 @@ describe("PresenceManager", () => {
319
312
  it("should isolate presences between documents", async () => {
320
313
  const result = await Effect.runPromise(
321
314
  Effect.gen(function* () {
322
- const pm = yield* PresenceManager.PresenceManagerTag;
315
+ const pm = yield* PresenceManagerTag;
323
316
 
324
317
  yield* pm.set("doc-1", "conn-1", { data: { x: 10 } });
325
318
  yield* pm.set("doc-2", "conn-2", { data: { x: 20 } });
@@ -344,7 +337,7 @@ describe("PresenceManager", () => {
344
337
  const result = await Effect.runPromise(
345
338
  Effect.scoped(
346
339
  Effect.gen(function* () {
347
- const pm = yield* PresenceManager.PresenceManagerTag;
340
+ const pm = yield* PresenceManagerTag;
348
341
 
349
342
  // Subscribe to doc-1 only
350
343
  const eventStream = yield* pm.subscribe("doc-1");
@@ -377,45 +370,11 @@ describe("PresenceManager", () => {
377
370
  });
378
371
  });
379
372
 
380
- describe("multiple connections per document", () => {
381
- it("should handle multiple connections setting presence independently", async () => {
382
- const result = await Effect.runPromise(
383
- Effect.gen(function* () {
384
- const pm = yield* PresenceManager.PresenceManagerTag;
385
-
386
- yield* pm.set("doc-1", "conn-1", { data: { x: 10 }, userId: "user-1" });
387
- yield* pm.set("doc-1", "conn-2", { data: { x: 20 }, userId: "user-2" });
388
- yield* pm.set("doc-1", "conn-3", { data: { x: 30 }, userId: "user-3" });
389
-
390
- // Update one connection
391
- yield* pm.set("doc-1", "conn-2", { data: { x: 200 }, userId: "user-2" });
392
-
393
- // Remove one connection
394
- yield* pm.remove("doc-1", "conn-1");
395
-
396
- return yield* pm.getSnapshot("doc-1");
397
- }).pipe(Effect.provide(PresenceManager.layer))
398
- );
399
-
400
- expect(Object.keys(result.presences).length).toBe(2);
401
- expect(result.presences["conn-1"]).toBeUndefined();
402
- expect(result.presences["conn-2"]).toEqual({
403
- data: { x: 200 },
404
- userId: "user-2",
405
- });
406
- expect(result.presences["conn-3"]).toEqual({
407
- data: { x: 30 },
408
- userId: "user-3",
409
- });
410
- });
411
- });
412
-
413
- describe("PresenceManagerTag", () => {
414
- it("should have correct tag identifier", () => {
415
- expect(PresenceManager.PresenceManagerTag.key).toBe(
416
- "@voidhash/mimic-server-effect/PresenceManager"
373
+ describe("Tag", () => {
374
+ it("should have correct identifier", () => {
375
+ expect(PresenceManagerTag.key).toBe(
376
+ "@voidhash/mimic-effect/PresenceManager"
417
377
  );
418
378
  });
419
379
  });
420
380
  });
421
-
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { Effect } from "effect";
3
+ import * as Protocol from "../src/Protocol";
4
+ import { Transaction, Document, Primitive } from "@voidhash/mimic";
5
+
6
+ describe("Protocol", () => {
7
+ describe("parseClientMessage", () => {
8
+ it("should parse auth message", async () => {
9
+ const data = JSON.stringify({ type: "auth", token: "test-token" });
10
+
11
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
12
+
13
+ expect(result.type).toBe("auth");
14
+ expect((result as Protocol.AuthMessage).token).toBe("test-token");
15
+ });
16
+
17
+ it("should parse ping message", async () => {
18
+ const data = JSON.stringify({ type: "ping" });
19
+
20
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
21
+
22
+ expect(result.type).toBe("ping");
23
+ });
24
+
25
+ it("should parse request_snapshot message", async () => {
26
+ const data = JSON.stringify({ type: "request_snapshot" });
27
+
28
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
29
+
30
+ expect(result.type).toBe("request_snapshot");
31
+ });
32
+
33
+ it("should parse presence_set message", async () => {
34
+ const data = JSON.stringify({
35
+ type: "presence_set",
36
+ data: { cursor: { x: 10, y: 20 } },
37
+ });
38
+
39
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
40
+
41
+ expect(result.type).toBe("presence_set");
42
+ expect((result as Protocol.PresenceSetMessage).data).toEqual({
43
+ cursor: { x: 10, y: 20 },
44
+ });
45
+ });
46
+
47
+ it("should parse presence_clear message", async () => {
48
+ const data = JSON.stringify({ type: "presence_clear" });
49
+
50
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
51
+
52
+ expect(result.type).toBe("presence_clear");
53
+ });
54
+
55
+ it("should parse submit message with transaction", async () => {
56
+ // Create a real transaction using the Document API
57
+ const schema = Primitive.Struct({
58
+ title: Primitive.String().default(""),
59
+ });
60
+ const doc = Document.make(schema);
61
+ doc.transaction((root) => root.title.set("Test"));
62
+ const tx = doc.flush();
63
+ const encodedTx = Transaction.encode(tx);
64
+
65
+ const data = JSON.stringify({
66
+ type: "submit",
67
+ transaction: encodedTx,
68
+ });
69
+
70
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
71
+
72
+ expect(result.type).toBe("submit");
73
+ const submitResult = result as Protocol.SubmitMessage;
74
+ expect(submitResult.transaction.id).toBe(tx.id);
75
+ });
76
+
77
+ it("should fail on invalid JSON", async () => {
78
+ const result = await Effect.runPromise(
79
+ Effect.either(Protocol.parseClientMessage("not json"))
80
+ );
81
+
82
+ expect(result._tag).toBe("Left");
83
+ });
84
+
85
+ it("should handle Uint8Array input", async () => {
86
+ const data = new TextEncoder().encode(
87
+ JSON.stringify({ type: "ping" })
88
+ );
89
+
90
+ const result = await Effect.runPromise(Protocol.parseClientMessage(data));
91
+
92
+ expect(result.type).toBe("ping");
93
+ });
94
+ });
95
+
96
+ describe("encodeServerMessage", () => {
97
+ it("should encode auth_result success", () => {
98
+ const message = Protocol.authResultSuccess("user-1", "write");
99
+ const encoded = Protocol.encodeServerMessage(message);
100
+ const parsed = JSON.parse(encoded);
101
+
102
+ expect(parsed.type).toBe("auth_result");
103
+ expect(parsed.success).toBe(true);
104
+ expect(parsed.userId).toBe("user-1");
105
+ expect(parsed.permission).toBe("write");
106
+ });
107
+
108
+ it("should encode auth_result failure", () => {
109
+ const message = Protocol.authResultFailure("Invalid token");
110
+ const encoded = Protocol.encodeServerMessage(message);
111
+ const parsed = JSON.parse(encoded);
112
+
113
+ expect(parsed.type).toBe("auth_result");
114
+ expect(parsed.success).toBe(false);
115
+ expect(parsed.error).toBe("Invalid token");
116
+ });
117
+
118
+ it("should encode pong", () => {
119
+ const message = Protocol.pong();
120
+ const encoded = Protocol.encodeServerMessage(message);
121
+ const parsed = JSON.parse(encoded);
122
+
123
+ expect(parsed.type).toBe("pong");
124
+ });
125
+
126
+ it("should encode snapshot", () => {
127
+ const message = Protocol.snapshotMessage({ title: "Test" }, 5);
128
+ const encoded = Protocol.encodeServerMessage(message);
129
+ const parsed = JSON.parse(encoded);
130
+
131
+ expect(parsed.type).toBe("snapshot");
132
+ expect(parsed.state).toEqual({ title: "Test" });
133
+ expect(parsed.version).toBe(5);
134
+ });
135
+
136
+ it("should encode error", () => {
137
+ const message = Protocol.errorMessage("tx-1", "Transaction rejected");
138
+ const encoded = Protocol.encodeServerMessage(message);
139
+ const parsed = JSON.parse(encoded);
140
+
141
+ expect(parsed.type).toBe("error");
142
+ expect(parsed.transactionId).toBe("tx-1");
143
+ expect(parsed.reason).toBe("Transaction rejected");
144
+ });
145
+
146
+ it("should encode presence_update", () => {
147
+ const message = Protocol.presenceUpdateMessage("conn-1", { x: 10 }, "user-1");
148
+ const encoded = Protocol.encodeServerMessage(message);
149
+ const parsed = JSON.parse(encoded);
150
+
151
+ expect(parsed.type).toBe("presence_update");
152
+ expect(parsed.id).toBe("conn-1");
153
+ expect(parsed.data).toEqual({ x: 10 });
154
+ expect(parsed.userId).toBe("user-1");
155
+ });
156
+
157
+ it("should encode presence_remove", () => {
158
+ const message = Protocol.presenceRemoveMessage("conn-1");
159
+ const encoded = Protocol.encodeServerMessage(message);
160
+ const parsed = JSON.parse(encoded);
161
+
162
+ expect(parsed.type).toBe("presence_remove");
163
+ expect(parsed.id).toBe("conn-1");
164
+ });
165
+
166
+ it("should encode presence_snapshot", () => {
167
+ const message = Protocol.presenceSnapshotMessage("self-id", {
168
+ "conn-1": { data: { x: 10 } },
169
+ });
170
+ const encoded = Protocol.encodeServerMessage(message);
171
+ const parsed = JSON.parse(encoded);
172
+
173
+ expect(parsed.type).toBe("presence_snapshot");
174
+ expect(parsed.selfId).toBe("self-id");
175
+ expect(parsed.presences["conn-1"].data).toEqual({ x: 10 });
176
+ });
177
+
178
+ it("should encode transaction with encoded transaction", () => {
179
+ const tx = Transaction.make([]);
180
+ const message = Protocol.transactionMessage(tx, 3);
181
+ const encoded = Protocol.encodeServerMessage(message);
182
+ const parsed = JSON.parse(encoded);
183
+
184
+ expect(parsed.type).toBe("transaction");
185
+ expect(parsed.version).toBe(3);
186
+ expect(parsed.transaction).toBeDefined();
187
+ expect(parsed.transaction.id).toBe(tx.id);
188
+ });
189
+ });
190
+ });
@@ -0,0 +1,259 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { Effect, Layer } from "effect";
3
+ import { StorageIntegrationTestSuite } from "../src/testing/StorageIntegrationTestSuite";
4
+ import { FailingStorage } from "../src/testing/FailingStorage";
5
+ import { ColdStorage, ColdStorageTag } from "../src/ColdStorage";
6
+ import { HotStorage, HotStorageTag } from "../src/HotStorage";
7
+
8
+ // =============================================================================
9
+ // Storage Integration Tests
10
+ // =============================================================================
11
+
12
+ describe("Storage Integration", () => {
13
+ const layer = Layer.mergeAll(
14
+ ColdStorage.InMemory.make(),
15
+ HotStorage.InMemory.make()
16
+ );
17
+
18
+ for (const test of StorageIntegrationTestSuite.makeTests()) {
19
+ it(test.name, () =>
20
+ Effect.runPromise(test.run.pipe(Effect.provide(layer)))
21
+ );
22
+ }
23
+ });
24
+
25
+ // =============================================================================
26
+ // Failure Scenario Tests
27
+ // =============================================================================
28
+
29
+ describe("Storage Failure Scenarios", () => {
30
+ describe("ColdStorage Failures", () => {
31
+ it("load failure propagates error", async () => {
32
+ const failingLayer = Layer.mergeAll(
33
+ FailingStorage.makeColdStorage({ failLoad: true }),
34
+ HotStorage.InMemory.make()
35
+ );
36
+
37
+ const result = await Effect.runPromise(
38
+ Effect.gen(function* () {
39
+ const cold = yield* ColdStorageTag;
40
+ return yield* Effect.either(cold.load("test-doc"));
41
+ }).pipe(Effect.provide(failingLayer))
42
+ );
43
+
44
+ expect(result._tag).toBe("Left");
45
+ if (result._tag === "Left") {
46
+ expect(result.left._tag).toBe("ColdStorageError");
47
+ }
48
+ });
49
+
50
+ it("save failure propagates error", async () => {
51
+ const failingLayer = Layer.mergeAll(
52
+ FailingStorage.makeColdStorage({ failSave: true }),
53
+ HotStorage.InMemory.make()
54
+ );
55
+
56
+ const result = await Effect.runPromise(
57
+ Effect.gen(function* () {
58
+ const cold = yield* ColdStorageTag;
59
+ return yield* Effect.either(
60
+ cold.save("test-doc", {
61
+ state: { data: "test" },
62
+ version: 1,
63
+ schemaVersion: 1,
64
+ savedAt: Date.now(),
65
+ })
66
+ );
67
+ }).pipe(Effect.provide(failingLayer))
68
+ );
69
+
70
+ expect(result._tag).toBe("Left");
71
+ if (result._tag === "Left") {
72
+ expect(result.left._tag).toBe("ColdStorageError");
73
+ }
74
+ });
75
+
76
+ it("failAfterN allows first N operations then fails", async () => {
77
+ const failingLayer = Layer.mergeAll(
78
+ FailingStorage.makeColdStorage({ failAfterN: 2, failLoad: true }),
79
+ HotStorage.InMemory.make()
80
+ );
81
+
82
+ const results = await Effect.runPromise(
83
+ Effect.gen(function* () {
84
+ const cold = yield* ColdStorageTag;
85
+
86
+ // First 2 operations succeed
87
+ const r1 = yield* Effect.either(cold.load("doc-1"));
88
+ const r2 = yield* Effect.either(cold.load("doc-2"));
89
+
90
+ // Third operation fails
91
+ const r3 = yield* Effect.either(cold.load("doc-3"));
92
+
93
+ return { r1, r2, r3 };
94
+ }).pipe(Effect.provide(failingLayer))
95
+ );
96
+
97
+ expect(results.r1._tag).toBe("Right");
98
+ expect(results.r2._tag).toBe("Right");
99
+ expect(results.r3._tag).toBe("Left");
100
+ });
101
+ });
102
+
103
+ describe("HotStorage Failures", () => {
104
+ it("append failure propagates error", async () => {
105
+ const failingLayer = Layer.mergeAll(
106
+ ColdStorage.InMemory.make(),
107
+ FailingStorage.makeHotStorage({ failAppend: true })
108
+ );
109
+
110
+ const result = await Effect.runPromise(
111
+ Effect.gen(function* () {
112
+ const hot = yield* HotStorageTag;
113
+ return yield* Effect.either(
114
+ hot.append("test-doc", {
115
+ transaction: { id: "tx-1", ops: [], timestamp: Date.now() },
116
+ version: 1,
117
+ timestamp: Date.now(),
118
+ })
119
+ );
120
+ }).pipe(Effect.provide(failingLayer))
121
+ );
122
+
123
+ expect(result._tag).toBe("Left");
124
+ if (result._tag === "Left") {
125
+ expect(result.left._tag).toBe("HotStorageError");
126
+ }
127
+ });
128
+
129
+ it("getEntries failure propagates error", async () => {
130
+ const failingLayer = Layer.mergeAll(
131
+ ColdStorage.InMemory.make(),
132
+ FailingStorage.makeHotStorage({ failGetEntries: true })
133
+ );
134
+
135
+ const result = await Effect.runPromise(
136
+ Effect.gen(function* () {
137
+ const hot = yield* HotStorageTag;
138
+ return yield* Effect.either(hot.getEntries("test-doc", 0));
139
+ }).pipe(Effect.provide(failingLayer))
140
+ );
141
+
142
+ expect(result._tag).toBe("Left");
143
+ if (result._tag === "Left") {
144
+ expect(result.left._tag).toBe("HotStorageError");
145
+ }
146
+ });
147
+
148
+ it("truncate failure propagates error", async () => {
149
+ const failingLayer = Layer.mergeAll(
150
+ ColdStorage.InMemory.make(),
151
+ FailingStorage.makeHotStorage({ failTruncate: true })
152
+ );
153
+
154
+ const result = await Effect.runPromise(
155
+ Effect.gen(function* () {
156
+ const hot = yield* HotStorageTag;
157
+ return yield* Effect.either(hot.truncate("test-doc", 5));
158
+ }).pipe(Effect.provide(failingLayer))
159
+ );
160
+
161
+ expect(result._tag).toBe("Left");
162
+ if (result._tag === "Left") {
163
+ expect(result.left._tag).toBe("HotStorageError");
164
+ }
165
+ });
166
+
167
+ it("failAfterN allows first N operations then fails", async () => {
168
+ const failingLayer = Layer.mergeAll(
169
+ ColdStorage.InMemory.make(),
170
+ FailingStorage.makeHotStorage({ failAfterN: 3, failAppend: true })
171
+ );
172
+
173
+ const results = await Effect.runPromise(
174
+ Effect.gen(function* () {
175
+ const hot = yield* HotStorageTag;
176
+
177
+ const makeEntry = (v: number) => ({
178
+ transaction: { id: `tx-${v}`, ops: [], timestamp: Date.now() },
179
+ version: v,
180
+ timestamp: Date.now(),
181
+ });
182
+
183
+ // First 3 appends succeed
184
+ const r1 = yield* Effect.either(hot.append("doc", makeEntry(1)));
185
+ const r2 = yield* Effect.either(hot.append("doc", makeEntry(2)));
186
+ const r3 = yield* Effect.either(hot.append("doc", makeEntry(3)));
187
+
188
+ // Fourth append fails
189
+ const r4 = yield* Effect.either(hot.append("doc", makeEntry(4)));
190
+
191
+ return { r1, r2, r3, r4 };
192
+ }).pipe(Effect.provide(failingLayer))
193
+ );
194
+
195
+ expect(results.r1._tag).toBe("Right");
196
+ expect(results.r2._tag).toBe("Right");
197
+ expect(results.r3._tag).toBe("Right");
198
+ expect(results.r4._tag).toBe("Left");
199
+ });
200
+ });
201
+
202
+ describe("Custom Error Messages", () => {
203
+ it("ColdStorage uses custom error message", async () => {
204
+ const failingLayer = Layer.mergeAll(
205
+ FailingStorage.makeColdStorage({
206
+ failLoad: true,
207
+ errorMessage: "Database connection timeout",
208
+ }),
209
+ HotStorage.InMemory.make()
210
+ );
211
+
212
+ const result = await Effect.runPromise(
213
+ Effect.gen(function* () {
214
+ const cold = yield* ColdStorageTag;
215
+ return yield* Effect.either(cold.load("test-doc"));
216
+ }).pipe(Effect.provide(failingLayer))
217
+ );
218
+
219
+ expect(result._tag).toBe("Left");
220
+ if (result._tag === "Left") {
221
+ expect(result.left.cause).toBeInstanceOf(Error);
222
+ expect((result.left.cause as Error).message).toBe(
223
+ "Database connection timeout"
224
+ );
225
+ }
226
+ });
227
+
228
+ it("HotStorage uses custom error message", async () => {
229
+ const failingLayer = Layer.mergeAll(
230
+ ColdStorage.InMemory.make(),
231
+ FailingStorage.makeHotStorage({
232
+ failAppend: true,
233
+ errorMessage: "Redis cluster unavailable",
234
+ })
235
+ );
236
+
237
+ const result = await Effect.runPromise(
238
+ Effect.gen(function* () {
239
+ const hot = yield* HotStorageTag;
240
+ return yield* Effect.either(
241
+ hot.append("test-doc", {
242
+ transaction: { id: "tx", ops: [], timestamp: Date.now() },
243
+ version: 1,
244
+ timestamp: Date.now(),
245
+ })
246
+ );
247
+ }).pipe(Effect.provide(failingLayer))
248
+ );
249
+
250
+ expect(result._tag).toBe("Left");
251
+ if (result._tag === "Left") {
252
+ expect(result.left.cause).toBeInstanceOf(Error);
253
+ expect((result.left.cause as Error).message).toBe(
254
+ "Redis cluster unavailable"
255
+ );
256
+ }
257
+ });
258
+ });
259
+ });
package/tsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "extends": "./tsconfig.build.json",
3
- "include": ["src", "test"],
3
+ "include": ["src", "tests"],
4
4
  "compilerOptions": {
5
5
  "allowJs": false,
6
6
  "strictNullChecks": true
package/tsdown.config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineConfig } from "tsdown";
2
2
 
3
- export const input = ["./src/index.ts"];
3
+ export const input = ["./src/index.ts", "./src/testing/index.ts"];
4
4
 
5
5
  export default defineConfig({
6
6
  target: ["es2017"],