@voidhash/mimic-effect 1.0.0-beta.1 → 1.0.0-beta.3

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 (111) hide show
  1. package/.turbo/turbo-build.log +102 -60
  2. package/dist/DocumentManager.cjs +109 -39
  3. package/dist/DocumentManager.d.cts +12 -4
  4. package/dist/DocumentManager.d.cts.map +1 -1
  5. package/dist/DocumentManager.d.mts +12 -4
  6. package/dist/DocumentManager.d.mts.map +1 -1
  7. package/dist/DocumentManager.mjs +110 -40
  8. package/dist/DocumentManager.mjs.map +1 -1
  9. package/dist/Errors.cjs +10 -1
  10. package/dist/Errors.d.cts +18 -3
  11. package/dist/Errors.d.cts.map +1 -1
  12. package/dist/Errors.d.mts +18 -3
  13. package/dist/Errors.d.mts.map +1 -1
  14. package/dist/Errors.mjs +9 -1
  15. package/dist/Errors.mjs.map +1 -1
  16. package/dist/HotStorage.cjs +23 -0
  17. package/dist/HotStorage.d.cts +17 -1
  18. package/dist/HotStorage.d.cts.map +1 -1
  19. package/dist/HotStorage.d.mts +17 -1
  20. package/dist/HotStorage.d.mts.map +1 -1
  21. package/dist/HotStorage.mjs +23 -0
  22. package/dist/HotStorage.mjs.map +1 -1
  23. package/dist/Metrics.cjs +23 -1
  24. package/dist/Metrics.d.cts +4 -0
  25. package/dist/Metrics.d.cts.map +1 -1
  26. package/dist/Metrics.d.mts +4 -0
  27. package/dist/Metrics.d.mts.map +1 -1
  28. package/dist/Metrics.mjs +21 -1
  29. package/dist/Metrics.mjs.map +1 -1
  30. package/dist/MimicClusterServerEngine.cjs +114 -36
  31. package/dist/MimicClusterServerEngine.d.cts.map +1 -1
  32. package/dist/MimicClusterServerEngine.d.mts +1 -1
  33. package/dist/MimicClusterServerEngine.d.mts.map +1 -1
  34. package/dist/MimicClusterServerEngine.mjs +119 -41
  35. package/dist/MimicClusterServerEngine.mjs.map +1 -1
  36. package/dist/MimicServer.cjs +1 -1
  37. package/dist/MimicServer.d.cts +1 -1
  38. package/dist/MimicServer.d.cts.map +1 -1
  39. package/dist/MimicServer.d.mts +1 -1
  40. package/dist/MimicServer.d.mts.map +1 -1
  41. package/dist/MimicServer.mjs +1 -1
  42. package/dist/MimicServerEngine.d.cts +7 -4
  43. package/dist/MimicServerEngine.d.cts.map +1 -1
  44. package/dist/MimicServerEngine.d.mts +7 -4
  45. package/dist/MimicServerEngine.d.mts.map +1 -1
  46. package/dist/MimicServerEngine.mjs.map +1 -1
  47. package/dist/index.cjs +3 -2
  48. package/dist/index.d.cts +2 -2
  49. package/dist/index.d.mts +2 -2
  50. package/dist/index.mjs +2 -2
  51. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  52. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  53. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  54. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  55. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  56. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  57. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  58. package/dist/testing/FailingStorage.cjs +135 -0
  59. package/dist/testing/FailingStorage.d.cts +43 -0
  60. package/dist/testing/FailingStorage.d.cts.map +1 -0
  61. package/dist/testing/FailingStorage.d.mts +43 -0
  62. package/dist/testing/FailingStorage.d.mts.map +1 -0
  63. package/dist/testing/FailingStorage.mjs +136 -0
  64. package/dist/testing/FailingStorage.mjs.map +1 -0
  65. package/dist/testing/HotStorageTestSuite.cjs +585 -0
  66. package/dist/testing/HotStorageTestSuite.d.cts +40 -0
  67. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  68. package/dist/testing/HotStorageTestSuite.d.mts +40 -0
  69. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  70. package/dist/testing/HotStorageTestSuite.mjs +585 -0
  71. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  72. package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
  73. package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
  74. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  75. package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
  76. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  77. package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
  78. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  79. package/dist/testing/assertions.cjs +114 -0
  80. package/dist/testing/assertions.mjs +109 -0
  81. package/dist/testing/assertions.mjs.map +1 -0
  82. package/dist/testing/index.cjs +14 -0
  83. package/dist/testing/index.d.cts +6 -0
  84. package/dist/testing/index.d.mts +6 -0
  85. package/dist/testing/index.mjs +7 -0
  86. package/dist/testing/types.cjs +15 -0
  87. package/dist/testing/types.d.cts +90 -0
  88. package/dist/testing/types.d.cts.map +1 -0
  89. package/dist/testing/types.d.mts +90 -0
  90. package/dist/testing/types.d.mts.map +1 -0
  91. package/dist/testing/types.mjs +16 -0
  92. package/dist/testing/types.mjs.map +1 -0
  93. package/package.json +8 -3
  94. package/src/DocumentManager.ts +195 -87
  95. package/src/Errors.ts +15 -1
  96. package/src/HotStorage.ts +75 -1
  97. package/src/Metrics.ts +24 -0
  98. package/src/MimicClusterServerEngine.ts +178 -56
  99. package/src/MimicServerEngine.ts +7 -3
  100. package/src/index.ts +2 -31
  101. package/src/testing/ColdStorageTestSuite.ts +589 -0
  102. package/src/testing/FailingStorage.ts +286 -0
  103. package/src/testing/HotStorageTestSuite.ts +762 -0
  104. package/src/testing/StorageIntegrationTestSuite.ts +504 -0
  105. package/src/testing/assertions.ts +181 -0
  106. package/src/testing/index.ts +83 -0
  107. package/src/testing/types.ts +100 -0
  108. package/tests/ColdStorage.test.ts +8 -120
  109. package/tests/HotStorage.test.ts +7 -126
  110. package/tests/StorageIntegration.test.ts +259 -0
  111. package/tsdown.config.ts +1 -1
@@ -1,7 +1,8 @@
1
1
  import { ColdStorageTag } from "./ColdStorage.mjs";
2
2
  import { HotStorageTag } from "./HotStorage.mjs";
3
- import { documentsActive, documentsCreated, documentsEvicted, documentsRestored, storageSnapshotLatency, storageSnapshots, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected } from "./Metrics.mjs";
3
+ import { documentsActive, documentsCreated, documentsEvicted, documentsRestored, storageSnapshotLatency, storageSnapshots, storageVersionGaps, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected, walAppendFailures } from "./Metrics.mjs";
4
4
  import { Context, Duration, Effect, HashMap, Layer, Metric, PubSub, Ref, Schedule, Stream } from "effect";
5
+ import { Document } from "@voidhash/mimic";
5
6
  import { ServerDocument } from "@voidhash/mimic/server";
6
7
 
7
8
  //#region src/DocumentManager.ts
@@ -45,7 +46,7 @@ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
45
46
  * Restore a document from storage
46
47
  */
47
48
  const restoreDocument = (documentId) => Effect.gen(function* () {
48
- const storedDoc = yield* Effect.catchAll(coldStorage.load(documentId), () => Effect.succeed(void 0));
49
+ const storedDoc = yield* coldStorage.load(documentId);
49
50
  let initialState;
50
51
  let initialVersion = 0;
51
52
  if (storedDoc) {
@@ -77,7 +78,29 @@ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
77
78
  }));
78
79
  }
79
80
  });
