@rpcbase/server 0.524.0 → 0.526.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 (46) hide show
  1. package/dist/email-Dzauaq11.js +12449 -0
  2. package/dist/email-Dzauaq11.js.map +1 -0
  3. package/dist/handler-BLwgdQv-.js +544 -0
  4. package/dist/handler-BLwgdQv-.js.map +1 -0
  5. package/dist/handler-Cq-OJ0Rf.js +182 -0
  6. package/dist/handler-Cq-OJ0Rf.js.map +1 -0
  7. package/dist/handler-Cq6MsoD4.js +124 -0
  8. package/dist/handler-Cq6MsoD4.js.map +1 -0
  9. package/dist/handler-Cx_ZP_NB.js +749 -0
  10. package/dist/handler-Cx_ZP_NB.js.map +1 -0
  11. package/dist/index.js +4783 -4935
  12. package/dist/index.js.map +1 -1
  13. package/dist/notifications.js +134 -199
  14. package/dist/notifications.js.map +1 -1
  15. package/dist/queryExecutor-DYVlCvns.js +295 -0
  16. package/dist/queryExecutor-DYVlCvns.js.map +1 -0
  17. package/dist/render_resend-CQb8_8G7.js +7 -0
  18. package/dist/render_resend-CQb8_8G7.js.map +1 -0
  19. package/dist/rts/index.js +581 -725
  20. package/dist/rts/index.js.map +1 -1
  21. package/dist/schemas-BR3K5Luo.js +3824 -0
  22. package/dist/schemas-BR3K5Luo.js.map +1 -0
  23. package/dist/shared-BfMSZm2P.js +87 -0
  24. package/dist/shared-BfMSZm2P.js.map +1 -0
  25. package/dist/ssrMiddleware.d.ts +1 -1
  26. package/dist/uploads.js +71 -84
  27. package/dist/uploads.js.map +1 -1
  28. package/package.json +9 -9
  29. package/dist/email-DK8uUU4X.js +0 -8045
  30. package/dist/email-DK8uUU4X.js.map +0 -1
  31. package/dist/handler-0rPClEv4.js +0 -663
  32. package/dist/handler-0rPClEv4.js.map +0 -1
  33. package/dist/handler-3uwH4f67.js +0 -924
  34. package/dist/handler-3uwH4f67.js.map +0 -1
  35. package/dist/handler-BsauvgjA.js +0 -153
  36. package/dist/handler-BsauvgjA.js.map +0 -1
  37. package/dist/handler-DnSJAQ_B.js +0 -203
  38. package/dist/handler-DnSJAQ_B.js.map +0 -1
  39. package/dist/queryExecutor-Bg1GGL3j.js +0 -407
  40. package/dist/queryExecutor-Bg1GGL3j.js.map +0 -1
  41. package/dist/render_resend_false-MiC__Smr.js +0 -6
  42. package/dist/render_resend_false-MiC__Smr.js.map +0 -1
  43. package/dist/schemas-Cjdjgehl.js +0 -4225
  44. package/dist/schemas-Cjdjgehl.js.map +0 -1
  45. package/dist/shared-BqZiSOmf.js +0 -111
  46. package/dist/shared-BqZiSOmf.js.map +0 -1
package/dist/rts/index.js CHANGED
@@ -1,752 +1,608 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { a as normalizeRtsQueryOptions, c as runRtsQuery, n as RTS_USER_ID_HEADER, o as resolveRtsQueryDependencyModelNames, t as RTS_TENANT_ID_QUERY_PARAM } from "../queryExecutor-DYVlCvns.js";
2
2
  import { models } from "@rpcbase/db";
3
- import { buildAbilityFromSession, getTenantRolesFromSessionUser, buildAbility } from "@rpcbase/db/acl";
3
+ import { buildAbility, buildAbilityFromSession, getTenantRolesFromSessionUser } from "@rpcbase/db/acl";
4
+ import { randomUUID } from "node:crypto";
4
5
  import { WebSocketServer } from "ws";
