@rpcbase/server 0.463.0 → 0.465.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.
@@ -0,0 +1,490 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { loadModel } from "@rpcbase/db";
3
+ import { WebSocketServer } from "ws";
4
+ const TENANT_ID_QUERY_PARAM = "rb-tenant-id";
5
+ const USER_ID_HEADER = "rb-user-id";
6
+ const QUERY_KEY_MAX_LEN = 4096;
7
+ const QUERY_MAX_LIMIT = 4096;
8
+ const initializedServers = /* @__PURE__ */ new WeakSet();
9
+ const customHandlers = [];
10
+ const sockets = /* @__PURE__ */ new Map();
11
+ const socketMeta = /* @__PURE__ */ new Map();
12
+ const socketWrappers = /* @__PURE__ */ new Map();
13
+ const socketCleanup = /* @__PURE__ */ new Map();
14
+ const socketSubscriptions = /* @__PURE__ */ new Map();
15
+ const subscriptions = /* @__PURE__ */ new Map();
16
+ const changeStreams = /* @__PURE__ */ new Map();
17
+ class RtsSocket {
18
+ id;
19
+ tenantId;
20
+ userId;
21
+ ws;
22
+ handlers = /* @__PURE__ */ new Map();
23
+ constructor({
24
+ id,
25
+ ws,
26
+ meta
27
+ }) {
28
+ this.id = id;
29
+ this.ws = ws;
30
+ this.tenantId = meta.tenantId;
31
+ this.userId = meta.userId;
32
+ }
33
+ on(event, handler) {
34
+ const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
35
+ set.add(handler);
36
+ this.handlers.set(event, set);
37
+ return () => this.off(event, handler);
38
+ }
39
+ off(event, handler) {
40
+ const set = this.handlers.get(event);
41
+ if (!set) return;
42
+ set.delete(handler);
43
+ if (!set.size) this.handlers.delete(event);
44
+ }
45
+ emit(event, payload) {
46
+ sendWs(this.ws, { type: "event", event, payload });
47
+ }
48
+ close() {
49
+ try {
50
+ this.ws.close();
51
+ } catch {
52
+ }
53
+ }
54
+ dispatch(event, payload) {
55
+ const set = this.handlers.get(event);
56
+ if (!set) return;
57
+ for (const handler of set) {
58
+ handler(payload);
59
+ }
60
+ }
61
+ }
62
+ const rawToText = (raw) => {
63
+ if (typeof raw === "string") return raw;
64
+ if (raw instanceof ArrayBuffer) return Buffer.from(raw).toString();
65
+ if (Array.isArray(raw)) return Buffer.concat(raw).toString();
66
+ return raw.toString();
67
+ };
68
+ const safeJsonParse = (raw) => JSON.parse(rawToText(raw));
69
+ const sendWs = (ws, message) => {
70
+ if (ws.readyState !== 1) return;
71
+ ws.send(JSON.stringify(message));
72
+ };
73
+ const unauthorized = (socket, message = "Unauthorized") => {
74
+ try {
75
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
76
+ socket.write(`Error: ${message}\r
77
+ `);
78
+ socket.end();
79
+ } catch {
80
+ socket.destroy();
81
+ }
82
+ };
83
+ const badRequest = (socket, message = "Bad Request") => {
84
+ try {
85
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
86
+ socket.write(`Error: ${message}\r
87
+ `);
88
+ socket.end();
89
+ } catch {
90
+ socket.destroy();
91
+ }
92
+ };
93
+ const runSessionMiddleware = async (sessionMiddleware, req) => {
94
+ await new Promise((resolve, reject) => {
95
+ sessionMiddleware(req, {}, (err) => {
96
+ if (err) reject(err);
97
+ else resolve();
98
+ });
99
+ });
100
+ };
101
+ const parseUpgradeMeta = async ({
102
+ req,
103
+ url,
104
+ sessionMiddleware
105
+ }) => {
106
+ const tenantId = url.searchParams.get(TENANT_ID_QUERY_PARAM);
107
+ if (!tenantId) {
108
+ throw new Error("Missing rb-tenant-id query parameter");
109
+ }
110
+ const raw = req.headers[USER_ID_HEADER];
111
+ const headerUserId = Array.isArray(raw) ? raw[0] : raw;
112
+ if (headerUserId) return { tenantId, userId: headerUserId };
113
+ if (!sessionMiddleware) {
114
+ throw new Error("Missing rb-user-id header (reverse-proxy) and no session middleware configured");
115
+ }
116
+ const upgradeReq = req;
117
+ try {
118
+ await runSessionMiddleware(sessionMiddleware, upgradeReq);
119
+ } catch {
120
+ throw new Error("Failed to load session for RTS");
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");
132
+ }
133
+ } else if (currentTenantId) {
134
+ if (currentTenantId !== tenantId) {
135
+ throw new Error("Tenant not authorized for this session");
136
+ }
137
+ } else {
138
+ throw new Error("Tenant not authorized for this session");
139
+ }
140
+ return { tenantId, userId: sessionUserId };
141
+ };
142
+ const getTenantModel = async (tenantId, modelName) => {
143
+ const ctx = {
144
+ req: {
145
+ session: {
146
+ user: {
147
+ current_tenant_id: tenantId
148
+ }
149
+ }
150
+ }
151
+ };
152
+ return loadModel(modelName, ctx);
153
+ };
154
+ const normalizeLimit = (limit) => {
155
+ if (typeof limit !== "number") return QUERY_MAX_LIMIT;
156
+ if (!Number.isFinite(limit)) return QUERY_MAX_LIMIT;
157
+ return Math.min(QUERY_MAX_LIMIT, Math.abs(limit));
158
+ };
159
+ const normalizeOptions = (options) => {
160
+ if (!options || typeof options !== "object") return {};
161
+ const normalized = {};
162
+ if (options.projection && typeof options.projection === "object") {
163
+ normalized.projection = options.projection;
164
+ }
165
+ if (options.sort && typeof options.sort === "object") {
166
+ normalized.sort = options.sort;
167
+ }
168
+ normalized.limit = normalizeLimit(options.limit);
169
+ return normalized;
170
+ };
171
+ const runAndSendQuery = async ({
172
+ tenantId,
173
+ targetSocketIds,
174
+ modelName,
175
+ queryKey,
176
+ query,
177
+ options
178
+ }) => {
179
+ const model = await getTenantModel(tenantId, modelName);
180
+ const projection = options.projection ?? void 0;
181
+ const sort = options.sort;
182
+ const limit = normalizeLimit(options.limit);
183
+ const queryPromise = model.find(query, projection);
184
+ if (sort && Object.keys(sort).length) {
185
+ queryPromise.sort(sort);
186
+ }
187
+ queryPromise.limit(limit);
188
+ const data = await queryPromise;
189
+ const payload = { type: "query_payload", modelName, queryKey, data };
190
+ for (const socketId of targetSocketIds) {
191
+ const ws = sockets.get(socketId);
192
+ if (!ws) continue;
193
+ sendWs(ws, payload);
194
+ }
195
+ };
196
+ const dispatchSubscriptionsForModel = async (tenantId, modelName) => {
197
+ const tenantSubs = subscriptions.get(tenantId);
198
+ const modelSubs = tenantSubs?.get(modelName);
199
+ if (!modelSubs || !modelSubs.size) return;
200
+ for (const [queryKey, sub] of modelSubs.entries()) {
201
+ const targetSocketIds = Array.from(sub.socketIds);
202
+ if (!targetSocketIds.length) continue;
203
+ try {
204
+ await runAndSendQuery({
205
+ tenantId,
206
+ targetSocketIds,
207
+ modelName,
208
+ queryKey,
209
+ query: sub.query,
210
+ options: sub.options
211
+ });
212
+ } catch (err) {
213
+ const error = err instanceof Error ? err.message : "Unknown error";
214
+ const payload = { type: "query_payload", modelName, queryKey, error };
215
+ for (const socketId of targetSocketIds) {
216
+ const ws = sockets.get(socketId);
217
+ if (!ws) continue;
218
+ sendWs(ws, payload);
219
+ }
220
+ }
221
+ }
222
+ };
223
+ const ensureChangeStream = async (tenantId, modelName) => {
224
+ const tenantStreams = changeStreams.get(tenantId) ?? /* @__PURE__ */ new Map();
225
+ changeStreams.set(tenantId, tenantStreams);
226
+ if (tenantStreams.has(modelName)) return;
227
+ const model = await getTenantModel(tenantId, modelName);
228
+ const stream = model.watch([], {
229
+ fullDocument: "updateLookup"
230
+ });
231
+ stream.on("change", () => {
232
+ void dispatchSubscriptionsForModel(tenantId, modelName);
233
+ });
234
+ stream.on("close", () => {
235
+ const map = changeStreams.get(tenantId);
236
+ map?.delete(modelName);
237
+ if (map && map.size === 0) changeStreams.delete(tenantId);
238
+ });
239
+ stream.on("error", () => {
240
+ try {
241
+ stream.close();
242
+ } catch {
243
+ }
244
+ });
245
+ tenantStreams.set(modelName, stream);
246
+ };
247
+ const addSocketSubscription = ({
248
+ socketId,
249
+ tenantId,
250
+ modelName,
251
+ queryKey,
252
+ query,
253
+ options
254
+ }) => {
255
+ const tenantSubs = subscriptions.get(tenantId) ?? /* @__PURE__ */ new Map();
256
+ subscriptions.set(tenantId, tenantSubs);
257
+ const modelSubs = tenantSubs.get(modelName) ?? /* @__PURE__ */ new Map();
258
+ tenantSubs.set(modelName, modelSubs);
259
+ const existing = modelSubs.get(queryKey);
260
+ if (existing) {
261
+ existing.socketIds.add(socketId);
262
+ } else {
263
+ modelSubs.set(queryKey, {
264
+ query,
265
+ options,
266
+ socketIds: /* @__PURE__ */ new Set([socketId])
267
+ });
268
+ }
269
+ const byModel = socketSubscriptions.get(socketId) ?? /* @__PURE__ */ new Map();
270
+ socketSubscriptions.set(socketId, byModel);
271
+ const querySet = byModel.get(modelName) ?? /* @__PURE__ */ new Set();
272
+ byModel.set(modelName, querySet);
273
+ querySet.add(queryKey);
274
+ };
275
+ const removeSocketSubscription = ({
276
+ socketId,
277
+ tenantId,
278
+ modelName,
279
+ queryKey
280
+ }) => {
281
+ const tenantSubs = subscriptions.get(tenantId);
282
+ const modelSubs = tenantSubs?.get(modelName);
283
+ const sub = modelSubs?.get(queryKey);
284
+ if (sub) {
285
+ sub.socketIds.delete(socketId);
286
+ if (!sub.socketIds.size) {
287
+ modelSubs?.delete(queryKey);
288
+ }
289
+ }
290
+ const byModel = socketSubscriptions.get(socketId);
291
+ const set = byModel?.get(modelName);
292
+ if (set) {
293
+ set.delete(queryKey);
294
+ if (!set.size) {
295
+ byModel?.delete(modelName);
296
+ }
297
+ }
298
+ if (modelSubs && modelSubs.size === 0) {
299
+ tenantSubs?.delete(modelName);
300
+ const tenantStreams = changeStreams.get(tenantId);
301
+ const stream = tenantStreams?.get(modelName);
302
+ if (stream) {
303
+ try {
304
+ stream.close();
305
+ } catch {
306
+ }
307
+ tenantStreams?.delete(modelName);
308
+ if (tenantStreams && tenantStreams.size === 0) changeStreams.delete(tenantId);
309
+ }
310
+ }
311
+ if (tenantSubs && tenantSubs.size === 0) subscriptions.delete(tenantId);
312
+ if (byModel && byModel.size === 0) socketSubscriptions.delete(socketId);
313
+ };
314
+ const cleanupSocket = (socketId) => {
315
+ const meta = socketMeta.get(socketId);
316
+ if (meta) {
317
+ const byModel = socketSubscriptions.get(socketId);
318
+ if (byModel) {
319
+ for (const [modelName, keys] of byModel.entries()) {
320
+ for (const queryKey of keys.values()) {
321
+ removeSocketSubscription({
322
+ socketId,
323
+ tenantId: meta.tenantId,
324
+ modelName,
325
+ queryKey
326
+ });
327
+ }
328
+ }
329
+ }
330
+ }
331
+ socketSubscriptions.delete(socketId);
332
+ const cleanupFns = socketCleanup.get(socketId) ?? [];
333
+ socketCleanup.delete(socketId);
334
+ for (const fn of cleanupFns) {
335
+ try {
336
+ fn();
337
+ } catch {
338
+ }
339
+ }
340
+ sockets.delete(socketId);
341
+ socketMeta.delete(socketId);
342
+ socketWrappers.delete(socketId);
343
+ };
344
+ const handleClientMessage = async ({
345
+ socketId,
346
+ meta,
347
+ message
348
+ }) => {
349
+ const ws = sockets.get(socketId);
350
+ if (!ws) return;
351
+ if (message.type === "event") {
352
+ const wrapper = socketWrappers.get(socketId);
353
+ wrapper?.dispatch(message.event, message.payload);
354
+ return;
355
+ }
356
+ if (!message.modelName || typeof message.modelName !== "string") return;
357
+ if (!message.queryKey || typeof message.queryKey !== "string") return;
358
+ if (message.queryKey.length > QUERY_KEY_MAX_LEN) return;
359
+ if (message.type === "remove_query") {
360
+ removeSocketSubscription({
361
+ socketId,
362
+ tenantId: meta.tenantId,
363
+ modelName: message.modelName,
364
+ queryKey: message.queryKey
365
+ });
366
+ return;
367
+ }
368
+ if (!message.query || typeof message.query !== "object") return;
369
+ const options = normalizeOptions(message.options);
370
+ if (message.type === "registerQuery") {
371
+ addSocketSubscription({
372
+ socketId,
373
+ tenantId: meta.tenantId,
374
+ modelName: message.modelName,
375
+ queryKey: message.queryKey,
376
+ query: message.query,
377
+ options
378
+ });
379
+ try {
380
+ await ensureChangeStream(meta.tenantId, message.modelName);
381
+ } catch (err) {
382
+ const error = err instanceof Error ? err.message : "Unable to initialize change stream";
383
+ sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey, error });
384
+ return;
385
+ }
386
+ }
387
+ try {
388
+ await runAndSendQuery({
389
+ tenantId: meta.tenantId,
390
+ targetSocketIds: [socketId],
391
+ modelName: message.modelName,
392
+ queryKey: message.queryKey,
393
+ query: message.query,
394
+ options
395
+ });
396
+ } catch (err) {
397
+ const error = err instanceof Error ? err.message : "Unknown error";
398
+ sendWs(ws, { type: "query_payload", modelName: message.modelName, queryKey: message.queryKey, error });
399
+ }
400
+ };
401
+ const initRts = ({
402
+ server,
403
+ path = "/rts",
404
+ sessionMiddleware
405
+ }) => {
406
+ if (initializedServers.has(server)) return;
407
+ initializedServers.add(server);
408
+ const wss = new WebSocketServer({ noServer: true });
409
+ server.on("upgrade", (req, socket, head) => {
410
+ let url;
411
+ try {
412
+ url = new URL(req.url ?? "", `http://${req.headers.host ?? "localhost"}`);
413
+ } catch {
414
+ badRequest(socket, "Invalid URL");
415
+ return;
416
+ }
417
+ if (url.pathname !== path) return;
418
+ void (async () => {
419
+ try {
420
+ const meta = await parseUpgradeMeta({ req, url, sessionMiddleware });
421
+ req.__rb_rts_meta = meta;
422
+ wss.handleUpgrade(req, socket, head, (ws) => {
423
+ wss.emit("connection", ws, req);
424
+ });
425
+ } catch (err) {
426
+ const message = err instanceof Error ? err.message : "RTS upgrade failed";
427
+ if (message.startsWith("Missing rb-tenant-id")) {
428
+ badRequest(socket, message);
429
+ return;
430
+ }
431
+ unauthorized(socket, message);
432
+ return;
433
+ }
434
+ })().catch(() => {
435
+ badRequest(socket, "RTS upgrade failed");
436
+ });
437
+ });
438
+ wss.on("connection", (ws, req) => {
439
+ const meta = req.__rb_rts_meta;
440
+ if (!meta) {
441
+ try {
442
+ ws.close();
443
+ } catch {
444
+ }
445
+ return;
446
+ }
447
+ const socketId = randomUUID();
448
+ sockets.set(socketId, ws);
449
+ socketMeta.set(socketId, meta);
450
+ const wrapper = new RtsSocket({ id: socketId, ws, meta });
451
+ socketWrappers.set(socketId, wrapper);
452
+ const cleanupFns = [];
453
+ for (const handler of customHandlers) {
454
+ try {
455
+ const cleanup = handler(wrapper);
456
+ if (typeof cleanup === "function") cleanupFns.push(cleanup);
457
+ } catch {
458
+ }
459
+ }
460
+ if (cleanupFns.length) socketCleanup.set(socketId, cleanupFns);
461
+ ws.on("message", (raw) => {
462
+ let parsed;
463
+ try {
464
+ parsed = safeJsonParse(raw);
465
+ } catch {
466
+ return;
467
+ }
468
+ if (!parsed || typeof parsed !== "object") return;
469
+ const message = parsed;
470
+ void handleClientMessage({ socketId, meta, message });
471
+ });
472
+ ws.on("close", () => {
473
+ cleanupSocket(socketId);
474
+ });
475
+ ws.on("error", () => {
476
+ cleanupSocket(socketId);
477
+ });
478
+ });
479
+ };
480
+ const registerRtsHandler = (handler) => {
481
+ customHandlers.push(handler);
482
+ };
483
+ const notifyRtsModelChanged = (tenantId, modelName) => {
484
+ void dispatchSubscriptionsForModel(tenantId, modelName);
485
+ };
486
+ export {
487
+ initRts as i,
488
+ notifyRtsModelChanged as n,
489
+ registerRtsHandler as r
490
+ };
package/dist/index.d.ts CHANGED
@@ -4,4 +4,5 @@ export * from './hashPassword';
4
4
  export * from './passwordHashStorage';
