@voidhash/mimic-effect 0.0.9 → 1.0.0-beta.2

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 (227) hide show
  1. package/.turbo/turbo-build.log +136 -90
  2. package/README.md +385 -0
  3. package/dist/ColdStorage.cjs +60 -0
  4. package/dist/ColdStorage.d.cts +53 -0
  5. package/dist/ColdStorage.d.cts.map +1 -0
  6. package/dist/ColdStorage.d.mts +53 -0
  7. package/dist/ColdStorage.d.mts.map +1 -0
  8. package/dist/ColdStorage.mjs +60 -0
  9. package/dist/ColdStorage.mjs.map +1 -0
  10. package/dist/DocumentManager.cjs +263 -82
  11. package/dist/DocumentManager.d.cts +44 -22
  12. package/dist/DocumentManager.d.cts.map +1 -1
  13. package/dist/DocumentManager.d.mts +44 -22
  14. package/dist/DocumentManager.d.mts.map +1 -1
  15. package/dist/DocumentManager.mjs +259 -67
  16. package/dist/DocumentManager.mjs.map +1 -1
  17. package/dist/Errors.cjs +54 -0
  18. package/dist/Errors.d.cts +96 -0
  19. package/dist/Errors.d.cts.map +1 -0
  20. package/dist/Errors.d.mts +96 -0
  21. package/dist/Errors.d.mts.map +1 -0
  22. package/dist/Errors.mjs +48 -0
  23. package/dist/Errors.mjs.map +1 -0
  24. package/dist/HotStorage.cjs +100 -0
  25. package/dist/HotStorage.d.cts +70 -0
  26. package/dist/HotStorage.d.cts.map +1 -0
  27. package/dist/HotStorage.d.mts +70 -0
  28. package/dist/HotStorage.d.mts.map +1 -0
  29. package/dist/HotStorage.mjs +100 -0
  30. package/dist/HotStorage.mjs.map +1 -0
  31. package/dist/Metrics.cjs +143 -0
  32. package/dist/Metrics.d.cts +31 -0
  33. package/dist/Metrics.d.cts.map +1 -0
  34. package/dist/Metrics.d.mts +31 -0
  35. package/dist/Metrics.d.mts.map +1 -0
  36. package/dist/Metrics.mjs +126 -0
  37. package/dist/Metrics.mjs.map +1 -0
  38. package/dist/MimicAuthService.cjs +61 -45
  39. package/dist/MimicAuthService.d.cts +61 -48
  40. package/dist/MimicAuthService.d.cts.map +1 -1
  41. package/dist/MimicAuthService.d.mts +61 -48
  42. package/dist/MimicAuthService.d.mts.map +1 -1
  43. package/dist/MimicAuthService.mjs +60 -36
  44. package/dist/MimicAuthService.mjs.map +1 -1
  45. package/dist/MimicClusterServerEngine.cjs +521 -0
  46. package/dist/MimicClusterServerEngine.d.cts +17 -0
  47. package/dist/MimicClusterServerEngine.d.cts.map +1 -0
  48. package/dist/MimicClusterServerEngine.d.mts +17 -0
  49. package/dist/MimicClusterServerEngine.d.mts.map +1 -0
  50. package/dist/MimicClusterServerEngine.mjs +523 -0
  51. package/dist/MimicClusterServerEngine.mjs.map +1 -0
  52. package/dist/MimicServer.cjs +205 -96
  53. package/dist/MimicServer.d.cts +9 -110
  54. package/dist/MimicServer.d.cts.map +1 -1
  55. package/dist/MimicServer.d.mts +9 -110
  56. package/dist/MimicServer.d.mts.map +1 -1
  57. package/dist/MimicServer.mjs +206 -90
  58. package/dist/MimicServer.mjs.map +1 -1
  59. package/dist/MimicServerEngine.cjs +97 -0
  60. package/dist/MimicServerEngine.d.cts +78 -0
  61. package/dist/MimicServerEngine.d.cts.map +1 -0
  62. package/dist/MimicServerEngine.d.mts +78 -0
  63. package/dist/MimicServerEngine.d.mts.map +1 -0
  64. package/dist/MimicServerEngine.mjs +97 -0
  65. package/dist/MimicServerEngine.mjs.map +1 -0
  66. package/dist/PresenceManager.cjs +75 -91
  67. package/dist/PresenceManager.d.cts +17 -66
  68. package/dist/PresenceManager.d.cts.map +1 -1
  69. package/dist/PresenceManager.d.mts +17 -66
  70. package/dist/PresenceManager.d.mts.map +1 -1
  71. package/dist/PresenceManager.mjs +74 -78
  72. package/dist/PresenceManager.mjs.map +1 -1
  73. package/dist/Protocol.cjs +146 -0
  74. package/dist/Protocol.d.cts +203 -0
  75. package/dist/Protocol.d.cts.map +1 -0
  76. package/dist/Protocol.d.mts +203 -0
  77. package/dist/Protocol.d.mts.map +1 -0
  78. package/dist/Protocol.mjs +132 -0
  79. package/dist/Protocol.mjs.map +1 -0
  80. package/dist/Types.d.cts +172 -0
  81. package/dist/Types.d.cts.map +1 -0
  82. package/dist/Types.d.mts +172 -0
  83. package/dist/Types.d.mts.map +1 -0
  84. package/dist/_virtual/rolldown_runtime.cjs +1 -25
  85. package/dist/_virtual/rolldown_runtime.mjs +4 -1
  86. package/dist/index.cjs +37 -75
  87. package/dist/index.d.cts +13 -12
  88. package/dist/index.d.mts +13 -12
  89. package/dist/index.mjs +12 -12
  90. package/dist/testing/ColdStorageTestSuite.cjs +508 -0
  91. package/dist/testing/ColdStorageTestSuite.d.cts +36 -0
  92. package/dist/testing/ColdStorageTestSuite.d.cts.map +1 -0
  93. package/dist/testing/ColdStorageTestSuite.d.mts +36 -0
  94. package/dist/testing/ColdStorageTestSuite.d.mts.map +1 -0
  95. package/dist/testing/ColdStorageTestSuite.mjs +508 -0
  96. package/dist/testing/ColdStorageTestSuite.mjs.map +1 -0
  97. package/dist/testing/FailingStorage.cjs +135 -0
  98. package/dist/testing/FailingStorage.d.cts +43 -0
  99. package/dist/testing/FailingStorage.d.cts.map +1 -0
  100. package/dist/testing/FailingStorage.d.mts +43 -0
  101. package/dist/testing/FailingStorage.d.mts.map +1 -0
  102. package/dist/testing/FailingStorage.mjs +136 -0
  103. package/dist/testing/FailingStorage.mjs.map +1 -0
  104. package/dist/testing/HotStorageTestSuite.cjs +585 -0
  105. package/dist/testing/HotStorageTestSuite.d.cts +40 -0
  106. package/dist/testing/HotStorageTestSuite.d.cts.map +1 -0
  107. package/dist/testing/HotStorageTestSuite.d.mts +40 -0
  108. package/dist/testing/HotStorageTestSuite.d.mts.map +1 -0
  109. package/dist/testing/HotStorageTestSuite.mjs +585 -0
  110. package/dist/testing/HotStorageTestSuite.mjs.map +1 -0
  111. package/dist/testing/StorageIntegrationTestSuite.cjs +349 -0
  112. package/dist/testing/StorageIntegrationTestSuite.d.cts +35 -0
  113. package/dist/testing/StorageIntegrationTestSuite.d.cts.map +1 -0
  114. package/dist/testing/StorageIntegrationTestSuite.d.mts +35 -0
  115. package/dist/testing/StorageIntegrationTestSuite.d.mts.map +1 -0
  116. package/dist/testing/StorageIntegrationTestSuite.mjs +349 -0
  117. package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -0
  118. package/dist/testing/assertions.cjs +114 -0
  119. package/dist/testing/assertions.mjs +109 -0
  120. package/dist/testing/assertions.mjs.map +1 -0
  121. package/dist/testing/index.cjs +14 -0
  122. package/dist/testing/index.d.cts +6 -0
  123. package/dist/testing/index.d.mts +6 -0
  124. package/dist/testing/index.mjs +7 -0
  125. package/dist/testing/types.cjs +15 -0
  126. package/dist/testing/types.d.cts +90 -0
  127. package/dist/testing/types.d.cts.map +1 -0
  128. package/dist/testing/types.d.mts +90 -0
  129. package/dist/testing/types.d.mts.map +1 -0
  130. package/dist/testing/types.mjs +16 -0
  131. package/dist/testing/types.mjs.map +1 -0
  132. package/package.json +18 -3
  133. package/src/ColdStorage.ts +136 -0
  134. package/src/DocumentManager.ts +550 -190
  135. package/src/Errors.ts +114 -0
  136. package/src/HotStorage.ts +239 -0
  137. package/src/Metrics.ts +187 -0
  138. package/src/MimicAuthService.ts +126 -64
  139. package/src/MimicClusterServerEngine.ts +946 -0
  140. package/src/MimicServer.ts +448 -195
  141. package/src/MimicServerEngine.ts +276 -0
  142. package/src/PresenceManager.ts +169 -240
  143. package/src/Protocol.ts +350 -0
  144. package/src/Types.ts +231 -0
  145. package/src/index.ts +57 -23
  146. package/src/testing/ColdStorageTestSuite.ts +589 -0
  147. package/src/testing/FailingStorage.ts +286 -0
  148. package/src/testing/HotStorageTestSuite.ts +762 -0
  149. package/src/testing/StorageIntegrationTestSuite.ts +504 -0
  150. package/src/testing/assertions.ts +181 -0
  151. package/src/testing/index.ts +83 -0
  152. package/src/testing/types.ts +100 -0
  153. package/tests/ColdStorage.test.ts +24 -0
  154. package/tests/DocumentManager.test.ts +158 -287
  155. package/tests/HotStorage.test.ts +24 -0
  156. package/tests/MimicAuthService.test.ts +102 -134
  157. package/tests/MimicClusterServerEngine.test.ts +587 -0
  158. package/tests/MimicServer.test.ts +90 -226
  159. package/tests/MimicServerEngine.test.ts +521 -0
  160. package/tests/PresenceManager.test.ts +22 -63
  161. package/tests/Protocol.test.ts +190 -0
  162. package/tests/StorageIntegration.test.ts +259 -0
  163. package/tsconfig.json +1 -1
  164. package/tsdown.config.ts +1 -1
  165. package/dist/DocumentProtocol.cjs +0 -94
  166. package/dist/DocumentProtocol.d.cts +0 -113
  167. package/dist/DocumentProtocol.d.cts.map +0 -1
  168. package/dist/DocumentProtocol.d.mts +0 -113
  169. package/dist/DocumentProtocol.d.mts.map +0 -1
  170. package/dist/DocumentProtocol.mjs +0 -89
  171. package/dist/DocumentProtocol.mjs.map +0 -1
  172. package/dist/MimicConfig.cjs +0 -60
  173. package/dist/MimicConfig.d.cts +0 -141
  174. package/dist/MimicConfig.d.cts.map +0 -1
  175. package/dist/MimicConfig.d.mts +0 -141
  176. package/dist/MimicConfig.d.mts.map +0 -1
  177. package/dist/MimicConfig.mjs +0 -50
  178. package/dist/MimicConfig.mjs.map +0 -1
  179. package/dist/MimicDataStorage.cjs +0 -83
  180. package/dist/MimicDataStorage.d.cts +0 -113
  181. package/dist/MimicDataStorage.d.cts.map +0 -1
  182. package/dist/MimicDataStorage.d.mts +0 -113
  183. package/dist/MimicDataStorage.d.mts.map +0 -1
  184. package/dist/MimicDataStorage.mjs +0 -74
  185. package/dist/MimicDataStorage.mjs.map +0 -1
  186. package/dist/WebSocketHandler.cjs +0 -365
  187. package/dist/WebSocketHandler.d.cts +0 -34
  188. package/dist/WebSocketHandler.d.cts.map +0 -1
  189. package/dist/WebSocketHandler.d.mts +0 -34
  190. package/dist/WebSocketHandler.d.mts.map +0 -1
  191. package/dist/WebSocketHandler.mjs +0 -355
  192. package/dist/WebSocketHandler.mjs.map +0 -1
  193. package/dist/auth/NoAuth.cjs +0 -43
  194. package/dist/auth/NoAuth.d.cts +0 -22
  195. package/dist/auth/NoAuth.d.cts.map +0 -1
  196. package/dist/auth/NoAuth.d.mts +0 -22
  197. package/dist/auth/NoAuth.d.mts.map +0 -1
  198. package/dist/auth/NoAuth.mjs +0 -36
  199. package/dist/auth/NoAuth.mjs.map +0 -1
  200. package/dist/errors.cjs +0 -74
  201. package/dist/errors.d.cts +0 -89
  202. package/dist/errors.d.cts.map +0 -1
  203. package/dist/errors.d.mts +0 -89
  204. package/dist/errors.d.mts.map +0 -1
  205. package/dist/errors.mjs +0 -67
  206. package/dist/errors.mjs.map +0 -1
  207. package/dist/storage/InMemoryDataStorage.cjs +0 -57
  208. package/dist/storage/InMemoryDataStorage.d.cts +0 -19
  209. package/dist/storage/InMemoryDataStorage.d.cts.map +0 -1
  210. package/dist/storage/InMemoryDataStorage.d.mts +0 -19
  211. package/dist/storage/InMemoryDataStorage.d.mts.map +0 -1
  212. package/dist/storage/InMemoryDataStorage.mjs +0 -48
  213. package/dist/storage/InMemoryDataStorage.mjs.map +0 -1
  214. package/src/DocumentProtocol.ts +0 -112
  215. package/src/MimicConfig.ts +0 -211
  216. package/src/MimicDataStorage.ts +0 -157
  217. package/src/WebSocketHandler.ts +0 -735
  218. package/src/auth/NoAuth.ts +0 -46
  219. package/src/errors.ts +0 -113
  220. package/src/storage/InMemoryDataStorage.ts +0 -66
  221. package/tests/DocumentProtocol.test.ts +0 -113
  222. package/tests/InMemoryDataStorage.test.ts +0 -190
  223. package/tests/MimicConfig.test.ts +0 -290
  224. package/tests/MimicDataStorage.test.ts +0 -190
  225. package/tests/NoAuth.test.ts +0 -94
  226. package/tests/WebSocketHandler.test.ts +0 -321
  227. package/tests/errors.test.ts +0 -77
