@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,12 +1,14 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
2
|
+
import { Effect, Layer, Stream, Fiber, Duration } from "effect";
|
|
3
|
+
import { Primitive, Document, Transaction } from "@voidhash/mimic";
|
|
4
|
+
import {
|
|
5
|
+
DocumentManager,
|
|
6
|
+
DocumentManagerTag,
|
|
7
|
+
DocumentManagerConfigTag,
|
|
8
|
+
} from "../src/DocumentManager";
|
|
9
|
+
import { ColdStorage } from "../src/ColdStorage";
|
|
10
|
+
import { HotStorage } from "../src/HotStorage";
|
|
11
|
+
import type { ResolvedConfig } from "../src/Types";
|
|
10
12
|
|
|
11
13
|
// =============================================================================
|
|
12
14
|
// Test Schema
|
|
@@ -21,15 +23,30 @@ const TestSchema = Primitive.Struct({
|
|
|
21
23
|
// Test Layer
|
|
22
24
|
// =============================================================================
|
|
23
25
|
|
|
24
|
-
const makeTestLayer = (
|
|
25
|
-
|
|
26
|
+
const makeTestLayer = (options?: {
|
|
27
|
+
initial?: { title?: string; count?: number };
|
|
28
|
+
}) => {
|
|
29
|
+
const config: ResolvedConfig<typeof TestSchema> = {
|
|
26
30
|
schema: TestSchema,
|
|
31
|
+
initial: options?.initial,
|
|
32
|
+
presence: undefined,
|
|
33
|
+
maxIdleTime: Duration.minutes(5),
|
|
27
34
|
maxTransactionHistory: 100,
|
|
28
|
-
|
|
35
|
+
snapshot: {
|
|
36
|
+
interval: Duration.minutes(5),
|
|
37
|
+
transactionThreshold: 100,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const configLayer = Layer.succeed(
|
|
42
|
+
DocumentManagerConfigTag,
|
|
43
|
+
config as ResolvedConfig<Primitive.AnyPrimitive>
|
|
44
|
+
);
|
|
29
45
|
|
|
30
46
|
return DocumentManager.layer.pipe(
|
|
31
47
|
Layer.provide(configLayer),
|
|
32
|
-
Layer.provide(
|
|
48
|
+
Layer.provide(ColdStorage.InMemory.make()),
|
|
49
|
+
Layer.provide(HotStorage.InMemory.make())
|
|
33
50
|
);
|
|
34
51
|
};
|
|
35
52
|
|
|
@@ -37,20 +54,16 @@ const makeTestLayer = () => {
|
|
|
37
54
|
// Helper Functions
|
|
38
55
|
// =============================================================================
|
|
39
56
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
57
|
+
const createValidTransaction = (
|
|
58
|
+
id: string,
|
|
59
|
+
title: string
|
|
60
|
+
): Transaction.Transaction => {
|
|
44
61
|
const doc = Document.make(TestSchema);
|
|
45
62
|
doc.transaction((root) => {
|
|
46
63
|
root.title.set(title);
|
|
47
64
|
});
|
|
48
65
|
const tx = doc.flush();
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
...tx,
|
|
52
|
-
id,
|
|
53
|
-
};
|
|
66
|
+
return { ...tx, id };
|
|
54
67
|
};
|
|
55
68
|
|
|
56
69
|
const createEmptyTransaction = (id: string): Transaction.Transaction => ({
|
|
@@ -67,11 +80,13 @@ describe("DocumentManager", () => {
|
|
|
67
80
|
describe("submit", () => {
|
|
68
81
|
it("should accept valid transactions", async () => {
|
|
69
82
|
const result = await Effect.runPromise(
|
|
70
|
-
Effect.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
Effect.scoped(
|
|
84
|
+
Effect.gen(function* () {
|
|
85
|
+
const manager = yield* DocumentManagerTag;
|
|
86
|
+
const tx = createValidTransaction("tx-1", "Hello World");
|
|
87
|
+
return yield* manager.submit("doc-1", tx);
|
|
88
|
+
})
|
|
89
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
75
90
|
);
|
|
76
91
|
|
|
77
92
|
expect(result.success).toBe(true);
|
|
@@ -82,11 +97,13 @@ describe("DocumentManager", () => {
|
|
|
82
97
|
|
|
83
98
|
it("should reject empty transactions", async () => {
|
|
84
99
|
const result = await Effect.runPromise(
|
|
85
|
-
Effect.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
Effect.scoped(
|
|
101
|
+
Effect.gen(function* () {
|
|
102
|
+
const manager = yield* DocumentManagerTag;
|
|
103
|
+
const tx = createEmptyTransaction("tx-empty");
|
|
104
|
+
return yield* manager.submit("doc-1", tx);
|
|
105
|
+
})
|
|
106
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
90
107
|
);
|
|
91
108
|
|
|
92
109
|
expect(result.success).toBe(false);
|
|
@@ -97,18 +114,17 @@ describe("DocumentManager", () => {
|
|
|
97
114
|
|
|
98
115
|
it("should reject duplicate transactions", async () => {
|
|
99
116
|
const result = await Effect.runPromise(
|
|
100
|
-
Effect.
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
Effect.scoped(
|
|
118
|
+
Effect.gen(function* () {
|
|
119
|
+
const manager = yield* DocumentManagerTag;
|
|
120
|
+
const tx = createValidTransaction("tx-dup", "First");
|
|
103
121
|
|
|
104
|
-
|
|
105
|
-
|
|
122
|
+
const first = yield* manager.submit("doc-1", tx);
|
|
123
|
+
const second = yield* manager.submit("doc-1", tx);
|
|
106
124
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return { first, second };
|
|
111
|
-
}).pipe(Effect.provide(makeTestLayer()))
|
|
125
|
+
return { first, second };
|
|
126
|
+
})
|
|
127
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
112
128
|
);
|
|
113
129
|
|
|
114
130
|
expect(result.first.success).toBe(true);
|
|
@@ -122,19 +138,21 @@ describe("DocumentManager", () => {
|
|
|
122
138
|
|
|
123
139
|
it("should increment version with each successful transaction", async () => {
|
|
124
140
|
const result = await Effect.runPromise(
|
|
125
|
-
Effect.
|
|
126
|
-
|
|
141
|
+
Effect.scoped(
|
|
142
|
+
Effect.gen(function* () {
|
|
143
|
+
const manager = yield* DocumentManagerTag;
|
|
127
144
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
145
|
+
const tx1 = createValidTransaction("tx-1", "One");
|
|
146
|
+
const tx2 = createValidTransaction("tx-2", "Two");
|
|
147
|
+
const tx3 = createValidTransaction("tx-3", "Three");
|
|
131
148
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
149
|
+
const r1 = yield* manager.submit("doc-1", tx1);
|
|
150
|
+
const r2 = yield* manager.submit("doc-1", tx2);
|
|
151
|
+
const r3 = yield* manager.submit("doc-1", tx3);
|
|
135
152
|
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
return { r1, r2, r3 };
|
|
154
|
+
})
|
|
155
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
138
156
|
);
|
|
139
157
|
|
|
140
158
|
expect(result.r1.success).toBe(true);
|
|
@@ -150,23 +168,24 @@ describe("DocumentManager", () => {
|
|
|
150
168
|
|
|
151
169
|
it("should handle different documents independently", async () => {
|
|
152
170
|
const result = await Effect.runPromise(
|
|
153
|
-
Effect.
|
|
154
|
-
|
|
171
|
+
Effect.scoped(
|
|
172
|
+
Effect.gen(function* () {
|
|
173
|
+
const manager = yield* DocumentManagerTag;
|
|
155
174
|
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
const txDoc1 = createValidTransaction("tx-doc1", "Doc 1");
|
|
176
|
+
const txDoc2 = createValidTransaction("tx-doc2", "Doc 2");
|
|
158
177
|
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
const r1 = yield* manager.submit("doc-1", txDoc1);
|
|
179
|
+
const r2 = yield* manager.submit("doc-2", txDoc2);
|
|
161
180
|
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
return { r1, r2 };
|
|
182
|
+
})
|
|
183
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
164
184
|
);
|
|
165
185
|
|
|
166
186
|
expect(result.r1.success).toBe(true);
|
|
167
187
|
expect(result.r2.success).toBe(true);
|
|
168
188
|
|
|
169
|
-
// Both should have version 1 since they are independent documents
|
|
170
189
|
if (result.r1.success && result.r2.success) {
|
|
171
190
|
expect(result.r1.version).toBe(1);
|
|
172
191
|
expect(result.r2.version).toBe(1);
|
|
@@ -177,134 +196,124 @@ describe("DocumentManager", () => {
|
|
|
177
196
|
describe("getSnapshot", () => {
|
|
178
197
|
it("should return initial snapshot for new document", async () => {
|
|
179
198
|
const result = await Effect.runPromise(
|
|
180
|
-
Effect.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
199
|
+
Effect.scoped(
|
|
200
|
+
Effect.gen(function* () {
|
|
201
|
+
const manager = yield* DocumentManagerTag;
|
|
202
|
+
return yield* manager.getSnapshot("new-doc");
|
|
203
|
+
})
|
|
204
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
184
205
|
);
|
|
185
206
|
|
|
186
207
|
expect(result.type).toBe("snapshot");
|
|
187
208
|
expect(result.version).toBe(0);
|
|
188
|
-
// Initial state from schema defaults
|
|
189
209
|
expect(result.state).toEqual({ title: "", count: 0 });
|
|
190
210
|
});
|
|
191
211
|
|
|
192
212
|
it("should return current state after transactions", async () => {
|
|
193
213
|
const result = await Effect.runPromise(
|
|
194
|
-
Effect.
|
|
195
|
-
|
|
214
|
+
Effect.scoped(
|
|
215
|
+
Effect.gen(function* () {
|
|
216
|
+
const manager = yield* DocumentManagerTag;
|
|
196
217
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
yield* manager.submit("doc-1", tx);
|
|
218
|
+
const tx = createValidTransaction("tx-1", "Updated Title");
|
|
219
|
+
yield* manager.submit("doc-1", tx);
|
|
200
220
|
|
|
201
|
-
|
|
202
|
-
|
|
221
|
+
return yield* manager.getSnapshot("doc-1");
|
|
222
|
+
})
|
|
223
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
203
224
|
);
|
|
204
225
|
|
|
205
226
|
expect(result.type).toBe("snapshot");
|
|
206
227
|
expect(result.version).toBe(1);
|
|
207
|
-
expect((result.state as
|
|
228
|
+
expect((result.state as { title: string }).title).toBe("Updated Title");
|
|
208
229
|
});
|
|
209
230
|
|
|
210
|
-
it("should
|
|
231
|
+
it("should use initial state for new documents", async () => {
|
|
211
232
|
const result = await Effect.runPromise(
|
|
212
|
-
Effect.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const snap1 = yield* manager.getSnapshot("doc-1");
|
|
223
|
-
const snap2 = yield* manager.getSnapshot("doc-2");
|
|
224
|
-
|
|
225
|
-
return { snap1, snap2 };
|
|
226
|
-
}).pipe(Effect.provide(makeTestLayer()))
|
|
233
|
+
Effect.scoped(
|
|
234
|
+
Effect.gen(function* () {
|
|
235
|
+
const manager = yield* DocumentManagerTag;
|
|
236
|
+
return yield* manager.getSnapshot("new-doc");
|
|
237
|
+
})
|
|
238
|
+
).pipe(
|
|
239
|
+
Effect.provide(
|
|
240
|
+
makeTestLayer({ initial: { title: "Initial Title", count: 42 } })
|
|
241
|
+
)
|
|
242
|
+
)
|
|
227
243
|
);
|
|
228
244
|
|
|
229
|
-
expect(
|
|
230
|
-
expect(
|
|
245
|
+
expect(result.type).toBe("snapshot");
|
|
246
|
+
expect(result.version).toBe(0);
|
|
247
|
+
expect(result.state).toEqual({ title: "Initial Title", count: 42 });
|
|
231
248
|
});
|
|
232
249
|
});
|
|
233
250
|
|
|
234
251
|
describe("subscribe", () => {
|
|
235
252
|
it("should receive broadcasts for submitted transactions", async () => {
|
|
236
253
|
const result = await Effect.runPromise(
|
|
237
|
-
Effect.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
// Subscribe to the document
|
|
241
|
-
const broadcastStream = yield* manager.subscribe("doc-1");
|
|
254
|
+
Effect.scoped(
|
|
255
|
+
Effect.gen(function* () {
|
|
256
|
+
const manager = yield* DocumentManagerTag;
|
|
242
257
|
|
|
243
|
-
|
|
244
|
-
const tx = createValidTransaction("tx-broadcast", "Broadcast Test");
|
|
258
|
+
const broadcastStream = yield* manager.subscribe("doc-1");
|
|
245
259
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
);
|
|
260
|
+
const collectFiber = yield* Effect.fork(
|
|
261
|
+
broadcastStream.pipe(Stream.take(1), Stream.runCollect)
|
|
262
|
+
);
|
|
250
263
|
|
|
251
|
-
|
|
252
|
-
yield* Effect.sleep(50);
|
|
264
|
+
yield* Effect.sleep(50);
|
|
253
265
|
|
|
254
|
-
|
|
255
|
-
|
|
266
|
+
const tx = createValidTransaction("tx-broadcast", "Broadcast Test");
|
|
267
|
+
yield* manager.submit("doc-1", tx);
|
|
256
268
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
);
|
|
269
|
+
const broadcasts = yield* Fiber.join(collectFiber).pipe(
|
|
270
|
+
Effect.timeout(2000)
|
|
271
|
+
);
|
|
261
272
|
|
|
262
|
-
|
|
263
|
-
|
|
273
|
+
return broadcasts;
|
|
274
|
+
})
|
|
275
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
264
276
|
);
|
|
265
277
|
|
|
266
278
|
expect(result).toBeDefined();
|
|
267
279
|
if (result) {
|
|
268
280
|
const broadcasts = Array.from(result);
|
|
269
281
|
expect(broadcasts.length).toBe(1);
|
|
270
|
-
expect(broadcasts[0]
|
|
282
|
+
expect(broadcasts[0]!.type).toBe("transaction");
|
|
271
283
|
}
|
|
272
284
|
});
|
|
273
285
|
|
|
274
286
|
it("should broadcast to multiple subscribers", async () => {
|
|
275
287
|
const result = await Effect.runPromise(
|
|
276
|
-
Effect.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return { broadcasts1, broadcasts2 };
|
|
307
|
-
}).pipe(Effect.scoped, Effect.provide(makeTestLayer()))
|
|
288
|
+
Effect.scoped(
|
|
289
|
+
Effect.gen(function* () {
|
|
290
|
+
const manager = yield* DocumentManagerTag;
|
|
291
|
+
|
|
292
|
+
const stream1 = yield* manager.subscribe("doc-1");
|
|
293
|
+
const stream2 = yield* manager.subscribe("doc-1");
|
|
294
|
+
|
|
295
|
+
const collectFiber1 = yield* Effect.fork(
|
|
296
|
+
stream1.pipe(Stream.take(1), Stream.runCollect)
|
|
297
|
+
);
|
|
298
|
+
const collectFiber2 = yield* Effect.fork(
|
|
299
|
+
stream2.pipe(Stream.take(1), Stream.runCollect)
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
yield* Effect.sleep(50);
|
|
303
|
+
|
|
304
|
+
const tx = createValidTransaction("tx-multi", "Multi Broadcast");
|
|
305
|
+
yield* manager.submit("doc-1", tx);
|
|
306
|
+
|
|
307
|
+
const broadcasts1 = yield* Fiber.join(collectFiber1).pipe(
|
|
308
|
+
Effect.timeout(2000)
|
|
309
|
+
);
|
|
310
|
+
const broadcasts2 = yield* Fiber.join(collectFiber2).pipe(
|
|
311
|
+
Effect.timeout(2000)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
return { broadcasts1, broadcasts2 };
|
|
315
|
+
})
|
|
316
|
+
).pipe(Effect.provide(makeTestLayer()))
|
|
308
317
|
);
|
|
309
318
|
|
|
310
319
|
expect(result.broadcasts1).toBeDefined();
|
|
@@ -316,149 +325,11 @@ describe("DocumentManager", () => {
|
|
|
316
325
|
});
|
|
317
326
|
});
|
|
318
327
|
|
|
319
|
-
describe("
|
|
320
|
-
it("should have
|
|
321
|
-
expect(
|
|
322
|
-
"@voidhash/mimic-
|
|
323
|
-
);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe("layer", () => {
|
|
328
|
-
it("should require MimicServerConfigTag and MimicDataStorageTag", async () => {
|
|
329
|
-
// This test verifies the layer composition works correctly
|
|
330
|
-
const result = await Effect.runPromise(
|
|
331
|
-
Effect.gen(function* () {
|
|
332
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
333
|
-
return typeof manager.submit === "function";
|
|
334
|
-
}).pipe(Effect.provide(makeTestLayer()))
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
expect(result).toBe(true);
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
describe("initial state", () => {
|
|
342
|
-
const makeTestLayerWithInitial = (initial: { title?: string; count?: number }) => {
|
|
343
|
-
const configLayer = MimicConfig.layer({
|
|
344
|
-
schema: TestSchema,
|
|
345
|
-
maxTransactionHistory: 100,
|
|
346
|
-
initial,
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return DocumentManager.layer.pipe(
|
|
350
|
-
Layer.provide(configLayer),
|
|
351
|
-
Layer.provide(InMemoryDataStorage.layer)
|
|
352
|
-
);
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
const makeTestLayerWithInitialFn = (
|
|
356
|
-
initialFn: (ctx: { documentId: string }) => Effect.Effect<{ title?: string; count?: number }>
|
|
357
|
-
) => {
|
|
358
|
-
const configLayer = MimicConfig.layer({
|
|
359
|
-
schema: TestSchema,
|
|
360
|
-
maxTransactionHistory: 100,
|
|
361
|
-
initial: initialFn,
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
return DocumentManager.layer.pipe(
|
|
365
|
-
Layer.provide(configLayer),
|
|
366
|
-
Layer.provide(InMemoryDataStorage.layer)
|
|
367
|
-
);
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
it("should use initial state for new documents", async () => {
|
|
371
|
-
const result = await Effect.runPromise(
|
|
372
|
-
Effect.gen(function* () {
|
|
373
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
374
|
-
return yield* manager.getSnapshot("new-doc");
|
|
375
|
-
}).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
expect(result.type).toBe("snapshot");
|
|
379
|
-
expect(result.version).toBe(0);
|
|
380
|
-
expect(result.state).toEqual({ title: "Initial Title", count: 42 });
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("should apply defaults for omitted fields in initial state", async () => {
|
|
384
|
-
const result = await Effect.runPromise(
|
|
385
|
-
Effect.gen(function* () {
|
|
386
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
387
|
-
return yield* manager.getSnapshot("new-doc");
|
|
388
|
-
}).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Only Title" })))
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
expect(result.type).toBe("snapshot");
|
|
392
|
-
// count should be 0 (default)
|
|
393
|
-
expect(result.state).toEqual({ title: "Only Title", count: 0 });
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("should prefer stored state over initial state", async () => {
|
|
397
|
-
const result = await Effect.runPromise(
|
|
398
|
-
Effect.gen(function* () {
|
|
399
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
400
|
-
|
|
401
|
-
// Apply a transaction to modify the document
|
|
402
|
-
const tx = createValidTransaction("tx-1", "Modified Title");
|
|
403
|
-
yield* manager.submit("doc-1", tx);
|
|
404
|
-
|
|
405
|
-
// Get snapshot - should show modified state, not initial
|
|
406
|
-
return yield* manager.getSnapshot("doc-1");
|
|
407
|
-
}).pipe(Effect.provide(makeTestLayerWithInitial({ title: "Initial Title", count: 42 })))
|
|
408
|
-
);
|
|
409
|
-
|
|
410
|
-
expect(result.type).toBe("snapshot");
|
|
411
|
-
expect((result.state as any).title).toBe("Modified Title");
|
|
412
|
-
// count should still be 42 since we only modified title
|
|
413
|
-
expect((result.state as any).count).toBe(42);
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
it("should use initial state function with documentId for new documents", async () => {
|
|
417
|
-
const result = await Effect.runPromise(
|
|
418
|
-
Effect.gen(function* () {
|
|
419
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
420
|
-
return yield* manager.getSnapshot("my-special-doc");
|
|
421
|
-
}).pipe(Effect.provide(makeTestLayerWithInitialFn(
|
|
422
|
-
({ documentId }) => Effect.succeed({ title: `Doc: ${documentId}`, count: documentId.length })
|
|
423
|
-
)))
|
|
328
|
+
describe("Tag", () => {
|
|
329
|
+
it("should have correct identifier", () => {
|
|
330
|
+
expect(DocumentManagerTag.key).toBe(
|
|
331
|
+
"@voidhash/mimic-effect/DocumentManager"
|
|
424
332
|
);
|
|
425
|
-
|
|
426
|
-
expect(result.type).toBe("snapshot");
|
|
427
|
-
expect(result.version).toBe(0);
|
|
428
|
-
expect(result.state).toEqual({ title: "Doc: my-special-doc", count: 14 });
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it("should call initial function with different documentIds", async () => {
|
|
432
|
-
const layer = makeTestLayerWithInitialFn(
|
|
433
|
-
({ documentId }) => Effect.succeed({ title: documentId, count: documentId.length })
|
|
434
|
-
);
|
|
435
|
-
|
|
436
|
-
const result = await Effect.runPromise(
|
|
437
|
-
Effect.gen(function* () {
|
|
438
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
439
|
-
const snap1 = yield* manager.getSnapshot("short");
|
|
440
|
-
const snap2 = yield* manager.getSnapshot("longer-document-id");
|
|
441
|
-
return { snap1, snap2 };
|
|
442
|
-
}).pipe(Effect.provide(layer))
|
|
443
|
-
);
|
|
444
|
-
|
|
445
|
-
expect(result.snap1.state).toEqual({ title: "short", count: 5 });
|
|
446
|
-
expect(result.snap2.state).toEqual({ title: "longer-document-id", count: 18 });
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it("should apply defaults to initial function result", async () => {
|
|
450
|
-
const result = await Effect.runPromise(
|
|
451
|
-
Effect.gen(function* () {
|
|
452
|
-
const manager = yield* DocumentManager.DocumentManagerTag;
|
|
453
|
-
return yield* manager.getSnapshot("test-doc");
|
|
454
|
-
}).pipe(Effect.provide(makeTestLayerWithInitialFn(
|
|
455
|
-
({ documentId }) => Effect.succeed({ title: documentId }) // count omitted
|
|
456
|
-
)))
|
|
457
|
-
);
|
|
458
|
-
|
|
459
|
-
expect(result.type).toBe("snapshot");
|
|
460
|
-
// count should be 0 (default)
|
|
461
|
-
expect(result.state).toEqual({ title: "test-doc", count: 0 });
|
|
462
333
|
});
|
|
463
334
|
});
|
|
464
335
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { HotStorage, HotStorageTag } from "../src/HotStorage";
|
|
4
|
+
import { HotStorageTestSuite } from "../src/testing";
|
|
5
|
+
|
|
6
|
+
describe("HotStorage", () => {
|
|
7
|
+
describe("InMemory", () => {
|
|
8
|
+
// Use the test suite utilities for comprehensive testing
|
|
9
|
+
const layer = HotStorage.InMemory.make();
|
|
10
|
+
|
|
11
|
+
// Run all test suite tests
|
|
12
|
+
for (const test of HotStorageTestSuite.makeTests()) {
|
|
13
|
+
it(`[${test.category}] ${test.name}`, () =>
|
|
14
|
+
Effect.runPromise(test.run.pipe(Effect.provide(layer)))
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("Tag", () => {
|
|
20
|
+
it("should have correct identifier", () => {
|
|
21
|
+
expect(HotStorageTag.key).toBe("@voidhash/mimic-effect/HotStorage");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|