@voidhash/mimic-effect 1.0.0-beta.1 → 1.0.0-beta.11
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 +264 -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 +265 -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 +40 -12
- package/dist/HotStorage.d.cts +21 -1
- package/dist/HotStorage.d.cts.map +1 -1
- package/dist/HotStorage.d.mts +21 -1
- package/dist/HotStorage.d.mts.map +1 -1
- package/dist/HotStorage.mjs +40 -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 +858 -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 +858 -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 +531 -0
- package/src/Errors.ts +15 -1
- package/src/HotStorage.ts +130 -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 +1161 -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
package/src/HotStorage.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Context, Effect, HashMap, Layer, Ref } from "effect";
|
|
7
7
|
import type { WalEntry } from "./Types";
|
|
8
|
-
import { HotStorageError } from "./Errors";
|
|
8
|
+
import { HotStorageError, WalVersionGapError } from "./Errors";
|
|
9
9
|
|
|
10
10
|
// =============================================================================
|
|
11
11
|
// HotStorage Interface
|
|
@@ -27,6 +27,32 @@ export interface HotStorage {
|
|
|
27
27
|
entry: WalEntry
|
|
28
28
|
) => Effect.Effect<void, HotStorageError>;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Append a WAL entry with version gap checking.
|
|
32
|
+
*
|
|
33
|
+
* This is an atomic operation that:
|
|
34
|
+
* 1. Verifies the previous entry has version = expectedVersion - 1
|
|
35
|
+
* (or this is the first entry if expectedVersion === 1, accounting for baseVersion)
|
|
36
|
+
* 2. Appends the entry if check passes
|
|
37
|
+
*
|
|
38
|
+
* Use this for two-phase commit to guarantee WAL ordering at write time.
|
|
39
|
+
*
|
|
40
|
+
* @param documentId - Document ID
|
|
41
|
+
* @param entry - WAL entry to append
|
|
42
|
+
* @param expectedVersion - The version this entry should have (entry.version)
|
|
43
|
+
* @param baseVersion - Optional known snapshot version. When provided, an empty WAL
|
|
44
|
+
* is treated as "at this version" rather than "new document at version 0".
|
|
45
|
+
* This is necessary after truncation or restart to correctly validate
|
|
46
|
+
* that the next entry is baseVersion + 1.
|
|
47
|
+
* @returns Effect that fails with WalVersionGapError if gap detected
|
|
48
|
+
*/
|
|
49
|
+
readonly appendWithCheck: (
|
|
50
|
+
documentId: string,
|
|
51
|
+
entry: WalEntry,
|
|
52
|
+
expectedVersion: number,
|
|
53
|
+
baseVersion?: number
|
|
54
|
+
) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
|
|
55
|
+
|
|
30
56
|
/**
|
|
31
57
|
* Get all WAL entries for a document since a given version.
|
|
32
58
|
* Returns entries with version > sinceVersion, ordered by version.
|
|
@@ -115,20 +141,96 @@ export namespace InMemory {
|
|
|
115
141
|
export const make = (): Layer.Layer<HotStorageTag> =>
|
|
116
142
|
Layer.effect(
|
|
117
143
|
HotStorageTag,
|
|
118
|
-
Effect.
|
|
144
|
+
Effect.fn("hot-storage.in-memory.create")(function* () {
|
|
119
145
|
const store = yield* Ref.make(HashMap.empty<string, WalEntry[]>());
|
|
120
146
|
|
|
121
147
|
return {
|
|
122
|
-
append: (
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
148
|
+
append: Effect.fn("hot-storage.append")(
|
|
149
|
+
function* (documentId: string, entry: WalEntry) {
|
|
150
|
+
yield* Ref.update(store, (map) => {
|
|
151
|
+
const existing = HashMap.get(map, documentId);
|
|
152
|
+
const entries =
|
|
153
|
+
existing._tag === "Some" ? existing.value : [];
|
|
154
|
+
return HashMap.set(map, documentId, [...entries, entry]);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
|
|
159
|
+
appendWithCheck: Effect.fn("hot-storage.append-with-check")(
|
|
160
|
+
function* (
|
|
161
|
+
documentId: string,
|
|
162
|
+
entry: WalEntry,
|
|
163
|
+
expectedVersion: number,
|
|
164
|
+
baseVersion?: number
|
|
165
|
+
) {
|
|
166
|
+
type CheckResult =
|
|
167
|
+
| { type: "ok" }
|
|
168
|
+
| { type: "gap"; lastVersion: number | undefined };
|
|
169
|
+
|
|
170
|
+
// Use Ref.modify for atomic check + update
|
|
171
|
+
const result: CheckResult = yield* Ref.modify(
|
|
172
|
+
store,
|
|
173
|
+
(map): [CheckResult, HashMap.HashMap<string, WalEntry[]>] => {
|
|
174
|
+
const existing = HashMap.get(map, documentId);
|
|
175
|
+
const entries =
|
|
176
|
+
existing._tag === "Some" ? existing.value : [];
|
|
177
|
+
|
|
178
|
+
// Find the highest version in existing entries
|
|
179
|
+
const lastEntryVersion =
|
|
180
|
+
entries.length > 0
|
|
181
|
+
? Math.max(...entries.map((e) => e.version))
|
|
182
|
+
: 0;
|
|
129
183
|
|
|
130
|
-
|
|
131
|
-
|
|
184
|
+
// Effective "last version" is max of entries and baseVersion
|
|
185
|
+
// This handles the case after truncation or restart where
|
|
186
|
+
// WAL is empty but we know the snapshot version
|
|
187
|
+
const effectiveLastVersion =
|
|
188
|
+
baseVersion !== undefined
|
|
189
|
+
? Math.max(lastEntryVersion, baseVersion)
|
|
190
|
+
: lastEntryVersion;
|
|
191
|
+
|
|
192
|
+
// Gap check
|
|
193
|
+
if (expectedVersion === 1) {
|
|
194
|
+
// First entry: should have no entries with version >= 1
|
|
195
|
+
// and baseVersion should be 0 or undefined
|
|
196
|
+
if (effectiveLastVersion >= 1) {
|
|
197
|
+
return [{ type: "gap", lastVersion: effectiveLastVersion }, map];
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
// Not first: effective last version should be expectedVersion - 1
|
|
201
|
+
if (effectiveLastVersion !== expectedVersion - 1) {
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
type: "gap",
|
|
205
|
+
lastVersion: effectiveLastVersion > 0 ? effectiveLastVersion : undefined,
|
|
206
|
+
},
|
|
207
|
+
map,
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// No gap: append and return success
|
|
213
|
+
return [
|
|
214
|
+
{ type: "ok" },
|
|
215
|
+
HashMap.set(map, documentId, [...entries, entry]),
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (result.type === "gap") {
|
|
221
|
+
return yield* Effect.fail(
|
|
222
|
+
new WalVersionGapError({
|
|
223
|
+
documentId,
|
|
224
|
+
expectedVersion,
|
|
225
|
+
actualPreviousVersion: result.lastVersion,
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
),
|
|
231
|
+
|
|
232
|
+
getEntries: Effect.fn("hot-storage.get-entries")(
|
|
233
|
+
function* (documentId: string, sinceVersion: number) {
|
|
132
234
|
const current = yield* Ref.get(store);
|
|
133
235
|
const existing = HashMap.get(current, documentId);
|
|
134
236
|
const entries =
|
|
@@ -136,21 +238,25 @@ export namespace InMemory {
|
|
|
136
238
|
return entries
|
|
137
239
|
.filter((e) => e.version > sinceVersion)
|
|
138
240
|
.sort((a, b) => a.version - b.version);
|
|
139
|
-
}
|
|
241
|
+
}
|
|
242
|
+
),
|
|
140
243
|
|
|
141
|
-
truncate: (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
244
|
+
truncate: Effect.fn("hot-storage.truncate")(
|
|
245
|
+
function* (documentId: string, upToVersion: number) {
|
|
246
|
+
yield* Ref.update(store, (map) => {
|
|
247
|
+
const existing = HashMap.get(map, documentId);
|
|
248
|
+
if (existing._tag === "None") {
|
|
249
|
+
return map;
|
|
250
|
+
}
|
|
251
|
+
const filtered = existing.value.filter(
|
|
252
|
+
(e) => e.version > upToVersion
|
|
253
|
+
);
|
|
254
|
+
return HashMap.set(map, documentId, filtered);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
),
|
|
152
258
|
};
|
|
153
|
-
})
|
|
259
|
+
})()
|
|
154
260
|
);
|
|
155
261
|
}
|
|
156
262
|
|
package/src/Metrics.ts
CHANGED
|
@@ -99,6 +99,11 @@ export const transactionsLatency = Metric.histogram(
|
|
|
99
99
|
*/
|
|
100
100
|
export const storageSnapshots = Metric.counter("mimic.storage.snapshots");
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Snapshots triggered by idle document detection
|
|
104
|
+
*/
|
|
105
|
+
export const storageIdleSnapshots = Metric.counter("mimic.storage.idle_snapshots");
|
|
106
|
+
|
|
102
107
|
/**
|
|
103
108
|
* Snapshot save duration histogram (milliseconds)
|
|
104
109
|
*/
|
|
@@ -116,6 +121,26 @@ export const storageSnapshotLatency = Metric.histogram(
|
|
|
116
121
|
*/
|
|
117
122
|
export const storageWalAppends = Metric.counter("mimic.storage.wal_appends");
|
|
118
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Version gaps detected during WAL replay
|
|
126
|
+
*/
|
|
127
|
+
export const storageVersionGaps = Metric.counter("mimic.storage.version_gaps");
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Failed WAL appends causing transaction rollback
|
|
131
|
+
*/
|
|
132
|
+
export const walAppendFailures = Metric.counter("mimic.storage.wal_append_failures");
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* ColdStorage load failures during document restore
|
|
136
|
+
*/
|
|
137
|
+
export const coldStorageLoadFailures = Metric.counter("mimic.storage.cold_load_failures");
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* HotStorage getEntries failures during document restore
|
|
141
|
+
*/
|
|
142
|
+
export const hotStorageLoadFailures = Metric.counter("mimic.storage.hot_load_failures");
|
|
143
|
+
|
|
119
144
|
// =============================================================================
|
|
120
145
|
// Presence Metrics
|
|
121
146
|
// =============================================================================
|
|
@@ -154,8 +179,13 @@ export const MimicMetrics = {
|
|
|
154
179
|
|
|
155
180
|
// Storage
|
|
156
181
|
storageSnapshots,
|
|
182
|
+
storageIdleSnapshots,
|
|
157
183
|
storageSnapshotLatency,
|
|
158
184
|
storageWalAppends,
|
|
185
|
+
storageVersionGaps,
|
|
186
|
+
walAppendFailures,
|
|
187
|
+
coldStorageLoadFailures,
|
|
188
|
+
hotStorageLoadFailures,
|
|
159
189
|
|
|
160
190
|
// Presence
|
|
161
191
|
presenceUpdates,
|