@@ -1,61 +1,73 @@
1
- import { __export } from "./_virtual/rolldown_runtime.mjs";
2
- import { MimicServerConfigTag } from "./MimicConfig.mjs";
3
- import { MimicDataStorageTag } from "./MimicDataStorage.mjs";
4
- import * as Effect from "effect/Effect";
5
- import * as Layer from "effect/Layer";
6
- import * as PubSub from "effect/PubSub";
7
- import * as Ref from "effect/Ref";
8
- import * as HashMap from "effect/HashMap";
9
- import * as Context from "effect/Context";
10
- import * as Stream from "effect/Stream";
1
+ import { ColdStorageTag } from "./ColdStorage.mjs";
2
+ import { HotStorageTag } from "./HotStorage.mjs";
3
+ import { documentsActive, documentsCreated, documentsEvicted, documentsRestored, storageSnapshotLatency, storageSnapshots, storageVersionGaps, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected, walAppendFailures } from "./Metrics.mjs";
4
+ import { Context, Duration, Effect, HashMap, Layer, Metric, PubSub, Ref, Schedule, Stream } from "effect";
5
+ import { Document } from "@voidhash/mimic";
11
6
  import { ServerDocument } from "@voidhash/mimic/server";
12
7
 
13
8
  //#region src/DocumentManager.ts