80
- const walEntries = yield* Effect.catchAll(hotStorage.getEntries(documentId, initialVersion), () => Effect.succeed([]));
81
+ const walEntries = yield* hotStorage.getEntries(documentId, initialVersion);
82
+ if (walEntries.length > 0) {
83
+ const firstWalVersion = walEntries[0].version;
84
+ const expectedFirst = initialVersion + 1;
85
+ if (firstWalVersion !== expectedFirst) {
86
+ yield* Effect.logWarning("WAL version gap detected", {
87
+ documentId,
88
+ snapshotVersion: initialVersion,
89
+ firstWalVersion,
90
+ expectedFirst
91
+ });
92
+ yield* Metric.increment(storageVersionGaps);
93
+ }
94
+ for (let i = 1; i < walEntries.length; i++) {
95
+ const prev = walEntries[i - 1].version;
96
+ const curr = walEntries[i].version;
97
+ if (curr !== prev + 1) yield* Effect.logWarning("WAL internal gap detected", {
98
+ documentId,
99
+ previousVersion: prev,
100
+ currentVersion: curr
101
+ });
102
+ }
103
+ }
81
104
  for (const entry of walEntries) {
82
105
  const result = document.submit(entry.transaction);
83
106
  if (!result.success) yield* Effect.logWarning("Skipping corrupted WAL entry", {
@@ -114,33 +137,47 @@ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
114
137
  return instance;
115
138
  });
116
139
  /**
117
- * Save a snapshot to ColdStorage and truncate WAL
140
+ * Save a snapshot to ColdStorage derived from WAL entries.
141
+ * This ensures snapshots are always based on durable WAL data.
142
+ * Idempotent: skips save if already snapshotted at target version.
143
+ * Truncate failures are non-fatal and will be retried on next snapshot.
118
144
  */
119
- const saveSnapshot = (documentId, instance) => Effect.gen(function* () {
120
- const state = instance.document.get();
121
- const version = instance.document.getVersion();
122
- if (state === void 0) return;
145
+ const saveSnapshot = (documentId, instance, targetVersion) => Effect.gen(function* () {
146
+ var _baseSnapshot$version;
147
+ if (targetVersion <= (yield* Ref.get(instance.lastSnapshotVersion))) return;
148
+ const snapshotStartTime = Date.now();
149
+ const baseSnapshot = yield* coldStorage.load(documentId);
150
+ const baseVersion = (_baseSnapshot$version = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.version) !== null && _baseSnapshot$version !== void 0 ? _baseSnapshot$version : 0;
151
+ const baseState = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.state;
152
+ const relevantEntries = (yield* hotStorage.getEntries(documentId, baseVersion)).filter((e) => e.version <= targetVersion);
153
+ if (relevantEntries.length === 0 && !baseSnapshot) return;
154
+ let snapshotState = baseState;
155
+ for (const entry of relevantEntries) {
156
+ const tempDoc = Document.make(config.schema, { initialState: snapshotState });
157
+ tempDoc.apply(entry.transaction.ops);
158
+ snapshotState = tempDoc.get();
159
+ }
160
+ if (snapshotState === void 0) return;
161
+ const snapshotVersion = relevantEntries.length > 0 ? relevantEntries[relevantEntries.length - 1].version : baseVersion;
162
+ if (snapshotVersion <= (yield* Ref.get(instance.lastSnapshotVersion))) return;
123
163
  const storedDoc = {
124
- state,
125
- version,
164
+ state: snapshotState,
165
+ version: snapshotVersion,
126
166
  schemaVersion: SCHEMA_VERSION,
127
167
  savedAt: Date.now()
128
168
  };
129
- const snapshotStartTime = Date.now();
130
- yield* Effect.catchAll(coldStorage.save(documentId, storedDoc), (e) => Effect.logError("Failed to save snapshot", {
131
- documentId,
132
- error: e
133
- }));
169
+ yield* coldStorage.save(documentId, storedDoc);
134
170
  const snapshotDuration = Date.now() - snapshotStartTime;
135
171
  yield* Metric.increment(storageSnapshots);
136
172
  yield* Metric.update(storageSnapshotLatency, snapshotDuration);
137
- yield* Effect.catchAll(hotStorage.truncate(documentId, version), (e) => Effect.logError("Failed to truncate WAL", {
173
+ yield* Ref.set(instance.lastSnapshotVersion, snapshotVersion);
174
+ yield* Ref.set(instance.lastSnapshotTime, Date.now());
175
+ yield* Ref.set(instance.transactionsSinceSnapshot, 0);
176
+ yield* Effect.catchAll(hotStorage.truncate(documentId, snapshotVersion), (e) => Effect.logWarning("WAL truncate failed - will retry on next snapshot", {
138
177
  documentId,
178
+ version: snapshotVersion,
139
179
  error: e
140
180
  }));
141
- yield* Ref.set(instance.lastSnapshotVersion, version);
142
- yield* Ref.set(instance.lastSnapshotTime, Date.now());
143
- yield* Ref.set(instance.transactionsSinceSnapshot, 0);
144
181
  });
145
182
  /**
146
183
  * Check if snapshot should be triggered
@@ -149,13 +186,14 @@ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
149
186
  const txCount = yield* Ref.get(instance.transactionsSinceSnapshot);
150
187
  const lastTime = yield* Ref.get(instance.lastSnapshotTime);
151
188
  const now = Date.now();
189
+ const currentVersion = instance.document.getVersion();
152
190
  const intervalMs = Duration.toMillis(config.snapshot.interval);
153
191
  if (txCount >= config.snapshot.transactionThreshold) {
154
- yield* saveSnapshot(documentId, instance);
192
+ yield* saveSnapshot(documentId, instance, currentVersion);
155
193
  return;
156
194
  }
157
195
  if (now - lastTime >= intervalMs) {
158
- yield* saveSnapshot(documentId, instance);
196
+ yield* saveSnapshot(documentId, instance, currentVersion);
159
197
  return;
160
198
  }
161
199
  });
@@ -165,7 +203,11 @@ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
165
203
  const now = Date.now();
166
204
  const maxIdleMs = Duration.toMillis(config.maxIdleTime);
167
205
  for (const [documentId, instance] of current) if (now - (yield* Ref.get(instance.lastActivityTime)) >= maxIdleMs) {
168
- yield* saveSnapshot(documentId, instance);
206
+ const currentVersion = instance.document.getVersion();
207
+ yield* Effect.catchAll(saveSnapshot(documentId, instance, currentVersion), (e) => Effect.logError("Failed to save snapshot during eviction", {
208
+ documentId,
209
+ error: e
210
+ }));
169
211
  yield* Ref.update(store, (map) => HashMap.remove(map, documentId));
170
212
  yield* Metric.increment(documentsEvicted);
171
213
  yield* Metric.incrementBy(documentsActive, -1);
@@ -175,32 +217,60 @@ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
175
217
  });
176
218
  yield* Effect.addFinalizer(() => Effect.gen(function* () {
177
219
  const current = yield* Ref.get(store);
178
- for (const [documentId, instance] of current) yield* saveSnapshot(documentId, instance);
220
+ for (const [documentId, instance] of current) {
221
+ const currentVersion = instance.document.getVersion();
222
+ yield* Effect.catchAll(saveSnapshot(documentId, instance, currentVersion), (e) => Effect.logError("Failed to save snapshot during shutdown", {
223
+ documentId,
224
+ error: e
225
+ }));
226
+ }
179
227
  yield* Effect.logInfo("DocumentManager shutdown complete");
180
228
  }));
181
229
  return {
182
230
  submit: (documentId, transaction) => Effect.gen(function* () {
183
231
  const instance = yield* getOrCreateDocument(documentId);
184
232
  const submitStartTime = Date.now();
185
- const result = instance.document.submit(transaction);
186
- const latency = Date.now() - submitStartTime;
187
- yield* Metric.update(transactionsLatency, latency);
188
- if (result.success) {
189
- yield* Metric.increment(transactionsProcessed);
190
- const walEntry = {
191
- transaction,
192
- version: result.version,
193
- timestamp: Date.now()
233
+ const validation = instance.document.validate(transaction);
234
+ if (!validation.valid) {
235
+ yield* Metric.increment(transactionsRejected);
236
+ const latency$1 = Date.now() - submitStartTime;
237
+ yield* Metric.update(transactionsLatency, latency$1);
238
+ return {
239
+ success: false,
240
+ reason: validation.reason
194
241
  };
195
- yield* Effect.catchAll(hotStorage.append(documentId, walEntry), (e) => Effect.logError("Failed to append to WAL", {
242
+ }
243
+ const walEntry = {
244
+ transaction,
245
+ version: validation.nextVersion,
246
+ timestamp: Date.now()
247
+ };
248
+ const appendResult = yield* Effect.either(hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion));
249
+ if (appendResult._tag === "Left") {
250
+ yield* Effect.logError("WAL append failed", {
196
251
  documentId,
197
- error: e
198
- }));
199
- yield* Metric.increment(storageWalAppends);
200
- yield* Ref.update(instance.transactionsSinceSnapshot, (n) => n + 1);
201
- yield* checkSnapshotTriggers(documentId, instance);
202
- } else yield* Metric.increment(transactionsRejected);
203
- return result;
252
+ version: validation.nextVersion,
253
+ error: appendResult.left
254
+ });
255
+ yield* Metric.increment(walAppendFailures);
256
+ const latency$1 = Date.now() - submitStartTime;
257
+ yield* Metric.update(transactionsLatency, latency$1);
258
+ return {
259
+ success: false,
260
+ reason: "Storage unavailable. Please retry."
261
+ };
262
+ }
263
+ instance.document.apply(transaction);
264
+ const latency = Date.now() - submitStartTime;
265
+ yield* Metric.update(transactionsLatency, latency);
266
+ yield* Metric.increment(transactionsProcessed);
267
+ yield* Metric.increment(storageWalAppends);
268
+ yield* Ref.update(instance.transactionsSinceSnapshot, (n) => n + 1);
269
+ yield* checkSnapshotTriggers(documentId, instance);
270
+ return {
271
+ success: true,
272
+ version: validation.nextVersion
273
+ };
204
274
  }),
205
275
  getSnapshot: (documentId) => Effect.gen(function* () {
206
276
  return (yield* getOrCreateDocument(documentId)).document.getSnapshot();
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentManager.mjs","names":["layer: Layer.Layer<\n DocumentManagerTag,\n never,\n ColdStorageTag | HotStorageTag | DocumentManagerConfigTag\n>","initialState: Primitive.InferSetInput<typeof config.schema> | undefined","instance: DocumentInstance<typeof config.schema>","Metrics.documentsRestored","Metrics.documentsCreated","Metrics.documentsActive","storedDoc: StoredDocument","Metrics.storageSnapshots","Metrics.storageSnapshotLatency","Metrics.documentsEvicted","Metrics.transactionsLatency","Metrics.transactionsProcessed","walEntry: WalEntry","Metrics.storageWalAppends","Metrics.transactionsRejected"],"sources":["../src/DocumentManager.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - DocumentManager\n *\n * Internal service for managing document lifecycle, including:\n * - Document creation and restoration\n * - Transaction processing\n * - WAL management\n * - Snapshot scheduling\n * - Idle document GC\n */\nimport {\n Context,\n Duration,\n Effect,\n Fiber,\n HashMap,\n Layer,\n Metric,\n PubSub,\n Ref,\n Schedule,\n Scope,\n Stream,\n} from \"effect\";\nimport { Primitive, Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\nimport type {\n Initial,\n ResolvedConfig,\n StoredDocument,\n WalEntry,\n} from \"./Types\";\nimport type { SnapshotMessage, ServerBroadcast } from \"./Protocol\";\nimport { ColdStorageTag, type ColdStorage } from \"./ColdStorage\";\nimport { HotStorageTag, type HotStorage } from \"./HotStorage\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// Submit Result Types\n// =============================================================================\n\n/**\n * Result of submitting a transaction\n */\nexport type SubmitResult =\n | { readonly success: true; readonly version: number }\n | { readonly success: false; readonly reason: string };\n\n// =============================================================================\n// DocumentManager Interface\n// =============================================================================\n\n/**\n * Internal service for managing document lifecycle.\n */\nexport interface DocumentManager {\n /**\n * Submit a transaction to a document.\n */\n readonly submit: (\n documentId: string,\n transaction: Transaction.Transaction\n ) => Effect.Effect<SubmitResult>;\n\n /**\n * Get a snapshot of a document.\n */\n readonly getSnapshot: (documentId: string) => Effect.Effect<SnapshotMessage>;\n\n /**\n * Subscribe to broadcasts for a document.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<ServerBroadcast>, never, Scope.Scope>;\n\n /**\n * Touch a document to update its last activity time.\n * Call this on any client activity to prevent idle GC.\n */\n readonly touch: (documentId: string) => Effect.Effect<void>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for DocumentManager service\n */\nexport class DocumentManagerTag extends Context.Tag(\n \"@voidhash/mimic-effect/DocumentManager\"\n)<DocumentManagerTag, DocumentManager>() {}\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\n/**\n * Document instance state\n */\ninterface DocumentInstance<TSchema extends Primitive.AnyPrimitive> {\n /** The underlying ServerDocument */\n readonly document: ServerDocument.ServerDocument<TSchema>;\n /** PubSub for broadcasting messages */\n readonly pubsub: PubSub.PubSub<ServerBroadcast>;\n /** Version at last snapshot */\n readonly lastSnapshotVersion: Ref.Ref<number>;\n /** Timestamp of last snapshot (ms) */\n readonly lastSnapshotTime: Ref.Ref<number>;\n /** Transactions since last snapshot */\n readonly transactionsSinceSnapshot: Ref.Ref<number>;\n /** Last activity timestamp (ms) */\n readonly lastActivityTime: Ref.Ref<number>;\n}\n\n// =============================================================================\n// Config Context Tag\n// =============================================================================\n\n/**\n * Context tag for DocumentManager configuration\n */\nexport class DocumentManagerConfigTag extends Context.Tag(\n \"@voidhash/mimic-effect/DocumentManagerConfig\"\n)<DocumentManagerConfigTag, ResolvedConfig<Primitive.AnyPrimitive>>() {}\n\n// =============================================================================\n// Layer Implementation\n// =============================================================================\n\n/**\n * Create the DocumentManager layer.\n * Requires ColdStorage, HotStorage, and DocumentManagerConfig.\n */\nexport const layer: Layer.Layer<\n DocumentManagerTag,\n never,\n ColdStorageTag | HotStorageTag | DocumentManagerConfigTag\n> = Layer.scoped(\n DocumentManagerTag,\n Effect.gen(function* () {\n const coldStorage = yield* ColdStorageTag;\n const hotStorage = yield* HotStorageTag;\n const config = yield* DocumentManagerConfigTag;\n\n // Store: documentId -> DocumentInstance\n const store = yield* Ref.make(\n HashMap.empty<string, DocumentInstance<Primitive.AnyPrimitive>>()\n );\n\n // Current schema version (hard-coded to 1 for now)\n const SCHEMA_VERSION = 1;\n\n /**\n * Compute initial state for a new document\n */\n const computeInitialState = (\n documentId: string\n ): Effect.Effect<Primitive.InferSetInput<typeof config.schema> | undefined> => {\n if (config.initial === undefined) {\n return Effect.succeed(undefined);\n }\n\n // Check if it's a function or static value\n if (typeof config.initial === \"function\") {\n return (config.initial as (ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<typeof config.schema>>)({ documentId });\n }\n\n return Effect.succeed(config.initial as Primitive.InferSetInput<typeof config.schema>);\n };\n\n /**\n * Restore a document from storage\n */\n const restoreDocument = (\n documentId: string\n ): Effect.Effect<DocumentInstance<typeof config.schema>> =>\n Effect.gen(function* () {\n // 1. Load snapshot from ColdStorage\n const storedDoc = yield* Effect.catchAll(\n coldStorage.load(documentId),\n () => Effect.succeed(undefined)\n );\n\n let initialState: Primitive.InferSetInput<typeof config.schema> | undefined;\n let initialVersion = 0;\n\n if (storedDoc) {\n // Use stored state\n initialState = storedDoc.state as Primitive.InferSetInput<typeof config.schema>;\n initialVersion = storedDoc.version;\n } else {\n // Compute initial state\n initialState = yield* computeInitialState(documentId);\n }\n\n // 2. Create PubSub for broadcasting\n const pubsub = yield* PubSub.unbounded<ServerBroadcast>();\n\n // 3. Create refs for tracking\n const lastSnapshotVersion = yield* Ref.make(initialVersion);\n const lastSnapshotTime = yield* Ref.make(Date.now());\n const transactionsSinceSnapshot = yield* Ref.make(0);\n const lastActivityTime = yield* Ref.make(Date.now());\n\n // 4. Create ServerDocument with callbacks\n const document = ServerDocument.make({\n schema: config.schema,\n initialState,\n initialVersion,\n maxTransactionHistory: config.maxTransactionHistory,\n onBroadcast: (message: ServerDocument.TransactionMessage) => {\n // This is called synchronously by ServerDocument\n // We need to publish to PubSub\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"transaction\",\n transaction: message.transaction,\n version: message.version,\n })\n );\n },\n onRejection: (transactionId: string, reason: string) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"error\",\n transactionId,\n reason,\n })\n );\n },\n });\n\n // 5. Load and replay WAL entries\n const walEntries = yield* Effect.catchAll(\n hotStorage.getEntries(documentId, initialVersion),\n () => Effect.succeed([] as WalEntry[])\n );\n\n for (const entry of walEntries) {\n const result = document.submit(entry.transaction);\n if (!result.success) {\n yield* Effect.logWarning(\"Skipping corrupted WAL entry\", {\n documentId,\n version: entry.version,\n reason: result.reason,\n });\n }\n }\n\n const instance: DocumentInstance<typeof config.schema> = {\n document,\n pubsub,\n lastSnapshotVersion,\n lastSnapshotTime,\n transactionsSinceSnapshot,\n lastActivityTime,\n };\n\n // Track metrics - determine if restored or created\n if (storedDoc) {\n yield* Metric.increment(Metrics.documentsRestored);\n } else {\n yield* Metric.increment(Metrics.documentsCreated);\n }\n yield* Metric.incrementBy(Metrics.documentsActive, 1);\n\n return instance;\n });\n\n /**\n * Get or create a document instance\n */\n const getOrCreateDocument = (\n documentId: string\n ): Effect.Effect<DocumentInstance<typeof config.schema>> =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n\n if (existing._tag === \"Some\") {\n // Update activity time\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n return existing.value as DocumentInstance<typeof config.schema>;\n }\n\n // Restore document\n const instance = yield* restoreDocument(documentId);\n\n // Store it\n yield* Ref.update(store, (map) =>\n HashMap.set(map, documentId, instance)\n );\n\n return instance;\n });\n\n /**\n * Save a snapshot to ColdStorage and truncate WAL\n */\n const saveSnapshot = (\n documentId: string,\n instance: DocumentInstance<typeof config.schema>\n ): Effect.Effect<void> =>\n Effect.gen(function* () {\n const state = instance.document.get();\n const version = instance.document.getVersion();\n\n if (state === undefined) {\n return;\n }\n\n const storedDoc: StoredDocument = {\n state,\n version,\n schemaVersion: SCHEMA_VERSION,\n savedAt: Date.now(),\n };\n\n const snapshotStartTime = Date.now();\n\n // Save to ColdStorage\n yield* Effect.catchAll(coldStorage.save(documentId, storedDoc), (e) =>\n Effect.logError(\"Failed to save snapshot\", { documentId, error: e })\n );\n\n // Track snapshot metrics\n const snapshotDuration = Date.now() - snapshotStartTime;\n yield* Metric.increment(Metrics.storageSnapshots);\n yield* Metric.update(Metrics.storageSnapshotLatency, snapshotDuration);\n\n // Truncate WAL\n yield* Effect.catchAll(hotStorage.truncate(documentId, version), (e) =>\n Effect.logError(\"Failed to truncate WAL\", { documentId, error: e })\n );\n\n // Update tracking\n yield* Ref.set(instance.lastSnapshotVersion, version);\n yield* Ref.set(instance.lastSnapshotTime, Date.now());\n yield* Ref.set(instance.transactionsSinceSnapshot, 0);\n });\n\n /**\n * Check if snapshot should be triggered\n */\n const checkSnapshotTriggers = (\n documentId: string,\n instance: DocumentInstance<typeof config.schema>\n ): Effect.Effect<void> =>\n Effect.gen(function* () {\n const txCount = yield* Ref.get(instance.transactionsSinceSnapshot);\n const lastTime = yield* Ref.get(instance.lastSnapshotTime);\n const now = Date.now();\n\n const intervalMs = Duration.toMillis(config.snapshot.interval);\n const threshold = config.snapshot.transactionThreshold;\n\n // Check transaction threshold\n if (txCount >= threshold) {\n yield* saveSnapshot(documentId, instance);\n return;\n }\n\n // Check time interval\n if (now - lastTime >= intervalMs) {\n yield* saveSnapshot(documentId, instance);\n return;\n }\n });\n\n /**\n * Start background GC fiber\n */\n const startGCFiber = Effect.gen(function* () {\n const gcLoop = Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const now = Date.now();\n const maxIdleMs = Duration.toMillis(config.maxIdleTime);\n\n for (const [documentId, instance] of current) {\n const lastActivity = yield* Ref.get(instance.lastActivityTime);\n if (now - lastActivity >= maxIdleMs) {\n // Save final snapshot before eviction\n yield* saveSnapshot(documentId, instance);\n\n // Remove from store\n yield* Ref.update(store, (map) => HashMap.remove(map, documentId));\n\n // Track eviction metrics\n yield* Metric.increment(Metrics.documentsEvicted);\n yield* Metric.incrementBy(Metrics.documentsActive, -1);\n\n yield* Effect.logInfo(\"Document evicted due to idle timeout\", {\n documentId,\n });\n }\n }\n });\n\n // Run GC every minute\n yield* gcLoop.pipe(\n Effect.repeat(Schedule.spaced(\"1 minute\")),\n Effect.fork\n );\n });\n\n // Start GC fiber\n yield* startGCFiber;\n\n // Cleanup on shutdown\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n for (const [documentId, instance] of current) {\n yield* saveSnapshot(documentId, instance);\n }\n yield* Effect.logInfo(\"DocumentManager shutdown complete\");\n })\n );\n\n return {\n submit: (documentId, transaction) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n const submitStartTime = Date.now();\n\n // Submit to ServerDocument\n const result = instance.document.submit(transaction);\n\n // Track latency\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n if (result.success) {\n // Track success\n yield* Metric.increment(Metrics.transactionsProcessed);\n\n // Append to WAL\n const walEntry: WalEntry = {\n transaction,\n version: result.version,\n timestamp: Date.now(),\n };\n\n yield* Effect.catchAll(\n hotStorage.append(documentId, walEntry),\n (e) =>\n Effect.logError(\"Failed to append to WAL\", {\n documentId,\n error: e,\n })\n );\n\n // Track WAL append\n yield* Metric.increment(Metrics.storageWalAppends);\n\n // Increment transaction count\n yield* Ref.update(\n instance.transactionsSinceSnapshot,\n (n) => n + 1\n );\n\n // Check snapshot triggers\n yield* checkSnapshotTriggers(documentId, instance);\n } else {\n // Track rejection\n yield* Metric.increment(Metrics.transactionsRejected);\n }\n\n return result;\n }),\n\n getSnapshot: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return instance.document.getSnapshot();\n }),\n\n subscribe: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return Stream.fromPubSub(instance.pubsub);\n }),\n\n touch: (documentId) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n }\n }),\n };\n })\n);\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const DocumentManager = {\n Tag: DocumentManagerTag,\n ConfigTag: DocumentManagerConfigTag,\n layer,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"DocumentManager.mjs","names":["initialState: Primitive.InferSetInput<typeof config.schema> | undefined","Metrics.storageVersionGaps","instance: DocumentInstance<typeof config.schema>","Metrics.documentsRestored","Metrics.documentsCreated","Metrics.documentsActive","snapshotState: Primitive.InferState<typeof config.schema> | undefined","storedDoc: StoredDocument","Metrics.storageSnapshots","Metrics.storageSnapshotLatency","Metrics.documentsEvicted","Metrics.transactionsRejected","latency","Metrics.transactionsLatency","walEntry: WalEntry","Metrics.walAppendFailures","Metrics.transactionsProcessed","Metrics.storageWalAppends"],"sources":["../src/DocumentManager.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - DocumentManager\n *\n * Internal service for managing document lifecycle, including:\n * - Document creation and restoration\n * - Transaction processing\n * - WAL management\n * - Snapshot scheduling\n * - Idle document GC\n */\nimport {\n Context,\n Duration,\n Effect,\n HashMap,\n Layer,\n Metric,\n PubSub,\n Ref,\n Schedule,\n Scope,\n Stream,\n} from \"effect\";\nimport { Document, Primitive, Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\nimport type {\n ResolvedConfig,\n StoredDocument,\n WalEntry,\n} from \"./Types\";\nimport type { SnapshotMessage, ServerBroadcast } from \"./Protocol\";\nimport { ColdStorageTag } from \"./ColdStorage\";\nimport { HotStorageTag } from \"./HotStorage\";\nimport { ColdStorageError, HotStorageError } from \"./Errors\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// Submit Result Types\n// =============================================================================\n\n/**\n * Result of submitting a transaction\n */\nexport type SubmitResult =\n | { readonly success: true; readonly version: number }\n | { readonly success: false; readonly reason: string };\n\n// =============================================================================\n// DocumentManager Interface\n// =============================================================================\n\n/**\n * Error type for DocumentManager operations\n */\nexport type DocumentManagerError = ColdStorageError | HotStorageError;\n\n/**\n * Internal service for managing document lifecycle.\n */\nexport interface DocumentManager {\n /**\n * Submit a transaction to a document.\n * May fail with ColdStorageError or HotStorageError if storage is unavailable.\n */\n readonly submit: (\n documentId: string,\n transaction: Transaction.Transaction\n ) => Effect.Effect<SubmitResult, DocumentManagerError>;\n\n /**\n * Get a snapshot of a document.\n * May fail with ColdStorageError or HotStorageError if storage is unavailable.\n */\n readonly getSnapshot: (documentId: string) => Effect.Effect<SnapshotMessage, DocumentManagerError>;\n\n /**\n * Subscribe to broadcasts for a document.\n * May fail with ColdStorageError or HotStorageError if storage is unavailable.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<Stream.Stream<ServerBroadcast>, DocumentManagerError, Scope.Scope>;\n\n /**\n * Touch a document to update its last activity time.\n * Call this on any client activity to prevent idle GC.\n */\n readonly touch: (documentId: string) => Effect.Effect<void>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for DocumentManager service\n */\nexport class DocumentManagerTag extends Context.Tag(\n \"@voidhash/mimic-effect/DocumentManager\"\n)<DocumentManagerTag, DocumentManager>() {}\n\n// =============================================================================\n// Internal Types\n// =============================================================================\n\n/**\n * Document instance state\n */\ninterface DocumentInstance<TSchema extends Primitive.AnyPrimitive> {\n /** The underlying ServerDocument */\n readonly document: ServerDocument.ServerDocument<TSchema>;\n /** PubSub for broadcasting messages */\n readonly pubsub: PubSub.PubSub<ServerBroadcast>;\n /** Version at last snapshot */\n readonly lastSnapshotVersion: Ref.Ref<number>;\n /** Timestamp of last snapshot (ms) */\n readonly lastSnapshotTime: Ref.Ref<number>;\n /** Transactions since last snapshot */\n readonly transactionsSinceSnapshot: Ref.Ref<number>;\n /** Last activity timestamp (ms) */\n readonly lastActivityTime: Ref.Ref<number>;\n}\n\n// =============================================================================\n// Config Context Tag\n// =============================================================================\n\n/**\n * Context tag for DocumentManager configuration\n */\nexport class DocumentManagerConfigTag extends Context.Tag(\n \"@voidhash/mimic-effect/DocumentManagerConfig\"\n)<DocumentManagerConfigTag, ResolvedConfig<Primitive.AnyPrimitive>>() {}\n\n// =============================================================================\n// Layer Implementation\n// =============================================================================\n\n/**\n * Create the DocumentManager layer.\n * Requires ColdStorage, HotStorage, and DocumentManagerConfig.\n */\nexport const layer = Layer.scoped(\n DocumentManagerTag,\n Effect.gen(function* () {\n const coldStorage = yield* ColdStorageTag;\n const hotStorage = yield* HotStorageTag;\n const config = yield* DocumentManagerConfigTag;\n\n // Store: documentId -> DocumentInstance\n const store = yield* Ref.make(\n HashMap.empty<string, DocumentInstance<Primitive.AnyPrimitive>>()\n );\n\n // Current schema version (hard-coded to 1 for now)\n const SCHEMA_VERSION = 1;\n\n /**\n * Compute initial state for a new document\n */\n const computeInitialState = (\n documentId: string\n ): Effect.Effect<Primitive.InferSetInput<typeof config.schema> | undefined> => {\n if (config.initial === undefined) {\n return Effect.succeed(undefined);\n }\n\n // Check if it's a function or static value\n if (typeof config.initial === \"function\") {\n return (config.initial as (ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<typeof config.schema>>)({ documentId });\n }\n\n return Effect.succeed(config.initial as Primitive.InferSetInput<typeof config.schema>);\n };\n\n /**\n * Restore a document from storage\n */\n const restoreDocument = (\n documentId: string\n ): Effect.Effect<DocumentInstance<typeof config.schema>, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n // 1. Load snapshot from ColdStorage (errors propagate - do not silently fallback)\n const storedDoc = yield* coldStorage.load(documentId);\n\n let initialState: Primitive.InferSetInput<typeof config.schema> | undefined;\n let initialVersion = 0;\n\n if (storedDoc) {\n // Use stored state\n initialState = storedDoc.state as Primitive.InferSetInput<typeof config.schema>;\n initialVersion = storedDoc.version;\n } else {\n // Compute initial state\n initialState = yield* computeInitialState(documentId);\n }\n\n // 2. Create PubSub for broadcasting\n const pubsub = yield* PubSub.unbounded<ServerBroadcast>();\n\n // 3. Create refs for tracking\n const lastSnapshotVersion = yield* Ref.make(initialVersion);\n const lastSnapshotTime = yield* Ref.make(Date.now());\n const transactionsSinceSnapshot = yield* Ref.make(0);\n const lastActivityTime = yield* Ref.make(Date.now());\n\n // 4. Create ServerDocument with callbacks\n const document = ServerDocument.make({\n schema: config.schema,\n initialState,\n initialVersion,\n maxTransactionHistory: config.maxTransactionHistory,\n onBroadcast: (message: ServerDocument.TransactionMessage) => {\n // This is called synchronously by ServerDocument\n // We need to publish to PubSub\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"transaction\",\n transaction: message.transaction,\n version: message.version,\n })\n );\n },\n onRejection: (transactionId: string, reason: string) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"error\",\n transactionId,\n reason,\n })\n );\n },\n });\n\n // 5. Load WAL entries (errors propagate - do not silently fallback)\n const walEntries = yield* hotStorage.getEntries(documentId, initialVersion);\n\n // 6. Verify WAL continuity (warning only, non-blocking)\n if (walEntries.length > 0) {\n const firstWalVersion = walEntries[0]!.version;\n const expectedFirst = initialVersion + 1;\n\n if (firstWalVersion !== expectedFirst) {\n yield* Effect.logWarning(\"WAL version gap detected\", {\n documentId,\n snapshotVersion: initialVersion,\n firstWalVersion,\n expectedFirst,\n });\n yield* Metric.increment(Metrics.storageVersionGaps);\n }\n\n // Check internal gaps\n for (let i = 1; i < walEntries.length; i++) {\n const prev = walEntries[i - 1]!.version;\n const curr = walEntries[i]!.version;\n if (curr !== prev + 1) {\n yield* Effect.logWarning(\"WAL internal gap detected\", {\n documentId,\n previousVersion: prev,\n currentVersion: curr,\n });\n }\n }\n }\n\n // 7. Replay WAL entries\n for (const entry of walEntries) {\n const result = document.submit(entry.transaction);\n if (!result.success) {\n yield* Effect.logWarning(\"Skipping corrupted WAL entry\", {\n documentId,\n version: entry.version,\n reason: result.reason,\n });\n }\n }\n\n const instance: DocumentInstance<typeof config.schema> = {\n document,\n pubsub,\n lastSnapshotVersion,\n lastSnapshotTime,\n transactionsSinceSnapshot,\n lastActivityTime,\n };\n\n // Track metrics - determine if restored or created\n if (storedDoc) {\n yield* Metric.increment(Metrics.documentsRestored);\n } else {\n yield* Metric.increment(Metrics.documentsCreated);\n }\n yield* Metric.incrementBy(Metrics.documentsActive, 1);\n\n return instance;\n });\n\n /**\n * Get or create a document instance\n */\n const getOrCreateDocument = (\n documentId: string\n ): Effect.Effect<DocumentInstance<typeof config.schema>, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n\n if (existing._tag === \"Some\") {\n // Update activity time\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n return existing.value as DocumentInstance<typeof config.schema>;\n }\n\n // Restore document\n const instance = yield* restoreDocument(documentId);\n\n // Store it\n yield* Ref.update(store, (map) =>\n HashMap.set(map, documentId, instance)\n );\n\n return instance;\n });\n\n /**\n * Save a snapshot to ColdStorage derived from WAL entries.\n * This ensures snapshots are always based on durable WAL data.\n * Idempotent: skips save if already snapshotted at target version.\n * Truncate failures are non-fatal and will be retried on next snapshot.\n */\n const saveSnapshot = (\n documentId: string,\n instance: DocumentInstance<typeof config.schema>,\n targetVersion: number\n ): Effect.Effect<void, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n const lastSnapshotVersion = yield* Ref.get(instance.lastSnapshotVersion);\n\n // Idempotency check: skip if already snapshotted at this version\n if (targetVersion <= lastSnapshotVersion) {\n return;\n }\n\n const snapshotStartTime = Date.now();\n\n // Load base snapshot from cold storage\n const baseSnapshot = yield* coldStorage.load(documentId);\n const baseVersion = baseSnapshot?.version ?? 0;\n const baseState = baseSnapshot?.state as Primitive.InferState<typeof config.schema> | undefined;\n\n // Load WAL entries from base to target\n const walEntries = yield* hotStorage.getEntries(documentId, baseVersion);\n const relevantEntries = walEntries.filter(e => e.version <= targetVersion);\n\n if (relevantEntries.length === 0 && !baseSnapshot) {\n // Nothing to snapshot\n return;\n }\n\n // Rebuild state by replaying WAL on base\n let snapshotState: Primitive.InferState<typeof config.schema> | undefined = baseState;\n for (const entry of relevantEntries) {\n // Create a temporary document to apply the transaction\n const tempDoc = Document.make(config.schema, { initialState: snapshotState });\n tempDoc.apply(entry.transaction.ops);\n snapshotState = tempDoc.get();\n }\n\n if (snapshotState === undefined) {\n return;\n }\n\n const snapshotVersion = relevantEntries.length > 0\n ? relevantEntries[relevantEntries.length - 1]!.version\n : baseVersion;\n\n // Re-check before saving (in case another snapshot completed while we were working)\n // This prevents a slower snapshot from overwriting a more recent one\n const currentLastSnapshot = yield* Ref.get(instance.lastSnapshotVersion);\n if (snapshotVersion <= currentLastSnapshot) {\n return;\n }\n\n const storedDoc: StoredDocument = {\n state: snapshotState,\n version: snapshotVersion,\n schemaVersion: SCHEMA_VERSION,\n savedAt: Date.now(),\n };\n\n // Save to ColdStorage - let errors propagate\n yield* coldStorage.save(documentId, storedDoc);\n\n // Track snapshot metrics\n const snapshotDuration = Date.now() - snapshotStartTime;\n yield* Metric.increment(Metrics.storageSnapshots);\n yield* Metric.update(Metrics.storageSnapshotLatency, snapshotDuration);\n\n // Update tracking BEFORE truncate (for idempotency on retry)\n yield* Ref.set(instance.lastSnapshotVersion, snapshotVersion);\n yield* Ref.set(instance.lastSnapshotTime, Date.now());\n yield* Ref.set(instance.transactionsSinceSnapshot, 0);\n\n // Truncate WAL - non-fatal, will be retried on next snapshot\n yield* Effect.catchAll(hotStorage.truncate(documentId, snapshotVersion), (e) =>\n Effect.logWarning(\"WAL truncate failed - will retry on next snapshot\", {\n documentId,\n version: snapshotVersion,\n error: e,\n })\n );\n });\n\n /**\n * Check if snapshot should be triggered\n */\n const checkSnapshotTriggers = (\n documentId: string,\n instance: DocumentInstance<typeof config.schema>\n ): Effect.Effect<void, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n const txCount = yield* Ref.get(instance.transactionsSinceSnapshot);\n const lastTime = yield* Ref.get(instance.lastSnapshotTime);\n const now = Date.now();\n const currentVersion = instance.document.getVersion();\n\n const intervalMs = Duration.toMillis(config.snapshot.interval);\n const threshold = config.snapshot.transactionThreshold;\n\n // Check transaction threshold\n if (txCount >= threshold) {\n yield* saveSnapshot(documentId, instance, currentVersion);\n return;\n }\n\n // Check time interval\n if (now - lastTime >= intervalMs) {\n yield* saveSnapshot(documentId, instance, currentVersion);\n return;\n }\n });\n\n /**\n * Start background GC fiber\n */\n const startGCFiber = Effect.gen(function* () {\n const gcLoop = Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const now = Date.now();\n const maxIdleMs = Duration.toMillis(config.maxIdleTime);\n\n for (const [documentId, instance] of current) {\n const lastActivity = yield* Ref.get(instance.lastActivityTime);\n if (now - lastActivity >= maxIdleMs) {\n // Save final snapshot before eviction (best effort)\n const currentVersion = instance.document.getVersion();\n yield* Effect.catchAll(saveSnapshot(documentId, instance, currentVersion), (e) =>\n Effect.logError(\"Failed to save snapshot during eviction\", {\n documentId,\n error: e,\n })\n );\n\n // Remove from store\n yield* Ref.update(store, (map) => HashMap.remove(map, documentId));\n\n // Track eviction metrics\n yield* Metric.increment(Metrics.documentsEvicted);\n yield* Metric.incrementBy(Metrics.documentsActive, -1);\n\n yield* Effect.logInfo(\"Document evicted due to idle timeout\", {\n documentId,\n });\n }\n }\n });\n\n // Run GC every minute\n yield* gcLoop.pipe(\n Effect.repeat(Schedule.spaced(\"1 minute\")),\n Effect.fork\n );\n });\n\n // Start GC fiber\n yield* startGCFiber;\n\n // Cleanup on shutdown\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n for (const [documentId, instance] of current) {\n // Best effort save - don't fail shutdown if storage is unavailable\n const currentVersion = instance.document.getVersion();\n yield* Effect.catchAll(saveSnapshot(documentId, instance, currentVersion), (e) =>\n Effect.logError(\"Failed to save snapshot during shutdown\", {\n documentId,\n error: e,\n })\n );\n }\n yield* Effect.logInfo(\"DocumentManager shutdown complete\");\n })\n );\n\n return {\n submit: (documentId, transaction) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n const submitStartTime = Date.now();\n\n // Phase 1: Validate (no side effects)\n const validation = instance.document.validate(transaction);\n\n if (!validation.valid) {\n // Track rejection\n yield* Metric.increment(Metrics.transactionsRejected);\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n return {\n success: false as const,\n reason: validation.reason,\n };\n }\n\n // Phase 2: Append to WAL with gap check (BEFORE state mutation)\n const walEntry: WalEntry = {\n transaction,\n version: validation.nextVersion,\n timestamp: Date.now(),\n };\n\n const appendResult = yield* Effect.either(\n hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion)\n );\n\n if (appendResult._tag === \"Left\") {\n // WAL append failed - do NOT apply, state unchanged\n yield* Effect.logError(\"WAL append failed\", {\n documentId,\n version: validation.nextVersion,\n error: appendResult.left,\n });\n yield* Metric.increment(Metrics.walAppendFailures);\n\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n\n // Return failure - client must retry\n return {\n success: false as const,\n reason: \"Storage unavailable. Please retry.\",\n };\n }\n\n // Phase 3: Apply (state mutation + broadcast)\n instance.document.apply(transaction);\n\n // Track metrics\n const latency = Date.now() - submitStartTime;\n yield* Metric.update(Metrics.transactionsLatency, latency);\n yield* Metric.increment(Metrics.transactionsProcessed);\n yield* Metric.increment(Metrics.storageWalAppends);\n\n // Increment transaction count\n yield* Ref.update(\n instance.transactionsSinceSnapshot,\n (n) => n + 1\n );\n\n // Check snapshot triggers\n yield* checkSnapshotTriggers(documentId, instance);\n\n return {\n success: true as const,\n version: validation.nextVersion,\n };\n }),\n\n getSnapshot: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return instance.document.getSnapshot();\n }),\n\n subscribe: (documentId) =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n return Stream.fromPubSub(instance.pubsub);\n }),\n\n touch: (documentId) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n yield* Ref.set(existing.value.lastActivityTime, Date.now());\n }\n }),\n };\n })\n);\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const DocumentManager = {\n Tag: DocumentManagerTag,\n ConfigTag: DocumentManagerConfigTag,\n layer,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiGA,IAAa,qBAAb,cAAwC,QAAQ,IAC9C,yCACD,EAAuC,CAAC;;;;AA+BzC,IAAa,2BAAb,cAA8C,QAAQ,IACpD,+CACD,EAAoE,CAAC;;;;;AAUtE,MAAa,QAAQ,MAAM,OACzB,oBACA,OAAO,IAAI,aAAa;CACtB,MAAM,cAAc,OAAO;CAC3B,MAAM,aAAa,OAAO;CAC1B,MAAM,SAAS,OAAO;CAGtB,MAAM,QAAQ,OAAO,IAAI,KACvB,QAAQ,OAAyD,CAClE;CAGD,MAAM,iBAAiB;;;;CAKvB,MAAM,uBACJ,eAC6E;AAC7E,MAAI,OAAO,YAAY,OACrB,QAAO,OAAO,QAAQ,OAAU;AAIlC,MAAI,OAAO,OAAO,YAAY,WAC5B,QAAQ,OAAO,QAA0G,EAAE,YAAY,CAAC;AAG1I,SAAO,OAAO,QAAQ,OAAO,QAAyD;;;;;CAMxF,MAAM,mBACJ,eAEA,OAAO,IAAI,aAAa;EAEtB,MAAM,YAAY,OAAO,YAAY,KAAK,WAAW;EAErD,IAAIA;EACJ,IAAI,iBAAiB;AAErB,MAAI,WAAW;AAEb,kBAAe,UAAU;AACzB,oBAAiB,UAAU;QAG3B,gBAAe,OAAO,oBAAoB,WAAW;EAIvD,MAAM,SAAS,OAAO,OAAO,WAA4B;EAGzD,MAAM,sBAAsB,OAAO,IAAI,KAAK,eAAe;EAC3D,MAAM,mBAAmB,OAAO,IAAI,KAAK,KAAK,KAAK,CAAC;EACpD,MAAM,4BAA4B,OAAO,IAAI,KAAK,EAAE;EACpD,MAAM,mBAAmB,OAAO,IAAI,KAAK,KAAK,KAAK,CAAC;EAGpD,MAAM,WAAW,eAAe,KAAK;GACnC,QAAQ,OAAO;GACf;GACA;GACA,uBAAuB,OAAO;GAC9B,cAAc,YAA+C;AAG3D,WAAO,QACL,OAAO,QAAQ,QAAQ;KACrB,MAAM;KACN,aAAa,QAAQ;KACrB,SAAS,QAAQ;KAClB,CAAC,CACH;;GAEH,cAAc,eAAuB,WAAmB;AACtD,WAAO,QACL,OAAO,QAAQ,QAAQ;KACrB,MAAM;KACN;KACA;KACD,CAAC,CACH;;GAEJ,CAAC;EAGF,MAAM,aAAa,OAAO,WAAW,WAAW,YAAY,eAAe;AAG3E,MAAI,WAAW,SAAS,GAAG;GACzB,MAAM,kBAAkB,WAAW,GAAI;GACvC,MAAM,gBAAgB,iBAAiB;AAEvC,OAAI,oBAAoB,eAAe;AACrC,WAAO,OAAO,WAAW,4BAA4B;KACnD;KACA,iBAAiB;KACjB;KACA;KACD,CAAC;AACF,WAAO,OAAO,UAAUC,mBAA2B;;AAIrD,QAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;IAC1C,MAAM,OAAO,WAAW,IAAI,GAAI;IAChC,MAAM,OAAO,WAAW,GAAI;AAC5B,QAAI,SAAS,OAAO,EAClB,QAAO,OAAO,WAAW,6BAA6B;KACpD;KACA,iBAAiB;KACjB,gBAAgB;KACjB,CAAC;;;AAMR,OAAK,MAAM,SAAS,YAAY;GAC9B,MAAM,SAAS,SAAS,OAAO,MAAM,YAAY;AACjD,OAAI,CAAC,OAAO,QACV,QAAO,OAAO,WAAW,gCAAgC;IACvD;IACA,SAAS,MAAM;IACf,QAAQ,OAAO;IAChB,CAAC;;EAIN,MAAMC,WAAmD;GACvD;GACA;GACA;GACA;GACA;GACA;GACD;AAGD,MAAI,UACF,QAAO,OAAO,UAAUC,kBAA0B;MAElD,QAAO,OAAO,UAAUC,iBAAyB;AAEnD,SAAO,OAAO,YAAYC,iBAAyB,EAAE;AAErD,SAAO;GACP;;;;CAKJ,MAAM,uBACJ,eAEA,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;EACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AAEjD,MAAI,SAAS,SAAS,QAAQ;AAE5B,UAAO,IAAI,IAAI,SAAS,MAAM,kBAAkB,KAAK,KAAK,CAAC;AAC3D,UAAO,SAAS;;EAIlB,MAAM,WAAW,OAAO,gBAAgB,WAAW;AAGnD,SAAO,IAAI,OAAO,QAAQ,QACxB,QAAQ,IAAI,KAAK,YAAY,SAAS,CACvC;AAED,SAAO;GACP;;;;;;;CAQJ,MAAM,gBACJ,YACA,UACA,kBAEA,OAAO,IAAI,aAAa;;AAItB,MAAI,kBAHwB,OAAO,IAAI,IAAI,SAAS,oBAAoB,EAItE;EAGF,MAAM,oBAAoB,KAAK,KAAK;EAGpC,MAAM,eAAe,OAAO,YAAY,KAAK,WAAW;EACxD,MAAM,mGAAc,aAAc,gFAAW;EAC7C,MAAM,wEAAY,aAAc;EAIhC,MAAM,mBADa,OAAO,WAAW,WAAW,YAAY,YAAY,EACrC,QAAO,MAAK,EAAE,WAAW,cAAc;AAE1E,MAAI,gBAAgB,WAAW,KAAK,CAAC,aAEnC;EAIF,IAAIC,gBAAwE;AAC5E,OAAK,MAAM,SAAS,iBAAiB;GAEnC,MAAM,UAAU,SAAS,KAAK,OAAO,QAAQ,EAAE,cAAc,eAAe,CAAC;AAC7E,WAAQ,MAAM,MAAM,YAAY,IAAI;AACpC,mBAAgB,QAAQ,KAAK;;AAG/B,MAAI,kBAAkB,OACpB;EAGF,MAAM,kBAAkB,gBAAgB,SAAS,IAC7C,gBAAgB,gBAAgB,SAAS,GAAI,UAC7C;AAKJ,MAAI,oBADwB,OAAO,IAAI,IAAI,SAAS,oBAAoB,EAEtE;EAGF,MAAMC,YAA4B;GAChC,OAAO;GACP,SAAS;GACT,eAAe;GACf,SAAS,KAAK,KAAK;GACpB;AAGD,SAAO,YAAY,KAAK,YAAY,UAAU;EAG9C,MAAM,mBAAmB,KAAK,KAAK,GAAG;AACtC,SAAO,OAAO,UAAUC,iBAAyB;AACjD,SAAO,OAAO,OAAOC,wBAAgC,iBAAiB;AAGtE,SAAO,IAAI,IAAI,SAAS,qBAAqB,gBAAgB;AAC7D,SAAO,IAAI,IAAI,SAAS,kBAAkB,KAAK,KAAK,CAAC;AACrD,SAAO,IAAI,IAAI,SAAS,2BAA2B,EAAE;AAGrD,SAAO,OAAO,SAAS,WAAW,SAAS,YAAY,gBAAgB,GAAG,MACxE,OAAO,WAAW,qDAAqD;GACrE;GACA,SAAS;GACT,OAAO;GACR,CAAC,CACH;GACD;;;;CAKJ,MAAM,yBACJ,YACA,aAEA,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,IAAI,IAAI,SAAS,0BAA0B;EAClE,MAAM,WAAW,OAAO,IAAI,IAAI,SAAS,iBAAiB;EAC1D,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,iBAAiB,SAAS,SAAS,YAAY;EAErD,MAAM,aAAa,SAAS,SAAS,OAAO,SAAS,SAAS;AAI9D,MAAI,WAHc,OAAO,SAAS,sBAGR;AACxB,UAAO,aAAa,YAAY,UAAU,eAAe;AACzD;;AAIF,MAAI,MAAM,YAAY,YAAY;AAChC,UAAO,aAAa,YAAY,UAAU,eAAe;AACzD;;GAEF;AA6CJ,QAxCqB,OAAO,IAAI,aAAa;AAiC3C,SAhCe,OAAO,IAAI,aAAa;GACrC,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;GACrC,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,YAAY,SAAS,SAAS,OAAO,YAAY;AAEvD,QAAK,MAAM,CAAC,YAAY,aAAa,QAEnC,KAAI,OADiB,OAAO,IAAI,IAAI,SAAS,iBAAiB,KACpC,WAAW;IAEnC,MAAM,iBAAiB,SAAS,SAAS,YAAY;AACrD,WAAO,OAAO,SAAS,aAAa,YAAY,UAAU,eAAe,GAAG,MAC1E,OAAO,SAAS,2CAA2C;KACzD;KACA,OAAO;KACR,CAAC,CACH;AAGD,WAAO,IAAI,OAAO,QAAQ,QAAQ,QAAQ,OAAO,KAAK,WAAW,CAAC;AAGlE,WAAO,OAAO,UAAUC,iBAAyB;AACjD,WAAO,OAAO,YAAYL,iBAAyB,GAAG;AAEtD,WAAO,OAAO,QAAQ,wCAAwC,EAC5D,YACD,CAAC;;IAGN,CAGY,KACZ,OAAO,OAAO,SAAS,OAAO,WAAW,CAAC,EAC1C,OAAO,KACR;GACD;AAMF,QAAO,OAAO,mBACZ,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;AACrC,OAAK,MAAM,CAAC,YAAY,aAAa,SAAS;GAE5C,MAAM,iBAAiB,SAAS,SAAS,YAAY;AACrD,UAAO,OAAO,SAAS,aAAa,YAAY,UAAU,eAAe,GAAG,MAC1E,OAAO,SAAS,2CAA2C;IACzD;IACA,OAAO;IACR,CAAC,CACH;;AAEH,SAAO,OAAO,QAAQ,oCAAoC;GAC1D,CACH;AAED,QAAO;EACL,SAAS,YAAY,gBACnB,OAAO,IAAI,aAAa;GACtB,MAAM,WAAW,OAAO,oBAAoB,WAAW;GACvD,MAAM,kBAAkB,KAAK,KAAK;GAGlC,MAAM,aAAa,SAAS,SAAS,SAAS,YAAY;AAE1D,OAAI,CAAC,WAAW,OAAO;AAErB,WAAO,OAAO,UAAUM,qBAA6B;IACrD,MAAMC,YAAU,KAAK,KAAK,GAAG;AAC7B,WAAO,OAAO,OAAOC,qBAA6BD,UAAQ;AAE1D,WAAO;KACL,SAAS;KACT,QAAQ,WAAW;KACpB;;GAIH,MAAME,WAAqB;IACzB;IACA,SAAS,WAAW;IACpB,WAAW,KAAK,KAAK;IACtB;GAED,MAAM,eAAe,OAAO,OAAO,OACjC,WAAW,gBAAgB,YAAY,UAAU,WAAW,YAAY,CACzE;AAED,OAAI,aAAa,SAAS,QAAQ;AAEhC,WAAO,OAAO,SAAS,qBAAqB;KAC1C;KACA,SAAS,WAAW;KACpB,OAAO,aAAa;KACrB,CAAC;AACF,WAAO,OAAO,UAAUC,kBAA0B;IAElD,MAAMH,YAAU,KAAK,KAAK,GAAG;AAC7B,WAAO,OAAO,OAAOC,qBAA6BD,UAAQ;AAG1D,WAAO;KACL,SAAS;KACT,QAAQ;KACT;;AAIH,YAAS,SAAS,MAAM,YAAY;GAGpC,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,UAAO,OAAO,OAAOC,qBAA6B,QAAQ;AAC1D,UAAO,OAAO,UAAUG,sBAA8B;AACtD,UAAO,OAAO,UAAUC,kBAA0B;AAGlD,UAAO,IAAI,OACT,SAAS,4BACR,MAAM,IAAI,EACZ;AAGD,UAAO,sBAAsB,YAAY,SAAS;AAElD,UAAO;IACL,SAAS;IACT,SAAS,WAAW;IACrB;IACD;EAEJ,cAAc,eACZ,OAAO,IAAI,aAAa;AAEtB,WADiB,OAAO,oBAAoB,WAAW,EACvC,SAAS,aAAa;IACtC;EAEJ,YAAY,eACV,OAAO,IAAI,aAAa;GACtB,MAAM,WAAW,OAAO,oBAAoB,WAAW;AACvD,UAAO,OAAO,WAAW,SAAS,OAAO;IACzC;EAEJ,QAAQ,eACN,OAAO,IAAI,aAAa;GACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;GACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AACjD,OAAI,SAAS,SAAS,OACpB,QAAO,IAAI,IAAI,SAAS,MAAM,kBAAkB,KAAK,KAAK,CAAC;IAE7D;EACL;EACD,CACH;AAMD,MAAa,kBAAkB;CAC7B,KAAK;CACL,WAAW;CACX;CACD"}
package/dist/Errors.cjs CHANGED
@@ -15,6 +15,14 @@ var ColdStorageError = class extends effect.Data.TaggedError("ColdStorageError")
15
15
  */
16
16
  var HotStorageError = class extends effect.Data.TaggedError("HotStorageError") {};
17
17
  /**
18
+ * Error when WAL append detects a version gap.
19
+ * This indicates either:
20
+ * - A bug in the application (skipped a version)
21
+ * - Concurrent writes to the same document
22
+ * - Data corruption in WAL storage
23
+ */
24
+ var WalVersionGapError = class extends effect.Data.TaggedError("WalVersionGapError") {};
25
+ /**
18
26
  * Error when authentication fails (invalid token, expired, etc.)
19
27
  */
20
28
  var AuthenticationError = class extends effect.Data.TaggedError("AuthenticationError") {};
@@ -42,4 +50,5 @@ exports.ColdStorageError = ColdStorageError;
42
50
  exports.HotStorageError = HotStorageError;
43
51
  exports.MessageParseError = MessageParseError;
44
52
  exports.MissingDocumentIdError = MissingDocumentIdError;
45
- exports.TransactionRejectedError = TransactionRejectedError;
53
+ exports.TransactionRejectedError = TransactionRejectedError;
54
+ exports.WalVersionGapError = WalVersionGapError;
package/dist/Errors.d.cts CHANGED
@@ -21,9 +21,24 @@ declare const HotStorageError_base: new <A extends Record<string, any> = {}>(arg
21
21
  */
22
22
  declare class HotStorageError extends HotStorageError_base<{
23
23
  readonly documentId: string;
24
- readonly operation: "append" | "getEntries" | "truncate";
24
+ readonly operation: "append" | "getEntries" | "truncate" | "appendWithCheck";
25
25
  readonly cause: unknown;
26
26
  }> {}
27
+ declare const WalVersionGapError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
28
+ readonly _tag: "WalVersionGapError";
29
+ } & Readonly<A>;
30
+ /**
31
+ * Error when WAL append detects a version gap.
32
+ * This indicates either:
33
+ * - A bug in the application (skipped a version)
34
+ * - Concurrent writes to the same document
35
+ * - Data corruption in WAL storage
36
+ */
37
+ declare class WalVersionGapError extends WalVersionGapError_base<{
38
+ readonly documentId: string;
39
+ readonly expectedVersion: number;
40
+ readonly actualPreviousVersion: number | undefined;
41
+ }> {}
27
42
  declare const AuthenticationError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
28
43
  readonly _tag: "AuthenticationError";
29
44
  } & Readonly<A>;
@@ -75,7 +90,7 @@ declare class TransactionRejectedError extends TransactionRejectedError_base<{
75
90
  /**
76
91
  * Union of all mimic-effect errors
77
92
  */
78
- type MimicError = ColdStorageError | HotStorageError | AuthenticationError | AuthorizationError | MissingDocumentIdError | MessageParseError | TransactionRejectedError;
93
+ type MimicError = ColdStorageError | HotStorageError | WalVersionGapError | AuthenticationError | AuthorizationError | MissingDocumentIdError | MessageParseError | TransactionRejectedError;
79
94
  //#endregion
80
- export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MimicError, MissingDocumentIdError, TransactionRejectedError };
95
+ export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MimicError, MissingDocumentIdError, TransactionRejectedError, WalVersionGapError };
81
96
  //# sourceMappingURL=Errors.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Errors.d.cts","names":[],"sources":["../src/Errors.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAca,gBAAA,SAAyB;;;;;cAIjC;;;;;AAJL;AAIK,cAKQ,eAAA,SAAwB,oBALhC,CAAA;;;;;cASA;;;;;;cASQ,mBAAA,SAA4B;EAb5B,SAAA,MAAA,EAAA,MAAgB;AAIxB,CAAA,CAAA,CAAA;cAaA;;;;;;cAKQ,kBAAA,SAA2B;;;;;AALnC,cASA,2BATA,EAAA,IAAA,CAAA,gBAAA,CAAA,MAAA,EAAA,GAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,IAAA,sBAAA,EAAA,EAAA,CAAA,CAAA,CAAA,SAAA,IAAA,GAAA,IAAA,GAAA,iBAAA,OAAA,KAAA,SAAA,MAAA,GAAA,KAAA,IAAA,IAAA,EAAA,CAAA,EAAA,EAAA,kCAAA;;;;;;cAkBQ,sBAAA,SAA+B;;;cAIvC;;;AAjBL;AAIK;;cAkBQ,iBAAA,SAA0B;;;cAElC;;;;;;cASQ,wBAAA,SAAiC;EApBjC,SAAA,aAAA,EAAA,MAAuB;EAI/B,SAAA,MAAA,EAAA,MAAA;;;;;KA8BO,UAAA,GACR,mBACA,kBACA,sBACA,qBACA,yBACA,oBACA"}
1
+ {"version":3,"file":"Errors.d.cts","names":[],"sources":["../src/Errors.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAca,gBAAA,SAAyB;;;;;cAIjC;;;;;AAJL;AAIK,cAKQ,eAAA,SAAwB,oBALhC,CAAA;;;;;cASA;;;;;;;AAJL;AAIK;;cASQ,kBAAA,SAA2B;;;;;cAInC;;;;;AAJL;AAIK,cASQ,mBAAA,SAA4B,wBATpC,CAAA;;;cAaA;;;;;;cAKQ,kBAAA,SAA2B;;;EAT3B,SAAA,MAAA,EAAA,MAAoB,GAAA,OAAA;AAI5B,CAAA,CAAA,CAAA;cASA;;;;;;cASQ,sBAAA,SAA+B;;;cAIvC;EAjBQ,SAAA,IAAA,EAAA,mBAA2B;AAInC,CAAA,WAAA,EAAA,CAAA;;;;cAkBQ,iBAAA,SAA0B;;;cAElC;;;;;AAXL;AAIK,cAgBQ,wBAAA,SAAiC,6BAhBzC,CAAA;;;;;;;KA8BO,UAAA,GACR,mBACA,kBACA,qBACA,sBACA,qBACA,yBACA,oBACA"}
package/dist/Errors.d.mts CHANGED
@@ -21,9 +21,24 @@ declare const HotStorageError_base: new <A extends Record<string, any> = {}>(arg
21
21
  */
22
22
  declare class HotStorageError extends HotStorageError_base<{
23
23
  readonly documentId: string;
24
- readonly operation: "append" | "getEntries" | "truncate";
24
+ readonly operation: "append" | "getEntries" | "truncate" | "appendWithCheck";
25
25
  readonly cause: unknown;
26
26
  }> {}
27
+ declare const WalVersionGapError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
28
+ readonly _tag: "WalVersionGapError";
29
+ } & Readonly<A>;
30
+ /**
31
+ * Error when WAL append detects a version gap.
32
+ * This indicates either:
33
+ * - A bug in the application (skipped a version)
34
+ * - Concurrent writes to the same document
35
+ * - Data corruption in WAL storage
36
+ */
37
+ declare class WalVersionGapError extends WalVersionGapError_base<{
38
+ readonly documentId: string;
39
+ readonly expectedVersion: number;
40
+ readonly actualPreviousVersion: number | undefined;
41
+ }> {}
27
42
  declare const AuthenticationError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }) => effect_Cause0.YieldableError & {
28
43
  readonly _tag: "AuthenticationError";
29
44
  } & Readonly<A>;
@@ -75,7 +90,7 @@ declare class TransactionRejectedError extends TransactionRejectedError_base<{
75
90
  /**
76
91
  * Union of all mimic-effect errors
77
92
  */
78
- type MimicError = ColdStorageError | HotStorageError | AuthenticationError | AuthorizationError | MissingDocumentIdError | MessageParseError | TransactionRejectedError;
93
+ type MimicError = ColdStorageError | HotStorageError | WalVersionGapError | AuthenticationError | AuthorizationError | MissingDocumentIdError | MessageParseError | TransactionRejectedError;
79
94
  //#endregion
80
- export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MimicError, MissingDocumentIdError, TransactionRejectedError };
95
+ export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MimicError, MissingDocumentIdError, TransactionRejectedError, WalVersionGapError };
81
96
  //# sourceMappingURL=Errors.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Errors.d.mts","names":[],"sources":["../src/Errors.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAca,gBAAA,SAAyB;;;;;cAIjC;;;;;AAJL;AAIK,cAKQ,eAAA,SAAwB,oBALhC,CAAA;;;;;cASA;;;;;;cASQ,mBAAA,SAA4B;EAb5B,SAAA,MAAA,EAAA,MAAgB;AAIxB,CAAA,CAAA,CAAA;cAaA;;;;;;cAKQ,kBAAA,SAA2B;;;;;AALnC,cASA,2BATA,EAAA,IAAA,CAAA,gBAAA,CAAA,MAAA,EAAA,GAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,IAAA,sBAAA,EAAA,EAAA,CAAA,CAAA,CAAA,SAAA,IAAA,GAAA,IAAA,GAAA,iBAAA,OAAA,KAAA,SAAA,MAAA,GAAA,KAAA,IAAA,IAAA,EAAA,CAAA,EAAA,EAAA,kCAAA;;;;;;cAkBQ,sBAAA,SAA+B;;;cAIvC;;;AAjBL;AAIK;;cAkBQ,iBAAA,SAA0B;;;cAElC;;;;;;cASQ,wBAAA,SAAiC;EApBjC,SAAA,aAAA,EAAA,MAAuB;EAI/B,SAAA,MAAA,EAAA,MAAA;;;;;KA8BO,UAAA,GACR,mBACA,kBACA,sBACA,qBACA,yBACA,oBACA"}
1
+ {"version":3,"file":"Errors.d.mts","names":[],"sources":["../src/Errors.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;cAca,gBAAA,SAAyB;;;;;cAIjC;;;;;AAJL;AAIK,cAKQ,eAAA,SAAwB,oBALhC,CAAA;;;;;cASA;;;;;;;AAJL;AAIK;;cASQ,kBAAA,SAA2B;;;;;cAInC;;;;;AAJL;AAIK,cASQ,mBAAA,SAA4B,wBATpC,CAAA;;;cAaA;;;;;;cAKQ,kBAAA,SAA2B;;;EAT3B,SAAA,MAAA,EAAA,MAAoB,GAAA,OAAA;AAI5B,CAAA,CAAA,CAAA;cASA;;;;;;cASQ,sBAAA,SAA+B;;;cAIvC;EAjBQ,SAAA,IAAA,EAAA,mBAA2B;AAInC,CAAA,WAAA,EAAA,CAAA;;;;cAkBQ,iBAAA,SAA0B;;;cAElC;;;;;AAXL;AAIK,cAgBQ,wBAAA,SAAiC,6BAhBzC,CAAA;;;;;;;KA8BO,UAAA,GACR,mBACA,kBACA,qBACA,sBACA,qBACA,yBACA,oBACA"}
package/dist/Errors.mjs CHANGED
@@ -15,6 +15,14 @@ var ColdStorageError = class extends Data.TaggedError("ColdStorageError") {};
15
15
  */
16
16
  var HotStorageError = class extends Data.TaggedError("HotStorageError") {};
17
17
  /**
18
+ * Error when WAL append detects a version gap.
19
+ * This indicates either:
20
+ * - A bug in the application (skipped a version)
21
+ * - Concurrent writes to the same document
22
+ * - Data corruption in WAL storage
23
+ */
24
+ var WalVersionGapError = class extends Data.TaggedError("WalVersionGapError") {};
25
+ /**
18
26
  * Error when authentication fails (invalid token, expired, etc.)
19
27
  */
20
28
  var AuthenticationError = class extends Data.TaggedError("AuthenticationError") {};
@@ -36,5 +44,5 @@ var MessageParseError = class extends Data.TaggedError("MessageParseError") {};
36
44
  var TransactionRejectedError = class extends Data.TaggedError("TransactionRejectedError") {};
37
45
 
38
46
  //#endregion
39
- export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MissingDocumentIdError, TransactionRejectedError };
47
+ export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MissingDocumentIdError, TransactionRejectedError, WalVersionGapError };
40
48
  //# sourceMappingURL=Errors.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Errors.mjs","names":[],"sources":["../src/Errors.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - Error Types\n *\n * All error types used throughout the mimic-effect package.\n */\nimport { Data } from \"effect\";\n\n// =============================================================================\n// Storage Errors\n// =============================================================================\n\n/**\n * Error when ColdStorage (snapshot storage) operations fail\n */\nexport class ColdStorageError extends Data.TaggedError(\"ColdStorageError\")<{\n readonly documentId: string;\n readonly operation: \"load\" | \"save\" | \"delete\";\n readonly cause: unknown;\n}> {}\n\n/**\n * Error when HotStorage (WAL storage) operations fail\n */\nexport class HotStorageError extends Data.TaggedError(\"HotStorageError\")<{\n readonly documentId: string;\n readonly operation: \"append\" | \"getEntries\" | \"truncate\";\n readonly cause: unknown;\n}> {}\n\n// =============================================================================\n// Auth Errors\n// =============================================================================\n\n/**\n * Error when authentication fails (invalid token, expired, etc.)\n */\nexport class AuthenticationError extends Data.TaggedError(\n \"AuthenticationError\"\n)<{\n readonly reason: string;\n}> {}\n\n/**\n * Error when authorization fails (user doesn't have required permission)\n */\nexport class AuthorizationError extends Data.TaggedError(\"AuthorizationError\")<{\n readonly reason: string;\n readonly required: \"read\" | \"write\";\n readonly actual: \"read\" | \"write\";\n}> {}\n\n// =============================================================================\n// Connection Errors\n// =============================================================================\n\n/**\n * Error when document ID is missing from WebSocket request path\n */\nexport class MissingDocumentIdError extends Data.TaggedError(\n \"MissingDocumentIdError\"\n)<{\n readonly path: string;\n}> {}\n\n/**\n * Error when WebSocket message cannot be parsed\n */\nexport class MessageParseError extends Data.TaggedError(\"MessageParseError\")<{\n readonly cause: unknown;\n}> {}\n\n// =============================================================================\n// Transaction Errors\n// =============================================================================\n\n/**\n * Error when a transaction is rejected by the document\n */\nexport class TransactionRejectedError extends Data.TaggedError(\n \"TransactionRejectedError\"\n)<{\n readonly transactionId: string;\n readonly reason: string;\n}> {}\n\n// =============================================================================\n// Union Type\n// =============================================================================\n\n/**\n * Union of all mimic-effect errors\n */\nexport type MimicError =\n | ColdStorageError\n | HotStorageError\n | AuthenticationError\n | AuthorizationError\n | MissingDocumentIdError\n | MessageParseError\n | TransactionRejectedError;\n"],"mappings":";;;;;;;;;;;AAcA,IAAa,mBAAb,cAAsC,KAAK,YAAY,mBAAmB,CAIvE;;;;AAKH,IAAa,kBAAb,cAAqC,KAAK,YAAY,kBAAkB,CAIrE;;;;AASH,IAAa,sBAAb,cAAyC,KAAK,YAC5C,sBACD,CAEE;;;;AAKH,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAI3E;;;;AASH,IAAa,yBAAb,cAA4C,KAAK,YAC/C,yBACD,CAEE;;;;AAKH,IAAa,oBAAb,cAAuC,KAAK,YAAY,oBAAoB,CAEzE;;;;AASH,IAAa,2BAAb,cAA8C,KAAK,YACjD,2BACD,CAGE"}
