@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentInstance.mjs","names":["initialState: Primitive.InferState<TSchema> | undefined","initial: Primitive.InferSetInput<TSchema> | undefined","Metrics.documentsRestored","Metrics.documentsCreated","Metrics.documentsActive","walEntries","storedDoc","Metrics.storageSnapshots","Metrics.storageSnapshotLatency","Metrics.transactionsRejected","latency","Metrics.transactionsLatency","walEntry: WalEntry","Metrics.walAppendFailures","Metrics.transactionsProcessed","Metrics.storageWalAppends","Metrics.storageVersionGaps","snapshotState: Primitive.InferState<TSchema> | undefined"],"sources":["../src/DocumentInstance.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - DocumentInstance\n *\n * Manages the lifecycle of a single document including:\n * - Restoration from storage (cold storage + WAL replay)\n * - Transaction submission with WAL persistence\n * - Snapshot saving and trigger checking\n *\n * Used by both MimicServerEngine (single-node) and MimicClusterServerEngine (clustered).\n */\nimport { Duration, Effect, Metric, PubSub, Ref } from \"effect\";\nimport { Document, type Primitive, type Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\nimport type { StoredDocument, WalEntry } from \"./Types\";\nimport type { ServerBroadcast } from \"./Protocol\";\nimport type { ColdStorage } from \"./ColdStorage\";\nimport type { HotStorage } from \"./HotStorage\";\nimport type { ColdStorageError } from \"./Errors\";\nimport type { HotStorageError } from \"./Errors\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// 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 * Configuration for a DocumentInstance\n */\nexport interface DocumentInstanceConfig<TSchema extends Primitive.AnyPrimitive> {\n readonly schema: TSchema;\n readonly initial?:\n | Primitive.InferSetInput<TSchema>\n | ((ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<TSchema>>);\n readonly maxTransactionHistory: number;\n readonly snapshot: {\n readonly interval: Duration.Duration;\n readonly transactionThreshold: number;\n };\n}\n\n/**\n * Snapshot tracking state\n */\nexport interface SnapshotTracking {\n readonly lastSnapshotVersion: number;\n readonly lastSnapshotTime: number;\n readonly transactionsSinceSnapshot: number;\n}\n\n/**\n * A DocumentInstance manages a single document's lifecycle\n */\nexport interface DocumentInstance<TSchema extends Primitive.AnyPrimitive> {\n /** The underlying ServerDocument */\n readonly document: ServerDocument.ServerDocument<TSchema>;\n /** PubSub for broadcasting messages to subscribers */\n readonly pubsub: PubSub.PubSub<ServerBroadcast>;\n /** Current snapshot tracking state */\n readonly getSnapshotTracking: Effect.Effect<SnapshotTracking>;\n /** Submit a transaction */\n readonly submit: (transaction: Transaction.Transaction) => Effect.Effect<SubmitResult, ColdStorageError | HotStorageError>;\n /** Save a snapshot to cold storage */\n readonly saveSnapshot: () => Effect.Effect<void, ColdStorageError | HotStorageError>;\n /** Check if snapshot should be triggered and save if needed */\n readonly checkSnapshotTriggers: () => Effect.Effect<void, ColdStorageError | HotStorageError>;\n /** Update last activity time (for external tracking) */\n readonly touch: () => Effect.Effect<void>;\n /** Get current document version */\n readonly getVersion: () => number;\n /** Get document snapshot */\n readonly getSnapshot: () => { state: unknown; version: number };\n /** Check if document has unsnapshot transactions that need persisting */\n readonly needsSnapshot: () => Effect.Effect<boolean>;\n /** Get the last activity timestamp for idle detection */\n readonly getLastActivityTime: () => Effect.Effect<number>;\n}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a DocumentInstance for a single document.\n *\n * This handles:\n * - Loading from cold storage or computing initial state\n * - Persisting initial state immediately (crash safety)\n * - Replaying WAL entries\n * - Transaction submission with WAL persistence\n * - Snapshot saving\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n documentId: string,\n config: DocumentInstanceConfig<TSchema>,\n coldStorage: ColdStorage,\n hotStorage: HotStorage\n): Effect.Effect<DocumentInstance<TSchema>, ColdStorageError | HotStorageError> =>\n Effect.gen(function* () {\n // Current schema version (hard-coded to 1 for now)\n const SCHEMA_VERSION = 1;\n\n // 1. Load snapshot from ColdStorage\n const storedDoc = yield* coldStorage.load(documentId);\n\n // Track initial values - only one will be set:\n // - initialState: raw state from storage (already in internal format)\n // - initial: computed from config (needs conversion to state format)\n let initialState: Primitive.InferState<TSchema> | undefined;\n let initial: Primitive.InferSetInput<TSchema> | undefined;\n let initialVersion = 0;\n\n if (storedDoc) {\n // Loading from storage - state is already in internal format\n initialState = storedDoc.state as Primitive.InferState<TSchema>;\n initialVersion = storedDoc.version;\n } else {\n // New document - compute initial value (set input format)\n initial = yield* computeInitialState(config, 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 lastSnapshotVersionRef = yield* Ref.make(initialVersion);\n const lastSnapshotTimeRef = yield* Ref.make(Date.now());\n const transactionsSinceSnapshotRef = yield* Ref.make(0);\n const lastActivityTimeRef = yield* Ref.make(Date.now());\n\n // 4. Create ServerDocument with callbacks\n const document = ServerDocument.make({\n schema: config.schema,\n initial,\n initialState,\n initialVersion,\n maxTransactionHistory: config.maxTransactionHistory,\n onBroadcast: (message: ServerDocument.TransactionMessage) => {\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. If this is a new document, immediately save to cold storage\n // This ensures the initial state is durable before any transactions are accepted.\n if (!storedDoc) {\n const initialStoredDoc = createStoredDocument(document.get(), 0, SCHEMA_VERSION);\n yield* coldStorage.save(documentId, initialStoredDoc);\n yield* Effect.logDebug(\"Initial state persisted to cold storage\", { documentId });\n }\n\n // 6. Load WAL entries\n const walEntries = yield* hotStorage.getEntries(documentId, initialVersion);\n\n // 7. Verify WAL continuity (warning only, non-blocking)\n yield* verifyWalContinuity(documentId, walEntries, initialVersion);\n\n // 8. Replay WAL entries\n yield* replayWalEntries(documentId, document, walEntries);\n\n // Track metrics\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 // ==========================================================================\n // Instance Methods\n // ==========================================================================\n\n const getSnapshotTracking = Effect.gen(function* () {\n return {\n lastSnapshotVersion: yield* Ref.get(lastSnapshotVersionRef),\n lastSnapshotTime: yield* Ref.get(lastSnapshotTimeRef),\n transactionsSinceSnapshot: yield* Ref.get(transactionsSinceSnapshotRef),\n };\n });\n\n const saveSnapshot = Effect.fn(\"document.snapshot.save\")(function* () {\n const targetVersion = document.getVersion();\n const lastSnapshotVersion = yield* Ref.get(lastSnapshotVersionRef);\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<TSchema> | undefined;\n\n // Load WAL entries from base to target\n const walEntries = yield* hotStorage.getEntries(documentId, baseVersion);\n\n // Compute snapshot state by replaying WAL on base\n const snapshotResult = computeSnapshotState(\n config.schema,\n baseState,\n walEntries,\n targetVersion\n );\n\n if (!snapshotResult) {\n return;\n }\n\n // Re-check before saving (in case another snapshot completed while we were working)\n const currentLastSnapshot = yield* Ref.get(lastSnapshotVersionRef);\n if (snapshotResult.version <= currentLastSnapshot) {\n return;\n }\n\n const storedDoc = createStoredDocument(\n snapshotResult.state,\n snapshotResult.version,\n SCHEMA_VERSION\n );\n\n // Save to ColdStorage\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(lastSnapshotVersionRef, snapshotResult.version);\n yield* Ref.set(lastSnapshotTimeRef, Date.now());\n yield* Ref.set(transactionsSinceSnapshotRef, 0);\n\n // Truncate WAL - non-fatal, will be retried on next snapshot\n yield* Effect.catchAll(hotStorage.truncate(documentId, snapshotResult.version), (e) =>\n Effect.logWarning(\"WAL truncate failed - will retry on next snapshot\", {\n documentId,\n version: snapshotResult.version,\n error: e,\n })\n );\n });\n\n const checkSnapshotTriggers = Effect.fn(\"document.snapshot.check-triggers\")(function* () {\n const txCount = yield* Ref.get(transactionsSinceSnapshotRef);\n const lastTime = yield* Ref.get(lastSnapshotTimeRef);\n\n if (shouldTriggerSnapshot(txCount, lastTime, config.snapshot)) {\n yield* saveSnapshot();\n }\n });\n\n const submit = Effect.fn(\"document.transaction.submit\")(function* (\n transaction: Transaction.Transaction\n ) {\n const submitStartTime = Date.now();\n\n // Update activity time\n yield* Ref.set(lastActivityTimeRef, Date.now());\n\n // Phase 1: Validate (no side effects)\n const validation = document.validate(transaction);\n\n if (!validation.valid) {\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 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 {\n success: false as const,\n reason: \"Storage unavailable. Please retry.\",\n };\n }\n\n // Phase 3: Apply (state mutation + broadcast)\n 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(transactionsSinceSnapshotRef, (n) => n + 1);\n\n // Check snapshot triggers\n yield* checkSnapshotTriggers();\n\n return {\n success: true as const,\n version: validation.nextVersion,\n };\n });\n\n const touch = Effect.fn(\"document.touch\")(function* () {\n yield* Ref.set(lastActivityTimeRef, Date.now());\n });\n\n const needsSnapshot = () =>\n Effect.map(Ref.get(transactionsSinceSnapshotRef), (n) => n > 0);\n\n const getLastActivityTime = () => Ref.get(lastActivityTimeRef);\n\n return {\n document,\n pubsub,\n getSnapshotTracking,\n submit,\n saveSnapshot,\n checkSnapshotTriggers,\n touch,\n getVersion: () => document.getVersion(),\n getSnapshot: () => document.getSnapshot(),\n needsSnapshot,\n getLastActivityTime,\n };\n });\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Compute initial state for a new document.\n */\nconst computeInitialState = <TSchema extends Primitive.AnyPrimitive>(\n config: DocumentInstanceConfig<TSchema>,\n documentId: string\n): Effect.Effect<Primitive.InferSetInput<TSchema> | undefined> => {\n if (config.initial === undefined) {\n return Effect.succeed(undefined);\n }\n\n if (typeof config.initial === \"function\") {\n return (config.initial as (ctx: { documentId: string }) => Effect.Effect<Primitive.InferSetInput<TSchema>>)({\n documentId,\n });\n }\n\n return Effect.succeed(config.initial as Primitive.InferSetInput<TSchema>);\n};\n\n/**\n * Verify WAL continuity and log warnings for any gaps.\n */\nconst verifyWalContinuity = Effect.fn(\"document.wal.verify\")(function* (\n documentId: string,\n walEntries: readonly WalEntry[],\n baseVersion: number\n) {\n if (walEntries.length === 0) {\n return;\n }\n\n const firstWalVersion = walEntries[0]!.version;\n const expectedFirst = baseVersion + 1;\n\n if (firstWalVersion !== expectedFirst) {\n yield* Effect.logWarning(\"WAL version gap detected\", {\n documentId,\n snapshotVersion: baseVersion,\n firstWalVersion,\n expectedFirst,\n });\n yield* Metric.increment(Metrics.storageVersionGaps);\n }\n\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/**\n * Replay WAL entries onto a ServerDocument.\n */\nconst replayWalEntries = Effect.fn(\"document.wal.replay\")(function* (\n documentId: string,\n document: ServerDocument.ServerDocument<Primitive.AnyPrimitive>,\n walEntries: readonly WalEntry[]\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\n/**\n * Compute snapshot state by replaying WAL entries on a base state.\n */\nconst computeSnapshotState = <TSchema extends Primitive.AnyPrimitive>(\n schema: TSchema,\n baseState: Primitive.InferState<TSchema> | undefined,\n walEntries: readonly WalEntry[],\n targetVersion: number\n): { state: Primitive.InferState<TSchema>; version: number } | undefined => {\n const relevantEntries = walEntries.filter((e) => e.version <= targetVersion);\n\n if (relevantEntries.length === 0 && baseState === undefined) {\n return undefined;\n }\n\n let snapshotState: Primitive.InferState<TSchema> | undefined = baseState;\n for (const entry of relevantEntries) {\n const tempDoc = Document.make(schema, { initialState: snapshotState });\n tempDoc.apply(entry.transaction.ops);\n snapshotState = tempDoc.get();\n }\n\n if (snapshotState === undefined) {\n return undefined;\n }\n\n const snapshotVersion =\n relevantEntries.length > 0 ? relevantEntries[relevantEntries.length - 1]!.version : 0;\n\n return { state: snapshotState, version: snapshotVersion };\n};\n\n/**\n * Check if a snapshot should be triggered.\n */\nconst shouldTriggerSnapshot = (\n transactionsSinceSnapshot: number,\n lastSnapshotTime: number,\n config: { interval: Duration.Duration; transactionThreshold: number }\n): boolean => {\n const now = Date.now();\n const intervalMs = Duration.toMillis(config.interval);\n\n if (transactionsSinceSnapshot >= config.transactionThreshold) {\n return true;\n }\n\n if (now - lastSnapshotTime >= intervalMs) {\n return true;\n }\n\n return false;\n};\n\n/**\n * Create a StoredDocument for persistence.\n */\nconst createStoredDocument = (\n state: unknown,\n version: number,\n schemaVersion: number\n): StoredDocument => ({\n state,\n version,\n schemaVersion,\n savedAt: Date.now(),\n});\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const DocumentInstance = {\n make,\n};\n"],"mappings":"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"}
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
@@ -44,27 +45,53 @@ var HotStorageTag = class extends effect.Context.Tag("@voidhash/mimic-effect/Hot
44
45
  const make = (effect$1) => effect.Layer.effect(HotStorageTag, effect$1);
45
46
  let InMemory;
46
47
  (function(_InMemory) {
47
- _InMemory.make = () => effect.Layer.effect(HotStorageTag, effect.Effect.gen(function* () {
48
+ _InMemory.make = () => effect.Layer.effect(HotStorageTag, effect.Effect.fn("hot-storage.in-memory.create")(function* () {
48
49
  const store = yield* effect.Ref.make(effect.HashMap.empty());
49
50
  return {
50
- append: (documentId, entry) => effect.Ref.update(store, (map) => {
51
- const existing = effect.HashMap.get(map, documentId);
52
- const entries = existing._tag === "Some" ? existing.value : [];
53
- return effect.HashMap.set(map, documentId, [...entries, entry]);
51
+ append: effect.Effect.fn("hot-storage.append")(function* (documentId, entry) {
52
+ yield* effect.Ref.update(store, (map) => {
53
+ const existing = effect.HashMap.get(map, documentId);
54
+ const entries = existing._tag === "Some" ? existing.value : [];
55
+ return effect.HashMap.set(map, documentId, [...entries, entry]);
56
+ });
54
57
  }),
55
- getEntries: (documentId, sinceVersion) => effect.Effect.gen(function* () {
58
+ appendWithCheck: effect.Effect.fn("hot-storage.append-with-check")(function* (documentId, entry, expectedVersion) {
59
+ const result = yield* effect.Ref.modify(store, (map) => {
60
+ const existing = effect.HashMap.get(map, documentId);
61
+ const entries = existing._tag === "Some" ? existing.value : [];
62
+ const lastVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
63
+ if (expectedVersion === 1) {
64
+ if (lastVersion >= 1) return [{
65
+ type: "gap",
66
+ lastVersion
67
+ }, map];
68
+ } else if (lastVersion !== expectedVersion - 1) return [{
69
+ type: "gap",
70
+ lastVersion: lastVersion > 0 ? lastVersion : void 0
71
+ }, map];
72
+ return [{ type: "ok" }, effect.HashMap.set(map, documentId, [...entries, entry])];
73
+ });
74
+ if (result.type === "gap") return yield* effect.Effect.fail(new require_Errors.WalVersionGapError({
75
+ documentId,
76
+ expectedVersion,
77
+ actualPreviousVersion: result.lastVersion
78
+ }));
79
+ }),
80
+ getEntries: effect.Effect.fn("hot-storage.get-entries")(function* (documentId, sinceVersion) {
56
81
  const current = yield* effect.Ref.get(store);
57
82
  const existing = effect.HashMap.get(current, documentId);
58
83
  return (existing._tag === "Some" ? existing.value : []).filter((e) => e.version > sinceVersion).sort((a, b) => a.version - b.version);
59
84
  }),
60
- truncate: (documentId, upToVersion) => effect.Ref.update(store, (map) => {
61
- const existing = effect.HashMap.get(map, documentId);
62
- if (existing._tag === "None") return map;
63
- const filtered = existing.value.filter((e) => e.version > upToVersion);
64
- return effect.HashMap.set(map, documentId, filtered);
85
+ truncate: effect.Effect.fn("hot-storage.truncate")(function* (documentId, upToVersion) {
86
+ yield* effect.Ref.update(store, (map) => {
87
+ const existing = effect.HashMap.get(map, documentId);
88
+ if (existing._tag === "None") return map;
89
+ const filtered = existing.value.filter((e) => e.version > upToVersion);
90
+ return effect.HashMap.set(map, documentId, filtered);
91
+ });
65
92
  })
66
93
  };
67
- }));
94
+ })());
68
95
  })(InMemory || (InMemory = {}));
69
96
  const HotStorage = {
70
97
  Tag: HotStorageTag,
@@ -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;AAwHA;;;;;;;;;EArIG,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;;cAoHzB;;uBAtIH,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;AAwHA;;;;;;;;;EArIG,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;;cAoHzB;;uBAtIH,MAAA,CAAO,OAAO,YAAY,GAAG,OACpC,KAAA,CAAM,MAAM,eAAe,GAAG"}
@@ -1,3 +1,4 @@
1
+ import { WalVersionGapError } from "./Errors.mjs";
1
2
  import { Context, Effect, HashMap, Layer, Ref } from "effect";
2
3
 
3
4
  //#region src/HotStorage.ts
@@ -44,27 +45,53 @@ var HotStorageTag = class extends Context.Tag("@voidhash/mimic-effect/HotStorage
44
45
  const make = (effect) => Layer.effect(HotStorageTag, effect);
45
46
  let InMemory;
46
47
  (function(_InMemory) {
47
- _InMemory.make = () => Layer.effect(HotStorageTag, Effect.gen(function* () {
48
+ _InMemory.make = () => Layer.effect(HotStorageTag, Effect.fn("hot-storage.in-memory.create")(function* () {
48
49
  const store = yield* Ref.make(HashMap.empty());
49
50
  return {
50
- append: (documentId, entry) => Ref.update(store, (map) => {
51
- const existing = HashMap.get(map, documentId);
52
- const entries = existing._tag === "Some" ? existing.value : [];
53
- return HashMap.set(map, documentId, [...entries, entry]);
51
+ append: Effect.fn("hot-storage.append")(function* (documentId, entry) {
52
+ yield* Ref.update(store, (map) => {
53
+ const existing = HashMap.get(map, documentId);
54
+ const entries = existing._tag === "Some" ? existing.value : [];
55
+ return HashMap.set(map, documentId, [...entries, entry]);
56
+ });
54
57
  }),
55
- getEntries: (documentId, sinceVersion) => Effect.gen(function* () {
58
+ appendWithCheck: Effect.fn("hot-storage.append-with-check")(function* (documentId, entry, expectedVersion) {
59
+ const result = yield* Ref.modify(store, (map) => {
60
+ const existing = HashMap.get(map, documentId);
61
+ const entries = existing._tag === "Some" ? existing.value : [];
62
+ const lastVersion = entries.length > 0 ? Math.max(...entries.map((e) => e.version)) : 0;
63
+ if (expectedVersion === 1) {
64
+ if (lastVersion >= 1) return [{
65
+ type: "gap",
66
+ lastVersion
67
+ }, map];
68
+ } else if (lastVersion !== expectedVersion - 1) return [{
69
+ type: "gap",
70
+ lastVersion: lastVersion > 0 ? lastVersion : void 0
71
+ }, map];
72
+ return [{ type: "ok" }, HashMap.set(map, documentId, [...entries, entry])];
73
+ });
74
+ if (result.type === "gap") return yield* Effect.fail(new WalVersionGapError({
75
+ documentId,
76
+ expectedVersion,
77
+ actualPreviousVersion: result.lastVersion
78
+ }));
79
+ }),
80
+ getEntries: Effect.fn("hot-storage.get-entries")(function* (documentId, sinceVersion) {
56
81
  const current = yield* Ref.get(store);
57
82
  const existing = HashMap.get(current, documentId);
58
83
  return (existing._tag === "Some" ? existing.value : []).filter((e) => e.version > sinceVersion).sort((a, b) => a.version - b.version);
59
84
  }),
60
- truncate: (documentId, upToVersion) => Ref.update(store, (map) => {
61
- const existing = HashMap.get(map, documentId);
62
- if (existing._tag === "None") return map;
63
- const filtered = existing.value.filter((e) => e.version > upToVersion);
64
- return HashMap.set(map, documentId, filtered);
85
+ truncate: Effect.fn("hot-storage.truncate")(function* (documentId, upToVersion) {
86
+ yield* Ref.update(store, (map) => {
87
+ const existing = HashMap.get(map, documentId);
88
+ if (existing._tag === "None") return map;
89
+ const filtered = existing.value.filter((e) => e.version > upToVersion);
90
+ return HashMap.set(map, documentId, filtered);
91
+ });
65
92
  })
66
93
  };
67
- }));
94
+ })());
68
95
  })(InMemory || (InMemory = {}));
69
96
  const HotStorage = {
70
97
  Tag: HotStorageTag,
@@ -1 +1 @@
1
- {"version":3,"file":"HotStorage.mjs","names":[],"sources":["../src/HotStorage.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - HotStorage\n *\n * Interface and implementations for Write-Ahead Log (WAL) storage.\n */\nimport { Context, Effect, HashMap, Layer, Ref } from \"effect\";\nimport type { WalEntry } from \"./Types\";\nimport { HotStorageError } from \"./Errors\";\n\n// =============================================================================\n// HotStorage Interface\n// =============================================================================\n\n/**\n * HotStorage interface for storing Write-Ahead Log entries.\n *\n * This is the \"hot\" tier of the two-tier storage system.\n * It stores every transaction as a WAL entry for durability between snapshots.\n * WAL entries are small (just the transaction) and writes are append-only.\n */\nexport interface HotStorage {\n /**\n * Append a WAL entry for a document.\n */\n readonly append: (\n documentId: string,\n entry: WalEntry\n ) => Effect.Effect<void, HotStorageError>;\n\n /**\n * Get all WAL entries for a document since a given version.\n * Returns entries with version > sinceVersion, ordered by version.\n */\n readonly getEntries: (\n documentId: string,\n sinceVersion: number\n ) => Effect.Effect<WalEntry[], HotStorageError>;\n\n /**\n * Truncate WAL entries up to (and including) a given version.\n * Called after a snapshot is saved to remove entries that are now in the snapshot.\n */\n readonly truncate: (\n documentId: string,\n upToVersion: number\n ) => Effect.Effect<void, HotStorageError>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for HotStorage service\n */\nexport class HotStorageTag extends Context.Tag(\"@voidhash/mimic-effect/HotStorage\")<\n HotStorageTag,\n HotStorage\n>() {}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a HotStorage layer from an Effect that produces a HotStorage service.\n *\n * This allows you to access other Effect services when implementing custom storage.\n *\n * @example\n * ```typescript\n * const Hot = HotStorage.make(\n * Effect.gen(function*() {\n * const redis = yield* RedisService\n *\n * return {\n * append: (documentId, entry) =>\n * redis.rpush(`wal:${documentId}`, JSON.stringify(entry)),\n * getEntries: (documentId, sinceVersion) =>\n * redis.lrange(`wal:${documentId}`, 0, -1).pipe(\n * Effect.map(entries =>\n * entries\n * .map(e => JSON.parse(e))\n * .filter(e => e.version > sinceVersion)\n * .sort((a, b) => a.version - b.version)\n * )\n * ),\n * truncate: (documentId, upToVersion) =>\n * // Implementation depends on Redis data structure\n * Effect.void,\n * }\n * })\n * )\n * ```\n */\nexport const make = <E, R>(\n effect: Effect.Effect<HotStorage, E, R>\n): Layer.Layer<HotStorageTag, E, R> =>\n Layer.effect(HotStorageTag, effect);\n\n// =============================================================================\n// InMemory Implementation\n// =============================================================================\n\n/**\n * In-memory HotStorage implementation.\n *\n * Useful for testing and development. Not suitable for production\n * as data is lost when the process restarts.\n */\nexport namespace InMemory {\n /**\n * Create an in-memory HotStorage layer.\n */\n export const make = (): Layer.Layer<HotStorageTag> =>\n Layer.effect(\n HotStorageTag,\n Effect.gen(function* () {\n const store = yield* Ref.make(HashMap.empty<string, WalEntry[]>());\n\n return {\n append: (documentId, entry) =>\n Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return HashMap.set(map, documentId, [...entries, entry]);\n }),\n\n getEntries: (documentId, sinceVersion) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return entries\n .filter((e) => e.version > sinceVersion)\n .sort((a, b) => a.version - b.version);\n }),\n\n truncate: (documentId, upToVersion) =>\n Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n if (existing._tag === \"None\") {\n return map;\n }\n const filtered = existing.value.filter(\n (e) => e.version > upToVersion\n );\n return HashMap.set(map, documentId, filtered);\n }),\n };\n })\n );\n}\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const HotStorage = {\n Tag: HotStorageTag,\n make,\n InMemory,\n};\n"],"mappings":";;;;;;;;;;;AAuDA,IAAa,gBAAb,cAAmC,QAAQ,IAAI,oCAAoC,EAGhF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCJ,MAAa,QACX,WAEA,MAAM,OAAO,eAAe,OAAO;;;wBAiBjC,MAAM,OACJ,eACA,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAA2B,CAAC;AAElE,SAAO;GACL,SAAS,YAAY,UACnB,IAAI,OAAO,QAAQ,QAAQ;IACzB,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;IAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;AAChD,WAAO,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC;KACxD;GAEJ,aAAa,YAAY,iBACvB,OAAO,IAAI,aAAa;IACtB,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;IACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AAGjD,YADE,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE,EAE7C,QAAQ,MAAM,EAAE,UAAU,aAAa,CACvC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ;KACxC;GAEJ,WAAW,YAAY,gBACrB,IAAI,OAAO,QAAQ,QAAQ;IACzB,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;AAC7C,QAAI,SAAS,SAAS,OACpB,QAAO;IAET,MAAM,WAAW,SAAS,MAAM,QAC7B,MAAM,EAAE,UAAU,YACpB;AACD,WAAO,QAAQ,IAAI,KAAK,YAAY,SAAS;KAC7C;GACL;GACD,CACH;;AAOL,MAAa,aAAa;CACxB,KAAK;CACL;CACA;CACD"}
1
+ {"version":3,"file":"HotStorage.mjs","names":["result: CheckResult"],"sources":["../src/HotStorage.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - HotStorage\n *\n * Interface and implementations for Write-Ahead Log (WAL) storage.\n */\nimport { Context, Effect, HashMap, Layer, Ref } from \"effect\";\nimport type { WalEntry } from \"./Types\";\nimport { HotStorageError, WalVersionGapError } from \"./Errors\";\n\n// =============================================================================\n// HotStorage Interface\n// =============================================================================\n\n/**\n * HotStorage interface for storing Write-Ahead Log entries.\n *\n * This is the \"hot\" tier of the two-tier storage system.\n * It stores every transaction as a WAL entry for durability between snapshots.\n * WAL entries are small (just the transaction) and writes are append-only.\n */\nexport interface HotStorage {\n /**\n * Append a WAL entry for a document.\n */\n readonly append: (\n documentId: string,\n entry: WalEntry\n ) => Effect.Effect<void, HotStorageError>;\n\n /**\n * Append a WAL entry with version gap checking.\n *\n * This is an atomic operation that:\n * 1. Verifies the previous entry has version = expectedVersion - 1\n * (or this is the first entry if expectedVersion === 1)\n * 2. Appends the entry if check passes\n *\n * Use this for two-phase commit to guarantee WAL ordering at write time.\n *\n * @param documentId - Document ID\n * @param entry - WAL entry to append\n * @param expectedVersion - The version this entry should have (entry.version)\n * @returns Effect that fails with WalVersionGapError if gap detected\n */\n readonly appendWithCheck: (\n documentId: string,\n entry: WalEntry,\n expectedVersion: number\n ) => Effect.Effect<void, HotStorageError | WalVersionGapError>;\n\n /**\n * Get all WAL entries for a document since a given version.\n * Returns entries with version > sinceVersion, ordered by version.\n */\n readonly getEntries: (\n documentId: string,\n sinceVersion: number\n ) => Effect.Effect<WalEntry[], HotStorageError>;\n\n /**\n * Truncate WAL entries up to (and including) a given version.\n * Called after a snapshot is saved to remove entries that are now in the snapshot.\n */\n readonly truncate: (\n documentId: string,\n upToVersion: number\n ) => Effect.Effect<void, HotStorageError>;\n}\n\n// =============================================================================\n// Context Tag\n// =============================================================================\n\n/**\n * Context tag for HotStorage service\n */\nexport class HotStorageTag extends Context.Tag(\"@voidhash/mimic-effect/HotStorage\")<\n HotStorageTag,\n HotStorage\n>() {}\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a HotStorage layer from an Effect that produces a HotStorage service.\n *\n * This allows you to access other Effect services when implementing custom storage.\n *\n * @example\n * ```typescript\n * const Hot = HotStorage.make(\n * Effect.gen(function*() {\n * const redis = yield* RedisService\n *\n * return {\n * append: (documentId, entry) =>\n * redis.rpush(`wal:${documentId}`, JSON.stringify(entry)),\n * getEntries: (documentId, sinceVersion) =>\n * redis.lrange(`wal:${documentId}`, 0, -1).pipe(\n * Effect.map(entries =>\n * entries\n * .map(e => JSON.parse(e))\n * .filter(e => e.version > sinceVersion)\n * .sort((a, b) => a.version - b.version)\n * )\n * ),\n * truncate: (documentId, upToVersion) =>\n * // Implementation depends on Redis data structure\n * Effect.void,\n * }\n * })\n * )\n * ```\n */\nexport const make = <E, R>(\n effect: Effect.Effect<HotStorage, E, R>\n): Layer.Layer<HotStorageTag, E, R> =>\n Layer.effect(HotStorageTag, effect);\n\n// =============================================================================\n// InMemory Implementation\n// =============================================================================\n\n/**\n * In-memory HotStorage implementation.\n *\n * Useful for testing and development. Not suitable for production\n * as data is lost when the process restarts.\n */\nexport namespace InMemory {\n /**\n * Create an in-memory HotStorage layer.\n */\n export const make = (): Layer.Layer<HotStorageTag> =>\n Layer.effect(\n HotStorageTag,\n Effect.fn(\"hot-storage.in-memory.create\")(function* () {\n const store = yield* Ref.make(HashMap.empty<string, WalEntry[]>());\n\n return {\n append: Effect.fn(\"hot-storage.append\")(\n function* (documentId: string, entry: WalEntry) {\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return HashMap.set(map, documentId, [...entries, entry]);\n });\n }\n ),\n\n appendWithCheck: Effect.fn(\"hot-storage.append-with-check\")(\n function* (\n documentId: string,\n entry: WalEntry,\n expectedVersion: number\n ) {\n type CheckResult =\n | { type: \"ok\" }\n | { type: \"gap\"; lastVersion: number | undefined };\n\n // Use Ref.modify for atomic check + update\n const result: CheckResult = yield* Ref.modify(\n store,\n (map): [CheckResult, HashMap.HashMap<string, WalEntry[]>] => {\n const existing = HashMap.get(map, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n\n // Find the highest version in existing entries\n const lastVersion =\n entries.length > 0\n ? Math.max(...entries.map((e) => e.version))\n : 0;\n\n // Gap check\n if (expectedVersion === 1) {\n // First entry: should have no entries with version >= 1\n if (lastVersion >= 1) {\n return [{ type: \"gap\", lastVersion }, map];\n }\n } else {\n // Not first: last entry should have version = expectedVersion - 1\n if (lastVersion !== expectedVersion - 1) {\n return [\n {\n type: \"gap\",\n lastVersion: lastVersion > 0 ? lastVersion : undefined,\n },\n map,\n ];\n }\n }\n\n // No gap: append and return success\n return [\n { type: \"ok\" },\n HashMap.set(map, documentId, [...entries, entry]),\n ];\n }\n );\n\n if (result.type === \"gap\") {\n return yield* Effect.fail(\n new WalVersionGapError({\n documentId,\n expectedVersion,\n actualPreviousVersion: result.lastVersion,\n })\n );\n }\n }\n ),\n\n getEntries: Effect.fn(\"hot-storage.get-entries\")(\n function* (documentId: string, sinceVersion: number) {\n const current = yield* Ref.get(store);\n const existing = HashMap.get(current, documentId);\n const entries =\n existing._tag === \"Some\" ? existing.value : [];\n return entries\n .filter((e) => e.version > sinceVersion)\n .sort((a, b) => a.version - b.version);\n }\n ),\n\n truncate: Effect.fn(\"hot-storage.truncate\")(\n function* (documentId: string, upToVersion: number) {\n yield* Ref.update(store, (map) => {\n const existing = HashMap.get(map, documentId);\n if (existing._tag === \"None\") {\n return map;\n }\n const filtered = existing.value.filter(\n (e) => e.version > upToVersion\n );\n return HashMap.set(map, documentId, filtered);\n });\n }\n ),\n };\n })()\n );\n}\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const HotStorage = {\n Tag: HotStorageTag,\n make,\n InMemory,\n};\n"],"mappings":";;;;;;;;;;;;AA4EA,IAAa,gBAAb,cAAmC,QAAQ,IAAI,oCAAoC,EAGhF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCJ,MAAa,QACX,WAEA,MAAM,OAAO,eAAe,OAAO;;;wBAiBjC,MAAM,OACJ,eACA,OAAO,GAAG,+BAA+B,CAAC,aAAa;EACrD,MAAM,QAAQ,OAAO,IAAI,KAAK,QAAQ,OAA2B,CAAC;AAElE,SAAO;GACL,QAAQ,OAAO,GAAG,qBAAqB,CACrC,WAAW,YAAoB,OAAiB;AAC9C,WAAO,IAAI,OAAO,QAAQ,QAAQ;KAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;KAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;AAChD,YAAO,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC;MACxD;KAEL;GAED,iBAAiB,OAAO,GAAG,gCAAgC,CACzD,WACE,YACA,OACA,iBACA;IAMA,MAAMA,SAAsB,OAAO,IAAI,OACrC,QACC,QAA4D;KAC3D,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;KAC7C,MAAM,UACJ,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE;KAGhD,MAAM,cACJ,QAAQ,SAAS,IACb,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,EAAE,QAAQ,CAAC,GAC1C;AAGN,SAAI,oBAAoB,GAEtB;UAAI,eAAe,EACjB,QAAO,CAAC;OAAE,MAAM;OAAO;OAAa,EAAE,IAAI;gBAIxC,gBAAgB,kBAAkB,EACpC,QAAO,CACL;MACE,MAAM;MACN,aAAa,cAAc,IAAI,cAAc;MAC9C,EACD,IACD;AAKL,YAAO,CACL,EAAE,MAAM,MAAM,EACd,QAAQ,IAAI,KAAK,YAAY,CAAC,GAAG,SAAS,MAAM,CAAC,CAClD;MAEJ;AAED,QAAI,OAAO,SAAS,MAClB,QAAO,OAAO,OAAO,KACnB,IAAI,mBAAmB;KACrB;KACA;KACA,uBAAuB,OAAO;KAC/B,CAAC,CACH;KAGN;GAED,YAAY,OAAO,GAAG,0BAA0B,CAC9C,WAAW,YAAoB,cAAsB;IACnD,MAAM,UAAU,OAAO,IAAI,IAAI,MAAM;IACrC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AAGjD,YADE,SAAS,SAAS,SAAS,SAAS,QAAQ,EAAE,EAE7C,QAAQ,MAAM,EAAE,UAAU,aAAa,CACvC,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ;KAE3C;GAED,UAAU,OAAO,GAAG,uBAAuB,CACzC,WAAW,YAAoB,aAAqB;AAClD,WAAO,IAAI,OAAO,QAAQ,QAAQ;KAChC,MAAM,WAAW,QAAQ,IAAI,KAAK,WAAW;AAC7C,SAAI,SAAS,SAAS,OACpB,QAAO;KAET,MAAM,WAAW,SAAS,MAAM,QAC7B,MAAM,EAAE,UAAU,YACpB;AACD,YAAO,QAAQ,IAAI,KAAK,YAAY,SAAS;MAC7C;KAEL;GACF;GACD,EAAE,CACL;;AAOL,MAAa,aAAa;CACxB,KAAK;CACL;CACA;CACD"}
package/dist/Metrics.cjs CHANGED
@@ -63,6 +63,10 @@ const transactionsLatency = effect.Metric.histogram("mimic.transactions.latency_
63
63
  */
64
64
  const storageSnapshots = effect.Metric.counter("mimic.storage.snapshots");
65
65
  /**
66
+ * Snapshots triggered by idle document detection
67
+ */
68
+ const storageIdleSnapshots = effect.Metric.counter("mimic.storage.idle_snapshots");
69
+ /**
66
70
  * Snapshot save duration histogram (milliseconds)
67
71
  */
68
72
  const storageSnapshotLatency = effect.Metric.histogram("mimic.storage.snapshot_latency_ms", effect.MetricBoundaries.exponential({
@@ -75,6 +79,22 @@ const storageSnapshotLatency = effect.Metric.histogram("mimic.storage.snapshot_l
75
79
  */
76
80
  const storageWalAppends = effect.Metric.counter("mimic.storage.wal_appends");
77
81
  /**
82
+ * Version gaps detected during WAL replay
83
+ */
84
+ const storageVersionGaps = effect.Metric.counter("mimic.storage.version_gaps");
85
+ /**
86
+ * Failed WAL appends causing transaction rollback
87
+ */
88
+ const walAppendFailures = effect.Metric.counter("mimic.storage.wal_append_failures");
89
+ /**
90
+ * ColdStorage load failures during document restore
91
+ */
92
+ const coldStorageLoadFailures = effect.Metric.counter("mimic.storage.cold_load_failures");
93
+ /**
94
+ * HotStorage getEntries failures during document restore
95
+ */
96
+ const hotStorageLoadFailures = effect.Metric.counter("mimic.storage.hot_load_failures");
97
+ /**
78
98
  * Presence set operations
79
99
  */
80
100
  const presenceUpdates = effect.Metric.counter("mimic.presence.updates");
@@ -95,8 +115,13 @@ const MimicMetrics = {
95
115
  transactionsRejected,
96
116
  transactionsLatency,
97
117
  storageSnapshots,
118
+ storageIdleSnapshots,
98
119
  storageSnapshotLatency,
99
120
  storageWalAppends,
121
+ storageVersionGaps,
122
+ walAppendFailures,
123
+ coldStorageLoadFailures,
124
+ hotStorageLoadFailures,
100
125
  presenceUpdates,
101
126
  presenceActive
102
127
  };
@@ -113,9 +138,12 @@ exports.documentsEvicted = documentsEvicted;
113
138
  exports.documentsRestored = documentsRestored;
114
139
  exports.presenceActive = presenceActive;
115
140
  exports.presenceUpdates = presenceUpdates;
141
+ exports.storageIdleSnapshots = storageIdleSnapshots;
116
142
  exports.storageSnapshotLatency = storageSnapshotLatency;
117
143
  exports.storageSnapshots = storageSnapshots;
144
+ exports.storageVersionGaps = storageVersionGaps;
118
145
  exports.storageWalAppends = storageWalAppends;
119
146
  exports.transactionsLatency = transactionsLatency;
120
147
  exports.transactionsProcessed = transactionsProcessed;
121
- exports.transactionsRejected = transactionsRejected;
148
+ exports.transactionsRejected = transactionsRejected;
149
+ exports.walAppendFailures = walAppendFailures;
@@ -17,8 +17,13 @@ declare const MimicMetrics: {
17
17
  transactionsRejected: Metric.Metric.Counter<number>;
18
18
  transactionsLatency: Metric.Metric<effect_MetricKeyType0.MetricKeyType.Histogram, number, effect_MetricState0.MetricState.Histogram>;
19
19
  storageSnapshots: Metric.Metric.Counter<number>;
20
+ storageIdleSnapshots: Metric.Metric.Counter<number>;
20
21
  storageSnapshotLatency: Metric.Metric<effect_MetricKeyType0.MetricKeyType.Histogram, number, effect_MetricState0.MetricState.Histogram>;
21
22
  storageWalAppends: Metric.Metric.Counter<number>;
23
+ storageVersionGaps: Metric.Metric.Counter<number>;
24
+ walAppendFailures: Metric.Metric.Counter<number>;
25
+ coldStorageLoadFailures: Metric.Metric.Counter<number>;
26
+ hotStorageLoadFailures: Metric.Metric.Counter<number>;
22
27
  presenceUpdates: Metric.Metric.Counter<number>;
23
28
  presenceActive: Metric.Metric.Gauge<number>;
24
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Metrics.d.cts","names":[],"sources":["../src/Metrics.ts"],"sourcesContent":[],"mappings":";;;;;;cAwIa"}
1
+ {"version":3,"file":"Metrics.d.cts","names":[],"sources":["../src/Metrics.ts"],"sourcesContent":[],"mappings":";;;;;;cAiKa"}
@@ -17,8 +17,13 @@ declare const MimicMetrics: {
17
17
  transactionsRejected: Metric.Metric.Counter<number>;
18
18
  transactionsLatency: Metric.Metric<effect_MetricKeyType0.MetricKeyType.Histogram, number, effect_MetricState0.MetricState.Histogram>;
19
19
  storageSnapshots: Metric.Metric.Counter<number>;
20
+ storageIdleSnapshots: Metric.Metric.Counter<number>;
20
21
  storageSnapshotLatency: Metric.Metric<effect_MetricKeyType0.MetricKeyType.Histogram, number, effect_MetricState0.MetricState.Histogram>;
21
22
  storageWalAppends: Metric.Metric.Counter<number>;
23
+ storageVersionGaps: Metric.Metric.Counter<number>;
24
+ walAppendFailures: Metric.Metric.Counter<number>;
25
+ coldStorageLoadFailures: Metric.Metric.Counter<number>;
26
+ hotStorageLoadFailures: Metric.Metric.Counter<number>;
22
27
  presenceUpdates: Metric.Metric.Counter<number>;
23
28
  presenceActive: Metric.Metric.Gauge<number>;
24
29
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Metrics.d.mts","names":[],"sources":["../src/Metrics.ts"],"sourcesContent":[],"mappings":";;;;;;cAwIa"}
1
+ {"version":3,"file":"Metrics.d.mts","names":[],"sources":["../src/Metrics.ts"],"sourcesContent":[],"mappings":";;;;;;cAiKa"}