@voidhash/mimic-effect 1.0.0-beta.16 → 1.0.0-beta.18
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/dist/ColdStorage.cjs +1 -1
- package/dist/ColdStorage.d.cts +2 -2
- package/dist/ColdStorage.d.cts.map +1 -1
- package/dist/ColdStorage.d.mts +2 -2
- package/dist/ColdStorage.d.mts.map +1 -1
- package/dist/ColdStorage.mjs +2 -2
- package/dist/ColdStorage.mjs.map +1 -1
- package/dist/DocumentInstance.cjs +13 -13
- package/dist/DocumentInstance.mjs +13 -13
- package/dist/DocumentInstance.mjs.map +1 -1
- package/dist/Errors.d.cts +8 -8
- package/dist/Errors.d.cts.map +1 -1
- package/dist/Errors.d.mts +8 -8
- package/dist/Errors.d.mts.map +1 -1
- package/dist/HotStorage.cjs +1 -1
- package/dist/HotStorage.d.cts +2 -2
- package/dist/HotStorage.d.mts +2 -2
- package/dist/HotStorage.mjs +2 -2
- package/dist/HotStorage.mjs.map +1 -1
- package/dist/Metrics.cjs +6 -6
- package/dist/Metrics.d.cts +21 -23
- package/dist/Metrics.d.cts.map +1 -1
- package/dist/Metrics.d.mts +21 -23
- package/dist/Metrics.d.mts.map +1 -1
- package/dist/Metrics.mjs +7 -7
- package/dist/Metrics.mjs.map +1 -1
- package/dist/MimicAuthService.cjs +1 -1
- package/dist/MimicAuthService.d.cts +2 -2
- package/dist/MimicAuthService.d.cts.map +1 -1
- package/dist/MimicAuthService.d.mts +2 -2
- package/dist/MimicAuthService.d.mts.map +1 -1
- package/dist/MimicAuthService.mjs +2 -2
- package/dist/MimicAuthService.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +38 -41
- package/dist/MimicClusterServerEngine.d.cts +1 -1
- package/dist/MimicClusterServerEngine.d.mts +1 -1
- package/dist/MimicClusterServerEngine.mjs +31 -34
- package/dist/MimicClusterServerEngine.mjs.map +1 -1
- package/dist/MimicServer.cjs +23 -23
- package/dist/MimicServer.d.cts +3 -3
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts +3 -3
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +22 -22
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +13 -13
- package/dist/MimicServerEngine.d.cts +2 -2
- package/dist/MimicServerEngine.d.mts +2 -2
- package/dist/MimicServerEngine.mjs +14 -14
- package/dist/MimicServerEngine.mjs.map +1 -1
- package/dist/PresenceManager.cjs +4 -4
- package/dist/PresenceManager.d.cts +2 -2
- package/dist/PresenceManager.d.mts +2 -2
- package/dist/PresenceManager.mjs +5 -5
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/Types.d.cts +1 -1
- package/dist/Types.d.mts +1 -1
- package/dist/testing/ColdStorageTestSuite.cjs +3 -3
- package/dist/testing/ColdStorageTestSuite.mjs +3 -3
- package/dist/testing/ColdStorageTestSuite.mjs.map +1 -1
- package/dist/testing/HotStorageTestSuite.cjs +13 -13
- package/dist/testing/HotStorageTestSuite.mjs +13 -13
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -1
- package/dist/testing/StorageIntegrationTestSuite.cjs +3 -3
- package/dist/testing/StorageIntegrationTestSuite.mjs +3 -3
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -1
- package/dist/testing/types.d.cts +3 -3
- package/dist/testing/types.d.cts.map +1 -1
- package/dist/testing/types.d.mts +1 -1
- package/dist/testing/types.d.mts.map +1 -1
- package/package.json +17 -21
- package/src/ColdStorage.ts +4 -5
- package/src/DocumentInstance.ts +13 -13
- package/src/HotStorage.ts +3 -3
- package/src/Metrics.ts +22 -16
- package/src/MimicAuthService.ts +3 -3
- package/src/MimicClusterServerEngine.ts +35 -35
- package/src/MimicServer.ts +26 -30
- package/src/MimicServerEngine.ts +15 -15
- package/src/PresenceManager.ts +6 -6
- package/src/Types.ts +1 -1
- package/src/testing/ColdStorageTestSuite.ts +3 -3
- package/src/testing/HotStorageTestSuite.ts +17 -17
- package/src/testing/StorageIntegrationTestSuite.ts +3 -3
- package/.turbo/turbo-build.log +0 -154
- package/tests/ColdStorage.test.ts +0 -24
- package/tests/DocumentInstance.test.ts +0 -669
- package/tests/HotStorage.test.ts +0 -24
- package/tests/MimicAuthService.test.ts +0 -153
- package/tests/MimicClusterServerEngine.test.ts +0 -587
- package/tests/MimicServer.test.ts +0 -142
- package/tests/MimicServerEngine.test.ts +0 -547
- package/tests/PresenceManager.test.ts +0 -380
- package/tests/Protocol.test.ts +0 -190
- package/tests/StorageIntegration.test.ts +0 -259
- package/tsconfig.build.json +0 -24
- package/tsconfig.json +0 -8
- package/tsdown.config.ts +0 -18
- package/vitest.mts +0 -11
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { Effect, Stream, Chunk, Fiber } from "effect";
|
|
3
|
-
import { PresenceManager, PresenceManagerTag } from "../src/PresenceManager";
|
|
4
|
-
|
|
5
|
-
describe("PresenceManager", () => {
|
|
6
|
-
describe("getSnapshot", () => {
|
|
7
|
-
it("should return empty snapshot for unknown document", async () => {
|
|
8
|
-
const result = await Effect.runPromise(
|
|
9
|
-
Effect.gen(function* () {
|
|
10
|
-
const pm = yield* PresenceManagerTag;
|
|
11
|
-
return yield* pm.getSnapshot("unknown-doc");
|
|
12
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
expect(result.presences).toEqual({});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("should return presences after set", async () => {
|
|
19
|
-
const result = await Effect.runPromise(
|
|
20
|
-
Effect.gen(function* () {
|
|
21
|
-
const pm = yield* PresenceManagerTag;
|
|
22
|
-
|
|
23
|
-
yield* pm.set("doc-1", "conn-1", {
|
|
24
|
-
data: { x: 10, y: 20 },
|
|
25
|
-
userId: "user-1",
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return yield* pm.getSnapshot("doc-1");
|
|
29
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
expect(result.presences).toEqual({
|
|
33
|
-
"conn-1": { data: { x: 10, y: 20 }, userId: "user-1" },
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("should return multiple presences", async () => {
|
|
38
|
-
const result = await Effect.runPromise(
|
|
39
|
-
Effect.gen(function* () {
|
|
40
|
-
const pm = yield* PresenceManagerTag;
|
|
41
|
-
|
|
42
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
|
|
43
|
-
yield* pm.set("doc-1", "conn-2", {
|
|
44
|
-
data: { x: 30, y: 40 },
|
|
45
|
-
userId: "user-2",
|
|
46
|
-
});
|
|
47
|
-
yield* pm.set("doc-1", "conn-3", { data: { x: 50, y: 60 } });
|
|
48
|
-
|
|
49
|
-
return yield* pm.getSnapshot("doc-1");
|
|
50
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
expect(Object.keys(result.presences).length).toBe(3);
|
|
54
|
-
expect(result.presences["conn-1"]).toEqual({ data: { x: 10, y: 20 } });
|
|
55
|
-
expect(result.presences["conn-2"]).toEqual({
|
|
56
|
-
data: { x: 30, y: 40 },
|
|
57
|
-
userId: "user-2",
|
|
58
|
-
});
|
|
59
|
-
expect(result.presences["conn-3"]).toEqual({ data: { x: 50, y: 60 } });
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe("set", () => {
|
|
64
|
-
it("should store presence entry", async () => {
|
|
65
|
-
const result = await Effect.runPromise(
|
|
66
|
-
Effect.gen(function* () {
|
|
67
|
-
const pm = yield* PresenceManagerTag;
|
|
68
|
-
|
|
69
|
-
yield* pm.set("doc-1", "conn-1", {
|
|
70
|
-
data: { cursor: { x: 100, y: 200 } },
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return yield* pm.getSnapshot("doc-1");
|
|
74
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
expect(result.presences["conn-1"]).toEqual({
|
|
78
|
-
data: { cursor: { x: 100, y: 200 } },
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("should update existing presence entry", async () => {
|
|
83
|
-
const result = await Effect.runPromise(
|
|
84
|
-
Effect.gen(function* () {
|
|
85
|
-
const pm = yield* PresenceManagerTag;
|
|
86
|
-
|
|
87
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
|
|
88
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 100, y: 200 } });
|
|
89
|
-
|
|
90
|
-
return yield* pm.getSnapshot("doc-1");
|
|
91
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
expect(result.presences["conn-1"]).toEqual({ data: { x: 100, y: 200 } });
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("should broadcast presence_update event", async () => {
|
|
98
|
-
const result = await Effect.runPromise(
|
|
99
|
-
Effect.scoped(
|
|
100
|
-
Effect.gen(function* () {
|
|
101
|
-
const pm = yield* PresenceManagerTag;
|
|
102
|
-
|
|
103
|
-
// Subscribe first
|
|
104
|
-
const eventStream = yield* pm.subscribe("doc-1");
|
|
105
|
-
|
|
106
|
-
// Collect events in background
|
|
107
|
-
const eventsFiber = yield* Effect.fork(
|
|
108
|
-
Stream.runCollect(Stream.take(eventStream, 1))
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
// Small delay to ensure subscription is ready
|
|
112
|
-
yield* Effect.sleep("10 millis");
|
|
113
|
-
|
|
114
|
-
// Set presence
|
|
115
|
-
yield* pm.set("doc-1", "conn-1", {
|
|
116
|
-
data: { x: 10, y: 20 },
|
|
117
|
-
userId: "user-1",
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// Wait for events
|
|
121
|
-
const events = yield* Fiber.join(eventsFiber);
|
|
122
|
-
|
|
123
|
-
return Chunk.toArray(events);
|
|
124
|
-
})
|
|
125
|
-
).pipe(Effect.provide(PresenceManager.layer))
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
expect(result.length).toBe(1);
|
|
129
|
-
expect(result[0]!.type).toBe("presence_update");
|
|
130
|
-
if (result[0]!.type === "presence_update") {
|
|
131
|
-
expect(result[0]!.id).toBe("conn-1");
|
|
132
|
-
expect(result[0]!.data).toEqual({ x: 10, y: 20 });
|
|
133
|
-
expect(result[0]!.userId).toBe("user-1");
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe("remove", () => {
|
|
139
|
-
it("should remove presence entry", async () => {
|
|
140
|
-
const result = await Effect.runPromise(
|
|
141
|
-
Effect.gen(function* () {
|
|
142
|
-
const pm = yield* PresenceManagerTag;
|
|
143
|
-
|
|
144
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
|
|
145
|
-
yield* pm.remove("doc-1", "conn-1");
|
|
146
|
-
|
|
147
|
-
return yield* pm.getSnapshot("doc-1");
|
|
148
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
expect(result.presences).toEqual({});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it("should not error when removing non-existent connection", async () => {
|
|
155
|
-
await expect(
|
|
156
|
-
Effect.runPromise(
|
|
157
|
-
Effect.gen(function* () {
|
|
158
|
-
const pm = yield* PresenceManagerTag;
|
|
159
|
-
yield* pm.remove("doc-1", "non-existent-conn");
|
|
160
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
161
|
-
)
|
|
162
|
-
).resolves.toBeUndefined();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("should not error when removing from non-existent document", async () => {
|
|
166
|
-
await expect(
|
|
167
|
-
Effect.runPromise(
|
|
168
|
-
Effect.gen(function* () {
|
|
169
|
-
const pm = yield* PresenceManagerTag;
|
|
170
|
-
yield* pm.remove("non-existent-doc", "conn-1");
|
|
171
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
172
|
-
)
|
|
173
|
-
).resolves.toBeUndefined();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("should broadcast presence_remove event", async () => {
|
|
177
|
-
const result = await Effect.runPromise(
|
|
178
|
-
Effect.scoped(
|
|
179
|
-
Effect.gen(function* () {
|
|
180
|
-
const pm = yield* PresenceManagerTag;
|
|
181
|
-
|
|
182
|
-
// Set presence first
|
|
183
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10, y: 20 } });
|
|
184
|
-
|
|
185
|
-
// Subscribe
|
|
186
|
-
const eventStream = yield* pm.subscribe("doc-1");
|
|
187
|
-
|
|
188
|
-
// Collect events in background
|
|
189
|
-
const eventsFiber = yield* Effect.fork(
|
|
190
|
-
Stream.runCollect(Stream.take(eventStream, 1))
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// Small delay to ensure subscription is ready
|
|
194
|
-
yield* Effect.sleep("10 millis");
|
|
195
|
-
|
|
196
|
-
// Remove presence
|
|
197
|
-
yield* pm.remove("doc-1", "conn-1");
|
|
198
|
-
|
|
199
|
-
// Wait for events
|
|
200
|
-
const events = yield* Fiber.join(eventsFiber);
|
|
201
|
-
|
|
202
|
-
return Chunk.toArray(events);
|
|
203
|
-
})
|
|
204
|
-
).pipe(Effect.provide(PresenceManager.layer))
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
expect(result.length).toBe(1);
|
|
208
|
-
expect(result[0]!.type).toBe("presence_remove");
|
|
209
|
-
if (result[0]!.type === "presence_remove") {
|
|
210
|
-
expect(result[0]!.id).toBe("conn-1");
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it("should not broadcast event when removing non-existent presence", async () => {
|
|
215
|
-
const result = await Effect.runPromise(
|
|
216
|
-
Effect.scoped(
|
|
217
|
-
Effect.gen(function* () {
|
|
218
|
-
const pm = yield* PresenceManagerTag;
|
|
219
|
-
|
|
220
|
-
// Subscribe to doc that has no presences
|
|
221
|
-
const eventStream = yield* pm.subscribe("doc-1");
|
|
222
|
-
|
|
223
|
-
// Collect events in background with a timeout
|
|
224
|
-
const eventsFiber = yield* Effect.fork(
|
|
225
|
-
Stream.runCollect(
|
|
226
|
-
Stream.take(eventStream, 1).pipe(Stream.timeout("50 millis"))
|
|
227
|
-
)
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// Small delay to ensure subscription is ready
|
|
231
|
-
yield* Effect.sleep("10 millis");
|
|
232
|
-
|
|
233
|
-
// Remove non-existent presence
|
|
234
|
-
yield* pm.remove("doc-1", "non-existent-conn");
|
|
235
|
-
|
|
236
|
-
// Wait for events (should timeout with no events)
|
|
237
|
-
const events = yield* Fiber.join(eventsFiber);
|
|
238
|
-
|
|
239
|
-
return Chunk.toArray(events);
|
|
240
|
-
})
|
|
241
|
-
).pipe(Effect.provide(PresenceManager.layer))
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
expect(result.length).toBe(0);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe("subscribe", () => {
|
|
249
|
-
it("should receive update events from stream", async () => {
|
|
250
|
-
const result = await Effect.runPromise(
|
|
251
|
-
Effect.scoped(
|
|
252
|
-
Effect.gen(function* () {
|
|
253
|
-
const pm = yield* PresenceManagerTag;
|
|
254
|
-
|
|
255
|
-
const eventStream = yield* pm.subscribe("doc-1");
|
|
256
|
-
|
|
257
|
-
const eventsFiber = yield* Effect.fork(
|
|
258
|
-
Stream.runCollect(Stream.take(eventStream, 2))
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
yield* Effect.sleep("10 millis");
|
|
262
|
-
|
|
263
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10 } });
|
|
264
|
-
yield* pm.set("doc-1", "conn-2", { data: { x: 20 } });
|
|
265
|
-
|
|
266
|
-
const events = yield* Fiber.join(eventsFiber);
|
|
267
|
-
return Chunk.toArray(events);
|
|
268
|
-
})
|
|
269
|
-
).pipe(Effect.provide(PresenceManager.layer))
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
expect(result.length).toBe(2);
|
|
273
|
-
expect(result[0]!.type).toBe("presence_update");
|
|
274
|
-
expect(result[1]!.type).toBe("presence_update");
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("should receive mixed update and remove events", async () => {
|
|
278
|
-
const result = await Effect.runPromise(
|
|
279
|
-
Effect.scoped(
|
|
280
|
-
Effect.gen(function* () {
|
|
281
|
-
const pm = yield* PresenceManagerTag;
|
|
282
|
-
|
|
283
|
-
// Set up initial presence
|
|
284
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10 } });
|
|
285
|
-
|
|
286
|
-
const eventStream = yield* pm.subscribe("doc-1");
|
|
287
|
-
|
|
288
|
-
const eventsFiber = yield* Effect.fork(
|
|
289
|
-
Stream.runCollect(Stream.take(eventStream, 3))
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
yield* Effect.sleep("10 millis");
|
|
293
|
-
|
|
294
|
-
yield* pm.set("doc-1", "conn-2", { data: { x: 20 } });
|
|
295
|
-
yield* pm.remove("doc-1", "conn-1");
|
|
296
|
-
yield* pm.set("doc-1", "conn-3", { data: { x: 30 } });
|
|
297
|
-
|
|
298
|
-
const events = yield* Fiber.join(eventsFiber);
|
|
299
|
-
return Chunk.toArray(events);
|
|
300
|
-
})
|
|
301
|
-
).pipe(Effect.provide(PresenceManager.layer))
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
expect(result.length).toBe(3);
|
|
305
|
-
expect(result[0]!.type).toBe("presence_update");
|
|
306
|
-
expect(result[1]!.type).toBe("presence_remove");
|
|
307
|
-
expect(result[2]!.type).toBe("presence_update");
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
describe("document isolation", () => {
|
|
312
|
-
it("should isolate presences between documents", async () => {
|
|
313
|
-
const result = await Effect.runPromise(
|
|
314
|
-
Effect.gen(function* () {
|
|
315
|
-
const pm = yield* PresenceManagerTag;
|
|
316
|
-
|
|
317
|
-
yield* pm.set("doc-1", "conn-1", { data: { x: 10 } });
|
|
318
|
-
yield* pm.set("doc-2", "conn-2", { data: { x: 20 } });
|
|
319
|
-
|
|
320
|
-
const snapshot1 = yield* pm.getSnapshot("doc-1");
|
|
321
|
-
const snapshot2 = yield* pm.getSnapshot("doc-2");
|
|
322
|
-
|
|
323
|
-
return { snapshot1, snapshot2 };
|
|
324
|
-
}).pipe(Effect.provide(PresenceManager.layer))
|
|
325
|
-
);
|
|
326
|
-
|
|
327
|
-
expect(Object.keys(result.snapshot1.presences).length).toBe(1);
|
|
328
|
-
expect(result.snapshot1.presences["conn-1"]).toEqual({ data: { x: 10 } });
|
|
329
|
-
expect(result.snapshot1.presences["conn-2"]).toBeUndefined();
|
|
330
|
-
|
|
331
|
-
expect(Object.keys(result.snapshot2.presences).length).toBe(1);
|
|
332
|
-
expect(result.snapshot2.presences["conn-2"]).toEqual({ data: { x: 20 } });
|
|
333
|
-
expect(result.snapshot2.presences["conn-1"]).toBeUndefined();
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
it("should isolate events between documents", async () => {
|
|
337
|
-
const result = await Effect.runPromise(
|
|
338
|
-
Effect.scoped(
|
|
339
|
-
Effect.gen(function* () {
|
|
340
|
-
const pm = yield* PresenceManagerTag;
|
|
341
|
-
|
|
342
|
-
// Subscribe to doc-1 only
|
|
343
|
-
const eventStream = yield* pm.subscribe("doc-1");
|
|
344
|
-
|
|
345
|
-
const eventsFiber = yield* Effect.fork(
|
|
346
|
-
Stream.runCollect(
|
|
347
|
-
Stream.take(eventStream, 1).pipe(Stream.timeout("100 millis"))
|
|
348
|
-
)
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
yield* Effect.sleep("10 millis");
|
|
352
|
-
|
|
353
|
-
// Set presence on doc-2 (should NOT trigger doc-1 event)
|
|
354
|
-
yield* pm.set("doc-2", "conn-1", { data: { x: 10 } });
|
|
355
|
-
|
|
356
|
-
// Set presence on doc-1 (should trigger event)
|
|
357
|
-
yield* pm.set("doc-1", "conn-2", { data: { x: 20 } });
|
|
358
|
-
|
|
359
|
-
const events = yield* Fiber.join(eventsFiber);
|
|
360
|
-
return Chunk.toArray(events);
|
|
361
|
-
})
|
|
362
|
-
).pipe(Effect.provide(PresenceManager.layer))
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
expect(result.length).toBe(1);
|
|
366
|
-
if (result[0]!.type === "presence_update") {
|
|
367
|
-
expect(result[0]!.id).toBe("conn-2");
|
|
368
|
-
expect(result[0]!.data).toEqual({ x: 20 });
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
describe("Tag", () => {
|
|
374
|
-
it("should have correct identifier", () => {
|
|
375
|
-
expect(PresenceManagerTag.key).toBe(
|
|
376
|
-
"@voidhash/mimic-effect/PresenceManager"
|
|
377
|
-
);
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
});
|
package/tests/Protocol.test.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
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
|
-
});
|