@voidhash/mimic-effect 1.0.0-beta.16 → 1.0.0-beta.17
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.
- package/dist/ColdStorage.cjs +1 -1
- package/dist/ColdStorage.d.cts +2 -2
- package/dist/ColdStorage.d.cts.map +1 -1
- package/dist/ColdStorage.d.mts +2 -2
- package/dist/ColdStorage.d.mts.map +1 -1
- package/dist/ColdStorage.mjs +2 -2
- package/dist/ColdStorage.mjs.map +1 -1
- package/dist/DocumentInstance.cjs +13 -13
- package/dist/DocumentInstance.mjs +13 -13
- package/dist/DocumentInstance.mjs.map +1 -1
- package/dist/Errors.d.cts +8 -8
- package/dist/Errors.d.cts.map +1 -1
- package/dist/Errors.d.mts +8 -8
- package/dist/Errors.d.mts.map +1 -1
- package/dist/HotStorage.cjs +1 -1
- package/dist/HotStorage.d.cts +2 -2
- package/dist/HotStorage.d.mts +2 -2
- package/dist/HotStorage.mjs +2 -2
- package/dist/HotStorage.mjs.map +1 -1
- package/dist/Metrics.cjs +6 -6
- package/dist/Metrics.d.cts +21 -23
- package/dist/Metrics.d.cts.map +1 -1
- package/dist/Metrics.d.mts +21 -23
- package/dist/Metrics.d.mts.map +1 -1
- package/dist/Metrics.mjs +7 -7
- package/dist/Metrics.mjs.map +1 -1
- package/dist/MimicAuthService.cjs +1 -1
- package/dist/MimicAuthService.d.cts +2 -2
- package/dist/MimicAuthService.d.cts.map +1 -1
- package/dist/MimicAuthService.d.mts +2 -2
- package/dist/MimicAuthService.d.mts.map +1 -1
- package/dist/MimicAuthService.mjs +2 -2
- package/dist/MimicAuthService.mjs.map +1 -1
- package/dist/MimicClusterServerEngine.cjs +38 -41
- package/dist/MimicClusterServerEngine.d.cts +1 -1
- package/dist/MimicClusterServerEngine.d.mts +1 -1
- package/dist/MimicClusterServerEngine.mjs +31 -34
- package/dist/MimicClusterServerEngine.mjs.map +1 -1
- package/dist/MimicServer.cjs +23 -23
- package/dist/MimicServer.d.cts +3 -3
- package/dist/MimicServer.d.cts.map +1 -1
- package/dist/MimicServer.d.mts +3 -3
- package/dist/MimicServer.d.mts.map +1 -1
- package/dist/MimicServer.mjs +22 -22
- package/dist/MimicServer.mjs.map +1 -1
- package/dist/MimicServerEngine.cjs +13 -13
- package/dist/MimicServerEngine.d.cts +2 -2
- package/dist/MimicServerEngine.d.mts +2 -2
- package/dist/MimicServerEngine.mjs +14 -14
- package/dist/MimicServerEngine.mjs.map +1 -1
- package/dist/PresenceManager.cjs +4 -4
- package/dist/PresenceManager.d.cts +2 -2
- package/dist/PresenceManager.d.mts +2 -2
- package/dist/PresenceManager.mjs +5 -5
- package/dist/PresenceManager.mjs.map +1 -1
- package/dist/Types.d.cts +1 -1
- package/dist/Types.d.mts +1 -1
- package/dist/testing/ColdStorageTestSuite.cjs +3 -3
- package/dist/testing/ColdStorageTestSuite.mjs +3 -3
- package/dist/testing/ColdStorageTestSuite.mjs.map +1 -1
- package/dist/testing/HotStorageTestSuite.cjs +13 -13
- package/dist/testing/HotStorageTestSuite.mjs +13 -13
- package/dist/testing/HotStorageTestSuite.mjs.map +1 -1
- package/dist/testing/StorageIntegrationTestSuite.cjs +3 -3
- package/dist/testing/StorageIntegrationTestSuite.mjs +3 -3
- package/dist/testing/StorageIntegrationTestSuite.mjs.map +1 -1
- package/dist/testing/types.d.cts +1 -1
- package/dist/testing/types.d.cts.map +1 -1
- package/dist/testing/types.d.mts +3 -3
- package/dist/testing/types.d.mts.map +1 -1
- package/package.json +17 -21
- package/src/ColdStorage.ts +4 -5
- package/src/DocumentInstance.ts +13 -13
- package/src/HotStorage.ts +3 -3
- package/src/Metrics.ts +22 -16
- package/src/MimicAuthService.ts +3 -3
- package/src/MimicClusterServerEngine.ts +35 -35
- package/src/MimicServer.ts +26 -30
- package/src/MimicServerEngine.ts +15 -15
- package/src/PresenceManager.ts +6 -6
- package/src/Types.ts +1 -1
- package/src/testing/ColdStorageTestSuite.ts +3 -3
- package/src/testing/HotStorageTestSuite.ts +17 -17
- package/src/testing/StorageIntegrationTestSuite.ts +3 -3
- package/.turbo/turbo-build.log +0 -154
- package/tests/ColdStorage.test.ts +0 -24
- package/tests/DocumentInstance.test.ts +0 -669
- package/tests/HotStorage.test.ts +0 -24
- package/tests/MimicAuthService.test.ts +0 -153
- package/tests/MimicClusterServerEngine.test.ts +0 -587
- package/tests/MimicServer.test.ts +0 -142
- package/tests/MimicServerEngine.test.ts +0 -547
- package/tests/PresenceManager.test.ts +0 -380
- package/tests/Protocol.test.ts +0 -190
- package/tests/StorageIntegration.test.ts +0 -259
- package/tsconfig.build.json +0 -24
- package/tsconfig.json +0 -8
- package/tsdown.config.ts +0 -18
- package/vitest.mts +0 -11
|
@@ -5,8 +5,8 @@ const require_DocumentInstance = require('./DocumentInstance.cjs');
|
|
|
5
5
|
const require_objectSpread2 = require('./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs');
|
|
6
6
|
const require_MimicServerEngine = require('./MimicServerEngine.cjs');
|
|
7
7
|
let effect = require("effect");
|
|
8
|
-
let
|
|
9
|
-
let
|
|
8
|
+
let effect_unstable_cluster = require("effect/unstable/cluster");
|
|
9
|
+
let effect_unstable_rpc = require("effect/unstable/rpc");
|
|
10
10
|
|
|
11
11
|
//#region src/MimicClusterServerEngine.ts
|
|
12
12
|
/**
|
|
@@ -28,90 +28,87 @@ const DEFAULT_SHARD_GROUP = "mimic-documents";
|
|
|
28
28
|
*/
|
|
29
29
|
const EncodedTransactionSchema = effect.Schema.Struct({
|
|
30
30
|
id: effect.Schema.String,
|
|
31
|
-
ops: effect.Schema.Array(effect.Schema.
|
|
31
|
+
ops: effect.Schema.Array(effect.Schema.Any)
|
|
32
32
|
});
|
|
33
33
|
/**
|
|
34
34
|
* Schema for submit result
|
|
35
35
|
*/
|
|
36
|
-
const SubmitResultSchema = effect.Schema.Union(effect.Schema.Struct({
|
|
36
|
+
const SubmitResultSchema = effect.Schema.Union([effect.Schema.Struct({
|
|
37
37
|
success: effect.Schema.Literal(true),
|
|
38
38
|
version: effect.Schema.Number
|
|
39
39
|
}), effect.Schema.Struct({
|
|
40
40
|
success: effect.Schema.Literal(false),
|
|
41
41
|
reason: effect.Schema.String
|
|
42
|
-
}));
|
|
42
|
+
})]);
|
|
43
43
|
/**
|
|
44
44
|
* Schema for snapshot response
|
|
45
45
|
*/
|
|
46
46
|
const SnapshotResponseSchema = effect.Schema.Struct({
|
|
47
|
-
state: effect.Schema.
|
|
47
|
+
state: effect.Schema.Any,
|
|
48
48
|
version: effect.Schema.Number
|
|
49
49
|
});
|
|
50
50
|
/**
|
|
51
51
|
* Schema for presence entry
|
|
52
52
|
*/
|
|
53
53
|
const PresenceEntrySchema = effect.Schema.Struct({
|
|
54
|
-
data: effect.Schema.
|
|
54
|
+
data: effect.Schema.Any,
|
|
55
55
|
userId: effect.Schema.optional(effect.Schema.String)
|
|
56
56
|
});
|
|
57
57
|
/**
|
|
58
58
|
* Schema for presence snapshot response
|
|
59
59
|
*/
|
|
60
|
-
const PresenceSnapshotResponseSchema = effect.Schema.Struct({ presences: effect.Schema.Record(
|
|
61
|
-
|
|
62
|
-
value: PresenceEntrySchema
|
|
63
|
-
}) });
|
|
64
|
-
effect.Schema.Union(effect.Schema.Struct({
|
|
60
|
+
const PresenceSnapshotResponseSchema = effect.Schema.Struct({ presences: effect.Schema.Record(effect.Schema.String, PresenceEntrySchema) });
|
|
61
|
+
effect.Schema.Union([effect.Schema.Struct({
|
|
65
62
|
type: effect.Schema.Literal("presence_update"),
|
|
66
63
|
id: effect.Schema.String,
|
|
67
|
-
data: effect.Schema.
|
|
64
|
+
data: effect.Schema.Any,
|
|
68
65
|
userId: effect.Schema.optional(effect.Schema.String)
|
|
69
66
|
}), effect.Schema.Struct({
|
|
70
67
|
type: effect.Schema.Literal("presence_remove"),
|
|
71
68
|
id: effect.Schema.String
|
|
72
|
-
}));
|
|
73
|
-
effect.Schema.
|
|
69
|
+
})]);
|
|
70
|
+
effect.Schema.Any;
|
|
74
71
|
/**
|
|
75
72
|
* Define the Mimic Document Entity with its RPC protocol.
|
|
76
73
|
* This entity handles document operations for a single documentId.
|
|
77
74
|
*/
|
|
78
|
-
const MimicDocumentEntity =
|
|
79
|
-
|
|
75
|
+
const MimicDocumentEntity = effect_unstable_cluster.Entity.make("MimicDocument", [
|
|
76
|
+
effect_unstable_rpc.Rpc.make("Submit", {
|
|
80
77
|
payload: { transaction: EncodedTransactionSchema },
|
|
81
78
|
success: SubmitResultSchema
|
|
82
79
|
}),
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
effect_unstable_rpc.Rpc.make("GetSnapshot", { success: SnapshotResponseSchema }),
|
|
81
|
+
effect_unstable_rpc.Rpc.make("GetTreeSnapshot", { success: effect.Schema.Any }),
|
|
82
|
+
effect_unstable_rpc.Rpc.make("Touch", { success: effect.Schema.Void }),
|
|
83
|
+
effect_unstable_rpc.Rpc.make("SetPresence", {
|
|
87
84
|
payload: {
|
|
88
85
|
connectionId: effect.Schema.String,
|
|
89
86
|
entry: PresenceEntrySchema
|
|
90
87
|
},
|
|
91
88
|
success: effect.Schema.Void
|
|
92
89
|
}),
|
|
93
|
-
|
|
90
|
+
effect_unstable_rpc.Rpc.make("RemovePresence", {
|
|
94
91
|
payload: { connectionId: effect.Schema.String },
|
|
95
92
|
success: effect.Schema.Void
|
|
96
93
|
}),
|
|
97
|
-
|
|
94
|
+
effect_unstable_rpc.Rpc.make("GetPresenceSnapshot", { success: PresenceSnapshotResponseSchema })
|
|
98
95
|
]);
|
|
99
96
|
/**
|
|
100
97
|
* Context tag for cluster engine configuration
|
|
101
98
|
*/
|
|
102
|
-
var MimicClusterConfigTag = class extends effect.
|
|
99
|
+
var MimicClusterConfigTag = class extends effect.ServiceMap.Service()("@voidhash/mimic-effect/MimicClusterConfig") {};
|
|
103
100
|
const resolveClusterConfig = (config) => {
|
|
104
101
|
var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$snapshot3, _config$shardGroup;
|
|
105
102
|
return {
|
|
106
103
|
schema: config.schema,
|
|
107
104
|
initial: config.initial,
|
|
108
105
|
presence: config.presence,
|
|
109
|
-
maxIdleTime: config.maxIdleTime ? effect.Duration.
|
|
106
|
+
maxIdleTime: config.maxIdleTime ? effect.Duration.fromInputUnsafe(config.maxIdleTime) : DEFAULT_MAX_IDLE_TIME,
|
|
110
107
|
maxTransactionHistory: (_config$maxTransactio = config.maxTransactionHistory) !== null && _config$maxTransactio !== void 0 ? _config$maxTransactio : DEFAULT_MAX_TRANSACTION_HISTORY,
|
|
111
108
|
snapshot: {
|
|
112
|
-
interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? effect.Duration.
|
|
109
|
+
interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? effect.Duration.fromInputUnsafe(config.snapshot.interval) : DEFAULT_SNAPSHOT_INTERVAL,
|
|
113
110
|
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
|
-
idleTimeout: ((_config$snapshot3 = config.snapshot) === null || _config$snapshot3 === void 0 ? void 0 : _config$snapshot3.idleTimeout) ? effect.Duration.
|
|
111
|
+
idleTimeout: ((_config$snapshot3 = config.snapshot) === null || _config$snapshot3 === void 0 ? void 0 : _config$snapshot3.idleTimeout) ? effect.Duration.fromInputUnsafe(config.snapshot.idleTimeout) : DEFAULT_SNAPSHOT_IDLE_TIMEOUT
|
|
115
112
|
},
|
|
116
113
|
shardGroup: (_config$shardGroup = config.shardGroup) !== null && _config$shardGroup !== void 0 ? _config$shardGroup : DEFAULT_SHARD_GROUP
|
|
117
114
|
};
|
|
@@ -134,7 +131,7 @@ const encodeTransaction = (tx) => {
|
|
|
134
131
|
* Create the entity handler for MimicDocument
|
|
135
132
|
*/
|
|
136
133
|
const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.fn("cluster.entity.handler.create")(function* () {
|
|
137
|
-
const documentId = (yield*
|
|
134
|
+
const documentId = (yield* effect_unstable_cluster.Entity.CurrentAddress).entityId;
|
|
138
135
|
const instance = yield* require_DocumentInstance.DocumentInstance.make(documentId, {
|
|
139
136
|
schema: config.schema,
|
|
140
137
|
initial: config.initial,
|
|
@@ -148,27 +145,27 @@ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.f
|
|
|
148
145
|
presencePubSub
|
|
149
146
|
});
|
|
150
147
|
yield* effect.Effect.addFinalizer(() => effect.Effect.fn("cluster.entity.finalize")(function* () {
|
|
151
|
-
yield* effect.Effect
|
|
148
|
+
yield* effect.Effect["catch"](instance.saveSnapshot(), (e) => effect.Effect.logError("Failed to save snapshot during entity finalization", {
|
|
152
149
|
documentId,
|
|
153
150
|
error: e
|
|
154
151
|
}));
|
|
155
|
-
yield* effect.Metric.
|
|
156
|
-
yield* effect.Metric.
|
|
152
|
+
yield* effect.Metric.update(require_Metrics.documentsActive, -1);
|
|
153
|
+
yield* effect.Metric.update(require_Metrics.documentsEvicted, 1);
|
|
157
154
|
yield* effect.Effect.logDebug("Entity finalized", { documentId });
|
|
158
155
|
})());
|
|
159
156
|
if (effect.Duration.toMillis(config.snapshot.idleTimeout) > 0) yield* effect.Effect.fn("cluster.entity.snapshot.loop")(function* () {
|
|
160
157
|
if (yield* instance.needsSnapshot()) {
|
|
161
|
-
yield* effect.Effect
|
|
158
|
+
yield* effect.Effect["catch"](instance.saveSnapshot(), (e) => effect.Effect.logWarning("Periodic snapshot failed in cluster entity", {
|
|
162
159
|
documentId,
|
|
163
160
|
error: e
|
|
164
161
|
}));
|
|
165
|
-
yield* effect.Metric.
|
|
162
|
+
yield* effect.Metric.update(require_Metrics.storageIdleSnapshots, 1);
|
|
166
163
|
}
|
|
167
|
-
})().pipe(effect.Effect.repeat(effect.Schedule.spaced(config.snapshot.idleTimeout)), effect.Effect.
|
|
164
|
+
})().pipe(effect.Effect.repeat(effect.Schedule.spaced(config.snapshot.idleTimeout)), effect.Effect.forkChild);
|
|
168
165
|
return {
|
|
169
166
|
Submit: effect.Effect.fn("cluster.document.transaction.submit")(function* ({ payload }) {
|
|
170
167
|
const transaction = decodeTransaction(payload.transaction);
|
|
171
|
-
return yield* instance.submit(transaction).pipe(effect.Effect
|
|
168
|
+
return yield* instance.submit(transaction).pipe(effect.Effect["catch"]((error) => effect.Effect.succeed({
|
|
172
169
|
success: false,
|
|
173
170
|
reason: `Storage error: ${String(error)}`
|
|
174
171
|
})));
|
|
@@ -185,8 +182,8 @@ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.f
|
|
|
185
182
|
SetPresence: effect.Effect.fn("cluster.presence.set")(function* ({ payload }) {
|
|
186
183
|
const { connectionId, entry } = payload;
|
|
187
184
|
yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { presences: effect.HashMap.set(s.presences, connectionId, entry) }));
|
|
188
|
-
yield* effect.Metric.
|
|
189
|
-
yield* effect.Metric.
|
|
185
|
+
yield* effect.Metric.update(require_Metrics.presenceUpdates, 1);
|
|
186
|
+
yield* effect.Metric.update(require_Metrics.presenceActive, 1);
|
|
190
187
|
const state = yield* effect.Ref.get(stateRef);
|
|
191
188
|
const event = {
|
|
192
189
|
type: "presence_update",
|
|
@@ -201,7 +198,7 @@ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.f
|
|
|
201
198
|
const state = yield* effect.Ref.get(stateRef);
|
|
202
199
|
if (!effect.HashMap.has(state.presences, connectionId)) return;
|
|
203
200
|
yield* effect.Ref.update(stateRef, (s) => require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, s), {}, { presences: effect.HashMap.remove(s.presences, connectionId) }));
|
|
204
|
-
yield* effect.Metric.
|
|
201
|
+
yield* effect.Metric.update(require_Metrics.presenceActive, -1);
|
|
205
202
|
const event = {
|
|
206
203
|
type: "presence_remove",
|
|
207
204
|
id: connectionId
|
|
@@ -216,7 +213,7 @@ const createEntityHandler = (config, coldStorage, hotStorage) => effect.Effect.f
|
|
|
216
213
|
})
|
|
217
214
|
};
|
|
218
215
|
})();
|
|
219
|
-
var SubscriptionStoreTag = class extends effect.
|
|
216
|
+
var SubscriptionStoreTag = class extends effect.ServiceMap.Service()("@voidhash/mimic-effect/SubscriptionStore") {};
|
|
220
217
|
const subscriptionStoreLayer = effect.Layer.effect(SubscriptionStoreTag, effect.Effect.fn("cluster.subscriptions.layer.create")(function* () {
|
|
221
218
|
const documentPubSubs = yield* effect.Ref.make(effect.HashMap.empty());
|
|
222
219
|
const presencePubSubs = yield* effect.Ref.make(effect.HashMap.empty());
|
|
@@ -282,14 +279,14 @@ const make = (config) => {
|
|
|
282
279
|
concurrency: 1,
|
|
283
280
|
mailboxCapacity: 4096
|
|
284
281
|
});
|
|
285
|
-
const engineLayer = effect.Layer.
|
|
282
|
+
const engineLayer = effect.Layer.effect(require_MimicServerEngine.MimicServerEngineTag, effect.Effect.gen(function* () {
|
|
286
283
|
const makeClient = yield* MimicDocumentEntity.client;
|
|
287
284
|
const subscriptionStore = yield* SubscriptionStoreTag;
|
|
288
285
|
return {
|
|
289
286
|
submit: (documentId, transaction) => effect.Effect.gen(function* () {
|
|
290
287
|
const client = makeClient(documentId);
|
|
291
288
|
const encodedTx = encodeTransaction(transaction);
|
|
292
|
-
const result = yield* client.Submit({ transaction: encodedTx }).pipe(effect.Effect
|
|
289
|
+
const result = yield* client.Submit({ transaction: encodedTx }).pipe(effect.Effect["catch"]((error) => effect.Effect.succeed({
|
|
293
290
|
success: false,
|
|
294
291
|
reason: `Cluster error: ${String(error)}`
|
|
295
292
|
})));
|
|
@@ -5,7 +5,7 @@ import { MimicAuthServiceTag } from "./MimicAuthService.cjs";
|
|
|
5
5
|
import { MimicServerEngineTag } from "./MimicServerEngine.cjs";
|
|
6
6
|
import { Layer } from "effect";
|
|
7
7
|
import { Primitive } from "@voidhash/mimic";
|
|
8
|
-
import { Sharding } from "
|
|
8
|
+
import { Sharding } from "effect/unstable/cluster";
|
|
9
9
|
|
|
10
10
|
//#region src/MimicClusterServerEngine.d.ts
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
|
|
|
5
5
|
import { MimicServerEngineTag } from "./MimicServerEngine.mjs";
|
|
6
6
|
import { Layer } from "effect";
|
|
7
7
|
import { Primitive } from "@voidhash/mimic";
|
|
8
|
-
import { Sharding } from "
|
|
8
|
+
import { Sharding } from "effect/unstable/cluster";
|
|
9
9
|
|
|
10
10
|
//#region src/MimicClusterServerEngine.d.ts
|
|
11
11
|
|
|
@@ -5,9 +5,9 @@ import { documentsActive, documentsEvicted, presenceActive, presenceUpdates, sto
|
|
|
5
5
|
import { DocumentInstance } from "./DocumentInstance.mjs";
|
|
6
6
|
import { _objectSpread2 } from "./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs";
|
|
7
7
|
import { MimicServerEngineTag } from "./MimicServerEngine.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import { Entity } from "
|
|
10
|
-
import { Rpc } from "
|
|
8
|
+
import { Duration, Effect, HashMap, Layer, Metric, PubSub, Ref, Schedule, Schema, ServiceMap, Stream } from "effect";
|
|
9
|
+
import { Entity } from "effect/unstable/cluster";
|
|
10
|
+
import { Rpc } from "effect/unstable/rpc";
|
|
11
11
|
|
|
12
12
|
//#region src/MimicClusterServerEngine.ts
|
|
13
13
|
/**
|
|
@@ -29,49 +29,46 @@ const DEFAULT_SHARD_GROUP = "mimic-documents";
|
|
|
29
29
|
*/
|
|
30
30
|
const EncodedTransactionSchema = Schema.Struct({
|
|
31
31
|
id: Schema.String,
|
|
32
|
-
ops: Schema.Array(Schema.
|
|
32
|
+
ops: Schema.Array(Schema.Any)
|
|
33
33
|
});
|
|
34
34
|
/**
|
|
35
35
|
* Schema for submit result
|
|
36
36
|
*/
|
|
37
|
-
const SubmitResultSchema = Schema.Union(Schema.Struct({
|
|
37
|
+
const SubmitResultSchema = Schema.Union([Schema.Struct({
|
|
38
38
|
success: Schema.Literal(true),
|
|
39
39
|
version: Schema.Number
|
|
40
40
|
}), Schema.Struct({
|
|
41
41
|
success: Schema.Literal(false),
|
|
42
42
|
reason: Schema.String
|
|
43
|
-
}));
|
|
43
|
+
})]);
|
|
44
44
|
/**
|
|
45
45
|
* Schema for snapshot response
|
|
46
46
|
*/
|
|
47
47
|
const SnapshotResponseSchema = Schema.Struct({
|
|
48
|
-
state: Schema.
|
|
48
|
+
state: Schema.Any,
|
|
49
49
|
version: Schema.Number
|
|
50
50
|
});
|
|
51
51
|
/**
|
|
52
52
|
* Schema for presence entry
|
|
53
53
|
*/
|
|
54
54
|
const PresenceEntrySchema = Schema.Struct({
|
|
55
|
-
data: Schema.
|
|
55
|
+
data: Schema.Any,
|
|
56
56
|
userId: Schema.optional(Schema.String)
|
|
57
57
|
});
|
|
58
58
|
/**
|
|
59
59
|
* Schema for presence snapshot response
|
|
60
60
|
*/
|
|
61
|
-
const PresenceSnapshotResponseSchema = Schema.Struct({ presences: Schema.Record(
|
|
62
|
-
|
|
63
|
-
value: PresenceEntrySchema
|
|
64
|
-
}) });
|
|
65
|
-
Schema.Union(Schema.Struct({
|
|
61
|
+
const PresenceSnapshotResponseSchema = Schema.Struct({ presences: Schema.Record(Schema.String, PresenceEntrySchema) });
|
|
62
|
+
Schema.Union([Schema.Struct({
|
|
66
63
|
type: Schema.Literal("presence_update"),
|
|
67
64
|
id: Schema.String,
|
|
68
|
-
data: Schema.
|
|
65
|
+
data: Schema.Any,
|
|
69
66
|
userId: Schema.optional(Schema.String)
|
|
70
67
|
}), Schema.Struct({
|
|
71
68
|
type: Schema.Literal("presence_remove"),
|
|
72
69
|
id: Schema.String
|
|
73
|
-
}));
|
|
74
|
-
Schema.
|
|
70
|
+
})]);
|
|
71
|
+
Schema.Any;
|
|
75
72
|
/**
|
|
76
73
|
* Define the Mimic Document Entity with its RPC protocol.
|
|
77
74
|
* This entity handles document operations for a single documentId.
|
|
@@ -82,7 +79,7 @@ const MimicDocumentEntity = Entity.make("MimicDocument", [
|
|
|
82
79
|
success: SubmitResultSchema
|
|
83
80
|
}),
|
|
84
81
|
Rpc.make("GetSnapshot", { success: SnapshotResponseSchema }),
|
|
85
|
-
Rpc.make("GetTreeSnapshot", { success: Schema.
|
|
82
|
+
Rpc.make("GetTreeSnapshot", { success: Schema.Any }),
|
|
86
83
|
Rpc.make("Touch", { success: Schema.Void }),
|
|
87
84
|
Rpc.make("SetPresence", {
|
|
88
85
|
payload: {
|
|
@@ -100,19 +97,19 @@ const MimicDocumentEntity = Entity.make("MimicDocument", [
|
|
|
100
97
|
/**
|
|
101
98
|
* Context tag for cluster engine configuration
|
|
102
99
|
*/
|
|
103
|
-
var MimicClusterConfigTag = class extends
|
|
100
|
+
var MimicClusterConfigTag = class extends ServiceMap.Service()("@voidhash/mimic-effect/MimicClusterConfig") {};
|
|
104
101
|
const resolveClusterConfig = (config) => {
|
|
105
102
|
var _config$maxTransactio, _config$snapshot, _config$snapshot$tran, _config$snapshot2, _config$snapshot3, _config$shardGroup;
|
|
106
103
|
return {
|
|
107
104
|
schema: config.schema,
|
|
108
105
|
initial: config.initial,
|
|
109
106
|
presence: config.presence,
|
|
110
|
-
maxIdleTime: config.maxIdleTime ? Duration.
|
|
107
|
+
maxIdleTime: config.maxIdleTime ? Duration.fromInputUnsafe(config.maxIdleTime) : DEFAULT_MAX_IDLE_TIME,
|
|
111
108
|
maxTransactionHistory: (_config$maxTransactio = config.maxTransactionHistory) !== null && _config$maxTransactio !== void 0 ? _config$maxTransactio : DEFAULT_MAX_TRANSACTION_HISTORY,
|
|
112
109
|
snapshot: {
|
|
113
|
-
interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? Duration.
|
|
110
|
+
interval: ((_config$snapshot = config.snapshot) === null || _config$snapshot === void 0 ? void 0 : _config$snapshot.interval) ? Duration.fromInputUnsafe(config.snapshot.interval) : DEFAULT_SNAPSHOT_INTERVAL,
|
|
114
111
|
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,
|
|
115
|
-
idleTimeout: ((_config$snapshot3 = config.snapshot) === null || _config$snapshot3 === void 0 ? void 0 : _config$snapshot3.idleTimeout) ? Duration.
|
|
112
|
+
idleTimeout: ((_config$snapshot3 = config.snapshot) === null || _config$snapshot3 === void 0 ? void 0 : _config$snapshot3.idleTimeout) ? Duration.fromInputUnsafe(config.snapshot.idleTimeout) : DEFAULT_SNAPSHOT_IDLE_TIMEOUT
|
|
116
113
|
},
|
|
117
114
|
shardGroup: (_config$shardGroup = config.shardGroup) !== null && _config$shardGroup !== void 0 ? _config$shardGroup : DEFAULT_SHARD_GROUP
|
|
118
115
|
};
|
|
@@ -149,27 +146,27 @@ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.fn("clus
|
|
|
149
146
|
presencePubSub
|
|
150
147
|
});
|
|
151
148
|
yield* Effect.addFinalizer(() => Effect.fn("cluster.entity.finalize")(function* () {
|
|
152
|
-
yield* Effect
|
|
149
|
+
yield* Effect["catch"](instance.saveSnapshot(), (e) => Effect.logError("Failed to save snapshot during entity finalization", {
|
|
153
150
|
documentId,
|
|
154
151
|
error: e
|
|
155
152
|
}));
|
|
156
|
-
yield* Metric.
|
|
157
|
-
yield* Metric.
|
|
153
|
+
yield* Metric.update(documentsActive, -1);
|
|
154
|
+
yield* Metric.update(documentsEvicted, 1);
|
|
158
155
|
yield* Effect.logDebug("Entity finalized", { documentId });
|
|
159
156
|
})());
|
|
160
157
|
if (Duration.toMillis(config.snapshot.idleTimeout) > 0) yield* Effect.fn("cluster.entity.snapshot.loop")(function* () {
|
|
161
158
|
if (yield* instance.needsSnapshot()) {
|
|
162
|
-
yield* Effect
|
|
159
|
+
yield* Effect["catch"](instance.saveSnapshot(), (e) => Effect.logWarning("Periodic snapshot failed in cluster entity", {
|
|
163
160
|
documentId,
|
|
164
161
|
error: e
|
|
165
162
|
}));
|
|
166
|
-
yield* Metric.
|
|
163
|
+
yield* Metric.update(storageIdleSnapshots, 1);
|
|
167
164
|
}
|
|
168
|
-
})().pipe(Effect.repeat(Schedule.spaced(config.snapshot.idleTimeout)), Effect.
|
|
165
|
+
})().pipe(Effect.repeat(Schedule.spaced(config.snapshot.idleTimeout)), Effect.forkChild);
|
|
169
166
|
return {
|
|
170
167
|
Submit: Effect.fn("cluster.document.transaction.submit")(function* ({ payload }) {
|
|
171
168
|
const transaction = decodeTransaction(payload.transaction);
|
|
172
|
-
return yield* instance.submit(transaction).pipe(Effect
|
|
169
|
+
return yield* instance.submit(transaction).pipe(Effect["catch"]((error) => Effect.succeed({
|
|
173
170
|
success: false,
|
|
174
171
|
reason: `Storage error: ${String(error)}`
|
|
175
172
|
})));
|
|
@@ -186,8 +183,8 @@ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.fn("clus
|
|
|
186
183
|
SetPresence: Effect.fn("cluster.presence.set")(function* ({ payload }) {
|
|
187
184
|
const { connectionId, entry } = payload;
|
|
188
185
|
yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { presences: HashMap.set(s.presences, connectionId, entry) }));
|
|
189
|
-
yield* Metric.
|
|
190
|
-
yield* Metric.
|
|
186
|
+
yield* Metric.update(presenceUpdates, 1);
|
|
187
|
+
yield* Metric.update(presenceActive, 1);
|
|
191
188
|
const state = yield* Ref.get(stateRef);
|
|
192
189
|
const event = {
|
|
193
190
|
type: "presence_update",
|
|
@@ -202,7 +199,7 @@ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.fn("clus
|
|
|
202
199
|
const state = yield* Ref.get(stateRef);
|
|
203
200
|
if (!HashMap.has(state.presences, connectionId)) return;
|
|
204
201
|
yield* Ref.update(stateRef, (s) => _objectSpread2(_objectSpread2({}, s), {}, { presences: HashMap.remove(s.presences, connectionId) }));
|
|
205
|
-
yield* Metric.
|
|
202
|
+
yield* Metric.update(presenceActive, -1);
|
|
206
203
|
const event = {
|
|
207
204
|
type: "presence_remove",
|
|
208
205
|
id: connectionId
|
|
@@ -217,7 +214,7 @@ const createEntityHandler = (config, coldStorage, hotStorage) => Effect.fn("clus
|
|
|
217
214
|
})
|
|
218
215
|
};
|
|
219
216
|
})();
|
|
220
|
-
var SubscriptionStoreTag = class extends
|
|
217
|
+
var SubscriptionStoreTag = class extends ServiceMap.Service()("@voidhash/mimic-effect/SubscriptionStore") {};
|
|
221
218
|
const subscriptionStoreLayer = Layer.effect(SubscriptionStoreTag, Effect.fn("cluster.subscriptions.layer.create")(function* () {
|
|
222
219
|
const documentPubSubs = yield* Ref.make(HashMap.empty());
|
|
223
220
|
const presencePubSubs = yield* Ref.make(HashMap.empty());
|
|
@@ -283,14 +280,14 @@ const make = (config) => {
|
|
|
283
280
|
concurrency: 1,
|
|
284
281
|
mailboxCapacity: 4096
|
|
285
282
|
});
|
|
286
|
-
const engineLayer = Layer.
|
|
283
|
+
const engineLayer = Layer.effect(MimicServerEngineTag, Effect.gen(function* () {
|
|
287
284
|
const makeClient = yield* MimicDocumentEntity.client;
|
|
288
285
|
const subscriptionStore = yield* SubscriptionStoreTag;
|
|
289
286
|
return {
|
|
290
287
|
submit: (documentId, transaction) => Effect.gen(function* () {
|
|
291
288
|
const client = makeClient(documentId);
|
|
292
289
|
const encodedTx = encodeTransaction(transaction);
|
|
293
|
-
const result = yield* client.Submit({ transaction: encodedTx }).pipe(Effect
|
|
290
|
+
const result = yield* client.Submit({ transaction: encodedTx }).pipe(Effect["catch"]((error) => Effect.succeed({
|
|
294
291
|
success: false,
|
|
295
292
|
reason: `Cluster error: ${String(error)}`
|
|
296
293
|
})));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MimicClusterServerEngine.mjs","names":["Metrics.documentsActive","Metrics.documentsEvicted","Metrics.storageIdleSnapshots","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 PubSub,\n Ref,\n Schedule,\n Schema,\n Stream,\n} from \"effect\";\nimport { Entity, Sharding } from \"@effect/cluster\";\nimport { Rpc } from \"@effect/rpc\";\nimport { type Primitive, type Transaction } from \"@voidhash/mimic\";\nimport type {\n MimicClusterServerEngineConfig,\n PresenceEntry,\n PresenceEvent,\n ResolvedClusterConfig,\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 {\n DocumentInstance,\n type DocumentInstance as DocumentInstanceInterface,\n} from \"./DocumentInstance\";\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_SNAPSHOT_IDLE_TIMEOUT = Duration.seconds(30);\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 (flat state)\n Rpc.make(\"GetSnapshot\", {\n success: SnapshotResponseSchema,\n }),\n\n // Get tree-like snapshot for rendering\n Rpc.make(\"GetTreeSnapshot\", {\n success: Schema.Unknown,\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 * Entity state that wraps DocumentInstance and adds presence management\n */\ninterface EntityState<TSchema extends Primitive.AnyPrimitive> {\n readonly instance: DocumentInstanceInterface<TSchema>;\n readonly presences: HashMap.HashMap<string, PresenceEntry>;\n readonly presencePubSub: PubSub.PubSub<PresenceEvent>;\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 idleTimeout: config.snapshot?.idleTimeout\n ? Duration.decode(config.snapshot.idleTimeout)\n : DEFAULT_SNAPSHOT_IDLE_TIMEOUT,\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.fn(\"cluster.entity.handler.create\")(function* () {\n // Get entity address to determine documentId\n const address = yield* Entity.CurrentAddress;\n const documentId = address.entityId;\n\n // Create DocumentInstance (fatal if unavailable - entity cannot start)\n const instance = yield* DocumentInstance.make(\n documentId,\n {\n schema: config.schema,\n initial: config.initial,\n maxTransactionHistory: config.maxTransactionHistory,\n snapshot: config.snapshot,\n },\n coldStorage,\n hotStorage\n ).pipe(Effect.orDie);\n\n // Create presence PubSub and state ref\n const presencePubSub = yield* PubSub.unbounded<PresenceEvent>();\n const stateRef = yield* Ref.make<EntityState<TSchema>>({\n instance,\n presences: HashMap.empty(),\n presencePubSub,\n });\n\n // Cleanup on entity finalization\n yield* Effect.addFinalizer(() =>\n Effect.fn(\"cluster.entity.finalize\")(function* () {\n // Best effort save - don't fail shutdown if storage is unavailable\n yield* Effect.catchAll(instance.saveSnapshot(), (e) =>\n Effect.logError(\"Failed to save snapshot during entity finalization\", {\n documentId,\n error: e,\n })\n );\n yield* Metric.incrementBy(Metrics.documentsActive, -1);\n yield* Metric.increment(Metrics.documentsEvicted);\n yield* Effect.logDebug(\"Entity finalized\", { documentId });\n })()\n );\n\n // Start periodic snapshot fiber for this entity\n const idleTimeoutMs = Duration.toMillis(config.snapshot.idleTimeout);\n if (idleTimeoutMs > 0) {\n const snapshotLoop = Effect.fn(\"cluster.entity.snapshot.loop\")(function* () {\n const needs = yield* instance.needsSnapshot();\n if (needs) {\n yield* Effect.catchAll(instance.saveSnapshot(), (e) =>\n Effect.logWarning(\"Periodic snapshot failed in cluster entity\", {\n documentId,\n error: e,\n })\n );\n yield* Metric.increment(Metrics.storageIdleSnapshots);\n }\n });\n\n // Run every idleTimeout\n yield* snapshotLoop().pipe(\n Effect.repeat(Schedule.spaced(config.snapshot.idleTimeout)),\n Effect.fork\n );\n }\n\n // Return RPC handlers\n return {\n Submit: Effect.fn(\"cluster.document.transaction.submit\")(function* ({\n payload,\n }) {\n // Decode transaction\n const transaction = decodeTransaction(payload.transaction);\n\n // Use DocumentInstance's submit method, catching storage errors\n return yield* instance.submit(transaction).pipe(\n Effect.catchAll((error) =>\n Effect.succeed({\n success: false as const,\n reason: `Storage error: ${String(error)}`,\n })\n )\n );\n }),\n\n GetSnapshot: Effect.fn(\"cluster.document.snapshot.get\")(function* () {\n return instance.getSnapshot();\n }),\n\n GetTreeSnapshot: Effect.fn(\"cluster.document.tree-snapshot.get\")(function* () {\n return instance.toSnapshot();\n }),\n\n Touch: Effect.fn(\"cluster.document.touch\")(function* () {\n yield* instance.touch();\n }),\n\n SetPresence: Effect.fn(\"cluster.presence.set\")(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.fn(\"cluster.presence.remove\")(function* ({\n payload,\n }) {\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.fn(\"cluster.presence.snapshot.get\")(\n 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// =============================================================================\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.fn(\"cluster.subscriptions.layer.create\")(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: Effect.fn(\n \"cluster.subscriptions.pubsub.get-or-create\"\n )(function* (documentId: string) {\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: Effect.fn(\n \"cluster.subscriptions.presence-pubsub.get-or-create\"\n )(function* (documentId: string) {\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 getTreeSnapshot: (documentId) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n return yield* client.GetTreeSnapshot(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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"}
|
|
1
|
+
{"version":3,"file":"MimicClusterServerEngine.mjs","names":["Metrics.documentsActive","Metrics.documentsEvicted","Metrics.storageIdleSnapshots","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 Duration,\n Effect,\n HashMap,\n Layer,\n Metric,\n PubSub,\n Ref,\n Schedule,\n Schema,\n ServiceMap,\n Stream,\n} from \"effect\";\nimport { Entity, Sharding } from \"effect/unstable/cluster\";\nimport { Rpc } from \"effect/unstable/rpc\";\nimport { type Primitive, type Transaction } from \"@voidhash/mimic\";\nimport type {\n MimicClusterServerEngineConfig,\n PresenceEntry,\n PresenceEvent,\n ResolvedClusterConfig,\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 {\n DocumentInstance,\n type DocumentInstance as DocumentInstanceInterface,\n} from \"./DocumentInstance\";\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_SNAPSHOT_IDLE_TIMEOUT = Duration.seconds(30);\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.Any),\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.Any,\n version: Schema.Number,\n});\n\n/**\n * Schema for presence entry\n */\nconst PresenceEntrySchema = Schema.Struct({\n data: Schema.Any,\n userId: Schema.optional(Schema.String),\n});\n\n/**\n * Schema for presence snapshot response\n */\nconst PresenceSnapshotResponseSchema = Schema.Struct({\n presences: Schema.Record(Schema.String, 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.Any,\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.Any;\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 (flat state)\n Rpc.make(\"GetSnapshot\", {\n success: SnapshotResponseSchema,\n }),\n\n // Get tree-like snapshot for rendering\n Rpc.make(\"GetTreeSnapshot\", {\n success: Schema.Any,\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 * Entity state that wraps DocumentInstance and adds presence management\n */\ninterface EntityState<TSchema extends Primitive.AnyPrimitive> {\n readonly instance: DocumentInstanceInterface<TSchema>;\n readonly presences: HashMap.HashMap<string, PresenceEntry>;\n readonly presencePubSub: PubSub.PubSub<PresenceEvent>;\n}\n\n// =============================================================================\n// Config Context Tag\n// =============================================================================\n\n/**\n * Context tag for cluster engine configuration\n */\nclass MimicClusterConfigTag extends ServiceMap.Service<MimicClusterConfigTag, ResolvedClusterConfig<Primitive.AnyPrimitive>>()(\n \"@voidhash/mimic-effect/MimicClusterConfig\"\n) {}\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.fromInputUnsafe(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.fromInputUnsafe(config.snapshot.interval)\n : DEFAULT_SNAPSHOT_INTERVAL,\n transactionThreshold:\n config.snapshot?.transactionThreshold ?? DEFAULT_SNAPSHOT_THRESHOLD,\n idleTimeout: config.snapshot?.idleTimeout\n ? Duration.fromInputUnsafe(config.snapshot.idleTimeout)\n : DEFAULT_SNAPSHOT_IDLE_TIMEOUT,\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.fn(\"cluster.entity.handler.create\")(function* () {\n // Get entity address to determine documentId\n const address = yield* Entity.CurrentAddress;\n const documentId = address.entityId;\n\n // Create DocumentInstance (fatal if unavailable - entity cannot start)\n const instance = yield* DocumentInstance.make(\n documentId,\n {\n schema: config.schema,\n initial: config.initial,\n maxTransactionHistory: config.maxTransactionHistory,\n snapshot: config.snapshot,\n },\n coldStorage,\n hotStorage\n ).pipe(Effect.orDie);\n\n // Create presence PubSub and state ref\n const presencePubSub = yield* PubSub.unbounded<PresenceEvent>();\n const stateRef = yield* Ref.make<EntityState<TSchema>>({\n instance,\n presences: HashMap.empty(),\n presencePubSub,\n });\n\n // Cleanup on entity finalization\n yield* Effect.addFinalizer(() =>\n Effect.fn(\"cluster.entity.finalize\")(function* () {\n // Best effort save - don't fail shutdown if storage is unavailable\n yield* Effect[\"catch\"](instance.saveSnapshot(), (e) =>\n Effect.logError(\"Failed to save snapshot during entity finalization\", {\n documentId,\n error: e,\n })\n );\n yield* Metric.update(Metrics.documentsActive, -1);\n yield* Metric.update(Metrics.documentsEvicted, 1);\n yield* Effect.logDebug(\"Entity finalized\", { documentId });\n })()\n );\n\n // Start periodic snapshot fiber for this entity\n const idleTimeoutMs = Duration.toMillis(config.snapshot.idleTimeout);\n if (idleTimeoutMs > 0) {\n const snapshotLoop = Effect.fn(\"cluster.entity.snapshot.loop\")(function* () {\n const needs = yield* instance.needsSnapshot();\n if (needs) {\n yield* Effect[\"catch\"](instance.saveSnapshot(), (e) =>\n Effect.logWarning(\"Periodic snapshot failed in cluster entity\", {\n documentId,\n error: e,\n })\n );\n yield* Metric.update(Metrics.storageIdleSnapshots, 1);\n }\n });\n\n // Run every idleTimeout\n yield* snapshotLoop().pipe(\n Effect.repeat(Schedule.spaced(config.snapshot.idleTimeout)),\n Effect.forkChild\n );\n }\n\n // Return RPC handlers\n return {\n Submit: Effect.fn(\"cluster.document.transaction.submit\")(function* ({\n payload,\n }) {\n // Decode transaction\n const transaction = decodeTransaction(payload.transaction);\n\n // Use DocumentInstance's submit method, catching storage errors\n return yield* instance.submit(transaction).pipe(\n Effect[\"catch\"]((error) =>\n Effect.succeed({\n success: false as const,\n reason: `Storage error: ${String(error)}`,\n })\n )\n );\n }),\n\n GetSnapshot: Effect.fn(\"cluster.document.snapshot.get\")(function* () {\n return instance.getSnapshot();\n }),\n\n GetTreeSnapshot: Effect.fn(\"cluster.document.tree-snapshot.get\")(function* () {\n return instance.toSnapshot();\n }),\n\n Touch: Effect.fn(\"cluster.document.touch\")(function* () {\n yield* instance.touch();\n }),\n\n SetPresence: Effect.fn(\"cluster.presence.set\")(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.update(Metrics.presenceUpdates, 1);\n yield* Metric.update(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.fn(\"cluster.presence.remove\")(function* ({\n payload,\n }) {\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.update(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.fn(\"cluster.presence.snapshot.get\")(\n 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// =============================================================================\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 ServiceMap.Service<SubscriptionStoreTag, SubscriptionStore>()(\n \"@voidhash/mimic-effect/SubscriptionStore\"\n) {}\n\nconst subscriptionStoreLayer = Layer.effect(\n SubscriptionStoreTag,\n Effect.fn(\"cluster.subscriptions.layer.create\")(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: Effect.fn(\n \"cluster.subscriptions.pubsub.get-or-create\"\n )(function* (documentId: string) {\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: Effect.fn(\n \"cluster.subscriptions.presence-pubsub.get-or-create\"\n )(function* (documentId: string) {\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.effect(\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[\"catch\"]((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 getTreeSnapshot: (documentId) =>\n Effect.gen(function* () {\n const client = makeClient(documentId);\n return yield* client.GetTreeSnapshot(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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"}
|