5
5
  export * from './ssrMiddleware';
6
6
  export * from './email';
7
+ export * from './rts/index';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,SAAS,CAAA;AACvB,cAAc,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
18
18
  import { jsx } from "react/jsx-runtime";
19
19
  import { createPath, matchRoutes, parsePath, createStaticRouter, StaticRouterProvider } from "@rpcbase/router";
20
20
  import { Resend } from "resend";
21
+ import { i, n, r } from "./index-BXODFGBH.js";
21
22
  function getDefaultExportFromCjs(x) {
22
23
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
23
24
  }
@@ -93,9 +94,9 @@ function requireLib() {
93
94
  }
94
95
  return ip;
95
96
  });
96
- for (var i = 0; i < forwardedIps.length; i++) {
97
- if (is.ip(forwardedIps[i])) {
98
- return forwardedIps[i];
97
+ for (var i2 = 0; i2 < forwardedIps.length; i2++) {
98
+ if (is.ip(forwardedIps[i2])) {
99
+ return forwardedIps[i2];
99
100
  }
100
101
  }
101
102
  return null;
@@ -206,8 +207,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? dirname(proces
206
207
  if (".js" === ext || ".mjs" === ext || ".cjs" === ext) file = file.slice(0, -1 * ext.length);
207
208
  const decodedFile = decodeURIComponent(file);
208
209
  if (!dir) dir = ".";
209
- const n = dir.lastIndexOf("/node_modules");
210
- if (n > -1) return `${dir.slice(n + 14).replace(/\//g, ".")}:${decodedFile}`;
210
+ const n2 = dir.lastIndexOf("/node_modules");
211
+ if (n2 > -1) return `${dir.slice(n2 + 14).replace(/\//g, ".")}:${decodedFile}`;
211
212
  if (dir.startsWith(normalizedBase)) {
212
213
  const moduleName = dir.slice(normalizedBase.length + 1).replace(/\//g, ".");
213
214
  return moduleName ? `${moduleName}:${decodedFile}` : decodedFile;
@@ -334,12 +335,12 @@ class UUID {
334
335
  }
335
336
  if (hex) {
336
337
  const inner = new Uint8Array(16);
337
- for (let i = 0; i < 16; i += 4) {
338
- const n = parseInt(hex.substring(2 * i, 2 * i + 8), 16);
339
- inner[i + 0] = n >>> 24;
340
- inner[i + 1] = n >>> 16;
341
- inner[i + 2] = n >>> 8;
342
- inner[i + 3] = n;
338
+ for (let i2 = 0; i2 < 16; i2 += 4) {
339
+ const n2 = parseInt(hex.substring(2 * i2, 2 * i2 + 8), 16);
340
+ inner[i2 + 0] = n2 >>> 24;
341
+ inner[i2 + 1] = n2 >>> 16;
342
+ inner[i2 + 2] = n2 >>> 8;
343
+ inner[i2 + 3] = n2;
343
344
  }
344
345
  return new UUID(inner);
345
346
  }
@@ -347,18 +348,18 @@ class UUID {
347
348
  }
348
349
  toString() {
349
350
  let text = "";
350
- for (let i = 0; i < this.bytes.length; i++) {
351
- text += DIGITS.charAt(this.bytes[i] >>> 4);
352
- text += DIGITS.charAt(15 & this.bytes[i]);
353
- if (3 === i || 5 === i || 7 === i || 9 === i) text += "-";
351
+ for (let i2 = 0; i2 < this.bytes.length; i2++) {
352
+ text += DIGITS.charAt(this.bytes[i2] >>> 4);
353
+ text += DIGITS.charAt(15 & this.bytes[i2]);
354
+ if (3 === i2 || 5 === i2 || 7 === i2 || 9 === i2) text += "-";
354
355
  }
355
356
  return text;
356
357
  }
357
358
  toHex() {
358
359
  let text = "";
359
- for (let i = 0; i < this.bytes.length; i++) {
360
- text += DIGITS.charAt(this.bytes[i] >>> 4);
361
- text += DIGITS.charAt(15 & this.bytes[i]);
360
+ for (let i2 = 0; i2 < this.bytes.length; i2++) {
361
+ text += DIGITS.charAt(this.bytes[i2] >>> 4);
362
+ text += DIGITS.charAt(15 & this.bytes[i2]);
362
363
  }
363
364
  return text;
364
365
  }
@@ -366,12 +367,12 @@ class UUID {
366
367
  return this.toString();
367
368
  }
368
369
  getVariant() {
369
- const n = this.bytes[8] >>> 4;
370
- if (n < 0) throw new Error("unreachable");
371
- if (n <= 7) return this.bytes.every((e) => 0 === e) ? "NIL" : "VAR_0";
372
- if (n <= 11) return "VAR_10";
373
- if (n <= 13) return "VAR_110";
374
- if (n <= 15) return this.bytes.every((e) => 255 === e) ? "MAX" : "VAR_RESERVED";
370
+ const n2 = this.bytes[8] >>> 4;
371
+ if (n2 < 0) throw new Error("unreachable");
372
+ if (n2 <= 7) return this.bytes.every((e) => 0 === e) ? "NIL" : "VAR_0";
373
+ if (n2 <= 11) return "VAR_10";
374
+ if (n2 <= 13) return "VAR_110";
375
+ if (n2 <= 15) return this.bytes.every((e) => 255 === e) ? "MAX" : "VAR_RESERVED";
375
376
  else throw new Error("unreachable");
376
377
  }
377
378
  getVersion() {
@@ -384,8 +385,8 @@ class UUID {
384
385
  return 0 === this.compareTo(other);
385
386
  }
386
387
  compareTo(other) {
387
- for (let i = 0; i < 16; i++) {
388
- const diff = this.bytes[i] - other.bytes[i];
388
+ for (let i2 = 0; i2 < 16; i2++) {
389
+ const diff = this.bytes[i2] - other.bytes[i2];
389
390
  if (0 !== diff) return Math.sign(diff);
390
391
  }
391
392
  return 0;
@@ -720,8 +721,8 @@ function removeTrailingSlash(url) {
720
721
  }
721
722
  async function retriable(fn, props) {
722
723
  let lastError = null;
723
- for (let i = 0; i < props.retryCount + 1; i++) {
724
- if (i > 0) await new Promise((r) => setTimeout(r, props.retryDelay));
724
+ for (let i2 = 0; i2 < props.retryCount + 1; i2++) {
725
+ if (i2 > 0) await new Promise((r2) => setTimeout(r2, props.retryDelay));
725
726
  try {
726
727
  const res = await fn();
727
728
  return res;
@@ -1423,8 +1424,8 @@ function getFilenameToChunkIdMap(stackParser) {
1423
1424
  if (result) acc[result[0]] = result[1];
1424
1425
  else {
1425
1426
  const parsedStack = stackParser(stackKey);
1426
- for (let i = parsedStack.length - 1; i >= 0; i--) {
1427
- const stackFrame = parsedStack[i];
1427
+ for (let i2 = parsedStack.length - 1; i2 >= 0; i2--) {
1428
+ const stackFrame = parsedStack[i2];
1428
1429
  const filename = stackFrame?.filename;
1429
1430
  const chunkId = chunkIdMap[stackKey];
1430
1431
  if (filename && chunkId) {
@@ -1632,8 +1633,8 @@ function createStackParser(platform, ...parsers) {
1632
1633
  return (stack, skipFirstLines = 0) => {
1633
1634
  const frames = [];
1634
1635
  const lines = stack.split("\n");
1635
- for (let i = skipFirstLines; i < lines.length; i++) {
1636
- const line = lines[i];
1636
+ for (let i2 = skipFirstLines; i2 < lines.length; i2++) {
1637
+ const line = lines[i2];
1637
1638
  if (line.length > 1024) continue;
1638
1639
  const cleanedLine = WEBPACK_ERROR_REGEXP.test(line) ? line.replace(WEBPACK_ERROR_REGEXP, "$1") : line;
1639
1640
  if (!cleanedLine.match(/\S*Error: /)) {
@@ -1715,10 +1716,10 @@ function extractExceptionKeysForMessage(err, maxLength = 40) {
1715
1716
  const keys = Object.keys(err);
1716
1717
  keys.sort();
1717
1718
  if (!keys.length) return "[object has no keys]";
1718
- for (let i = keys.length; i > 0; i--) {
1719
- const serialized = keys.slice(0, i).join(", ");
1719
+ for (let i2 = keys.length; i2 > 0; i2--) {
1720
+ const serialized = keys.slice(0, i2).join(", ");
1720
1721
  if (!(serialized.length > maxLength)) {
1721
- if (i === keys.length) return serialized;
1722
+ if (i2 === keys.length) return serialized;
1722
1723
  return serialized.length <= maxLength ? serialized : `${serialized.slice(0, maxLength)}...`;
1723
1724
  }
1724
1725
  }
@@ -1827,8 +1828,8 @@ const MAX_CONTEXTLINES_COLNO = 1e3;
1827
1828
  const MAX_CONTEXTLINES_LINENO = 1e4;
1828
1829
  async function addSourceContext(frames) {
1829
1830
  const filesToLines = {};
1830
- for (let i = frames.length - 1; i >= 0; i--) {
1831
- const frame = frames[i];
1831
+ for (let i2 = frames.length - 1; i2 >= 0; i2--) {
1832
+ const frame = frames[i2];
1832
1833
  const filename = frame?.filename;
1833
1834
  if (!frame || "string" != typeof filename || "number" != typeof frame.lineno || shouldSkipContextLinesForFile(filename) || shouldSkipContextLinesForFrame(frame)) continue;
1834
1835
  const filesToLinesOutput = filesToLines[filename];
@@ -1844,7 +1845,7 @@ async function addSourceContext(frames) {
1844
1845
  if (!filesToLineRanges) continue;
1845
1846
  filesToLineRanges.sort((a, b) => a - b);
1846
1847
  const ranges = makeLineReaderRanges(filesToLineRanges);
1847
- if (ranges.every((r) => rangeExistsInContentCache(file, r))) continue;
1848
+ if (ranges.every((r2) => rangeExistsInContentCache(file, r2))) continue;
1848
1849
  const cache = emplace(LRU_FILE_CONTENTS_CACHE, file, {});
1849
1850
  readlinePromises.push(getContextLinesFromFile(file, ranges, cache));
1850
1851
  }
@@ -1912,8 +1913,8 @@ function addSourceContextToFrames(frames, cache) {
1912
1913
  function addContextToFrame(lineno, frame, contents) {
1913
1914
  if (void 0 === frame.lineno || void 0 === contents) return;
1914
1915
  frame.pre_context = [];
1915
- for (let i = makeRangeStart(lineno); i < lineno; i++) {
1916
- const line = contents[i];
1916
+ for (let i2 = makeRangeStart(lineno); i2 < lineno; i2++) {
1917
+ const line = contents[i2];
1917
1918
  if (void 0 === line) return void clearLineContext(frame);
1918
1919
  frame.pre_context.push(line);
1919
1920
  }
@@ -1921,8 +1922,8 @@ function addContextToFrame(lineno, frame, contents) {
1921
1922
  frame.context_line = contents[lineno];
1922
1923
  const end = makeRangeEnd(lineno);
1923
1924
  frame.post_context = [];
1924
- for (let i = lineno + 1; i <= end; i++) {
1925
- const line = contents[i];
1925
+ for (let i2 = lineno + 1; i2 <= end; i2++) {
1926
+ const line = contents[i2];
1926
1927
  if (void 0 === line) break;
1927
1928
  frame.post_context.push(line);
1928
1929
  }
@@ -1943,29 +1944,29 @@ function shouldSkipContextLinesForFrame(frame) {
1943
1944
  function rangeExistsInContentCache(file, range) {
1944
1945
  const contents = LRU_FILE_CONTENTS_CACHE.get(file);
1945
1946
  if (void 0 === contents) return false;
1946
- for (let i = range[0]; i <= range[1]; i++) if (void 0 === contents[i]) return false;
1947
+ for (let i2 = range[0]; i2 <= range[1]; i2++) if (void 0 === contents[i2]) return false;
1947
1948
  return true;
1948
1949
  }
1949
1950
  function makeLineReaderRanges(lines) {
1950
1951
  if (!lines.length) return [];
1951
- let i = 0;
1952
+ let i2 = 0;
1952
1953
  const line = lines[0];
1953
1954
  if ("number" != typeof line) return [];
1954
1955
  let current = makeContextRange(line);
1955
1956
  const out = [];
1956
1957
  while (true) {
1957
- if (i === lines.length - 1) {
1958
+ if (i2 === lines.length - 1) {
1958
1959
  out.push(current);
1959
1960
  break;
1960
1961
  }
1961
- const next = lines[i + 1];
1962
+ const next = lines[i2 + 1];
1962
1963
  if ("number" != typeof next) break;
1963
1964
  if (next <= current[1]) current[1] = next + DEFAULT_LINES_OF_CONTEXT;
1964
1965
  else {
1965
1966
  out.push(current);
1966
1967
  current = makeContextRange(next);
1967
1968
  }
1968
- i++;
1969
+ i2++;
1969
1970
  }
1970
1971
  return out;
1971
1972
  }
@@ -3378,7 +3379,9 @@ const initServer = async (app, serverEnv) => {
3378
3379
  if (isProduction$1) {
3379
3380
  sessionConfig.cookie.secure = true;
3380
3381
  }
3381
- app.use(session(sessionConfig));
3382
+ const sessionMiddleware = session(sessionConfig);
3383
+ app.locals.rbSessionMiddleware = sessionMiddleware;
3384
+ app.use(sessionMiddleware);
3382
3385
  };
3383
3386
  async function hashPassword(password, salt) {
3384
3387
  const keyLength = 64;
@@ -3422,15 +3425,15 @@ const parseEnvInt = (value) => {
3422
3425
  return parsed;
3423
3426
  };
3424
3427
  const isPowerOfTwo = (value) => (value & value - 1) === 0;
3425
- const estimateScryptMemoryBytes = ({ N, r, p }) => {
3426
- return 128 * r * (N + p);
3428
+ const estimateScryptMemoryBytes = ({ N, r: r2, p }) => {
3429
+ return 128 * r2 * (N + p);
3427
3430
  };
3428
3431
  const validateScryptParams = (params) => {
3429
- const { N, r, p, keylen, saltBytes, maxmemBytes } = params;
3432
+ const { N, r: r2, p, keylen, saltBytes, maxmemBytes } = params;
3430
3433
  if (!Number.isSafeInteger(N) || N < 2 || N > MAX_SCRYPT_N || !isPowerOfTwo(N)) {
3431
3434
  return { ok: false, error: "invalid_scrypt_N" };
3432
3435
  }
3433
- if (!Number.isSafeInteger(r) || r < 1 || r > MAX_SCRYPT_R) {
3436
+ if (!Number.isSafeInteger(r2) || r2 < 1 || r2 > MAX_SCRYPT_R) {
3434
3437
  return { ok: false, error: "invalid_scrypt_r" };
3435
3438
  }
3436
3439
  if (!Number.isSafeInteger(p) || p < 1 || p > MAX_SCRYPT_P) {
@@ -3445,7 +3448,7 @@ const validateScryptParams = (params) => {
3445
3448
  if (!Number.isSafeInteger(maxmemBytes) || maxmemBytes < 16 * 1024 * 1024 || maxmemBytes > MAX_SCRYPT_MAXMEM_BYTES) {
3446
3449
  return { ok: false, error: "invalid_scrypt_maxmem" };
3447
3450
  }
3448
- const estimatedMem = estimateScryptMemoryBytes({ N, r, p });
3451
+ const estimatedMem = estimateScryptMemoryBytes({ N, r: r2, p });
3449
3452
  if (estimatedMem > maxmemBytes) {
3450
3453
  return { ok: false, error: "scrypt_params_exceed_maxmem" };
3451
3454
  }
@@ -3480,9 +3483,9 @@ const getCurrentScryptParams = (opts) => {
3480
3483
  return params;
3481
3484
  };
3482
3485
  const scryptAsync = async (password, salt, params) => {
3483
- const { N, r, p, keylen, maxmemBytes } = params;
3486
+ const { N, r: r2, p, keylen, maxmemBytes } = params;
3484
3487
  return await new Promise((resolve, reject) => {
3485
- crypto.scrypt(password, salt, keylen, { N, r, p, maxmem: maxmemBytes }, (err, derivedKey) => {
3488
+ crypto.scrypt(password, salt, keylen, { N, r: r2, p, maxmem: maxmemBytes }, (err, derivedKey) => {
3486
3489
  if (err) {
3487
3490
  reject(err);
3488
3491
  return;
@@ -3529,10 +3532,10 @@ const parseStoredScryptHash = (stored) => {
3529
3532
  params.set(key, value);
3530
3533
  }
3531
3534
  const N = params.get("N");
3532
- const r = params.get("r");
3535
+ const r2 = params.get("r");
3533
3536
  const p = params.get("p");
3534
3537
  const keylen = params.get("keylen");
3535
- if (N === void 0 || r === void 0 || p === void 0 || keylen === void 0) return null;
3538
+ if (N === void 0 || r2 === void 0 || p === void 0 || keylen === void 0) return null;
3536
3539
  if (params.size !== 4) return null;
3537
3540
  const salt = parseB64(saltB64);
3538
3541
  const dk = parseB64(dkB64);
@@ -3543,31 +3546,31 @@ const parseStoredScryptHash = (stored) => {
3543
3546
  const currentMaxmemBytes = getCurrentMaxmemBytes();
3544
3547
  const validated = validateScryptParams({
3545
3548
  N,
3546
- r,
3549
+ r: r2,
3547
3550
  p,
3548
3551
  keylen,
3549
3552
  saltBytes: salt.length,
3550
3553
  maxmemBytes: currentMaxmemBytes
3551
3554
  });
3552
3555
  if (!validated.ok) return null;
3553
- return { N, r, p, keylen, salt, dk };
3556
+ return { N, r: r2, p, keylen, salt, dk };
3554
3557
  };
3555
3558
  async function hashPasswordForStorage(password, opts) {
3556
- const { N, r, p, keylen, saltBytes, maxmemBytes } = getCurrentScryptParams(opts);
3559
+ const { N, r: r2, p, keylen, saltBytes, maxmemBytes } = getCurrentScryptParams(opts);
3557
3560
  const salt = crypto.randomBytes(saltBytes);
3558
- const dk = await scryptAsync(password, salt, { N, r, p, keylen, maxmemBytes });
3561
+ const dk = await scryptAsync(password, salt, { N, r: r2, p, keylen, maxmemBytes });
3559
3562
  const saltB64 = salt.toString("base64");
3560
3563
  const dkB64 = dk.toString("base64");
3561
- return `$scrypt$N=${N},r=${r},p=${p},keylen=${keylen}$${saltB64}$${dkB64}`;
3564
+ return `$scrypt$N=${N},r=${r2},p=${p},keylen=${keylen}$${saltB64}$${dkB64}`;
3562
3565
  }
3563
3566
  async function verifyPasswordFromStorage(password, stored) {
3564
3567
  const parsed = parseStoredScryptHash(stored);
3565
3568
  if (!parsed) return false;
3566
- const { N, r, p, keylen, salt, dk } = parsed;
3569
+ const { N, r: r2, p, keylen, salt, dk } = parsed;
3567
3570
  const maxmemBytes = getCurrentMaxmemBytes();
3568
3571
  let derivedKey;
3569
3572
  try {
3570
- derivedKey = await scryptAsync(password, salt, { N, r, p, keylen, maxmemBytes });
3573
+ derivedKey = await scryptAsync(password, salt, { N, r: r2, p, keylen, maxmemBytes });
3571
3574
  } catch {
3572
3575
  return false;
3573
3576
  }
@@ -3590,7 +3593,7 @@ function createLocation(current, to, state = null, key) {
3590
3593
  return location;
3591
3594
  }
3592
3595
  function getShortCircuitMatches(routes) {
3593
- const route = routes.length === 1 ? routes[0] : routes.find((r) => r.index || !r.path || r.path === "/") || {
3596
+ const route = routes.length === 1 ? routes[0] : routes.find((r2) => r2.index || !r2.path || r2.path === "/") || {
3594
3597
  id: "__shim-error-route__"
3595
3598
  };
3596
3599
  return {
@@ -4095,7 +4098,10 @@ export {
4095
4098
  getDerivedKey,
4096
4099
  hashPassword,
4097
4100
  hashPasswordForStorage,
4101
+ i as initRts,
4098
4102
  initServer,
4103
+ n as notifyRtsModelChanged,
4104
+ r as registerRtsHandler,
4099
4105
  sendEmail,
4100
4106
  ssrMiddleware,
4101
4107
  verifyPasswordFromStorage
@@ -1 +1 @@
1
- {"version":3,"file":"initServer.d.ts","sourceRoot":"","sources":["../src/initServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAuBrC,KAAK,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AA8EtD,eAAO,MAAM,UAAU,GAAU,KAAK,WAAW,EAAE,WAAW,SAAS,kBA2DtE,CAAA"}
1
+ {"version":3,"file":"initServer.d.ts","sourceRoot":"","sources":["../src/initServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAuBrC,KAAK,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,CAAA;AA8EtD,eAAO,MAAM,UAAU,GAAU,KAAK,WAAW,EAAE,WAAW,SAAS,kBA6DtE,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { Server as HttpServer } from 'node:http';
2
+ import { RequestHandler } from 'express';
3
+ import { WebSocket } from 'ws';
4
+ type SocketMeta = {
5
+ tenantId: string;
6
+ userId: string;
7
+ };
8
+ type HandlerFn = (socket: RtsSocket) => void | (() => void);
9
+ declare class RtsSocket {
10
+ readonly id: string;
11
+ readonly tenantId: string;
12
+ readonly userId: string;
13
+ private readonly ws;
14
+ private readonly handlers;
15
+ constructor({ id, ws, meta, }: {
16
+ id: string;
17
+ ws: WebSocket;
18
+ meta: SocketMeta;
19
+ });
20
+ on(event: string, handler: (payload: unknown) => void): () => void;
21
+ off(event: string, handler: (payload: unknown) => void): void;
22
+ emit(event: string, payload?: unknown): void;
23
+ close(): void;
24
+ dispatch(event: string, payload: unknown): void;
25
+ }
26
+ export declare const initRts: ({ server, path, sessionMiddleware, }: {
27
+ server: HttpServer;
28
+ path?: string;
29
+ sessionMiddleware?: RequestHandler;
30
+ }) => void;
31
+ export declare const registerRtsHandler: (handler: HandlerFn) => void;
32
+ export declare const notifyRtsModelChanged: (tenantId: string, modelName: string) => void;
33
+ export {};
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rts/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAG7C,OAAO,EAAiC,KAAK,SAAS,EAAE,MAAM,IAAI,CAAA;AAqBlE,KAAK,UAAU,GAAG;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAoBD,KAAK,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;AAoB3D,cAAM,SAAS;IACb,SAAgB,EAAE,EAAE,MAAM,CAAA;IAC1B,SAAgB,QAAQ,EAAE,MAAM,CAAA;IAChC,SAAgB,MAAM,EAAE,MAAM,CAAA;IAE9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqD;gBAE3D,EACjB,EAAE,EACF,EAAE,EACF,IAAI,GACL,EAAE;QACD,EAAE,EAAE,MAAM,CAAA;QACV,EAAE,EAAE,SAAS,CAAA;QACb,IAAI,EAAE,UAAU,CAAA;KACjB;IAOM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI;IAOlE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAO7D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI;IAI5C,KAAK,IAAI,IAAI;IAQb,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI;CAOvD;AA4aD,eAAO,MAAM,OAAO,GAAI,sCAIrB;IACD,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,cAAc,CAAA;CACnC,KAAG,IAyFH,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,SAAS,SAAS,KAAG,IAEvD,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,KAAG,IAE3E,CAAA"}
package/dist/rts.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './rts/index';
2
+ //# sourceMappingURL=rts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rts.d.ts","sourceRoot":"","sources":["../src/rts.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
package/dist/rts.js ADDED
@@ -0,0 +1,6 @@
1
+ import { i, n, r } from "./index-BXODFGBH.js";
2
+ export {
3
+ i as initRts,
4
+ n as notifyRtsModelChanged,
5
+ r as registerRtsHandler
6
+ };
package/package.json CHANGED
@@ -1,12 +1,24 @@
1
1
  {
2
2
  "name": "@rpcbase/server",
3
- "version": "0.463.0",
3
+ "version": "0.465.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
8
8
  "main": "./dist/index.js",
9
9
  "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./rts": {
17
+ "types": "./dist/rts.d.ts",
18
+ "import": "./dist/rts.js",
19
+ "default": "./dist/rts.js"
20
+ }
21
+ },
10
22
  "scripts": {
11
23
  "build": "wireit",
12
24
  "test": "wireit",
@@ -64,9 +76,11 @@
64
76
  "express-session": "1.18.2",
65
77
  "http-proxy-middleware": "3.0.5",
66
78
  "redis": "5.10.0",
67
- "resend": "6.5.2"
79
+ "resend": "6.5.2",
80
+ "ws": "8.18.0"
68
81
  },
69
82
  "devDependencies": {
83
+ "@types/ws": "8.18.1",
70
84
  "request-ip": "3.3.0"
71
85
  }
72
86
  }