@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,24 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
|
-
import { ColdStorage, ColdStorageTag } from "../src/ColdStorage";
|
|
4
|
-
import { ColdStorageTestSuite } from "../src/testing";
|
|
5
|
-
|
|
6
|
-
describe("ColdStorage", () => {
|
|
7
|
-
describe("InMemory", () => {
|
|
8
|
-
// Use the test suite utilities for comprehensive testing
|
|
9
|
-
const layer = ColdStorage.InMemory.make();
|
|
10
|
-
|
|
11
|
-
// Run all test suite tests
|
|
12
|
-
for (const test of ColdStorageTestSuite.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(ColdStorageTag.key).toBe("@voidhash/mimic-effect/ColdStorage");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
});
|
|
@@ -1,669 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { Effect, Duration, Stream, Fiber } from "effect";
|
|
3
|
-
import { Primitive, Document, Transaction } from "@voidhash/mimic";
|
|
4
|
-
import { DocumentInstance, type SubmitResult } from "../src/DocumentInstance";
|
|
5
|
-
import { ColdStorage, ColdStorageTag } from "../src/ColdStorage";
|
|
6
|
-
import { HotStorage, HotStorageTag } from "../src/HotStorage";
|
|
7
|
-
|
|
8
|
-
// =============================================================================
|
|
9
|
-
// Test Schema
|
|
10
|
-
// =============================================================================
|
|
11
|
-
|
|
12
|
-
const TestSchema = Primitive.Struct({
|
|
13
|
-
title: Primitive.String().default(""),
|
|
14
|
-
count: Primitive.Number().default(0),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// =============================================================================
|
|
18
|
-
// Helper Functions
|
|
19
|
-
// =============================================================================
|
|
20
|
-
|
|
21
|
-
const createValidTransaction = (
|
|
22
|
-
id: string,
|
|
23
|
-
title: string
|
|
24
|
-
): Transaction.Transaction => {
|
|
25
|
-
const doc = Document.make(TestSchema);
|
|
26
|
-
doc.transaction((root) => {
|
|
27
|
-
root.title.set(title);
|
|
28
|
-
});
|
|
29
|
-
const tx = doc.flush();
|
|
30
|
-
return { ...tx, id };
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const createEmptyTransaction = (id: string): Transaction.Transaction => ({
|
|
34
|
-
id,
|
|
35
|
-
ops: [],
|
|
36
|
-
timestamp: Date.now(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// =============================================================================
|
|
40
|
-
// Test Config Factory
|
|
41
|
-
// =============================================================================
|
|
42
|
-
|
|
43
|
-
const makeTestConfig = (options?: {
|
|
44
|
-
initial?: { title?: string; count?: number };
|
|
45
|
-
}) => ({
|
|
46
|
-
schema: TestSchema,
|
|
47
|
-
initial: options?.initial,
|
|
48
|
-
maxTransactionHistory: 100,
|
|
49
|
-
snapshot: {
|
|
50
|
-
interval: Duration.minutes(5),
|
|
51
|
-
transactionThreshold: 100,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// =============================================================================
|
|
56
|
-
// DocumentInstance Tests
|
|
57
|
-
// =============================================================================
|
|
58
|
-
|
|
59
|
-
describe("DocumentInstance", () => {
|
|
60
|
-
describe("make", () => {
|
|
61
|
-
it("should create a new document instance", async () => {
|
|
62
|
-
const result = await Effect.runPromise(
|
|
63
|
-
Effect.gen(function* () {
|
|
64
|
-
const coldStorage = yield* ColdStorageTag;
|
|
65
|
-
const hotStorage = yield* HotStorageTag;
|
|
66
|
-
|
|
67
|
-
const instance = yield* DocumentInstance.make(
|
|
68
|
-
"doc-1",
|
|
69
|
-
makeTestConfig(),
|
|
70
|
-
coldStorage,
|
|
71
|
-
hotStorage
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
hasDocument: instance.document !== undefined,
|
|
76
|
-
hasPubsub: instance.pubsub !== undefined,
|
|
77
|
-
hasSubmit: typeof instance.submit === "function",
|
|
78
|
-
hasGetSnapshot: typeof instance.getSnapshot === "function",
|
|
79
|
-
hasSaveSnapshot: typeof instance.saveSnapshot === "function",
|
|
80
|
-
};
|
|
81
|
-
}).pipe(
|
|
82
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
83
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
84
|
-
)
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(result.hasDocument).toBe(true);
|
|
88
|
-
expect(result.hasPubsub).toBe(true);
|
|
89
|
-
expect(result.hasSubmit).toBe(true);
|
|
90
|
-
expect(result.hasGetSnapshot).toBe(true);
|
|
91
|
-
expect(result.hasSaveSnapshot).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("should initialize with default state when no initial provided", async () => {
|
|
95
|
-
const result = await Effect.runPromise(
|
|
96
|
-
Effect.gen(function* () {
|
|
97
|
-
const coldStorage = yield* ColdStorageTag;
|
|
98
|
-
const hotStorage = yield* HotStorageTag;
|
|
99
|
-
|
|
100
|
-
const instance = yield* DocumentInstance.make(
|
|
101
|
-
"doc-default",
|
|
102
|
-
makeTestConfig(),
|
|
103
|
-
coldStorage,
|
|
104
|
-
hotStorage
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
return instance.getSnapshot();
|
|
108
|
-
}).pipe(
|
|
109
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
110
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
111
|
-
)
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
expect(result.version).toBe(0);
|
|
115
|
-
expect(result.state).toEqual({ title: "", count: 0 });
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it("should use initial state for new documents", async () => {
|
|
119
|
-
const result = await Effect.runPromise(
|
|
120
|
-
Effect.gen(function* () {
|
|
121
|
-
const coldStorage = yield* ColdStorageTag;
|
|
122
|
-
const hotStorage = yield* HotStorageTag;
|
|
123
|
-
|
|
124
|
-
const instance = yield* DocumentInstance.make(
|
|
125
|
-
"doc-initial",
|
|
126
|
-
makeTestConfig({ initial: { title: "Initial Title", count: 42 } }),
|
|
127
|
-
coldStorage,
|
|
128
|
-
hotStorage
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
return instance.getSnapshot();
|
|
132
|
-
}).pipe(
|
|
133
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
134
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
135
|
-
)
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
expect(result.version).toBe(0);
|
|
139
|
-
expect(result.state).toEqual({ title: "Initial Title", count: 42 });
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("should restore from cold storage if document exists", async () => {
|
|
143
|
-
const result = await Effect.runPromise(
|
|
144
|
-
Effect.gen(function* () {
|
|
145
|
-
const coldStorage = yield* ColdStorageTag;
|
|
146
|
-
const hotStorage = yield* HotStorageTag;
|
|
147
|
-
|
|
148
|
-
// Pre-populate cold storage
|
|
149
|
-
yield* coldStorage.save("doc-restore", {
|
|
150
|
-
state: { title: "Restored", count: 100 },
|
|
151
|
-
version: 5,
|
|
152
|
-
schemaVersion: 1,
|
|
153
|
-
savedAt: Date.now(),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const instance = yield* DocumentInstance.make(
|
|
157
|
-
"doc-restore",
|
|
158
|
-
makeTestConfig(),
|
|
159
|
-
coldStorage,
|
|
160
|
-
hotStorage
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
return instance.getSnapshot();
|
|
164
|
-
}).pipe(
|
|
165
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
166
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
167
|
-
)
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
expect(result.version).toBe(5);
|
|
171
|
-
expect(result.state).toEqual({ title: "Restored", count: 100 });
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe("submit", () => {
|
|
176
|
-
it("should accept valid transactions", async () => {
|
|
177
|
-
const result = await Effect.runPromise(
|
|
178
|
-
Effect.gen(function* () {
|
|
179
|
-
const coldStorage = yield* ColdStorageTag;
|
|
180
|
-
const hotStorage = yield* HotStorageTag;
|
|
181
|
-
|
|
182
|
-
const instance = yield* DocumentInstance.make(
|
|
183
|
-
"doc-submit-1",
|
|
184
|
-
makeTestConfig(),
|
|
185
|
-
coldStorage,
|
|
186
|
-
hotStorage
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const tx = createValidTransaction("tx-1", "Hello World");
|
|
190
|
-
return yield* instance.submit(tx);
|
|
191
|
-
}).pipe(
|
|
192
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
193
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
194
|
-
)
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
expect(result.success).toBe(true);
|
|
198
|
-
if (result.success) {
|
|
199
|
-
expect(result.version).toBe(1);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it("should reject empty transactions", async () => {
|
|
204
|
-
const result = await Effect.runPromise(
|
|
205
|
-
Effect.gen(function* () {
|
|
206
|
-
const coldStorage = yield* ColdStorageTag;
|
|
207
|
-
const hotStorage = yield* HotStorageTag;
|
|
208
|
-
|
|
209
|
-
const instance = yield* DocumentInstance.make(
|
|
210
|
-
"doc-submit-empty",
|
|
211
|
-
makeTestConfig(),
|
|
212
|
-
coldStorage,
|
|
213
|
-
hotStorage
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
const tx = createEmptyTransaction("tx-empty");
|
|
217
|
-
return yield* instance.submit(tx);
|
|
218
|
-
}).pipe(
|
|
219
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
220
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
221
|
-
)
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
expect(result.success).toBe(false);
|
|
225
|
-
if (!result.success) {
|
|
226
|
-
expect(result.reason).toBe("Transaction is empty");
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("should reject duplicate transactions", async () => {
|
|
231
|
-
const result = await Effect.runPromise(
|
|
232
|
-
Effect.gen(function* () {
|
|
233
|
-
const coldStorage = yield* ColdStorageTag;
|
|
234
|
-
const hotStorage = yield* HotStorageTag;
|
|
235
|
-
|
|
236
|
-
const instance = yield* DocumentInstance.make(
|
|
237
|
-
"doc-submit-dup",
|
|
238
|
-
makeTestConfig(),
|
|
239
|
-
coldStorage,
|
|
240
|
-
hotStorage
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
const tx = createValidTransaction("tx-dup", "First");
|
|
244
|
-
|
|
245
|
-
const first = yield* instance.submit(tx);
|
|
246
|
-
const second = yield* instance.submit(tx);
|
|
247
|
-
|
|
248
|
-
return { first, second };
|
|
249
|
-
}).pipe(
|
|
250
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
251
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
252
|
-
)
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
expect(result.first.success).toBe(true);
|
|
256
|
-
expect(result.second.success).toBe(false);
|
|
257
|
-
if (!result.second.success) {
|
|
258
|
-
expect(result.second.reason).toBe(
|
|
259
|
-
"Transaction has already been processed"
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it("should increment version with each successful transaction", async () => {
|
|
265
|
-
const result = await Effect.runPromise(
|
|
266
|
-
Effect.gen(function* () {
|
|
267
|
-
const coldStorage = yield* ColdStorageTag;
|
|
268
|
-
const hotStorage = yield* HotStorageTag;
|
|
269
|
-
|
|
270
|
-
const instance = yield* DocumentInstance.make(
|
|
271
|
-
"doc-submit-versions",
|
|
272
|
-
makeTestConfig(),
|
|
273
|
-
coldStorage,
|
|
274
|
-
hotStorage
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const tx1 = createValidTransaction("tx-1", "One");
|
|
278
|
-
const tx2 = createValidTransaction("tx-2", "Two");
|
|
279
|
-
const tx3 = createValidTransaction("tx-3", "Three");
|
|
280
|
-
|
|
281
|
-
const r1 = yield* instance.submit(tx1);
|
|
282
|
-
const r2 = yield* instance.submit(tx2);
|
|
283
|
-
const r3 = yield* instance.submit(tx3);
|
|
284
|
-
|
|
285
|
-
return { r1, r2, r3 };
|
|
286
|
-
}).pipe(
|
|
287
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
288
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
289
|
-
)
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
expect(result.r1.success).toBe(true);
|
|
293
|
-
expect(result.r2.success).toBe(true);
|
|
294
|
-
expect(result.r3.success).toBe(true);
|
|
295
|
-
|
|
296
|
-
if (result.r1.success && result.r2.success && result.r3.success) {
|
|
297
|
-
expect(result.r1.version).toBe(1);
|
|
298
|
-
expect(result.r2.version).toBe(2);
|
|
299
|
-
expect(result.r3.version).toBe(3);
|
|
300
|
-
}
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("should persist transactions to WAL", async () => {
|
|
304
|
-
const result = await Effect.runPromise(
|
|
305
|
-
Effect.gen(function* () {
|
|
306
|
-
const coldStorage = yield* ColdStorageTag;
|
|
307
|
-
const hotStorage = yield* HotStorageTag;
|
|
308
|
-
|
|
309
|
-
const instance = yield* DocumentInstance.make(
|
|
310
|
-
"doc-wal",
|
|
311
|
-
makeTestConfig(),
|
|
312
|
-
coldStorage,
|
|
313
|
-
hotStorage
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
const tx1 = createValidTransaction("tx-1", "First");
|
|
317
|
-
const tx2 = createValidTransaction("tx-2", "Second");
|
|
318
|
-
|
|
319
|
-
yield* instance.submit(tx1);
|
|
320
|
-
yield* instance.submit(tx2);
|
|
321
|
-
|
|
322
|
-
// Check WAL entries
|
|
323
|
-
const entries = yield* hotStorage.getEntries("doc-wal", 0);
|
|
324
|
-
return entries;
|
|
325
|
-
}).pipe(
|
|
326
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
327
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
328
|
-
)
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
expect(result.length).toBe(2);
|
|
332
|
-
expect(result[0]!.version).toBe(1);
|
|
333
|
-
expect(result[1]!.version).toBe(2);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe("getSnapshot", () => {
|
|
338
|
-
it("should return current state after transactions", async () => {
|
|
339
|
-
const result = await Effect.runPromise(
|
|
340
|
-
Effect.gen(function* () {
|
|
341
|
-
const coldStorage = yield* ColdStorageTag;
|
|
342
|
-
const hotStorage = yield* HotStorageTag;
|
|
343
|
-
|
|
344
|
-
const instance = yield* DocumentInstance.make(
|
|
345
|
-
"doc-snapshot",
|
|
346
|
-
makeTestConfig({ initial: { title: "Initial" } }),
|
|
347
|
-
coldStorage,
|
|
348
|
-
hotStorage
|
|
349
|
-
);
|
|
350
|
-
|
|
351
|
-
const tx = createValidTransaction("tx-1", "Updated Title");
|
|
352
|
-
yield* instance.submit(tx);
|
|
353
|
-
|
|
354
|
-
return instance.getSnapshot();
|
|
355
|
-
}).pipe(
|
|
356
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
357
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
358
|
-
)
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
expect(result.version).toBe(1);
|
|
362
|
-
expect((result.state as { title: string }).title).toBe("Updated Title");
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
describe("getVersion", () => {
|
|
367
|
-
it("should return current version", async () => {
|
|
368
|
-
const result = await Effect.runPromise(
|
|
369
|
-
Effect.gen(function* () {
|
|
370
|
-
const coldStorage = yield* ColdStorageTag;
|
|
371
|
-
const hotStorage = yield* HotStorageTag;
|
|
372
|
-
|
|
373
|
-
const instance = yield* DocumentInstance.make(
|
|
374
|
-
"doc-version",
|
|
375
|
-
makeTestConfig(),
|
|
376
|
-
coldStorage,
|
|
377
|
-
hotStorage
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
const v0 = instance.getVersion();
|
|
381
|
-
|
|
382
|
-
const tx1 = createValidTransaction("tx-1", "First");
|
|
383
|
-
yield* instance.submit(tx1);
|
|
384
|
-
const v1 = instance.getVersion();
|
|
385
|
-
|
|
386
|
-
const tx2 = createValidTransaction("tx-2", "Second");
|
|
387
|
-
yield* instance.submit(tx2);
|
|
388
|
-
const v2 = instance.getVersion();
|
|
389
|
-
|
|
390
|
-
return { v0, v1, v2 };
|
|
391
|
-
}).pipe(
|
|
392
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
393
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
394
|
-
)
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
expect(result.v0).toBe(0);
|
|
398
|
-
expect(result.v1).toBe(1);
|
|
399
|
-
expect(result.v2).toBe(2);
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
describe("touch", () => {
|
|
404
|
-
it("should update activity time without error", async () => {
|
|
405
|
-
const result = await Effect.runPromise(
|
|
406
|
-
Effect.gen(function* () {
|
|
407
|
-
const coldStorage = yield* ColdStorageTag;
|
|
408
|
-
const hotStorage = yield* HotStorageTag;
|
|
409
|
-
|
|
410
|
-
const instance = yield* DocumentInstance.make(
|
|
411
|
-
"doc-touch",
|
|
412
|
-
makeTestConfig(),
|
|
413
|
-
coldStorage,
|
|
414
|
-
hotStorage
|
|
415
|
-
);
|
|
416
|
-
|
|
417
|
-
yield* instance.touch();
|
|
418
|
-
return true;
|
|
419
|
-
}).pipe(
|
|
420
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
421
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
422
|
-
)
|
|
423
|
-
);
|
|
424
|
-
|
|
425
|
-
expect(result).toBe(true);
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
describe("pubsub broadcasts", () => {
|
|
430
|
-
it("should broadcast transactions to subscribers", async () => {
|
|
431
|
-
const result = await Effect.runPromise(
|
|
432
|
-
Effect.gen(function* () {
|
|
433
|
-
const coldStorage = yield* ColdStorageTag;
|
|
434
|
-
const hotStorage = yield* HotStorageTag;
|
|
435
|
-
|
|
436
|
-
const instance = yield* DocumentInstance.make(
|
|
437
|
-
"doc-broadcast",
|
|
438
|
-
makeTestConfig(),
|
|
439
|
-
coldStorage,
|
|
440
|
-
hotStorage
|
|
441
|
-
);
|
|
442
|
-
|
|
443
|
-
// Subscribe to broadcasts
|
|
444
|
-
const broadcastStream = Stream.fromPubSub(instance.pubsub);
|
|
445
|
-
|
|
446
|
-
// Start collecting in background
|
|
447
|
-
const collectFiber = yield* Effect.fork(
|
|
448
|
-
broadcastStream.pipe(Stream.take(1), Stream.runCollect)
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
// Wait a bit for subscription to be ready
|
|
452
|
-
yield* Effect.sleep(50);
|
|
453
|
-
|
|
454
|
-
// Submit a transaction
|
|
455
|
-
const tx = createValidTransaction("tx-broadcast", "Broadcast Test");
|
|
456
|
-
yield* instance.submit(tx);
|
|
457
|
-
|
|
458
|
-
// Wait for broadcast
|
|
459
|
-
const broadcasts = yield* Fiber.join(collectFiber).pipe(
|
|
460
|
-
Effect.timeout(2000)
|
|
461
|
-
);
|
|
462
|
-
|
|
463
|
-
return broadcasts;
|
|
464
|
-
}).pipe(
|
|
465
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
466
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
467
|
-
)
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
expect(result).toBeDefined();
|
|
471
|
-
if (result) {
|
|
472
|
-
const broadcasts = Array.from(result);
|
|
473
|
-
expect(broadcasts.length).toBe(1);
|
|
474
|
-
expect(broadcasts[0]!.type).toBe("transaction");
|
|
475
|
-
}
|
|
476
|
-
});
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
describe("getSnapshotTracking", () => {
|
|
480
|
-
it("should track snapshot state", async () => {
|
|
481
|
-
const result = await Effect.runPromise(
|
|
482
|
-
Effect.gen(function* () {
|
|
483
|
-
const coldStorage = yield* ColdStorageTag;
|
|
484
|
-
const hotStorage = yield* HotStorageTag;
|
|
485
|
-
|
|
486
|
-
const instance = yield* DocumentInstance.make(
|
|
487
|
-
"doc-tracking",
|
|
488
|
-
makeTestConfig(),
|
|
489
|
-
coldStorage,
|
|
490
|
-
hotStorage
|
|
491
|
-
);
|
|
492
|
-
|
|
493
|
-
const initialTracking = yield* instance.getSnapshotTracking;
|
|
494
|
-
|
|
495
|
-
// Submit some transactions
|
|
496
|
-
yield* instance.submit(createValidTransaction("tx-1", "One"));
|
|
497
|
-
yield* instance.submit(createValidTransaction("tx-2", "Two"));
|
|
498
|
-
|
|
499
|
-
const afterTracking = yield* instance.getSnapshotTracking;
|
|
500
|
-
|
|
501
|
-
return { initialTracking, afterTracking };
|
|
502
|
-
}).pipe(
|
|
503
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
504
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
505
|
-
)
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
expect(result.initialTracking.transactionsSinceSnapshot).toBe(0);
|
|
509
|
-
expect(result.afterTracking.transactionsSinceSnapshot).toBe(2);
|
|
510
|
-
});
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
describe("saveSnapshot", () => {
|
|
514
|
-
it("should save snapshot to cold storage", async () => {
|
|
515
|
-
const result = await Effect.runPromise(
|
|
516
|
-
Effect.gen(function* () {
|
|
517
|
-
const coldStorage = yield* ColdStorageTag;
|
|
518
|
-
const hotStorage = yield* HotStorageTag;
|
|
519
|
-
|
|
520
|
-
const instance = yield* DocumentInstance.make(
|
|
521
|
-
"doc-save-snapshot",
|
|
522
|
-
makeTestConfig(),
|
|
523
|
-
coldStorage,
|
|
524
|
-
hotStorage
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
// Submit some transactions
|
|
528
|
-
yield* instance.submit(createValidTransaction("tx-1", "Final Title"));
|
|
529
|
-
|
|
530
|
-
// Save snapshot manually
|
|
531
|
-
yield* instance.saveSnapshot();
|
|
532
|
-
|
|
533
|
-
// Verify cold storage was updated
|
|
534
|
-
const stored = yield* coldStorage.load("doc-save-snapshot");
|
|
535
|
-
return stored;
|
|
536
|
-
}).pipe(
|
|
537
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
538
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
539
|
-
)
|
|
540
|
-
);
|
|
541
|
-
|
|
542
|
-
expect(result).toBeDefined();
|
|
543
|
-
expect(result!.version).toBe(1);
|
|
544
|
-
expect((result!.state as { title: string }).title).toBe("Final Title");
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
it("should be idempotent when called multiple times at same version", async () => {
|
|
548
|
-
const result = await Effect.runPromise(
|
|
549
|
-
Effect.gen(function* () {
|
|
550
|
-
const coldStorage = yield* ColdStorageTag;
|
|
551
|
-
const hotStorage = yield* HotStorageTag;
|
|
552
|
-
|
|
553
|
-
const instance = yield* DocumentInstance.make(
|
|
554
|
-
"doc-idempotent-snapshot",
|
|
555
|
-
makeTestConfig(),
|
|
556
|
-
coldStorage,
|
|
557
|
-
hotStorage
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
yield* instance.submit(createValidTransaction("tx-1", "Test"));
|
|
561
|
-
|
|
562
|
-
// Save snapshot multiple times
|
|
563
|
-
yield* instance.saveSnapshot();
|
|
564
|
-
yield* instance.saveSnapshot();
|
|
565
|
-
yield* instance.saveSnapshot();
|
|
566
|
-
|
|
567
|
-
return true;
|
|
568
|
-
}).pipe(
|
|
569
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
570
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
571
|
-
)
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
expect(result).toBe(true);
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
describe("WAL replay", () => {
|
|
579
|
-
it("should replay WAL entries on restore", async () => {
|
|
580
|
-
const result = await Effect.runPromise(
|
|
581
|
-
Effect.gen(function* () {
|
|
582
|
-
const coldStorage = yield* ColdStorageTag;
|
|
583
|
-
const hotStorage = yield* HotStorageTag;
|
|
584
|
-
|
|
585
|
-
// Pre-populate cold storage with base state
|
|
586
|
-
yield* coldStorage.save("doc-wal-replay", {
|
|
587
|
-
state: { title: "Base", count: 0 },
|
|
588
|
-
version: 0,
|
|
589
|
-
schemaVersion: 1,
|
|
590
|
-
savedAt: Date.now(),
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
// Pre-populate WAL with entries
|
|
594
|
-
const tx1 = createValidTransaction("tx-1", "After WAL 1");
|
|
595
|
-
yield* hotStorage.append("doc-wal-replay", {
|
|
596
|
-
transaction: tx1,
|
|
597
|
-
version: 1,
|
|
598
|
-
timestamp: Date.now(),
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
const tx2 = createValidTransaction("tx-2", "After WAL 2");
|
|
602
|
-
yield* hotStorage.append("doc-wal-replay", {
|
|
603
|
-
transaction: tx2,
|
|
604
|
-
version: 2,
|
|
605
|
-
timestamp: Date.now(),
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
// Create instance - should replay WAL
|
|
609
|
-
const instance = yield* DocumentInstance.make(
|
|
610
|
-
"doc-wal-replay",
|
|
611
|
-
makeTestConfig(),
|
|
612
|
-
coldStorage,
|
|
613
|
-
hotStorage
|
|
614
|
-
);
|
|
615
|
-
|
|
616
|
-
return instance.getSnapshot();
|
|
617
|
-
}).pipe(
|
|
618
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
619
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
620
|
-
)
|
|
621
|
-
);
|
|
622
|
-
|
|
623
|
-
// Version should reflect replayed WAL
|
|
624
|
-
expect(result.version).toBe(2);
|
|
625
|
-
expect((result.state as { title: string }).title).toBe("After WAL 2");
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
describe("initial state function", () => {
|
|
630
|
-
it("should support initial state as effect function", async () => {
|
|
631
|
-
const result = await Effect.runPromise(
|
|
632
|
-
Effect.gen(function* () {
|
|
633
|
-
const coldStorage = yield* ColdStorageTag;
|
|
634
|
-
const hotStorage = yield* HotStorageTag;
|
|
635
|
-
|
|
636
|
-
const instance = yield* DocumentInstance.make(
|
|
637
|
-
"doc-dynamic-initial",
|
|
638
|
-
{
|
|
639
|
-
schema: TestSchema,
|
|
640
|
-
initial: (ctx) =>
|
|
641
|
-
Effect.succeed({
|
|
642
|
-
title: `Document: ${ctx.documentId}`,
|
|
643
|
-
count: 100,
|
|
644
|
-
}),
|
|
645
|
-
maxTransactionHistory: 100,
|
|
646
|
-
snapshot: {
|
|
647
|
-
interval: Duration.minutes(5),
|
|
648
|
-
transactionThreshold: 100,
|
|
649
|
-
},
|
|
650
|
-
},
|
|
651
|
-
coldStorage,
|
|
652
|
-
hotStorage
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
return instance.getSnapshot();
|
|
656
|
-
}).pipe(
|
|
657
|
-
Effect.provide(ColdStorage.InMemory.make()),
|
|
658
|
-
Effect.provide(HotStorage.InMemory.make())
|
|
659
|
-
)
|
|
660
|
-
);
|
|
661
|
-
|
|
662
|
-
expect(result.version).toBe(0);
|
|
663
|
-
expect(result.state).toEqual({
|
|
664
|
-
title: "Document: doc-dynamic-initial",
|
|
665
|
-
count: 100,
|
|
666
|
-
});
|
|
667
|
-
});
|
|
668
|
-
});
|
|
669
|
-
});
|