@voidhash/mimic-effect 1.0.0-beta.1 → 1.0.0-beta.10
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 +116 -74
- package/dist/ColdStorage.cjs +9 -5
- package/dist/ColdStorage.d.cts.map +1 -1
- package/dist/ColdStorage.d.mts.map +1 -1
- package/dist/ColdStorage.mjs +9 -5
- package/dist/ColdStorage.mjs.map +1 -1
- package/dist/DocumentInstance.cjs +263 -0
- package/dist/DocumentInstance.d.cts +78 -0
- package/dist/DocumentInstance.d.cts.map +1 -0
- package/dist/DocumentInstance.d.mts +78 -0
- package/dist/DocumentInstance.d.mts.map +1 -0
- package/dist/DocumentInstance.mjs +264 -0
- package/dist/DocumentInstance.mjs.map +1 -0
- package/dist/Errors.cjs +10 -1
- package/dist/Errors.d.cts +18 -3
- package/dist/Errors.d.cts.map +1 -1
- package/dist/Errors.d.mts +18 -3
- package/dist/Errors.d.mts.map +1 -1
- package/dist/Errors.mjs +9 -1
- package/dist/Errors.mjs.map +1 -1
- package/dist/HotStorage.cjs +39 -12
- package/dist/HotStorage.d.cts +17 -1
- package/dist/HotStorage.d.cts.map +1 -1
- package/dist/HotStorage.d.mts +17 -1
- package/dist/HotStorage.d.mts.map +1 -1
- package/dist/HotStorage.mjs +39 -12
- package/dist/HotStorage.mjs.map +1 -1
- package/dist/Metrics.cjs +29 -1
- package/dist/Metrics.d.cts +5 -0
- package/dist/Metrics.d.cts.map +1 -1
- package/dist/Metrics.d.mts +5 -0
- package/dist/Metrics.d.mts.map +1 -1
- package/dist/Metrics.mjs +26 -1
- package/dist/Metrics.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +44 -139
- package/dist/MimicClusterServerEngine.d.cts.map +1 -1
- package/dist/MimicClusterServerEngine.d.mts +1 -1
- package/dist/MimicClusterServerEngine.d.mts.map +1 -1
- package/dist/MimicClusterServerEngine.mjs +46 -141
- package/dist/MimicClusterServerEngine.mjs.map +1 -1
- package/dist/MimicServer.cjs +20 -20
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +20 -20
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +92 -11
- package/dist/MimicServerEngine.d.cts +12 -4
- package/dist/MimicServerEngine.d.cts.map +1 -1
- package/dist/MimicServerEngine.d.mts +12 -4
- package/dist/MimicServerEngine.d.mts.map +1 -1
- package/dist/MimicServerEngine.mjs +94 -13
- package/dist/MimicServerEngine.mjs.map +1 -1
- package/dist/PresenceManager.cjs +5 -5
- package/dist/PresenceManager.d.cts.map +1 -1
- package/dist/PresenceManager.d.mts.map +1 -1
- package/dist/PresenceManager.mjs +5 -5
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/Protocol.d.cts +1 -1
- package/dist/Protocol.d.mts +1 -1
- package/dist/Types.d.cts +9 -2
- package/dist/Types.d.cts.map +1 -1
- package/dist/Types.d.mts +9 -2
- package/dist/Types.d.mts.map +1 -1
- package/dist/index.cjs +5 -6
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -3
- 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 +162 -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 +163 -0
- package/dist/testing/FailingStorage.mjs.map +1 -0
- package/dist/testing/HotStorageTestSuite.cjs +820 -0
- package/dist/testing/HotStorageTestSuite.d.cts +42 -0
- package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
- package/dist/testing/HotStorageTestSuite.d.mts +42 -0
- package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
- package/dist/testing/HotStorageTestSuite.mjs +820 -0
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.cjs +487 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts +37 -0
- package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts +37 -0
- package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
- package/dist/testing/StorageIntegrationTestSuite.mjs +487 -0
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
- package/dist/testing/assertions.cjs +117 -0
- package/dist/testing/assertions.mjs +112 -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 +8 -3
- package/src/ColdStorage.ts +21 -12
- package/src/DocumentInstance.ts +527 -0
- package/src/Errors.ts +15 -1
- package/src/HotStorage.ts +115 -24
- package/src/Metrics.ts +30 -0
- package/src/MimicClusterServerEngine.ts +120 -275
- package/src/MimicServer.ts +83 -75
- package/src/MimicServerEngine.ts +230 -30
- package/src/PresenceManager.ts +44 -34
- package/src/Types.ts +9 -2
- package/src/index.ts +5 -35
- package/src/testing/ColdStorageTestSuite.ts +589 -0
- package/src/testing/FailingStorage.ts +338 -0
- package/src/testing/HotStorageTestSuite.ts +1105 -0
- package/src/testing/StorageIntegrationTestSuite.ts +736 -0
- package/src/testing/assertions.ts +188 -0
- package/src/testing/index.ts +83 -0
- package/src/testing/types.ts +100 -0
- package/tests/ColdStorage.test.ts +8 -120
- package/tests/DocumentInstance.test.ts +669 -0
- package/tests/HotStorage.test.ts +7 -126
- package/tests/StorageIntegration.test.ts +259 -0
- package/tsdown.config.ts +1 -1
- package/dist/DocumentManager.cjs +0 -229
- package/dist/DocumentManager.d.cts +0 -59
- package/dist/DocumentManager.d.cts.map +0 -1
- package/dist/DocumentManager.d.mts +0 -59
- package/dist/DocumentManager.d.mts.map +0 -1
- package/dist/DocumentManager.mjs +0 -227
- package/dist/DocumentManager.mjs.map +0 -1
- package/src/DocumentManager.ts +0 -506
- package/tests/DocumentManager.test.ts +0 -335
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ColdStorageError, HotStorageError } from "../Errors.cjs";
|
|
2
|
+
import { ColdStorageTag } from "../ColdStorage.cjs";
|
|
3
|
+
import { HotStorageTag } from "../HotStorage.cjs";
|
|
4
|
+
import { StorageTestCase } from "./types.cjs";
|
|
5
|
+
import * as effect0 from "effect";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
|
|
8
|
+
//#region src/testing/StorageIntegrationTestSuite.d.ts
|
|
9
|
+
|
|
10
|
+
declare const Categories: {
|
|
11
|
+
readonly SNAPSHOT_WAL_COORDINATION: "Snapshot + WAL Coordination";
|
|
12
|
+
readonly VERSION_VERIFICATION: "Version Verification";
|
|
13
|
+
readonly RECOVERY_SCENARIOS: "Recovery Scenarios";
|
|
14
|
+
readonly TRANSACTION_ENCODING: "Transaction Encoding";
|
|
15
|
+
};
|
|
16
|
+
type IntegrationTestCase = StorageTestCase<ColdStorageError | HotStorageError, ColdStorageTag | HotStorageTag>;
|
|
17
|
+
/**
|
|
18
|
+
* Generate all integration test cases
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
declare const StorageIntegrationTestSuite: {
|
|
22
|
+
Categories: {
|
|
23
|
+
readonly SNAPSHOT_WAL_COORDINATION: "Snapshot + WAL Coordination";
|
|
24
|
+
readonly VERSION_VERIFICATION: "Version Verification";
|
|
25
|
+
readonly RECOVERY_SCENARIOS: "Recovery Scenarios";
|
|
26
|
+
readonly TRANSACTION_ENCODING: "Transaction Encoding";
|
|
27
|
+
};
|
|
28
|
+
makeTests: () => IntegrationTestCase[];
|
|
29
|
+
runAll: <R>(layer: effect0.Layer.Layer<ColdStorageTag | HotStorageTag, never, R>) => Effect.Effect<{
|
|
30
|
+
name: string;
|
|
31
|
+
passed: boolean;
|
|
32
|
+
error?: unknown;
|
|
33
|
+
}[], never, R>;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { Categories, StorageIntegrationTestSuite };
|
|
37
|
+
//# sourceMappingURL=StorageIntegrationTestSuite.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageIntegrationTestSuite.d.cts","names":[],"sources":["../../src/testing/StorageIntegrationTestSuite.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cA+Ca;;;;;;KAmDR,mBAAA,GAAsB,gBAAgB,mBAAmB,iBAAiB,iBAAiB;;;;;cAynBnF;;;;;;;mBAlCgB;qBAWH,OAAA,CAAA,KAAA,CAAM,MAAM,iBAAiB,sBAAsB,OAAE,MAAA,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ColdStorageError, HotStorageError } from "../Errors.mjs";
|
|
2
|
+
import { ColdStorageTag } from "../ColdStorage.mjs";
|
|
3
|
+
import { HotStorageTag } from "../HotStorage.mjs";
|
|
4
|
+
import { StorageTestCase } from "./types.mjs";
|
|
5
|
+
import * as effect0 from "effect";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
|
|
8
|
+
//#region src/testing/StorageIntegrationTestSuite.d.ts
|
|
9
|
+
|
|
10
|
+
declare const Categories: {
|
|
11
|
+
readonly SNAPSHOT_WAL_COORDINATION: "Snapshot + WAL Coordination";
|
|
12
|
+
readonly VERSION_VERIFICATION: "Version Verification";
|
|
13
|
+
readonly RECOVERY_SCENARIOS: "Recovery Scenarios";
|
|
14
|
+
readonly TRANSACTION_ENCODING: "Transaction Encoding";
|
|
15
|
+
};
|
|
16
|
+
type IntegrationTestCase = StorageTestCase<ColdStorageError | HotStorageError, ColdStorageTag | HotStorageTag>;
|
|
17
|
+
/**
|
|
18
|
+
* Generate all integration test cases
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
declare const StorageIntegrationTestSuite: {
|
|
22
|
+
Categories: {
|
|
23
|
+
readonly SNAPSHOT_WAL_COORDINATION: "Snapshot + WAL Coordination";
|
|
24
|
+
readonly VERSION_VERIFICATION: "Version Verification";
|
|
25
|
+
readonly RECOVERY_SCENARIOS: "Recovery Scenarios";
|
|
26
|
+
readonly TRANSACTION_ENCODING: "Transaction Encoding";
|
|
27
|
+
};
|
|
28
|
+
makeTests: () => IntegrationTestCase[];
|
|
29
|
+
runAll: <R>(layer: effect0.Layer.Layer<ColdStorageTag | HotStorageTag, never, R>) => Effect.Effect<{
|
|
30
|
+
name: string;
|
|
31
|
+
passed: boolean;
|
|
32
|
+
error?: unknown;
|
|
33
|
+
}[], never, R>;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { Categories, StorageIntegrationTestSuite };
|
|
37
|
+
//# sourceMappingURL=StorageIntegrationTestSuite.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageIntegrationTestSuite.d.mts","names":[],"sources":["../../src/testing/StorageIntegrationTestSuite.ts"],"sourcesContent":[],"mappings":";;;;;;;;;cA+Ca;;;;;;KAmDR,mBAAA,GAAsB,gBAAgB,mBAAmB,iBAAiB,iBAAiB;;;;;cAynBnF;;;;;;;mBAlCgB;qBAWH,OAAA,CAAA,KAAA,CAAM,MAAM,iBAAiB,sBAAsB,OAAE,MAAA,CAAA"}
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { ColdStorageTag } from "../ColdStorage.mjs";
|
|
2
|
+
import { HotStorageTag } from "../HotStorage.mjs";
|
|
3
|
+
import { assertDefined, assertEmpty, assertEqual, assertLength, assertTrue, assertUndefined } from "./assertions.mjs";
|
|
4
|
+
import { Effect, Schema } from "effect";
|
|
5
|
+
import { Operation, OperationDefinition, OperationPath, Transaction } from "@voidhash/mimic";
|
|
6
|
+
|
|
7
|
+
//#region src/testing/StorageIntegrationTestSuite.ts
|
|
8
|
+
/**
|
|
9
|
+
* @voidhash/mimic-effect/testing - StorageIntegrationTestSuite
|
|
10
|
+
*
|
|
11
|
+
* Integration tests for verifying Hot/Cold storage coordination.
|
|
12
|
+
* Tests snapshot + WAL replay, failure handling, and version verification.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { StorageIntegrationTestSuite } from "@voidhash/mimic-effect/testing";
|
|
17
|
+
* import { describe, it } from "vitest";
|
|
18
|
+
* import { Effect, Layer } from "effect";
|
|
19
|
+
* import { ColdStorage, HotStorage } from "@voidhash/mimic-effect";
|
|
20
|
+
*
|
|
21
|
+
* describe("Storage Integration", () => {
|
|
22
|
+
* const layer = Layer.mergeAll(
|
|
23
|
+
* ColdStorage.InMemory.make(),
|
|
24
|
+
* HotStorage.InMemory.make()
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* for (const test of StorageIntegrationTestSuite.makeTests()) {
|
|
28
|
+
* it(test.name, () =>
|
|
29
|
+
* Effect.runPromise(test.run.pipe(Effect.provide(layer)))
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
const Categories = {
|
|
36
|
+
SNAPSHOT_WAL_COORDINATION: "Snapshot + WAL Coordination",
|
|
37
|
+
VERSION_VERIFICATION: "Version Verification",
|
|
38
|
+
RECOVERY_SCENARIOS: "Recovery Scenarios",
|
|
39
|
+
TRANSACTION_ENCODING: "Transaction Encoding"
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Test operation definition for creating proper Operation objects in tests.
|
|
43
|
+
*/
|
|
44
|
+
const TestSetDefinition = OperationDefinition.make({
|
|
45
|
+
kind: "test.set",
|
|
46
|
+
payload: Schema.Unknown,
|
|
47
|
+
target: Schema.Unknown,
|
|
48
|
+
apply: (payload) => payload
|
|
49
|
+
});
|
|
50
|
+
const makeSnapshot = (version, state = { data: `v${version}` }) => ({
|
|
51
|
+
state,
|
|
52
|
+
version,
|
|
53
|
+
schemaVersion: 1,
|
|
54
|
+
savedAt: Date.now()
|
|
55
|
+
});
|
|
56
|
+
const makeWalEntry = (version, pathString = "data", payload = `v${version}`) => ({
|
|
57
|
+
transaction: Transaction.make([Operation.fromDefinition(OperationPath.make(pathString), TestSetDefinition, payload)]),
|
|
58
|
+
version,
|
|
59
|
+
timestamp: Date.now()
|
|
60
|
+
});
|
|
61
|
+
const snapshotWalCoordinationTests = [
|
|
62
|
+
{
|
|
63
|
+
name: "load empty document returns undefined snapshot and empty WAL",
|
|
64
|
+
category: Categories.SNAPSHOT_WAL_COORDINATION,
|
|
65
|
+
run: Effect.gen(function* () {
|
|
66
|
+
const cold = yield* ColdStorageTag;
|
|
67
|
+
const hot = yield* HotStorageTag;
|
|
68
|
+
const snapshot = yield* cold.load("empty-doc");
|
|
69
|
+
const walEntries = yield* hot.getEntries("empty-doc", 0);
|
|
70
|
+
assertUndefined(snapshot, "Snapshot should be undefined for new doc");
|
|
71
|
+
assertEmpty(walEntries, "WAL should be empty for new doc");
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: "restore from snapshot only (no WAL)",
|
|
76
|
+
category: Categories.SNAPSHOT_WAL_COORDINATION,
|
|
77
|
+
run: Effect.gen(function* () {
|
|
78
|
+
const cold = yield* ColdStorageTag;
|
|
79
|
+
const hot = yield* HotStorageTag;
|
|
80
|
+
const docId = "snapshot-only";
|
|
81
|
+
const snapshot = makeSnapshot(5, { title: "Hello" });
|
|
82
|
+
yield* cold.save(docId, snapshot);
|
|
83
|
+
const loaded = yield* cold.load(docId);
|
|
84
|
+
const walEntries = yield* hot.getEntries(docId, 5);
|
|
85
|
+
assertDefined(loaded, "Snapshot should be loaded");
|
|
86
|
+
assertEqual(loaded.version, 5, "Snapshot version should match");
|
|
87
|
+
assertEqual(loaded.state, { title: "Hello" }, "Snapshot state should match");
|
|
88
|
+
assertEmpty(walEntries, "WAL should be empty after snapshot version");
|
|
89
|
+
})
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "restore from WAL only (no snapshot)",
|
|
93
|
+
category: Categories.SNAPSHOT_WAL_COORDINATION,
|
|
94
|
+
run: Effect.gen(function* () {
|
|
95
|
+
const cold = yield* ColdStorageTag;
|
|
96
|
+
const hot = yield* HotStorageTag;
|
|
97
|
+
const docId = "wal-only";
|
|
98
|
+
yield* hot.append(docId, makeWalEntry(1));
|
|
99
|
+
yield* hot.append(docId, makeWalEntry(2));
|
|
100
|
+
yield* hot.append(docId, makeWalEntry(3));
|
|
101
|
+
const snapshot = yield* cold.load(docId);
|
|
102
|
+
const walEntries = yield* hot.getEntries(docId, 0);
|
|
103
|
+
assertUndefined(snapshot, "No snapshot should exist");
|
|
104
|
+
assertLength(walEntries, 3, "Should have 3 WAL entries");
|
|
105
|
+
assertEqual(walEntries[0].version, 1, "First entry should be v1");
|
|
106
|
+
assertEqual(walEntries[2].version, 3, "Last entry should be v3");
|
|
107
|
+
})
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "restore from snapshot + WAL replay",
|
|
111
|
+
category: Categories.SNAPSHOT_WAL_COORDINATION,
|
|
112
|
+
run: Effect.gen(function* () {
|
|
113
|
+
const cold = yield* ColdStorageTag;
|
|
114
|
+
const hot = yield* HotStorageTag;
|
|
115
|
+
const docId = "snapshot-plus-wal";
|
|
116
|
+
yield* cold.save(docId, makeSnapshot(5));
|
|
117
|
+
yield* hot.append(docId, makeWalEntry(6));
|
|
118
|
+
yield* hot.append(docId, makeWalEntry(7));
|
|
119
|
+
yield* hot.append(docId, makeWalEntry(8));
|
|
120
|
+
const snapshot = yield* cold.load(docId);
|
|
121
|
+
const walEntries = yield* hot.getEntries(docId, snapshot.version);
|
|
122
|
+
assertEqual(snapshot.version, 5, "Snapshot at v5");
|
|
123
|
+
assertLength(walEntries, 3, "3 WAL entries after snapshot");
|
|
124
|
+
assertEqual(walEntries[0].version, 6, "First WAL entry is v6");
|
|
125
|
+
assertEqual(walEntries[2].version, 8, "Last WAL entry is v8");
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "truncate WAL after snapshot",
|
|
130
|
+
category: Categories.SNAPSHOT_WAL_COORDINATION,
|
|
131
|
+
run: Effect.gen(function* () {
|
|
132
|
+
const cold = yield* ColdStorageTag;
|
|
133
|
+
const hot = yield* HotStorageTag;
|
|
134
|
+
const docId = "truncate-test";
|
|
135
|
+
for (let i = 1; i <= 5; i++) yield* hot.append(docId, makeWalEntry(i));
|
|
136
|
+
yield* cold.save(docId, makeSnapshot(3));
|
|
137
|
+
yield* hot.truncate(docId, 3);
|
|
138
|
+
const walEntries = yield* hot.getEntries(docId, 0);
|
|
139
|
+
assertLength(walEntries, 2, "Only v4 and v5 should remain");
|
|
140
|
+
assertEqual(walEntries[0].version, 4, "First remaining is v4");
|
|
141
|
+
assertEqual(walEntries[1].version, 5, "Last remaining is v5");
|
|
142
|
+
})
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "snapshot overwrites previous snapshot",
|
|
146
|
+
category: Categories.SNAPSHOT_WAL_COORDINATION,
|
|
147
|
+
run: Effect.gen(function* () {
|
|
148
|
+
const cold = yield* ColdStorageTag;
|
|
149
|
+
const docId = "overwrite-test";
|
|
150
|
+
yield* cold.save(docId, makeSnapshot(1, { old: true }));
|
|
151
|
+
yield* cold.save(docId, makeSnapshot(5, { new: true }));
|
|
152
|
+
const loaded = yield* cold.load(docId);
|
|
153
|
+
assertEqual(loaded.version, 5, "Should have newer version");
|
|
154
|
+
assertEqual(loaded.state, { new: true }, "Should have newer state");
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
const versionVerificationTests = [
|
|
159
|
+
{
|
|
160
|
+
name: "WAL entries are ordered by version",
|
|
161
|
+
category: Categories.VERSION_VERIFICATION,
|
|
162
|
+
run: Effect.gen(function* () {
|
|
163
|
+
const hot = yield* HotStorageTag;
|
|
164
|
+
const docId = "ordering-test";
|
|
165
|
+
yield* hot.append(docId, makeWalEntry(3));
|
|
166
|
+
yield* hot.append(docId, makeWalEntry(1));
|
|
167
|
+
yield* hot.append(docId, makeWalEntry(2));
|
|
168
|
+
const entries = yield* hot.getEntries(docId, 0);
|
|
169
|
+
assertLength(entries, 3, "All entries should be returned");
|
|
170
|
+
assertEqual(entries[0].version, 1, "First should be v1");
|
|
171
|
+
assertEqual(entries[1].version, 2, "Second should be v2");
|
|
172
|
+
assertEqual(entries[2].version, 3, "Third should be v3");
|
|
173
|
+
})
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
name: "getEntries filters by sinceVersion correctly",
|
|
177
|
+
category: Categories.VERSION_VERIFICATION,
|
|
178
|
+
run: Effect.gen(function* () {
|
|
179
|
+
const hot = yield* HotStorageTag;
|
|
180
|
+
const docId = "filter-test";
|
|
181
|
+
for (let i = 1; i <= 10; i++) yield* hot.append(docId, makeWalEntry(i));
|
|
182
|
+
const fromV5 = yield* hot.getEntries(docId, 5);
|
|
183
|
+
const fromV8 = yield* hot.getEntries(docId, 8);
|
|
184
|
+
const fromV10 = yield* hot.getEntries(docId, 10);
|
|
185
|
+
assertLength(fromV5, 5, "v6-v10 = 5 entries");
|
|
186
|
+
assertEqual(fromV5[0].version, 6, "First entry after v5 is v6");
|
|
187
|
+
assertLength(fromV8, 2, "v9-v10 = 2 entries");
|
|
188
|
+
assertEqual(fromV8[0].version, 9, "First entry after v8 is v9");
|
|
189
|
+
assertEmpty(fromV10, "No entries after v10");
|
|
190
|
+
})
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "detect version gap between snapshot and WAL",
|
|
194
|
+
category: Categories.VERSION_VERIFICATION,
|
|
195
|
+
run: Effect.gen(function* () {
|
|
196
|
+
const cold = yield* ColdStorageTag;
|
|
197
|
+
const hot = yield* HotStorageTag;
|
|
198
|
+
const docId = "gap-detection";
|
|
199
|
+
yield* cold.save(docId, makeSnapshot(5));
|
|
200
|
+
yield* hot.append(docId, makeWalEntry(7));
|
|
201
|
+
yield* hot.append(docId, makeWalEntry(8));
|
|
202
|
+
const snapshot = yield* cold.load(docId);
|
|
203
|
+
const walEntries = yield* hot.getEntries(docId, snapshot.version);
|
|
204
|
+
assertEqual(snapshot.version, 5, "Snapshot at v5");
|
|
205
|
+
assertLength(walEntries, 2, "Two WAL entries");
|
|
206
|
+
assertTrue(walEntries[0].version !== snapshot.version + 1, "Should detect gap (v7 != v6)");
|
|
207
|
+
})
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "detect internal WAL gaps",
|
|
211
|
+
category: Categories.VERSION_VERIFICATION,
|
|
212
|
+
run: Effect.gen(function* () {
|
|
213
|
+
const hot = yield* HotStorageTag;
|
|
214
|
+
const docId = "internal-gap";
|
|
215
|
+
yield* hot.append(docId, makeWalEntry(1));
|
|
216
|
+
yield* hot.append(docId, makeWalEntry(2));
|
|
217
|
+
yield* hot.append(docId, makeWalEntry(4));
|
|
218
|
+
yield* hot.append(docId, makeWalEntry(5));
|
|
219
|
+
const entries = yield* hot.getEntries(docId, 0);
|
|
220
|
+
let gapFound = false;
|
|
221
|
+
for (let i = 1; i < entries.length; i++) {
|
|
222
|
+
const prev = entries[i - 1].version;
|
|
223
|
+
if (entries[i].version !== prev + 1) {
|
|
224
|
+
gapFound = true;
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
assertTrue(gapFound, "Should detect internal gap between v2 and v4");
|
|
229
|
+
})
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "no gap when WAL is continuous",
|
|
233
|
+
category: Categories.VERSION_VERIFICATION,
|
|
234
|
+
run: Effect.gen(function* () {
|
|
235
|
+
const cold = yield* ColdStorageTag;
|
|
236
|
+
const hot = yield* HotStorageTag;
|
|
237
|
+
const docId = "no-gap";
|
|
238
|
+
yield* cold.save(docId, makeSnapshot(5));
|
|
239
|
+
yield* hot.append(docId, makeWalEntry(6));
|
|
240
|
+
yield* hot.append(docId, makeWalEntry(7));
|
|
241
|
+
yield* hot.append(docId, makeWalEntry(8));
|
|
242
|
+
const snapshot = yield* cold.load(docId);
|
|
243
|
+
assertTrue(!((yield* hot.getEntries(docId, snapshot.version))[0].version !== snapshot.version + 1), "Should not detect gap (v6 == v6)");
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
];
|
|
247
|
+
const recoveryScenarioTests = [
|
|
248
|
+
{
|
|
249
|
+
name: "full recovery: snapshot + WAL + new transactions",
|
|
250
|
+
category: Categories.RECOVERY_SCENARIOS,
|
|
251
|
+
run: Effect.gen(function* () {
|
|
252
|
+
const cold = yield* ColdStorageTag;
|
|
253
|
+
const hot = yield* HotStorageTag;
|
|
254
|
+
const docId = "full-recovery";
|
|
255
|
+
yield* cold.save(docId, makeSnapshot(3, { count: 3 }));
|
|
256
|
+
yield* hot.append(docId, makeWalEntry(4));
|
|
257
|
+
yield* hot.append(docId, makeWalEntry(5));
|
|
258
|
+
const snapshot = yield* cold.load(docId);
|
|
259
|
+
const walEntries = yield* hot.getEntries(docId, snapshot.version);
|
|
260
|
+
assertEqual(snapshot.version, 3, "Snapshot version");
|
|
261
|
+
assertLength(walEntries, 2, "WAL entries to replay");
|
|
262
|
+
yield* hot.append(docId, makeWalEntry(6));
|
|
263
|
+
const newWal = yield* hot.getEntries(docId, 5);
|
|
264
|
+
assertLength(newWal, 1, "One new entry after recovery");
|
|
265
|
+
assertEqual(newWal[0].version, 6, "New entry is v6");
|
|
266
|
+
})
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: "recovery from only WAL (cold start)",
|
|
270
|
+
category: Categories.RECOVERY_SCENARIOS,
|
|
271
|
+
run: Effect.gen(function* () {
|
|
272
|
+
const cold = yield* ColdStorageTag;
|
|
273
|
+
const hot = yield* HotStorageTag;
|
|
274
|
+
const docId = "cold-start";
|
|
275
|
+
yield* hot.append(docId, makeWalEntry(1));
|
|
276
|
+
yield* hot.append(docId, makeWalEntry(2));
|
|
277
|
+
const snapshot = yield* cold.load(docId);
|
|
278
|
+
const walEntries = yield* hot.getEntries(docId, 0);
|
|
279
|
+
assertUndefined(snapshot, "No snapshot exists");
|
|
280
|
+
assertLength(walEntries, 2, "All WAL entries from beginning");
|
|
281
|
+
})
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: "recovery after truncation failure (WAL has old entries)",
|
|
285
|
+
category: Categories.RECOVERY_SCENARIOS,
|
|
286
|
+
run: Effect.gen(function* () {
|
|
287
|
+
const cold = yield* ColdStorageTag;
|
|
288
|
+
const hot = yield* HotStorageTag;
|
|
289
|
+
const docId = "truncate-failed";
|
|
290
|
+
yield* hot.append(docId, makeWalEntry(3));
|
|
291
|
+
yield* hot.append(docId, makeWalEntry(4));
|
|
292
|
+
yield* hot.append(docId, makeWalEntry(5));
|
|
293
|
+
yield* hot.append(docId, makeWalEntry(6));
|
|
294
|
+
yield* cold.save(docId, makeSnapshot(5));
|
|
295
|
+
const snapshot = yield* cold.load(docId);
|
|
296
|
+
const walEntries = yield* hot.getEntries(docId, snapshot.version);
|
|
297
|
+
assertEqual(snapshot.version, 5, "Snapshot at v5");
|
|
298
|
+
assertLength(walEntries, 1, "Only v6 should be replayed");
|
|
299
|
+
assertEqual(walEntries[0].version, 6, "Entry is v6");
|
|
300
|
+
})
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: "idempotent snapshot save",
|
|
304
|
+
category: Categories.RECOVERY_SCENARIOS,
|
|
305
|
+
run: Effect.gen(function* () {
|
|
306
|
+
const cold = yield* ColdStorageTag;
|
|
307
|
+
const docId = "idempotent";
|
|
308
|
+
const snapshot1 = makeSnapshot(5, { first: true });
|
|
309
|
+
const snapshot2 = makeSnapshot(5, { second: true });
|
|
310
|
+
yield* cold.save(docId, snapshot1);
|
|
311
|
+
yield* cold.save(docId, snapshot2);
|
|
312
|
+
assertEqual((yield* cold.load(docId)).state, { second: true }, "Second save overwrites");
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
];
|
|
316
|
+
const transactionEncodingTests = [
|
|
317
|
+
{
|
|
318
|
+
name: "OperationPath survives full recovery cycle (snapshot + WAL)",
|
|
319
|
+
category: Categories.TRANSACTION_ENCODING,
|
|
320
|
+
run: Effect.gen(function* () {
|
|
321
|
+
const cold = yield* ColdStorageTag;
|
|
322
|
+
const hot = yield* HotStorageTag;
|
|
323
|
+
const docId = "op-path-recovery";
|
|
324
|
+
yield* cold.save(docId, makeSnapshot(3, { count: 3 }));
|
|
325
|
+
yield* hot.append(docId, makeWalEntry(4, "users/0/name", "Alice"));
|
|
326
|
+
yield* hot.append(docId, makeWalEntry(5, "users/1/name", "Bob"));
|
|
327
|
+
const snapshot = yield* cold.load(docId);
|
|
328
|
+
const walEntries = yield* hot.getEntries(docId, snapshot.version);
|
|
329
|
+
assertLength(walEntries, 2, "Should have 2 WAL entries");
|
|
330
|
+
const firstOp = walEntries[0].transaction.ops[0];
|
|
331
|
+
const secondOp = walEntries[1].transaction.ops[0];
|
|
332
|
+
assertTrue(firstOp.path._tag === "OperationPath", "First op path should be OperationPath");
|
|
333
|
+
assertTrue(typeof firstOp.path.toTokens === "function", "First op path should have toTokens method");
|
|
334
|
+
assertEqual(firstOp.path.toTokens(), [
|
|
335
|
+
"users",
|
|
336
|
+
"0",
|
|
337
|
+
"name"
|
|
338
|
+
], "First op path tokens should be correct");
|
|
339
|
+
assertEqual(secondOp.path.toTokens(), [
|
|
340
|
+
"users",
|
|
341
|
+
"1",
|
|
342
|
+
"name"
|
|
343
|
+
], "Second op path tokens should be correct");
|
|
344
|
+
})
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "OperationPath methods work after WAL-only recovery",
|
|
348
|
+
category: Categories.TRANSACTION_ENCODING,
|
|
349
|
+
run: Effect.gen(function* () {
|
|
350
|
+
const cold = yield* ColdStorageTag;
|
|
351
|
+
const hot = yield* HotStorageTag;
|
|
352
|
+
const docId = "op-path-wal-only";
|
|
353
|
+
yield* hot.append(docId, makeWalEntry(1, "config/theme", "dark"));
|
|
354
|
+
yield* hot.append(docId, makeWalEntry(2, "config/language", "en"));
|
|
355
|
+
assertUndefined(yield* cold.load(docId), "No snapshot should exist");
|
|
356
|
+
const walEntries = yield* hot.getEntries(docId, 0);
|
|
357
|
+
assertLength(walEntries, 2, "Should have 2 WAL entries");
|
|
358
|
+
const path = walEntries[0].transaction.ops[0].path;
|
|
359
|
+
assertEqual(path.concat(OperationPath.make("subkey")).toTokens(), [
|
|
360
|
+
"config",
|
|
361
|
+
"theme",
|
|
362
|
+
"subkey"
|
|
363
|
+
], "concat should work");
|
|
364
|
+
assertEqual(path.pop().toTokens(), ["config"], "pop should work");
|
|
365
|
+
assertEqual(path.append("extra").toTokens(), [
|
|
366
|
+
"config",
|
|
367
|
+
"theme",
|
|
368
|
+
"extra"
|
|
369
|
+
], "append should work");
|
|
370
|
+
})
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "transaction encoding survives truncation cycle",
|
|
374
|
+
category: Categories.TRANSACTION_ENCODING,
|
|
375
|
+
run: Effect.gen(function* () {
|
|
376
|
+
const cold = yield* ColdStorageTag;
|
|
377
|
+
const hot = yield* HotStorageTag;
|
|
378
|
+
const docId = "op-path-truncate-cycle";
|
|
379
|
+
for (let i = 1; i <= 5; i++) yield* hot.append(docId, makeWalEntry(i, `path/${i}`, `value${i}`));
|
|
380
|
+
yield* cold.save(docId, makeSnapshot(3));
|
|
381
|
+
yield* hot.truncate(docId, 3);
|
|
382
|
+
const walEntries = yield* hot.getEntries(docId, 0);
|
|
383
|
+
assertLength(walEntries, 2, "Should have versions 4 and 5");
|
|
384
|
+
for (const entry of walEntries) {
|
|
385
|
+
const op = entry.transaction.ops[0];
|
|
386
|
+
assertTrue(op.path._tag === "OperationPath", "Path should be OperationPath after truncation");
|
|
387
|
+
assertTrue(typeof op.path.toTokens === "function", "Path should have toTokens method after truncation");
|
|
388
|
+
}
|
|
389
|
+
assertEqual(walEntries[0].transaction.ops[0].path.toTokens(), ["path", "4"], "Version 4 path should be correct");
|
|
390
|
+
assertEqual(walEntries[1].transaction.ops[0].path.toTokens(), ["path", "5"], "Version 5 path should be correct");
|
|
391
|
+
})
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "complex nested paths survive integration roundtrip",
|
|
395
|
+
category: Categories.TRANSACTION_ENCODING,
|
|
396
|
+
run: Effect.gen(function* () {
|
|
397
|
+
const hot = yield* HotStorageTag;
|
|
398
|
+
const docId = "complex-nested-paths";
|
|
399
|
+
yield* hot.append(docId, makeWalEntry(1, "documents/users/0/profile/settings/notifications", { enabled: true }));
|
|
400
|
+
const entries = yield* hot.getEntries(docId, 0);
|
|
401
|
+
assertLength(entries, 1, "Should have one entry");
|
|
402
|
+
const op = entries[0].transaction.ops[0];
|
|
403
|
+
assertEqual(op.path.toTokens(), [
|
|
404
|
+
"documents",
|
|
405
|
+
"users",
|
|
406
|
+
"0",
|
|
407
|
+
"profile",
|
|
408
|
+
"settings",
|
|
409
|
+
"notifications"
|
|
410
|
+
], "Complex nested path should survive roundtrip");
|
|
411
|
+
assertEqual(op.path.shift().toTokens(), [
|
|
412
|
+
"users",
|
|
413
|
+
"0",
|
|
414
|
+
"profile",
|
|
415
|
+
"settings",
|
|
416
|
+
"notifications"
|
|
417
|
+
], "shift should work on complex path");
|
|
418
|
+
})
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "multiple operations per transaction preserve all OperationPaths",
|
|
422
|
+
category: Categories.TRANSACTION_ENCODING,
|
|
423
|
+
run: Effect.gen(function* () {
|
|
424
|
+
const hot = yield* HotStorageTag;
|
|
425
|
+
const docId = "multi-op-integration";
|
|
426
|
+
const entry = {
|
|
427
|
+
transaction: Transaction.make([
|
|
428
|
+
Operation.fromDefinition(OperationPath.make("users/0"), TestSetDefinition, { name: "Alice" }),
|
|
429
|
+
Operation.fromDefinition(OperationPath.make("users/1"), TestSetDefinition, { name: "Bob" }),
|
|
430
|
+
Operation.fromDefinition(OperationPath.make("meta/count"), TestSetDefinition, 2)
|
|
431
|
+
]),
|
|
432
|
+
version: 1,
|
|
433
|
+
timestamp: Date.now()
|
|
434
|
+
};
|
|
435
|
+
yield* hot.append(docId, entry);
|
|
436
|
+
const entries = yield* hot.getEntries(docId, 0);
|
|
437
|
+
assertLength(entries, 1, "Should have one entry");
|
|
438
|
+
const ops = entries[0].transaction.ops;
|
|
439
|
+
assertEqual(ops.length, 3, "Should have 3 operations");
|
|
440
|
+
assertEqual(ops[0].path.toTokens(), ["users", "0"], "First path correct");
|
|
441
|
+
assertEqual(ops[1].path.toTokens(), ["users", "1"], "Second path correct");
|
|
442
|
+
assertEqual(ops[2].path.toTokens(), ["meta", "count"], "Third path correct");
|
|
443
|
+
for (const op of ops) {
|
|
444
|
+
assertTrue(typeof op.path.concat === "function", "Each path should have concat method");
|
|
445
|
+
assertTrue(typeof op.path.append === "function", "Each path should have append method");
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
}
|
|
449
|
+
];
|
|
450
|
+
/**
|
|
451
|
+
* Generate all integration test cases
|
|
452
|
+
*/
|
|
453
|
+
const makeTests = () => [
|
|
454
|
+
...snapshotWalCoordinationTests,
|
|
455
|
+
...versionVerificationTests,
|
|
456
|
+
...recoveryScenarioTests,
|
|
457
|
+
...transactionEncodingTests
|
|
458
|
+
];
|
|
459
|
+
/**
|
|
460
|
+
* Run all integration tests and collect results
|
|
461
|
+
*/
|
|
462
|
+
const runAll = (layer) => Effect.gen(function* () {
|
|
463
|
+
const tests = makeTests();
|
|
464
|
+
const results = [];
|
|
465
|
+
for (const test of tests) {
|
|
466
|
+
const result = yield* Effect.either(test.run.pipe(Effect.provide(layer)));
|
|
467
|
+
if (result._tag === "Right") results.push({
|
|
468
|
+
name: test.name,
|
|
469
|
+
passed: true
|
|
470
|
+
});
|
|
471
|
+
else results.push({
|
|
472
|
+
name: test.name,
|
|
473
|
+
passed: false,
|
|
474
|
+
error: result.left
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return results;
|
|
478
|
+
});
|
|
479
|
+
const StorageIntegrationTestSuite = {
|
|
480
|
+
Categories,
|
|
481
|
+
makeTests,
|
|
482
|
+
runAll
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
//#endregion
|
|
486
|
+
export { Categories, StorageIntegrationTestSuite };
|
|
487
|
+
//# sourceMappingURL=StorageIntegrationTestSuite.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageIntegrationTestSuite.mjs","names":["snapshotWalCoordinationTests: IntegrationTestCase[]","versionVerificationTests: IntegrationTestCase[]","recoveryScenarioTests: IntegrationTestCase[]","transactionEncodingTests: IntegrationTestCase[]","entry: WalEntry","results: Array<{ name: string; passed: boolean; error?: unknown }>"],"sources":["../../src/testing/StorageIntegrationTestSuite.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect/testing - StorageIntegrationTestSuite\n *\n * Integration tests for verifying Hot/Cold storage coordination.\n * Tests snapshot + WAL replay, failure handling, and version verification.\n *\n * @example\n * ```typescript\n * import { StorageIntegrationTestSuite } from \"@voidhash/mimic-effect/testing\";\n * import { describe, it } from \"vitest\";\n * import { Effect, Layer } from \"effect\";\n * import { ColdStorage, HotStorage } from \"@voidhash/mimic-effect\";\n *\n * describe(\"Storage Integration\", () => {\n * const layer = Layer.mergeAll(\n * ColdStorage.InMemory.make(),\n * HotStorage.InMemory.make()\n * );\n *\n * for (const test of StorageIntegrationTestSuite.makeTests()) {\n * it(test.name, () =>\n * Effect.runPromise(test.run.pipe(Effect.provide(layer)))\n * );\n * }\n * });\n * ```\n */\nimport { Effect, Schema } from \"effect\";\nimport { Transaction, OperationPath, Operation, OperationDefinition } from \"@voidhash/mimic\";\nimport { ColdStorageTag } from \"../ColdStorage\";\nimport { HotStorageTag } from \"../HotStorage\";\nimport { ColdStorageError, HotStorageError } from \"../Errors\";\nimport type { StoredDocument, WalEntry } from \"../Types\";\nimport type { StorageTestCase } from \"./types\";\nimport {\n assertEqual,\n assertTrue,\n assertLength,\n assertEmpty,\n assertDefined,\n assertUndefined,\n} from \"./assertions\";\n\n// =============================================================================\n// Test Categories\n// =============================================================================\n\nexport const Categories = {\n SNAPSHOT_WAL_COORDINATION: \"Snapshot + WAL Coordination\",\n VERSION_VERIFICATION: \"Version Verification\",\n RECOVERY_SCENARIOS: \"Recovery Scenarios\",\n TRANSACTION_ENCODING: \"Transaction Encoding\",\n} as const;\n\n// =============================================================================\n// Test Operation Definitions\n// =============================================================================\n\n/**\n * Test operation definition for creating proper Operation objects in tests.\n */\nconst TestSetDefinition = OperationDefinition.make({\n kind: \"test.set\" as const,\n payload: Schema.Unknown,\n target: Schema.Unknown,\n apply: (payload: unknown) => payload,\n});\n\n// =============================================================================\n// Test Helpers\n// =============================================================================\n\nconst makeSnapshot = (\n version: number,\n state: unknown = { data: `v${version}` }\n): StoredDocument => ({\n state,\n version,\n schemaVersion: 1,\n savedAt: Date.now(),\n});\n\nconst makeWalEntry = (\n version: number,\n pathString: string = \"data\",\n payload: unknown = `v${version}`\n): WalEntry => ({\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(pathString), TestSetDefinition, payload),\n ]),\n version,\n timestamp: Date.now(),\n});\n\n// =============================================================================\n// Test Definitions\n// =============================================================================\n\ntype IntegrationTestCase = StorageTestCase<ColdStorageError | HotStorageError, ColdStorageTag | HotStorageTag>;\n\nconst snapshotWalCoordinationTests: IntegrationTestCase[] = [\n {\n name: \"load empty document returns undefined snapshot and empty WAL\",\n category: Categories.SNAPSHOT_WAL_COORDINATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const snapshot = yield* cold.load(\"empty-doc\");\n const walEntries = yield* hot.getEntries(\"empty-doc\", 0);\n\n assertUndefined(snapshot, \"Snapshot should be undefined for new doc\");\n assertEmpty(walEntries, \"WAL should be empty for new doc\");\n }),\n },\n\n {\n name: \"restore from snapshot only (no WAL)\",\n category: Categories.SNAPSHOT_WAL_COORDINATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"snapshot-only\";\n const snapshot = makeSnapshot(5, { title: \"Hello\" });\n\n yield* cold.save(docId, snapshot);\n\n const loaded = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, 5);\n\n assertDefined(loaded, \"Snapshot should be loaded\");\n assertEqual(loaded!.version, 5, \"Snapshot version should match\");\n assertEqual(loaded!.state, { title: \"Hello\" }, \"Snapshot state should match\");\n assertEmpty(walEntries, \"WAL should be empty after snapshot version\");\n }),\n },\n\n {\n name: \"restore from WAL only (no snapshot)\",\n category: Categories.SNAPSHOT_WAL_COORDINATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"wal-only\";\n\n yield* hot.append(docId, makeWalEntry(1));\n yield* hot.append(docId, makeWalEntry(2));\n yield* hot.append(docId, makeWalEntry(3));\n\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, 0);\n\n assertUndefined(snapshot, \"No snapshot should exist\");\n assertLength(walEntries, 3, \"Should have 3 WAL entries\");\n assertEqual(walEntries[0]!.version, 1, \"First entry should be v1\");\n assertEqual(walEntries[2]!.version, 3, \"Last entry should be v3\");\n }),\n },\n\n {\n name: \"restore from snapshot + WAL replay\",\n category: Categories.SNAPSHOT_WAL_COORDINATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"snapshot-plus-wal\";\n\n // Save snapshot at v5\n yield* cold.save(docId, makeSnapshot(5));\n\n // Add WAL entries for v6, v7, v8\n yield* hot.append(docId, makeWalEntry(6));\n yield* hot.append(docId, makeWalEntry(7));\n yield* hot.append(docId, makeWalEntry(8));\n\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, snapshot!.version);\n\n assertEqual(snapshot!.version, 5, \"Snapshot at v5\");\n assertLength(walEntries, 3, \"3 WAL entries after snapshot\");\n assertEqual(walEntries[0]!.version, 6, \"First WAL entry is v6\");\n assertEqual(walEntries[2]!.version, 8, \"Last WAL entry is v8\");\n }),\n },\n\n {\n name: \"truncate WAL after snapshot\",\n category: Categories.SNAPSHOT_WAL_COORDINATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"truncate-test\";\n\n // Add WAL entries 1-5\n for (let i = 1; i <= 5; i++) {\n yield* hot.append(docId, makeWalEntry(i));\n }\n\n // Save snapshot at v3\n yield* cold.save(docId, makeSnapshot(3));\n\n // Truncate WAL up to v3\n yield* hot.truncate(docId, 3);\n\n const walEntries = yield* hot.getEntries(docId, 0);\n\n assertLength(walEntries, 2, \"Only v4 and v5 should remain\");\n assertEqual(walEntries[0]!.version, 4, \"First remaining is v4\");\n assertEqual(walEntries[1]!.version, 5, \"Last remaining is v5\");\n }),\n },\n\n {\n name: \"snapshot overwrites previous snapshot\",\n category: Categories.SNAPSHOT_WAL_COORDINATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n\n const docId = \"overwrite-test\";\n\n yield* cold.save(docId, makeSnapshot(1, { old: true }));\n yield* cold.save(docId, makeSnapshot(5, { new: true }));\n\n const loaded = yield* cold.load(docId);\n\n assertEqual(loaded!.version, 5, \"Should have newer version\");\n assertEqual(loaded!.state, { new: true }, \"Should have newer state\");\n }),\n },\n];\n\nconst versionVerificationTests: IntegrationTestCase[] = [\n {\n name: \"WAL entries are ordered by version\",\n category: Categories.VERSION_VERIFICATION,\n run: Effect.gen(function* () {\n const hot = yield* HotStorageTag;\n\n const docId = \"ordering-test\";\n\n // Append out of order\n yield* hot.append(docId, makeWalEntry(3));\n yield* hot.append(docId, makeWalEntry(1));\n yield* hot.append(docId, makeWalEntry(2));\n\n const entries = yield* hot.getEntries(docId, 0);\n\n assertLength(entries, 3, \"All entries should be returned\");\n assertEqual(entries[0]!.version, 1, \"First should be v1\");\n assertEqual(entries[1]!.version, 2, \"Second should be v2\");\n assertEqual(entries[2]!.version, 3, \"Third should be v3\");\n }),\n },\n\n {\n name: \"getEntries filters by sinceVersion correctly\",\n category: Categories.VERSION_VERIFICATION,\n run: Effect.gen(function* () {\n const hot = yield* HotStorageTag;\n\n const docId = \"filter-test\";\n\n for (let i = 1; i <= 10; i++) {\n yield* hot.append(docId, makeWalEntry(i));\n }\n\n const fromV5 = yield* hot.getEntries(docId, 5);\n const fromV8 = yield* hot.getEntries(docId, 8);\n const fromV10 = yield* hot.getEntries(docId, 10);\n\n assertLength(fromV5, 5, \"v6-v10 = 5 entries\");\n assertEqual(fromV5[0]!.version, 6, \"First entry after v5 is v6\");\n\n assertLength(fromV8, 2, \"v9-v10 = 2 entries\");\n assertEqual(fromV8[0]!.version, 9, \"First entry after v8 is v9\");\n\n assertEmpty(fromV10, \"No entries after v10\");\n }),\n },\n\n {\n name: \"detect version gap between snapshot and WAL\",\n category: Categories.VERSION_VERIFICATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"gap-detection\";\n\n // Snapshot at v5\n yield* cold.save(docId, makeSnapshot(5));\n\n // WAL starts at v7 (gap: v6 missing)\n yield* hot.append(docId, makeWalEntry(7));\n yield* hot.append(docId, makeWalEntry(8));\n\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, snapshot!.version);\n\n assertEqual(snapshot!.version, 5, \"Snapshot at v5\");\n assertLength(walEntries, 2, \"Two WAL entries\");\n\n // Gap detection: first WAL entry should be v6, but it's v7\n const firstWalVersion = walEntries[0]!.version;\n const expectedFirst = snapshot!.version + 1;\n const hasGap = firstWalVersion !== expectedFirst;\n\n assertTrue(hasGap, \"Should detect gap (v7 != v6)\");\n }),\n },\n\n {\n name: \"detect internal WAL gaps\",\n category: Categories.VERSION_VERIFICATION,\n run: Effect.gen(function* () {\n const hot = yield* HotStorageTag;\n\n const docId = \"internal-gap\";\n\n yield* hot.append(docId, makeWalEntry(1));\n yield* hot.append(docId, makeWalEntry(2));\n // Skip v3\n yield* hot.append(docId, makeWalEntry(4));\n yield* hot.append(docId, makeWalEntry(5));\n\n const entries = yield* hot.getEntries(docId, 0);\n\n // Check for internal gaps\n let gapFound = false;\n for (let i = 1; i < entries.length; i++) {\n const prev = entries[i - 1]!.version;\n const curr = entries[i]!.version;\n if (curr !== prev + 1) {\n gapFound = true;\n break;\n }\n }\n\n assertTrue(gapFound, \"Should detect internal gap between v2 and v4\");\n }),\n },\n\n {\n name: \"no gap when WAL is continuous\",\n category: Categories.VERSION_VERIFICATION,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"no-gap\";\n\n yield* cold.save(docId, makeSnapshot(5));\n yield* hot.append(docId, makeWalEntry(6));\n yield* hot.append(docId, makeWalEntry(7));\n yield* hot.append(docId, makeWalEntry(8));\n\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, snapshot!.version);\n\n const firstWalVersion = walEntries[0]!.version;\n const expectedFirst = snapshot!.version + 1;\n const hasGap = firstWalVersion !== expectedFirst;\n\n assertTrue(!hasGap, \"Should not detect gap (v6 == v6)\");\n }),\n },\n];\n\nconst recoveryScenarioTests: IntegrationTestCase[] = [\n {\n name: \"full recovery: snapshot + WAL + new transactions\",\n category: Categories.RECOVERY_SCENARIOS,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"full-recovery\";\n\n // Initial state: snapshot at v3, WAL v4-v5\n yield* cold.save(docId, makeSnapshot(3, { count: 3 }));\n yield* hot.append(docId, makeWalEntry(4));\n yield* hot.append(docId, makeWalEntry(5));\n\n // \"Recovery\" - load snapshot and WAL\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, snapshot!.version);\n\n assertEqual(snapshot!.version, 3, \"Snapshot version\");\n assertLength(walEntries, 2, \"WAL entries to replay\");\n\n // Simulate new transaction after recovery\n yield* hot.append(docId, makeWalEntry(6));\n\n const newWal = yield* hot.getEntries(docId, 5);\n assertLength(newWal, 1, \"One new entry after recovery\");\n assertEqual(newWal[0]!.version, 6, \"New entry is v6\");\n }),\n },\n\n {\n name: \"recovery from only WAL (cold start)\",\n category: Categories.RECOVERY_SCENARIOS,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"cold-start\";\n\n // Only WAL entries, no snapshot (new document that hasn't been snapshotted)\n yield* hot.append(docId, makeWalEntry(1));\n yield* hot.append(docId, makeWalEntry(2));\n\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, 0);\n\n assertUndefined(snapshot, \"No snapshot exists\");\n assertLength(walEntries, 2, \"All WAL entries from beginning\");\n }),\n },\n\n {\n name: \"recovery after truncation failure (WAL has old entries)\",\n category: Categories.RECOVERY_SCENARIOS,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"truncate-failed\";\n\n // Simulate: snapshot saved at v5, but truncate failed\n // So WAL still has v3, v4, v5, v6\n yield* hot.append(docId, makeWalEntry(3));\n yield* hot.append(docId, makeWalEntry(4));\n yield* hot.append(docId, makeWalEntry(5));\n yield* hot.append(docId, makeWalEntry(6));\n\n yield* cold.save(docId, makeSnapshot(5));\n\n // Recovery should only replay v6\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, snapshot!.version);\n\n assertEqual(snapshot!.version, 5, \"Snapshot at v5\");\n assertLength(walEntries, 1, \"Only v6 should be replayed\");\n assertEqual(walEntries[0]!.version, 6, \"Entry is v6\");\n }),\n },\n\n {\n name: \"idempotent snapshot save\",\n category: Categories.RECOVERY_SCENARIOS,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n\n const docId = \"idempotent\";\n\n const snapshot1 = makeSnapshot(5, { first: true });\n const snapshot2 = makeSnapshot(5, { second: true });\n\n yield* cold.save(docId, snapshot1);\n yield* cold.save(docId, snapshot2);\n\n const loaded = yield* cold.load(docId);\n\n // Last write wins\n assertEqual(loaded!.state, { second: true }, \"Second save overwrites\");\n }),\n },\n];\n\nconst transactionEncodingTests: IntegrationTestCase[] = [\n {\n name: \"OperationPath survives full recovery cycle (snapshot + WAL)\",\n category: Categories.TRANSACTION_ENCODING,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"op-path-recovery\";\n\n // Save snapshot at v3\n yield* cold.save(docId, makeSnapshot(3, { count: 3 }));\n\n // Add WAL entries with proper OperationPath\n yield* hot.append(docId, makeWalEntry(4, \"users/0/name\", \"Alice\"));\n yield* hot.append(docId, makeWalEntry(5, \"users/1/name\", \"Bob\"));\n\n // Simulate recovery\n const snapshot = yield* cold.load(docId);\n const walEntries = yield* hot.getEntries(docId, snapshot!.version);\n\n assertLength(walEntries, 2, \"Should have 2 WAL entries\");\n\n // Verify OperationPath is properly reconstructed\n const firstOp = walEntries[0]!.transaction.ops[0]!;\n const secondOp = walEntries[1]!.transaction.ops[0]!;\n\n assertTrue(\n firstOp.path._tag === \"OperationPath\",\n \"First op path should be OperationPath\"\n );\n assertTrue(\n typeof firstOp.path.toTokens === \"function\",\n \"First op path should have toTokens method\"\n );\n assertEqual(\n firstOp.path.toTokens(),\n [\"users\", \"0\", \"name\"],\n \"First op path tokens should be correct\"\n );\n assertEqual(\n secondOp.path.toTokens(),\n [\"users\", \"1\", \"name\"],\n \"Second op path tokens should be correct\"\n );\n }),\n },\n\n {\n name: \"OperationPath methods work after WAL-only recovery\",\n category: Categories.TRANSACTION_ENCODING,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"op-path-wal-only\";\n\n // No snapshot, only WAL\n yield* hot.append(docId, makeWalEntry(1, \"config/theme\", \"dark\"));\n yield* hot.append(docId, makeWalEntry(2, \"config/language\", \"en\"));\n\n const snapshot = yield* cold.load(docId);\n assertUndefined(snapshot, \"No snapshot should exist\");\n\n const walEntries = yield* hot.getEntries(docId, 0);\n assertLength(walEntries, 2, \"Should have 2 WAL entries\");\n\n // Test OperationPath methods\n const path = walEntries[0]!.transaction.ops[0]!.path;\n\n // Test concat\n const extended = path.concat(OperationPath.make(\"subkey\"));\n assertEqual(\n extended.toTokens(),\n [\"config\", \"theme\", \"subkey\"],\n \"concat should work\"\n );\n\n // Test pop\n const popped = path.pop();\n assertEqual(popped.toTokens(), [\"config\"], \"pop should work\");\n\n // Test append\n const appended = path.append(\"extra\");\n assertEqual(\n appended.toTokens(),\n [\"config\", \"theme\", \"extra\"],\n \"append should work\"\n );\n }),\n },\n\n {\n name: \"transaction encoding survives truncation cycle\",\n category: Categories.TRANSACTION_ENCODING,\n run: Effect.gen(function* () {\n const cold = yield* ColdStorageTag;\n const hot = yield* HotStorageTag;\n\n const docId = \"op-path-truncate-cycle\";\n\n // Add WAL entries 1-5\n for (let i = 1; i <= 5; i++) {\n yield* hot.append(docId, makeWalEntry(i, `path/${i}`, `value${i}`));\n }\n\n // Save snapshot at v3 and truncate\n yield* cold.save(docId, makeSnapshot(3));\n yield* hot.truncate(docId, 3);\n\n // Verify remaining entries have proper OperationPath\n const walEntries = yield* hot.getEntries(docId, 0);\n assertLength(walEntries, 2, \"Should have versions 4 and 5\");\n\n for (const entry of walEntries) {\n const op = entry.transaction.ops[0]!;\n assertTrue(\n op.path._tag === \"OperationPath\",\n \"Path should be OperationPath after truncation\"\n );\n assertTrue(\n typeof op.path.toTokens === \"function\",\n \"Path should have toTokens method after truncation\"\n );\n }\n\n assertEqual(\n walEntries[0]!.transaction.ops[0]!.path.toTokens(),\n [\"path\", \"4\"],\n \"Version 4 path should be correct\"\n );\n assertEqual(\n walEntries[1]!.transaction.ops[0]!.path.toTokens(),\n [\"path\", \"5\"],\n \"Version 5 path should be correct\"\n );\n }),\n },\n\n {\n name: \"complex nested paths survive integration roundtrip\",\n category: Categories.TRANSACTION_ENCODING,\n run: Effect.gen(function* () {\n const hot = yield* HotStorageTag;\n\n const docId = \"complex-nested-paths\";\n\n const complexPath = \"documents/users/0/profile/settings/notifications\";\n yield* hot.append(docId, makeWalEntry(1, complexPath, { enabled: true }));\n\n const entries = yield* hot.getEntries(docId, 0);\n assertLength(entries, 1, \"Should have one entry\");\n\n const op = entries[0]!.transaction.ops[0]!;\n assertEqual(\n op.path.toTokens(),\n [\"documents\", \"users\", \"0\", \"profile\", \"settings\", \"notifications\"],\n \"Complex nested path should survive roundtrip\"\n );\n\n // Test that path operations work on complex paths\n const shifted = op.path.shift();\n assertEqual(\n shifted.toTokens(),\n [\"users\", \"0\", \"profile\", \"settings\", \"notifications\"],\n \"shift should work on complex path\"\n );\n }),\n },\n\n {\n name: \"multiple operations per transaction preserve all OperationPaths\",\n category: Categories.TRANSACTION_ENCODING,\n run: Effect.gen(function* () {\n const hot = yield* HotStorageTag;\n\n const docId = \"multi-op-integration\";\n\n const entry: WalEntry = {\n transaction: Transaction.make([\n Operation.fromDefinition(OperationPath.make(\"users/0\"), TestSetDefinition, { name: \"Alice\" }),\n Operation.fromDefinition(OperationPath.make(\"users/1\"), TestSetDefinition, { name: \"Bob\" }),\n Operation.fromDefinition(OperationPath.make(\"meta/count\"), TestSetDefinition, 2),\n ]),\n version: 1,\n timestamp: Date.now(),\n };\n\n yield* hot.append(docId, entry);\n\n const entries = yield* hot.getEntries(docId, 0);\n assertLength(entries, 1, \"Should have one entry\");\n\n const ops = entries[0]!.transaction.ops;\n assertEqual(ops.length, 3, \"Should have 3 operations\");\n\n // Verify all paths\n assertEqual(ops[0]!.path.toTokens(), [\"users\", \"0\"], \"First path correct\");\n assertEqual(ops[1]!.path.toTokens(), [\"users\", \"1\"], \"Second path correct\");\n assertEqual(ops[2]!.path.toTokens(), [\"meta\", \"count\"], \"Third path correct\");\n\n // Verify all have methods\n for (const op of ops) {\n assertTrue(\n typeof op.path.concat === \"function\",\n \"Each path should have concat method\"\n );\n assertTrue(\n typeof op.path.append === \"function\",\n \"Each path should have append method\"\n );\n }\n }),\n },\n];\n\n// =============================================================================\n// Test Suite Export\n// =============================================================================\n\n/**\n * Generate all integration test cases\n */\nexport const makeTests = (): IntegrationTestCase[] => [\n ...snapshotWalCoordinationTests,\n ...versionVerificationTests,\n ...recoveryScenarioTests,\n ...transactionEncodingTests,\n];\n\n/**\n * Run all integration tests and collect results\n */\nexport const runAll = <R>(\n layer: import(\"effect\").Layer.Layer<ColdStorageTag | HotStorageTag, never, R>\n) =>\n Effect.gen(function* () {\n const tests = makeTests();\n const results: Array<{ name: string; passed: boolean; error?: unknown }> = [];\n\n for (const test of tests) {\n const result = yield* Effect.either(test.run.pipe(Effect.provide(layer)));\n\n if (result._tag === \"Right\") {\n results.push({ name: test.name, passed: true });\n } else {\n results.push({ name: test.name, passed: false, error: result.left });\n }\n }\n\n return results;\n });\n\n// =============================================================================\n// Export Namespace\n// =============================================================================\n\nexport const StorageIntegrationTestSuite = {\n Categories,\n makeTests,\n runAll,\n};\n"],"mappings":"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"}
|