@rpcbase/server 0.508.0 → 0.509.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/index.js CHANGED
@@ -4,7 +4,7 @@ import MongoStore from "connect-mongo";
4
4
  import { createClient } from "redis";
5
5
  import { MongoClient } from "mongodb";
6
6
  import env from "@rpcbase/env";
7
- import { initApiClient, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
7
+ import { initApiClient, STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY, RtsSsrRuntimeProvider, SsrErrorFallback, SSR_ERROR_STATE_GLOBAL_KEY, serializeSsrErrorState } from "@rpcbase/client";
8
8
  import { dirname, posix, sep } from "path";
9
9
  import fs, { createReadStream, readFileSync } from "node:fs";
10
10
  import { createInterface } from "node:readline";
@@ -16,11 +16,12 @@ import fsPromises from "node:fs/promises";
16
16
  import inspector from "node:inspector";
17
17
  import path from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
- import { Transform } from "node:stream";
19
+ import { Writable, Transform } from "node:stream";
20
20
  import { StrictMode, createElement } from "react";
21
21
  import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
22
22
  import { jsx } from "react/jsx-runtime";
23
23
  import { createPath, matchRoutes, parsePath, createStaticRouter, StaticRouterProvider } from "@rpcbase/router";
24
+ import { r as resolveRtsRequestTenantId, i as isRtsRequestAuthorized, b as buildRtsAbilityFromRequest, n as normalizeRtsQueryOptions, a as runRtsQuery } from "./queryExecutor-BZ0GSPM1.js";
24
25
  import { s } from "./email-DEw8keax.js";
25
26
  function getDefaultExportFromCjs(x) {
26
27
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -4423,6 +4424,142 @@ async function applyRouteLoaders(req, dataRoutes) {
4423
4424
  statusCode
4424
4425
  };
4425
4426
  }
4427
+ const DEFAULT_MAX_QUERIES = 32;
4428
+ const DEFAULT_MAX_DOCS_PER_QUERY = 500;
4429
+ const DEFAULT_MAX_SERIALIZED_BYTES = 200 * 1024;
4430
+ const makeRegistrationKey = (modelName, queryKey) => `${modelName}.${queryKey}`;
4431
+ const hasSessionUser = (req) => {
4432
+ const id = req.session?.user?.id;
4433
+ return typeof id === "string" && id.trim().length > 0;
4434
+ };
4435
+ const escapeForInlineScript = (value) => {
4436
+ return value.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4437
+ };
4438
+ const createRtsSsrCollector = (req, opts) => {
4439
+ const maxQueries = DEFAULT_MAX_QUERIES;
4440
+ const maxDocsPerQuery = DEFAULT_MAX_DOCS_PER_QUERY;
4441
+ const maxSerializedBytes = DEFAULT_MAX_SERIALIZED_BYTES;
4442
+ const registrations = /* @__PURE__ */ new Map();
4443
+ const resolved = /* @__PURE__ */ new Map();
4444
+ const runtime = {
4445
+ registerQuery(query) {
4446
+ const modelName = typeof query.modelName === "string" ? query.modelName.trim() : "";
4447
+ const queryKey = typeof query.queryKey === "string" ? query.queryKey.trim() : "";
4448
+ if (!modelName || !queryKey) return;
4449
+ const key = makeRegistrationKey(modelName, queryKey);
4450
+ if (registrations.has(key)) return;
4451
+ if (registrations.size >= maxQueries) return;
4452
+ registrations.set(key, {
4453
+ modelName,
4454
+ queryKey,
4455
+ query: query.query ?? {},
4456
+ options: query.options ?? {}
4457
+ });
4458
+ },
4459
+ getQueryData(modelName, queryKey) {
4460
+ return resolved.get(makeRegistrationKey(modelName, queryKey));
4461
+ }
4462
+ };
4463
+ const resolve = async () => {
4464
+ if (!registrations.size) return null;
4465
+ const tenantId = resolveRtsRequestTenantId(req);
4466
+ if (!tenantId) return null;
4467
+ if (hasSessionUser(req) && !isRtsRequestAuthorized(req, tenantId)) {
4468
+ return null;
4469
+ }
4470
+ const { ability, userId } = await buildRtsAbilityFromRequest(req, tenantId);
4471
+ const queryEntries = [];
4472
+ for (const registration of registrations.values()) {
4473
+ const options = normalizeRtsQueryOptions(registration.options);
4474
+ try {
4475
+ const data = await runRtsQuery({
4476
+ tenantId,
4477
+ ability,
4478
+ modelName: registration.modelName,
4479
+ query: registration.query,
4480
+ options
4481
+ });
4482
+ const boundedData = maxDocsPerQuery > 0 ? data.slice(0, maxDocsPerQuery) : data;
4483
+ queryEntries.push({
4484
+ modelName: registration.modelName,
4485
+ queryKey: registration.queryKey,
4486
+ data: boundedData
4487
+ });
4488
+ } catch {
4489
+ continue;
4490
+ }
4491
+ }
4492
+ if (!queryEntries.length) return null;
4493
+ const payload = {
4494
+ v: 1,
4495
+ tenantId,
4496
+ uid: userId,
4497
+ queries: []
4498
+ };
4499
+ for (const entry of queryEntries) {
4500
+ payload.queries.push(entry);
4501
+ const bytes = Buffer.byteLength(JSON.stringify(payload), "utf8");
4502
+ if (bytes > maxSerializedBytes) {
4503
+ payload.queries.pop();
4504
+ break;
4505
+ }
4506
+ }
4507
+ resolved.clear();
4508
+ for (const entry of payload.queries) {
4509
+ resolved.set(makeRegistrationKey(entry.modelName, entry.queryKey), entry.data);
4510
+ }
4511
+ return payload.queries.length ? payload : null;
4512
+ };
4513
+ const hasRegistrations = () => registrations.size > 0;
4514
+ return { runtime, hasRegistrations, resolve };
4515
+ };
4516
+ const renderRtsHydrationScript = (data) => {
4517
+ if (!data) return "";
4518
+ const serialized = escapeForInlineScript(JSON.stringify(data));
4519
+ return `<script>window.${STATIC_RPCBASE_RTS_HYDRATION_DATA_KEY}=${serialized};<\/script>`;
4520
+ };
4521
+ const RTS_SSR_PREPASS_TIMEOUT_MS = 4e3;
4522
+ const runRtsPrepass = async (element) => {
4523
+ return await new Promise((resolve) => {
4524
+ let isDone = false;
4525
+ let timeoutId = null;
4526
+ const finish = (ok) => {
4527
+ if (isDone) return;
4528
+ isDone = true;
4529
+ if (timeoutId) {
4530
+ clearTimeout(timeoutId);
4531
+ }
4532
+ resolve(ok);
4533
+ };
4534
+ const sink = new Writable({
4535
+ write(_chunk, _encoding, callback) {
4536
+ callback();
4537
+ }
4538
+ });
4539
+ sink.on("finish", () => {
4540
+ finish(true);
4541
+ });
4542
+ sink.on("error", () => {
4543
+ finish(false);
4544
+ });
4545
+ const { pipe, abort } = renderToPipeableStream(element, {
4546
+ onAllReady() {
4547
+ if (isDone) return;
4548
+ pipe(sink);
4549
+ },
4550
+ onShellError() {
4551
+ finish(false);
4552
+ abort();
4553
+ },
4554
+ onError() {
4555
+ }
4556
+ });
4557
+ timeoutId = setTimeout(() => {
4558
+ abort();
4559
+ finish(false);
4560
+ }, RTS_SSR_PREPASS_TIMEOUT_MS);
4561
+ });
4562
+ };
4426
4563
  async function renderSSR(req, dataRoutes) {
4427
4564
  const routerContext = await applyRouteLoaders(req, dataRoutes);
4428
4565
  const isMatched = routerContext.matches.length > 0;
@@ -4433,7 +4570,8 @@ async function renderSSR(req, dataRoutes) {
4433
4570
  statusCode: routerContext.statusCode ?? routerContext.redirectResponse.status ?? 302,
4434
4571
  redirectResponse: routerContext.redirectResponse,
4435
4572
  redirectRouteId: routerContext.redirectRouteId,
4436
- redirectRoutePath: routerContext.redirectRoutePath
4573
+ redirectRoutePath: routerContext.redirectRoutePath,
4574
+ rtsHydrationData: null
4437
4575
  };
4438
4576
  }
4439
4577
  if (routerContext.errors) {
@@ -4468,18 +4606,34 @@ async function renderSSR(req, dataRoutes) {
4468
4606
  throw error;
4469
4607
  }
4470
4608
  }
4471
- const router = createStaticRouter(dataRoutes, routerContext);
4472
- const element = /* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(
4473
- StaticRouterProvider,
4474
- {
4475
- router,
4476
- context: routerContext
4609
+ const buildElement = (runtime2) => {
4610
+ const router = createStaticRouter(dataRoutes, routerContext);
4611
+ const app = /* @__PURE__ */ jsx(
4612
+ StaticRouterProvider,
4613
+ {
4614
+ router,
4615
+ context: routerContext
4616
+ }
4617
+ );
4618
+ return /* @__PURE__ */ jsx(StrictMode, { children: runtime2 ? /* @__PURE__ */ jsx(RtsSsrRuntimeProvider, { value: runtime2, children: app }) : app });
4619
+ };
4620
+ const tenantId = resolveRtsRequestTenantId(req);
4621
+ let rtsHydrationData = null;
4622
+ let runtime = null;
4623
+ if (tenantId) {
4624
+ const collector = createRtsSsrCollector(req);
4625
+ const prepassOk = await runRtsPrepass(buildElement(collector.runtime));
4626
+ if (prepassOk && collector.hasRegistrations()) {
4627
+ rtsHydrationData = await collector.resolve();
4477
4628
  }
4478
- ) });
4629
+ runtime = collector.runtime;
4630
+ }
4631
+ const element = buildElement(runtime);
4479
4632
  return {
4480
4633
  element,
4481
4634
  isMatched,
4482
- statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200)
4635
+ statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200),
4636
+ rtsHydrationData
4483
4637
  };
4484
4638
  }
4485
4639
  const ABORT_DELAY_MS = 1e4;
@@ -4630,6 +4784,7 @@ const ssrMiddleware = ({
4630
4784
  element,
4631
4785
  isMatched,
4632
4786
  statusCode,
4787
+ rtsHydrationData,
4633
4788
  redirectResponse,
4634
4789
  redirectRouteId,
4635
4790
  redirectRoutePath
@@ -4697,9 +4852,10 @@ const ssrMiddleware = ({
4697
4852
  });
4698
4853
  const start = htmlStart;
4699
4854
  const end = htmlEnd;
4855
+ const rtsHydrationScript = renderRtsHydrationScript(rtsHydrationData);
4700
4856
  res.write(start);
4701
4857
  transformStream.on("finish", () => {
4702
- res.end(end);
4858
+ res.end(`${rtsHydrationScript}${end}`);
4703
4859
  });
4704
4860
  pipe(transformStream);
4705
4861
  }