14
9
  /**
15
- * @since 0.0.1
16
- * Document manager that handles multiple document instances.
10
+ * @voidhash/mimic-effect - DocumentManager
11
+ *
12
+ * Internal service for managing document lifecycle, including:
13
+ * - Document creation and restoration
14
+ * - Transaction processing
15
+ * - WAL management
16
+ * - Snapshot scheduling
17
+ * - Idle document GC
17
18
  */
18
- var DocumentManager_exports = /* @__PURE__ */ __export({
19
- DocumentManagerTag: () => DocumentManagerTag,
20
- layer: () => layer
21
- });
22
19
  /**
23
- * Context tag for DocumentManager.
20
+ * Context tag for DocumentManager service
24
21
  */
25
- var DocumentManagerTag = class extends Context.Tag("@voidhash/mimic-server-effect/DocumentManager")() {};
22
+ var DocumentManagerTag = class extends Context.Tag("@voidhash/mimic-effect/DocumentManager")() {};
26
23
  /**
27
- * Create the DocumentManager service.
24
+ * Context tag for DocumentManager configuration
28
25
  */
29
- const makeDocumentManager = Effect.gen(function* () {
30
- const config = yield* MimicServerConfigTag;
31
- const storage = yield* MimicDataStorageTag;
32
- const documents = yield* Ref.make(HashMap.empty());
33
- const getOrCreateDocument = (documentId) => Effect.gen(function* () {
34
- const current = yield* Ref.get(documents);
35
- const existing = HashMap.get(current, documentId);
36
- if (existing._tag === "Some") {
37
- yield* Ref.update(existing.value.refCount, (n) => n + 1);
38
- return existing.value;
39
- }
40
- const rawState = yield* Effect.catchAll(storage.load(documentId), () => Effect.succeed(void 0));
41
- const initialState = rawState !== void 0 ? yield* storage.onLoad(rawState) : config.initial !== void 0 ? yield* config.initial({ documentId }) : void 0;
26
+ var DocumentManagerConfigTag = class extends Context.Tag("@voidhash/mimic-effect/DocumentManagerConfig")() {};
27
+ /**
28
+ * Create the DocumentManager layer.
29
+ * Requires ColdStorage, HotStorage, and DocumentManagerConfig.
30
+ */
31
+ const layer = Layer.scoped(DocumentManagerTag, Effect.gen(function* () {
32
+ const coldStorage = yield* ColdStorageTag;
33
+ const hotStorage = yield* HotStorageTag;
34
+ const config = yield* DocumentManagerConfigTag;
35
+ const store = yield* Ref.make(HashMap.empty());
36
+ const SCHEMA_VERSION = 1;
37
+ /**
38
+ * Compute initial state for a new document
39
+ */
40
+ const computeInitialState = (documentId) => {
41
+ if (config.initial === void 0) return Effect.succeed(void 0);
42
+ if (typeof config.initial === "function") return config.initial({ documentId });
43
+ return Effect.succeed(config.initial);
44
+ };
45
+ /**
46
+ * Restore a document from storage
47
+ */
48
+ const restoreDocument = (documentId) => Effect.gen(function* () {
49
+ const storedDoc = yield* coldStorage.load(documentId);
50
+ let initialState;
51
+ let initialVersion = 0;
52
+ if (storedDoc) {
53
+ initialState = storedDoc.state;
54
+ initialVersion = storedDoc.version;
55
+ } else initialState = yield* computeInitialState(documentId);
42
56
  const pubsub = yield* PubSub.unbounded();
43
- const serverDocument = ServerDocument.make({
57
+ const lastSnapshotVersion = yield* Ref.make(initialVersion);
58
+ const lastSnapshotTime = yield* Ref.make(Date.now());
59
+ const transactionsSinceSnapshot = yield* Ref.make(0);
60
+ const lastActivityTime = yield* Ref.make(Date.now());
61
+ const document = ServerDocument.make({
44
62
  schema: config.schema,
45
63
  initialState,
64
+ initialVersion,
46
65
  maxTransactionHistory: config.maxTransactionHistory,
47
- onBroadcast: (transactionMessage) => {
48
- const currentState = serverDocument.get();
49
- Effect.runFork(Effect.gen(function* () {
50
- if (currentState !== void 0) {
51
- const transformedState = yield* storage.onSave(currentState);
52
- yield* Effect.catchAll(storage.save(documentId, transformedState), (error) => Effect.logError("Failed to save document", error));
53
- }
54
- }));
66
+ onBroadcast: (message) => {
55
67
  Effect.runSync(PubSub.publish(pubsub, {
56
68
  type: "transaction",
57
- transaction: transactionMessage.transaction,
58
- version: transactionMessage.version
69
+ transaction: message.transaction,
70
+ version: message.version
59
71
  }));
60
72
  },
61
73
  onRejection: (transactionId, reason) => {
@@ -66,40 +78,220 @@ const makeDocumentManager = Effect.gen(function* () {
66
78
  }));
67
79
  }
68
80
  });
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
+ }
104
+ for (const entry of walEntries) {
105
+ const result = document.submit(entry.transaction);
106
+ if (!result.success) yield* Effect.logWarning("Skipping corrupted WAL entry", {
107
+ documentId,
108
+ version: entry.version,
109
+ reason: result.reason
110
+ });
111
+ }
69
112
  const instance = {
70
- document: serverDocument,
113
+ document,
71
114
  pubsub,
72
- refCount: yield* Ref.make(1)
115
+ lastSnapshotVersion,
116
+ lastSnapshotTime,
117
+ transactionsSinceSnapshot,
118
+ lastActivityTime
73
119
  };
74
- yield* Ref.update(documents, (map) => HashMap.set(map, documentId, instance));
120
+ if (storedDoc) yield* Metric.increment(documentsRestored);
121
+ else yield* Metric.increment(documentsCreated);
122
+ yield* Metric.incrementBy(documentsActive, 1);
75
123
  return instance;
76
124
  });
77
- const submit = (documentId, transaction) => Effect.gen(function* () {
78
- return (yield* getOrCreateDocument(documentId)).document.submit(transaction);
79
- });
80
- const getSnapshot = (documentId) => Effect.gen(function* () {
81
- return (yield* getOrCreateDocument(documentId)).document.getSnapshot();
125
+ /**
126
+ * Get or create a document instance
127
+ */
128
+ const getOrCreateDocument = (documentId) => Effect.gen(function* () {
129
+ const current = yield* Ref.get(store);
130
+ const existing = HashMap.get(current, documentId);
131
+ if (existing._tag === "Some") {
132
+ yield* Ref.set(existing.value.lastActivityTime, Date.now());
133
+ return existing.value;
134
+ }
135
+ const instance = yield* restoreDocument(documentId);
136
+ yield* Ref.update(store, (map) => HashMap.set(map, documentId, instance));
137
+ return instance;
82
138
  });
83
- const subscribe = (documentId) => Effect.gen(function* () {
84
- const instance = yield* getOrCreateDocument(documentId);
85
- const queue = yield* PubSub.subscribe(instance.pubsub);
86
- yield* Effect.addFinalizer(() => Effect.gen(function* () {
87
- yield* Ref.updateAndGet(instance.refCount, (n) => n - 1);
139
+ /**
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.
144
+ */
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;
163
+ const storedDoc = {
164
+ state: snapshotState,
165
+ version: snapshotVersion,
166
+ schemaVersion: SCHEMA_VERSION,
167
+ savedAt: Date.now()
168
+ };
169
+ yield* coldStorage.save(documentId, storedDoc);
170
+ const snapshotDuration = Date.now() - snapshotStartTime;
171
+ yield* Metric.increment(storageSnapshots);
172
+ yield* Metric.update(storageSnapshotLatency, snapshotDuration);
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", {
177
+ documentId,
178
+ version: snapshotVersion,
179
+ error: e
88
180
  }));
89
- return Stream.fromQueue(queue);
90
181
  });
182
+ /**
183
+ * Check if snapshot should be triggered
184
+ */
185
+ const checkSnapshotTriggers = (documentId, instance) => Effect.gen(function* () {
186
+ const txCount = yield* Ref.get(instance.transactionsSinceSnapshot);
187
+ const lastTime = yield* Ref.get(instance.lastSnapshotTime);
188
+ const now = Date.now();
189
+ const currentVersion = instance.document.getVersion();
190
+ const intervalMs = Duration.toMillis(config.snapshot.interval);
191
+ if (txCount >= config.snapshot.transactionThreshold) {
192
+ yield* saveSnapshot(documentId, instance, currentVersion);
193
+ return;
194
+ }
195
+ if (now - lastTime >= intervalMs) {
196
+ yield* saveSnapshot(documentId, instance, currentVersion);
197
+ return;
198
+ }
199
+ });
200
+ yield* Effect.gen(function* () {
201
+ yield* Effect.gen(function* () {
202
+ const current = yield* Ref.get(store);
203
+ const now = Date.now();
204
+ const maxIdleMs = Duration.toMillis(config.maxIdleTime);
205
+ for (const [documentId, instance] of current) if (now - (yield* Ref.get(instance.lastActivityTime)) >= maxIdleMs) {
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
+ }));
211
+ yield* Ref.update(store, (map) => HashMap.remove(map, documentId));
212
+ yield* Metric.increment(documentsEvicted);
213
+ yield* Metric.incrementBy(documentsActive, -1);
214
+ yield* Effect.logInfo("Document evicted due to idle timeout", { documentId });
215
+ }
216
+ }).pipe(Effect.repeat(Schedule.spaced("1 minute")), Effect.fork);
217
+ });
218
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
219
+ const current = yield* Ref.get(store);
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
+ }
227
+ yield* Effect.logInfo("DocumentManager shutdown complete");
228
+ }));
91
229
  return {
92
- submit,
93
- getSnapshot,
94
- subscribe
230
+ submit: (documentId, transaction) => Effect.gen(function* () {
231
+ const instance = yield* getOrCreateDocument(documentId);
232
+ const submitStartTime = 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
241
+ };
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", {
251
+ documentId,
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
+ };
274
+ }),
275
+ getSnapshot: (documentId) => Effect.gen(function* () {
276
+ return (yield* getOrCreateDocument(documentId)).document.getSnapshot();
277
+ }),
278
+ subscribe: (documentId) => Effect.gen(function* () {
279
+ const instance = yield* getOrCreateDocument(documentId);
280
+ return Stream.fromPubSub(instance.pubsub);
281
+ }),
282
+ touch: (documentId) => Effect.gen(function* () {
283
+ const current = yield* Ref.get(store);
284
+ const existing = HashMap.get(current, documentId);
285
+ if (existing._tag === "Some") yield* Ref.set(existing.value.lastActivityTime, Date.now());
286
+ })
95
287
  };
96
- });
97
- /**
98
- * Layer that provides DocumentManager.
99
- * Requires MimicServerConfigTag and MimicDataStorageTag.
100
- */
101
- const layer = Layer.effect(DocumentManagerTag, makeDocumentManager);
288
+ }));
289
+ const DocumentManager = {
290
+ Tag: DocumentManagerTag,
291
+ ConfigTag: DocumentManagerConfigTag,
292
+ layer
293
+ };
102
294
 
