@rpcbase/server 0.538.0 → 0.540.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/email-DK8uUU4X.js +8045 -0
  2. package/dist/email-DK8uUU4X.js.map +1 -0
  3. package/dist/handler--FFBJMl6.js +153 -0
  4. package/dist/handler--FFBJMl6.js.map +1 -0
  5. package/dist/handler-0rPClEv4.js +663 -0
  6. package/dist/handler-0rPClEv4.js.map +1 -0
  7. package/dist/handler-COnCnprN.js +203 -0
  8. package/dist/handler-COnCnprN.js.map +1 -0
  9. package/dist/handler-ClQF4MOn.js +931 -0
  10. package/dist/handler-ClQF4MOn.js.map +1 -0
  11. package/dist/index.js +4988 -4830
  12. package/dist/index.js.map +1 -1
  13. package/dist/notifications.js +199 -134
  14. package/dist/notifications.js.map +1 -1
  15. package/dist/queryExecutor-Bol_iR8f.js +453 -0
  16. package/dist/queryExecutor-Bol_iR8f.js.map +1 -0
  17. package/dist/render_resend_false-MiC__Smr.js +6 -0
  18. package/dist/render_resend_false-MiC__Smr.js.map +1 -0
  19. package/dist/rts/index.d.ts +0 -1
  20. package/dist/rts/index.d.ts.map +1 -1
  21. package/dist/rts/index.js +1003 -842
  22. package/dist/rts/index.js.map +1 -1
  23. package/dist/schemas-Cjdjgehl.js +4225 -0
  24. package/dist/schemas-Cjdjgehl.js.map +1 -0
  25. package/dist/shared-nE84Or5W.js +111 -0
  26. package/dist/shared-nE84Or5W.js.map +1 -0
  27. package/dist/ssrMiddleware.d.ts +1 -1
  28. package/dist/uploads.js +99 -84
  29. package/dist/uploads.js.map +1 -1
  30. package/package.json +9 -9
  31. package/dist/email-H8nTAGxe.js +0 -12449
  32. package/dist/email-H8nTAGxe.js.map +0 -1
  33. package/dist/handler-BBzEodA0.js +0 -182
  34. package/dist/handler-BBzEodA0.js.map +0 -1
  35. package/dist/handler-BLwgdQv-.js +0 -544
  36. package/dist/handler-BLwgdQv-.js.map +0 -1
  37. package/dist/handler-CZD5p1Jv.js +0 -28
  38. package/dist/handler-CZD5p1Jv.js.map +0 -1
  39. package/dist/handler-Cq6MsoD4.js +0 -124
  40. package/dist/handler-Cq6MsoD4.js.map +0 -1
  41. package/dist/handler-DBtnVvP2.js +0 -756
  42. package/dist/handler-DBtnVvP2.js.map +0 -1
  43. package/dist/queryExecutor-JadZcQSQ.js +0 -318
  44. package/dist/queryExecutor-JadZcQSQ.js.map +0 -1
  45. package/dist/render_resend-DQANggpW.js +0 -7
  46. package/dist/render_resend-DQANggpW.js.map +0 -1
  47. package/dist/rts/api/cleanup/handler.d.ts +0 -9
  48. package/dist/rts/api/cleanup/handler.d.ts.map +0 -1
  49. package/dist/rts/api/cleanup/index.d.ts +0 -11
  50. package/dist/rts/api/cleanup/index.d.ts.map +0 -1
  51. package/dist/schemas-BR3K5Luo.js +0 -3824
  52. package/dist/schemas-BR3K5Luo.js.map +0 -1
  53. package/dist/shared-DhZ_rDdo.js +0 -87
  54. package/dist/shared-DhZ_rDdo.js.map +0 -1
package/dist/rts/index.js CHANGED
@@ -1,845 +1,1006 @@
1
- import { a as normalizeRtsQueryOptions, c as runRtsCount, l as runRtsQuery, n as RTS_USER_ID_HEADER, o as resolveRtsQueryDependencyModelNames, t as RTS_TENANT_ID_QUERY_PARAM } from "../queryExecutor-JadZcQSQ.js";
2
- import { models } from "@rpcbase/db";
3
- import { buildAbility, buildAbilityFromSession, getTenantRolesFromSessionUser } from "@rpcbase/db/acl";
4
1
  import { randomUUID } from "node:crypto";
2
+ import { models } from "@rpcbase/db";
3
+ import { buildAbilityFromSession, getTenantRolesFromSessionUser, buildAbility } from "@rpcbase/db/acl";
5
4
  import { WebSocketServer } from "ws";