1
+ {"version":3,"file":"Errors.mjs","names":[],"sources":["../src/Errors.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - Error Types\n *\n * All error types used throughout the mimic-effect package.\n */\nimport { Data } from \"effect\";\n\n// =============================================================================\n// Storage Errors\n// =============================================================================\n\n/**\n * Error when ColdStorage (snapshot storage) operations fail\n */\nexport class ColdStorageError extends Data.TaggedError(\"ColdStorageError\")<{\n readonly documentId: string;\n readonly operation: \"load\" | \"save\" | \"delete\";\n readonly cause: unknown;\n}> {}\n\n/**\n * Error when HotStorage (WAL storage) operations fail\n */\nexport class HotStorageError extends Data.TaggedError(\"HotStorageError\")<{\n readonly documentId: string;\n readonly operation: \"append\" | \"getEntries\" | \"truncate\" | \"appendWithCheck\";\n readonly cause: unknown;\n}> {}\n\n/**\n * Error when WAL append detects a version gap.\n * This indicates either:\n * - A bug in the application (skipped a version)\n * - Concurrent writes to the same document\n * - Data corruption in WAL storage\n */\nexport class WalVersionGapError extends Data.TaggedError(\"WalVersionGapError\")<{\n readonly documentId: string;\n readonly expectedVersion: number;\n readonly actualPreviousVersion: number | undefined;\n}> {}\n\n// =============================================================================\n// Auth Errors\n// =============================================================================\n\n/**\n * Error when authentication fails (invalid token, expired, etc.)\n */\nexport class AuthenticationError extends Data.TaggedError(\n \"AuthenticationError\"\n)<{\n readonly reason: string;\n}> {}\n\n/**\n * Error when authorization fails (user doesn't have required permission)\n */\nexport class AuthorizationError extends Data.TaggedError(\"AuthorizationError\")<{\n readonly reason: string;\n readonly required: \"read\" | \"write\";\n readonly actual: \"read\" | \"write\";\n}> {}\n\n// =============================================================================\n// Connection Errors\n// =============================================================================\n\n/**\n * Error when document ID is missing from WebSocket request path\n */\nexport class MissingDocumentIdError extends Data.TaggedError(\n \"MissingDocumentIdError\"\n)<{\n readonly path: string;\n}> {}\n\n/**\n * Error when WebSocket message cannot be parsed\n */\nexport class MessageParseError extends Data.TaggedError(\"MessageParseError\")<{\n readonly cause: unknown;\n}> {}\n\n// =============================================================================\n// Transaction Errors\n// =============================================================================\n\n/**\n * Error when a transaction is rejected by the document\n */\nexport class TransactionRejectedError extends Data.TaggedError(\n \"TransactionRejectedError\"\n)<{\n readonly transactionId: string;\n readonly reason: string;\n}> {}\n\n// =============================================================================\n// Union Type\n// =============================================================================\n\n/**\n * Union of all mimic-effect errors\n */\nexport type MimicError =\n | ColdStorageError\n | HotStorageError\n | WalVersionGapError\n | AuthenticationError\n | AuthorizationError\n | MissingDocumentIdError\n | MessageParseError\n | TransactionRejectedError;\n"],"mappings":";;;;;;;;;;;AAcA,IAAa,mBAAb,cAAsC,KAAK,YAAY,mBAAmB,CAIvE;;;;AAKH,IAAa,kBAAb,cAAqC,KAAK,YAAY,kBAAkB,CAIrE;;;;;;;;AASH,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAI3E;;;;AASH,IAAa,sBAAb,cAAyC,KAAK,YAC5C,sBACD,CAEE;;;;AAKH,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAI3E;;;;AASH,IAAa,yBAAb,cAA4C,KAAK,YAC/C,yBACD,CAEE;;;;AAKH,IAAa,oBAAb,cAAuC,KAAK,YAAY,oBAAoB,CAEzE;;;;AASH,IAAa,2BAAb,cAA8C,KAAK,YACjD,2BACD,CAGE"}
@@ -1,3 +1,4 @@
1
+ const require_Errors = require('./Errors.cjs');
1
2
  let effect = require("effect");
