@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
@@ -0,0 +1,523 @@
1
+ import { __require } from "./_virtual/rolldown_runtime.mjs";
2
+ import { ColdStorageTag } from "./ColdStorage.mjs";
3
+ import { HotStorageTag } from "./HotStorage.mjs";
4
+ import { documentsActive, documentsCreated, documentsEvicted, documentsRestored, presenceActive, presenceUpdates, storageSnapshotLatency, storageSnapshots, storageVersionGaps, storageWalAppends, transactionsLatency, transactionsProcessed, transactionsRejected, walAppendFailures } from "./Metrics.mjs";
5
+ import { _objectSpread2 } from "./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs";
6
+ import { MimicServerEngineTag } from "./MimicServerEngine.mjs";
7
+ import { Context, Duration, Effect, HashMap, Layer, Metric, PubSub, Ref, Schema, Stream } from "effect";
8
+ import { Document } from "@voidhash/mimic";
9
+ import { ServerDocument } from "@voidhash/mimic/server";
10
+ import { Entity } from "@effect/cluster";
11
+ import { Rpc } from "@effect/rpc";
12
+
13
+ //#region src/MimicClusterServerEngine.ts
14
+ /**
15
+ * @voidhash/mimic-effect - MimicClusterServerEngine
16
+ *
17
+ * Clustered document management service using Effect Cluster for horizontal scaling.
18
+ * Each document becomes a cluster Entity with automatic sharding, failover, and location-transparent routing.
19
+ *
20
+ * This is an alternative to MimicServerEngine for distributed deployments.
21
+ */
22
+ const DEFAULT_MAX_IDLE_TIME = Duration.minutes(5);
23
+ const DEFAULT_MAX_TRANSACTION_HISTORY = 1e3;
24
+ const DEFAULT_SNAPSHOT_INTERVAL = Duration.minutes(5);
25
+ const DEFAULT_SNAPSHOT_THRESHOLD = 100;
26
+ const DEFAULT_SHARD_GROUP = "mimic-documents";
27
+ /**
28
+ * Schema for encoded transaction (wire format)
29
+ */
30
+ const EncodedTransactionSchema = Schema.Struct({
31
+ id: Schema.String,
32
+ ops: Schema.Array(Schema.Unknown)
33
+ });
34
+ /**
35
+ * Schema for submit result
36
+ */
37
+ const SubmitResultSchema = Schema.Union(Schema.Struct({
38
+ success: Schema.Literal(true),
39
+ version: Schema.Number
40
+ }), Schema.Struct({
41
+ success: Schema.Literal(false),
42
+ reason: Schema.String
43
+ }));
44
+ /**
45
+ * Schema for snapshot response
46
+ */
47
+ const SnapshotResponseSchema = Schema.Struct({
48
+ state: Schema.Unknown,
49
+ version: Schema.Number
50
+ });
51
+ /**
52
+ * Schema for presence entry
53
+ */
54
+ const PresenceEntrySchema = Schema.Struct({
55
+ data: Schema.Unknown,
56
+ userId: Schema.optional(Schema.String)
57
+ });
58
+ /**
59
+ * Schema for presence snapshot response
60
+ */
61
+ const PresenceSnapshotResponseSchema = Schema.Struct({ presences: Schema.Record({
62
+ key: Schema.String,
63
+ value: PresenceEntrySchema
64
+ }) });
65
+ Schema.Union(Schema.Struct({
66
+ type: Schema.Literal("presence_update"),
67
+ id: Schema.String,
68
+ data: Schema.Unknown,
69
+ userId: Schema.optional(Schema.String)
70
+ }), Schema.Struct({
71
+ type: Schema.Literal("presence_remove"),
72
+ id: Schema.String
73
+ }));
74
+ Schema.Unknown;
75
+ /**
76
+ * Define the Mimic Document Entity with its RPC protocol.
77
+ * This entity handles document operations for a single documentId.
78
+ */
79
+ const MimicDocumentEntity = Entity.make("MimicDocument", [
80
+ Rpc.make("Submit", {
81
+ payload: { transaction: EncodedTransactionSchema },
82
+ success: SubmitResultSchema
83
+ }),
84
+ Rpc.make("GetSnapshot", { success: SnapshotResponseSchema }),
85
+ Rpc.make("Touch", { success: Schema.Void }),
86
+ Rpc.make("SetPresence", {
87
+ payload: {
88
+ connectionId: Schema.String,
89
+ entry: PresenceEntrySchema
90
+ },
91
+ success: Schema.Void
92
+ }),
93
+ Rpc.make("RemovePresence", {
94
+ payload: { connectionId: Schema.String },
95
+ success: Schema.Void
96
+ }),
97
+ Rpc.make("GetPresenceSnapshot", { success: PresenceSnapshotResponseSchema })
98
+ ]);
99
+ /**
100
+ * Context tag for cluster engine configuration
101
+ */
102
+ var MimicClusterConfigTag = class extends Context.Tag("@voidhash/mimic-effect/MimicClusterConfig")() {};
103
+ const resolveClusterConfig = (config) => {
104
+ var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$shardGroup;
105
+ return {
106
+ schema: config.schema,
107
+ initial: config.initial,
108
+ presence: config.presence,
109
+ maxIdleTime: config.maxIdleTime ? Duration.decode(config.maxIdleTime) : DEFAULT_MAX_IDLE_TIME,
110
+ maxTransactionHistory: (_config$maxTransactio = config.maxTransactionHistory) !== null && _config$maxTransactio !== void 0 ? _config$maxTransactio : DEFAULT_MAX_TRANSACTION_HISTORY,
111
+ snapshot: {
112
+ interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? Duration.decode(config.snapshot.interval) : DEFAULT_SNAPSHOT_INTERVAL,
113
+ transactionThreshold: (_config$snapshot$tran = (_config$snapshot2 = config.snapshot) === null || _config$snapshot2 === void 0 ? void 0 : _config$snapshot2.transactionThreshold) !== null && _config$snapshot$tran !== void 0 ? _config$snapshot$tran : DEFAULT_SNAPSHOT_THRESHOLD
114
+ },
115
+ shardGroup: (_config$shardGroup = config.shardGroup) !== null && _config$shardGroup !== void 0 ? _config$shardGroup : DEFAULT_SHARD_GROUP
116
+ };
117
+ };
118
+ /**
119
+ * Decode an encoded transaction to a Transaction object
120
+ */
121
+ const decodeTransaction = (encoded) => {
122
+ const { Transaction: Transaction$1 } = __require("@voidhash/mimic");
123
+ return Transaction$1.decode(encoded);
124
+ };
125
+ /**
126
+ * Encode a Transaction to wire format
127
+ */
128
+ const encodeTransaction = (tx) => {
129
+ const { Transaction: Transaction$1 } = __require("@voidhash/mimic");
130
+ return Transaction$1.encode(tx);
131
+ };
132
+ /**
133
+ * Create the entity handler for MimicDocument
134
+ */
135
+ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.gen(function* () {
136
+ const documentId = (yield* Entity.CurrentAddress).entityId;
137
+ const SCHEMA_VERSION = 1;
138
+ const computeInitialState = () => {
139
+ if (config.initial === void 0) return Effect.succeed(void 0);
140
+ if (typeof config.initial === "function") return config.initial({ documentId });
141
+ return Effect.succeed(config.initial);
142
+ };
143
+ const storedDoc = yield* coldStorage.load(documentId).pipe(Effect.orDie);
144
+ let initialState;
145
+ let initialVersion = 0;
146
+ if (storedDoc) {
147
+ initialState = storedDoc.state;
148
+ initialVersion = storedDoc.version;
149
+ } else initialState = yield* computeInitialState();
150
+ const broadcastPubSub = yield* PubSub.unbounded();
151
+ const presencePubSub = yield* PubSub.unbounded();
152
+ const stateRef = yield* Ref.make({
153
+ document: void 0,
154
+ broadcastPubSub,
155
+ presences: HashMap.empty(),
156
+ presencePubSub,
157
+ lastSnapshotVersion: initialVersion,
158
+ lastSnapshotTime: Date.now(),
159
+ transactionsSinceSnapshot: 0
160
+ });
161
+ const document = ServerDocument.make({
162
+ schema: config.schema,
163
+ initialState,
164
+ initialVersion,
165
+ maxTransactionHistory: config.maxTransactionHistory,
166
+ onBroadcast: (message) => {
167
+ Effect.runSync(PubSub.publish(broadcastPubSub, {
168
+ type: "transaction",
169
+ transaction: message.transaction,
170
+ version: message.version
171
+ }));
172
+ },
173
+ onRejection: (transactionId, reason) => {
174
+ Effect.runSync(PubSub.publish(broadcastPubSub, {
175
+ type: "error",
176
+ transactionId,
177
+ reason
178
+ }));
179
+ }
180
+ });
181
+ yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { document }));
182
+ const walEntries = yield* hotStorage.getEntries(documentId, initialVersion).pipe(Effect.orDie);
183
+ if (walEntries.length > 0) {
184
+ const firstWalVersion = walEntries[0].version;
185
+ const expectedFirst = initialVersion + 1;
186
+ if (firstWalVersion !== expectedFirst) {
187
+ yield* Effect.logWarning("WAL version gap detected", {
188
+ documentId,
189
+ snapshotVersion: initialVersion,
190
+ firstWalVersion,
191
+ expectedFirst
192
+ });
193
+ yield* Metric.increment(storageVersionGaps);
194
+ }
195
+ for (let i = 1; i < walEntries.length; i++) {
196
+ const prev = walEntries[i - 1].version;
197
+ const curr = walEntries[i].version;
198
+ if (curr !== prev + 1) yield* Effect.logWarning("WAL internal gap detected", {
199
+ documentId,
200
+ previousVersion: prev,
201
+ currentVersion: curr
202
+ });
203
+ }
204
+ }
205
+ for (const entry of walEntries) {
206
+ const result = document.submit(entry.transaction);
207
+ if (!result.success) yield* Effect.logWarning("Skipping corrupted WAL entry", {
208
+ documentId,
209
+ version: entry.version,
210
+ reason: result.reason
211
+ });
212
+ }
213
+ if (storedDoc) yield* Metric.increment(documentsRestored);
214
+ else yield* Metric.increment(documentsCreated);
215
+ yield* Metric.incrementBy(documentsActive, 1);
216
+ /**
217
+ * Save snapshot to ColdStorage derived from WAL entries.
218
+ * This ensures snapshots are always based on durable WAL data.
219
+ * Idempotent: skips save if already snapshotted at target version.
220
+ * Truncate failures are non-fatal and will be retried on next snapshot.
221
+ */
222
+ const saveSnapshot = (targetVersion) => Effect.gen(function* () {
223
+ var _baseSnapshot$version;
224
+ if (targetVersion <= (yield* Ref.get(stateRef)).lastSnapshotVersion) return;
225
+ const snapshotStartTime = Date.now();
226
+ const baseSnapshotResult = yield* Effect.either(coldStorage.load(documentId));
227
+ if (baseSnapshotResult._tag === "Left") {
228
+ yield* Effect.logError("Failed to load base snapshot for WAL replay", {
229
+ documentId,
230
+ error: baseSnapshotResult.left
231
+ });
232
+ return;
233
+ }
234
+ const baseSnapshot = baseSnapshotResult.right;
235
+ const baseVersion = (_baseSnapshot$version = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.version) !== null && _baseSnapshot$version !== void 0 ? _baseSnapshot$version : 0;
236
+ const baseState = baseSnapshot === null || baseSnapshot === void 0 ? void 0 : baseSnapshot.state;
237
+ const walEntriesResult = yield* Effect.either(hotStorage.getEntries(documentId, baseVersion));
238
+ if (walEntriesResult._tag === "Left") {
239
+ yield* Effect.logError("Failed to load WAL entries for snapshot", {
240
+ documentId,
241
+ error: walEntriesResult.left
242
+ });
243
+ return;
244
+ }
245
+ const relevantEntries = walEntriesResult.right.filter((e) => e.version <= targetVersion);
246
+ if (relevantEntries.length === 0 && !baseSnapshot) return;
247
+ let snapshotState = baseState;
248
+ for (const entry of relevantEntries) {
249
+ const tempDoc = Document.make(config.schema, { initialState: snapshotState });
250
+ tempDoc.apply(entry.transaction.ops);
251
+ snapshotState = tempDoc.get();
252
+ }
253
+ if (snapshotState === void 0) return;
254
+ const snapshotVersion = relevantEntries.length > 0 ? relevantEntries[relevantEntries.length - 1].version : baseVersion;
255
+ if (snapshotVersion <= (yield* Ref.get(stateRef)).lastSnapshotVersion) return;
256
+ const storedDocument = {
257
+ state: snapshotState,
258
+ version: snapshotVersion,
259
+ schemaVersion: SCHEMA_VERSION,
260
+ savedAt: Date.now()
261
+ };
262
+ yield* Effect.catchAll(coldStorage.save(documentId, storedDocument), (e) => Effect.logError("Failed to save snapshot", {
263
+ documentId,
264
+ error: e
265
+ }));
266
+ const snapshotDuration = Date.now() - snapshotStartTime;
267
+ yield* Metric.increment(storageSnapshots);
268
+ yield* Metric.update(storageSnapshotLatency, snapshotDuration);
269
+ yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, {
270
+ lastSnapshotVersion: snapshotVersion,
271
+ lastSnapshotTime: Date.now(),
272
+ transactionsSinceSnapshot: 0
273
+ }));
274
+ yield* Effect.catchAll(hotStorage.truncate(documentId, snapshotVersion), (e) => Effect.logWarning("WAL truncate failed - will retry on next snapshot", {
275
+ documentId,
276
+ version: snapshotVersion,
277
+ error: e
278
+ }));
279
+ });
280
+ /**
281
+ * Check if snapshot should be triggered
282
+ */
283
+ const checkSnapshotTriggers = Effect.gen(function* () {
284
+ const state = yield* Ref.get(stateRef);
285
+ const now = Date.now();
286
+ const currentVersion = state.document.getVersion();
287
+ const intervalMs = Duration.toMillis(config.snapshot.interval);
288
+ const threshold = config.snapshot.transactionThreshold;
289
+ if (state.transactionsSinceSnapshot >= threshold) {
290
+ yield* saveSnapshot(currentVersion);
291
+ return;
292
+ }
293
+ if (now - state.lastSnapshotTime >= intervalMs) {
294
+ yield* saveSnapshot(currentVersion);
295
+ return;
296
+ }
297
+ });
298
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
299
+ yield* saveSnapshot((yield* Ref.get(stateRef)).document.getVersion());
300
+ yield* Metric.incrementBy(documentsActive, -1);
301
+ yield* Metric.increment(documentsEvicted);
302
+ yield* Effect.logDebug("Entity finalized", { documentId });
303
+ }));
304
+ return {
305
+ Submit: Effect.fnUntraced(function* ({ payload }) {
306
+ const submitStartTime = Date.now();
307
+ const state = yield* Ref.get(stateRef);
308
+ const transaction = decodeTransaction(payload.transaction);
309
+ const validation = state.document.validate(transaction);
310
+ if (!validation.valid) {
311
+ yield* Metric.increment(transactionsRejected);
312
+ const latency$1 = Date.now() - submitStartTime;
313
+ yield* Metric.update(transactionsLatency, latency$1);
314
+ return {
315
+ success: false,
316
+ reason: validation.reason
317
+ };
318
+ }
319
+ const walEntry = {
320
+ transaction,
321
+ version: validation.nextVersion,
322
+ timestamp: Date.now()
323
+ };
324
+ const appendResult = yield* Effect.either(hotStorage.appendWithCheck(documentId, walEntry, validation.nextVersion));
325
+ if (appendResult._tag === "Left") {
326
+ yield* Effect.logError("WAL append failed", {
327
+ documentId,
328
+ version: validation.nextVersion,
329
+ error: appendResult.left
330
+ });
331
+ yield* Metric.increment(walAppendFailures);
332
+ const latency$1 = Date.now() - submitStartTime;
333
+ yield* Metric.update(transactionsLatency, latency$1);
334
+ return {
335
+ success: false,
336
+ reason: "Storage unavailable. Please retry."
337
+ };
338
+ }
339
+ state.document.apply(transaction);
340
+ const latency = Date.now() - submitStartTime;
341
+ yield* Metric.update(transactionsLatency, latency);
342
+ yield* Metric.increment(transactionsProcessed);
343
+ yield* Metric.increment(storageWalAppends);
344
+ yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { transactionsSinceSnapshot: s.transactionsSinceSnapshot + 1 }));
345
+ yield* checkSnapshotTriggers;
346
+ return {
347
+ success: true,
348
+ version: validation.nextVersion
349
+ };
350
+ }),
351
+ GetSnapshot: Effect.fnUntraced(function* () {
352
+ return (yield* Ref.get(stateRef)).document.getSnapshot();
353
+ }),
354
+ Touch: Effect.fnUntraced(function* () {}),
355
+ SetPresence: Effect.fnUntraced(function* ({ payload }) {
356
+ const { connectionId, entry } = payload;
357
+ yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { presences: HashMap.set(s.presences, connectionId, entry) }));
358
+ yield* Metric.increment(presenceUpdates);
359
+ yield* Metric.incrementBy(presenceActive, 1);
360
+ const state = yield* Ref.get(stateRef);
361
+ const event = {
362
+ type: "presence_update",
363
+ id: connectionId,
364
+ data: entry.data,
365
+ userId: entry.userId
366
+ };
367
+ yield* PubSub.publish(state.presencePubSub, event);
368
+ }),
369
+ RemovePresence: Effect.fnUntraced(function* ({ payload }) {
370
+ const { connectionId } = payload;
371
+ const state = yield* Ref.get(stateRef);
372
+ if (!HashMap.has(state.presences, connectionId)) return;
373
+ yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { presences: HashMap.remove(s.presences, connectionId) }));
374
+ yield* Metric.incrementBy(presenceActive, -1);
375
+ const event = {
376
+ type: "presence_remove",
377
+ id: connectionId
378
+ };
379
+ yield* PubSub.publish(state.presencePubSub, event);
380
+ }),
381
+ GetPresenceSnapshot: Effect.fnUntraced(function* () {
382
+ const state = yield* Ref.get(stateRef);
383
+ const presences = {};
384
+ for (const [id, entry] of state.presences) presences[id] = entry;
385
+ return { presences };
386
+ })
387
+ };
388
+ });
389
+ var SubscriptionStoreTag = class extends Context.Tag("@voidhash/mimic-effect/SubscriptionStore")() {};
390
+ const subscriptionStoreLayer = Layer.effect(SubscriptionStoreTag, Effect.gen(function* () {
391
+ const documentPubSubs = yield* Ref.make(HashMap.empty());
392
+ const presencePubSubs = yield* Ref.make(HashMap.empty());
393
+ return {
394
+ getOrCreatePubSub: (documentId) => Effect.gen(function* () {
395
+ const current = yield* Ref.get(documentPubSubs);
396
+ const existing = HashMap.get(current, documentId);
397
+ if (existing._tag === "Some") return existing.value;
398
+ const pubsub = yield* PubSub.unbounded();
399
+ yield* Ref.update(documentPubSubs, (map) => HashMap.set(map, documentId, pubsub));
400
+ return pubsub;
401
+ }),
402
+ getOrCreatePresencePubSub: (documentId) => Effect.gen(function* () {
403
+ const current = yield* Ref.get(presencePubSubs);
404
+ const existing = HashMap.get(current, documentId);
405
+ if (existing._tag === "Some") return existing.value;
406
+ const pubsub = yield* PubSub.unbounded();
407
+ yield* Ref.update(presencePubSubs, (map) => HashMap.set(map, documentId, pubsub));
408
+ return pubsub;
409
+ })
410
+ };
411
+ }));
412
+ /**
413
+ * Create a MimicClusterServerEngine layer.
414
+ *
415
+ * This creates a clustered document management service using Effect Cluster.
416
+ * Each document becomes a cluster Entity with automatic sharding and failover.
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * // 1. Create the engine
421
+ * const Engine = MimicClusterServerEngine.make({
422
+ * schema: DocSchema,
423
+ * initial: { title: "Untitled" },
424
+ * presence: CursorPresence,
425
+ * maxIdleTime: "5 minutes",
426
+ * snapshot: { interval: "5 minutes", transactionThreshold: 100 },
427
+ * shardGroup: "documents",
428
+ * })
429
+ *
430
+ * // 2. Create the WebSocket route
431
+ * const MimicRoute = MimicServer.layerHttpLayerRouter({
432
+ * path: "/mimic",
433
+ * })
434
+ *
435
+ * // 3. Wire together with cluster infrastructure
436
+ * const MimicLive = MimicRoute.pipe(
437
+ * Layer.provide(Engine),
438
+ * Layer.provide(ColdStorage.S3.make(...)),
439
+ * Layer.provide(HotStorage.Redis.make(...)),
440
+ * Layer.provide(MimicAuthService.make(...)),
441
+ * Layer.provide(ClusterInfrastructure),
442
+ * )
443
+ * ```
444
+ */
445
+ const make = (config) => {
446
+ const resolvedConfig = resolveClusterConfig(config);
447
+ const configLayer = Layer.succeed(MimicClusterConfigTag, resolvedConfig);
448
+ const entityLayer = MimicDocumentEntity.toLayer(Effect.gen(function* () {
449
+ return yield* createEntityHandler(yield* MimicClusterConfigTag, yield* ColdStorageTag, yield* HotStorageTag);
450
+ }), {
451
+ maxIdleTime: resolvedConfig.maxIdleTime,
452
+ concurrency: 1,
453
+ mailboxCapacity: 4096
454
+ });
455
+ const engineLayer = Layer.scoped(MimicServerEngineTag, Effect.gen(function* () {
456
+ const makeClient = yield* MimicDocumentEntity.client;
457
+ const subscriptionStore = yield* SubscriptionStoreTag;
458
+ return {
459
+ submit: (documentId, transaction) => Effect.gen(function* () {
460
+ const client = makeClient(documentId);
461
+ const encodedTx = encodeTransaction(transaction);
462
+ const result = yield* client.Submit({ transaction: encodedTx }).pipe(Effect.catchAll((error) => Effect.succeed({
463
+ success: false,
464
+ reason: `Cluster error: ${String(error)}`
465
+ })));
466
+ if (result.success) {
467
+ const pubsub = yield* subscriptionStore.getOrCreatePubSub(documentId);
468
+ yield* PubSub.publish(pubsub, {
469
+ type: "transaction",
470
+ transaction,
471
+ version: result.version
472
+ });
473
+ }
474
+ return result;
475
+ }),
476
+ getSnapshot: (documentId) => Effect.gen(function* () {
477
+ return yield* makeClient(documentId).GetSnapshot(void 0).pipe(Effect.orDie);
478
+ }),
479
+ subscribe: (documentId) => Effect.gen(function* () {
480
+ const pubsub = yield* subscriptionStore.getOrCreatePubSub(documentId);
481
+ return Stream.fromPubSub(pubsub);
482
+ }),
483
+ touch: (documentId) => Effect.gen(function* () {
484
+ yield* makeClient(documentId).Touch(void 0).pipe(Effect.orDie);
485
+ }),
486
+ getPresenceSnapshot: (documentId) => Effect.gen(function* () {
487
+ return yield* makeClient(documentId).GetPresenceSnapshot(void 0).pipe(Effect.orDie);
488
+ }),
489
+ setPresence: (documentId, connectionId, entry) => Effect.gen(function* () {
490
+ yield* makeClient(documentId).SetPresence({
491
+ connectionId,
492
+ entry
493
+ }).pipe(Effect.orDie);
494
+ const pubsub = yield* subscriptionStore.getOrCreatePresencePubSub(documentId);
495
+ yield* PubSub.publish(pubsub, {
496
+ type: "presence_update",
497
+ id: connectionId,
498
+ data: entry.data,
499
+ userId: entry.userId
500
+ });
501
+ }),
502
+ removePresence: (documentId, connectionId) => Effect.gen(function* () {
503
+ yield* makeClient(documentId).RemovePresence({ connectionId }).pipe(Effect.orDie);
504
+ const pubsub = yield* subscriptionStore.getOrCreatePresencePubSub(documentId);
505
+ yield* PubSub.publish(pubsub, {
506
+ type: "presence_remove",
507
+ id: connectionId
508
+ });
509
+ }),
510
+ subscribePresence: (documentId) => Effect.gen(function* () {
511
+ const pubsub = yield* subscriptionStore.getOrCreatePresencePubSub(documentId);
512
+ return Stream.fromPubSub(pubsub);
513
+ }),
514
+ config: resolvedConfig
515
+ };
516
+ }));
517
+ return Layer.mergeAll(entityLayer, engineLayer).pipe(Layer.provideMerge(subscriptionStoreLayer), Layer.provideMerge(configLayer));
518
+ };
519
+ const MimicClusterServerEngine = { make };
520
+
521
+ //#endregion
522
+ export { MimicClusterServerEngine };
523
+ //# sourceMappingURL=MimicClusterServerEngine.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MimicClusterServerEngine.mjs","names":["Transaction","initialState: Primitive.InferSetInput<TSchema> | undefined","Metrics.storageVersionGaps","Metrics.documentsRestored","Metrics.documentsCreated","Metrics.documentsActive","snapshotState: Primitive.InferState<TSchema> | undefined","storedDocument: StoredDocument","Metrics.storageSnapshots","Metrics.storageSnapshotLatency","Metrics.documentsEvicted","Metrics.transactionsRejected","latency","Metrics.transactionsLatency","walEntry: WalEntry","Metrics.walAppendFailures","Metrics.transactionsProcessed","Metrics.storageWalAppends","Metrics.presenceUpdates","Metrics.presenceActive","event: PresenceEvent","presences: Record<string, PresenceEntry>"],"sources":["../src/MimicClusterServerEngine.ts"],"sourcesContent":["/**\n * @voidhash/mimic-effect - MimicClusterServerEngine\n *\n * Clustered document management service using Effect Cluster for horizontal scaling.\n * Each document becomes a cluster Entity with automatic sharding, failover, and location-transparent routing.\n *\n * This is an alternative to MimicServerEngine for distributed deployments.\n */\nimport {\n Context,\n Duration,\n Effect,\n HashMap,\n Layer,\n Metric,\n Option,\n PubSub,\n Ref,\n Schema,\n Scope,\n Stream,\n} from \"effect\";\nimport { Entity, Sharding } from \"@effect/cluster\";\nimport { Rpc } from \"@effect/rpc\";\nimport { Document, type Presence, type Primitive, type Transaction } from \"@voidhash/mimic\";\nimport { ServerDocument } from \"@voidhash/mimic/server\";\nimport type {\n MimicClusterServerEngineConfig,\n PresenceEntry,\n PresenceEvent,\n PresenceSnapshot,\n ResolvedClusterConfig,\n StoredDocument,\n WalEntry,\n} from \"./Types\";\nimport type * as Protocol from \"./Protocol\";\nimport { ColdStorageTag, type ColdStorage } from \"./ColdStorage\";\nimport { HotStorageTag, type HotStorage } from \"./HotStorage\";\nimport { MimicAuthServiceTag } from \"./MimicAuthService\";\nimport { MimicServerEngineTag, type MimicServerEngine } from \"./MimicServerEngine\";\nimport type { SubmitResult } from \"./DocumentManager\";\nimport * as Metrics from \"./Metrics\";\n\n// =============================================================================\n// Default Configuration\n// =============================================================================\n\nconst DEFAULT_MAX_IDLE_TIME = Duration.minutes(5);\nconst DEFAULT_MAX_TRANSACTION_HISTORY = 1000;\nconst DEFAULT_SNAPSHOT_INTERVAL = Duration.minutes(5);\nconst DEFAULT_SNAPSHOT_THRESHOLD = 100;\nconst DEFAULT_SHARD_GROUP = \"mimic-documents\";\n\n// =============================================================================\n// RPC Schemas\n// =============================================================================\n\n/**\n * Schema for encoded transaction (wire format)\n */\nconst EncodedTransactionSchema = Schema.Struct({\n id: Schema.String,\n ops: Schema.Array(Schema.Unknown),\n});\n\n/**\n * Schema for submit result\n */\nconst SubmitResultSchema = Schema.Union(\n Schema.Struct({\n success: Schema.Literal(true),\n version: Schema.Number,\n }),\n Schema.Struct({\n success: Schema.Literal(false),\n reason: Schema.String,\n })\n);\n\n/**\n * Schema for snapshot response\n */\nconst SnapshotResponseSchema = Schema.Struct({\n state: Schema.Unknown,\n version: Schema.Number,\n});\n\n/**\n * Schema for presence entry\n */\nconst PresenceEntrySchema = Schema.Struct({\n data: Schema.Unknown,\n userId: Schema.optional(Schema.String),\n});\n\n/**\n * Schema for presence snapshot response\n */\nconst PresenceSnapshotResponseSchema = Schema.Struct({\n presences: Schema.Record({ key: Schema.String, value: PresenceEntrySchema }),\n});\n\n/**\n * Schema for presence event\n */\nconst PresenceEventSchema = Schema.Union(\n Schema.Struct({\n type: Schema.Literal(\"presence_update\"),\n id: Schema.String,\n data: Schema.Unknown,\n userId: Schema.optional(Schema.String),\n }),\n Schema.Struct({\n type: Schema.Literal(\"presence_remove\"),\n id: Schema.String,\n })\n);\n\n/**\n * Schema for server message (for broadcasts)\n */\nconst ServerMessageSchema = Schema.Unknown;\n\n// =============================================================================\n// Mimic Document Entity Definition\n// =============================================================================\n\n/**\n * Define the Mimic Document Entity with its RPC protocol.\n * This entity handles document operations for a single documentId.\n */\nconst MimicDocumentEntity = Entity.make(\"MimicDocument\", [\n // Submit a transaction\n Rpc.make(\"Submit\", {\n payload: { transaction: EncodedTransactionSchema },\n success: SubmitResultSchema,\n }),\n\n // Get document snapshot\n Rpc.make(\"GetSnapshot\", {\n success: SnapshotResponseSchema,\n }),\n\n // Touch document to prevent idle GC\n Rpc.make(\"Touch\", {\n success: Schema.Void,\n }),\n\n // Set presence for a connection\n Rpc.make(\"SetPresence\", {\n payload: {\n connectionId: Schema.String,\n entry: PresenceEntrySchema,\n },\n success: Schema.Void,\n }),\n\n // Remove presence for a connection\n Rpc.make(\"RemovePresence\", {\n payload: { connectionId: Schema.String },\n success: Schema.Void,\n }),\n\n // Get presence snapshot\n Rpc.make(\"GetPresenceSnapshot\", {\n success: PresenceSnapshotResponseSchema,\n }),\n]);\n\n// =============================================================================\n// Entity State Types\n// =============================================================================\n\n/**\n * Document state managed by the entity\n */\ninterface EntityDocumentState<TSchema extends Primitive.AnyPrimitive> {\n readonly document: ServerDocument.ServerDocument<TSchema>;\n readonly broadcastPubSub: PubSub.PubSub<Protocol.ServerMessage>;\n readonly presences: HashMap.HashMap<string, PresenceEntry>;\n readonly presencePubSub: PubSub.PubSub<PresenceEvent>;\n readonly lastSnapshotVersion: number;\n readonly lastSnapshotTime: number;\n readonly transactionsSinceSnapshot: number;\n}\n\n// =============================================================================\n// Config Context Tag\n// =============================================================================\n\n/**\n * Context tag for cluster engine configuration\n */\nclass MimicClusterConfigTag extends Context.Tag(\n \"@voidhash/mimic-effect/MimicClusterConfig\"\n)<MimicClusterConfigTag, ResolvedClusterConfig<Primitive.AnyPrimitive>>() {}\n\n// =============================================================================\n// Resolve Configuration\n// =============================================================================\n\nconst resolveClusterConfig = <TSchema extends Primitive.AnyPrimitive>(\n config: MimicClusterServerEngineConfig<TSchema>\n): ResolvedClusterConfig<TSchema> => ({\n schema: config.schema,\n initial: config.initial,\n presence: config.presence,\n maxIdleTime: config.maxIdleTime\n ? Duration.decode(config.maxIdleTime)\n : DEFAULT_MAX_IDLE_TIME,\n maxTransactionHistory:\n config.maxTransactionHistory ?? DEFAULT_MAX_TRANSACTION_HISTORY,\n snapshot: {\n interval: config.snapshot?.interval\n ? Duration.decode(config.snapshot.interval)\n : DEFAULT_SNAPSHOT_INTERVAL,\n transactionThreshold:\n config.snapshot?.transactionThreshold ?? DEFAULT_SNAPSHOT_THRESHOLD,\n },\n shardGroup: config.shardGroup ?? DEFAULT_SHARD_GROUP,\n});\n\n// =============================================================================\n// Helper to decode/encode transactions\n// =============================================================================\n\n/**\n * Decode an encoded transaction to a Transaction object\n */\nconst decodeTransaction = (\n encoded: { id: string; ops: readonly unknown[] }\n): Transaction.Transaction => {\n // Import Transaction dynamically to avoid circular deps\n const { Transaction } = require(\"@voidhash/mimic\");\n return Transaction.decode(encoded as Transaction.EncodedTransaction);\n};\n\n/**\n * Encode a Transaction to wire format\n */\nconst encodeTransaction = (\n tx: Transaction.Transaction\n): { id: string; ops: readonly unknown[] } => {\n const { Transaction } = require(\"@voidhash/mimic\");\n return Transaction.encode(tx);\n};\n\n// =============================================================================\n// Entity Handler Factory\n// =============================================================================\n\n/**\n * Create the entity handler for MimicDocument\n */\nconst createEntityHandler = <TSchema extends Primitive.AnyPrimitive>(\n config: ResolvedClusterConfig<TSchema>,\n coldStorage: ColdStorage,\n hotStorage: HotStorage\n) =>\n Effect.gen(function* () {\n // Get entity address to determine documentId\n const address = yield* Entity.CurrentAddress;\n const documentId = address.entityId;\n\n // Current schema version (hard-coded to 1 for now)\n const SCHEMA_VERSION = 1;\n\n // Compute initial state\n const computeInitialState = (): Effect.Effect<\n Primitive.InferSetInput<TSchema> | undefined\n > => {\n if (config.initial === undefined) {\n return Effect.succeed(undefined);\n }\n\n if (typeof config.initial === \"function\") {\n return (\n config.initial as (ctx: {\n documentId: string;\n }) => Effect.Effect<Primitive.InferSetInput<TSchema>>\n )({ documentId });\n }\n\n return Effect.succeed(\n config.initial as Primitive.InferSetInput<TSchema>\n );\n };\n\n // Load snapshot from ColdStorage (fatal if unavailable - entity cannot start)\n const storedDoc = yield* coldStorage.load(documentId).pipe(\n Effect.orDie // Entity cannot initialize without storage\n );\n\n let initialState: Primitive.InferSetInput<TSchema> | undefined;\n let initialVersion = 0;\n\n if (storedDoc) {\n initialState =\n storedDoc.state as Primitive.InferSetInput<TSchema>;\n initialVersion = storedDoc.version;\n } else {\n initialState = yield* computeInitialState();\n }\n\n // Create PubSubs for broadcasting\n const broadcastPubSub = yield* PubSub.unbounded<Protocol.ServerMessage>();\n const presencePubSub = yield* PubSub.unbounded<PresenceEvent>();\n\n // Create state ref\n const stateRef = yield* Ref.make<EntityDocumentState<TSchema>>({\n document: undefined as unknown as ServerDocument.ServerDocument<TSchema>,\n broadcastPubSub,\n presences: HashMap.empty(),\n presencePubSub,\n lastSnapshotVersion: initialVersion,\n lastSnapshotTime: Date.now(),\n transactionsSinceSnapshot: 0,\n });\n\n // 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 Effect.runSync(\n PubSub.publish(broadcastPubSub, {\n type: \"transaction\",\n transaction: message.transaction,\n version: message.version,\n } as Protocol.ServerMessage)\n );\n },\n onRejection: (transactionId: string, reason: string) => {\n Effect.runSync(\n PubSub.publish(broadcastPubSub, {\n type: \"error\",\n transactionId,\n reason,\n } as Protocol.ServerMessage)\n );\n },\n });\n\n // Update state with document\n yield* Ref.update(stateRef, (s) => ({ ...s, document }));\n\n // Load WAL entries (fatal if unavailable - entity cannot start)\n const walEntries = yield* hotStorage.getEntries(documentId, initialVersion).pipe(\n Effect.orDie // Entity cannot initialize without storage\n );\n\n // 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 // 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 // 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 * Save 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 = (targetVersion: number) => Effect.gen(function* () {\n const state = yield* Ref.get(stateRef);\n\n // Idempotency check: skip if already snapshotted at this version\n if (targetVersion <= state.lastSnapshotVersion) {\n return;\n }\n\n const snapshotStartTime = Date.now();\n\n // Load base snapshot from cold storage (best effort - log error but don't crash entity)\n const baseSnapshotResult = yield* Effect.either(coldStorage.load(documentId));\n if (baseSnapshotResult._tag === \"Left\") {\n yield* Effect.logError(\"Failed to load base snapshot for WAL replay\", {\n documentId,\n error: baseSnapshotResult.left,\n });\n return;\n }\n const baseSnapshot = baseSnapshotResult.right;\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 walEntriesResult = yield* Effect.either(hotStorage.getEntries(documentId, baseVersion));\n if (walEntriesResult._tag === \"Left\") {\n yield* Effect.logError(\"Failed to load WAL entries for snapshot\", {\n documentId,\n error: walEntriesResult.left,\n });\n return;\n }\n const walEntries = walEntriesResult.right;\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<TSchema> | 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 currentState = yield* Ref.get(stateRef);\n if (snapshotVersion <= currentState.lastSnapshotVersion) {\n return;\n }\n\n const storedDocument: StoredDocument = {\n state: snapshotState,\n version: snapshotVersion,\n schemaVersion: SCHEMA_VERSION,\n savedAt: Date.now(),\n };\n\n // Save to ColdStorage (best effort - log error but don't crash entity)\n yield* Effect.catchAll(\n coldStorage.save(documentId, storedDocument),\n (e) =>\n Effect.logError(\"Failed to save snapshot\", { documentId, error: e })\n );\n\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.update(stateRef, (s) => ({\n ...s,\n lastSnapshotVersion: snapshotVersion,\n lastSnapshotTime: Date.now(),\n transactionsSinceSnapshot: 0,\n }));\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 = Effect.gen(function* () {\n const state = yield* Ref.get(stateRef);\n const now = Date.now();\n const currentVersion = state.document.getVersion();\n\n const intervalMs = Duration.toMillis(config.snapshot.interval);\n const threshold = config.snapshot.transactionThreshold;\n\n if (state.transactionsSinceSnapshot >= threshold) {\n yield* saveSnapshot(currentVersion);\n return;\n }\n\n if (now - state.lastSnapshotTime >= intervalMs) {\n yield* saveSnapshot(currentVersion);\n return;\n }\n });\n\n // Cleanup on entity finalization\n yield* Effect.addFinalizer(() =>\n Effect.gen(function* () {\n // Save final snapshot before entity is garbage collected\n const state = yield* Ref.get(stateRef);\n const currentVersion = state.document.getVersion();\n yield* saveSnapshot(currentVersion);\n yield* Metric.incrementBy(Metrics.documentsActive, -1);\n yield* Metric.increment(Metrics.documentsEvicted);\n yield* Effect.logDebug(\"Entity finalized\", { documentId });\n })\n );\n\n // Return RPC handlers\n return {\n Submit: Effect.fnUntraced(function* ({ payload }) {\n const submitStartTime = Date.now();\n const state = yield* Ref.get(stateRef);\n\n // Decode transaction\n const transaction = decodeTransaction(payload.transaction);\n\n // Phase 1: Validate (no side effects)\n const validation = state.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 state.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(stateRef, (s) => ({\n ...s,\n transactionsSinceSnapshot: s.transactionsSinceSnapshot + 1,\n }));\n\n // Check snapshot triggers\n yield* checkSnapshotTriggers;\n\n return {\n success: true as const,\n version: validation.nextVersion,\n };\n }),\n\n GetSnapshot: Effect.fnUntraced(function* () {\n const state = yield* Ref.get(stateRef);\n return state.document.getSnapshot();\n }),\n\n Touch: Effect.fnUntraced(function* () {\n // Entity touch is handled automatically by the cluster framework\n // Just update last activity time conceptually\n return void 0;\n }),\n\n SetPresence: Effect.fnUntraced(function* ({ payload }) {\n const { connectionId, entry } = payload;\n\n yield* Ref.update(stateRef, (s) => ({\n ...s,\n presences: HashMap.set(s.presences, connectionId, entry),\n }));\n\n yield* Metric.increment(Metrics.presenceUpdates);\n yield* Metric.incrementBy(Metrics.presenceActive, 1);\n\n const state = yield* Ref.get(stateRef);\n const event: PresenceEvent = {\n type: \"presence_update\",\n id: connectionId,\n data: entry.data,\n userId: entry.userId,\n };\n yield* PubSub.publish(state.presencePubSub, event);\n }),\n\n RemovePresence: Effect.fnUntraced(function* ({ payload }) {\n const { connectionId } = payload;\n const state = yield* Ref.get(stateRef);\n\n if (!HashMap.has(state.presences, connectionId)) {\n return;\n }\n\n yield* Ref.update(stateRef, (s) => ({\n ...s,\n presences: HashMap.remove(s.presences, connectionId),\n }));\n\n yield* Metric.incrementBy(Metrics.presenceActive, -1);\n\n const event: PresenceEvent = {\n type: \"presence_remove\",\n id: connectionId,\n };\n yield* PubSub.publish(state.presencePubSub, event);\n }),\n\n GetPresenceSnapshot: Effect.fnUntraced(function* () {\n const state = yield* Ref.get(stateRef);\n const presences: Record<string, PresenceEntry> = {};\n for (const [id, entry] of state.presences) {\n presences[id] = entry;\n }\n return { presences };\n }),\n };\n });\n\n// =============================================================================\n// Subscription Store (for managing subscriptions at the gateway level)\n// =============================================================================\n\n/**\n * Store for managing document subscriptions\n * This is needed because cluster entities don't support streaming directly\n */\ninterface SubscriptionStore {\n readonly getOrCreatePubSub: (\n documentId: string\n ) => Effect.Effect<PubSub.PubSub<Protocol.ServerMessage>>;\n readonly getOrCreatePresencePubSub: (\n documentId: string\n ) => Effect.Effect<PubSub.PubSub<PresenceEvent>>;\n}\n\nclass SubscriptionStoreTag extends Context.Tag(\n \"@voidhash/mimic-effect/SubscriptionStore\"\n)<SubscriptionStoreTag, SubscriptionStore>() {}\n\nconst subscriptionStoreLayer = Layer.effect(\n SubscriptionStoreTag,\n Effect.gen(function* () {\n const documentPubSubs = yield* Ref.make(\n HashMap.empty<string, PubSub.PubSub<Protocol.ServerMessage>>()\n );\n const presencePubSubs = yield* Ref.make(\n HashMap.empty<string, PubSub.PubSub<PresenceEvent>>()\n );\n\n return {\n getOrCreatePubSub: (documentId: string) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(documentPubSubs);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n return existing.value;\n }\n\n const pubsub = yield* PubSub.unbounded<Protocol.ServerMessage>();\n yield* Ref.update(documentPubSubs, (map) =>\n HashMap.set(map, documentId, pubsub)\n );\n return pubsub;\n }),\n\n getOrCreatePresencePubSub: (documentId: string) =>\n Effect.gen(function* () {\n const current = yield* Ref.get(presencePubSubs);\n const existing = HashMap.get(current, documentId);\n if (existing._tag === \"Some\") {\n return existing.value;\n }\n\n const pubsub = yield* PubSub.unbounded<PresenceEvent>();\n yield* Ref.update(presencePubSubs, (map) =>\n HashMap.set(map, documentId, pubsub)\n );\n return pubsub;\n }),\n };\n })\n);\n\n// =============================================================================\n// Factory\n// =============================================================================\n\n/**\n * Create a MimicClusterServerEngine layer.\n *\n * This creates a clustered document management service using Effect Cluster.\n * Each document becomes a cluster Entity with automatic sharding and failover.\n *\n * @example\n * ```typescript\n * // 1. Create the engine\n * const Engine = MimicClusterServerEngine.make({\n * schema: DocSchema,\n * initial: { title: \"Untitled\" },\n * presence: CursorPresence,\n * maxIdleTime: \"5 minutes\",\n * snapshot: { interval: \"5 minutes\", transactionThreshold: 100 },\n * shardGroup: \"documents\",\n * })\n *\n * // 2. Create the WebSocket route\n * const MimicRoute = MimicServer.layerHttpLayerRouter({\n * path: \"/mimic\",\n * })\n *\n * // 3. Wire together with cluster infrastructure\n * const MimicLive = MimicRoute.pipe(\n * Layer.provide(Engine),\n * Layer.provide(ColdStorage.S3.make(...)),\n * Layer.provide(HotStorage.Redis.make(...)),\n * Layer.provide(MimicAuthService.make(...)),\n * Layer.provide(ClusterInfrastructure),\n * )\n * ```\n */\nexport const make = <TSchema extends Primitive.AnyPrimitive>(\n config: MimicClusterServerEngineConfig<TSchema>\n): Layer.Layer<\n MimicServerEngineTag,\n never,\n ColdStorageTag | HotStorageTag | MimicAuthServiceTag | Sharding.Sharding\n> => {\n const resolvedConfig = resolveClusterConfig(config);\n\n // Create config layer\n const configLayer = Layer.succeed(\n MimicClusterConfigTag,\n resolvedConfig as ResolvedClusterConfig<Primitive.AnyPrimitive>\n );\n\n // Create entity layer that registers with sharding\n const entityLayer = MimicDocumentEntity.toLayer(\n Effect.gen(function* () {\n const clusterConfig = yield* MimicClusterConfigTag;\n const coldStorage = yield* ColdStorageTag;\n const hotStorage = yield* HotStorageTag;\n\n return yield* createEntityHandler(\n clusterConfig as ResolvedClusterConfig<TSchema>,\n coldStorage,\n hotStorage\n );\n }),\n {\n maxIdleTime: resolvedConfig.maxIdleTime,\n concurrency: 1, // Sequential message processing per document\n mailboxCapacity: 4096,\n }\n );\n\n // Create the engine service\n const engineLayer = Layer.scoped(\n MimicServerEngineTag,\n Effect.gen(function* () {\n // Get entity client maker\n const makeClient = yield* MimicDocumentEntity.client;\n\n // Get subscription store\n const subscriptionStore = yield* SubscriptionStoreTag;\n\n const engine: MimicServerEngine = {\n submit: (documentId, transaction) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n const encodedTx = encodeTransaction(transaction);\n const result = yield* client.Submit({\n transaction: encodedTx as { id: string; ops: unknown[] },\n }).pipe(\n Effect.catchAll((error) =>\n Effect.succeed({\n success: false as const,\n reason: `Cluster error: ${String(error)}`,\n })\n )\n );\n\n // Broadcast to local subscribers if success\n if (result.success) {\n const pubsub =\n yield* subscriptionStore.getOrCreatePubSub(documentId);\n yield* PubSub.publish(pubsub, {\n type: \"transaction\",\n transaction,\n version: result.version,\n } as Protocol.ServerMessage);\n }\n\n return result;\n }),\n\n getSnapshot: (documentId) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n return yield* client.GetSnapshot(undefined as void).pipe(Effect.orDie);\n }),\n\n subscribe: (documentId) =>\n Effect.gen(function* () {\n const pubsub =\n yield* subscriptionStore.getOrCreatePubSub(documentId);\n return Stream.fromPubSub(pubsub);\n }),\n\n touch: (documentId) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n yield* client.Touch(undefined as void).pipe(Effect.orDie);\n }),\n\n getPresenceSnapshot: (documentId) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n return yield* client.GetPresenceSnapshot(undefined as void).pipe(Effect.orDie);\n }),\n\n setPresence: (documentId, connectionId, entry) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n yield* client.SetPresence({ connectionId, entry }).pipe(Effect.orDie);\n\n // Broadcast to local presence subscribers\n const pubsub =\n yield* subscriptionStore.getOrCreatePresencePubSub(documentId);\n yield* PubSub.publish(pubsub, {\n type: \"presence_update\",\n id: connectionId,\n data: entry.data,\n userId: entry.userId,\n });\n }),\n\n removePresence: (documentId, connectionId) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n yield* client.RemovePresence({ connectionId }).pipe(Effect.orDie);\n\n // Broadcast to local presence subscribers\n const pubsub =\n yield* subscriptionStore.getOrCreatePresencePubSub(documentId);\n yield* PubSub.publish(pubsub, {\n type: \"presence_remove\",\n id: connectionId,\n });\n }),\n\n subscribePresence: (documentId) =>\n Effect.gen(function* () {\n const pubsub =\n yield* subscriptionStore.getOrCreatePresencePubSub(documentId);\n return Stream.fromPubSub(pubsub);\n }),\n\n config: resolvedConfig as ResolvedClusterConfig<Primitive.AnyPrimitive>,\n };\n\n return engine;\n })\n );\n\n // Compose all layers\n return Layer.mergeAll(entityLayer, engineLayer).pipe(\n Layer.provideMerge(subscriptionStoreLayer),\n Layer.provideMerge(configLayer)\n );\n};\n\n// =============================================================================\n// Re-export namespace\n// =============================================================================\n\nexport const MimicClusterServerEngine = {\n make,\n};\n"],"mappings":"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"}