@voidhash/mimic-effect 0.0.2 → 0.0.4
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/.turbo/turbo-build.log +99 -14
- package/dist/DocumentManager.cjs +118 -0
- package/dist/DocumentManager.d.cts +45 -0
- package/dist/DocumentManager.d.cts.map +1 -0
- package/dist/DocumentManager.d.mts +45 -0
- package/dist/DocumentManager.d.mts.map +1 -0
- package/dist/DocumentManager.mjs +105 -0
- package/dist/DocumentManager.mjs.map +1 -0
- package/dist/DocumentProtocol.cjs +94 -0
- package/dist/DocumentProtocol.d.cts +113 -0
- package/dist/DocumentProtocol.d.cts.map +1 -0
- package/dist/DocumentProtocol.d.mts +113 -0
- package/dist/DocumentProtocol.d.mts.map +1 -0
- package/dist/DocumentProtocol.mjs +89 -0
- package/dist/DocumentProtocol.mjs.map +1 -0
- package/dist/MimicAuthService.cjs +55 -0
- package/dist/MimicAuthService.d.cts +65 -0
- package/dist/MimicAuthService.d.cts.map +1 -0
- package/dist/MimicAuthService.d.mts +65 -0
- package/dist/MimicAuthService.d.mts.map +1 -0
- package/dist/MimicAuthService.mjs +47 -0
- package/dist/MimicAuthService.mjs.map +1 -0
- package/dist/MimicConfig.cjs +52 -0
- package/dist/MimicConfig.d.cts +115 -0
- package/dist/MimicConfig.d.cts.map +1 -0
- package/dist/MimicConfig.d.mts +115 -0
- package/dist/MimicConfig.d.mts.map +1 -0
- package/dist/MimicConfig.mjs +43 -0
- package/dist/MimicConfig.mjs.map +1 -0
- package/dist/MimicDataStorage.cjs +83 -0
- package/dist/MimicDataStorage.d.cts +113 -0
- package/dist/MimicDataStorage.d.cts.map +1 -0
- package/dist/MimicDataStorage.d.mts +113 -0
- package/dist/MimicDataStorage.d.mts.map +1 -0
- package/dist/MimicDataStorage.mjs +74 -0
- package/dist/MimicDataStorage.mjs.map +1 -0
- package/dist/MimicServer.cjs +122 -0
- package/dist/MimicServer.d.cts +106 -0
- package/dist/MimicServer.d.cts.map +1 -0
- package/dist/MimicServer.d.mts +106 -0
- package/dist/MimicServer.d.mts.map +1 -0
- package/dist/MimicServer.mjs +116 -0
- package/dist/MimicServer.mjs.map +1 -0
- package/dist/PresenceManager.cjs +108 -0
- package/dist/PresenceManager.d.cts +91 -0
- package/dist/PresenceManager.d.cts.map +1 -0
- package/dist/PresenceManager.d.mts +91 -0
- package/dist/PresenceManager.d.mts.map +1 -0
- package/dist/PresenceManager.mjs +95 -0
- package/dist/PresenceManager.mjs.map +1 -0
- package/dist/WebSocketHandler.cjs +365 -0
- package/dist/WebSocketHandler.d.cts +34 -0
- package/dist/WebSocketHandler.d.cts.map +1 -0
- package/dist/WebSocketHandler.d.mts +34 -0
- package/dist/WebSocketHandler.d.mts.map +1 -0
- package/dist/WebSocketHandler.mjs +355 -0
- package/dist/WebSocketHandler.mjs.map +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.cjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/defineProperty.mjs +14 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.mjs +27 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.cjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPrimitive.mjs +16 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/toPropertyKey.mjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.cjs +18 -0
- package/dist/_virtual/_@oxc-project_runtime@0.103.0/helpers/typeof.mjs +12 -0
- package/dist/_virtual/rolldown_runtime.cjs +43 -0
- package/dist/{chunk-C6wwvPpM.mjs → _virtual/rolldown_runtime.mjs} +1 -1
- package/dist/auth/NoAuth.cjs +43 -0
- package/dist/auth/NoAuth.d.cts +22 -0
- package/dist/auth/NoAuth.d.cts.map +1 -0
- package/dist/auth/NoAuth.d.mts +22 -0
- package/dist/auth/NoAuth.d.mts.map +1 -0
- package/dist/auth/NoAuth.mjs +36 -0
- package/dist/auth/NoAuth.mjs.map +1 -0
- package/dist/errors.cjs +74 -0
- package/dist/errors.d.cts +89 -0
- package/dist/errors.d.cts.map +1 -0
- package/dist/errors.d.mts +89 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +67 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +29 -1227
- package/dist/index.d.cts +12 -795
- package/dist/index.d.mts +12 -795
- package/dist/index.mjs +13 -1162
- package/dist/storage/InMemoryDataStorage.cjs +57 -0
- package/dist/storage/InMemoryDataStorage.d.cts +19 -0
- package/dist/storage/InMemoryDataStorage.d.cts.map +1 -0
- package/dist/storage/InMemoryDataStorage.d.mts +19 -0
- package/dist/storage/InMemoryDataStorage.d.mts.map +1 -0
- package/dist/storage/InMemoryDataStorage.mjs +48 -0
- package/dist/storage/InMemoryDataStorage.mjs.map +1 -0
- package/package.json +3 -3
- package/src/DocumentManager.ts +2 -2
- package/src/MimicConfig.ts +22 -1
- package/src/MimicServer.ts +11 -161
- package/tests/DocumentManager.test.ts +61 -0
- package/tests/MimicConfig.test.ts +72 -0
- package/tests/MimicServer.test.ts +55 -162
- package/tsdown.config.ts +1 -1
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_MimicConfig = require('./MimicConfig.cjs');
|
|
3
|
+
const require_DocumentManager = require('./DocumentManager.cjs');
|
|
4
|
+
const require_MimicAuthService = require('./MimicAuthService.cjs');
|
|
5
|
+
const require_PresenceManager = require('./PresenceManager.cjs');
|
|
6
|
+
const require_errors = require('./errors.cjs');
|
|
7
|
+
const require_objectSpread2 = require('./_virtual/_@oxc-project_runtime@0.103.0/helpers/objectSpread2.cjs');
|
|
8
|
+
let effect_Effect = require("effect/Effect");
|
|
9
|
+
effect_Effect = require_rolldown_runtime.__toESM(effect_Effect);
|
|
10
|
+
let effect_Stream = require("effect/Stream");
|
|
11
|
+
effect_Stream = require_rolldown_runtime.__toESM(effect_Stream);
|
|
12
|
+
let effect_Duration = require("effect/Duration");
|
|
13
|
+
effect_Duration = require_rolldown_runtime.__toESM(effect_Duration);
|
|
14
|
+
let _voidhash_mimic = require("@voidhash/mimic");
|
|
15
|
+
let effect_Fiber = require("effect/Fiber");
|
|
16
|
+
effect_Fiber = require_rolldown_runtime.__toESM(effect_Fiber);
|
|
17
|
+
|
|
18
|
+
//#region src/WebSocketHandler.ts
|
|
19
|
+
/**
|
|
20
|
+
* @since 0.0.1
|
|
21
|
+
* WebSocket connection handler using Effect Platform Socket API.
|
|
22
|
+
*/
|
|
23
|
+
var WebSocketHandler_exports = /* @__PURE__ */ require_rolldown_runtime.__export({
|
|
24
|
+
extractDocumentId: () => extractDocumentId,
|
|
25
|
+
handleConnection: () => handleConnection,
|
|
26
|
+
makeHandler: () => makeHandler
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Extract document ID from URL path.
|
|
30
|
+
* Expected format: /doc/{documentId}
|
|
31
|
+
*/
|
|
32
|
+
const extractDocumentId = (path) => {
|
|
33
|
+
const parts = path.replace(/^\/+/, "").split("/");
|
|
34
|
+
const docIndex = parts.lastIndexOf("doc");
|
|
35
|
+
const part = parts[docIndex + 1];
|
|
36
|
+
if (docIndex !== -1 && part) return effect_Effect.succeed(decodeURIComponent(part));
|
|
37
|
+
return effect_Effect.fail(new require_errors.MissingDocumentIdError({}));
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Decodes an encoded client message from the wire format.
|
|
41
|
+
*/
|
|
42
|
+
const decodeClientMessage = (encoded) => {
|
|
43
|
+
if (encoded.type === "submit") return {
|
|
44
|
+
type: "submit",
|
|
45
|
+
transaction: _voidhash_mimic.Transaction.decode(encoded.transaction)
|
|
46
|
+
};
|
|
47
|
+
return encoded;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Encodes a server message for the wire format.
|
|
51
|
+
*/
|
|
52
|
+
const encodeServerMessageForWire = (message) => {
|
|
53
|
+
if (message.type === "transaction") return {
|
|
54
|
+
type: "transaction",
|
|
55
|
+
transaction: _voidhash_mimic.Transaction.encode(message.transaction),
|
|
56
|
+
version: message.version
|
|
57
|
+
};
|
|
58
|
+
return message;
|
|
59
|
+
};
|
|
60
|
+
const parseClientMessage = (data) => effect_Effect.try({
|
|
61
|
+
try: () => {
|
|
62
|
+
const text = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
63
|
+
return decodeClientMessage(JSON.parse(text));
|
|
64
|
+
},
|
|
65
|
+
catch: (cause) => new require_errors.MessageParseError({ cause })
|
|
66
|
+
});
|
|
67
|
+
const encodeServerMessage = (message) => JSON.stringify(encodeServerMessageForWire(message));
|
|
68
|
+
/**
|
|
69
|
+
* Handle a WebSocket connection for a document.
|
|
70
|
+
*
|
|
71
|
+
* @param socket - The Effect Platform Socket
|
|
72
|
+
* @param path - The URL path (e.g., "/doc/my-document-id")
|
|
73
|
+
* @returns An Effect that handles the connection lifecycle
|
|
74
|
+
*/
|
|
75
|
+
const handleConnection = (socket, path) => effect_Effect.gen(function* () {
|
|
76
|
+
const config = yield* require_MimicConfig.MimicServerConfigTag;
|
|
77
|
+
const authService = yield* require_MimicAuthService.MimicAuthServiceTag;
|
|
78
|
+
const documentManager = yield* require_DocumentManager.DocumentManagerTag;
|
|
79
|
+
const presenceManager = yield* require_PresenceManager.PresenceManagerTag;
|
|
80
|
+
const documentId = yield* extractDocumentId(path);
|
|
81
|
+
const connectionId = crypto.randomUUID();
|
|
82
|
+
let state = {
|
|
83
|
+
documentId,
|
|
84
|
+
connectionId,
|
|
85
|
+
authenticated: false
|
|
86
|
+
};
|
|
87
|
+
let hasPresence = false;
|
|
88
|
+
const write = yield* socket.writer;
|
|
89
|
+
const sendMessage = (message) => write(encodeServerMessage(message));
|
|
90
|
+
const sendPresenceSnapshot = effect_Effect.gen(function* () {
|
|
91
|
+
if (!config.presence) return;
|
|
92
|
+
yield* sendMessage({
|
|
93
|
+
type: "presence_snapshot",
|
|
94
|
+
selfId: connectionId,
|
|
95
|
+
presences: (yield* presenceManager.getSnapshot(documentId)).presences
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const handleAuth = (token) => effect_Effect.gen(function* () {
|
|
99
|
+
const result = yield* authService.authenticate(token);
|
|
100
|
+
if (result.success) {
|
|
101
|
+
state = require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, state), {}, {
|
|
102
|
+
authenticated: true,
|
|
103
|
+
userId: result.userId
|
|
104
|
+
});
|
|
105
|
+
yield* sendMessage({
|
|
106
|
+
type: "auth_result",
|
|
107
|
+
success: true
|
|
108
|
+
});
|
|
109
|
+
yield* sendPresenceSnapshot;
|
|
110
|
+
} else yield* sendMessage({
|
|
111
|
+
type: "auth_result",
|
|
112
|
+
success: false,
|
|
113
|
+
error: result.error
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
const handlePresenceSet = (data) => effect_Effect.gen(function* () {
|
|
117
|
+
if (!state.authenticated) return;
|
|
118
|
+
if (!config.presence) return;
|
|
119
|
+
const validated = _voidhash_mimic.Presence.validateSafe(config.presence, data);
|
|
120
|
+
if (validated === void 0) {
|
|
121
|
+
yield* effect_Effect.logWarning("Invalid presence data received", {
|
|
122
|
+
connectionId,
|
|
123
|
+
data
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
yield* presenceManager.set(documentId, connectionId, {
|
|
128
|
+
data: validated,
|
|
129
|
+
userId: state.userId
|
|
130
|
+
});
|
|
131
|
+
hasPresence = true;
|
|
132
|
+
});
|
|
133
|
+
const handlePresenceClear = effect_Effect.gen(function* () {
|
|
134
|
+
if (!state.authenticated) return;
|
|
135
|
+
if (!config.presence) return;
|
|
136
|
+
yield* presenceManager.remove(documentId, connectionId);
|
|
137
|
+
hasPresence = false;
|
|
138
|
+
});
|
|
139
|
+
const handleMessage = (message) => effect_Effect.gen(function* () {
|
|
140
|
+
switch (message.type) {
|
|
141
|
+
case "auth":
|
|
142
|
+
yield* handleAuth(message.token);
|
|
143
|
+
break;
|
|
144
|
+
case "ping":
|
|
145
|
+
yield* sendMessage({ type: "pong" });
|
|
146
|
+
break;
|
|
147
|
+
case "submit":
|
|
148
|
+
if (!state.authenticated) {
|
|
149
|
+
yield* sendMessage({
|
|
150
|
+
type: "error",
|
|
151
|
+
transactionId: message.transaction.id,
|
|
152
|
+
reason: "Not authenticated"
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const submitResult = yield* documentManager.submit(documentId, message.transaction);
|
|
157
|
+
if (!submitResult.success) yield* sendMessage({
|
|
158
|
+
type: "error",
|
|
159
|
+
transactionId: message.transaction.id,
|
|
160
|
+
reason: submitResult.reason
|
|
161
|
+
});
|
|
162
|
+
break;
|
|
163
|
+
case "request_snapshot":
|
|
164
|
+
if (!state.authenticated) return;
|
|
165
|
+
yield* sendMessage(yield* effect_Effect.catchAll(documentManager.getSnapshot(documentId), () => effect_Effect.succeed({
|
|
166
|
+
type: "snapshot",
|
|
167
|
+
state: null,
|
|
168
|
+
version: 0
|
|
169
|
+
})));
|
|
170
|
+
break;
|
|
171
|
+
case "presence_set":
|
|
172
|
+
yield* handlePresenceSet(message.data);
|
|
173
|
+
break;
|
|
174
|
+
case "presence_clear":
|
|
175
|
+
yield* handlePresenceClear;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const subscribeFiber = yield* effect_Effect.fork(effect_Effect.gen(function* () {
|
|
180
|
+
while (!state.authenticated) yield* effect_Effect.sleep(effect_Duration.millis(100));
|
|
181
|
+
const broadcastStream = yield* effect_Effect.catchAll(documentManager.subscribe(documentId), () => effect_Effect.succeed(effect_Stream.empty));
|
|
182
|
+
yield* effect_Stream.runForEach(broadcastStream, (broadcast) => sendMessage(broadcast));
|
|
183
|
+
}).pipe(effect_Effect.scoped));
|
|
184
|
+
const presenceFiber = yield* effect_Effect.fork(effect_Effect.gen(function* () {
|
|
185
|
+
if (!config.presence) return;
|
|
186
|
+
while (!state.authenticated) yield* effect_Effect.sleep(effect_Duration.millis(100));
|
|
187
|
+
const presenceStream = yield* presenceManager.subscribe(documentId);
|
|
188
|
+
yield* effect_Stream.runForEach(presenceStream, (event) => effect_Effect.gen(function* () {
|
|
189
|
+
if (event.id === connectionId) return;
|
|
190
|
+
if (event.type === "presence_update") yield* sendMessage({
|
|
191
|
+
type: "presence_update",
|
|
192
|
+
id: event.id,
|
|
193
|
+
data: event.data,
|
|
194
|
+
userId: event.userId
|
|
195
|
+
});
|
|
196
|
+
else if (event.type === "presence_remove") yield* sendMessage({
|
|
197
|
+
type: "presence_remove",
|
|
198
|
+
id: event.id
|
|
199
|
+
});
|
|
200
|
+
}));
|
|
201
|
+
}).pipe(effect_Effect.scoped));
|
|
202
|
+
yield* effect_Effect.addFinalizer(() => effect_Effect.gen(function* () {
|
|
203
|
+
yield* effect_Fiber.interrupt(subscribeFiber);
|
|
204
|
+
yield* effect_Fiber.interrupt(presenceFiber);
|
|
205
|
+
if (hasPresence && config.presence) yield* presenceManager.remove(documentId, connectionId);
|
|
206
|
+
}));
|
|
207
|
+
yield* socket.runRaw((data) => effect_Effect.gen(function* () {
|
|
208
|
+
yield* handleMessage(yield* parseClientMessage(data));
|
|
209
|
+
}).pipe(effect_Effect.catchAll((error) => effect_Effect.logError("Message handling error", error))));
|
|
210
|
+
});
|
|
211
|
+
/**
|
|
212
|
+
* Create a handler function for the WebSocket server.
|
|
213
|
+
* Returns a function that takes a socket and document ID.
|
|
214
|
+
*/
|
|
215
|
+
const makeHandler = effect_Effect.gen(function* () {
|
|
216
|
+
const config = yield* require_MimicConfig.MimicServerConfigTag;
|
|
217
|
+
const authService = yield* require_MimicAuthService.MimicAuthServiceTag;
|
|
218
|
+
const documentManager = yield* require_DocumentManager.DocumentManagerTag;
|
|
219
|
+
const presenceManager = yield* require_PresenceManager.PresenceManagerTag;
|
|
220
|
+
return (socket, documentId) => handleConnectionWithDocumentId(socket, documentId).pipe(effect_Effect.provideService(require_MimicConfig.MimicServerConfigTag, config), effect_Effect.provideService(require_MimicAuthService.MimicAuthServiceTag, authService), effect_Effect.provideService(require_DocumentManager.DocumentManagerTag, documentManager), effect_Effect.provideService(require_PresenceManager.PresenceManagerTag, presenceManager), effect_Effect.scoped);
|
|
221
|
+
});
|
|
222
|
+
/**
|
|
223
|
+
* Handle a WebSocket connection for a document (using document ID directly).
|
|
224
|
+
*/
|
|
225
|
+
const handleConnectionWithDocumentId = (socket, documentId) => effect_Effect.gen(function* () {
|
|
226
|
+
const config = yield* require_MimicConfig.MimicServerConfigTag;
|
|
227
|
+
const authService = yield* require_MimicAuthService.MimicAuthServiceTag;
|
|
228
|
+
const documentManager = yield* require_DocumentManager.DocumentManagerTag;
|
|
229
|
+
const presenceManager = yield* require_PresenceManager.PresenceManagerTag;
|
|
230
|
+
const connectionId = crypto.randomUUID();
|
|
231
|
+
let state = {
|
|
232
|
+
documentId,
|
|
233
|
+
connectionId,
|
|
234
|
+
authenticated: false
|
|
235
|
+
};
|
|
236
|
+
let hasPresence = false;
|
|
237
|
+
const write = yield* socket.writer;
|
|
238
|
+
const sendMessage = (message) => write(encodeServerMessage(message));
|
|
239
|
+
const sendPresenceSnapshot = effect_Effect.gen(function* () {
|
|
240
|
+
if (!config.presence) return;
|
|
241
|
+
yield* sendMessage({
|
|
242
|
+
type: "presence_snapshot",
|
|
243
|
+
selfId: connectionId,
|
|
244
|
+
presences: (yield* presenceManager.getSnapshot(documentId)).presences
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
const handleAuth = (token) => effect_Effect.gen(function* () {
|
|
248
|
+
const result = yield* authService.authenticate(token);
|
|
249
|
+
if (result.success) {
|
|
250
|
+
state = require_objectSpread2._objectSpread2(require_objectSpread2._objectSpread2({}, state), {}, {
|
|
251
|
+
authenticated: true,
|
|
252
|
+
userId: result.userId
|
|
253
|
+
});
|
|
254
|
+
yield* sendMessage({
|
|
255
|
+
type: "auth_result",
|
|
256
|
+
success: true
|
|
257
|
+
});
|
|
258
|
+
yield* sendPresenceSnapshot;
|
|
259
|
+
} else yield* sendMessage({
|
|
260
|
+
type: "auth_result",
|
|
261
|
+
success: false,
|
|
262
|
+
error: result.error
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
const handlePresenceSet = (data) => effect_Effect.gen(function* () {
|
|
266
|
+
if (!state.authenticated) return;
|
|
267
|
+
if (!config.presence) return;
|
|
268
|
+
const validated = _voidhash_mimic.Presence.validateSafe(config.presence, data);
|
|
269
|
+
if (validated === void 0) {
|
|
270
|
+
yield* effect_Effect.logWarning("Invalid presence data received", {
|
|
271
|
+
connectionId,
|
|
272
|
+
data
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
yield* presenceManager.set(documentId, connectionId, {
|
|
277
|
+
data: validated,
|
|
278
|
+
userId: state.userId
|
|
279
|
+
});
|
|
280
|
+
hasPresence = true;
|
|
281
|
+
});
|
|
282
|
+
const handlePresenceClear = effect_Effect.gen(function* () {
|
|
283
|
+
if (!state.authenticated) return;
|
|
284
|
+
if (!config.presence) return;
|
|
285
|
+
yield* presenceManager.remove(documentId, connectionId);
|
|
286
|
+
hasPresence = false;
|
|
287
|
+
});
|
|
288
|
+
const handleMessage = (message) => effect_Effect.gen(function* () {
|
|
289
|
+
switch (message.type) {
|
|
290
|
+
case "auth":
|
|
291
|
+
yield* handleAuth(message.token);
|
|
292
|
+
break;
|
|
293
|
+
case "ping":
|
|
294
|
+
yield* sendMessage({ type: "pong" });
|
|
295
|
+
break;
|
|
296
|
+
case "submit":
|
|
297
|
+
if (!state.authenticated) {
|
|
298
|
+
yield* sendMessage({
|
|
299
|
+
type: "error",
|
|
300
|
+
transactionId: message.transaction.id,
|
|
301
|
+
reason: "Not authenticated"
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const submitResult = yield* documentManager.submit(documentId, message.transaction);
|
|
306
|
+
if (!submitResult.success) yield* sendMessage({
|
|
307
|
+
type: "error",
|
|
308
|
+
transactionId: message.transaction.id,
|
|
309
|
+
reason: submitResult.reason
|
|
310
|
+
});
|
|
311
|
+
break;
|
|
312
|
+
case "request_snapshot":
|
|
313
|
+
if (!state.authenticated) return;
|
|
314
|
+
yield* sendMessage(yield* documentManager.getSnapshot(documentId));
|
|
315
|
+
break;
|
|
316
|
+
case "presence_set":
|
|
317
|
+
yield* handlePresenceSet(message.data);
|
|
318
|
+
break;
|
|
319
|
+
case "presence_clear":
|
|
320
|
+
yield* handlePresenceClear;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
const subscribeFiber = yield* effect_Effect.fork(effect_Effect.gen(function* () {
|
|
325
|
+
while (!state.authenticated) yield* effect_Effect.sleep(effect_Duration.millis(100));
|
|
326
|
+
const broadcastStream = yield* documentManager.subscribe(documentId);
|
|
327
|
+
yield* effect_Stream.runForEach(broadcastStream, (broadcast) => sendMessage(broadcast));
|
|
328
|
+
}).pipe(effect_Effect.scoped));
|
|
329
|
+
const presenceFiber = yield* effect_Effect.fork(effect_Effect.gen(function* () {
|
|
330
|
+
if (!config.presence) return;
|
|
331
|
+
while (!state.authenticated) yield* effect_Effect.sleep(effect_Duration.millis(100));
|
|
332
|
+
const presenceStream = yield* presenceManager.subscribe(documentId);
|
|
333
|
+
yield* effect_Stream.runForEach(presenceStream, (event) => effect_Effect.gen(function* () {
|
|
334
|
+
if (event.id === connectionId) return;
|
|
335
|
+
if (event.type === "presence_update") yield* sendMessage({
|
|
336
|
+
type: "presence_update",
|
|
337
|
+
id: event.id,
|
|
338
|
+
data: event.data,
|
|
339
|
+
userId: event.userId
|
|
340
|
+
});
|
|
341
|
+
else if (event.type === "presence_remove") yield* sendMessage({
|
|
342
|
+
type: "presence_remove",
|
|
343
|
+
id: event.id
|
|
344
|
+
});
|
|
345
|
+
}));
|
|
346
|
+
}).pipe(effect_Effect.scoped));
|
|
347
|
+
yield* effect_Effect.addFinalizer(() => effect_Effect.gen(function* () {
|
|
348
|
+
yield* effect_Fiber.interrupt(subscribeFiber);
|
|
349
|
+
yield* effect_Fiber.interrupt(presenceFiber);
|
|
350
|
+
if (hasPresence && config.presence) yield* presenceManager.remove(documentId, connectionId);
|
|
351
|
+
}));
|
|
352
|
+
yield* socket.runRaw((data) => effect_Effect.gen(function* () {
|
|
353
|
+
yield* handleMessage(yield* parseClientMessage(data));
|
|
354
|
+
}).pipe(effect_Effect.catchAll((error) => effect_Effect.logError("Message handling error", error))));
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
//#endregion
|
|
358
|
+
Object.defineProperty(exports, 'WebSocketHandler_exports', {
|
|
359
|
+
enumerable: true,
|
|
360
|
+
get: function () {
|
|
361
|
+
return WebSocketHandler_exports;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
exports.extractDocumentId = extractDocumentId;
|
|
365
|
+
exports.handleConnection = handleConnection;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { MimicServerConfigTag } from "./MimicConfig.cjs";
|
|
2
|
+
import { DocumentManagerTag } from "./DocumentManager.cjs";
|
|
3
|
+
import { MimicAuthServiceTag } from "./MimicAuthService.cjs";
|
|
4
|
+
import { PresenceManagerTag } from "./PresenceManager.cjs";
|
|
5
|
+
import { MessageParseError, MissingDocumentIdError } from "./errors.cjs";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Scope from "effect/Scope";
|
|
8
|
+
import * as Socket from "@effect/platform/Socket";
|
|
9
|
+
|
|
10
|
+
//#region src/WebSocketHandler.d.ts
|
|
11
|
+
declare namespace WebSocketHandler_d_exports {
|
|
12
|
+
export { extractDocumentId, handleConnection, makeHandler };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extract document ID from URL path.
|
|
16
|
+
* Expected format: /doc/{documentId}
|
|
17
|
+
*/
|
|
18
|
+
declare const extractDocumentId: (path: string) => Effect.Effect<string, MissingDocumentIdError>;
|
|
19
|
+
/**
|
|
20
|
+
* Handle a WebSocket connection for a document.
|
|
21
|
+
*
|
|
22
|
+
* @param socket - The Effect Platform Socket
|
|
23
|
+
* @param path - The URL path (e.g., "/doc/my-document-id")
|
|
24
|
+
* @returns An Effect that handles the connection lifecycle
|
|
25
|
+
*/
|
|
26
|
+
declare const handleConnection: (socket: Socket.Socket, path: string) => Effect.Effect<void, Socket.SocketError | MissingDocumentIdError | MessageParseError, MimicServerConfigTag | MimicAuthServiceTag | DocumentManagerTag | PresenceManagerTag | Scope.Scope>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a handler function for the WebSocket server.
|
|
29
|
+
* Returns a function that takes a socket and document ID.
|
|
30
|
+
*/
|
|
31
|
+
declare const makeHandler: Effect.Effect<(socket: Socket.Socket, documentId: string) => Effect.Effect<void, MessageParseError | Socket.SocketError, never>, never, DocumentManagerTag | MimicAuthServiceTag | MimicServerConfigTag | PresenceManagerTag>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { WebSocketHandler_d_exports };
|
|
34
|
+
//# sourceMappingURL=WebSocketHandler.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSocketHandler.d.cts","names":[],"sources":["../src/WebSocketHandler.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;AAyJa,cAAA,iBAEY,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAtB,MAAA,CAAO,MAAA,CAAA,MAAM,EAAS,sBAAT,CAAA;AAuEhB;;;;;;;AAM+C,cANlC,gBAMkC,EAAA,CAAA,MAAA,EALrC,MAAA,CAAO,MAK8B,EAAA,IAAA,EAAA,MAAA,EAAA,GAH5C,MAAA,CAAO,MAGqC,CAAA,IAAA,EAD7C,MAAA,CAAO,WACsC,GADxB,sBACwB,GADC,iBACD,EAA7C,oBAA6C,GAAtB,mBAAsB,GAAA,kBAAA,GAAqB,kBAArB,GAA0C,KAAA,CAAM,KAAhD,CAAA;;;;;AA2PlC,cAAA,WAcX,EAdsB,MAAA,CAAA,MActB,CAAA,CAAA,MAAA,EARgB,MAAA,CAAO,MAQvB,EAAA,UAAA,EAAA,MAAA,EAAA,GARiD,MAAA,CAAA,MAQjD,CAAA,IAAA,EARiD,iBAQjD,GARiD,MAAA,CAAA,WAQjD,EAAA,KAAA,CAAA,EAAA,KAAA,EARiD,kBAQjD,GARiD,mBAQjD,GARiD,oBAQjD,GARiD,kBAQjD,CAAA"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { MimicServerConfigTag } from "./MimicConfig.mjs";
|
|
2
|
+
import { DocumentManagerTag } from "./DocumentManager.mjs";
|
|
3
|
+
import { MimicAuthServiceTag } from "./MimicAuthService.mjs";
|
|
4
|
+
import { PresenceManagerTag } from "./PresenceManager.mjs";
|
|
5
|
+
import { MessageParseError, MissingDocumentIdError } from "./errors.mjs";
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import * as Scope from "effect/Scope";
|
|
8
|
+
import * as Socket from "@effect/platform/Socket";
|
|
9
|
+
|
|
10
|
+
//#region src/WebSocketHandler.d.ts
|
|
11
|
+
declare namespace WebSocketHandler_d_exports {
|
|
12
|
+
export { extractDocumentId, handleConnection, makeHandler };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Extract document ID from URL path.
|
|
16
|
+
* Expected format: /doc/{documentId}
|
|
17
|
+
*/
|
|
18
|
+
declare const extractDocumentId: (path: string) => Effect.Effect<string, MissingDocumentIdError>;
|
|
19
|
+
/**
|
|
20
|
+
* Handle a WebSocket connection for a document.
|
|
21
|
+
*
|
|
22
|
+
* @param socket - The Effect Platform Socket
|
|
23
|
+
* @param path - The URL path (e.g., "/doc/my-document-id")
|
|
24
|
+
* @returns An Effect that handles the connection lifecycle
|
|
25
|
+
*/
|
|
26
|
+
declare const handleConnection: (socket: Socket.Socket, path: string) => Effect.Effect<void, Socket.SocketError | MissingDocumentIdError | MessageParseError, MimicServerConfigTag | MimicAuthServiceTag | DocumentManagerTag | PresenceManagerTag | Scope.Scope>;
|
|
27
|
+
/**
|
|
28
|
+
* Create a handler function for the WebSocket server.
|
|
29
|
+
* Returns a function that takes a socket and document ID.
|
|
30
|
+
*/
|
|
31
|
+
declare const makeHandler: Effect.Effect<(socket: Socket.Socket, documentId: string) => Effect.Effect<void, MessageParseError | Socket.SocketError, never>, never, DocumentManagerTag | MimicAuthServiceTag | MimicServerConfigTag | PresenceManagerTag>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { WebSocketHandler_d_exports };
|
|
34
|
+
//# sourceMappingURL=WebSocketHandler.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSocketHandler.d.mts","names":[],"sources":["../src/WebSocketHandler.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;AAyJa,cAAA,iBAEY,EAAA,CAAA,IAAA,EAAA,MAAA,EAAA,GAAtB,MAAA,CAAO,MAAA,CAAA,MAAM,EAAS,sBAAT,CAAA;AAuEhB;;;;;;;AAM+C,cANlC,gBAMkC,EAAA,CAAA,MAAA,EALrC,MAAA,CAAO,MAK8B,EAAA,IAAA,EAAA,MAAA,EAAA,GAH5C,MAAA,CAAO,MAGqC,CAAA,IAAA,EAD7C,MAAA,CAAO,WACsC,GADxB,sBACwB,GADC,iBACD,EAA7C,oBAA6C,GAAtB,mBAAsB,GAAA,kBAAA,GAAqB,kBAArB,GAA0C,KAAA,CAAM,KAAhD,CAAA;;;;;AA2PlC,cAAA,WAcX,EAdsB,MAAA,CAAA,MActB,CAAA,CAAA,MAAA,EARgB,MAAA,CAAO,MAQvB,EAAA,UAAA,EAAA,MAAA,EAAA,GARiD,MAAA,CAAA,MAQjD,CAAA,IAAA,EARiD,iBAQjD,GARiD,MAAA,CAAA,WAQjD,EAAA,KAAA,CAAA,EAAA,KAAA,EARiD,kBAQjD,GARiD,mBAQjD,GARiD,oBAQjD,GARiD,kBAQjD,CAAA"}
|