2
3
 
3
4
  //#region src/HotStorage.ts
@@ -52,6 +53,28 @@ let InMemory;
52
53
  const entries = existing._tag === "Some" ? existing.value : [];
53
54
  return effect.HashMap.set(map, documentId, [...entries, entry]);
54
55
  }),
56
+ appendWithCheck: (documentId, entry, expectedVersion) => effect.Effect.gen(function* () {
57
+ const result = yield* effect.Ref.modify(store, (map) => {
58
+ const existing = effect.HashMap.get(map, documentId);
59
+ const entries = existing._tag === "Some" ? existing.value : [];
60
+ const lastVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
61
+ if (expectedVersion === 1) {
62
+ if (lastVersion >= 1) return [{
63
+ type: "gap",
64
+ lastVersion
65
+ }, map];
66
+ } else if (lastVersion !== expectedVersion - 1) return [{
67
+ type: "gap",
68
+ lastVersion: lastVersion > 0 ? lastVersion : void 0
69
+ }, map];
70
+ return [{ type: "ok" }, effect.HashMap.set(map, documentId, [...entries, entry])];
71
+ });
72
+ if (result.type === "gap") return yield* effect.Effect.fail(new require_Errors.WalVersionGapError({
73
+ documentId,
74
+ expectedVersion,
75
+ actualPreviousVersion: result.lastVersion
76
+ }));
77
+ }),
55
78
  getEntries: (documentId, sinceVersion) => effect.Effect.gen(function* () {
56
79
  const current = yield* effect.Ref.get(store);
57
80
  const existing = effect.HashMap.get(current, documentId);
@@ -1,5 +1,5 @@
1
1
  import { WalEntry } from "./Types.cjs";
2
- import { HotStorageError } from "./Errors.cjs";
2
+ import { HotStorageError, WalVersionGapError } from "./Errors.cjs";
3
3
  import { Context, Effect, Layer } from "effect";
4
4
 
5
5
  //#region src/HotStorage.d.ts
@@ -16,6 +16,22 @@ interface HotStorage {
16
16
  * Append a WAL entry for a document.
17
17
  */
18
18
  readonly append: (documentId: string, entry: WalEntry) => Effect.Effect<void, HotStorageError>;
19
+ /**
20
+ * Append a WAL entry with version gap checking.
21
+ *
22
+ * This is an atomic operation that:
23
+ * 1. Verifies the previous entry has version = expectedVersion - 1
24
+ * (or this is the first entry if expectedVersion === 1)
25
+ * 2. Appends the entry if check passes
26
+ *
27
+ * Use this for two-phase commit to guarantee WAL ordering at write time.
28
+ *
29
+ * @param documentId - Document ID
30
+ * @param entry - WAL entry to append
31
+ * @param expectedVersion - The version this entry should have (entry.version)
32
+ * @returns Effect that fails with WalVersionGapError if gap detected
33
+ */
34
+ readonly appendWithCheck: (documentId: string, entry: WalEntry, expectedVersion: number) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
19
35
  /**
20
36
  * Get all WAL entries for a document since a given version.
21
37
  * Returns entries with version > sinceVersion, ordered by version.
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorage.d.cts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA8CC,UA1BgB,UAAA,CA0BhB;;;;+CApBU,aACJ,MAAA,CAAO,aAAa;EA4Bd;AAuDb;AAkDA;;EAhEwB,SAAA,UAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,GA5DjB,MAAA,CAAO,MA4DU,CA5DH,QA4DG,EAAA,EA5DS,eA4DT,CAAA;EAAY;;;;EACN,SAAA,QAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,WAAA,EAAA,MAAA,EAAA,GApDvB,MAAA,CAAO,MAoDgB,CAAA,IAAA,EApDH,eAoDG,CAAA;;cAnD7B,kBAmDQ,kBAAA,cAAA,EAAA,mCAAA,YAAA,CAAA;;;;cA1CI,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cA8CzB;;uBAhEH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
1
+ {"version":3,"file":"HotStorage.d.cts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAyDO,UArCU,UAAA,CAqCH;EASa;;;EAC1B,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAzCU,QAyCV,EAAA,GAxCM,MAAA,CAAO,MAwCb,CAAA,IAAA,EAxC0B,eAwC1B,CAAA;;;;;AASD;AAuDA;AAuGA;;;;;;;;;EApHG,SAAM,eAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAxEE,QAwEF,EAAA,eAAA,EAAA,MAAA,EAAA,GAtEF,MAAA,CAAO,MAsEL,CAAA,IAAA,EAtEkB,eAsElB,GAtEoC,kBAsEpC,CAAA;;;;;qEA7DF,MAAA,CAAO,OAAO,YAAY;;;;;kEAS1B,MAAA,CAAO,aAAa;;cAC1B;;;;cASY,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cAmGzB;;uBArHH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
@@ -1,5 +1,5 @@
1
1
  import { WalEntry } from "./Types.mjs";
2
- import { HotStorageError } from "./Errors.mjs";
2
+ import { HotStorageError, WalVersionGapError } from "./Errors.mjs";
3
3
  import { Context, Effect, Layer } from "effect";
4
4
 
5
5
  //#region src/HotStorage.d.ts
@@ -16,6 +16,22 @@ interface HotStorage {
16
16
  * Append a WAL entry for a document.
17
17
  */
18
18
  readonly append: (documentId: string, entry: WalEntry) => Effect.Effect<void, HotStorageError>;
19
+ /**
20
+ * Append a WAL entry with version gap checking.
21
+ *
22
+ * This is an atomic operation that:
23
+ * 1. Verifies the previous entry has version = expectedVersion - 1
24
+ * (or this is the first entry if expectedVersion === 1)
25
+ * 2. Appends the entry if check passes
26
+ *
27
+ * Use this for two-phase commit to guarantee WAL ordering at write time.
28
+ *
29
+ * @param documentId - Document ID
30
+ * @param entry - WAL entry to append
31
+ * @param expectedVersion - The version this entry should have (entry.version)
32
+ * @returns Effect that fails with WalVersionGapError if gap detected
33
+ */
34
+ readonly appendWithCheck: (documentId: string, entry: WalEntry, expectedVersion: number) => Effect.Effect<void, HotStorageError | WalVersionGapError>;
19
35
  /**
20
36
  * Get all WAL entries for a document since a given version.
21
37
  * Returns entries with version > sinceVersion, ordered by version.
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorage.d.mts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA8CC,UA1BgB,UAAA,CA0BhB;;;;+CApBU,aACJ,MAAA,CAAO,aAAa;EA4Bd;AAuDb;AAkDA;;EAhEwB,SAAA,UAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,GA5DjB,MAAA,CAAO,MA4DU,CA5DH,QA4DG,EAAA,EA5DS,eA4DT,CAAA;EAAY;;;;EACN,SAAA,QAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,WAAA,EAAA,MAAA,EAAA,GApDvB,MAAA,CAAO,MAoDgB,CAAA,IAAA,EApDH,eAoDG,CAAA;;cAnD7B,kBAmDQ,kBAAA,cAAA,EAAA,mCAAA,YAAA,CAAA;;;;cA1CI,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cA8CzB;;uBAhEH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
1
+ {"version":3,"file":"HotStorage.d.mts","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAyDO,UArCU,UAAA,CAqCH;EASa;;;EAC1B,SAAA,MAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAzCU,QAyCV,EAAA,GAxCM,MAAA,CAAO,MAwCb,CAAA,IAAA,EAxC0B,eAwC1B,CAAA;;;;;AASD;AAuDA;AAuGA;;;;;;;;;EApHG,SAAM,eAAA,EAAA,CAAA,UAAA,EAAA,MAAA,EAAA,KAAA,EAxEE,QAwEF,EAAA,eAAA,EAAA,MAAA,EAAA,GAtEF,MAAA,CAAO,MAsEL,CAAA,IAAA,EAtEkB,eAsElB,GAtEoC,kBAsEpC,CAAA;;;;;qEA7DF,MAAA,CAAO,OAAO,YAAY;;;;;kEAS1B,MAAA,CAAO,aAAa;;cAC1B;;;;cASY,aAAA,SAAsB,kBAAA;;;;;;;kBAuDlB,QAAA;;;;oBAIS,KAAA,CAAM,MAAM;;cAmGzB;;uBArHH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}