@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.
- package/.turbo/turbo-build.log +136 -90
- package/README.md +385 -0
- package/dist/ColdStorage.cjs +60 -0
- package/dist/ColdStorage.d.cts +53 -0
- package/dist/ColdStorage.d.cts.map +1 -0
- package/dist/ColdStorage.d.mts +53 -0
- package/dist/ColdStorage.d.mts.map +1 -0
- package/dist/ColdStorage.mjs +60 -0
- package/dist/ColdStorage.mjs.map +1 -0
- package/dist/DocumentManager.cjs +263 -82
- package/dist/DocumentManager.d.cts +44 -22
- package/dist/DocumentManager.d.cts.map +1 -1
- package/dist/DocumentManager.d.mts +44 -22
- package/dist/DocumentManager.d.mts.map +1 -1
- package/dist/DocumentManager.mjs +259 -67
- package/dist/DocumentManager.mjs.map +1 -1
- package/dist/Errors.cjs +54 -0
- package/dist/Errors.d.cts +96 -0
- package/dist/Errors.d.cts.map +1 -0
- package/dist/Errors.d.mts +96 -0
- package/dist/Errors.d.mts.map +1 -0
- package/dist/Errors.mjs +48 -0
- package/dist/Errors.mjs.map +1 -0
- package/dist/HotStorage.cjs +100 -0
- package/dist/HotStorage.d.cts +70 -0
- package/dist/HotStorage.d.cts.map +1 -0
- package/dist/HotStorage.d.mts +70 -0
- package/dist/HotStorage.d.mts.map +1 -0
- package/dist/HotStorage.mjs +100 -0
- package/dist/HotStorage.mjs.map +1 -0
- package/dist/Metrics.cjs +143 -0
- package/dist/Metrics.d.cts +31 -0
- package/dist/Metrics.d.cts.map +1 -0
- package/dist/Metrics.d.mts +31 -0
- package/dist/Metrics.d.mts.map +1 -0
- package/dist/Metrics.mjs +126 -0
- package/dist/Metrics.mjs.map +1 -0
- package/dist/MimicAuthService.cjs +61 -45
- package/dist/MimicAuthService.d.cts +61 -48
- package/dist/MimicAuthService.d.cts.map +1 -1
- package/dist/MimicAuthService.d.mts +61 -48
- package/dist/MimicAuthService.d.mts.map +1 -1
- package/dist/MimicAuthService.mjs +60 -36
- package/dist/MimicAuthService.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +521 -0
- package/dist/MimicClusterServerEngine.d.cts +17 -0
- package/dist/MimicClusterServerEngine.d.cts.map +1 -0
- package/dist/MimicClusterServerEngine.d.mts +17 -0
- package/dist/MimicClusterServerEngine.d.mts.map +1 -0
- package/dist/MimicClusterServerEngine.mjs +523 -0
- package/dist/MimicClusterServerEngine.mjs.map +1 -0
- package/dist/MimicServer.cjs +205 -96
- package/dist/MimicServer.d.cts +9 -110
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts +9 -110
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +206 -90
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +97 -0
- package/dist/MimicServerEngine.d.cts +78 -0
- package/dist/MimicServerEngine.d.cts.map +1 -0
- package/dist/MimicServerEngine.d.mts +78 -0
- package/dist/MimicServerEngine.d.mts.map +1 -0
- package/dist/MimicServerEngine.mjs +97 -0
- package/dist/MimicServerEngine.mjs.map +1 -0
- package/dist/PresenceManager.cjs +75 -91
- package/dist/PresenceManager.d.cts +17 -66
- package/dist/PresenceManager.d.cts.map +1 -1
- package/dist/PresenceManager.d.mts +17 -66
- package/dist/PresenceManager.d.mts.map +1 -1
- package/dist/PresenceManager.mjs +74 -78
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/Protocol.cjs +146 -0
- package/dist/Protocol.d.cts +203 -0
- package/dist/Protocol.d.cts.map +1 -0
- package/dist/Protocol.d.mts +203 -0
- package/dist/Protocol.d.mts.map +1 -0
- package/dist/Protocol.mjs +132 -0
- package/dist/Protocol.mjs.map +1 -0
- package/dist/Types.d.cts +172 -0
- package/dist/Types.d.cts.map +1 -0
- package/dist/Types.d.mts +172 -0
- package/dist/Types.d.mts.map +1 -0
- package/dist/_virtual/rolldown_runtime.cjs +1 -25
- package/dist/_virtual/rolldown_runtime.mjs +4 -1
- package/dist/index.cjs +37 -75
- package/dist/index.d.cts +13 -12
- package/dist/index.d.mts +13 -12
- package/dist/index.mjs +12 -12
- package/dist/testing/ColdStorageTestSuite.cjs +508 -0
- package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
- package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
- package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
- package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
- package/dist/testing/ColdStorageTestSuite.mjs +508 -0
- package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
- package/dist/testing/FailingStorage.cjs +135 -0
- package/dist/testing/FailingStorage.d.cts +43 -0
- package/dist/testing/FailingStorage.d.cts.map +1 -0
- package/dist/testing/FailingStorage.d.mts +43 -0
- package/dist/testing/FailingStorage.d.mts.map +1 -0
- package/dist/testing/FailingStorage.mjs +136 -0
- package/dist/testing/FailingStorage.mjs.map +1 -0
- package/dist/testing/HotStorageTestSuite.cjs +585 -0
- package/dist/testing/HotStorageTestSuite.d.cts +40 -0
- package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
- package/dist/testing/HotStorageTestSuite.d.mts +40 -0
- package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
- package/dist/testing/HotStorageTestSuite.mjs +585 -0
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
- package/dist/testing/assertions.cjs +114 -0
- package/dist/testing/assertions.mjs +109 -0
- package/dist/testing/assertions.mjs.map +1 -0
- package/dist/testing/index.cjs +14 -0
- package/dist/testing/index.d.cts +6 -0
- package/dist/testing/index.d.mts +6 -0
- package/dist/testing/index.mjs +7 -0
- package/dist/testing/types.cjs +15 -0
- package/dist/testing/types.d.cts +90 -0
- package/dist/testing/types.d.cts.map +1 -0
- package/dist/testing/types.d.mts +90 -0
- package/dist/testing/types.d.mts.map +1 -0
- package/dist/testing/types.mjs +16 -0
- package/dist/testing/types.mjs.map +1 -0
- package/package.json +18 -3
- package/src/ColdStorage.ts +136 -0
- package/src/DocumentManager.ts +550 -190
- package/src/Errors.ts +114 -0
- package/src/HotStorage.ts +239 -0
- package/src/Metrics.ts +187 -0
- package/src/MimicAuthService.ts +126 -64
- package/src/MimicClusterServerEngine.ts +946 -0
- package/src/MimicServer.ts +448 -195
- package/src/MimicServerEngine.ts +276 -0
- package/src/PresenceManager.ts +169 -240
- package/src/Protocol.ts +350 -0
- package/src/Types.ts +231 -0
- package/src/index.ts +57 -23
- package/src/testing/ColdStorageTestSuite.ts +589 -0
- package/src/testing/FailingStorage.ts +286 -0
- package/src/testing/HotStorageTestSuite.ts +762 -0
- package/src/testing/StorageIntegrationTestSuite.ts +504 -0
- package/src/testing/assertions.ts +181 -0
- package/src/testing/index.ts +83 -0
- package/src/testing/types.ts +100 -0
- package/tests/ColdStorage.test.ts +24 -0
- package/tests/DocumentManager.test.ts +158 -287
- package/tests/HotStorage.test.ts +24 -0
- package/tests/MimicAuthService.test.ts +102 -134
- package/tests/MimicClusterServerEngine.test.ts +587 -0
- package/tests/MimicServer.test.ts +90 -226
- package/tests/MimicServerEngine.test.ts +521 -0
- package/tests/PresenceManager.test.ts +22 -63
- package/tests/Protocol.test.ts +190 -0
- package/tests/StorageIntegration.test.ts +259 -0
- package/tsconfig.json +1 -1
- package/tsdown.config.ts +1 -1
- package/dist/DocumentProtocol.cjs +0 -94
- package/dist/DocumentProtocol.d.cts +0 -113
- package/dist/DocumentProtocol.d.cts.map +0 -1
- package/dist/DocumentProtocol.d.mts +0 -113
- package/dist/DocumentProtocol.d.mts.map +0 -1
- package/dist/DocumentProtocol.mjs +0 -89
- package/dist/DocumentProtocol.mjs.map +0 -1
- package/dist/MimicConfig.cjs +0 -60
- package/dist/MimicConfig.d.cts +0 -141
- package/dist/MimicConfig.d.cts.map +0 -1
- package/dist/MimicConfig.d.mts +0 -141
- package/dist/MimicConfig.d.mts.map +0 -1
- package/dist/MimicConfig.mjs +0 -50
- package/dist/MimicConfig.mjs.map +0 -1
- package/dist/MimicDataStorage.cjs +0 -83
- package/dist/MimicDataStorage.d.cts +0 -113
- package/dist/MimicDataStorage.d.cts.map +0 -1
- package/dist/MimicDataStorage.d.mts +0 -113
- package/dist/MimicDataStorage.d.mts.map +0 -1
- package/dist/MimicDataStorage.mjs +0 -74
- package/dist/MimicDataStorage.mjs.map +0 -1
- package/dist/WebSocketHandler.cjs +0 -365
- package/dist/WebSocketHandler.d.cts +0 -34
- package/dist/WebSocketHandler.d.cts.map +0 -1
- package/dist/WebSocketHandler.d.mts +0 -34
- package/dist/WebSocketHandler.d.mts.map +0 -1
- package/dist/WebSocketHandler.mjs +0 -355
- package/dist/WebSocketHandler.mjs.map +0 -1
- package/dist/auth/NoAuth.cjs +0 -43
- package/dist/auth/NoAuth.d.cts +0 -22
- package/dist/auth/NoAuth.d.cts.map +0 -1
- package/dist/auth/NoAuth.d.mts +0 -22
- package/dist/auth/NoAuth.d.mts.map +0 -1
- package/dist/auth/NoAuth.mjs +0 -36
- package/dist/auth/NoAuth.mjs.map +0 -1
- package/dist/errors.cjs +0 -74
- package/dist/errors.d.cts +0 -89
- package/dist/errors.d.cts.map +0 -1
- package/dist/errors.d.mts +0 -89
- package/dist/errors.d.mts.map +0 -1
- package/dist/errors.mjs +0 -67
- package/dist/errors.mjs.map +0 -1
- package/dist/storage/InMemoryDataStorage.cjs +0 -57
- package/dist/storage/InMemoryDataStorage.d.cts +0 -19
- package/dist/storage/InMemoryDataStorage.d.cts.map +0 -1
- package/dist/storage/InMemoryDataStorage.d.mts +0 -19
- package/dist/storage/InMemoryDataStorage.d.mts.map +0 -1
- package/dist/storage/InMemoryDataStorage.mjs +0 -48
- package/dist/storage/InMemoryDataStorage.mjs.map +0 -1
- package/src/DocumentProtocol.ts +0 -112
- package/src/MimicConfig.ts +0 -211
- package/src/MimicDataStorage.ts +0 -157
- package/src/WebSocketHandler.ts +0 -735
- package/src/auth/NoAuth.ts +0 -46
- package/src/errors.ts +0 -113
- package/src/storage/InMemoryDataStorage.ts +0 -66
- package/tests/DocumentProtocol.test.ts +0 -113
- package/tests/InMemoryDataStorage.test.ts +0 -190
- package/tests/MimicConfig.test.ts +0 -290
- package/tests/MimicDataStorage.test.ts +0 -190
- package/tests/NoAuth.test.ts +0 -94
- package/tests/WebSocketHandler.test.ts +0 -321
- package/tests/errors.test.ts +0 -77
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
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*
|
|
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
|
|
18
|
+
it("should return presences after set", async () => {
|
|
26
19
|
const result = await Effect.runPromise(
|
|
27
20
|
Effect.gen(function* () {
|
|
28
|
-
const pm = yield*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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*
|
|
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("
|
|
381
|
-
it("should
|
|
382
|
-
|
|
383
|
-
|
|
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