@rpcbase/server 0.462.0 → 0.463.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.
@@ -1,4 +1,9 @@
1
1
  import { Request } from 'express';
2
2
  import { StaticHandlerContext } from '../../router/src';
3
- export declare function applyRouteLoaders(req: Request, dataRoutes: any[]): Promise<StaticHandlerContext>;
3
+ export type RouterContextWithRedirect = StaticHandlerContext & {
4
+ redirectResponse?: Response;
5
+ redirectRouteId?: string | null;
6
+ redirectRoutePath?: string | null;
7
+ };
8
+ export declare function applyRouteLoaders(req: Request, dataRoutes: any[]): Promise<RouterContextWithRedirect>;
4
9
  //# sourceMappingURL=applyRouteLoaders.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"applyRouteLoaders.d.ts","sourceRoot":"","sources":["../src/applyRouteLoaders.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAA;AAC/B,OAAO,EACL,oBAAoB,EAMrB,MAAM,iBAAiB,CAAA;AAqExB,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,GAAG,EAAE,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAsI/B"}
1
+ {"version":3,"file":"applyRouteLoaders.d.ts","sourceRoot":"","sources":["../src/applyRouteLoaders.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,OAAO,EAAC,MAAM,SAAS,CAAA;AAC/B,OAAO,EACL,oBAAoB,EAMrB,MAAM,iBAAiB,CAAA;AAuFxB,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG;IAC7D,gBAAgB,CAAC,EAAE,QAAQ,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,CAAA;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,OAAO,EACZ,UAAU,EAAE,GAAG,EAAE,GAChB,OAAO,CAAC,yBAAyB,CAAC,CA6KpC"}
package/dist/index.js CHANGED
@@ -3615,6 +3615,15 @@ const getErrorStatus = (error) => {
3615
3615
  if (typeof candidate?.response?.status === "number") return candidate.response.status;
3616
3616
  return void 0;
3617
3617
  };
3618
+ const isResponseLike = (value) => {
3619
+ return Boolean(
3620
+ value && typeof value === "object" && typeof value.status === "number" && value.headers && typeof value.headers.get === "function"
3621
+ );
3622
+ };
3623
+ const isRedirectResponse = (value) => {
3624
+ if (!isResponseLike(value)) return false;
3625
+ return value.status >= 300 && value.status < 400 && Boolean(value.headers.get("Location"));
3626
+ };
3618
3627
  async function applyRouteLoaders(req, dataRoutes) {
3619
3628
  const baseUrl = `${req.protocol}://${req.get("host")}`;
3620
3629
  const url = new URL(req.originalUrl, baseUrl);
@@ -3659,8 +3668,12 @@ async function applyRouteLoaders(req, dataRoutes) {
3659
3668
  timeoutId = setTimeout(() => {
3660
3669
  const err = new Error(`Loader timeout after ${LOADER_TIMEOUT_MS}ms`);
3661
3670
  err.status = 504;
3662
- console.error("[rpcbase timeout][server loader]", { routeId: route.id, ms: LOADER_TIMEOUT_MS, url: req.originalUrl });
3663
- reject({ id: route.id, reason: err });
3671
+ console.error("[rpcbase timeout][server loader]", {
3672
+ routeId: route.id,
3673
+ ms: LOADER_TIMEOUT_MS,
3674
+ url: req.originalUrl
3675
+ });
3676
+ reject({ id: route.id, path: route.path, reason: err });
3664
3677
  }, LOADER_TIMEOUT_MS);
3665
3678
  });
3666
3679
  const loaderPromise = (async () => {
@@ -3669,9 +3682,9 @@ async function applyRouteLoaders(req, dataRoutes) {
3669
3682
  params,
3670
3683
  ctx: { req }
3671
3684
  });
3672
- return { id: route.id, data };
3685
+ return { id: route.id, path: route.path, data };
3673
3686
  } catch (error) {
3674
- throw { id: route.id, reason: error };
3687
+ throw { id: route.id, path: route.path, reason: error };
3675
3688
  } finally {
3676
3689
  if (timeoutId) {
3677
3690
  clearTimeout(timeoutId);
@@ -3687,15 +3700,33 @@ async function applyRouteLoaders(req, dataRoutes) {
3687
3700
  let errors = null;
3688
3701
  let hasNotFoundError = false;
3689
3702
  let hasNonNotFoundError = false;
3703
+ let redirectResponse = null;
3704
+ let redirectRouteId = null;
3705
+ let redirectRoutePath = null;
3690
3706
  for (const result of loaderPromisesResults) {
3691
3707
  if (result.status === "fulfilled") {
3692
3708
  if (result.value) {
3693
- loaderData[result.value.id] = result.value.data;
3709
+ if (isRedirectResponse(result.value.data)) {
3710
+ redirectResponse = result.value.data;
3711
+ redirectRouteId = result.value.id;
3712
+ redirectRoutePath = result.value.path ?? null;
3713
+ } else {
3714
+ loaderData[result.value.id] = result.value.data;
3715
+ }
3694
3716
  }
3695
3717
  } else if (result.status === "rejected") {
3696
3718
  const id = result.reason?.id;
3697
3719
  if (!id) {
3698
- throw new Error(`missing route ID in error: ${result.reason}`);
3720
+ throw new Error(
3721
+ `missing route ID in error: ${result.reason}`
3722
+ );
3723
+ }
3724
+ const reasonCandidate = result.reason?.reason ?? result.reason;
3725
+ if (isRedirectResponse(reasonCandidate)) {
3726
+ redirectResponse = reasonCandidate;
3727
+ redirectRouteId = id;
3728
+ redirectRoutePath = result.reason?.path ?? null;
3729
+ continue;
3699
3730
  }
3700
3731
  if (!errors) {
3701
3732
  errors = {};
@@ -3709,6 +3740,19 @@ async function applyRouteLoaders(req, dataRoutes) {
3709
3740
  errors[id] = result.reason;
3710
3741
  }
3711
3742
  }
3743
+ if (redirectResponse) {
3744
+ return {
3745
+ ...baseContext,
3746
+ matches,
3747
+ loaderData,
3748
+ actionData: null,
3749
+ errors: null,
3750
+ statusCode: redirectResponse.status,
3751
+ redirectResponse,
3752
+ redirectRouteId,
3753
+ redirectRoutePath
3754
+ };
3755
+ }
3712
3756
  let statusCode = 200;
3713
3757
  if (errors) {
3714
3758
  if (hasNonNotFoundError) {
@@ -3729,19 +3773,46 @@ async function applyRouteLoaders(req, dataRoutes) {
3729
3773
  };
3730
3774
  }
3731
3775
  async function renderSSR(req, dataRoutes) {
3732
- let routerContext;
3733
- try {
3734
- routerContext = await applyRouteLoaders(req, dataRoutes);
3735
- } catch (err) {
3736
- console.log(err);
3737
- throw err;
3776
+ const routerContext = await applyRouteLoaders(req, dataRoutes);
3777
+ const isMatched = routerContext.matches.length > 0;
3778
+ if (routerContext.redirectResponse) {
3779
+ return {
3780
+ element: null,
3781
+ isMatched,
3782
+ statusCode: routerContext.statusCode ?? routerContext.redirectResponse.status ?? 302,
3783
+ redirectResponse: routerContext.redirectResponse,
3784
+ redirectRouteId: routerContext.redirectRouteId,
3785
+ redirectRoutePath: routerContext.redirectRoutePath
3786
+ };
3738
3787
  }
3739
3788
  if (routerContext.errors) {
3740
3789
  if (routerContext.statusCode === 404) {
3741
- console.warn(`SSR 404 ${req.method} ${req.originalUrl}`, { errors: routerContext.errors });
3790
+ console.warn(`SSR 404 ${req.method} ${req.originalUrl}`, {
3791
+ errors: routerContext.errors
3792
+ });
3742
3793
  } else {
3743
- console.error(`SSR ${routerContext.statusCode || 500} ${req.method} ${req.originalUrl}`, routerContext.errors);
3744
- const error = new Error("SSR loader error");
3794
+ const matchesSummary = routerContext.matches?.map((m) => ({
3795
+ routeId: m.route.id,
3796
+ routePath: m.route.path,
3797
+ pathname: m.pathname,
3798
+ pathnameBase: m.pathnameBase
3799
+ }));
3800
+ console.error(
3801
+ `SSR ${routerContext.statusCode || 500} ${req.method} ${req.originalUrl}`,
3802
+ {
3803
+ errors: routerContext.errors,
3804
+ matches: matchesSummary
3805
+ }
3806
+ );
3807
+ const errorEntries = Object.entries(routerContext.errors || {});
3808
+ const firstErrorEntry = errorEntries[0]?.[1];
3809
+ const firstReason = firstErrorEntry?.reason ?? firstErrorEntry;
3810
+ if (errorEntries.length === 1 && firstReason instanceof Error) {
3811
+ firstReason.details = routerContext.errors;
3812
+ throw firstReason;
3813
+ }
3814
+ const extra = firstErrorEntry?.id ? ` (route ${firstErrorEntry.id}${firstErrorEntry?.path ? ` ${firstErrorEntry.path}` : ""})` : "";
3815
+ const error = new Error(`SSR loader error${extra}`);
3745
3816
  error.details = routerContext.errors;
3746
3817
  throw error;
3747
3818
  }
@@ -3754,8 +3825,11 @@ async function renderSSR(req, dataRoutes) {
3754
3825
  context: routerContext
3755
3826
  }
3756
3827
  ) });
3757
- const isMatched = routerContext.matches.length > 0;
3758
- return { element, isMatched, statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200) };
3828
+ return {
3829
+ element,
3830
+ isMatched,
3831
+ statusCode: routerContext.statusCode ?? (routerContext.errors ? 500 : 200)
3832
+ };
3759
3833
  }
3760
3834
  const ABORT_DELAY_MS = 1e4;
3761
3835
  const APP_HTML_PLACEHOLDER = "<!--app-html-->";
@@ -3764,6 +3838,33 @@ const FALLBACK_ERROR_TEMPLATE_START = `<!doctype html><html lang="en"><head><met
3764
3838
  const FALLBACK_ERROR_TEMPLATE_END = "</main></body></html>";
3765
3839
  const isProduction = env.NODE_ENV === "production";
3766
3840
  const templateHtml = isProduction ? readFileSync("./build/dist/client/src/client/index.html", "utf-8") : "";
3841
+ const handleRedirectionResponse = (res, redirectResponse, location) => {
3842
+ res.status(redirectResponse.status || 302);
3843
+ try {
3844
+ const headers = redirectResponse.headers;
3845
+ const setCookies = headers.getSetCookie?.();
3846
+ const fallbackSetCookies = [];
3847
+ for (const [key, value] of headers) {
3848
+ if (key.toLowerCase() === "set-cookie") {
3849
+ if (!setCookies?.length) {
3850
+ fallbackSetCookies.push(value);
3851
+ }
3852
+ continue;
3853
+ }
3854
+ res.setHeader(key, value);
3855
+ }
3856
+ if (setCookies?.length) {
3857
+ res.setHeader("Set-Cookie", setCookies);
3858
+ } else if (fallbackSetCookies.length === 1) {
3859
+ res.setHeader("Set-Cookie", fallbackSetCookies[0]);
3860
+ } else if (fallbackSetCookies.length > 1) {
3861
+ res.setHeader("Set-Cookie", fallbackSetCookies);
3862
+ }
3863
+ } catch {
3864
+ }
3865
+ if (location) res.setHeader("Location", location);
3866
+ res.end();
3867
+ };
3767
3868
  const formatErrorDetails = (error) => {
3768
3869
  if (isProduction) return void 0;
3769
3870
  if (!error) return void 0;
@@ -3874,7 +3975,32 @@ const ssrMiddleware = ({
3874
3975
  htmlStart = templateStart;
3875
3976
  }
3876
3977
  htmlEnd = template.slice(placeholderIndex + APP_HTML_PLACEHOLDER.length);
3877
- const { element, isMatched, statusCode } = await renderSSR(req, dataRoutes);
3978
+ const {
3979
+ element,
3980
+ isMatched,
3981
+ statusCode,
3982
+ redirectResponse,
3983
+ redirectRouteId,
3984
+ redirectRoutePath
3985
+ } = await renderSSR(req, dataRoutes);
3986
+ if (redirectResponse) {
3987
+ if (!responseCommitted) {
3988
+ const location = redirectResponse.headers?.get?.("Location");
3989
+ if (!isProduction) {
3990
+ console.info("SSR redirect", {
3991
+ method: req.method,
3992
+ url: req.originalUrl,
3993
+ status: redirectResponse.status,
3994
+ location,
3995
+ routeId: redirectRouteId ?? void 0,
3996
+ routePath: redirectRoutePath ?? void 0
3997
+ });
3998
+ }
3999
+ responseCommitted = true;
4000
+ handleRedirectionResponse(res, redirectResponse, location);
4001
+ }
4002
+ return;
4003
+ }
3878
4004
  if (!isMatched) {
3879
4005
  next();
3880
4006
  return;
@@ -2,8 +2,11 @@ import { ReactNode } from 'react';
2
2
  import { StaticHandler } from '../../router/src';
3
3
  import * as express from "express";
4
4
  export declare function renderSSR(req: express.Request, dataRoutes: StaticHandler["dataRoutes"]): Promise<{
5
- element: ReactNode;
5
+ element: ReactNode | null;
6
6
  isMatched: boolean;
7
7
  statusCode: number;
8
+ redirectResponse?: Response;
9
+ redirectRouteId?: string | null;
10
+ redirectRoutePath?: string | null;
8
11
  }>;
9
12
  //# sourceMappingURL=renderSSR.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderSSR.d.ts","sourceRoot":"","sources":["../src/renderSSR.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,SAAS,EAAc,MAAM,OAAO,CAAA;AAC7C,OAAO,EAGL,aAAa,EACd,MAAM,iBAAiB,CAAA;AAOxB,wBAAsB,SAAS,CAC7B,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,GACtC,OAAO,CAAC;IAAC,OAAO,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAC,CAAC,CAuCvE"}
1
+ {"version":3,"file":"renderSSR.d.ts","sourceRoot":"","sources":["../src/renderSSR.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,SAAS,EAAc,MAAM,OAAO,CAAA;AAC7C,OAAO,EAGL,aAAa,EACd,MAAM,iBAAiB,CAAA;AAOxB,wBAAsB,SAAS,CAC7B,GAAG,EAAE,OAAO,CAAC,OAAO,EACpB,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,GACtC,OAAO,CAAC;IACT,OAAO,EAAE,SAAS,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,QAAQ,CAAA;IAC3B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC,CAAC,CAwED"}
@@ -1 +1 @@
1
- {"version":3,"file":"ssrMiddleware.d.ts","sourceRoot":"","sources":["../src/ssrMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAwE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACjI,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAErE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAM3C,KAAK,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAwF9D,eAAO,MAAM,aAAa,GAAI,kFAM3B;IACD,YAAY,EAAE,aAAa,CAAC;IAC5B,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACxC,mBAAmB,CAAC,EAAE,aAAa,CAAC;QAAE,KAAK,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAC,CAAC;IACtE,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B,MAAW,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBA8I1D,CAAA"}
1
+ {"version":3,"file":"ssrMiddleware.d.ts","sourceRoot":"","sources":["../src/ssrMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAwE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACjI,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,OAAO,CAAA;AAErE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAM3C,KAAK,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AAkI9D,eAAO,MAAM,aAAa,GAAI,kFAM3B;IACD,YAAY,EAAE,aAAa,CAAC;IAC5B,UAAU,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC;IACxC,mBAAmB,CAAC,EAAE,aAAa,CAAC;QAAE,KAAK,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAC,CAAC;IACtE,mBAAmB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC1B,MAAW,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,kBA0K1D,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/server",
3
- "version": "0.462.0",
3
+ "version": "0.463.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"