@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.
Files changed (142) hide show
  1. package/.turbo/turbo-build.log +116 -74
  2. package/dist/ColdStorage.cjs +9 -5
  3. package/dist/ColdStorage.d.cts.map +1 -1
  4. package/dist/ColdStorage.d.mts.map +1 -1
  5. package/dist/ColdStorage.mjs +9 -5
  6. package/dist/ColdStorage.mjs.map +1 -1
  7. package/dist/DocumentInstance.cjs +263 -0
  8. package/dist/DocumentInstance.d.cts +78 -0
  9. package/dist/DocumentInstance.d.cts.map +1 -0
  10. package/dist/DocumentInstance.d.mts +78 -0
  11. package/dist/DocumentInstance.d.mts.map +1 -0
  12. package/dist/DocumentInstance.mjs +264 -0
  13. package/dist/DocumentInstance.mjs.map +1 -0
  14. package/dist/Errors.cjs +10 -1
  15. package/dist/Errors.d.cts +18 -3
  16. package/dist/Errors.d.cts.map +1 -1
  17. package/dist/Errors.d.mts +18 -3
  18. package/dist/Errors.d.mts.map +1 -1
  19. package/dist/Errors.mjs +9 -1
  20. package/dist/Errors.mjs.map +1 -1
  21. package/dist/HotStorage.cjs +39 -12
  22. package/dist/HotStorage.d.cts +17 -1
  23. package/dist/HotStorage.d.cts.map +1 -1
  24. package/dist/HotStorage.d.mts +17 -1
  25. package/dist/HotStorage.d.mts.map +1 -1
  26. package/dist/HotStorage.mjs +39 -12
  27. package/dist/HotStorage.mjs.map +1 -1
  28. package/dist/Metrics.cjs +29 -1
  29. package/dist/Metrics.d.cts +5 -0
  30. package/dist/Metrics.d.cts.map +1 -1
  31. package/dist/Metrics.d.mts +5 -0
  32. package/dist/Metrics.d.mts.map +1 -1
  33. package/dist/Metrics.mjs +26 -1
  34. package/dist/Metrics.mjs.map +1 -1
  35. package/dist/MimicClusterServerEngine.cjs +44 -139
  36. package/dist/MimicClusterServerEngine.d.cts.map +1 -1
  37. package/dist/MimicClusterServerEngine.d.mts +1 -1
  38. package/dist/MimicClusterServerEngine.d.mts.map +1 -1
  39. package/dist/MimicClusterServerEngine.mjs +46 -141
  40. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  41. package/dist/MimicServer.cjs +20 -20
  42. package/dist/MimicServer.d.cts.map +1 -1
  43. package/dist/MimicServer.d.mts.map +1 -1
  44. package/dist/MimicServer.mjs +20 -20
  45. package/dist/MimicServer.mjs.map +1 -1
  46. package/dist/MimicServerEngine.cjs +92 -11
  47. package/dist/MimicServerEngine.d.cts +12 -4
  48. package/dist/MimicServerEngine.d.cts.map +1 -1
  49. package/dist/MimicServerEngine.d.mts +12 -4
  50. package/dist/MimicServerEngine.d.mts.map +1 -1
  51. package/dist/MimicServerEngine.mjs +94 -13
  52. package/dist/MimicServerEngine.mjs.map +1 -1
  53. package/dist/PresenceManager.cjs +5 -5
  54. package/dist/PresenceManager.d.cts.map +1 -1
  55. package/dist/PresenceManager.d.mts.map +1 -1
  56. package/dist/PresenceManager.mjs +5 -5
  57. package/dist/PresenceManager.mjs.map +1 -1
  58. package/dist/Protocol.d.cts +1 -1
  59. package/dist/Protocol.d.mts +1 -1
  60. package/dist/Types.d.cts +9 -2
  61. package/dist/Types.d.cts.map +1 -1
  62. package/dist/Types.d.mts +9 -2
  63. package/dist/Types.d.mts.map +1 -1
  64. package/dist/index.cjs +5 -6
  65. package/dist/index.d.cts +3 -3
  66. package/dist/index.d.mts +3 -3
  67. package/dist/index.mjs +3 -3
  68. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  69. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  70. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  71. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  72. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  73. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  74. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  75. package/dist/testing/FailingStorage.cjs +162 -0
  76. package/dist/testing/FailingStorage.d.cts +43 -0
  77. package/dist/testing/FailingStorage.d.cts.map +1 -0
  78. package/dist/testing/FailingStorage.d.mts +43 -0
  79. package/dist/testing/FailingStorage.d.mts.map +1 -0
  80. package/dist/testing/FailingStorage.mjs +163 -0
  81. package/dist/testing/FailingStorage.mjs.map +1 -0
  82. package/dist/testing/HotStorageTestSuite.cjs +820 -0
  83. package/dist/testing/HotStorageTestSuite.d.cts +42 -0
  84. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  85. package/dist/testing/HotStorageTestSuite.d.mts +42 -0
  86. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  87. package/dist/testing/HotStorageTestSuite.mjs +820 -0
  88. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  89. package/dist/testing/StorageIntegrationTestSuite.cjs +487 -0
  90. package/dist/testing/StorageIntegrationTestSuite.d.cts +37 -0
  91. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  92. package/dist/testing/StorageIntegrationTestSuite.d.mts +37 -0
  93. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  94. package/dist/testing/StorageIntegrationTestSuite.mjs +487 -0
  95. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  96. package/dist/testing/assertions.cjs +117 -0
  97. package/dist/testing/assertions.mjs +112 -0
  98. package/dist/testing/assertions.mjs.map +1 -0
  99. package/dist/testing/index.cjs +14 -0
  100. package/dist/testing/index.d.cts +6 -0
  101. package/dist/testing/index.d.mts +6 -0
  102. package/dist/testing/index.mjs +7 -0
  103. package/dist/testing/types.cjs +15 -0
  104. package/dist/testing/types.d.cts +90 -0
  105. package/dist/testing/types.d.cts.map +1 -0
  106. package/dist/testing/types.d.mts +90 -0
  107. package/dist/testing/types.d.mts.map +1 -0
  108. package/dist/testing/types.mjs +16 -0
  109. package/dist/testing/types.mjs.map +1 -0
  110. package/package.json +8 -3
  111. package/src/ColdStorage.ts +21 -12
  112. package/src/DocumentInstance.ts +527 -0
  113. package/src/Errors.ts +15 -1
  114. package/src/HotStorage.ts +115 -24
  115. package/src/Metrics.ts +30 -0
  116. package/src/MimicClusterServerEngine.ts +120 -275
  117. package/src/MimicServer.ts +83 -75
  118. package/src/MimicServerEngine.ts +230 -30
  119. package/src/PresenceManager.ts +44 -34
  120. package/src/Types.ts +9 -2
  121. package/src/index.ts +5 -35
  122. package/src/testing/ColdStorageTestSuite.ts +589 -0
  123. package/src/testing/FailingStorage.ts +338 -0
  124. package/src/testing/HotStorageTestSuite.ts +1105 -0
  125. package/src/testing/StorageIntegrationTestSuite.ts +736 -0
  126. package/src/testing/assertions.ts +188 -0
  127. package/src/testing/index.ts +83 -0
  128. package/src/testing/types.ts +100 -0
  129. package/tests/ColdStorage.test.ts +8 -120
  130. package/tests/DocumentInstance.test.ts +669 -0
  131. package/tests/HotStorage.test.ts +7 -126
  132. package/tests/StorageIntegration.test.ts +259 -0
  133. package/tsdown.config.ts +1 -1
  134. package/dist/DocumentManager.cjs +0 -229
  135. package/dist/DocumentManager.d.cts +0 -59
  136. package/dist/DocumentManager.d.cts.map +0 -1
  137. package/dist/DocumentManager.d.mts +0 -59
  138. package/dist/DocumentManager.d.mts.map +0 -1
  139. package/dist/DocumentManager.mjs +0 -227
  140. package/dist/DocumentManager.mjs.map +0 -1
  141. package/src/DocumentManager.ts +0 -506
  142. 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,27 @@ 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)
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
+ * @returns Effect that fails with WalVersionGapError if gap detected
44
+ */
45
+ readonly appendWithCheck: (
46
+ documentId: string,
47
+ entry: WalEntry,
48
+ expectedVersion: number
49
+ ) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
50
+
30
51
  /**
31
52
  * Get all WAL entries for a document since a given version.
32
53
  * Returns entries with version > sinceVersion, ordered by version.
@@ -115,20 +136,86 @@ export namespace InMemory {
115
136
  export const make = (): Layer.Layer<HotStorageTag> =>
116
137
  Layer.effect(
117
138
  HotStorageTag,
118
- Effect.gen(function* () {
139
+ Effect.fn("hot-storage.in-memory.create")(function* () {
119
140
  const store = yield* Ref.make(HashMap.empty<string, WalEntry[]>());
120
141
 
121
142
  return {
122
- append: (documentId, entry) =>
123
- Ref.update(store, (map) => {
124
- const existing = HashMap.get(map, documentId);
125
- const entries =
126
- existing._tag === "Some" ? existing.value : [];
127
- return HashMap.set(map, documentId, [...entries, entry]);
128
- }),
143
+ append: Effect.fn("hot-storage.append")(
144
+ function* (documentId: string, entry: WalEntry) {
145
+ yield* Ref.update(store, (map) => {
146
+ const existing = HashMap.get(map, documentId);
147
+ const entries =
148
+ existing._tag === "Some" ? existing.value : [];
149
+ return HashMap.set(map, documentId, [...entries, entry]);
150
+ });
151
+ }
152
+ ),
153
+
154
+ appendWithCheck: Effect.fn("hot-storage.append-with-check")(
155
+ function* (
156
+ documentId: string,
157
+ entry: WalEntry,
158
+ expectedVersion: number
159
+ ) {
160
+ type CheckResult =
161
+ | { type: "ok" }
162
+ | { type: "gap"; lastVersion: number | undefined };
163
+
164
+ // Use Ref.modify for atomic check + update
165
+ const result: CheckResult = yield* Ref.modify(
166
+ store,
167
+ (map): [CheckResult, HashMap.HashMap<string, WalEntry[]>] => {
168
+ const existing = HashMap.get(map, documentId);
169
+ const entries =
170
+ existing._tag === "Some" ? existing.value : [];
129
171
 
130
- getEntries: (documentId, sinceVersion) =>
131
- Effect.gen(function* () {
172
+ // Find the highest version in existing entries
173
+ const lastVersion =
174
+ entries.length > 0
175
+ ? Math.max(...entries.map((e) => e.version))
176
+ : 0;
177
+
178
+ // Gap check
179
+ if (expectedVersion === 1) {
180
+ // First entry: should have no entries with version >= 1
181
+ if (lastVersion >= 1) {
182
+ return [{ type: "gap", lastVersion }, map];
183
+ }
184
+ } else {
185
+ // Not first: last entry should have version = expectedVersion - 1
186
+ if (lastVersion !== expectedVersion - 1) {
187
+ return [
188
+ {
189
+ type: "gap",
190
+ lastVersion: lastVersion > 0 ? lastVersion : undefined,
191
+ },
192
+ map,
193
+ ];
194
+ }
195
+ }
196
+
197
+ // No gap: append and return success
198
+ return [
199
+ { type: "ok" },
200
+ HashMap.set(map, documentId, [...entries, entry]),
201
+ ];
202
+ }
203
+ );
204
+
205
+ if (result.type === "gap") {
206
+ return yield* Effect.fail(
207
+ new WalVersionGapError({
208
+ documentId,
209
+ expectedVersion,
210
+ actualPreviousVersion: result.lastVersion,
211
+ })
212
+ );
213
+ }
214
+ }
215
+ ),
216
+
217
+ getEntries: Effect.fn("hot-storage.get-entries")(
218
+ function* (documentId: string, sinceVersion: number) {
132
219
  const current = yield* Ref.get(store);
133
220
  const existing = HashMap.get(current, documentId);
134
221
  const entries =
@@ -136,21 +223,25 @@ export namespace InMemory {
136
223
  return entries
137
224
  .filter((e) => e.version > sinceVersion)
138
225
  .sort((a, b) => a.version - b.version);
139
- }),
226
+ }
227
+ ),
140
228
 
141
- truncate: (documentId, upToVersion) =>
142
- Ref.update(store, (map) => {
143
- const existing = HashMap.get(map, documentId);
144
- if (existing._tag === "None") {
145
- return map;
146
- }
147
- const filtered = existing.value.filter(
148
- (e) => e.version > upToVersion
149
- );
150
- return HashMap.set(map, documentId, filtered);
151
- }),
229
+ truncate: Effect.fn("hot-storage.truncate")(
230
+ function* (documentId: string, upToVersion: number) {
231
+ yield* Ref.update(store, (map) => {
232
+ const existing = HashMap.get(map, documentId);
233
+ if (existing._tag === "None") {
234
+ return map;
235
+ }
236
+ const filtered = existing.value.filter(
237
+ (e) => e.version > upToVersion
238
+ );
239
+ return HashMap.set(map, documentId, filtered);
240
+ });
241
+ }
242
+ ),
152
243
  };
153
- })
244
+ })()
154
245
  );
155
246
  }
156
247
 
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,