5
- import { R as RTS_TENANT_ID_QUERY_PARAM, c as RTS_USER_ID_HEADER, n as normalizeRtsQueryOptions, d as resolveRtsQueryDependencyModelNames, a as runRtsQuery } from "../queryExecutor-Bg1GGL3j.js";
6
- const routes = Object.entries({
7
- .../* @__PURE__ */ Object.assign({ "./api/changes/handler.ts": () => import("../handler-BsauvgjA.js") })
8
- }).reduce((acc, [path, mod]) => {
9
- acc[path.replace("./api/", "@rpcbase/server/rts/api/")] = mod;
10
- return acc;
6
+ //#region src/rts/routes.ts
7
+ var routes = Object.entries({ .../* @__PURE__ */ Object.assign({ "./api/changes/handler.ts": () => import("../handler-Cq6MsoD4.js") }) }).reduce((acc, [path, mod]) => {
8
+ acc[path.replace("./api/", "@rpcbase/server/rts/api/")] = mod;
9
+ return acc;
11
10
  }, {});
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 subscriptions = /* @__PURE__ */ new Map();
25
- const changeStreams = /* @__PURE__ */ new Map();
26
- const dispatchTimers = /* @__PURE__ */ new Map();
27
- const upgradeMeta = /* @__PURE__ */ new WeakMap();
28
- let maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES;
29
- let maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;
30
- let dispatchDebounceMs = DEFAULT_DISPATCH_DEBOUNCE_MS;
31
- let allowInternalModels = false;
32
- class RtsSocket {
33
- id;
34
- tenantId;
35
- userId;
36
- ws;
37
- handlers = /* @__PURE__ */ new Map();
38
- constructor({
39
- id,
40
- ws,
41
- meta
42
- }) {
43
- this.id = id;
44
- this.ws = ws;
45
- this.tenantId = meta.tenantId;
46
- this.userId = meta.userId;
47
- }
48
- on(event, handler) {
49
- const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
50
- set.add(handler);
51
- this.handlers.set(event, set);
52
- return () => this.off(event, handler);
53
- }
54
- off(event, handler) {
55
- const set = this.handlers.get(event);
56
- if (!set) return;
57
- set.delete(handler);
58
- if (!set.size) this.handlers.delete(event);
59
- }
60
- emit(event, payload) {
61
- sendWs(this.ws, {
62
- type: "event",
63
- event,
64
- payload
65
- });
66
- }
67
- close() {
68
- try {
69
- this.ws.close();
70
- } catch {
71
- }
72
- }
73
- dispatch(event, payload) {
74
- const set = this.handlers.get(event);
75
- if (!set) return;
76
- for (const handler of set) {
77
- handler(payload);
78
- }
79
- }
80
- }
81
- const rawToText = (raw) => {
82
- if (typeof raw === "string") return raw;
83
- if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString();
84
- if (Array.isArray(raw)) return Buffer.concat(raw).toString();
85
- return raw.toString();
11
+ //#endregion
12
+ //#region src/rts/index.ts
13
+ var QUERY_KEY_MAX_LEN = 4096;
14
+ var INTERNAL_MODEL_NAMES = new Set(["RBRtsChange", "RBRtsCounter"]);
15
+ var DEFAULT_MAX_PAYLOAD_BYTES = 1024 * 1024;
16
+ var DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 256;
17
+ var DEFAULT_DISPATCH_DEBOUNCE_MS = 25;
18
+ var initializedServers = /* @__PURE__ */ new WeakSet();
19
+ var customHandlers = [];
20
+ var sockets = /* @__PURE__ */ new Map();
21
+ var socketMeta = /* @__PURE__ */ new Map();
22
+ var socketWrappers = /* @__PURE__ */ new Map();
23
+ var socketCleanup = /* @__PURE__ */ new Map();
24
+ var socketSubscriptions = /* @__PURE__ */ new Map();
25
+ var subscriptions = /* @__PURE__ */ new Map();
26
+ var changeStreams = /* @__PURE__ */ new Map();
27
+ var dispatchTimers = /* @__PURE__ */ new Map();
28
+ var upgradeMeta = /* @__PURE__ */ new WeakMap();
29
+ var maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES;
30
+ var maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;
31
+ var dispatchDebounceMs = DEFAULT_DISPATCH_DEBOUNCE_MS;
32
+ var allowInternalModels = false;
33
+ var RtsSocket = class {
34
+ id;
35
+ tenantId;
36
+ userId;
37
+ ws;
38
+ handlers = /* @__PURE__ */ new Map();
39
+ constructor({ id, ws, meta }) {
40
+ this.id = id;
41
+ this.ws = ws;
42
+ this.tenantId = meta.tenantId;
43
+ this.userId = meta.userId;
44
+ }
45
+ on(event, handler) {
46
+ const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
47
+ set.add(handler);
48
+ this.handlers.set(event, set);
49
+ return () => this.off(event, handler);
50
+ }
51
+ off(event, handler) {
52
+ const set = this.handlers.get(event);
53
+ if (!set) return;
54
+ set.delete(handler);
55
+ if (!set.size) this.handlers.delete(event);
56
+ }
57
+ emit(event, payload) {
58
+ sendWs(this.ws, {
59
+ type: "event",
60
+ event,
61
+ payload
62
+ });
63
+ }
64
+ close() {
65
+ try {
66
+ this.ws.close();
67
+ } catch {}
68
+ }
69
+ dispatch(event, payload) {
70
+ const set = this.handlers.get(event);
71
+ if (!set) return;
72
+ for (const handler of set) handler(payload);
73
+ }
86
74
  };
87
- const safeJsonParse = (raw) => JSON.parse(rawToText(raw));
88
- const sendWs = (ws, message) => {
89
- if (ws.readyState !== 1) return;
90
- ws.send(JSON.stringify(message));
75
+ var rawToText = (raw) => {
76
+ if (typeof raw === "string") return raw;
77
+ if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString();
78
+ if (Array.isArray(raw)) return Buffer.concat(raw).toString();
79
+ return raw.toString();
91
80
  };
92
- const ensureSocketErrorHandler = (socket, context) => {
93
- if (socket.listenerCount("error") > 0) return;
94
- socket.on("error", (err) => {
95
- const message = err instanceof Error ? err.message : String(err);
96
- const name = err instanceof Error ? err.name : typeof err;
97
- const stack = err instanceof Error ? err.stack : void 0;
98
- const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
99
- if (code === "ECONNRESET" || code == null && message.includes("ECONNRESET")) return;
100
- console.warn("[rb/rts] socket error", {
101
- name,
102
- code,
103
- message,
104
- stack,
105
- remoteAddress: socket.remoteAddress,
106
- remotePort: socket.remotePort,
107
- localAddress: socket.localAddress,
108
- localPort: socket.localPort,
109
- ...context?.()
110
- });
111
- });
81
+ var safeJsonParse = (raw) => JSON.parse(rawToText(raw));
82
+ var sendWs = (ws, message) => {
83
+ if (ws.readyState !== 1) return;
84
+ ws.send(JSON.stringify(message));
112
85
  };
113
- const redactErrorMessage = (err) => {
114
- const raw = err instanceof Error ? err.message : "Unknown error";
115
- const trimmedModelList = raw.replace(/\.\s+Available models:[\s\S]*$/, "");
116
- const maxLen = 256;
117
- if (trimmedModelList.length <= maxLen) return trimmedModelList;
118
- return trimmedModelList.slice(0, maxLen);
86
+ var ensureSocketErrorHandler = (socket, context) => {
87
+ if (socket.listenerCount("error") > 0) return;
88
+ socket.on("error", (err) => {
89
+ const message = err instanceof Error ? err.message : String(err);
90
+ const name = err instanceof Error ? err.name : typeof err;
91
+ const stack = err instanceof Error ? err.stack : void 0;
92
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
93
+ if (code === "ECONNRESET" || code == null && message.includes("ECONNRESET")) return;
94
+ console.warn("[rb/rts] socket error", {
95
+ name,
96
+ code,
97
+ message,
98
+ stack,
99
+ remoteAddress: socket.remoteAddress,
100
+ remotePort: socket.remotePort,
101
+ localAddress: socket.localAddress,
102
+ localPort: socket.localPort,
103
+ ...context?.()
104
+ });
105
+ });
119
106
  };
120
- const unauthorized = (socket, message = "Unauthorized") => {
121
- ensureSocketErrorHandler(socket);
122
- try {
123
- socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
124
- socket.write(`Error: ${message}\r
125
- `);
126
- socket.end();
127
- } catch {
128
- socket.destroy();
129
- }
107
+ var redactErrorMessage = (err) => {
108
+ const trimmedModelList = (err instanceof Error ? err.message : "Unknown error").replace(/\.\s+Available models:[\s\S]*$/, "");
109
+ const maxLen = 256;
110
+ if (trimmedModelList.length <= maxLen) return trimmedModelList;
111
+ return trimmedModelList.slice(0, maxLen);
130
112
  };
131
- const badRequest = (socket, message = "Bad Request") => {
132
- ensureSocketErrorHandler(socket);
133
- try {
134
- socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
135
- socket.write(`Error: ${message}\r
136
- `);
137
- socket.end();
138
- } catch {
139
- socket.destroy();
140
- }
113
+ var unauthorized = (socket, message = "Unauthorized") => {
114
+ ensureSocketErrorHandler(socket);
115
+ try {
116
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
117
+ socket.write(`Error: ${message}\r\n`);
118
+ socket.end();
119
+ } catch {
120
+ socket.destroy();
121
+ }
141
122
  };
142
- const runSessionMiddleware = async (sessionMiddleware, req) => {
143
- await new Promise((resolve, reject) => {
144
- const next = (err) => {
145
- if (err) reject(err);
146
- else resolve();
147
- };
148
- sessionMiddleware(req, {}, next);
149
- });
123
+ var badRequest = (socket, message = "Bad Request") => {
124
+ ensureSocketErrorHandler(socket);
125
+ try {
126
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
127
+ socket.write(`Error: ${message}\r\n`);
128
+ socket.end();
129
+ } catch {
130
+ socket.destroy();
131
+ }
150
132
  };
151
- const parseUpgradeMeta = async ({
152
- req,
153
- url,
154
- sessionMiddleware
155
- }) => {
156
- const tenantId = url.searchParams.get(RTS_TENANT_ID_QUERY_PARAM);
157
- if (!tenantId) {
158
- throw new Error("Missing rb-tenant-id query parameter");
159
- }
160
- if (sessionMiddleware) {
161
- const upgradeReq = req;
162
- try {
163
- await runSessionMiddleware(sessionMiddleware, upgradeReq);
164
- } catch {
165
- throw new Error("Failed to load session for RTS");
166
- }
167
- const sessionUser = upgradeReq.session?.user;
168
- const sessionUserId = sessionUser?.id;
169
- if (!sessionUserId) {
170
- throw new Error("Not signed in (missing session.user.id)");
171
- }
172
- const signedInTenants = sessionUser?.signedInTenants;
173
- const currentTenantId = sessionUser?.currentTenantId;
174
- if (Array.isArray(signedInTenants) && signedInTenants.length > 0) {
175
- if (!signedInTenants.includes(tenantId)) {
176
- throw new Error("Tenant not authorized for this session");
177
- }
178
- } else if (currentTenantId) {
179
- if (currentTenantId !== tenantId) {
180
- throw new Error("Tenant not authorized for this session");
181
- }
182
- } else {
183
- throw new Error("Tenant not authorized for this session");
184
- }
185
- const ability2 = buildAbilityFromSession({
186
- tenantId,
187
- session: upgradeReq.session
188
- });
189
- return {
190
- tenantId,
191
- userId: sessionUserId,
192
- ability: ability2
193
- };
194
- }
195
- const raw = req.headers[RTS_USER_ID_HEADER];
196
- const headerUserId = Array.isArray(raw) ? raw[0] : raw;
197
- if (!headerUserId) {
198
- throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
199
- }
200
- const rbCtx = {
201
- req: {
202
- session: null
203
- }
204
- };
205
- const User = await models.getGlobal("RBUser", rbCtx);
206
- const user = await User.findById(headerUserId, {
207
- tenants: 1,
208
- tenantRoles: 1
209
- }).lean();
210
- const tenantsRaw = user?.tenants;
211
- const tenants = Array.isArray(tenantsRaw) ? tenantsRaw.map((t) => String(t)) : [];
212
- if (!tenants.includes(tenantId)) {
213
- throw new Error("Tenant not authorized for this session");
214
- }
215
- const roles = getTenantRolesFromSessionUser(user, tenantId);
216
- const ability = buildAbility({
217
- tenantId,
218
- userId: headerUserId,
219
- roles: roles.length ? roles : ["owner"]
220
- });
221
- return {
222
- tenantId,
223
- userId: headerUserId,
224
- ability
225
- };
133
+ var runSessionMiddleware = async (sessionMiddleware, req) => {
134
+ await new Promise((resolve, reject) => {
135
+ const next = (err) => {
136
+ if (err) reject(err);
137
+ else resolve();
138
+ };
139
+ sessionMiddleware(req, {}, next);
140
+ });
226
141
  };
227
- const getTenantModel = async (tenantId, modelName) => {
228
- const ctx = {
229
- req: {
230
- session: {
231
- user: {
232
- currentTenantId: tenantId
233
- }
234
- }
235
- }
236
- };
237
- return models.getUnsafe(modelName, ctx);
142
+ var parseUpgradeMeta = async ({ req, url, sessionMiddleware }) => {
143
+ const tenantId = url.searchParams.get(RTS_TENANT_ID_QUERY_PARAM);
144
+ if (!tenantId) throw new Error("Missing rb-tenant-id query parameter");
145
+ if (sessionMiddleware) {
146
+ const upgradeReq = req;
147
+ try {
148
+ await runSessionMiddleware(sessionMiddleware, upgradeReq);
149
+ } catch {
150
+ throw new Error("Failed to load session for RTS");
151
+ }
152
+ const sessionUser = upgradeReq.session?.user;
153
+ const sessionUserId = sessionUser?.id;
154
+ if (!sessionUserId) throw new Error("Not signed in (missing session.user.id)");
155
+ const signedInTenants = sessionUser?.signedInTenants;
156
+ const currentTenantId = sessionUser?.currentTenantId;
157
+ if (Array.isArray(signedInTenants) && signedInTenants.length > 0) {
158
+ if (!signedInTenants.includes(tenantId)) throw new Error("Tenant not authorized for this session");
159
+ } else if (currentTenantId) {
160
+ if (currentTenantId !== tenantId) throw new Error("Tenant not authorized for this session");
161
+ } else throw new Error("Tenant not authorized for this session");
162
+ return {
163
+ tenantId,
164
+ userId: sessionUserId,
165
+ ability: buildAbilityFromSession({
166
+ tenantId,
167
+ session: upgradeReq.session
168
+ })
169
+ };
170
+ }
171
+ const raw = req.headers[RTS_USER_ID_HEADER];
172
+ const headerUserId = Array.isArray(raw) ? raw[0] : raw;
173
+ if (!headerUserId) throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
174
+ const user = await (await models.getGlobal("RBUser", { req: { session: null } })).findById(headerUserId, {
175
+ tenants: 1,
176
+ tenantRoles: 1
177
+ }).lean();
178
+ const tenantsRaw = user?.tenants;
179
+ if (!(Array.isArray(tenantsRaw) ? tenantsRaw.map((t) => String(t)) : []).includes(tenantId)) throw new Error("Tenant not authorized for this session");
180
+ const roles = getTenantRolesFromSessionUser(user, tenantId);
181
+ return {
182
+ tenantId,
183
+ userId: headerUserId,
184
+ ability: buildAbility({
185
+ tenantId,
186
+ userId: headerUserId,
187
+ roles: roles.length ? roles : ["owner"]
188
+ })
189
+ };
238
190
  };
239
- const makeDispatchKey = (tenantId, modelName) => `${tenantId}:${modelName}`;
240
- const clearDispatchTimer = (tenantId, modelName) => {
241
- const key = makeDispatchKey(tenantId, modelName);
242
- const timer = dispatchTimers.get(key);
243
- if (!timer) return;
244
- clearTimeout(timer);
245
- dispatchTimers.delete(key);
191
+ var getTenantModel = async (tenantId, modelName) => {
192
+ const ctx = { req: { session: { user: { currentTenantId: tenantId } } } };
193
+ return models.getUnsafe(modelName, ctx);
246
194
  };
247
- const scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
248
- const key = makeDispatchKey(tenantId, modelName);
249
- if (dispatchTimers.has(key)) return;
250
- const delay = Math.max(0, Math.min(1e3, Math.floor(dispatchDebounceMs)));
251
- dispatchTimers.set(key, setTimeout(() => {
252
- dispatchTimers.delete(key);
253
- void dispatchSubscriptionsForModel(tenantId, modelName);
254
- }, delay));
195
+ var makeDispatchKey = (tenantId, modelName) => `${tenantId}:${modelName}`;
196
+ var clearDispatchTimer = (tenantId, modelName) => {
197
+ const key = makeDispatchKey(tenantId, modelName);
198
+ const timer = dispatchTimers.get(key);
199
+ if (!timer) return;
200
+ clearTimeout(timer);
201
+ dispatchTimers.delete(key);
255
202
  };
256
- const runAndSendQuery = async ({
257
- tenantId,
258
- targetSocketIds,
259
- ability,
260
- modelName,
261
- queryKey,
262
- query,
263
- options
264
- }) => {
265
- const result = await runRtsQuery({
266
- tenantId,
267
- ability,
268
- modelName,
269
- query,
270
- options,
271
- allowInternalModels
272
- });
273
- const payload = {
274
- type: "query-payload",
275
- modelName,
276
- queryKey,
277
- data: result.data,
278
- ...result.pageInfo ? {
279
- pageInfo: result.pageInfo
280
- } : {},
281
- ...typeof result.totalCount === "number" ? {
282
- totalCount: result.totalCount
283
- } : {}
284
- };
285
- for (const socketId of targetSocketIds) {
286
- const ws = sockets.get(socketId);
287
- if (!ws) continue;
288
- sendWs(ws, payload);
289
- }
203
+ var scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
204
+ const key = makeDispatchKey(tenantId, modelName);
205
+ if (dispatchTimers.has(key)) return;
206
+ const delay = Math.max(0, Math.min(1e3, Math.floor(dispatchDebounceMs)));
207
+ dispatchTimers.set(key, setTimeout(() => {
208
+ dispatchTimers.delete(key);
209
+ dispatchSubscriptionsForModel(tenantId, modelName);
210
+ }, delay));
290
211
  };
291
- const subscriptionDependsOnModel = (changedModelName, ownerModelName, subscription) => {
292
- if (ownerModelName === changedModelName) return true;
293
- return subscription.dependencyModelNames.includes(changedModelName);
212
+ var runAndSendQuery = async ({ tenantId, targetSocketIds, ability, modelName, queryKey, query, options }) => {
213
+ const result = await runRtsQuery({
214
+ tenantId,
215
+ ability,
216
+ modelName,
217
+ query,
218
+ options,
219
+ allowInternalModels
220
+ });
221
+ const payload = {
222
+ type: "query-payload",
223
+ modelName,
224
+ queryKey,
225
+ data: result.data,
226
+ ...result.pageInfo ? { pageInfo: result.pageInfo } : {},
227
+ ...typeof result.totalCount === "number" ? { totalCount: result.totalCount } : {}
228
+ };
229
+ for (const socketId of targetSocketIds) {
230
+ const ws = sockets.get(socketId);
231
+ if (!ws) continue;
232
+ sendWs(ws, payload);
233
+ }
294
234
  };
295
- const hasAnySubscriptionsDependingOnModel = (tenantId, modelName) => {
296
- const tenantSubs = subscriptions.get(tenantId);
297
- if (!tenantSubs) return false;
298
- for (const userSubs of tenantSubs.values()) {
299
- for (const [ownerModelName, modelSubs] of userSubs.entries()) {
300
- for (const subscription of modelSubs.values()) {
301
- if (subscriptionDependsOnModel(modelName, ownerModelName, subscription)) {
302
- return true;
303
- }
304
- }
305
- }
306
- }
307
- return false;
235
+ var subscriptionDependsOnModel = (changedModelName, ownerModelName, subscription) => {
236
+ if (ownerModelName === changedModelName) return true;
237
+ return subscription.dependencyModelNames.includes(changedModelName);
308
238
  };
309
- const dispatchSubscriptionsForModel = async (tenantId, modelName) => {
310
- const tenantSubs = subscriptions.get(tenantId);
311
- if (!tenantSubs || !tenantSubs.size) return;
312
- for (const userSubs of tenantSubs.values()) {
313
- for (const [ownerModelName, modelSubs] of userSubs.entries()) {
314
- for (const [queryKey, sub] of modelSubs.entries()) {
315
- if (!subscriptionDependsOnModel(modelName, ownerModelName, sub)) continue;
316
- const targetSocketIds = Array.from(sub.socketIds);
317
- if (!targetSocketIds.length) continue;
318
- const socketId = targetSocketIds[0];
319
- const meta = socketMeta.get(socketId);
320
- const ability = meta?.ability;
321
- if (!ability) continue;
322
- try {
323
- await runAndSendQuery({
324
- tenantId,
325
- targetSocketIds,
326
- ability,
327
- modelName: ownerModelName,
328
- queryKey,
329
- query: sub.query,
330
- options: sub.options
331
- });
332
- } catch (err) {
333
- const error = redactErrorMessage(err);
334
- const payload = {
335
- type: "query-payload",
336
- modelName: ownerModelName,
337
- queryKey,
338
- error
339
- };
340
- for (const socketId2 of targetSocketIds) {
341
- const ws = sockets.get(socketId2);
342
- if (!ws) continue;
343
- sendWs(ws, payload);
344
- }
345
- }
346
- }
347
- }
348
- }
239
+ var hasAnySubscriptionsDependingOnModel = (tenantId, modelName) => {
240
+ const tenantSubs = subscriptions.get(tenantId);
241
+ if (!tenantSubs) return false;
242
+ 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;
243
+ return false;
349
244
  };
350
- const ensureChangeStream = async (tenantId, modelName) => {
351
- const tenantStreams = changeStreams.get(tenantId) ?? /* @__PURE__ */ new Map();
352
- changeStreams.set(tenantId, tenantStreams);
353
- if (tenantStreams.has(modelName)) return;
354
- const model = await getTenantModel(tenantId, modelName);
355
- const stream = model.watch([], {
356
- fullDocument: "updateLookup"
357
- });
358
- stream.on("change", () => {
359
- scheduleDispatchSubscriptionsForModel(tenantId, modelName);
360
- });
361
- stream.on("close", () => {
362
- clearDispatchTimer(tenantId, modelName);
363
- const map = changeStreams.get(tenantId);
364
- map?.delete(modelName);
365
- if (map && map.size === 0) changeStreams.delete(tenantId);
366
- });
367
- stream.on("error", () => {
368
- try {
369
- clearDispatchTimer(tenantId, modelName);
370
- stream.close();
371
- } catch {
372
- }
373
- });
374
- tenantStreams.set(modelName, stream);
245
+ var dispatchSubscriptionsForModel = async (tenantId, modelName) => {
246
+ const tenantSubs = subscriptions.get(tenantId);
247
+ if (!tenantSubs || !tenantSubs.size) return;
248
+ for (const userSubs of tenantSubs.values()) for (const [ownerModelName, modelSubs] of userSubs.entries()) for (const [queryKey, sub] of modelSubs.entries()) {
249
+ if (!subscriptionDependsOnModel(modelName, ownerModelName, sub)) continue;
250
+ const targetSocketIds = Array.from(sub.socketIds);
251
+ if (!targetSocketIds.length) continue;
252
+ const socketId = targetSocketIds[0];
253
+ const ability = socketMeta.get(socketId)?.ability;
254
+ if (!ability) continue;
255
+ try {
256
+ await runAndSendQuery({
257
+ tenantId,
258
+ targetSocketIds,
259
+ ability,
260
+ modelName: ownerModelName,
261
+ queryKey,
262
+ query: sub.query,
263
+ options: sub.options
264
+ });
265
+ } catch (err) {
266
+ const payload = {
267
+ type: "query-payload",
268
+ modelName: ownerModelName,
269
+ queryKey,
270
+ error: redactErrorMessage(err)
271
+ };
272
+ for (const socketId of targetSocketIds) {
273
+ const ws = sockets.get(socketId);
274
+ if (!ws) continue;
275
+ sendWs(ws, payload);
276
+ }
277
+ }
278
+ }
375
279
  };
376
- const addSocketSubscription = ({
377
- socketId,
378
- tenantId,
379
- userId,
380
- modelName,
381
- queryKey,
382
- query,
383
- options,
384
- dependencyModelNames
385
- }) => {
386
- const tenantSubs = subscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
387
- subscriptions.set(tenantId, tenantSubs);
388
- const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
389
- tenantSubs.set(userId, userSubs);
390
- const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
391
- userSubs.set(modelName, modelSubs);
392
- const existing = modelSubs.get(queryKey);
393
- if (existing) {
394
- existing.socketIds.add(socketId);
395
- existing.dependencyModelNames = Array.from(/* @__PURE__ */ new Set([...existing.dependencyModelNames, ...dependencyModelNames]));
396
- } else {
397
- modelSubs.set(queryKey, {
398
- query,
399
- options,
400
- dependencyModelNames: Array.from(new Set(dependencyModelNames)),
401
- socketIds: /* @__PURE__ */ new Set([socketId])
402
- });
403
- }
404
- const byModel = socketSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
405
- socketSubscriptions.set(socketId, byModel);
406
- const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
407
- byModel.set(modelName, querySet);
408
- querySet.add(queryKey);
280
+ var ensureChangeStream = async (tenantId, modelName) => {
281
+ const tenantStreams = changeStreams.get(tenantId) ?? /* @__PURE__ */ new Map();
282
+ changeStreams.set(tenantId, tenantStreams);
283
+ if (tenantStreams.has(modelName)) return;
284
+ const stream = (await getTenantModel(tenantId, modelName)).watch([], { fullDocument: "updateLookup" });
285
+ stream.on("change", () => {
286
+ scheduleDispatchSubscriptionsForModel(tenantId, modelName);
287
+ });
288
+ stream.on("close", () => {
289
+ clearDispatchTimer(tenantId, modelName);
290
+ const map = changeStreams.get(tenantId);
291
+ map?.delete(modelName);
292
+ if (map && map.size === 0) changeStreams.delete(tenantId);
293
+ });
294
+ stream.on("error", () => {
295
+ try {
296
+ clearDispatchTimer(tenantId, modelName);
297
+ stream.close();
298
+ } catch {}
299
+ });
300
+ tenantStreams.set(modelName, stream);
409
301
  };
410
- const removeSocketSubscription = ({
411
- socketId,
412
- tenantId,
413
- userId,
414
- modelName,
415
- queryKey
416
- }) => {
417
- const tenantSubs = subscriptions.get(tenantId);
418
- const userSubs = tenantSubs?.get(userId);
419
- const modelSubs = userSubs?.get(modelName);
420
- const sub = modelSubs?.get(queryKey);
421
- const affectedModelNames = /* @__PURE__ */ new Set([modelName, ...sub?.dependencyModelNames ?? []]);
422
- if (sub) {
423
- sub.socketIds.delete(socketId);
424
- if (!sub.socketIds.size) {
425
- modelSubs?.delete(queryKey);
426
- }
427
- }
428
- const byModel = socketSubscriptions.get(socketId);
429
- const set = byModel?.get(modelName);
430
- if (set) {
431
- set.delete(queryKey);
432
- if (!set.size) {
433
- byModel?.delete(modelName);
434
- }
435
- }
436
- if (modelSubs && modelSubs.size === 0) {
437
- userSubs?.delete(modelName);
438
- }
439
- if (userSubs && userSubs.size === 0) {
440
- tenantSubs?.delete(userId);
441
- }
442
- for (const affectedModelName of affectedModelNames) {
443
- if (hasAnySubscriptionsDependingOnModel(tenantId, affectedModelName)) continue;
444
- const tenantStreams = changeStreams.get(tenantId);
445
- const stream = tenantStreams?.get(affectedModelName);
446
- if (stream) {
447
- try {
448
- stream.close();
449
- } catch {
450
- }
451
- clearDispatchTimer(tenantId, affectedModelName);
452
- tenantStreams?.delete(affectedModelName);
453
- if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
454
- }
455
- }
456
- if (tenantSubs && tenantSubs.size === 0) subscriptions.delete(tenantId);
457
- if (byModel && byModel.size === 0) socketSubscriptions.delete(socketId);
302
+ var addSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKey, query, options, dependencyModelNames }) => {
303
+ const tenantSubs = subscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
304
+ subscriptions.set(tenantId, tenantSubs);
305
+ const userSubs = tenantSubs.get(userId) ?? /* @__PURE__ */ new Map();
306
+ tenantSubs.set(userId, userSubs);
307
+ const modelSubs = userSubs.get(modelName) ?? /* @__PURE__ */ new Map();
308
+ userSubs.set(modelName, modelSubs);
309
+ const existing = modelSubs.get(queryKey);
310
+ if (existing) {
311
+ existing.socketIds.add(socketId);
312
+ existing.dependencyModelNames = Array.from(new Set([...existing.dependencyModelNames, ...dependencyModelNames]));
313
+ } else modelSubs.set(queryKey, {
314
+ query,
315
+ options,
316
+ dependencyModelNames: Array.from(new Set(dependencyModelNames)),
317
+ socketIds: new Set([socketId])
318
+ });
319
+ const byModel = socketSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
320
+ socketSubscriptions.set(socketId, byModel);
321
+ const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
322
+ byModel.set(modelName, querySet);
323
+ querySet.add(queryKey);
458
324
  };
459
- const cleanupSocket = (socketId) => {
460
- const meta = socketMeta.get(socketId);
461
- if (meta) {
462
- const byModel = socketSubscriptions.get(socketId);
463
- if (byModel) {
464
- for (const [modelName, keys] of byModel.entries()) {
465
- for (const queryKey of keys.values()) {
466
- removeSocketSubscription({
467
- socketId,
468
- tenantId: meta.tenantId,
469
- userId: meta.userId,
470
- modelName,
471
- queryKey
472
- });
473
- }
474
- }
475
- }
476
- }
477
- socketSubscriptions.delete(socketId);
478
- const cleanupFns = socketCleanup.get(socketId) ?? [];
479
- socketCleanup.delete(socketId);
480
- for (const fn of cleanupFns) {
481
- try {
482
- fn();
483
- } catch {
484
- }
485
- }
486
- sockets.delete(socketId);
487
- socketMeta.delete(socketId);
488
- socketWrappers.delete(socketId);
325
+ var removeSocketSubscription = ({ socketId, tenantId, userId, modelName, queryKey }) => {
326
+ const tenantSubs = subscriptions.get(tenantId);
327
+ const userSubs = tenantSubs?.get(userId);
328
+ const modelSubs = userSubs?.get(modelName);
329
+ const sub = modelSubs?.get(queryKey);
330
+ const affectedModelNames = new Set([modelName, ...sub?.dependencyModelNames ?? []]);
331
+ if (sub) {
332
+ sub.socketIds.delete(socketId);
333
+ if (!sub.socketIds.size) modelSubs?.delete(queryKey);
334
+ }
335
+ const byModel = socketSubscriptions.get(socketId);
336
+ const set = byModel?.get(modelName);
337
+ if (set) {
338
+ set.delete(queryKey);
339
+ if (!set.size) byModel?.delete(modelName);
340
+ }
341
+ if (modelSubs && modelSubs.size === 0) userSubs?.delete(modelName);
342
+ if (userSubs && userSubs.size === 0) tenantSubs?.delete(userId);
343
+ for (const affectedModelName of affectedModelNames) {
344
+ if (hasAnySubscriptionsDependingOnModel(tenantId, affectedModelName)) continue;
345
+ const tenantStreams = changeStreams.get(tenantId);
346
+ const stream = tenantStreams?.get(affectedModelName);
347
+ if (stream) {
348
+ try {
349
+ stream.close();
350
+ } catch {}
351
+ clearDispatchTimer(tenantId, affectedModelName);
352
+ tenantStreams?.delete(affectedModelName);
353
+ if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
354
+ }
355
+ }
356
+ if (tenantSubs && tenantSubs.size === 0) subscriptions.delete(tenantId);
357
+ if (byModel && byModel.size === 0) socketSubscriptions.delete(socketId);
489
358
  };
490
- const handleClientMessage = async ({
491
- socketId,
492
- meta,
493
- message
494
- }) => {
495
- const ws = sockets.get(socketId);
496
- if (!ws) return;
497
- if (message.type === "event") {
498
- const wrapper = socketWrappers.get(socketId);
499
- wrapper?.dispatch(message.event, message.payload);
500
- return;
501
- }
502
- if (!message.modelName || typeof message.modelName !== "string") return;
503
- if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
504
- sendWs(ws, {
505
- type: "query-payload",
506
- modelName: message.modelName,
507
- queryKey: message.queryKey ?? "",
508
- error: "Model not allowed"
509
- });
510
- return;
511
- }
512
- if (!message.queryKey || typeof message.queryKey !== "string") return;
513
- if (message.queryKey.length > QUERY_KEY_MAX_LEN) return;
514
- if (message.type === "remove-query") {
515
- removeSocketSubscription({
516
- socketId,
517
- tenantId: meta.tenantId,
518
- userId: meta.userId,
519
- modelName: message.modelName,
520
- queryKey: message.queryKey
521
- });
522
- return;
523
- }
524
- if (!message.query || typeof message.query !== "object") return;
525
- const options = normalizeRtsQueryOptions(message.options);
526
- const ability = meta.ability;
527
- if (!ability.can("read", message.modelName)) {
528
- sendWs(ws, {
529
- type: "query-payload",
530
- modelName: message.modelName,
531
- queryKey: message.queryKey,
532
- error: "forbidden"
533
- });
534
- return;
535
- }
536
- if (message.type === "register-query") {
537
- const existing = socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false;
538
- if (!existing) {
539
- let count = 0;
540
- const byModel = socketSubscriptions.get(socketId);
541
- if (byModel) {
542
- for (const set of byModel.values()) count += set.size;
543
- }
544
- if (count >= maxSubscriptionsPerSocket) {
545
- sendWs(ws, {
546
- type: "query-payload",
547
- modelName: message.modelName,
548
- queryKey: message.queryKey,
549
- error: "Too many subscriptions"
550
- });
551
- return;
552
- }
553
- }
554
- let dependencyModelNames = [];
555
- if (options.populate !== void 0) {
556
- try {
557
- dependencyModelNames = await resolveRtsQueryDependencyModelNames({
558
- tenantId: meta.tenantId,
559
- ability,
560
- modelName: message.modelName,
561
- options,
562
- allowInternalModels
563
- });
564
- } catch (err) {
565
- const error = redactErrorMessage(err);
566
- sendWs(ws, {
567
- type: "query-payload",
568
- modelName: message.modelName,
569
- queryKey: message.queryKey,
570
- error
571
- });
572
- return;
573
- }
574
- }
575
- addSocketSubscription({
576
- socketId,
577
- tenantId: meta.tenantId,
578
- userId: meta.userId,
579
- modelName: message.modelName,
580
- queryKey: message.queryKey,
581
- query: message.query,
582
- options,
583
- dependencyModelNames
584
- });
585
- try {
586
- const modelNamesToWatch = /* @__PURE__ */ new Set([message.modelName, ...dependencyModelNames]);
587
- for (const modelName of modelNamesToWatch) {
588
- await ensureChangeStream(meta.tenantId, modelName);
589
- }
590
- } catch (err) {
591
- const error = redactErrorMessage(err);
592
- sendWs(ws, {
593
- type: "query-payload",
594
- modelName: message.modelName,
595
- queryKey: message.queryKey,
596
- error
597
- });
598
- return;
599
- }
600
- if (message.runInitialQuery === false) {
601
- return;
602
- }
603
- }
604
- try {
605
- await runAndSendQuery({
606
- tenantId: meta.tenantId,
607
- targetSocketIds: [socketId],
608
- ability,
609
- modelName: message.modelName,
610
- queryKey: message.queryKey,
611
- query: message.query,
612
- options
613
- });
614
- } catch (err) {
615
- const error = redactErrorMessage(err);
616
- sendWs(ws, {
617
- type: "query-payload",
618
- modelName: message.modelName,
619
- queryKey: message.queryKey,
620
- error
621
- });
622
- }
359
+ var cleanupSocket = (socketId) => {
360
+ const meta = socketMeta.get(socketId);
361
+ if (meta) {
362
+ const byModel = socketSubscriptions.get(socketId);
363
+ if (byModel) for (const [modelName, keys] of byModel.entries()) for (const queryKey of keys.values()) removeSocketSubscription({
364
+ socketId,
365
+ tenantId: meta.tenantId,
366
+ userId: meta.userId,
367
+ modelName,
368
+ queryKey
369
+ });
370
+ }
371
+ socketSubscriptions.delete(socketId);
372
+ const cleanupFns = socketCleanup.get(socketId) ?? [];
373
+ socketCleanup.delete(socketId);
374
+ for (const fn of cleanupFns) try {
375
+ fn();
376
+ } catch {}
377
+ sockets.delete(socketId);
378
+ socketMeta.delete(socketId);
379
+ socketWrappers.delete(socketId);
623
380
  };
624
- const initRts = ({
625
- server,
626
- path = "/rts",
627
- sessionMiddleware,
628
- maxPayloadBytes: maxPayloadBytesArg,
629
- maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg,
630
- dispatchDebounceMs: dispatchDebounceMsArg,
631
- allowInternalModels: allowInternalModelsArg
632
- }) => {
633
- if (initializedServers.has(server)) return;
634
- initializedServers.add(server);
635
- if (typeof maxPayloadBytesArg === "number" && Number.isFinite(maxPayloadBytesArg) && maxPayloadBytesArg > 0) {
636
- maxPayloadBytes = Math.floor(maxPayloadBytesArg);
637
- }
638
- if (typeof maxSubscriptionsPerSocketArg === "number" && Number.isFinite(maxSubscriptionsPerSocketArg) && maxSubscriptionsPerSocketArg > 0) {
639
- maxSubscriptionsPerSocket = Math.floor(maxSubscriptionsPerSocketArg);
640
- }
641
- if (typeof dispatchDebounceMsArg === "number" && Number.isFinite(dispatchDebounceMsArg) && dispatchDebounceMsArg >= 0) {
642
- dispatchDebounceMs = Math.floor(dispatchDebounceMsArg);
643
- }
644
- allowInternalModels = Boolean(allowInternalModelsArg);
645
- const wss = new WebSocketServer({
646
- noServer: true,
647
- maxPayload: maxPayloadBytes
648
- });
649
- server.on("upgrade", (req, socket, head) => {
650
- ensureSocketErrorHandler(socket, () => ({
651
- upgradeHost: req.headers.host ?? "",
652
- upgradeUrl: req.url ?? "",
653
- userAgent: typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : ""
654
- }));
655
- upgradeMeta.delete(req);
656
- let url;
657
- try {
658
- url = new URL(req.url ?? "", `http://${req.headers.host ?? "localhost"}`);
659
- } catch {
660
- badRequest(socket, "Invalid URL");
661
- return;
662
- }
663
- if (url.pathname !== path) return;
664
- void (async () => {
665
- try {
666
- const meta = await parseUpgradeMeta({
667
- req,
668
- url,
669
- sessionMiddleware
670
- });
671
- upgradeMeta.set(req, meta);
672
- wss.handleUpgrade(req, socket, head, (ws) => {
673
- wss.emit("connection", ws, req);
674
- });
675
- } catch (err) {
676
- const message = err instanceof Error ? err.message : "RTS upgrade failed";
677
- if (message.startsWith("Missing rb-tenant-id")) {
678
- badRequest(socket, message);
679
- return;
680
- }
681
- unauthorized(socket, message);
682
- return;
683
- }
684
- })().catch(() => {
685
- badRequest(socket, "RTS upgrade failed");
686
- });
687
- });
688
- wss.on("connection", (ws, req) => {
689
- const meta = upgradeMeta.get(req);
690
- upgradeMeta.delete(req);
691
- if (!meta) {
692
- try {
693
- ws.close();
694
- } catch {
695
- }
696
- return;
697
- }
698
- const socketId = randomUUID();
699
- sockets.set(socketId, ws);
700
- socketMeta.set(socketId, meta);
701
- const wrapper = new RtsSocket({
702
- id: socketId,
703
- ws,
704
- meta
705
- });
706
- socketWrappers.set(socketId, wrapper);
707
- const cleanupFns = [];
708
- for (const handler of customHandlers) {
709
- try {
710
- const cleanup = handler(wrapper);
711
- if (typeof cleanup === "function") cleanupFns.push(cleanup);
712
- } catch {
713
- }
714
- }
715
- if (cleanupFns.length) socketCleanup.set(socketId, cleanupFns);
716
- ws.on("message", (raw) => {
717
- let parsed;
718
- try {
719
- parsed = safeJsonParse(raw);
720
- } catch {
721
- return;
722
- }
723
- if (!parsed || typeof parsed !== "object") return;
724
- const message = parsed;
725
- if (message.type !== "event" && message.type !== "run-query" && message.type !== "register-query" && message.type !== "remove-query") return;
726
- void handleClientMessage({
727
- socketId,
728
- meta,
729
- message
730
- });
731
- });
732
- ws.on("close", () => {
733
- cleanupSocket(socketId);
734
- });
735
- ws.on("error", () => {
736
- cleanupSocket(socketId);
737
- });
738
- });
381
+ var handleClientMessage = async ({ socketId, meta, message }) => {
382
+ const ws = sockets.get(socketId);
383
+ if (!ws) return;
384
+ if (message.type === "event") {
385
+ socketWrappers.get(socketId)?.dispatch(message.event, message.payload);
386
+ return;
387
+ }
388
+ if (!message.modelName || typeof message.modelName !== "string") return;
389
+ if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
390
+ sendWs(ws, {
391
+ type: "query-payload",
392
+ modelName: message.modelName,
393
+ queryKey: message.queryKey ?? "",
394
+ error: "Model not allowed"
395
+ });
396
+ return;
397
+ }
398
+ if (!message.queryKey || typeof message.queryKey !== "string") return;
399
+ if (message.queryKey.length > QUERY_KEY_MAX_LEN) return;
400
+ if (message.type === "remove-query") {
401
+ removeSocketSubscription({
402
+ socketId,
403
+ tenantId: meta.tenantId,
404
+ userId: meta.userId,
405
+ modelName: message.modelName,
406
+ queryKey: message.queryKey
407
+ });
408
+ return;
409
+ }
410
+ if (!message.query || typeof message.query !== "object") return;
411
+ const options = normalizeRtsQueryOptions(message.options);
412
+ const ability = meta.ability;
413
+ if (!ability.can("read", message.modelName)) {
414
+ sendWs(ws, {
415
+ type: "query-payload",
416
+ modelName: message.modelName,
417
+ queryKey: message.queryKey,
418
+ error: "forbidden"
419
+ });
420
+ return;
421
+ }
422
+ if (message.type === "register-query") {
423
+ if (!(socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false)) {
424
+ let count = 0;
425
+ const byModel = socketSubscriptions.get(socketId);
426
+ if (byModel) for (const set of byModel.values()) count += set.size;
427
+ if (count >= maxSubscriptionsPerSocket) {
428
+ sendWs(ws, {
429
+ type: "query-payload",
430
+ modelName: message.modelName,
431
+ queryKey: message.queryKey,
432
+ error: "Too many subscriptions"
433
+ });
434
+ return;
435
+ }
436
+ }
437
+ let dependencyModelNames = [];
438
+ if (options.populate !== void 0) try {
439
+ dependencyModelNames = await resolveRtsQueryDependencyModelNames({
440
+ tenantId: meta.tenantId,
441
+ ability,
442
+ modelName: message.modelName,
443
+ options,
444
+ allowInternalModels
445
+ });
446
+ } catch (err) {
447
+ const error = redactErrorMessage(err);
448
+ sendWs(ws, {
449
+ type: "query-payload",
450
+ modelName: message.modelName,
451
+ queryKey: message.queryKey,
452
+ error
453
+ });
454
+ return;
455
+ }
456
+ addSocketSubscription({
457
+ socketId,
458
+ tenantId: meta.tenantId,
459
+ userId: meta.userId,
460
+ modelName: message.modelName,
461
+ queryKey: message.queryKey,
462
+ query: message.query,
463
+ options,
464
+ dependencyModelNames
465
+ });
466
+ try {
467
+ const modelNamesToWatch = new Set([message.modelName, ...dependencyModelNames]);
468
+ for (const modelName of modelNamesToWatch) await ensureChangeStream(meta.tenantId, modelName);
469
+ } catch (err) {
470
+ const error = redactErrorMessage(err);
471
+ sendWs(ws, {
472
+ type: "query-payload",
473
+ modelName: message.modelName,
474
+ queryKey: message.queryKey,
475
+ error
476
+ });
477
+ return;
478
+ }
479
+ if (message.runInitialQuery === false) return;
480
+ }
481
+ try {
482
+ await runAndSendQuery({
483
+ tenantId: meta.tenantId,
484
+ targetSocketIds: [socketId],
485
+ ability,
486
+ modelName: message.modelName,
487
+ queryKey: message.queryKey,
488
+ query: message.query,
489
+ options
490
+ });
491
+ } catch (err) {
492
+ const error = redactErrorMessage(err);
493
+ sendWs(ws, {
494
+ type: "query-payload",
495
+ modelName: message.modelName,
496
+ queryKey: message.queryKey,
497
+ error
498
+ });
499
+ }
739
500
  };
740
- const registerRtsHandler = (handler) => {
741
- customHandlers.push(handler);
501
+ var initRts = ({ server, path = "/rts", sessionMiddleware, maxPayloadBytes: maxPayloadBytesArg, maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg, dispatchDebounceMs: dispatchDebounceMsArg, allowInternalModels: allowInternalModelsArg }) => {
502
+ if (initializedServers.has(server)) return;
503
+ initializedServers.add(server);
504
+ if (typeof maxPayloadBytesArg === "number" && Number.isFinite(maxPayloadBytesArg) && maxPayloadBytesArg > 0) maxPayloadBytes = Math.floor(maxPayloadBytesArg);
505
+ if (typeof maxSubscriptionsPerSocketArg === "number" && Number.isFinite(maxSubscriptionsPerSocketArg) && maxSubscriptionsPerSocketArg > 0) maxSubscriptionsPerSocket = Math.floor(maxSubscriptionsPerSocketArg);
506
+ if (typeof dispatchDebounceMsArg === "number" && Number.isFinite(dispatchDebounceMsArg) && dispatchDebounceMsArg >= 0) dispatchDebounceMs = Math.floor(dispatchDebounceMsArg);
507
+ allowInternalModels = Boolean(allowInternalModelsArg);
508
+ const wss = new WebSocketServer({
509
+ noServer: true,
510
+ maxPayload: maxPayloadBytes
511
+ });
512
+ server.on("upgrade", (req, socket, head) => {
513
+ ensureSocketErrorHandler(socket, () => ({
514
+ upgradeHost: req.headers.host ?? "",
515
+ upgradeUrl: req.url ?? "",
516
+ userAgent: typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : ""
517
+ }));
518
+ upgradeMeta.delete(req);
519
+ let url;
520
+ try {
521
+ url = new URL(req.url ?? "", `http://${req.headers.host ?? "localhost"}`);
522
+ } catch {
523
+ badRequest(socket, "Invalid URL");
524
+ return;
525
+ }
526
+ if (url.pathname !== path) return;
527
+ (async () => {
528
+ try {
529
+ const meta = await parseUpgradeMeta({
530
+ req,
531
+ url,
532
+ sessionMiddleware
533
+ });
534
+ upgradeMeta.set(req, meta);
535
+ wss.handleUpgrade(req, socket, head, (ws) => {
536
+ wss.emit("connection", ws, req);
537
+ });
538
+ } catch (err) {
539
+ const message = err instanceof Error ? err.message : "RTS upgrade failed";
540
+ if (message.startsWith("Missing rb-tenant-id")) {
541
+ badRequest(socket, message);
542
+ return;
543
+ }
544
+ unauthorized(socket, message);
545
+ return;
546
+ }
547
+ })().catch(() => {
548
+ badRequest(socket, "RTS upgrade failed");
549
+ });
550
+ });
551
+ wss.on("connection", (ws, req) => {
552
+ const meta = upgradeMeta.get(req);
553
+ upgradeMeta.delete(req);
554
+ if (!meta) {
555
+ try {
556
+ ws.close();
557
+ } catch {}
558
+ return;
559
+ }
560
+ const socketId = randomUUID();
561
+ sockets.set(socketId, ws);
562
+ socketMeta.set(socketId, meta);
563
+ const wrapper = new RtsSocket({
564
+ id: socketId,
565
+ ws,
566
+ meta
567
+ });
568
+ socketWrappers.set(socketId, wrapper);
569
+ const cleanupFns = [];
570
+ for (const handler of customHandlers) try {
571
+ const cleanup = handler(wrapper);
572
+ if (typeof cleanup === "function") cleanupFns.push(cleanup);
573
+ } catch {}
574
+ if (cleanupFns.length) socketCleanup.set(socketId, cleanupFns);
575
+ ws.on("message", (raw) => {
576
+ let parsed;
577
+ try {
578
+ parsed = safeJsonParse(raw);
579
+ } catch {
580
+ return;
581
+ }
582
+ if (!parsed || typeof parsed !== "object") return;
583
+ const message = parsed;
584
+ if (message.type !== "event" && message.type !== "run-query" && message.type !== "register-query" && message.type !== "remove-query") return;
585
+ handleClientMessage({
586
+ socketId,
587
+ meta,
588
+ message
589
+ });
590
+ });
591
+ ws.on("close", () => {
592
+ cleanupSocket(socketId);
593
+ });
594
+ ws.on("error", () => {
595
+ cleanupSocket(socketId);
596
+ });
597
+ });
742
598
  };
743
- const notifyRtsModelChanged = (tenantId, modelName) => {
744
- scheduleDispatchSubscriptionsForModel(tenantId, modelName);
599
+ var registerRtsHandler = (handler) => {
600
+ customHandlers.push(handler);
745
601
  };
746
- export {
747
- initRts,
748
- notifyRtsModelChanged,
749
- registerRtsHandler,
750
- routes
602
+ var notifyRtsModelChanged = (tenantId, modelName) => {
603
+ scheduleDispatchSubscriptionsForModel(tenantId, modelName);
751
604
  };
752
- //# sourceMappingURL=index.js.map
605
+ //#endregion
606
+ export { initRts, notifyRtsModelChanged, registerRtsHandler, routes };
607
+
608
+ //# sourceMappingURL=index.js.map