6
- //#region src/rts/routes.ts
7
- var routes = Object.entries({ .../* @__PURE__ */ Object.assign({
8
- "./api/changes/handler.ts": () => import("../handler-Cq6MsoD4.js"),
9
- "./api/cleanup/handler.ts": () => import("../handler-CZD5p1Jv.js")
10
- }) }).reduce((acc, [path, mod]) => {
11
- acc[path.replace("./api/", "@rpcbase/server/rts/api/")] = mod;
12
- return acc;
5
+ import { R as RTS_TENANT_ID_QUERY_PARAM, d as RTS_USER_ID_HEADER, n as normalizeRtsQueryOptions, e as resolveRtsQueryDependencyModelNames, a as runRtsQuery, c as runRtsCount } from "../queryExecutor-Bol_iR8f.js";
6
+ const routes = Object.entries({
7
+ .../* @__PURE__ */ Object.assign({ "./api/changes/handler.ts": () => import("../handler--FFBJMl6.js") })
8
+ }).reduce((acc, [path, mod]) => {
9
+ acc[path.replace("./api/", "@rpcbase/server/rts/api/")] = mod;
10
+ return acc;
13
11
  }, {});
14
- //#endregion
15
- //#region src/rts/index.ts
16
- var QUERY_KEY_MAX_LEN = 4096;
17
- var INTERNAL_MODEL_NAMES = new Set(["RBRtsChange", "RBRtsCounter"]);
18
- var DEFAULT_MAX_PAYLOAD_BYTES = 1024 * 1024;
19
- var DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 256;
20
- var DEFAULT_DISPATCH_DEBOUNCE_MS = 25;
21
- var initializedServers = /* @__PURE__ */ new WeakSet();
22
- var customHandlers = [];
23
- var sockets = /* @__PURE__ */ new Map();
24
- var socketMeta = /* @__PURE__ */ new Map();
25
- var socketWrappers = /* @__PURE__ */ new Map();
26
- var socketCleanup = /* @__PURE__ */ new Map();
27
- var socketSubscriptions = /* @__PURE__ */ new Map();
28
- var socketCountSubscriptions = /* @__PURE__ */ new Map();
29
- var subscriptions = /* @__PURE__ */ new Map();
30
- var countSubscriptions = /* @__PURE__ */ new Map();
31
- var changeStreams = /* @__PURE__ */ new Map();
32
- var dispatchTimers = /* @__PURE__ */ new Map();
33
- var upgradeMeta = /* @__PURE__ */ new WeakMap();
34
- var maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES;
35
- var maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;
36
- var dispatchDebounceMs = DEFAULT_DISPATCH_DEBOUNCE_MS;
37
- var allowInternalModels = false;
38
- var RtsSocket = class {
39
- id;
40
- tenantId;
41
- userId;
42
- ws;
43
- handlers = /* @__PURE__ */ new Map();
44
- constructor({ id, ws, meta }) {
45
- this.id = id;
46
- this.ws = ws;
47
- this.tenantId = meta.tenantId;
48
- this.userId = meta.userId;
49
- }
50
- on(event, handler) {
51
- const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
52
- set.add(handler);
53
- this.handlers.set(event, set);
54
- return () => this.off(event, handler);
55
- }
56
- off(event, handler) {
57
- const set = this.handlers.get(event);
58
- if (!set) return;
59
- set.delete(handler);
60
- if (!set.size) this.handlers.delete(event);
61
- }
62
- emit(event, payload) {
63
- sendWs(this.ws, {
64
- type: "event",
65
- event,
66
- payload
67
- });
68
- }
69
- close() {
70
- try {
71
- this.ws.close();
72
- } catch {}
73
- }
74
- dispatch(event, payload) {
75
- const set = this.handlers.get(event);
76
- if (!set) return;
77
- for (const handler of set) handler(payload);
78
- }
79
- };
80
- var rawToText = (raw) => {
81
- if (typeof raw === "string") return raw;
82
- if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString();
83
- if (Array.isArray(raw)) return Buffer.concat(raw).toString();
84
- return raw.toString();
85
- };
86
- var safeJsonParse = (raw) => JSON.parse(rawToText(raw));
87
- var sendWs = (ws, message) => {
88
- if (ws.readyState !== 1) return;
89
- ws.send(JSON.stringify(message));
90
- };
91
- var ensureSocketErrorHandler = (socket, context) => {
92
- if (socket.listenerCount("error") > 0) return;
93
- socket.on("error", (err) => {
94
- const message = err instanceof Error ? err.message : String(err);
95
- const name = err instanceof Error ? err.name : typeof err;
96
- const stack = err instanceof Error ? err.stack : void 0;
97
- const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
98
- if (code === "ECONNRESET" || code == null && message.includes("ECONNRESET")) return;
99
- console.warn("[rb/rts] socket error", {
100
- name,
101
- code,
102
- message,
103
- stack,
104
- remoteAddress: socket.remoteAddress,
105
- remotePort: socket.remotePort,
106
- localAddress: socket.localAddress,
107
- localPort: socket.localPort,
108
- ...context?.()
109
- });
110
- });
111
- };
112
- var redactErrorMessage = (err) => {
113
- const trimmedModelList = (err instanceof Error ? err.message : "Unknown error").replace(/\.\s+Available models:[\s\S]*$/, "");
114
- const maxLen = 256;
115
- if (trimmedModelList.length <= maxLen) return trimmedModelList;
116
- return trimmedModelList.slice(0, maxLen);
117
- };
118
- var unauthorized = (socket, message = "Unauthorized") => {
119
- ensureSocketErrorHandler(socket);
120
- try {
121
- socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
122
- socket.write(`Error: ${message}\r\n`);
123
- socket.end();
124
- } catch {
125
- socket.destroy();
126
- }
127
- };
128
- var badRequest = (socket, message = "Bad Request") => {
129
- ensureSocketErrorHandler(socket);
130
- try {
131
- socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
132
- socket.write(`Error: ${message}\r\n`);
133
- socket.end();
134
- } catch {
135
- socket.destroy();
136
- }
137
- };
138
- var runSessionMiddleware = async (sessionMiddleware, req) => {
139
- await new Promise((resolve, reject) => {
140
- const next = (err) => {
141
- if (err) reject(err);
142
- else resolve();
143
- };
144
- sessionMiddleware(req, {}, next);
145
- });
146
- };
147
- var parseUpgradeMeta = async ({ req, url, sessionMiddleware }) => {
148
- const tenantId = url.searchParams.get(RTS_TENANT_ID_QUERY_PARAM);
149
- if (!tenantId) throw new Error("Missing rb-tenant-id query parameter");
150
- if (sessionMiddleware) {
151
- const upgradeReq = req;
152
- try {
153
- await runSessionMiddleware(sessionMiddleware, upgradeReq);
154
- } catch {
155
- throw new Error("Failed to load session for RTS");
156
- }
157
- const sessionUser = upgradeReq.session?.user;
158
- const sessionUserId = sessionUser?.id;
159
- if (!sessionUserId) throw new Error("Not signed in (missing session.user.id)");
160
- const signedInTenants = sessionUser?.signedInTenants;
161
- const currentTenantId = sessionUser?.currentTenantId;
162
- if (Array.isArray(signedInTenants) && signedInTenants.length > 0) {
163
- if (!signedInTenants.includes(tenantId)) throw new Error("Tenant not authorized for this session");
164
- } else if (currentTenantId) {
165
- if (currentTenantId !== tenantId) throw new Error("Tenant not authorized for this session");
166
- } else throw new Error("Tenant not authorized for this session");
167
- return {
168
- tenantId,
169
- userId: sessionUserId,
170
- ability: buildAbilityFromSession({
171
- tenantId,
172
- session: upgradeReq.session
173
- })
174
- };
175
- }
176
- const raw = req.headers[RTS_USER_ID_HEADER];
177
- const headerUserId = Array.isArray(raw) ? raw[0] : raw;
178
- if (!headerUserId) throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
179
- const user = await (await models.getGlobal("RBUser", { req: { session: null } })).findById(headerUserId, {
180
- tenants: 1,
181
- tenantRoles: 1
182
- }).lean();
183
- const tenantsRaw = user?.tenants;
184
- if (!(Array.isArray(tenantsRaw) ? tenantsRaw.map((t) => String(t)) : []).includes(tenantId)) throw new Error("Tenant not authorized for this session");
185
- const roles = getTenantRolesFromSessionUser(user, tenantId);
186
- return {
187
- tenantId,
188
- userId: headerUserId,
189
- ability: buildAbility({
190
- tenantId,
191
- userId: headerUserId,
192
- roles: roles.length ? roles : ["owner"]
193
- })
194
- };
195
- };
196
- var getTenantModel = async (tenantId, modelName) => {
197
- const ctx = { req: { session: { user: { currentTenantId: tenantId } } } };
198
- return models.getUnsafe(modelName, ctx);
199
- };
200
- var makeDispatchKey = (tenantId, modelName) => `${tenantId}:${modelName}`;
201
- var clearDispatchTimer = (tenantId, modelName) => {
202
- const key = makeDispatchKey(tenantId, modelName);
203
- const timer = dispatchTimers.get(key);
204
- if (!timer) return;
205
- clearTimeout(timer);
206
- dispatchTimers.delete(key);
207
- };
208
- var scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
209
- const key = makeDispatchKey(tenantId, modelName);
210
- if (dispatchTimers.has(key)) return;
211
- const delay = Math.max(0, Math.min(1e3, Math.floor(dispatchDebounceMs)));
212
- dispatchTimers.set(key, setTimeout(() => {
213
- dispatchTimers.delete(key);
214
- Promise.all([dispatchSubscriptionsForModel(tenantId, modelName), dispatchCountSubscriptionsForModel(tenantId, modelName)]).catch(() => {});
215
- }, delay));
216
- };
217
- var runAndSendQuery = async ({ tenantId, targetSocketIds, ability, modelName, queryKey, query, options }) => {
218
- const result = await runRtsQuery({
219
- tenantId,
220
- ability,
221
- modelName,
222
- query,
223
- options,
224
- allowInternalModels
225
- });
226
- const payload = {
227
- type: "query-payload",
228
- modelName,
229
- queryKey,
230
- data: result.data,
231
- ...result.pageInfo ? { pageInfo: result.pageInfo } : {},
232
- ...typeof result.totalCount === "number" ? { totalCount: result.totalCount } : {}
233
- };
234
- for (const socketId of targetSocketIds) {
235
- const ws = sockets.get(socketId);
236
- if (!ws) continue;
237
- sendWs(ws, payload);
238
- }
239
- };
240
- var runAndSendCount = async ({ tenantId, targetSocketIds, ability, modelName, queryKey, query }) => {
241
- const payload = {
242
- type: "count-payload",
243
- modelName,
244
- queryKey,
245
- count: await runRtsCount({
246
- tenantId,
247
- ability,
248
- modelName,
249
- query,
250
- allowInternalModels
251
- })
252
- };
253
- for (const socketId of targetSocketIds) {
254
- const ws = sockets.get(socketId);
255
- if (!ws) continue;
256
- sendWs(ws, payload);
257
- }
258
- };
259
- var subscriptionDependsOnModel = (changedModelName, ownerModelName, subscription) => {
260
- if (ownerModelName === changedModelName) return true;
261
- return subscription.dependencyModelNames.includes(changedModelName);
262
- };
263
- var hasAnySubscriptionsDependingOnModel = (tenantId, modelName) => {
264
- const tenantSubs = subscriptions.get(tenantId);
265
- if (!tenantSubs) return false;
266
- for (const userSubs of tenantSubs.values()) for (const [ownerModelName, modelSubs] of userSubs.entries()) for (const subscription of modelSubs.values()) if (subscriptionDependsOnModel(modelName, ownerModelName, subscription)) return true;
267
- return false;
268
- };
269
- var hasAnyCountSubscriptionsForModel = (tenantId, modelName) => {
270
- const tenantSubs = countSubscriptions.get(tenantId);
271
- if (!tenantSubs) return false;
272
- for (const userSubs of tenantSubs.values()) if (userSubs.get(modelName)?.size) return true;
273
- return false;
274
- };
275
- var dispatchSubscriptionsForModel = async (tenantId, modelName) => {
276
- const tenantSubs = subscriptions.get(tenantId);
277
- if (!tenantSubs || !tenantSubs.size) return;
278
- for (const userSubs of tenantSubs.values()) for (const [ownerModelName, modelSubs] of userSubs.entries()) for (const [queryKey, sub] of modelSubs.entries()) {
279
- if (!subscriptionDependsOnModel(modelName, ownerModelName, sub)) continue;
280
- const targetSocketIds = Array.from(sub.socketIds);
281
- if (!targetSocketIds.length) continue;
282
- const socketId = targetSocketIds[0];
283
- const ability = socketMeta.get(socketId)?.ability;
284
- if (!ability) continue;
285
- try {
286
- await runAndSendQuery({
287
- tenantId,
288
- targetSocketIds,
289
- ability,
290
- modelName: ownerModelName,
291
- queryKey,
292
- query: sub.query,
293
- options: sub.options
294
- });
295
- } catch (err) {
296
- const payload = {
297
- type: "query-payload",
298
- modelName: ownerModelName,
299
- queryKey,
300
- error: redactErrorMessage(err)
301
- };
302
- for (const socketId of targetSocketIds) {
303
- const ws = sockets.get(socketId);
304
- if (!ws) continue;
305
- sendWs(ws, payload);
306
- }
307
- }
308
- }
309
- };
310
- var dispatchCountSubscriptionsForModel = async (tenantId, modelName) => {
311
- const tenantSubs = countSubscriptions.get(tenantId);
312
- if (!tenantSubs || !tenantSubs.size) return;
313
- for (const userSubs of tenantSubs.values()) {
314
- const modelSubs = userSubs.get(modelName);
315
- if (!modelSubs) continue;
316
- for (const [queryKey, sub] of modelSubs.entries()) {
317
- const targetSocketIds = Array.from(sub.socketIds);
318
- if (!targetSocketIds.length) continue;
319
- const socketId = targetSocketIds[0];
320
- const ability = socketMeta.get(socketId)?.ability;
321
- if (!ability) continue;
322
- try {
323
- await runAndSendCount({
324
- tenantId,
325
- targetSocketIds,
326
- ability,
327
- modelName,
328
- queryKey,
329
- query: sub.query
330
- });
331
- } catch (err) {
332
- const payload = {
333
- type: "count-payload",
334
- modelName,
335
- queryKey,
336
- error: redactErrorMessage(err)
337
- };
338
- for (const targetSocketId of targetSocketIds) {
339
- const ws = sockets.get(targetSocketId);
340
- if (!ws) continue;
341
- sendWs(ws, payload);
342
- }
343
- }
344
- }
345
- }
346
- };
347
- var ensureChangeStream = async (tenantId, modelName) => {
348
- const tenantStreams = changeStreams.get(tenantId) ?? /* @__PURE__ */ new Map();
349
- changeStreams.set(tenantId, tenantStreams);
350
- if (tenantStreams.has(modelName)) return;
351
- const stream = (await getTenantModel(tenantId, modelName)).watch([], { fullDocument: "updateLookup" });
352
- stream.on("change", () => {
353
- scheduleDispatchSubscriptionsForModel(tenantId, modelName);
354
- });
355
- stream.on("close", () => {
356
- clearDispatchTimer(tenantId, modelName);
357
- const map = changeStreams.get(tenantId);
358
- map?.delete(modelName);
359
- if (map && map.size === 0) changeStreams.delete(tenantId);
360
- });
361
- stream.on("error", () => {
362
- try {
363
- clearDispatchTimer(tenantId, modelName);
364
- stream.close();
365
- } catch {}
366
- });
367
- tenantStreams.set(modelName, stream);
368
- };
369
- var addSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKey, query, options, dependencyModelNames }) => {
370
- const tenantSubs = subscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
371
- subscriptions.set(tenantId, tenantSubs);
372
- const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
373
- tenantSubs.set(userId, userSubs);
374
- const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
375
- userSubs.set(modelName, modelSubs);
376
- const existing = modelSubs.get(queryKey);
377
- if (existing) {
378
- existing.socketIds.add(socketId);
379
- existing.dependencyModelNames = Array.from(new Set([...existing.dependencyModelNames, ...dependencyModelNames]));
380
- } else modelSubs.set(queryKey, {
381
- query,
382
- options,
383
- dependencyModelNames: Array.from(new Set(dependencyModelNames)),
384
- socketIds: new Set([socketId])
385
- });
386
- const byModel = socketSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
387
- socketSubscriptions.set(socketId, byModel);
388
- const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
389
- byModel.set(modelName, querySet);
390
- querySet.add(queryKey);
391
- };
392
- var addSocketCountSubscription = ({ socketId, tenantId, userId, modelName, queryKey, query }) => {
393
- const tenantSubs = countSubscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
394
- countSubscriptions.set(tenantId, tenantSubs);
395
- const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
396
- tenantSubs.set(userId, userSubs);
397
- const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
398
- userSubs.set(modelName, modelSubs);
399
- const existing = modelSubs.get(queryKey);
400
- if (existing) existing.socketIds.add(socketId);
401
- else modelSubs.set(queryKey, {
402
- query,
403
- socketIds: new Set([socketId])
404
- });
405
- const byModel = socketCountSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
406
- socketCountSubscriptions.set(socketId, byModel);
407
- const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
408
- byModel.set(modelName, querySet);
409
- querySet.add(queryKey);
410
- };
411
- var removeSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKey }) => {
412
- const tenantSubs = subscriptions.get(tenantId);
413
- const userSubs = tenantSubs?.get(userId);
414
- const modelSubs = userSubs?.get(modelName);
415
- const sub = modelSubs?.get(queryKey);
416
- const affectedModelNames = new Set([modelName, ...sub?.dependencyModelNames ?? []]);
417
- if (sub) {
418
- sub.socketIds.delete(socketId);
419
- if (!sub.socketIds.size) modelSubs?.delete(queryKey);
420
- }
421
- const byModel = socketSubscriptions.get(socketId);
422
- const set = byModel?.get(modelName);
423
- if (set) {
424
- set.delete(queryKey);
425
- if (!set.size) byModel?.delete(modelName);
426
- }
427
- if (modelSubs && modelSubs.size === 0) userSubs?.delete(modelName);
428
- if (userSubs && userSubs.size === 0) tenantSubs?.delete(userId);
429
- for (const affectedModelName of affectedModelNames) {
430
- if (hasAnySubscriptionsDependingOnModel(tenantId, affectedModelName) || hasAnyCountSubscriptionsForModel(tenantId, affectedModelName)) continue;
431
- const tenantStreams = changeStreams.get(tenantId);
432
- const stream = tenantStreams?.get(affectedModelName);
433
- if (stream) {
434
- try {
435
- stream.close();
436
- } catch {}
437
- clearDispatchTimer(tenantId, affectedModelName);
438
- tenantStreams?.delete(affectedModelName);
439
- if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
440
- }
441
- }
442
- if (tenantSubs && tenantSubs.size === 0) subscriptions.delete(tenantId);
443
- if (byModel && byModel.size === 0) socketSubscriptions.delete(socketId);
444
- };
445
- var removeSocketCountSubscription = ({ socketId, tenantId, userId, modelName, queryKey }) => {
446
- const tenantSubs = countSubscriptions.get(tenantId);
447
- const userSubs = tenantSubs?.get(userId);
448
- const modelSubs = userSubs?.get(modelName);
449
- const sub = modelSubs?.get(queryKey);
450
- if (sub) {
451
- sub.socketIds.delete(socketId);
452
- if (!sub.socketIds.size) modelSubs?.delete(queryKey);
453
- }
454
- const byModel = socketCountSubscriptions.get(socketId);
455
- const set = byModel?.get(modelName);
456
- if (set) {
457
- set.delete(queryKey);
458
- if (!set.size) byModel?.delete(modelName);
459
- }
460
- if (modelSubs && modelSubs.size === 0) userSubs?.delete(modelName);
461
- if (userSubs && userSubs.size === 0) tenantSubs?.delete(userId);
462
- if (!hasAnySubscriptionsDependingOnModel(tenantId, modelName) && !hasAnyCountSubscriptionsForModel(tenantId, modelName)) {
463
- const tenantStreams = changeStreams.get(tenantId);
464
- const stream = tenantStreams?.get(modelName);
465
- if (stream) {
466
- try {
467
- stream.close();
468
- } catch {}
469
- clearDispatchTimer(tenantId, modelName);
470
- tenantStreams?.delete(modelName);
471
- if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
472
- }
473
- }
474
- if (tenantSubs && tenantSubs.size === 0) countSubscriptions.delete(tenantId);
475
- if (byModel && byModel.size === 0) socketCountSubscriptions.delete(socketId);
476
- };
477
- var getSocketSubscriptionCount = (socketId) => {
478
- let count = 0;
479
- const querySubscriptionsByModel = socketSubscriptions.get(socketId);
480
- if (querySubscriptionsByModel) for (const set of querySubscriptionsByModel.values()) count += set.size;
481
- const countSubscriptionsByModel = socketCountSubscriptions.get(socketId);
482
- if (countSubscriptionsByModel) for (const set of countSubscriptionsByModel.values()) count += set.size;
483
- return count;
484
- };
485
- var cleanupSocket = (socketId) => {
486
- const meta = socketMeta.get(socketId);
487
- if (meta) {
488
- const byModel = socketSubscriptions.get(socketId);
489
- if (byModel) for (const [modelName, keys] of byModel.entries()) for (const queryKey of keys.values()) removeSocketSubscription({
490
- socketId,
491
- tenantId: meta.tenantId,
492
- userId: meta.userId,
493
- modelName,
494
- queryKey
495
- });
496
- const countByModel = socketCountSubscriptions.get(socketId);
497
- if (countByModel) for (const [modelName, keys] of countByModel.entries()) for (const queryKey of keys.values()) removeSocketCountSubscription({
498
- socketId,
499
- tenantId: meta.tenantId,
500
- userId: meta.userId,
501
- modelName,
502
- queryKey
503
- });
504
- }
505
- socketSubscriptions.delete(socketId);
506
- socketCountSubscriptions.delete(socketId);
507
- const cleanupFns = socketCleanup.get(socketId) ?? [];
508
- socketCleanup.delete(socketId);
509
- for (const fn of cleanupFns) try {
510
- fn();
511
- } catch {}
512
- sockets.delete(socketId);
513
- socketMeta.delete(socketId);
514
- socketWrappers.delete(socketId);
515
- };
516
- var clearTenantDispatchTimers = (tenantId) => {
517
- const prefix = `${tenantId}:`;
518
- for (const key of Array.from(dispatchTimers.keys())) {
519
- if (!key.startsWith(prefix)) continue;
520
- const timer = dispatchTimers.get(key);
521
- if (timer) clearTimeout(timer);
522
- dispatchTimers.delete(key);
523
- }
524
- };
525
- var cleanupRtsTenant = (tenantId) => {
526
- const normalizedTenantId = tenantId.trim();
527
- if (!normalizedTenantId) return;
528
- const tenantSocketIds = Array.from(socketMeta.entries()).filter(([, meta]) => meta.tenantId === normalizedTenantId).map(([socketId]) => socketId);
529
- for (const socketId of tenantSocketIds) {
530
- const ws = sockets.get(socketId);
531
- cleanupSocket(socketId);
532
- if (ws) try {
533
- ws.close();
534
- } catch {}
535
- }
536
- clearTenantDispatchTimers(normalizedTenantId);
537
- const tenantStreams = changeStreams.get(normalizedTenantId);
538
- if (tenantStreams) {
539
- for (const [modelName, stream] of tenantStreams.entries()) {
540
- try {
541
- stream.close();
542
- } catch {}
543
- clearDispatchTimer(normalizedTenantId, modelName);
544
- }
545
- changeStreams.delete(normalizedTenantId);
546
- }
547
- subscriptions.delete(normalizedTenantId);
548
- countSubscriptions.delete(normalizedTenantId);
549
- };
550
- var handleClientMessage = async ({ socketId, meta, message }) => {
551
- const ws = sockets.get(socketId);
552
- if (!ws) return;
553
- if (message.type === "event") {
554
- socketWrappers.get(socketId)?.dispatch(message.event, message.payload);
555
- return;
556
- }
557
- const isCountMessage = message.type === "run-count" || message.type === "register-count" || message.type === "remove-count";
558
- const sendAccessError = (error) => {
559
- if (isCountMessage) {
560
- sendWs(ws, {
561
- type: "count-payload",
562
- modelName: message.modelName ?? "",
563
- queryKey: message.queryKey ?? "",
564
- error
565
- });
566
- return;
567
- }
568
- sendWs(ws, {
569
- type: "query-payload",
570
- modelName: message.modelName ?? "",
571
- queryKey: message.queryKey ?? "",
572
- error
573
- });
574
- };
575
- if (!message.modelName || typeof message.modelName !== "string") return;
576
- if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
577
- sendAccessError("Model not allowed");
578
- return;
579
- }
580
- if (!message.queryKey || typeof message.queryKey !== "string") return;
581
- if (message.queryKey.length > QUERY_KEY_MAX_LEN) return;
582
- if (message.type === "remove-query") {
583
- removeSocketSubscription({
584
- socketId,
585
- tenantId: meta.tenantId,
586
- userId: meta.userId,
587
- modelName: message.modelName,
588
- queryKey: message.queryKey
589
- });
590
- return;
591
- }
592
- if (message.type === "remove-count") {
593
- removeSocketCountSubscription({
594
- socketId,
595
- tenantId: meta.tenantId,
596
- userId: meta.userId,
597
- modelName: message.modelName,
598
- queryKey: message.queryKey
599
- });
600
- return;
601
- }
602
- if (!message.query || typeof message.query !== "object") return;
603
- const ability = meta.ability;
604
- if (!ability.can("read", message.modelName)) {
605
- sendAccessError("forbidden");
606
- return;
607
- }
608
- if (message.type === "register-query" || message.type === "run-query") {
609
- const options = normalizeRtsQueryOptions(message.options);
610
- if (message.type === "register-query") {
611
- if (!(socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false) && getSocketSubscriptionCount(socketId) >= maxSubscriptionsPerSocket) {
612
- sendWs(ws, {
613
- type: "query-payload",
614
- modelName: message.modelName,
615
- queryKey: message.queryKey,
616
- error: "Too many subscriptions"
617
- });
618
- return;
619
- }
620
- let dependencyModelNames = [];
621
- if (options.populate !== void 0) try {
622
- dependencyModelNames = await resolveRtsQueryDependencyModelNames({
623
- tenantId: meta.tenantId,
624
- ability,
625
- modelName: message.modelName,
626
- options,
627
- allowInternalModels
628
- });
629
- } catch (err) {
630
- const error = redactErrorMessage(err);
631
- sendWs(ws, {
632
- type: "query-payload",
633
- modelName: message.modelName,
634
- queryKey: message.queryKey,
635
- error
636
- });
637
- return;
638
- }
639
- addSocketSubscription({
640
- socketId,
641
- tenantId: meta.tenantId,
642
- userId: meta.userId,
643
- modelName: message.modelName,
644
- queryKey: message.queryKey,
645
- query: message.query,
646
- options,
647
- dependencyModelNames
648
- });
649
- try {
650
- const modelNamesToWatch = new Set([message.modelName, ...dependencyModelNames]);
651
- for (const modelName of modelNamesToWatch) await ensureChangeStream(meta.tenantId, modelName);
652
- } catch (err) {
653
- const error = redactErrorMessage(err);
654
- sendWs(ws, {
655
- type: "query-payload",
656
- modelName: message.modelName,
657
- queryKey: message.queryKey,
658
- error
659
- });
660
- return;
661
- }
662
- if (message.runInitialQuery === false) return;
663
- }
664
- try {
665
- await runAndSendQuery({
666
- tenantId: meta.tenantId,
667
- targetSocketIds: [socketId],
668
- ability,
669
- modelName: message.modelName,
670
- queryKey: message.queryKey,
671
- query: message.query,
672
- options
673
- });
674
- } catch (err) {
675
- const error = redactErrorMessage(err);
676
- sendWs(ws, {
677
- type: "query-payload",
678
- modelName: message.modelName,
679
- queryKey: message.queryKey,
680
- error
681
- });
682
- }
683
- return;
684
- }
685
- if (message.type === "register-count" || message.type === "run-count") {
686
- if (message.type === "register-count") {
687
- if (!(socketCountSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false) && getSocketSubscriptionCount(socketId) >= maxSubscriptionsPerSocket) {
688
- sendWs(ws, {
689
- type: "count-payload",
690
- modelName: message.modelName,
691
- queryKey: message.queryKey,
692
- error: "Too many subscriptions"
693
- });
694
- return;
695
- }
696
- addSocketCountSubscription({
697
- socketId,
698
- tenantId: meta.tenantId,
699
- userId: meta.userId,
700
- modelName: message.modelName,
701
- queryKey: message.queryKey,
702
- query: message.query
703
- });
704
- try {
705
- await ensureChangeStream(meta.tenantId, message.modelName);
706
- } catch (err) {
707
- const error = redactErrorMessage(err);
708
- sendWs(ws, {
709
- type: "count-payload",
710
- modelName: message.modelName,
711
- queryKey: message.queryKey,
712
- error
713
- });
714
- return;
715
- }
716
- if (message.runInitialQuery === false) return;
717
- }
718
- try {
719
- await runAndSendCount({
720
- tenantId: meta.tenantId,
721
- targetSocketIds: [socketId],
722
- ability,
723
- modelName: message.modelName,
724
- queryKey: message.queryKey,
725
- query: message.query
726
- });
727
- } catch (err) {
728
- const error = redactErrorMessage(err);
729
- sendWs(ws, {
730
- type: "count-payload",
731
- modelName: message.modelName,
732
- queryKey: message.queryKey,
733
- error
734
- });
735
- }
736
- }
737
- };
738
- var initRts = ({ server, path = "/rts", sessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg }) => {
739
- if (initializedServers.has(server)) return;
740
- initializedServers.add(server);
741
- if (typeof maxPayloadBytesArg === "number" && Number.isFinite(maxPayloadBytesArg) && maxPayloadBytesArg > 0) maxPayloadBytes = Math.floor(maxPayloadBytesArg);
742
- if (typeof maxSubscriptionsPerSocketArg === "number" && Number.isFinite(maxSubscriptionsPerSocketArg) && maxSubscriptionsPerSocketArg > 0) maxSubscriptionsPerSocket = Math.floor(maxSubscriptionsPerSocketArg);
743
- if (typeof dispatchDebounceMsArg === "number" && Number.isFinite(dispatchDebounceMsArg) && dispatchDebounceMsArg >= 0) dispatchDebounceMs = Math.floor(dispatchDebounceMsArg);
744
- allowInternalModels = Boolean(allowInternalModelsArg);
745
- const wss = new WebSocketServer({
746
- noServer: true,
747
- maxPayload: maxPayloadBytes
748
- });
749
- server.on("upgrade", (req, socket, head) => {
750
- ensureSocketErrorHandler(socket, () => ({
751
- upgradeHost: req.headers.host ?? "",
752
- upgradeUrl: req.url ?? "",
753
- userAgent: typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : ""
754
- }));
755
- upgradeMeta.delete(req);
756
- let url;
757
- try {
758
- url = new URL(req.url ?? "", `http://${req.headers.host ?? "localhost"}`);
759
- } catch {
760
- badRequest(socket, "Invalid URL");
761
- return;
762
- }
763
- if (url.pathname !== path) return;
764
- (async () => {
765
- try {
766
- const meta = await parseUpgradeMeta({
767
- req,
768
- url,
769
- sessionMiddleware
770
- });
771
- upgradeMeta.set(req, meta);
772
- wss.handleUpgrade(req, socket, head, (ws) => {
773
- wss.emit("connection", ws, req);
774
- });
775
- } catch (err) {
776
- const message = err instanceof Error ? err.message : "RTS upgrade failed";
777
- if (message.startsWith("Missing rb-tenant-id")) {
778
- badRequest(socket, message);
779
- return;
780
- }
781
- unauthorized(socket, message);
782
- return;
783
- }
784
- })().catch(() => {
785
- badRequest(socket, "RTS upgrade failed");
786
- });
787
- });
788
- wss.on("connection", (ws, req) => {
789
- const meta = upgradeMeta.get(req);
790
- upgradeMeta.delete(req);
791
- if (!meta) {
792
- try {
793
- ws.close();
794
- } catch {}
795
- return;
796
- }
797
- const socketId = randomUUID();
798
- sockets.set(socketId, ws);
799
- socketMeta.set(socketId, meta);
800
- const wrapper = new RtsSocket({
801
- id: socketId,
802
- ws,
803
- meta
804
- });
805
- socketWrappers.set(socketId, wrapper);
806
- const cleanupFns = [];
807
- for (const handler of customHandlers) try {
808
- const cleanup = handler(wrapper);
809
- if (typeof cleanup === "function") cleanupFns.push(cleanup);
810
- } catch {}
811
- if (cleanupFns.length) socketCleanup.set(socketId, cleanupFns);
812
- ws.on("message", (raw) => {
813
- let parsed;
814
- try {
815
- parsed = safeJsonParse(raw);
816
- } catch {
817
- return;
818
- }
819
- if (!parsed || typeof parsed !== "object") return;
820
- const message = parsed;
821
- if (message.type !== "event" && message.type !== "run-query" && message.type !== "register-query" && message.type !== "remove-query" && message.type !== "run-count" && message.type !== "register-count" && message.type !== "remove-count") return;
822
- handleClientMessage({
823
- socketId,
824
- meta,
825
- message
826
- });
827
- });
828
- ws.on("close", () => {
829
- cleanupSocket(socketId);
830
- });
831
- ws.on("error", () => {
832
- cleanupSocket(socketId);
833
- });
834
- });
835
- };
836
- var registerRtsHandler = (handler) => {
837
- customHandlers.push(handler);
838
- };
839
- var notifyRtsModelChanged = (tenantId, modelName) => {
840
- scheduleDispatchSubscriptionsForModel(tenantId, modelName);
841
- };
842
- //#endregion
843
- export { cleanupRtsTenant, initRts, notifyRtsModelChanged, registerRtsHandler, routes };
844
-
845
- //# sourceMappingURL=index.js.map
12
+ const QUERY_KEY_MAX_LEN = 4096;
13
+ const INTERNAL_MODEL_NAMES = /* @__PURE__ */ new Set(["RBRtsChange", "RBRtsCounter"]);
14
+ const DEFAULT_MAX_PAYLOAD_BYTES = 1024 * 1024;
15
+ const DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 256;
16
+ const DEFAULT_DISPATCH_DEBOUNCE_MS = 25;
17
+ const initializedServers = /* @__PURE__ */ new WeakSet();
18
+ const customHandlers = [];
19
+ const sockets = /* @__PURE__ */ new Map();
20
+ const socketMeta = /* @__PURE__ */ new Map();
21
+ const socketWrappers = /* @__PURE__ */ new Map();
22
+ const socketCleanup = /* @__PURE__ */ new Map();
23
+ const socketSubscriptions = /* @__PURE__ */ new Map();
24
+ const socketCountSubscriptions = /* @__PURE__ */ new Map();
25
+ const subscriptions = /* @__PURE__ */ new Map();
26
+ const countSubscriptions = /* @__PURE__ */ new Map();
27
+ const changeStreams = /* @__PURE__ */ new Map();
28
+ const dispatchTimers = /* @__PURE__ */ new Map();
29
+ const upgradeMeta = /* @__PURE__ */ new WeakMap();
30
+ let maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES;
31
+ let maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;
32
+ let dispatchDebounceMs = DEFAULT_DISPATCH_DEBOUNCE_MS;
33
+ let allowInternalModels = false;
34
+ class RtsSocket {
35
+ id;
36
+ tenantId;
37
+ userId;
38
+ ws;
39
+ handlers = /* @__PURE__ */ new Map();
40
+ constructor({
41
+ id,
42
+ ws,
43
+ meta
44
+ }) {
45
+ this.id = id;
46
+ this.ws = ws;
47
+ this.tenantId = meta.tenantId;
48
+ this.userId = meta.userId;
49
+ }
50
+ on(event, handler) {
51
+ const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
52
+ set.add(handler);
53
+ this.handlers.set(event, set);
54
+ return () => this.off(event, handler);
55
+ }
56
+ off(event, handler) {
57
+ const set = this.handlers.get(event);
58
+ if (!set) return;
59
+ set.delete(handler);
60
+ if (!set.size) this.handlers.delete(event);
61
+ }
62
+ emit(event, payload) {
63
+ sendWs(this.ws, {
64
+ type: "event",
65
+ event,
66
+ payload
67
+ });
68
+ }
69
+ close() {
70
+ try {
71
+ this.ws.close();
72
+ } catch {
73
+ }
74
+ }
75
+ dispatch(event, payload) {
76
+ const set = this.handlers.get(event);
77
+ if (!set) return;
78
+ for (const handler of set) {
79
+ handler(payload);
80
+ }
81
+ }
82
+ }
83
+ const rawToText = (raw) => {
84
+ if (typeof raw === "string") return raw;
85
+ if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString();
86
+ if (Array.isArray(raw)) return Buffer.concat(raw).toString();
87
+ return raw.toString();
88
+ };
89
+ const safeJsonParse = (raw) => JSON.parse(rawToText(raw));
90
+ const sendWs = (ws, message) => {
91
+ if (ws.readyState !== 1) return;
92
+ ws.send(JSON.stringify(message));
93
+ };
94
+ const ensureSocketErrorHandler = (socket, context) => {
95
+ if (socket.listenerCount("error") > 0) return;
96
+ socket.on("error", (err) => {
97
+ const message = err instanceof Error ? err.message : String(err);
98
+ const name = err instanceof Error ? err.name : typeof err;
99
+ const stack = err instanceof Error ? err.stack : void 0;
100
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
101
+ if (code === "ECONNRESET" || code == null && message.includes("ECONNRESET")) return;
102
+ console.warn("[rb/rts] socket error", {
103
+ name,
104
+ code,
105
+ message,
106
+ stack,
107
+ remoteAddress: socket.remoteAddress,
108
+ remotePort: socket.remotePort,
109
+ localAddress: socket.localAddress,
110
+ localPort: socket.localPort,
111
+ ...context?.()
112
+ });
113
+ });
114
+ };
115
+ const redactErrorMessage = (err) => {
116
+ const raw = err instanceof Error ? err.message : "Unknown error";
117
+ const trimmedModelList = raw.replace(/\.\s+Available models:[\s\S]*$/, "");
118
+ const maxLen = 256;
119
+ if (trimmedModelList.length <= maxLen) return trimmedModelList;
120
+ return trimmedModelList.slice(0, maxLen);
121
+ };
122
+ const unauthorized = (socket, message = "Unauthorized") => {
123
+ ensureSocketErrorHandler(socket);
124
+ try {
125
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
126
+ socket.write(`Error: ${message}\r
127
+ `);
128
+ socket.end();
129
+ } catch {
130
+ socket.destroy();
131
+ }
132
+ };
133
+ const badRequest = (socket, message = "Bad Request") => {
134
+ ensureSocketErrorHandler(socket);
135
+ try {
136
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
137
+ socket.write(`Error: ${message}\r
138
+ `);
139
+ socket.end();
140
+ } catch {
141
+ socket.destroy();
142
+ }
143
+ };
144
+ const runSessionMiddleware = async (sessionMiddleware, req) => {
145
+ await new Promise((resolve, reject) => {
146
+ const next = (err) => {
147
+ if (err) reject(err);
148
+ else resolve();
149
+ };
150
+ sessionMiddleware(req, {}, next);
151
+ });
152
+ };
153
+ const parseUpgradeMeta = async ({
154
+ req,
155
+ url,
156
+ sessionMiddleware
157
+ }) => {
158
+ const tenantId = url.searchParams.get(RTS_TENANT_ID_QUERY_PARAM);
159
+ if (!tenantId) {
160
+ throw new Error("Missing rb-tenant-id query parameter");
161
+ }
162
+ if (sessionMiddleware) {
163
+ const upgradeReq = req;
164
+ try {
165
+ await runSessionMiddleware(sessionMiddleware, upgradeReq);
166
+ } catch {
167
+ throw new Error("Failed to load session for RTS");
168
+ }
169
+ const sessionUser = upgradeReq.session?.user;
170
+ const sessionUserId = sessionUser?.id;
171
+ if (!sessionUserId) {
172
+ throw new Error("Not signed in (missing session.user.id)");
173
+ }
174
+ const signedInTenants = sessionUser?.signedInTenants;
175
+ const currentTenantId = sessionUser?.currentTenantId;
176
+ if (Array.isArray(signedInTenants) && signedInTenants.length > 0) {
177
+ if (!signedInTenants.includes(tenantId)) {
178
+ throw new Error("Tenant not authorized for this session");
179
+ }
180
+ } else if (currentTenantId) {
181
+ if (currentTenantId !== tenantId) {
182
+ throw new Error("Tenant not authorized for this session");
183
+ }
184
+ } else {
185
+ throw new Error("Tenant not authorized for this session");
186
+ }
187
+ const ability2 = buildAbilityFromSession({
188
+ tenantId,
189
+ session: upgradeReq.session
190
+ });
191
+ return {
192
+ tenantId,
193
+ userId: sessionUserId,
194
+ ability: ability2
195
+ };
196
+ }
197
+ const raw = req.headers[RTS_USER_ID_HEADER];
198
+ const headerUserId = Array.isArray(raw) ? raw[0] : raw;
199
+ if (!headerUserId) {
200
+ throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
201
+ }
202
+ const rbCtx = {
203
+ req: {
204
+ session: null
205
+ }
206
+ };
207
+ const User = await models.getGlobal("RBUser", rbCtx);
208
+ const user = await User.findById(headerUserId, {
209
+ tenants: 1,
210
+ tenantRoles: 1
211
+ }).lean();
212
+ const tenantsRaw = user?.tenants;
213
+ const tenants = Array.isArray(tenantsRaw) ? tenantsRaw.map((t) => String(t)) : [];
214
+ if (!tenants.includes(tenantId)) {
215
+ throw new Error("Tenant not authorized for this session");
216
+ }
217
+ const roles = getTenantRolesFromSessionUser(user, tenantId);
218
+ const ability = buildAbility({
219
+ tenantId,
220
+ userId: headerUserId,
221
+ roles: roles.length ? roles : ["owner"]
222
+ });
223
+ return {
224
+ tenantId,
225
+ userId: headerUserId,
226
+ ability
227
+ };
228
+ };
229
+ const getTenantModel = async (tenantId, modelName) => {
230
+ const ctx = {
231
+ req: {
232
+ session: {
233
+ user: {
234
+ currentTenantId: tenantId
235
+ }
236
+ }
237
+ }
238
+ };
239
+ return models.getUnsafe(modelName, ctx);
240
+ };
241
+ const makeDispatchKey = (tenantId, modelName) => `${tenantId}:${modelName}`;
242
+ const clearDispatchTimer = (tenantId, modelName) => {
243
+ const key = makeDispatchKey(tenantId, modelName);
244
+ const timer = dispatchTimers.get(key);
245
+ if (!timer) return;
246
+ clearTimeout(timer);
247
+ dispatchTimers.delete(key);
248
+ };
249
+ const scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
250
+ const key = makeDispatchKey(tenantId, modelName);
251
+ if (dispatchTimers.has(key)) return;
252
+ const delay = Math.max(0, Math.min(1e3, Math.floor(dispatchDebounceMs)));
253
+ dispatchTimers.set(key, setTimeout(() => {
254
+ dispatchTimers.delete(key);
255
+ void Promise.all([dispatchSubscriptionsForModel(tenantId, modelName), dispatchCountSubscriptionsForModel(tenantId, modelName)]).catch(() => {
256
+ });
257
+ }, delay));
258
+ };
259
+ const runAndSendQuery = async ({
260
+ tenantId,
261
+ targetSocketIds,
262
+ ability,
263
+ modelName,
264
+ queryKey,
265
+ query,
266
+ options
267
+ }) => {
268
+ const result = await runRtsQuery({
269
+ tenantId,
270
+ ability,
271
+ modelName,
272
+ query,
273
+ options,
274
+ allowInternalModels
275
+ });
276
+ const payload = {
277
+ type: "query-payload",
278
+ modelName,
279
+ queryKey,
280
+ data: result.data,
281
+ ...result.pageInfo ? {
282
+ pageInfo: result.pageInfo
283
+ } : {},
284
+ ...typeof result.totalCount === "number" ? {
285
+ totalCount: result.totalCount
286
+ } : {}
287
+ };
288
+ for (const socketId of targetSocketIds) {
289
+ const ws = sockets.get(socketId);
290
+ if (!ws) continue;
291
+ sendWs(ws, payload);
292
+ }
293
+ };
294
+ const runAndSendCount = async ({
295
+ tenantId,
296
+ targetSocketIds,
297
+ ability,
298
+ modelName,
299
+ queryKey,
300
+ query
301
+ }) => {
302
+ const count = await runRtsCount({
303
+ tenantId,
304
+ ability,
305
+ modelName,
306
+ query,
307
+ allowInternalModels
308
+ });
309
+ const payload = {
310
+ type: "count-payload",
311
+ modelName,
312
+ queryKey,
313
+ count
314
+ };
315
+ for (const socketId of targetSocketIds) {
316
+ const ws = sockets.get(socketId);
317
+ if (!ws) continue;
318
+ sendWs(ws, payload);
319
+ }
320
+ };
321
+ const subscriptionDependsOnModel = (changedModelName, ownerModelName, subscription) => {
322
+ if (ownerModelName === changedModelName) return true;
323
+ return subscription.dependencyModelNames.includes(changedModelName);
324
+ };
325
+ const hasAnySubscriptionsDependingOnModel = (tenantId, modelName) => {
326
+ const tenantSubs = subscriptions.get(tenantId);
327
+ if (!tenantSubs) return false;
328
+ for (const userSubs of tenantSubs.values()) {
329
+ for (const [ownerModelName, modelSubs] of userSubs.entries()) {
330
+ for (const subscription of modelSubs.values()) {
331
+ if (subscriptionDependsOnModel(modelName, ownerModelName, subscription)) {
332
+ return true;
333
+ }
334
+ }
335
+ }
336
+ }
337
+ return false;
338
+ };
339
+ const hasAnyCountSubscriptionsForModel = (tenantId, modelName) => {
340
+ const tenantSubs = countSubscriptions.get(tenantId);
341
+ if (!tenantSubs) return false;
342
+ for (const userSubs of tenantSubs.values()) {
343
+ const modelSubs = userSubs.get(modelName);
344
+ if (modelSubs?.size) {
345
+ return true;
346
+ }
347
+ }
348
+ return false;
349
+ };
350
+ const dispatchSubscriptionsForModel = async (tenantId, modelName) => {
351
+ const tenantSubs = subscriptions.get(tenantId);
352
+ if (!tenantSubs || !tenantSubs.size) return;
353
+ for (const userSubs of tenantSubs.values()) {
354
+ for (const [ownerModelName, modelSubs] of userSubs.entries()) {
355
+ for (const [queryKey, sub] of modelSubs.entries()) {
356
+ if (!subscriptionDependsOnModel(modelName, ownerModelName, sub)) continue;
357
+ const targetSocketIds = Array.from(sub.socketIds);
358
+ if (!targetSocketIds.length) continue;
359
+ const socketId = targetSocketIds[0];
360
+ const meta = socketMeta.get(socketId);
361
+ const ability = meta?.ability;
362
+ if (!ability) continue;
363
+ try {
364
+ await runAndSendQuery({
365
+ tenantId,
366
+ targetSocketIds,
367
+ ability,
368
+ modelName: ownerModelName,
369
+ queryKey,
370
+ query: sub.query,
371
+ options: sub.options
372
+ });
373
+ } catch (err) {
374
+ const error = redactErrorMessage(err);
375
+ const payload = {
376
+ type: "query-payload",
377
+ modelName: ownerModelName,
378
+ queryKey,
379
+ error
380
+ };
381
+ for (const socketId2 of targetSocketIds) {
382
+ const ws = sockets.get(socketId2);
383
+ if (!ws) continue;
384
+ sendWs(ws, payload);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ };
391
+ const dispatchCountSubscriptionsForModel = async (tenantId, modelName) => {
392
+ const tenantSubs = countSubscriptions.get(tenantId);
393
+ if (!tenantSubs || !tenantSubs.size) return;
394
+ for (const userSubs of tenantSubs.values()) {
395
+ const modelSubs = userSubs.get(modelName);
396
+ if (!modelSubs) continue;
397
+ for (const [queryKey, sub] of modelSubs.entries()) {
398
+ const targetSocketIds = Array.from(sub.socketIds);
399
+ if (!targetSocketIds.length) continue;
400
+ const socketId = targetSocketIds[0];
401
+ const meta = socketMeta.get(socketId);
402
+ const ability = meta?.ability;
403
+ if (!ability) continue;
404
+ try {
405
+ await runAndSendCount({
406
+ tenantId,
407
+ targetSocketIds,
408
+ ability,
409
+ modelName,
410
+ queryKey,
411
+ query: sub.query
412
+ });
413
+ } catch (err) {
414
+ const error = redactErrorMessage(err);
415
+ const payload = {
416
+ type: "count-payload",
417
+ modelName,
418
+ queryKey,
419
+ error
420
+ };
421
+ for (const targetSocketId of targetSocketIds) {
422
+ const ws = sockets.get(targetSocketId);
423
+ if (!ws) continue;
424
+ sendWs(ws, payload);
425
+ }
426
+ }
427
+ }
428
+ }
429
+ };
430
+ const ensureChangeStream = async (tenantId, modelName) => {
431
+ const tenantStreams = changeStreams.get(tenantId) ?? /* @__PURE__ */ new Map();
432
+ changeStreams.set(tenantId, tenantStreams);
433
+ if (tenantStreams.has(modelName)) return;
434
+ const model = await getTenantModel(tenantId, modelName);
435
+ const stream = model.watch([], {
436
+ fullDocument: "updateLookup"
437
+ });
438
+ stream.on("change", () => {
439
+ scheduleDispatchSubscriptionsForModel(tenantId, modelName);
440
+ });
441
+ stream.on("close", () => {
442
+ clearDispatchTimer(tenantId, modelName);
443
+ const map = changeStreams.get(tenantId);
444
+ map?.delete(modelName);
445
+ if (map && map.size === 0) changeStreams.delete(tenantId);
446
+ });
447
+ stream.on("error", () => {
448
+ try {
449
+ clearDispatchTimer(tenantId, modelName);
450
+ stream.close();
451
+ } catch {
452
+ }
453
+ });
454
+ tenantStreams.set(modelName, stream);
455
+ };
456
+ const addSocketSubscription = ({
457
+ socketId,
458
+ tenantId,
459
+ userId,
460
+ modelName,
461
+ queryKey,
462
+ query,
463
+ options,
464
+ dependencyModelNames
465
+ }) => {
466
+ const tenantSubs = subscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
467
+ subscriptions.set(tenantId, tenantSubs);
468
+ const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
469
+ tenantSubs.set(userId, userSubs);
470
+ const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
471
+ userSubs.set(modelName, modelSubs);
472
+ const existing = modelSubs.get(queryKey);
473
+ if (existing) {
474
+ existing.socketIds.add(socketId);
475
+ existing.dependencyModelNames = Array.from(/* @__PURE__ */ new Set([...existing.dependencyModelNames, ...dependencyModelNames]));
476
+ } else {
477
+ modelSubs.set(queryKey, {
478
+ query,
479
+ options,
480
+ dependencyModelNames: Array.from(new Set(dependencyModelNames)),
481
+ socketIds: /* @__PURE__ */ new Set([socketId])
482
+ });
483
+ }
484
+ const byModel = socketSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
485
+ socketSubscriptions.set(socketId, byModel);
486
+ const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
487
+ byModel.set(modelName, querySet);
488
+ querySet.add(queryKey);
489
+ };
490
+ const addSocketCountSubscription = ({
491
+ socketId,
492
+ tenantId,
493
+ userId,
494
+ modelName,
495
+ queryKey,
496
+ query
497
+ }) => {
498
+ const tenantSubs = countSubscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
499
+ countSubscriptions.set(tenantId, tenantSubs);
500
+ const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
501
+ tenantSubs.set(userId, userSubs);
502
+ const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
503
+ userSubs.set(modelName, modelSubs);
504
+ const existing = modelSubs.get(queryKey);
505
+ if (existing) {
506
+ existing.socketIds.add(socketId);
507
+ } else {
508
+ modelSubs.set(queryKey, {
509
+ query,
510
+ socketIds: /* @__PURE__ */ new Set([socketId])
511
+ });
512
+ }
513
+ const byModel = socketCountSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
514
+ socketCountSubscriptions.set(socketId, byModel);
515
+ const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
516
+ byModel.set(modelName, querySet);
517
+ querySet.add(queryKey);
518
+ };
519
+ const removeSocketSubscription = ({
520
+ socketId,
521
+ tenantId,
522
+ userId,
523
+ modelName,
524
+ queryKey
525
+ }) => {
526
+ const tenantSubs = subscriptions.get(tenantId);
527
+ const userSubs = tenantSubs?.get(userId);
528
+ const modelSubs = userSubs?.get(modelName);
529
+ const sub = modelSubs?.get(queryKey);
530
+ const affectedModelNames = /* @__PURE__ */ new Set([modelName, ...sub?.dependencyModelNames ?? []]);
531
+ if (sub) {
532
+ sub.socketIds.delete(socketId);
533
+ if (!sub.socketIds.size) {
534
+ modelSubs?.delete(queryKey);
535
+ }
536
+ }
537
+ const byModel = socketSubscriptions.get(socketId);
538
+ const set = byModel?.get(modelName);
539
+ if (set) {
540
+ set.delete(queryKey);
541
+ if (!set.size) {
542
+ byModel?.delete(modelName);
543
+ }
544
+ }
545
+ if (modelSubs && modelSubs.size === 0) {
546
+ userSubs?.delete(modelName);
547
+ }
548
+ if (userSubs && userSubs.size === 0) {
549
+ tenantSubs?.delete(userId);
550
+ }
551
+ for (const affectedModelName of affectedModelNames) {
552
+ if (hasAnySubscriptionsDependingOnModel(tenantId, affectedModelName) || hasAnyCountSubscriptionsForModel(tenantId, affectedModelName)) {
553
+ continue;
554
+ }
555
+ const tenantStreams = changeStreams.get(tenantId);
556
+ const stream = tenantStreams?.get(affectedModelName);
557
+ if (stream) {
558
+ try {
559
+ stream.close();
560
+ } catch {
561
+ }
562
+ clearDispatchTimer(tenantId, affectedModelName);
563
+ tenantStreams?.delete(affectedModelName);
564
+ if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
565
+ }
566
+ }
567
+ if (tenantSubs && tenantSubs.size === 0) subscriptions.delete(tenantId);
568
+ if (byModel && byModel.size === 0) socketSubscriptions.delete(socketId);
569
+ };
570
+ const removeSocketCountSubscription = ({
571
+ socketId,
572
+ tenantId,
573
+ userId,
574
+ modelName,
575
+ queryKey
576
+ }) => {
577
+ const tenantSubs = countSubscriptions.get(tenantId);
578
+ const userSubs = tenantSubs?.get(userId);
579
+ const modelSubs = userSubs?.get(modelName);
580
+ const sub = modelSubs?.get(queryKey);
581
+ if (sub) {
582
+ sub.socketIds.delete(socketId);
583
+ if (!sub.socketIds.size) {
584
+ modelSubs?.delete(queryKey);
585
+ }
586
+ }
587
+ const byModel = socketCountSubscriptions.get(socketId);
588
+ const set = byModel?.get(modelName);
589
+ if (set) {
590
+ set.delete(queryKey);
591
+ if (!set.size) {
592
+ byModel?.delete(modelName);
593
+ }
594
+ }
595
+ if (modelSubs && modelSubs.size === 0) {
596
+ userSubs?.delete(modelName);
597
+ }
598
+ if (userSubs && userSubs.size === 0) {
599
+ tenantSubs?.delete(userId);
600
+ }
601
+ if (!hasAnySubscriptionsDependingOnModel(tenantId, modelName) && !hasAnyCountSubscriptionsForModel(tenantId, modelName)) {
602
+ const tenantStreams = changeStreams.get(tenantId);
603
+ const stream = tenantStreams?.get(modelName);
604
+ if (stream) {
605
+ try {
606
+ stream.close();
607
+ } catch {
608
+ }
609
+ clearDispatchTimer(tenantId, modelName);
610
+ tenantStreams?.delete(modelName);
611
+ if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
612
+ }
613
+ }
614
+ if (tenantSubs && tenantSubs.size === 0) countSubscriptions.delete(tenantId);
615
+ if (byModel && byModel.size === 0) socketCountSubscriptions.delete(socketId);
616
+ };
617
+ const getSocketSubscriptionCount = (socketId) => {
618
+ let count = 0;
619
+ const querySubscriptionsByModel = socketSubscriptions.get(socketId);
620
+ if (querySubscriptionsByModel) {
621
+ for (const set of querySubscriptionsByModel.values()) count += set.size;
622
+ }
623
+ const countSubscriptionsByModel = socketCountSubscriptions.get(socketId);
624
+ if (countSubscriptionsByModel) {
625
+ for (const set of countSubscriptionsByModel.values()) count += set.size;
626
+ }
627
+ return count;
628
+ };
629
+ const cleanupSocket = (socketId) => {
630
+ const meta = socketMeta.get(socketId);
631
+ if (meta) {
632
+ const byModel = socketSubscriptions.get(socketId);
633
+ if (byModel) {
634
+ for (const [modelName, keys] of byModel.entries()) {
635
+ for (const queryKey of keys.values()) {
636
+ removeSocketSubscription({
637
+ socketId,
638
+ tenantId: meta.tenantId,
639
+ userId: meta.userId,
640
+ modelName,
641
+ queryKey
642
+ });
643
+ }
644
+ }
645
+ }
646
+ const countByModel = socketCountSubscriptions.get(socketId);
647
+ if (countByModel) {
648
+ for (const [modelName, keys] of countByModel.entries()) {
649
+ for (const queryKey of keys.values()) {
650
+ removeSocketCountSubscription({
651
+ socketId,
652
+ tenantId: meta.tenantId,
653
+ userId: meta.userId,
654
+ modelName,
655
+ queryKey
656
+ });
657
+ }
658
+ }
659
+ }
660
+ }
661
+ socketSubscriptions.delete(socketId);
662
+ socketCountSubscriptions.delete(socketId);
663
+ const cleanupFns = socketCleanup.get(socketId) ?? [];
664
+ socketCleanup.delete(socketId);
665
+ for (const fn of cleanupFns) {
666
+ try {
667
+ fn();
668
+ } catch {
669
+ }
670
+ }
671
+ sockets.delete(socketId);
672
+ socketMeta.delete(socketId);
673
+ socketWrappers.delete(socketId);
674
+ };
675
+ const handleClientMessage = async ({
676
+ socketId,
677
+ meta,
678
+ message
679
+ }) => {
680
+ const ws = sockets.get(socketId);
681
+ if (!ws) return;
682
+ if (message.type === "event") {
683
+ const wrapper = socketWrappers.get(socketId);
684
+ wrapper?.dispatch(message.event, message.payload);
685
+ return;
686
+ }
687
+ const isCountMessage = message.type === "run-count" || message.type === "register-count" || message.type === "remove-count";
688
+ const sendAccessError = (error) => {
689
+ if (isCountMessage) {
690
+ sendWs(ws, {
691
+ type: "count-payload",
692
+ modelName: message.modelName ?? "",
693
+ queryKey: message.queryKey ?? "",
694
+ error
695
+ });
696
+ return;
697
+ }
698
+ sendWs(ws, {
699
+ type: "query-payload",
700
+ modelName: message.modelName ?? "",
701
+ queryKey: message.queryKey ?? "",
702
+ error
703
+ });
704
+ };
705
+ if (!message.modelName || typeof message.modelName !== "string") return;
706
+ if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
707
+ sendAccessError("Model not allowed");
708
+ return;
709
+ }
710
+ if (!message.queryKey || typeof message.queryKey !== "string") return;
711
+ if (message.queryKey.length > QUERY_KEY_MAX_LEN) return;
712
+ if (message.type === "remove-query") {
713
+ removeSocketSubscription({
714
+ socketId,
715
+ tenantId: meta.tenantId,
716
+ userId: meta.userId,
717
+ modelName: message.modelName,
718
+ queryKey: message.queryKey
719
+ });
720
+ return;
721
+ }
722
+ if (message.type === "remove-count") {
723
+ removeSocketCountSubscription({
724
+ socketId,
725
+ tenantId: meta.tenantId,
726
+ userId: meta.userId,
727
+ modelName: message.modelName,
728
+ queryKey: message.queryKey
729
+ });
730
+ return;
731
+ }
732
+ if (!message.query || typeof message.query !== "object") return;
733
+ const ability = meta.ability;
734
+ if (!ability.can("read", message.modelName)) {
735
+ sendAccessError("forbidden");
736
+ return;
737
+ }
738
+ if (message.type === "register-query" || message.type === "run-query") {
739
+ const options = normalizeRtsQueryOptions(message.options);
740
+ if (message.type === "register-query") {
741
+ const existing = socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false;
742
+ if (!existing && getSocketSubscriptionCount(socketId) >= maxSubscriptionsPerSocket) {
743
+ sendWs(ws, {
744
+ type: "query-payload",
745
+ modelName: message.modelName,
746
+ queryKey: message.queryKey,
747
+ error: "Too many subscriptions"
748
+ });
749
+ return;
750
+ }
751
+ let dependencyModelNames = [];
752
+ if (options.populate !== void 0) {
753
+ try {
754
+ dependencyModelNames = await resolveRtsQueryDependencyModelNames({
755
+ tenantId: meta.tenantId,
756
+ ability,
757
+ modelName: message.modelName,
758
+ options,
759
+ allowInternalModels
760
+ });
761
+ } catch (err) {
762
+ const error = redactErrorMessage(err);
763
+ sendWs(ws, {
764
+ type: "query-payload",
765
+ modelName: message.modelName,
766
+ queryKey: message.queryKey,
767
+ error
768
+ });
769
+ return;
770
+ }
771
+ }
772
+ addSocketSubscription({
773
+ socketId,
774
+ tenantId: meta.tenantId,
775
+ userId: meta.userId,
776
+ modelName: message.modelName,
777
+ queryKey: message.queryKey,
778
+ query: message.query,
779
+ options,
780
+ dependencyModelNames
781
+ });
782
+ try {
783
+ const modelNamesToWatch = /* @__PURE__ */ new Set([message.modelName, ...dependencyModelNames]);
784
+ for (const modelName of modelNamesToWatch) {
785
+ await ensureChangeStream(meta.tenantId, modelName);
786
+ }
787
+ } catch (err) {
788
+ const error = redactErrorMessage(err);
789
+ sendWs(ws, {
790
+ type: "query-payload",
791
+ modelName: message.modelName,
792
+ queryKey: message.queryKey,
793
+ error
794
+ });
795
+ return;
796
+ }
797
+ if (message.runInitialQuery === false) {
798
+ return;
799
+ }
800
+ }
801
+ try {
802
+ await runAndSendQuery({
803
+ tenantId: meta.tenantId,
804
+ targetSocketIds: [socketId],
805
+ ability,
806
+ modelName: message.modelName,
807
+ queryKey: message.queryKey,
808
+ query: message.query,
809
+ options
810
+ });
811
+ } catch (err) {
812
+ const error = redactErrorMessage(err);
813
+ sendWs(ws, {
814
+ type: "query-payload",
815
+ modelName: message.modelName,
816
+ queryKey: message.queryKey,
817
+ error
818
+ });
819
+ }
820
+ return;
821
+ }
822
+ if (message.type === "register-count" || message.type === "run-count") {
823
+ if (message.type === "register-count") {
824
+ const existing = socketCountSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false;
825
+ if (!existing && getSocketSubscriptionCount(socketId) >= maxSubscriptionsPerSocket) {
826
+ sendWs(ws, {
827
+ type: "count-payload",
828
+ modelName: message.modelName,
829
+ queryKey: message.queryKey,
830
+ error: "Too many subscriptions"
831
+ });
832
+ return;
833
+ }
834
+ addSocketCountSubscription({
835
+ socketId,
836
+ tenantId: meta.tenantId,
837
+ userId: meta.userId,
838
+ modelName: message.modelName,
839
+ queryKey: message.queryKey,
840
+ query: message.query
841
+ });
842
+ try {
843
+ await ensureChangeStream(meta.tenantId, message.modelName);
844
+ } catch (err) {
845
+ const error = redactErrorMessage(err);
846
+ sendWs(ws, {
847
+ type: "count-payload",
848
+ modelName: message.modelName,
849
+ queryKey: message.queryKey,
850
+ error
851
+ });
852
+ return;
853
+ }
854
+ if (message.runInitialQuery === false) {
855
+ return;
856
+ }
857
+ }
858
+ try {
859
+ await runAndSendCount({
860
+ tenantId: meta.tenantId,
861
+ targetSocketIds: [socketId],
862
+ ability,
863
+ modelName: message.modelName,
864
+ queryKey: message.queryKey,
865
+ query: message.query
866
+ });
867
+ } catch (err) {
868
+ const error = redactErrorMessage(err);
869
+ sendWs(ws, {
870
+ type: "count-payload",
871
+ modelName: message.modelName,
872
+ queryKey: message.queryKey,
873
+ error
874
+ });
875
+ }
876
+ }
877
+ };
878
+ const initRts = ({
879
+ server,
880
+ path = "/rts",
881
+ sessionMiddleware,
882
+ maxPayloadBytes: maxPayloadBytesArg,
883
+ maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg,
884
+ dispatchDebounceMs: dispatchDebounceMsArg,
885
+ allowInternalModels: allowInternalModelsArg
886
+ }) => {
887
+ if (initializedServers.has(server)) return;
888
+ initializedServers.add(server);
889
+ if (typeof maxPayloadBytesArg === "number" && Number.isFinite(maxPayloadBytesArg) && maxPayloadBytesArg > 0) {
890
+ maxPayloadBytes = Math.floor(maxPayloadBytesArg);
891
+ }
892
+ if (typeof maxSubscriptionsPerSocketArg === "number" && Number.isFinite(maxSubscriptionsPerSocketArg) && maxSubscriptionsPerSocketArg > 0) {
893
+ maxSubscriptionsPerSocket = Math.floor(maxSubscriptionsPerSocketArg);
894
+ }
895
+ if (typeof dispatchDebounceMsArg === "number" && Number.isFinite(dispatchDebounceMsArg) && dispatchDebounceMsArg >= 0) {
896
+ dispatchDebounceMs = Math.floor(dispatchDebounceMsArg);
897
+ }
898
+ allowInternalModels = Boolean(allowInternalModelsArg);
899
+ const wss = new WebSocketServer({
900
+ noServer: true,
901
+ maxPayload: maxPayloadBytes
902
+ });
903
+ server.on("upgrade", (req, socket, head) => {
904
+ ensureSocketErrorHandler(socket, () => ({
905
+ upgradeHost: req.headers.host ?? "",
906
+ upgradeUrl: req.url ?? "",
907
+ userAgent: typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : ""
908
+ }));
909
+ upgradeMeta.delete(req);
910
+ let url;
911
+ try {
912
+ url = new URL(req.url ?? "", `http://${req.headers.host ?? "localhost"}`);
913
+ } catch {
914
+ badRequest(socket, "Invalid URL");
915
+ return;
916
+ }
917
+ if (url.pathname !== path) return;
918
+ void (async () => {
919
+ try {
920
+ const meta = await parseUpgradeMeta({
921
+ req,
922
+ url,
923
+ sessionMiddleware
924
+ });
925
+ upgradeMeta.set(req, meta);
926
+ wss.handleUpgrade(req, socket, head, (ws) => {
927
+ wss.emit("connection", ws, req);
928
+ });
929
+ } catch (err) {
930
+ const message = err instanceof Error ? err.message : "RTS upgrade failed";
931
+ if (message.startsWith("Missing rb-tenant-id")) {
932
+ badRequest(socket, message);
933
+ return;
934
+ }
935
+ unauthorized(socket, message);
936
+ return;
937
+ }
938
+ })().catch(() => {
939
+ badRequest(socket, "RTS upgrade failed");
940
+ });
941
+ });
942
+ wss.on("connection", (ws, req) => {
943
+ const meta = upgradeMeta.get(req);
944
+ upgradeMeta.delete(req);
945
+ if (!meta) {
946
+ try {
947
+ ws.close();
948
+ } catch {
949
+ }
950
+ return;
951
+ }
952
+ const socketId = randomUUID();
953
+ sockets.set(socketId, ws);
954
+ socketMeta.set(socketId, meta);
955
+ const wrapper = new RtsSocket({
956
+ id: socketId,
957
+ ws,
958
+ meta
959
+ });
960
+ socketWrappers.set(socketId, wrapper);
961
+ const cleanupFns = [];
962
+ for (const handler of customHandlers) {
963
+ try {
964
+ const cleanup = handler(wrapper);
965
+ if (typeof cleanup === "function") cleanupFns.push(cleanup);
966
+ } catch {
967
+ }
968
+ }
969
+ if (cleanupFns.length) socketCleanup.set(socketId, cleanupFns);
970
+ ws.on("message", (raw) => {
971
+ let parsed;
972
+ try {
973
+ parsed = safeJsonParse(raw);
974
+ } catch {
975
+ return;
976
+ }
977
+ if (!parsed || typeof parsed !== "object") return;
978
+ const message = parsed;
979
+ if (message.type !== "event" && message.type !== "run-query" && message.type !== "register-query" && message.type !== "remove-query" && message.type !== "run-count" && message.type !== "register-count" && message.type !== "remove-count") return;
980
+ void handleClientMessage({
981
+ socketId,
982
+ meta,
983
+ message
984
+ });
985
+ });
986
+ ws.on("close", () => {
987
+ cleanupSocket(socketId);
988
+ });
989
+ ws.on("error", () => {
990
+ cleanupSocket(socketId);
991
+ });
992
+ });
993
+ };
994
+ const registerRtsHandler = (handler) => {
995
+ customHandlers.push(handler);
996
+ };
997
+ const notifyRtsModelChanged = (tenantId, modelName) => {
998
+ scheduleDispatchSubscriptionsForModel(tenantId, modelName);
999
+ };
1000
+ export {
1001
+ initRts,
1002
+ notifyRtsModelChanged,
1003
+ registerRtsHandler,
1004
+ routes
1005
+ };
1006
+ //# sourceMappingURL=index.js.map