@voidhash/mimic-effect 0.0.1-alpha.1

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.
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as Effect from "effect/Effect";
3
+ import * as MimicDataStorage from "../src/MimicDataStorage";
4
+
5
+ // =============================================================================
6
+ // Error Tests
7
+ // =============================================================================
8
+
9
+ describe("MimicDataStorage", () => {
10
+ describe("StorageLoadError", () => {
11
+ it("should have correct message", () => {
12
+ const error = new MimicDataStorage.StorageLoadError({
13
+ documentId: "doc-123",
14
+ cause: new Error("Connection failed"),
15
+ });
16
+ expect(error.message).toBe(
17
+ "Failed to load document doc-123: Error: Connection failed"
18
+ );
19
+ expect(error._tag).toBe("StorageLoadError");
20
+ expect(error.documentId).toBe("doc-123");
21
+ });
22
+ });
23
+
24
+ describe("StorageSaveError", () => {
25
+ it("should have correct message", () => {
26
+ const error = new MimicDataStorage.StorageSaveError({
27
+ documentId: "doc-456",
28
+ cause: new Error("Disk full"),
29
+ });
30
+ expect(error.message).toBe(
31
+ "Failed to save document doc-456: Error: Disk full"
32
+ );
33
+ expect(error._tag).toBe("StorageSaveError");
34
+ expect(error.documentId).toBe("doc-456");
35
+ });
36
+ });
37
+
38
+ describe("StorageDeleteError", () => {
39
+ it("should have correct message", () => {
40
+ const error = new MimicDataStorage.StorageDeleteError({
41
+ documentId: "doc-789",
42
+ cause: new Error("Permission denied"),
43
+ });
44
+ expect(error.message).toBe(
45
+ "Failed to delete document doc-789: Error: Permission denied"
46
+ );
47
+ expect(error._tag).toBe("StorageDeleteError");
48
+ expect(error.documentId).toBe("doc-789");
49
+ });
50
+ });
51
+
52
+ describe("make", () => {
53
+ it("should create storage with required functions", async () => {
54
+ const storage = MimicDataStorage.make({
55
+ load: (documentId) => Effect.succeed({ id: documentId, data: "test" }),
56
+ save: (_documentId, _state) => Effect.void,
57
+ });
58
+
59
+ const loaded = await Effect.runPromise(storage.load("doc-1"));
60
+ expect(loaded).toEqual({ id: "doc-1", data: "test" });
61
+
62
+ await Effect.runPromise(storage.save("doc-1", { data: "new" }));
63
+ });
64
+
65
+ it("should provide default delete implementation", async () => {
66
+ const storage = MimicDataStorage.make({
67
+ load: (_documentId) => Effect.succeed(undefined),
68
+ save: (_documentId, _state) => Effect.void,
69
+ });
70
+
71
+ // Default delete should be a no-op
72
+ await Effect.runPromise(storage.delete("doc-1"));
73
+ });
74
+
75
+ it("should provide default onLoad implementation (pass-through)", async () => {
76
+ const storage = MimicDataStorage.make({
77
+ load: (_documentId) => Effect.succeed(undefined),
78
+ save: (_documentId, _state) => Effect.void,
79
+ });
80
+
81
+ const state = { title: "Test" };
82
+ const result = await Effect.runPromise(storage.onLoad(state));
83
+ expect(result).toBe(state);
84
+ });
85
+
86
+ it("should provide default onSave implementation (pass-through)", async () => {
87
+ const storage = MimicDataStorage.make({
88
+ load: (_documentId) => Effect.succeed(undefined),
89
+ save: (_documentId, _state) => Effect.void,
90
+ });
91
+
92
+ const state = { title: "Test" };
93
+ const result = await Effect.runPromise(storage.onSave(state));
94
+ expect(result).toBe(state);
95
+ });
96
+
97
+ it("should accept custom delete implementation", async () => {
98
+ let deletedId: string | null = null;
99
+ const storage = MimicDataStorage.make({
100
+ load: (_documentId) => Effect.succeed(undefined),
101
+ save: (_documentId, _state) => Effect.void,
102
+ delete: (documentId) => {
103
+ deletedId = documentId;
104
+ return Effect.void;
105
+ },
106
+ });
107
+
108
+ await Effect.runPromise(storage.delete("doc-to-delete"));
109
+ expect(deletedId).toBe("doc-to-delete");
110
+ });
111
+
112
+ it("should accept custom onLoad implementation", async () => {
113
+ const storage = MimicDataStorage.make({
114
+ load: (_documentId) => Effect.succeed(undefined),
115
+ save: (_documentId, _state) => Effect.void,
116
+ onLoad: (state) =>
117
+ Effect.succeed({ ...(state as object), loaded: true }),
118
+ });
119
+
120
+ const result = await Effect.runPromise(
121
+ storage.onLoad({ title: "Test" })
122
+ );
123
+ expect(result).toEqual({ title: "Test", loaded: true });
124
+ });
125
+
126
+ it("should accept custom onSave implementation", async () => {
127
+ const storage = MimicDataStorage.make({
128
+ load: (_documentId) => Effect.succeed(undefined),
129
+ save: (_documentId, _state) => Effect.void,
130
+ onSave: (state) =>
131
+ Effect.succeed({ ...(state as object), savedAt: "now" }),
132
+ });
133
+
134
+ const result = await Effect.runPromise(
135
+ storage.onSave({ title: "Test" })
136
+ );
137
+ expect(result).toEqual({ title: "Test", savedAt: "now" });
138
+ });
139
+ });
140
+
141
+ describe("layer", () => {
142
+ it("should create a layer that provides MimicDataStorageTag", async () => {
143
+ const testStorage = MimicDataStorage.make({
144
+ load: (_documentId) => Effect.succeed({ test: true }),
145
+ save: (_documentId, _state) => Effect.void,
146
+ });
147
+
148
+ const testLayer = MimicDataStorage.layer(testStorage);
149
+
150
+ const result = await Effect.runPromise(
151
+ Effect.gen(function* () {
152
+ const storage = yield* MimicDataStorage.MimicDataStorageTag;
153
+ return yield* storage.load("doc-1");
154
+ }).pipe(Effect.provide(testLayer))
155
+ );
156
+
157
+ expect(result).toEqual({ test: true });
158
+ });
159
+ });
160
+
161
+ describe("layerEffect", () => {
162
+ it("should create a layer from an Effect", async () => {
163
+ const testLayer = MimicDataStorage.layerEffect(
164
+ Effect.succeed(
165
+ MimicDataStorage.make({
166
+ load: (_documentId) => Effect.succeed({ fromEffect: true }),
167
+ save: (_documentId, _state) => Effect.void,
168
+ })
169
+ )
170
+ );
171
+
172
+ const result = await Effect.runPromise(
173
+ Effect.gen(function* () {
174
+ const storage = yield* MimicDataStorage.MimicDataStorageTag;
175
+ return yield* storage.load("doc-1");
176
+ }).pipe(Effect.provide(testLayer))
177
+ );
178
+
179
+ expect(result).toEqual({ fromEffect: true });
180
+ });
181
+ });
182
+
183
+ describe("MimicDataStorageTag", () => {
184
+ it("should have the correct tag identifier", () => {
185
+ expect(MimicDataStorage.MimicDataStorageTag.key).toBe(
186
+ "@voidhash/mimic-server-effect/MimicDataStorage"
187
+ );
188
+ });
189
+ });
190
+ });
@@ -0,0 +1,385 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Schema from "effect/Schema";
5
+ import { Primitive, Presence } from "@voidhash/mimic";
6
+ import * as MimicServer from "../src/MimicServer";
7
+ import * as MimicAuthService from "../src/MimicAuthService";
8
+ import * as InMemoryDataStorage from "../src/storage/InMemoryDataStorage";
9
+
10
+ // =============================================================================
11
+ // Test Schema
12
+ // =============================================================================
13
+
14
+ const TestSchema = Primitive.Struct({
15
+ title: Primitive.String().default(""),
16
+ completed: Primitive.Boolean().default(false),
17
+ });
18
+
19
+ // =============================================================================
20
+ // MimicServer Tests
21
+ // =============================================================================
22
+
23
+ describe("MimicServer", () => {
24
+ describe("layer", () => {
25
+ it("should create a layer with default auth and storage", async () => {
26
+ const testLayer = MimicServer.layer({
27
+ basePath: "/mimic/test",
28
+ schema: TestSchema,
29
+ });
30
+
31
+ // Verify the layer provides the expected services
32
+ const result = await Effect.runPromise(
33
+ Effect.gen(function* () {
34
+ const handler = yield* MimicServer.MimicWebSocketHandler;
35
+ return typeof handler === "function";
36
+ }).pipe(Effect.provide(testLayer))
37
+ );
38
+
39
+ expect(result).toBe(true);
40
+ });
41
+
42
+ it("should allow custom auth layer to be provided", async () => {
43
+ let authCalled = false;
44
+
45
+ const customAuthLayer = MimicAuthService.layer({
46
+ authHandler: (token) => {
47
+ authCalled = true;
48
+ return { success: true, userId: token };
49
+ },
50
+ });
51
+
52
+ const testLayer = MimicServer.layer({
53
+ basePath: "/mimic/test",
54
+ schema: TestSchema,
55
+ }).pipe(Layer.provideMerge(customAuthLayer));
56
+
57
+ // Verify the layer compiles with custom auth
58
+ const result = await Effect.runPromise(
59
+ Effect.gen(function* () {
60
+ const handler = yield* MimicServer.MimicWebSocketHandler;
61
+ return typeof handler === "function";
62
+ }).pipe(Effect.provide(testLayer))
63
+ );
64
+
65
+ expect(result).toBe(true);
66
+ });
67
+
68
+ it("should support maxTransactionHistory option", async () => {
69
+ const testLayer = MimicServer.layer({
70
+ basePath: "/mimic/test",
71
+ schema: TestSchema,
72
+ maxTransactionHistory: 500,
73
+ });
74
+
75
+ const result = await Effect.runPromise(
76
+ Effect.gen(function* () {
77
+ const handler = yield* MimicServer.MimicWebSocketHandler;
78
+ return typeof handler === "function";
79
+ }).pipe(Effect.provide(testLayer))
80
+ );
81
+
82
+ expect(result).toBe(true);
83
+ });
84
+ });
85
+
86
+ describe("handlerLayer", () => {
87
+ it("should create a layer that provides MimicWebSocketHandler", async () => {
88
+ const testLayer = MimicServer.handlerLayer({
89
+ schema: TestSchema,
90
+ });
91
+
92
+ const result = await Effect.runPromise(
93
+ Effect.gen(function* () {
94
+ const handler = yield* MimicServer.MimicWebSocketHandler;
95
+ return typeof handler === "function";
96
+ }).pipe(Effect.provide(testLayer))
97
+ );
98
+
99
+ expect(result).toBe(true);
100
+ });
101
+ });
102
+
103
+ describe("documentManagerLayer", () => {
104
+ it("should create a layer that provides DocumentManager", async () => {
105
+ const testLayer = MimicServer.documentManagerLayer({
106
+ schema: TestSchema,
107
+ });
108
+
109
+ // Just verify the layer compiles and provides the service
110
+ const result = await Effect.runPromise(
111
+ Effect.gen(function* () {
112
+ // DocumentManager is provided by the layer
113
+ return true;
114
+ }).pipe(Effect.provide(testLayer))
115
+ );
116
+
117
+ expect(result).toBe(true);
118
+ });
119
+ });
120
+
121
+ describe("layerHttpLayerRouter", () => {
122
+ it("should create a layer with default auth and storage", () => {
123
+ // Verify the function returns a layer without throwing
124
+ const routeLayer = MimicServer.layerHttpLayerRouter({
125
+ basePath: "/mimic/test",
126
+ schema: TestSchema,
127
+ });
128
+
129
+ expect(routeLayer).toBeDefined();
130
+ });
131
+
132
+ it("should accept custom authLayer option", () => {
133
+ const customAuthLayer = MimicAuthService.layer({
134
+ authHandler: (token) => ({ success: true, userId: token }),
135
+ });
136
+
137
+ const routeLayer = MimicServer.layerHttpLayerRouter({
138
+ basePath: "/mimic/test",
139
+ schema: TestSchema,
140
+ authLayer: customAuthLayer,
141
+ });
142
+
143
+ expect(routeLayer).toBeDefined();
144
+ });
145
+
146
+ it("should accept custom storageLayer option", () => {
147
+ const routeLayer = MimicServer.layerHttpLayerRouter({
148
+ basePath: "/mimic/test",
149
+ schema: TestSchema,
150
+ storageLayer: InMemoryDataStorage.layer,
151
+ });
152
+
153
+ expect(routeLayer).toBeDefined();
154
+ });
155
+
156
+ it("should accept both custom authLayer and storageLayer", () => {
157
+ const customAuthLayer = MimicAuthService.layer({
158
+ authHandler: (token) => ({ success: true, userId: token }),
159
+ });
160
+
161
+ const routeLayer = MimicServer.layerHttpLayerRouter({
162
+ basePath: "/mimic/test",
163
+ schema: TestSchema,
164
+ authLayer: customAuthLayer,
165
+ storageLayer: InMemoryDataStorage.layer,
166
+ });
167
+
168
+ expect(routeLayer).toBeDefined();
169
+ });
170
+
171
+ it("should use default basePath when not provided", () => {
172
+ const routeLayer = MimicServer.layerHttpLayerRouter({
173
+ schema: TestSchema,
174
+ });
175
+
176
+ expect(routeLayer).toBeDefined();
177
+ });
178
+
179
+ it("should support maxTransactionHistory option", () => {
180
+ const routeLayer = MimicServer.layerHttpLayerRouter({
181
+ basePath: "/mimic/test",
182
+ schema: TestSchema,
183
+ maxTransactionHistory: 500,
184
+ });
185
+
186
+ expect(routeLayer).toBeDefined();
187
+ });
188
+ });
189
+
190
+ describe("MimicWebSocketHandler tag", () => {
191
+ it("should have the correct tag identifier", () => {
192
+ expect(MimicServer.MimicWebSocketHandler.key).toBe(
193
+ "@voidhash/mimic-server-effect/MimicWebSocketHandler"
194
+ );
195
+ });
196
+ });
197
+
198
+ describe("MimicLayerOptions", () => {
199
+ it("should accept all optional properties", () => {
200
+ // TypeScript compile-time check - if this compiles, the interface is correct
201
+ const options: MimicServer.MimicLayerOptions<typeof TestSchema> = {
202
+ schema: TestSchema,
203
+ basePath: "/custom/path",
204
+ maxTransactionHistory: 1000,
205
+ };
206
+
207
+ expect(options.schema).toBe(TestSchema);
208
+ expect(options.basePath).toBe("/custom/path");
209
+ expect(options.maxTransactionHistory).toBe(1000);
210
+ });
211
+
212
+ it("should work with only required properties", () => {
213
+ const options: MimicServer.MimicLayerOptions<typeof TestSchema> = {
214
+ schema: TestSchema,
215
+ };
216
+
217
+ expect(options.schema).toBe(TestSchema);
218
+ expect(options.basePath).toBeUndefined();
219
+ expect(options.maxTransactionHistory).toBeUndefined();
220
+ });
221
+ });
222
+
223
+ describe("presence support", () => {
224
+ const CursorPresence = Presence.make({
225
+ schema: Schema.Struct({
226
+ x: Schema.Number,
227
+ y: Schema.Number,
228
+ name: Schema.optional(Schema.String),
229
+ }),
230
+ });
231
+
232
+ describe("layer", () => {
233
+ it("should accept presence option", async () => {
234
+ const testLayer = MimicServer.layer({
235
+ basePath: "/mimic/test",
236
+ schema: TestSchema,
237
+ presence: CursorPresence,
238
+ });
239
+
240
+ const result = await Effect.runPromise(
241
+ Effect.gen(function* () {
242
+ const handler = yield* MimicServer.MimicWebSocketHandler;
243
+ return typeof handler === "function";
244
+ }).pipe(Effect.provide(testLayer))
245
+ );
246
+
247
+ expect(result).toBe(true);
248
+ });
249
+
250
+ it("should work with presence and custom auth", async () => {
251
+ const customAuthLayer = MimicAuthService.layer({
252
+ authHandler: (token) => ({ success: true, userId: token }),
253
+ });
254
+
255
+ const testLayer = MimicServer.layer({
256
+ basePath: "/mimic/test",
257
+ schema: TestSchema,
258
+ presence: CursorPresence,
259
+ }).pipe(Layer.provideMerge(customAuthLayer));
260
+
261
+ const result = await Effect.runPromise(
262
+ Effect.gen(function* () {
263
+ const handler = yield* MimicServer.MimicWebSocketHandler;
264
+ return typeof handler === "function";
265
+ }).pipe(Effect.provide(testLayer))
266
+ );
267
+
268
+ expect(result).toBe(true);
269
+ });
270
+
271
+ it("should work with presence and maxTransactionHistory", async () => {
272
+ const testLayer = MimicServer.layer({
273
+ basePath: "/mimic/test",
274
+ schema: TestSchema,
275
+ presence: CursorPresence,
276
+ maxTransactionHistory: 500,
277
+ });
278
+
279
+ const result = await Effect.runPromise(
280
+ Effect.gen(function* () {
281
+ const handler = yield* MimicServer.MimicWebSocketHandler;
282
+ return typeof handler === "function";
283
+ }).pipe(Effect.provide(testLayer))
284
+ );
285
+
286
+ expect(result).toBe(true);
287
+ });
288
+ });
289
+
290
+ describe("handlerLayer", () => {
291
+ it("should accept presence option", async () => {
292
+ const testLayer = MimicServer.handlerLayer({
293
+ schema: TestSchema,
294
+ presence: CursorPresence,
295
+ });
296
+
297
+ const result = await Effect.runPromise(
298
+ Effect.gen(function* () {
299
+ const handler = yield* MimicServer.MimicWebSocketHandler;
300
+ return typeof handler === "function";
301
+ }).pipe(Effect.provide(testLayer))
302
+ );
303
+
304
+ expect(result).toBe(true);
305
+ });
306
+ });
307
+
308
+ describe("documentManagerLayer", () => {
309
+ it("should accept presence option", async () => {
310
+ const testLayer = MimicServer.documentManagerLayer({
311
+ schema: TestSchema,
312
+ presence: CursorPresence,
313
+ });
314
+
315
+ const result = await Effect.runPromise(
316
+ Effect.gen(function* () {
317
+ return true;
318
+ }).pipe(Effect.provide(testLayer))
319
+ );
320
+
321
+ expect(result).toBe(true);
322
+ });
323
+ });
324
+
325
+ describe("layerHttpLayerRouter", () => {
326
+ it("should accept presence option", () => {
327
+ const routeLayer = MimicServer.layerHttpLayerRouter({
328
+ basePath: "/mimic/test",
329
+ schema: TestSchema,
330
+ presence: CursorPresence,
331
+ });
332
+
333
+ expect(routeLayer).toBeDefined();
334
+ });
335
+
336
+ it("should work with presence and custom authLayer", () => {
337
+ const customAuthLayer = MimicAuthService.layer({
338
+ authHandler: (token) => ({ success: true, userId: token }),
339
+ });
340
+
341
+ const routeLayer = MimicServer.layerHttpLayerRouter({
342
+ basePath: "/mimic/test",
343
+ schema: TestSchema,
344
+ presence: CursorPresence,
345
+ authLayer: customAuthLayer,
346
+ });
347
+
348
+ expect(routeLayer).toBeDefined();
349
+ });
350
+
351
+ it("should work with presence and all options", () => {
352
+ const customAuthLayer = MimicAuthService.layer({
353
+ authHandler: (token) => ({ success: true, userId: token }),
354
+ });
355
+
356
+ const routeLayer = MimicServer.layerHttpLayerRouter({
357
+ basePath: "/mimic/test",
358
+ schema: TestSchema,
359
+ presence: CursorPresence,
360
+ maxTransactionHistory: 500,
361
+ authLayer: customAuthLayer,
362
+ storageLayer: InMemoryDataStorage.layer,
363
+ });
364
+
365
+ expect(routeLayer).toBeDefined();
366
+ });
367
+ });
368
+
369
+ describe("MimicLayerOptions with presence", () => {
370
+ it("should accept presence in options", () => {
371
+ const options: MimicServer.MimicLayerOptions<typeof TestSchema> = {
372
+ schema: TestSchema,
373
+ basePath: "/custom/path",
374
+ maxTransactionHistory: 1000,
375
+ presence: CursorPresence,
376
+ };
377
+
378
+ expect(options.schema).toBe(TestSchema);
379
+ expect(options.basePath).toBe("/custom/path");
380
+ expect(options.maxTransactionHistory).toBe(1000);
381
+ expect(options.presence).toBe(CursorPresence);
382
+ });
383
+ });
384
+ });
385
+ });
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as Effect from "effect/Effect";
3
+ import * as NoAuth from "../src/auth/NoAuth";
4
+ import { MimicAuthServiceTag } from "../src/MimicAuthService";
5
+
6
+ // =============================================================================
7
+ // NoAuth Tests
8
+ // =============================================================================
9
+
10
+ describe("NoAuth", () => {
11
+ describe("authenticate", () => {
12
+ it("should always return success: true", async () => {
13
+ const result = await Effect.runPromise(
14
+ Effect.gen(function* () {
15
+ const authService = yield* MimicAuthServiceTag;
16
+ return yield* authService.authenticate("any-token");
17
+ }).pipe(Effect.provide(NoAuth.layer))
18
+ );
19
+
20
+ expect(result).toEqual({ success: true });
21
+ });
22
+
23
+ it("should succeed with empty token", async () => {
24
+ const result = await Effect.runPromise(
25
+ Effect.gen(function* () {
26
+ const authService = yield* MimicAuthServiceTag;
27
+ return yield* authService.authenticate("");
28
+ }).pipe(Effect.provide(NoAuth.layer))
29
+ );
30
+
31
+ expect(result.success).toBe(true);
32
+ });
33
+
34
+ it("should succeed with any arbitrary token", async () => {
35
+ const tokens = [
36
+ "valid-token-123",
37
+ "invalid-token",
38
+ "abc123xyz",
39
+ "special!@#$%^&*()",
40
+ "very-long-token-" + "x".repeat(1000),
41
+ ];
42
+
43
+ for (const token of tokens) {
44
+ const result = await Effect.runPromise(
45
+ Effect.gen(function* () {
46
+ const authService = yield* MimicAuthServiceTag;
47
+ return yield* authService.authenticate(token);
48
+ }).pipe(Effect.provide(NoAuth.layer))
49
+ );
50
+
51
+ expect(result.success).toBe(true);
52
+ }
53
+ });
54
+
55
+ it("should not include userId in result", async () => {
56
+ const result = await Effect.runPromise(
57
+ Effect.gen(function* () {
58
+ const authService = yield* MimicAuthServiceTag;
59
+ return yield* authService.authenticate("test-token");
60
+ }).pipe(Effect.provide(NoAuth.layer))
61
+ );
62
+
63
+ expect(result.success).toBe(true);
64
+ if (result.success) {
65
+ expect(result.userId).toBeUndefined();
66
+ }
67
+ });
68
+ });
69
+
70
+ describe("layer aliases", () => {
71
+ it("should have layerDefault as an alias for layer", () => {
72
+ expect(NoAuth.layerDefault).toBe(NoAuth.layer);
73
+ });
74
+ });
75
+
76
+ describe("multiple authentications", () => {
77
+ it("should handle multiple sequential authentications", async () => {
78
+ const results = await Effect.runPromise(
79
+ Effect.gen(function* () {
80
+ const authService = yield* MimicAuthServiceTag;
81
+ const result1 = yield* authService.authenticate("token-1");
82
+ const result2 = yield* authService.authenticate("token-2");
83
+ const result3 = yield* authService.authenticate("token-3");
84
+ return [result1, result2, result3];
85
+ }).pipe(Effect.provide(NoAuth.layer))
86
+ );
87
+
88
+ expect(results).toHaveLength(3);
89
+ for (const result of results) {
90
+ expect(result.success).toBe(true);
91
+ }
92
+ });
93
+ });
94
+ });