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