103
295
  //#endregion
104
- export { DocumentManagerTag, DocumentManager_exports, layer };
296
+ export { DocumentManager, DocumentManagerConfigTag, DocumentManagerTag, layer };
105
297
  //# sourceMappingURL=DocumentManager.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentManager.mjs","names":["instance: DocumentInstance","layer: Layer.Layer<\n DocumentManagerTag,\n never,\n MimicServerConfigTag | MimicDataStorageTag\n>"],"sources":["../src/DocumentManager.ts"],"sourcesContent":["/**\n * @since 0.0.1\n * Document manager that handles multiple document instances.\n */\nimport * as Effect from \"effect/Effect\";\nimport * as Layer from \"effect/Layer\";\nimport * as PubSub from \"effect/PubSub\";\nimport * as Ref from \"effect/Ref\";\nimport * as HashMap from \"effect/HashMap\";\nimport * as Context from \"effect/Context\";\nimport * as Scope from \"effect/Scope\";\nimport * as Stream from \"effect/Stream\";\nimport type { Primitive, Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\n\nimport * as Protocol from \"./DocumentProtocol.js\";\nimport { MimicServerConfigTag } from \"./MimicConfig.js\";\nimport { MimicDataStorageTag } from \"./MimicDataStorage.js\";\nimport { DocumentNotFoundError } from \"./errors.js\";\n\n// =============================================================================\n// Document Instance\n// =============================================================================\n\n/**\n * A managed document instance that holds state and manages subscribers.\n */\ninterface DocumentInstance {\n /** The underlying ServerDocument */\n readonly document: ServerDocument.ServerDocument<Primitive.AnyPrimitive>;\n /** PubSub for broadcasting messages to subscribers */\n readonly pubsub: PubSub.PubSub<Protocol.ServerBroadcast>;\n /** Reference count for cleanup */\n readonly refCount: Ref.Ref<number>;\n}\n\n// =============================================================================\n// Document Manager Service\n// =============================================================================\n\n/**\n * Service interface for the DocumentManager.\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<Protocol.SubmitResult>;\n\n /**\n * Get a snapshot of a document.\n */\n readonly getSnapshot: (\n documentId: string\n ) => Effect.Effect<Protocol.SnapshotMessage>;\n\n /**\n * Subscribe to broadcasts for a document.\n * Returns a Stream of server broadcasts.\n */\n readonly subscribe: (\n documentId: string\n ) => Effect.Effect<\n Stream.Stream<Protocol.ServerBroadcast>,\n never,\n Scope.Scope\n >;\n}\n\n/**\n * Context tag for DocumentManager.\n */\nexport class DocumentManagerTag extends Context.Tag(\n \"@voidhash/mimic-server-effect/DocumentManager\"\n)<DocumentManagerTag, DocumentManager>() {}\n\n// =============================================================================\n// Document Manager Implementation\n// =============================================================================\n\n/**\n * Create the DocumentManager service.\n */\nconst makeDocumentManager = Effect.gen(function* () {\n const config = yield* MimicServerConfigTag;\n const storage = yield* MimicDataStorageTag;\n \n // Map of document ID to document instance\n const documents = yield* Ref.make(\n HashMap.empty<string, DocumentInstance>()\n );\n\n // Get or create a document instance\n const getOrCreateDocument = (\n documentId: string\n ): Effect.Effect<DocumentInstance> =>\n Effect.gen(function* () {\n const current = yield* Ref.get(documents);\n const existing = HashMap.get(current, documentId);\n\n if (existing._tag === \"Some\") {\n // Increment ref count\n yield* Ref.update(existing.value.refCount, (n) => n + 1);\n return existing.value;\n }\n\n // Load initial state from storage\n const rawState = yield* Effect.catchAll(\n storage.load(documentId),\n () => Effect.succeed(undefined)\n );\n\n // Transform loaded state with onLoad hook, or compute initial state for new docs\n const initialState = rawState !== undefined\n ? yield* storage.onLoad(rawState)\n : config.initial !== undefined\n ? yield* config.initial({ documentId })\n : undefined;\n\n // Create PubSub for broadcasting\n const pubsub = yield* PubSub.unbounded<Protocol.ServerBroadcast>();\n\n // Create ServerDocument with broadcast callback\n const serverDocument = ServerDocument.make({\n schema: config.schema,\n initialState: initialState as Primitive.InferSetInput<typeof config.schema> | undefined,\n maxTransactionHistory: config.maxTransactionHistory,\n onBroadcast: (transactionMessage) => {\n // Get current state and save to storage\n const currentState = serverDocument.get();\n \n // Run save in background (fire-and-forget with error logging)\n Effect.runFork(\n Effect.gen(function* () {\n if (currentState !== undefined) {\n const transformedState = yield* storage.onSave(currentState);\n yield* Effect.catchAll(\n storage.save(documentId, transformedState),\n (error) => Effect.logError(\"Failed to save document\", error)\n );\n }\n })\n );\n\n // Broadcast to subscribers\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"transaction\",\n transaction: transactionMessage.transaction as Protocol.Transaction,\n version: transactionMessage.version,\n })\n );\n },\n onRejection: (transactionId, reason) => {\n Effect.runSync(\n PubSub.publish(pubsub, {\n type: \"error\",\n transactionId,\n reason,\n })\n );\n },\n });\n\n const refCount = yield* Ref.make(1);\n\n const instance: DocumentInstance = {\n document: serverDocument,\n pubsub,\n refCount,\n };\n\n // Store in map\n yield* Ref.update(documents, (map) =>\n HashMap.set(map, documentId, instance)\n );\n\n return instance;\n });\n\n // Submit a transaction\n const submit = (\n documentId: string,\n transaction: Transaction.Transaction\n ): Effect.Effect<Protocol.SubmitResult> =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n const result = instance.document.submit(transaction);\n return result;\n });\n\n // Get a snapshot\n const getSnapshot = (\n documentId: string\n ): Effect.Effect<Protocol.SnapshotMessage> =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n const snapshot = instance.document.getSnapshot();\n return snapshot;\n });\n\n // Subscribe to broadcasts\n const subscribe = (\n documentId: string\n ): Effect.Effect<\n Stream.Stream<Protocol.ServerBroadcast>,\n never,\n Scope.Scope\n > =>\n Effect.gen(function* () {\n const instance = yield* getOrCreateDocument(documentId);\n\n // Subscribe to the PubSub\n const queue = yield* PubSub.subscribe(instance.pubsub);\n\n // Ensure cleanup on scope close\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n // Decrement ref count\n const count = yield* Ref.updateAndGet(\n instance.refCount,\n (n) => n - 1\n );\n\n // If no more subscribers, we could clean up the document\n // For now, we keep it alive (could add idle timeout)\n })\n );\n\n // Convert queue to stream\n return Stream.fromQueue(queue);\n });\n\n const manager: DocumentManager = {\n submit,\n getSnapshot,\n subscribe,\n };\n\n return manager;\n});\n\n/**\n * Layer that provides DocumentManager.\n * Requires MimicServerConfigTag and MimicDataStorageTag.\n */\nexport const layer: Layer.Layer<\n DocumentManagerTag,\n never,\n MimicServerConfigTag | MimicDataStorageTag\n> = Layer.effect(DocumentManagerTag, makeDocumentManager);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAa,qBAAb,cAAwC,QAAQ,IAC9C,gDACD,EAAuC,CAAC;;;;AASzC,MAAM,sBAAsB,OAAO,IAAI,aAAa;CAClD,MAAM,SAAS,OAAO;CACtB,MAAM,UAAU,OAAO;CAGvB,MAAM,YAAY,OAAO,IAAI,KAC3B,QAAQ,OAAiC,CAC1C;CAGD,MAAM,uBACJ,eAEA,OAAO,IAAI,aAAa;EACtB,MAAM,UAAU,OAAO,IAAI,IAAI,UAAU;EACzC,MAAM,WAAW,QAAQ,IAAI,SAAS,WAAW;AAEjD,MAAI,SAAS,SAAS,QAAQ;AAE5B,UAAO,IAAI,OAAO,SAAS,MAAM,WAAW,MAAM,IAAI,EAAE;AACxD,UAAO,SAAS;;EAIlB,MAAM,WAAW,OAAO,OAAO,SAC7B,QAAQ,KAAK,WAAW,QAClB,OAAO,QAAQ,OAAU,CAChC;EAGD,MAAM,eAAe,aAAa,SAC9B,OAAO,QAAQ,OAAO,SAAS,GAC/B,OAAO,YAAY,SACjB,OAAO,OAAO,QAAQ,EAAE,YAAY,CAAC,GACrC;EAGN,MAAM,SAAS,OAAO,OAAO,WAAqC;EAGlE,MAAM,iBAAiB,eAAe,KAAK;GACzC,QAAQ,OAAO;GACD;GACd,uBAAuB,OAAO;GAC9B,cAAc,uBAAuB;IAEnC,MAAM,eAAe,eAAe,KAAK;AAGzC,WAAO,QACL,OAAO,IAAI,aAAa;AACtB,SAAI,iBAAiB,QAAW;MAC9B,MAAM,mBAAmB,OAAO,QAAQ,OAAO,aAAa;AAC5D,aAAO,OAAO,SACZ,QAAQ,KAAK,YAAY,iBAAiB,GACzC,UAAU,OAAO,SAAS,2BAA2B,MAAM,CAC7D;;MAEH,CACH;AAGD,WAAO,QACL,OAAO,QAAQ,QAAQ;KACrB,MAAM;KACN,aAAa,mBAAmB;KAChC,SAAS,mBAAmB;KAC7B,CAAC,CACH;;GAEH,cAAc,eAAe,WAAW;AACtC,WAAO,QACL,OAAO,QAAQ,QAAQ;KACrB,MAAM;KACN;KACA;KACD,CAAC,CACH;;GAEJ,CAAC;EAIF,MAAMA,WAA6B;GACjC,UAAU;GACV;GACA,UALe,OAAO,IAAI,KAAK,EAAE;GAMlC;AAGD,SAAO,IAAI,OAAO,YAAY,QAC5B,QAAQ,IAAI,KAAK,YAAY,SAAS,CACvC;AAED,SAAO;GACP;CAGJ,MAAM,UACJ,YACA,gBAEA,OAAO,IAAI,aAAa;AAGtB,UAFiB,OAAO,oBAAoB,WAAW,EAC/B,SAAS,OAAO,YAAY;GAEpD;CAGJ,MAAM,eACJ,eAEA,OAAO,IAAI,aAAa;AAGtB,UAFiB,OAAO,oBAAoB,WAAW,EAC7B,SAAS,aAAa;GAEhD;CAGJ,MAAM,aACJ,eAMA,OAAO,IAAI,aAAa;EACtB,MAAM,WAAW,OAAO,oBAAoB,WAAW;EAGvD,MAAM,QAAQ,OAAO,OAAO,UAAU,SAAS,OAAO;AAGtD,SAAO,OAAO,mBACZ,OAAO,IAAI,aAAa;AAER,UAAO,IAAI,aACvB,SAAS,WACR,MAAM,IAAI,EACZ;IAID,CACH;AAGD,SAAO,OAAO,UAAU,MAAM;GAC9B;AAQJ,QANiC;EAC/B;EACA;EACA;EACD;EAGD;;;;;AAMF,MAAaC,QAIT,MAAM,OAAO,oBAAoB,oBAAoB"}
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":"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"}
@@ -0,0 +1,54 @@
1
+ let effect = require("effect");
2
+
3
+ //#region src/Errors.ts
4
+ /**
5
+ * @voidhash/mimic-effect - Error Types
6
+ *
7
+ * All error types used throughout the mimic-effect package.
8
+ */
9
+ /**
10
+ * Error when ColdStorage (snapshot storage) operations fail
11
+ */
12
+ var ColdStorageError = class extends effect.Data.TaggedError("ColdStorageError") {};
13
+ /**
14
+ * Error when HotStorage (WAL storage) operations fail
15
+ */
16
+ var HotStorageError = class extends effect.Data.TaggedError("HotStorageError") {};
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
+ /**
26
+ * Error when authentication fails (invalid token, expired, etc.)
27
+ */
28
+ var AuthenticationError = class extends effect.Data.TaggedError("AuthenticationError") {};
29
+ /**
30
+ * Error when authorization fails (user doesn't have required permission)
31
+ */
32
+ var AuthorizationError = class extends effect.Data.TaggedError("AuthorizationError") {};
33
+ /**
34
+ * Error when document ID is missing from WebSocket request path
35
+ */
36
+ var MissingDocumentIdError = class extends effect.Data.TaggedError("MissingDocumentIdError") {};
37
+ /**
38
+ * Error when WebSocket message cannot be parsed
39
+ */
40
+ var MessageParseError = class extends effect.Data.TaggedError("MessageParseError") {};
41
+ /**
42
+ * Error when a transaction is rejected by the document
43
+ */
44
+ var TransactionRejectedError = class extends effect.Data.TaggedError("TransactionRejectedError") {};
45
+
46
+ //#endregion
47
+ exports.AuthenticationError = AuthenticationError;
48
+ exports.AuthorizationError = AuthorizationError;
49
+ exports.ColdStorageError = ColdStorageError;
50
+ exports.HotStorageError = HotStorageError;
51
+ exports.MessageParseError = MessageParseError;
52
+ exports.MissingDocumentIdError = MissingDocumentIdError;
53
+ exports.TransactionRejectedError = TransactionRejectedError;
54
+ exports.WalVersionGapError = WalVersionGapError;
@@ -0,0 +1,96 @@
1
+ import * as effect_Types0 from "effect/Types";
2
+ import * as effect_Cause0 from "effect/Cause";
3
+
4
+ //#region src/Errors.d.ts
5
+ declare const ColdStorageError_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 & {
6
+ readonly _tag: "ColdStorageError";
7
+ } & Readonly<A>;
8
+ /**
9
+ * Error when ColdStorage (snapshot storage) operations fail
10
+ */
11
+ declare class ColdStorageError extends ColdStorageError_base<{
12
+ readonly documentId: string;
13
+ readonly operation: "load" | "save" | "delete";
14
+ readonly cause: unknown;
15
+ }> {}
16
+ declare const HotStorageError_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 & {
17
+ readonly _tag: "HotStorageError";
18
+ } & Readonly<A>;
19
+ /**
20
+ * Error when HotStorage (WAL storage) operations fail
21
+ */
22
+ declare class HotStorageError extends HotStorageError_base<{
23
+ readonly documentId: string;
24
+ readonly operation: "append" | "getEntries" | "truncate" | "appendWithCheck";
25
+ readonly cause: unknown;
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
+ }> {}
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 & {
43
+ readonly _tag: "AuthenticationError";
44
+ } & Readonly<A>;
45
+ /**
46
+ * Error when authentication fails (invalid token, expired, etc.)
47
+ */
48
+ declare class AuthenticationError extends AuthenticationError_base<{
49
+ readonly reason: string;
50
+ }> {}
51
+ declare const AuthorizationError_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 & {
52
+ readonly _tag: "AuthorizationError";
53
+ } & Readonly<A>;
54
+ /**
55
+ * Error when authorization fails (user doesn't have required permission)
56
+ */
57
+ declare class AuthorizationError extends AuthorizationError_base<{
58
+ readonly reason: string;
59
+ readonly required: "read" | "write";
60
+ readonly actual: "read" | "write";
61
+ }> {}
62
+ declare const MissingDocumentIdError_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 & {
63
+ readonly _tag: "MissingDocumentIdError";
64
+ } & Readonly<A>;
65
+ /**
66
+ * Error when document ID is missing from WebSocket request path
67
+ */
68
+ declare class MissingDocumentIdError extends MissingDocumentIdError_base<{
69
+ readonly path: string;
70
+ }> {}
71
+ declare const MessageParseError_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 & {
72
+ readonly _tag: "MessageParseError";
73
+ } & Readonly<A>;
74
+ /**
75
+ * Error when WebSocket message cannot be parsed
76
+ */
77
+ declare class MessageParseError extends MessageParseError_base<{
78
+ readonly cause: unknown;
79
+ }> {}
80
+ declare const TransactionRejectedError_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 & {
81
+ readonly _tag: "TransactionRejectedError";
82
+ } & Readonly<A>;
83
+ /**
84
+ * Error when a transaction is rejected by the document
85
+ */
86
+ declare class TransactionRejectedError extends TransactionRejectedError_base<{
87
+ readonly transactionId: string;
88
+ readonly reason: string;
89
+ }> {}
90
+ /**
91
+ * Union of all mimic-effect errors
92
+ */
93
+ type MimicError = ColdStorageError | HotStorageError | WalVersionGapError | AuthenticationError | AuthorizationError | MissingDocumentIdError | MessageParseError | TransactionRejectedError;
94
+ //#endregion
95
+ export { AuthenticationError, AuthorizationError, ColdStorageError, HotStorageError, MessageParseError, MimicError, MissingDocumentIdError, TransactionRejectedError, WalVersionGapError };
96
+ //# sourceMappingURL=Errors.d.cts.map
@@ -0,0 +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;;;;;;;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"}