@rpcbase/server 0.467.0 → 0.468.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.
- package/dist/handler-DHunTqwt.js +3384 -0
- package/dist/{index-BXODFGBH.js → index-BSIupjlE.js} +112 -40
- package/dist/index.js +8075 -60
- package/dist/render_resend_false-MiC__Smr.js +5 -0
- package/dist/rts/api/changes/handler.d.ts +9 -0
- package/dist/rts/api/changes/handler.d.ts.map +1 -0
- package/dist/rts/api/changes/handler.test.d.ts +2 -0
- package/dist/rts/api/changes/handler.test.d.ts.map +1 -0
- package/dist/rts/api/changes/index.d.ts +25 -0
- package/dist/rts/api/changes/index.d.ts.map +1 -0
- package/dist/rts/index.d.ts +5 -1
- package/dist/rts/index.d.ts.map +1 -1
- package/dist/rts/index.ws.test.d.ts +2 -0
- package/dist/rts/index.ws.test.d.ts.map +1 -0
- package/dist/rts/routes.d.ts +2 -0
- package/dist/rts/routes.d.ts.map +1 -0
- package/dist/rts.d.ts +1 -0
- package/dist/rts.d.ts.map +1 -1
- package/dist/rts.js +9 -2
- package/package.json +5 -5
|
@@ -5,6 +5,10 @@ const TENANT_ID_QUERY_PARAM = "rb-tenant-id";
|
|
|
5
5
|
const USER_ID_HEADER = "rb-user-id";
|
|
6
6
|
const QUERY_KEY_MAX_LEN = 4096;
|
|
7
7
|
const QUERY_MAX_LIMIT = 4096;
|
|
8
|
+
const INTERNAL_MODEL_NAMES = /* @__PURE__ */ new Set(["RtsChange", "RtsCounter"]);
|
|
9
|
+
const DEFAULT_MAX_PAYLOAD_BYTES = 1024 * 1024;
|
|
10
|
+
const DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET = 256;
|
|
11
|
+
const DEFAULT_DISPATCH_DEBOUNCE_MS = 25;
|
|
8
12
|
const initializedServers = /* @__PURE__ */ new WeakSet();
|
|
9
13
|
const customHandlers = [];
|
|
10
14
|
const sockets = /* @__PURE__ */ new Map();
|
|
@@ -14,6 +18,12 @@ const socketCleanup = /* @__PURE__ */ new Map();
|
|
|
14
18
|
const socketSubscriptions = /* @__PURE__ */ new Map();
|
|
15
19
|
const subscriptions = /* @__PURE__ */ new Map();
|
|
16
20
|
const changeStreams = /* @__PURE__ */ new Map();
|
|
21
|
+
const dispatchTimers = /* @__PURE__ */ new Map();
|
|
22
|
+
const upgradeMeta = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
let maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES;
|
|
24
|
+
let maxSubscriptionsPerSocket = DEFAULT_MAX_SUBSCRIPTIONS_PER_SOCKET;
|
|
25
|
+
let dispatchDebounceMs = DEFAULT_DISPATCH_DEBOUNCE_MS;
|
|
26
|
+
let allowInternalModels = false;
|
|
17
27
|
class RtsSocket {
|
|
18
28
|
id;
|
|
19
29
|
tenantId;
|
|
@@ -70,6 +80,13 @@ const sendWs = (ws, message) => {
|
|
|
70
80
|
if (ws.readyState !== 1) return;
|
|
71
81
|
ws.send(JSON.stringify(message));
|
|
72
82
|
};
|
|
83
|
+
const redactErrorMessage = (err) => {
|
|
84
|
+
const raw = err instanceof Error ? err.message : "Unknown error";
|
|
85
|
+
const trimmedModelList = raw.replace(/\.\s+Available models:[\s\S]*$/, "");
|
|
86
|
+
const maxLen = 256;
|
|
87
|
+
if (trimmedModelList.length <= maxLen) return trimmedModelList;
|
|
88
|
+
return trimmedModelList.slice(0, maxLen);
|
|
89
|
+
};
|
|
73
90
|
const unauthorized = (socket, message = "Unauthorized") => {
|
|
74
91
|
try {
|
|
75
92
|
socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
|
|
@@ -92,10 +109,11 @@ const badRequest = (socket, message = "Bad Request") => {
|
|
|
92
109
|
};
|
|
93
110
|
const runSessionMiddleware = async (sessionMiddleware, req) => {
|
|
94
111
|
await new Promise((resolve, reject) => {
|
|
95
|
-
|
|
112
|
+
const next = (err) => {
|
|
96
113
|
if (err) reject(err);
|
|
97
114
|
else resolve();
|
|
98
|
-
}
|
|
115
|
+
};
|
|
116
|
+
sessionMiddleware(req, {}, next);
|
|
99
117
|
});
|
|
100
118
|
};
|
|
101
119
|
const parseUpgradeMeta = async ({
|
|
@@ -107,37 +125,39 @@ const parseUpgradeMeta = async ({
|
|
|
107
125
|
if (!tenantId) {
|
|
108
126
|
throw new Error("Missing rb-tenant-id query parameter");
|
|
109
127
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
const sessionUser = upgradeReq.session?.user;
|
|
123
|
-
const sessionUserId = sessionUser?.id;
|
|
124
|
-
if (!sessionUserId) {
|
|
125
|
-
throw new Error("Not signed in (missing session.user.id)");
|
|
126
|
-
}
|
|
127
|
-
const signedInTenants = sessionUser?.signed_in_tenants;
|
|
128
|
-
const currentTenantId = sessionUser?.current_tenant_id;
|
|
129
|
-
if (Array.isArray(signedInTenants) && signedInTenants.length > 0) {
|
|
130
|
-
if (!signedInTenants.includes(tenantId)) {
|
|
131
|
-
throw new Error("Tenant not authorized for this session");
|
|
128
|
+
if (sessionMiddleware) {
|
|
129
|
+
const upgradeReq = req;
|
|
130
|
+
try {
|
|
131
|
+
await runSessionMiddleware(sessionMiddleware, upgradeReq);
|
|
132
|
+
} catch {
|
|
133
|
+
throw new Error("Failed to load session for RTS");
|
|
134
|
+
}
|
|
135
|
+
const sessionUser = upgradeReq.session?.user;
|
|
136
|
+
const sessionUserId = sessionUser?.id;
|
|
137
|
+
if (!sessionUserId) {
|
|
138
|
+
throw new Error("Not signed in (missing session.user.id)");
|
|
132
139
|
}
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
const signedInTenants = sessionUser?.signed_in_tenants;
|
|
141
|
+
const currentTenantId = sessionUser?.current_tenant_id;
|
|
142
|
+
if (Array.isArray(signedInTenants) && signedInTenants.length > 0) {
|
|
143
|
+
if (!signedInTenants.includes(tenantId)) {
|
|
144
|
+
throw new Error("Tenant not authorized for this session");
|
|
145
|
+
}
|
|
146
|
+
} else if (currentTenantId) {
|
|
147
|
+
if (currentTenantId !== tenantId) {
|
|
148
|
+
throw new Error("Tenant not authorized for this session");
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
135
151
|
throw new Error("Tenant not authorized for this session");
|
|
136
152
|
}
|
|
137
|
-
|
|
138
|
-
throw new Error("Tenant not authorized for this session");
|
|
153
|
+
return { tenantId, userId: sessionUserId };
|
|
139
154
|
}
|
|
140
|
-
|
|
155
|
+
const raw = req.headers[USER_ID_HEADER];
|
|
156
|
+
const headerUserId = Array.isArray(raw) ? raw[0] : raw;
|
|
157
|
+
if (!headerUserId) {
|
|
158
|
+
throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
|
|
159
|
+
}
|
|
160
|
+
return { tenantId, userId: headerUserId };
|
|
141
161
|
};
|
|
142
162
|
const getTenantModel = async (tenantId, modelName) => {
|
|
143
163
|
const ctx = {
|
|
@@ -159,15 +179,32 @@ const normalizeLimit = (limit) => {
|
|
|
159
179
|
const normalizeOptions = (options) => {
|
|
160
180
|
if (!options || typeof options !== "object") return {};
|
|
161
181
|
const normalized = {};
|
|
162
|
-
if (options.projection && typeof options.projection === "object") {
|
|
182
|
+
if (options.projection && typeof options.projection === "object" && !Array.isArray(options.projection)) {
|
|
163
183
|
normalized.projection = options.projection;
|
|
164
184
|
}
|
|
165
|
-
if (options.sort && typeof options.sort === "object") {
|
|
185
|
+
if (options.sort && typeof options.sort === "object" && !Array.isArray(options.sort)) {
|
|
166
186
|
normalized.sort = options.sort;
|
|
167
187
|
}
|
|
168
188
|
normalized.limit = normalizeLimit(options.limit);
|
|
169
189
|
return normalized;
|
|
170
190
|
};
|
|
191
|
+
const makeDispatchKey = (tenantId, modelName) => `${tenantId}:${modelName}`;
|
|
192
|
+
const clearDispatchTimer = (tenantId, modelName) => {
|
|
193
|
+
const key = makeDispatchKey(tenantId, modelName);
|
|
194
|
+
const timer = dispatchTimers.get(key);
|
|
195
|
+
if (!timer) return;
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
dispatchTimers.delete(key);
|
|
198
|
+
};
|
|
199
|
+
const scheduleDispatchSubscriptionsForModel = (tenantId, modelName) => {
|
|
200
|
+
const key = makeDispatchKey(tenantId, modelName);
|
|
201
|
+
if (dispatchTimers.has(key)) return;
|
|
202
|
+
const delay = Math.max(0, Math.min(1e3, Math.floor(dispatchDebounceMs)));
|
|
203
|
+
dispatchTimers.set(key, setTimeout(() => {
|
|
204
|
+
dispatchTimers.delete(key);
|
|
205
|
+
void dispatchSubscriptionsForModel(tenantId, modelName);
|
|
206
|
+
}, delay));
|
|
207
|
+
};
|
|
171
208
|
const runAndSendQuery = async ({
|
|
172
209
|
tenantId,
|
|
173
210
|
targetSocketIds,
|
|
@@ -210,7 +247,7 @@ const dispatchSubscriptionsForModel = async (tenantId, modelName) => {
|
|
|
210
247
|
options: sub.options
|
|
211
248
|
});
|
|
212
249
|
} catch (err) {
|
|
213
|
-
const error = err
|
|
250
|
+
const error = redactErrorMessage(err);
|
|
214
251
|
const payload = { type: "query_payload", modelName, queryKey, error };
|
|
215
252
|
for (const socketId of targetSocketIds) {
|
|
216
253
|
const ws = sockets.get(socketId);
|
|
@@ -229,15 +266,17 @@ const ensureChangeStream = async (tenantId, modelName) => {
|
|
|
229
266
|
fullDocument: "updateLookup"
|
|
230
267
|
});
|
|
231
268
|
stream.on("change", () => {
|
|
232
|
-
|
|
269
|
+
scheduleDispatchSubscriptionsForModel(tenantId, modelName);
|
|
233
270
|
});
|
|
234
271
|
stream.on("close", () => {
|
|
272
|
+
clearDispatchTimer(tenantId, modelName);
|
|
235
273
|
const map = changeStreams.get(tenantId);
|
|
236
274
|
map?.delete(modelName);
|
|
237
275
|
if (map && map.size === 0) changeStreams.delete(tenantId);
|
|
238
276
|
});
|
|
239
277
|
stream.on("error", () => {
|
|
240
278
|
try {
|
|
279
|
+
clearDispatchTimer(tenantId, modelName);
|
|
241
280
|
stream.close();
|
|
242
281
|
} catch {
|
|
243
282
|
}
|
|
@@ -304,6 +343,7 @@ const removeSocketSubscription = ({
|
|
|
304
343
|
stream.close();
|
|
305
344
|
} catch {
|
|
306
345
|
}
|
|
346
|
+
clearDispatchTimer(tenantId, modelName);
|
|
307
347
|
tenantStreams?.delete(modelName);
|
|
308
348
|
if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
|
|
309
349
|
}
|
|
@@ -354,6 +394,10 @@ const handleClientMessage = async ({
|
|
|
354
394
|
return;
|
|
355
395
|
}
|
|
356
396
|
if (!message.modelName || typeof message.modelName !== "string") return;
|
|
397
|
+
if (!allowInternalModels && INTERNAL_MODEL_NAMES.has(message.modelName)) {
|
|
398
|
+
sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey ?? "", error: "Model not allowed" });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
357
401
|
if (!message.queryKey || typeof message.queryKey !== "string") return;
|
|
358
402
|
if (message.queryKey.length > QUERY_KEY_MAX_LEN) return;
|
|
359
403
|
if (message.type === "remove_query") {
|
|
@@ -368,6 +412,18 @@ const handleClientMessage = async ({
|
|
|
368
412
|
if (!message.query || typeof message.query !== "object") return;
|
|
369
413
|
const options = normalizeOptions(message.options);
|
|
370
414
|
if (message.type === "registerQuery") {
|
|
415
|
+
const existing = socketSubscriptions.get(socketId)?.get(message.modelName)?.has(message.queryKey) ?? false;
|
|
416
|
+
if (!existing) {
|
|
417
|
+
let count = 0;
|
|
418
|
+
const byModel = socketSubscriptions.get(socketId);
|
|
419
|
+
if (byModel) {
|
|
420
|
+
for (const set of byModel.values()) count += set.size;
|
|
421
|
+
}
|
|
422
|
+
if (count >= maxSubscriptionsPerSocket) {
|
|
423
|
+
sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey, error: "Too many subscriptions" });
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
371
427
|
addSocketSubscription({
|
|
372
428
|
socketId,
|
|
373
429
|
tenantId: meta.tenantId,
|
|
@@ -379,7 +435,7 @@ const handleClientMessage = async ({
|
|
|
379
435
|
try {
|
|
380
436
|
await ensureChangeStream(meta.tenantId, message.modelName);
|
|
381
437
|
} catch (err) {
|
|
382
|
-
const error = err
|
|
438
|
+
const error = redactErrorMessage(err);
|
|
383
439
|
sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey, error });
|
|
384
440
|
return;
|
|
385
441
|
}
|
|
@@ -394,19 +450,34 @@ const handleClientMessage = async ({
|
|
|
394
450
|
options
|
|
395
451
|
});
|
|
396
452
|
} catch (err) {
|
|
397
|
-
const error = err
|
|
453
|
+
const error = redactErrorMessage(err);
|
|
398
454
|
sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey, error });
|
|
399
455
|
}
|
|
400
456
|
};
|
|
401
457
|
const initRts = ({
|
|
402
458
|
server,
|
|
403
459
|
path = "/rts",
|
|
404
|
-
sessionMiddleware
|
|
460
|
+
sessionMiddleware,
|
|
461
|
+
maxPayloadBytes: maxPayloadBytesArg,
|
|
462
|
+
maxSubscriptionsPerSocket: maxSubscriptionsPerSocketArg,
|
|
463
|
+
dispatchDebounceMs: dispatchDebounceMsArg,
|
|
464
|
+
allowInternalModels: allowInternalModelsArg
|
|
405
465
|
}) => {
|
|
406
466
|
if (initializedServers.has(server)) return;
|
|
407
467
|
initializedServers.add(server);
|
|
408
|
-
|
|
468
|
+
if (typeof maxPayloadBytesArg === "number" && Number.isFinite(maxPayloadBytesArg) && maxPayloadBytesArg > 0) {
|
|
469
|
+
maxPayloadBytes = Math.floor(maxPayloadBytesArg);
|
|
470
|
+
}
|
|
471
|
+
if (typeof maxSubscriptionsPerSocketArg === "number" && Number.isFinite(maxSubscriptionsPerSocketArg) && maxSubscriptionsPerSocketArg > 0) {
|
|
472
|
+
maxSubscriptionsPerSocket = Math.floor(maxSubscriptionsPerSocketArg);
|
|
473
|
+
}
|
|
474
|
+
if (typeof dispatchDebounceMsArg === "number" && Number.isFinite(dispatchDebounceMsArg) && dispatchDebounceMsArg >= 0) {
|
|
475
|
+
dispatchDebounceMs = Math.floor(dispatchDebounceMsArg);
|
|
476
|
+
}
|
|
477
|
+
allowInternalModels = Boolean(allowInternalModelsArg);
|
|
478
|
+
const wss = new WebSocketServer({ noServer: true, maxPayload: maxPayloadBytes });
|
|
409
479
|
server.on("upgrade", (req, socket, head) => {
|
|
480
|
+
upgradeMeta.delete(req);
|
|
410
481
|
let url;
|
|
411
482
|
try {
|
|
412
483
|
url = new URL(req.url ?? "", `http://${req.headers.host ?? "localhost"}`);
|
|
@@ -418,7 +489,7 @@ const initRts = ({
|
|
|
418
489
|
void (async () => {
|
|
419
490
|
try {
|
|
420
491
|
const meta = await parseUpgradeMeta({ req, url, sessionMiddleware });
|
|
421
|
-
req
|
|
492
|
+
upgradeMeta.set(req, meta);
|
|
422
493
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
423
494
|
wss.emit("connection", ws, req);
|
|
424
495
|
});
|
|
@@ -436,7 +507,8 @@ const initRts = ({
|
|
|
436
507
|
});
|
|
437
508
|
});
|
|
438
509
|
wss.on("connection", (ws, req) => {
|
|
439
|
-
const meta = req
|
|
510
|
+
const meta = upgradeMeta.get(req);
|
|
511
|
+
upgradeMeta.delete(req);
|
|
440
512
|
if (!meta) {
|
|
441
513
|
try {
|
|
442
514
|
ws.close();
|
|
@@ -481,7 +553,7 @@ const registerRtsHandler = (handler) => {
|
|
|
481
553
|
customHandlers.push(handler);
|
|
482
554
|
};
|
|
483
555
|
const notifyRtsModelChanged = (tenantId, modelName) => {
|
|
484
|
-
|
|
556
|
+
scheduleDispatchSubscriptionsForModel(tenantId, modelName);
|
|
485
557
|
};
|
|
486
558
|
export {
|
|
487
559
|
initRts as i,
|