@nifrajs/web 3.5.0 → 4.0.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/backend-only.d.ts +15 -0
- package/dist/backend-only.d.ts.map +1 -0
- package/dist/backend-only.js +16 -0
- package/dist/backend-only.js.map +1 -0
- package/dist/boundary.d.ts +5 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +11 -2
- package/dist/boundary.js.map +1 -1
- package/dist/build-plan.d.ts +18 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +82 -34
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +4 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +68 -13
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +57 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +356 -85
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +1 -1
- package/dist/bun-dev-entry.js +1 -1
- package/dist/cdn.d.ts +74 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +132 -0
- package/dist/cdn.js.map +1 -0
- package/dist/client.d.ts +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +111 -15
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +4 -3
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +12 -3
- package/dist/conformance.js.map +1 -1
- package/dist/csp.d.ts +93 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +122 -0
- package/dist/csp.js.map +1 -0
- package/dist/dev-client.d.ts +76 -0
- package/dist/dev-client.d.ts.map +1 -0
- package/dist/dev-client.js +280 -0
- package/dist/dev-client.js.map +1 -0
- package/dist/dev-diagnostics.d.ts +26 -2
- package/dist/dev-diagnostics.d.ts.map +1 -1
- package/dist/dev-diagnostics.js +38 -7
- package/dist/dev-diagnostics.js.map +1 -1
- package/dist/dev-error.d.ts +4 -13
- package/dist/dev-error.d.ts.map +1 -1
- package/dist/dev-error.js +46 -4
- package/dist/dev-error.js.map +1 -1
- package/dist/dev-feed.d.ts +298 -0
- package/dist/dev-feed.d.ts.map +1 -0
- package/dist/dev-feed.js +797 -0
- package/dist/dev-feed.js.map +1 -0
- package/dist/dev-indicator.d.ts +38 -0
- package/dist/dev-indicator.d.ts.map +1 -0
- package/dist/dev-indicator.js +93 -0
- package/dist/dev-indicator.js.map +1 -0
- package/dist/dev-session.d.ts +77 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +580 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-sourcemap.d.ts +27 -0
- package/dist/dev-sourcemap.d.ts.map +1 -0
- package/dist/dev-sourcemap.js +235 -0
- package/dist/dev-sourcemap.js.map +1 -0
- package/dist/dev-ssr-graph.d.ts.map +1 -1
- package/dist/dev-ssr-graph.js +13 -5
- package/dist/dev-ssr-graph.js.map +1 -1
- package/dist/dev.d.ts +15 -2
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +69 -44
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic-catalog.d.ts +35 -0
- package/dist/diagnostic-catalog.d.ts.map +1 -0
- package/dist/diagnostic-catalog.js +203 -0
- package/dist/diagnostic-catalog.js.map +1 -0
- package/dist/diagnostic-prompt.d.ts +49 -0
- package/dist/diagnostic-prompt.d.ts.map +1 -0
- package/dist/diagnostic-prompt.js +303 -0
- package/dist/diagnostic-prompt.js.map +1 -0
- package/dist/diagnostic.d.ts +17 -23
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +31 -39
- package/dist/diagnostic.js.map +1 -1
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +4 -1
- package/dist/draft.js.map +1 -1
- package/dist/fn.d.ts +5 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +13 -3
- package/dist/fn.js.map +1 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +4 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +13 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cdn/cloudflare.d.ts +48 -0
- package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
- package/dist/internal/cdn/cloudflare.js +94 -0
- package/dist/internal/cdn/cloudflare.js.map +1 -0
- package/dist/internal/cdn/core.d.ts +91 -0
- package/dist/internal/cdn/core.d.ts.map +1 -0
- package/dist/internal/cdn/core.js +220 -0
- package/dist/internal/cdn/core.js.map +1 -0
- package/dist/internal/cdn/fastly.d.ts +17 -0
- package/dist/internal/cdn/fastly.d.ts.map +1 -0
- package/dist/internal/cdn/fastly.js +43 -0
- package/dist/internal/cdn/fastly.js.map +1 -0
- package/dist/internal/cdn/vercel.d.ts +31 -0
- package/dist/internal/cdn/vercel.d.ts.map +1 -0
- package/dist/internal/cdn/vercel.js +56 -0
- package/dist/internal/cdn/vercel.js.map +1 -0
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +141 -25
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/dev-csp.d.ts +24 -0
- package/dist/internal/dev-csp.d.ts.map +1 -0
- package/dist/internal/dev-csp.js +160 -0
- package/dist/internal/dev-csp.js.map +1 -0
- package/dist/internal/dev-reserved.d.ts +25 -0
- package/dist/internal/dev-reserved.d.ts.map +1 -0
- package/dist/internal/dev-reserved.js +39 -0
- package/dist/internal/dev-reserved.js.map +1 -0
- package/dist/internal/html-spans.d.ts +10 -0
- package/dist/internal/html-spans.d.ts.map +1 -0
- package/dist/internal/html-spans.js +46 -0
- package/dist/internal/html-spans.js.map +1 -0
- package/dist/internal/loading-runtime.d.ts +44 -0
- package/dist/internal/loading-runtime.d.ts.map +1 -0
- package/dist/internal/loading-runtime.js +206 -0
- package/dist/internal/loading-runtime.js.map +1 -0
- package/dist/internal/matches-runtime.d.ts +8 -0
- package/dist/internal/matches-runtime.d.ts.map +1 -0
- package/dist/internal/matches-runtime.js +61 -0
- package/dist/internal/matches-runtime.js.map +1 -0
- package/dist/internal/mount-shadow.d.ts +36 -0
- package/dist/internal/mount-shadow.d.ts.map +1 -0
- package/dist/internal/mount-shadow.js +65 -0
- package/dist/internal/mount-shadow.js.map +1 -0
- package/dist/internal/node-builtins.d.ts +3 -0
- package/dist/internal/node-builtins.d.ts.map +1 -0
- package/dist/internal/node-builtins.js +79 -0
- package/dist/internal/node-builtins.js.map +1 -0
- package/dist/internal/output-guard.d.ts +65 -0
- package/dist/internal/output-guard.d.ts.map +1 -0
- package/dist/internal/output-guard.js +630 -0
- package/dist/internal/output-guard.js.map +1 -0
- package/dist/internal/page-execution.d.ts +20 -8
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +571 -105
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts +21 -0
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +104 -22
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +12 -0
- package/dist/internal/private-env.d.ts.map +1 -0
- package/dist/internal/private-env.js +335 -0
- package/dist/internal/private-env.js.map +1 -0
- package/dist/internal/public-url.d.ts +3 -0
- package/dist/internal/public-url.d.ts.map +1 -0
- package/dist/internal/public-url.js +18 -0
- package/dist/internal/public-url.js.map +1 -0
- package/dist/internal/render-document.d.ts +25 -19
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +77 -61
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/response-controls.d.ts +75 -0
- package/dist/internal/response-controls.d.ts.map +1 -0
- package/dist/internal/response-controls.js +256 -0
- package/dist/internal/response-controls.js.map +1 -0
- package/dist/internal/runtime-contract.d.ts +11 -16
- package/dist/internal/runtime-contract.d.ts.map +1 -1
- package/dist/internal/runtime-contract.js.map +1 -1
- package/dist/internal/secret-scan.d.ts +104 -0
- package/dist/internal/secret-scan.d.ts.map +1 -0
- package/dist/internal/secret-scan.js +485 -0
- package/dist/internal/secret-scan.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +6 -29
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +6 -66
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/source-map.d.ts +47 -0
- package/dist/internal/source-map.d.ts.map +1 -0
- package/dist/internal/source-map.js +225 -0
- package/dist/internal/source-map.js.map +1 -0
- package/dist/internal/target-compat.d.ts +27 -0
- package/dist/internal/target-compat.d.ts.map +1 -0
- package/dist/internal/target-compat.js +77 -0
- package/dist/internal/target-compat.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +6 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -1
- package/dist/internal/timing-safe-equal.js +10 -0
- package/dist/internal/timing-safe-equal.js.map +1 -1
- package/dist/internal/vitals-runtime.d.ts +18 -0
- package/dist/internal/vitals-runtime.d.ts.map +1 -0
- package/dist/internal/vitals-runtime.js +38 -0
- package/dist/internal/vitals-runtime.js.map +1 -0
- package/dist/internal/web-app.d.ts +38 -9
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +199 -59
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/zone-graph.d.ts +89 -0
- package/dist/internal/zone-graph.d.ts.map +1 -0
- package/dist/internal/zone-graph.js +492 -0
- package/dist/internal/zone-graph.js.map +1 -0
- package/dist/isr.d.ts +83 -6
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +263 -36
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +31 -6
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +220 -13
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +386 -9
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +26 -5
- package/dist/module-graph.d.ts.map +1 -1
- package/dist/module-graph.js +17 -7
- package/dist/module-graph.js.map +1 -1
- package/dist/navigation.d.ts +17 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +11 -5
- package/dist/navigation.js.map +1 -1
- package/dist/nonce.d.ts +47 -0
- package/dist/nonce.d.ts.map +1 -0
- package/dist/nonce.js +63 -0
- package/dist/nonce.js.map +1 -0
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +4 -3
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +9 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +18 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/svg.d.ts +22 -3
- package/dist/plugins/svg.d.ts.map +1 -1
- package/dist/plugins/svg.js +112 -1
- package/dist/plugins/svg.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +107 -3
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +378 -17
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts +61 -0
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
- package/dist/plugins/vite-zone-guard.js +208 -0
- package/dist/plugins/vite-zone-guard.js.map +1 -0
- package/dist/plugins/zone-guard.d.ts +27 -0
- package/dist/plugins/zone-guard.d.ts.map +1 -0
- package/dist/plugins/zone-guard.js +72 -0
- package/dist/plugins/zone-guard.js.map +1 -0
- package/dist/prerender.d.ts +10 -0
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +35 -3
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +9 -0
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +67 -9
- package/dist/public-dir.js.map +1 -1
- package/dist/pwa-manifest.d.ts +68 -0
- package/dist/pwa-manifest.d.ts.map +1 -0
- package/dist/pwa-manifest.js +178 -0
- package/dist/pwa-manifest.js.map +1 -0
- package/dist/query.js +4 -4
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +42 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +6 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-manifest.d.ts +3 -0
- package/dist/route-manifest.d.ts.map +1 -1
- package/dist/route-manifest.js +26 -3
- package/dist/route-manifest.js.map +1 -1
- package/dist/route-types.d.ts +24 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +195 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router.d.ts +28 -6
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +129 -40
- package/dist/router.js.map +1 -1
- package/dist/vitals.d.ts +33 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +16 -0
- package/dist/vitals.js.map +1 -0
- package/dist/vite.d.ts +16 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +187 -36
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +70 -0
- package/dist/zones.d.ts.map +1 -0
- package/dist/zones.js +365 -0
- package/dist/zones.js.map +1 -0
- package/package.json +67 -14
- package/src/backend-only.ts +16 -0
- package/src/boundary.ts +18 -5
- package/src/build-plan.ts +90 -35
- package/src/build-vite.ts +77 -11
- package/src/build.ts +466 -96
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +111 -18
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -0
- package/src/dev-client.ts +349 -0
- package/src/dev-diagnostics.ts +60 -7
- package/src/dev-error.ts +49 -5
- package/src/dev-feed.ts +1140 -0
- package/src/dev-indicator.ts +113 -0
- package/src/dev-session.ts +729 -0
- package/src/dev-sourcemap.ts +281 -0
- package/src/dev-ssr-graph.ts +16 -5
- package/src/dev.ts +86 -43
- package/src/diagnostic-catalog.ts +243 -0
- package/src/diagnostic-prompt.ts +355 -0
- package/src/diagnostic.ts +63 -66
- package/src/draft.ts +4 -1
- package/src/fn.ts +19 -3
- package/src/fs.ts +13 -2
- package/src/index.ts +42 -10
- package/src/internal/cdn/cloudflare.ts +146 -0
- package/src/internal/cdn/core.ts +316 -0
- package/src/internal/cdn/fastly.ts +62 -0
- package/src/internal/cdn/vercel.ts +95 -0
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/codegen.ts +161 -24
- package/src/internal/dev-csp.ts +176 -0
- package/src/internal/dev-reserved.ts +52 -0
- package/src/internal/html-spans.ts +50 -0
- package/src/internal/loading-runtime.ts +244 -0
- package/src/internal/matches-runtime.ts +62 -0
- package/src/internal/mount-shadow.ts +88 -0
- package/src/internal/node-builtins.ts +79 -0
- package/src/internal/output-guard.ts +701 -0
- package/src/internal/page-execution.ts +795 -185
- package/src/internal/parity.ts +150 -36
- package/src/internal/private-env.ts +334 -0
- package/src/internal/public-url.ts +22 -0
- package/src/internal/render-document.ts +114 -67
- package/src/internal/response-controls.ts +284 -0
- package/src/internal/runtime-contract.ts +10 -15
- package/src/internal/secret-scan.ts +623 -0
- package/src/internal/server-boundary.ts +6 -69
- package/src/internal/source-map.ts +256 -0
- package/src/internal/target-compat.ts +92 -0
- package/src/internal/timing-safe-equal.ts +13 -0
- package/src/internal/vitals-runtime.ts +53 -0
- package/src/internal/web-app.ts +262 -84
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +325 -37
- package/src/llms-txt.ts +28 -4
- package/src/manifest.ts +670 -16
- package/src/module-graph.ts +53 -9
- package/src/navigation.ts +29 -6
- package/src/nonce.ts +104 -0
- package/src/plugins/css-modules.ts +5 -3
- package/src/plugins/kit.ts +24 -0
- package/src/plugins/svg.ts +135 -4
- package/src/plugins/vite-leak-guard.ts +529 -16
- package/src/plugins/vite-zone-guard.ts +263 -0
- package/src/plugins/zone-guard.ts +110 -0
- package/src/prerender.ts +49 -3
- package/src/public-dir.ts +69 -8
- package/src/pwa-manifest.ts +226 -0
- package/src/query.ts +4 -4
- package/src/render-seam.ts +46 -1
- package/src/route-manifest.ts +15 -3
- package/src/route-types.ts +264 -0
- package/src/router.ts +163 -63
- package/src/vitals.ts +44 -0
- package/src/vite.ts +207 -36
- package/src/zones.ts +430 -0
- package/dist/plugins/vite-server-only.d.ts +0 -49
- package/dist/plugins/vite-server-only.d.ts.map +0 -1
- package/dist/plugins/vite-server-only.js +0 -57
- package/dist/plugins/vite-server-only.js.map +0 -1
- package/dist/server-only.d.ts +0 -21
- package/dist/server-only.d.ts.map +0 -1
- package/dist/server-only.js +0 -25
- package/dist/server-only.js.map +0 -1
- package/src/plugins/vite-server-only.ts +0 -75
- package/src/server-only.ts +0 -25
|
@@ -1,12 +1,101 @@
|
|
|
1
|
+
import { NIFRA_BACKEND_BIND_PLATFORM, NIFRA_PLATFORM_CLIENT_IP_DERIVED, } from "@nifrajs/core/mount";
|
|
2
|
+
import { status as statusResult } from "@nifrajs/core/server";
|
|
1
3
|
import { boundaryDescriptors, resolveStaticBoundaries, startDynamicBoundaries, } from "../boundary.js";
|
|
2
4
|
import { DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.js";
|
|
3
5
|
import { defer, ndjsonStream, prepareDeferred } from "../deferred.js";
|
|
4
6
|
import { isDraftEnabled } from "../draft.js";
|
|
5
|
-
import {
|
|
7
|
+
import { routeTags } from "../isr.js";
|
|
8
|
+
import { createMatcher, DATA_HEADER, NAV_FROM_HEADER, REDIRECT_HEADER, RETAIN_HEADER, REVALIDATE_HEADER, STATUS_HEADER, } from "../router.js";
|
|
6
9
|
import { searchOf, searchOfChain } from "../search.js";
|
|
7
10
|
import { mergeHeads, resolveMeta } from "./head-merge.js";
|
|
8
|
-
import {
|
|
11
|
+
import { EMPTY_RETAIN, isControlFlow, isResponseResult, isStatusSignal, layoutErrorId, renderPageResult, STATUS_SIGNAL, STATUS_TEXT, scopeParams, tagLayoutError, withDuplicateInstanceHint, } from "./render-document.js";
|
|
9
12
|
import { urlPartsFor } from "./request-url.js";
|
|
13
|
+
import { ACTION_SCOPE, DATA_RESPONSE_HEADERS, MIDDLEWARE_SCOPE, PAGE_SCOPE, PageResponseControls, PRIVATE_NO_STORE, PRIVATE_PAGE_HEADERS, privateOutcome, varyOnDataHeader, } from "./response-controls.js";
|
|
14
|
+
/** The page slot of an `ssr = false` route with no `HydrateFallback`: a component that renders nothing. */
|
|
15
|
+
const EMPTY_LEAF = () => null;
|
|
16
|
+
/** The request-scoping seam of an in-process `api` (see `inProcessClient`), if it carries one. */
|
|
17
|
+
function platformBinderOf(api) {
|
|
18
|
+
if ((typeof api !== "object" && typeof api !== "function") || api === null)
|
|
19
|
+
return undefined;
|
|
20
|
+
const bind = api[NIFRA_BACKEND_BIND_PLATFORM];
|
|
21
|
+
return typeof bind === "function" ? bind : undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The page request's platform identity for loader calls: `clientIp` (read lazily, so a backend that
|
|
25
|
+
* never reads `c.clientIp` never pays the socket lookup), `env` and `waitUntil` - and nothing else. The
|
|
26
|
+
* page request's headers (cookie, authorization) never travel; a loader passes them explicitly if it
|
|
27
|
+
* wants an authenticated call. The prototype marker tells the backend its `clientIp` is already derived,
|
|
28
|
+
* so its own trust declaration does not re-read forwarding headers the synthesized request does not
|
|
29
|
+
* carry; a spread copy drops both the getter and the marker, so it falls back to re-deriving.
|
|
30
|
+
*/
|
|
31
|
+
class LoaderPlatform {
|
|
32
|
+
env;
|
|
33
|
+
waitUntil;
|
|
34
|
+
#c;
|
|
35
|
+
constructor(c) {
|
|
36
|
+
this.#c = c;
|
|
37
|
+
this.env = c.env;
|
|
38
|
+
this.waitUntil = c.waitUntil;
|
|
39
|
+
}
|
|
40
|
+
get clientIp() {
|
|
41
|
+
return this.#c.clientIp;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
;
|
|
45
|
+
LoaderPlatform.prototype[NIFRA_PLATFORM_CLIENT_IP_DERIVED] = true;
|
|
46
|
+
/**
|
|
47
|
+
* A redirect answering a data request (a client navigation or form submit), as the client router reads
|
|
48
|
+
* one: fetch would follow a 3xx and hand the router another page's data, so the target rides
|
|
49
|
+
* `x-nifra-redirect` on a 204 that keeps the redirect's own headers. `undefined` for any other outcome
|
|
50
|
+
* or request.
|
|
51
|
+
*/
|
|
52
|
+
function dataRedirect(req, outcome) {
|
|
53
|
+
if (req.headers.get(DATA_HEADER) === null)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (isResponseResult(outcome)) {
|
|
56
|
+
const plain = outcome.plain;
|
|
57
|
+
if (plain === undefined)
|
|
58
|
+
return dataRedirect(req, outcome.toResponse());
|
|
59
|
+
if (plain.status < 300 || plain.status >= 400)
|
|
60
|
+
return undefined;
|
|
61
|
+
const { location, ...own } = plain.headers ?? {};
|
|
62
|
+
if (location === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
return statusResult(204, undefined, {
|
|
65
|
+
headers: {
|
|
66
|
+
...own,
|
|
67
|
+
[REDIRECT_HEADER]: appTarget(location, req.url),
|
|
68
|
+
...DATA_RESPONSE_HEADERS,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (!(outcome instanceof Response) || outcome.status < 300 || outcome.status >= 400) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const location = outcome.headers.get("location");
|
|
76
|
+
if (location === null)
|
|
77
|
+
return undefined;
|
|
78
|
+
const headers = new Headers(outcome.headers);
|
|
79
|
+
headers.delete("location");
|
|
80
|
+
headers.delete("content-length");
|
|
81
|
+
headers.set(REDIRECT_HEADER, appTarget(location, req.url));
|
|
82
|
+
for (const [name, value] of Object.entries(DATA_RESPONSE_HEADERS))
|
|
83
|
+
headers.set(name, value);
|
|
84
|
+
return new Response(null, { status: 204, headers });
|
|
85
|
+
}
|
|
86
|
+
/** A same-origin target as a path, which the client router can follow without loading a document. */
|
|
87
|
+
function appTarget(location, requestUrl) {
|
|
88
|
+
let target;
|
|
89
|
+
try {
|
|
90
|
+
target = new URL(location, requestUrl);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return location; // the browser fails on it the same way when it loads it
|
|
94
|
+
}
|
|
95
|
+
return target.origin === new URL(requestUrl).origin
|
|
96
|
+
? target.pathname + target.search + target.hash
|
|
97
|
+
: location;
|
|
98
|
+
}
|
|
10
99
|
/**
|
|
11
100
|
* Own the page request program behind one private seam.
|
|
12
101
|
*
|
|
@@ -18,11 +107,56 @@ import { urlPartsFor } from "./request-url.js";
|
|
|
18
107
|
*/
|
|
19
108
|
export function createPageRequestExecutor(options) {
|
|
20
109
|
const { adapter, manifest, clientEntry, api } = options;
|
|
110
|
+
// Bound once per render, so every loader, action and boundary in that render shares one view of the
|
|
111
|
+
// in-process client carrying the visitor's platform. An `api` without the seam passes through as-is.
|
|
112
|
+
const bindApi = platformBinderOf(api);
|
|
113
|
+
// One view per render, shared by every loader, action and boundary it runs.
|
|
114
|
+
const apiFor = (c) => bindApi === undefined ? api : bindApi.call(api, new LoaderPlatform(c));
|
|
21
115
|
const cssLoading = normalizeCssLoading(options.cssLoading ?? DEFAULT_CSS_LOADING);
|
|
22
116
|
const titleOption = options.title === undefined ? {} : { title: options.title };
|
|
117
|
+
const cspOption = options.csp === undefined ? {} : { csp: options.csp };
|
|
23
118
|
const prerenderedSet = new Set(options.prerenderedPaths ?? []);
|
|
119
|
+
const prerenderedHandover = options.prerenderedListUrl === undefined
|
|
120
|
+
? { prerenderedPaths: options.prerenderedPaths ?? [] }
|
|
121
|
+
: { prerenderedListUrl: options.prerenderedListUrl };
|
|
24
122
|
const matchManifestRoute = createMatcher(manifest.routes.map((route) => ({ routeId: route.id, pattern: route.pattern })));
|
|
25
123
|
const routeById = new Map(manifest.routes.map((route) => [route.id, route]));
|
|
124
|
+
// A nested `_404` answers an unmatched URL from inside the catch-all, never as a route of its own:
|
|
125
|
+
// a registered `/admin/*` would take URLs from a route the router only reaches by backtracking.
|
|
126
|
+
// Param names are indexed by position, so two directories that name one segment differently share
|
|
127
|
+
// the index; each target keeps its own names to read a match back.
|
|
128
|
+
const notFoundTargets = [];
|
|
129
|
+
const notFoundPatterns = [];
|
|
130
|
+
for (const [id, page] of Object.entries(manifest.notFounds ?? {})) {
|
|
131
|
+
for (const scope of page.scopes) {
|
|
132
|
+
const names = [];
|
|
133
|
+
const pattern = scope.pattern.replace(/([:*])([A-Za-z_][A-Za-z0-9_]*)/g, (_match, sigil, name) => `${sigil}p${names.push(name) - 1}`);
|
|
134
|
+
notFoundPatterns.push({ routeId: String(notFoundTargets.length), pattern });
|
|
135
|
+
notFoundTargets.push({
|
|
136
|
+
id,
|
|
137
|
+
page,
|
|
138
|
+
names,
|
|
139
|
+
route: {
|
|
140
|
+
id,
|
|
141
|
+
pattern: scope.pattern,
|
|
142
|
+
layoutIds: page.layoutIds,
|
|
143
|
+
layoutParams: scope.layoutParams,
|
|
144
|
+
...(page.middlewareIds === undefined
|
|
145
|
+
? {}
|
|
146
|
+
: {
|
|
147
|
+
middlewareIds: page.middlewareIds,
|
|
148
|
+
...(scope.middlewareParams === undefined
|
|
149
|
+
? {}
|
|
150
|
+
: { middlewareParams: scope.middlewareParams }),
|
|
151
|
+
}),
|
|
152
|
+
errorIds: page.errorIds,
|
|
153
|
+
file: page.file,
|
|
154
|
+
load: page.load,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const matchNotFoundScope = notFoundPatterns.length === 0 ? undefined : createMatcher(notFoundPatterns);
|
|
26
160
|
const draftFlag = (req) => options.draftSecret === undefined
|
|
27
161
|
? Promise.resolve(false)
|
|
28
162
|
: isDraftEnabled(req, options.draftSecret);
|
|
@@ -53,26 +187,46 @@ export function createPageRequestExecutor(options) {
|
|
|
53
187
|
}
|
|
54
188
|
return modules;
|
|
55
189
|
};
|
|
190
|
+
// No modules, data, or per-request state: reuse the settled result for page-only routes.
|
|
191
|
+
const emptyLayoutRun = {
|
|
192
|
+
modules: [],
|
|
193
|
+
layoutData: undefined,
|
|
194
|
+
retained: [],
|
|
195
|
+
pending: undefined,
|
|
196
|
+
};
|
|
56
197
|
/** Run layout loaders, awaiting gates before starting the page loader and retaining only safe data. */
|
|
57
|
-
const runLayoutChain = async (route, ctx, retain
|
|
198
|
+
const runLayoutChain = async (route, ctx, controls, retain) => {
|
|
199
|
+
if (route.layoutIds.length === 0)
|
|
200
|
+
return emptyLayoutRun;
|
|
58
201
|
const modules = await loadLayoutModules(route);
|
|
59
202
|
if (!modules.some((m) => m.loader !== undefined)) {
|
|
60
|
-
return { modules, layoutData: undefined, retained: [], pending:
|
|
203
|
+
return { modules, layoutData: undefined, retained: [], pending: undefined };
|
|
61
204
|
}
|
|
62
205
|
const results = new Array(modules.length).fill(null);
|
|
63
206
|
const retained = [];
|
|
64
207
|
const pending = [];
|
|
65
208
|
for (let i = 0; i < modules.length; i++) {
|
|
66
|
-
const
|
|
209
|
+
const layout = modules[i];
|
|
210
|
+
const loader = layout.loader;
|
|
67
211
|
if (loader === undefined)
|
|
68
212
|
continue;
|
|
69
|
-
if (retain
|
|
70
|
-
|
|
71
|
-
|
|
213
|
+
if (retain !== undefined && layout.gate !== true) {
|
|
214
|
+
let keep;
|
|
215
|
+
try {
|
|
216
|
+
keep = keepsLayoutData(route, i, layout, ctx.params, retain);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
throw tagLayoutError(err, route.layoutIds[i]);
|
|
220
|
+
}
|
|
221
|
+
if (keep) {
|
|
222
|
+
retained.push(i);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
72
225
|
}
|
|
73
226
|
const scoped = {
|
|
74
227
|
...ctx,
|
|
75
228
|
params: scopeParams(ctx.params, route.layoutParams?.[i]),
|
|
229
|
+
set: controls.scope(i),
|
|
76
230
|
};
|
|
77
231
|
if (modules[i]?.gate === true) {
|
|
78
232
|
try {
|
|
@@ -85,19 +239,37 @@ export function createPageRequestExecutor(options) {
|
|
|
85
239
|
}
|
|
86
240
|
const layoutId = route.layoutIds[i];
|
|
87
241
|
const index = i;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
242
|
+
try {
|
|
243
|
+
const value = loader(scoped);
|
|
244
|
+
// Scalars settle synchronously. Objects keep await's intrinsic promise/thenable
|
|
245
|
+
// assimilation: property-presence and prototype probes are unsafe for proxies.
|
|
246
|
+
if (value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
247
|
+
pending.push((async () => {
|
|
248
|
+
try {
|
|
249
|
+
results[index] = await value;
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
throw tagLayoutError(err, layoutId);
|
|
253
|
+
}
|
|
254
|
+
})());
|
|
94
255
|
}
|
|
95
|
-
|
|
256
|
+
else
|
|
257
|
+
results[index] = value;
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
// Preserve a synchronous throw as a pending layout failure, just like the async lane.
|
|
261
|
+
pending.push(Promise.reject(tagLayoutError(err, layoutId)));
|
|
262
|
+
}
|
|
96
263
|
}
|
|
97
|
-
return {
|
|
264
|
+
return {
|
|
265
|
+
modules,
|
|
266
|
+
layoutData: results,
|
|
267
|
+
retained,
|
|
268
|
+
pending: pending.length === 0 ? undefined : pending.length === 1 ? pending[0] : Promise.all(pending),
|
|
269
|
+
};
|
|
98
270
|
};
|
|
99
271
|
/** Run only authorization gates before an action; ordinary layout data runs after a native action. */
|
|
100
|
-
const runLayoutGates = async (route, ctx) => {
|
|
272
|
+
const runLayoutGates = async (route, ctx, controls) => {
|
|
101
273
|
const modules = await loadLayoutModules(route);
|
|
102
274
|
for (let i = 0; i < modules.length; i++) {
|
|
103
275
|
const mod = modules[i];
|
|
@@ -107,6 +279,7 @@ export function createPageRequestExecutor(options) {
|
|
|
107
279
|
await mod.loader({
|
|
108
280
|
...ctx,
|
|
109
281
|
params: scopeParams(ctx.params, route.layoutParams?.[i]),
|
|
282
|
+
set: controls.scope(i),
|
|
110
283
|
});
|
|
111
284
|
}
|
|
112
285
|
catch (err) {
|
|
@@ -115,6 +288,38 @@ export function createPageRequestExecutor(options) {
|
|
|
115
288
|
}
|
|
116
289
|
return modules;
|
|
117
290
|
};
|
|
291
|
+
/** Outermost first; a failure carries its directory so the nearest `_error` above it renders. */
|
|
292
|
+
const runMiddleware = async (route, ctx, controls) => {
|
|
293
|
+
const ids = route.middlewareIds ?? [];
|
|
294
|
+
for (let i = 0; i < ids.length; i++) {
|
|
295
|
+
const id = ids[i];
|
|
296
|
+
const entry = manifest.middlewares?.[id];
|
|
297
|
+
const dirTag = `${id.slice(0, id.length - "_middleware".length)}_layout`;
|
|
298
|
+
let outcome;
|
|
299
|
+
try {
|
|
300
|
+
const middleware = (await entry.load()).default;
|
|
301
|
+
// A `_layout.backend.ts` that exports no middleware is a layout data module, nothing more.
|
|
302
|
+
if (middleware === undefined)
|
|
303
|
+
continue;
|
|
304
|
+
if (typeof middleware !== "function") {
|
|
305
|
+
throw new Error(`[nifra/web] "${entry.file}" exports a middleware that is not a function.`);
|
|
306
|
+
}
|
|
307
|
+
outcome = await middleware({
|
|
308
|
+
...ctx,
|
|
309
|
+
params: scopeParams(ctx.params, route.middlewareParams?.[i]),
|
|
310
|
+
set: controls.scope(MIDDLEWARE_SCOPE + i),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
throw tagLayoutError(err, dirTag);
|
|
315
|
+
}
|
|
316
|
+
if (outcome === undefined)
|
|
317
|
+
continue;
|
|
318
|
+
throw tagLayoutError(isControlFlow(outcome)
|
|
319
|
+
? outcome
|
|
320
|
+
: new Error(`[nifra/web] the middleware in "${entry.file}" returned a value. Middleware returns nothing to let the request through, or a redirect(), a status such as notFound(), or a Response to answer it.`), dirTag);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
118
323
|
const retainedIndices = (header) => {
|
|
119
324
|
if (header === null || header === "")
|
|
120
325
|
return EMPTY_RETAIN;
|
|
@@ -126,55 +331,88 @@ export function createPageRequestExecutor(options) {
|
|
|
126
331
|
}
|
|
127
332
|
return out;
|
|
128
333
|
};
|
|
129
|
-
|
|
334
|
+
/**
|
|
335
|
+
* The browser's side of a client navigation: the page it is leaving and the layout slots it asked
|
|
336
|
+
* to keep. Undefined unless this is a data request whose `from` is same-origin and routable.
|
|
337
|
+
*/
|
|
338
|
+
const retainContextOf = (req) => {
|
|
130
339
|
if (req.headers.get(DATA_HEADER) === null)
|
|
131
|
-
return
|
|
340
|
+
return undefined;
|
|
132
341
|
const requested = retainedIndices(req.headers.get(RETAIN_HEADER));
|
|
133
342
|
const from = req.headers.get(NAV_FROM_HEADER);
|
|
134
343
|
if (requested.size === 0 || from === null)
|
|
135
|
-
return
|
|
136
|
-
let
|
|
344
|
+
return undefined;
|
|
345
|
+
let toUrl;
|
|
346
|
+
let fromUrl;
|
|
137
347
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (source.origin !== current.origin)
|
|
141
|
-
return EMPTY_RETAIN;
|
|
142
|
-
fromPath = source.pathname + source.search;
|
|
348
|
+
toUrl = new URL(req.url);
|
|
349
|
+
fromUrl = new URL(from, toUrl);
|
|
143
350
|
}
|
|
144
351
|
catch {
|
|
145
|
-
return
|
|
352
|
+
return undefined;
|
|
146
353
|
}
|
|
147
|
-
|
|
354
|
+
if (fromUrl.origin !== toUrl.origin)
|
|
355
|
+
return undefined;
|
|
356
|
+
const previousMatch = matchManifestRoute(fromUrl.pathname + fromUrl.search);
|
|
148
357
|
if (previousMatch === null)
|
|
149
|
-
return
|
|
358
|
+
return undefined;
|
|
150
359
|
const previous = routeById.get(previousMatch.routeId);
|
|
151
360
|
if (previous === undefined)
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
361
|
+
return undefined;
|
|
362
|
+
return { requested, from: previous, fromParams: previousMatch.params, fromUrl, toUrl };
|
|
363
|
+
};
|
|
364
|
+
/** A query change re-runs layout loaders by default: any can read it, and none declares keys. */
|
|
365
|
+
const keepsLayoutData = (route, index, layout, params, retain) => {
|
|
366
|
+
if (!retain.requested.has(index))
|
|
367
|
+
return false;
|
|
368
|
+
const previous = retain.from;
|
|
369
|
+
if (route.layoutIds[index] !== previous.layoutIds[index])
|
|
370
|
+
return false;
|
|
371
|
+
const owned = route.layoutParams?.[index] ?? [];
|
|
372
|
+
const previouslyOwned = previous.layoutParams?.[index] ?? [];
|
|
373
|
+
const defaultShouldRevalidate = retain.fromUrl.search !== retain.toUrl.search ||
|
|
374
|
+
owned.length !== previouslyOwned.length ||
|
|
375
|
+
owned.some((name, i) => name !== previouslyOwned[i]) ||
|
|
376
|
+
owned.some((name) => params[name] !== retain.fromParams[name]);
|
|
377
|
+
const decide = layout.shouldRevalidate;
|
|
378
|
+
if (typeof decide !== "function")
|
|
379
|
+
return !defaultShouldRevalidate;
|
|
380
|
+
// Copies, so a hook that mutates its arguments cannot reach the request's params or the next
|
|
381
|
+
// layout's view of the navigation.
|
|
382
|
+
const answer = decide({
|
|
383
|
+
currentUrl: new URL(retain.fromUrl),
|
|
384
|
+
nextUrl: new URL(retain.toUrl),
|
|
385
|
+
currentParams: { ...retain.fromParams },
|
|
386
|
+
nextParams: { ...params },
|
|
387
|
+
defaultShouldRevalidate,
|
|
388
|
+
});
|
|
389
|
+
return answer === false;
|
|
167
390
|
};
|
|
168
391
|
const loaderSearch = (searchSchema, request) => searchOf(searchSchema, urlPartsFor(request).search);
|
|
169
392
|
const searchChainOf = (layoutMods, mod, request) => searchOfChain([...layoutMods.map((m) => m.searchSchema), mod.searchSchema], urlPartsFor(request).search);
|
|
170
|
-
|
|
171
|
-
|
|
393
|
+
// `ssr = false`: the slot holds `HydrateFallback`, which the browser hydrates before the page.
|
|
394
|
+
const serverLeafOf = (route, mod) => {
|
|
395
|
+
if (mod.ssr !== false)
|
|
396
|
+
return mod.default;
|
|
397
|
+
if (mod.hydrate === false) {
|
|
398
|
+
throw new Error(`[nifra/web] route "${route.id}" sets both \`ssr = false\` and \`hydrate = false\`: the ` +
|
|
399
|
+
"server skips its component and no client would ever render it. Remove one of the two.");
|
|
400
|
+
}
|
|
401
|
+
return mod.HydrateFallback ?? EMPTY_LEAF;
|
|
402
|
+
};
|
|
403
|
+
const resolveChainAndHead = (layoutModules, page, metaArgs, leaf = page.default) => {
|
|
404
|
+
const chain = [...layoutModules.map((m) => m.default), leaf];
|
|
172
405
|
const heads = [
|
|
173
406
|
...layoutModules.map((m) => resolveMeta(m.meta, metaArgs)),
|
|
174
407
|
resolveMeta(page.meta, metaArgs),
|
|
175
408
|
];
|
|
176
409
|
return { chain, head: mergeHeads(heads) };
|
|
177
410
|
};
|
|
411
|
+
/** What `useMatches` reports for a chain: the layouts' ids and `handle`s, then the page's. */
|
|
412
|
+
const matchChainOf = (layoutIds, layoutModules, pageId, page) => ({
|
|
413
|
+
ids: [...layoutIds, pageId],
|
|
414
|
+
handles: [...layoutModules.map((layout) => layout.handle), page.handle],
|
|
415
|
+
});
|
|
178
416
|
const dirOfId = (id, suffix) => id === suffix ? "" : id.slice(0, id.length - suffix.length - 1);
|
|
179
417
|
const boundaryFor = (route, err) => {
|
|
180
418
|
const ids = route.errorIds ?? [];
|
|
@@ -206,7 +444,7 @@ export function createPageRequestExecutor(options) {
|
|
|
206
444
|
return "/";
|
|
207
445
|
}
|
|
208
446
|
};
|
|
209
|
-
const renderError = async (route, errorId, err, nonce) => {
|
|
447
|
+
const renderError = async (route, errorId, err, personalized, nonce) => {
|
|
210
448
|
const errDir = dirOfId(errorId, "_error");
|
|
211
449
|
const keptLayoutIds = route.layoutIds.filter((id) => {
|
|
212
450
|
const ld = dirOfId(id, "_layout");
|
|
@@ -215,15 +453,21 @@ export function createPageRequestExecutor(options) {
|
|
|
215
453
|
const layouts = await Promise.all(keptLayoutIds.map((id) => manifest.layouts[id].load().then((m) => m.default)));
|
|
216
454
|
const { default: errComp } = await (manifest.errors?.[errorId]).load();
|
|
217
455
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
456
|
+
// An error's text can carry a connection string or a query, so a production page gets neither.
|
|
457
|
+
const production = typeof process === "undefined" || process.env.NODE_ENV === "production";
|
|
218
458
|
return renderPageResult({
|
|
219
459
|
adapter,
|
|
220
460
|
chain: [...layouts, errComp],
|
|
221
|
-
data:
|
|
461
|
+
data: production
|
|
462
|
+
? { name: "Error", message: "Internal Server Error" }
|
|
463
|
+
: { name: e.name, message: e.message },
|
|
222
464
|
clientEntry,
|
|
223
465
|
routeId: errorId,
|
|
224
466
|
status: 500,
|
|
225
467
|
hydrate: false,
|
|
468
|
+
...(personalized ? { headers: PRIVATE_PAGE_HEADERS } : {}),
|
|
226
469
|
...(nonce === undefined ? {} : { nonce }),
|
|
470
|
+
...cspOption,
|
|
227
471
|
...titleOption,
|
|
228
472
|
});
|
|
229
473
|
};
|
|
@@ -242,6 +486,7 @@ export function createPageRequestExecutor(options) {
|
|
|
242
486
|
data: null,
|
|
243
487
|
clientEntry,
|
|
244
488
|
routeId,
|
|
489
|
+
matchChain: { ids: [routeId], handles: [loaded.handle] },
|
|
245
490
|
...(path !== undefined
|
|
246
491
|
? {
|
|
247
492
|
search: searchOf(loaded.searchSchema, path.includes("?") ? path.slice(path.indexOf("?")) : ""),
|
|
@@ -251,20 +496,36 @@ export function createPageRequestExecutor(options) {
|
|
|
251
496
|
status,
|
|
252
497
|
...(extraHeaders !== undefined ? { headers: extraHeaders } : {}),
|
|
253
498
|
...(nonce === undefined ? {} : { nonce }),
|
|
499
|
+
...cspOption,
|
|
254
500
|
...preloadOf(routeId),
|
|
255
501
|
...stylesOf(routeId),
|
|
256
|
-
|
|
502
|
+
...prerenderedHandover,
|
|
257
503
|
...titleOption,
|
|
258
504
|
});
|
|
259
505
|
};
|
|
260
|
-
const renderStatusSignal = async (req, env, signal) => {
|
|
506
|
+
const renderStatusSignal = async (req, env, signal, personalized) => {
|
|
261
507
|
const { status, headers } = signal[STATUS_SIGNAL];
|
|
262
508
|
if (req.headers.get(DATA_HEADER) !== null) {
|
|
509
|
+
// Same URL as the status page, so it is never storable: a cache keyed on the URL alone would
|
|
510
|
+
// otherwise answer a document request with this empty body.
|
|
263
511
|
const responseHeaders = new Headers(headers);
|
|
264
512
|
responseHeaders.set(STATUS_HEADER, String(status));
|
|
513
|
+
responseHeaders.set("cache-control", PRIVATE_NO_STORE);
|
|
514
|
+
responseHeaders.set("vary", varyOnDataHeader(responseHeaders.get("vary")));
|
|
265
515
|
return new Response(null, { status, headers: responseHeaders });
|
|
266
516
|
}
|
|
267
|
-
|
|
517
|
+
const nonce = await resolveNonce(req, env);
|
|
518
|
+
if (headers === undefined && !personalized) {
|
|
519
|
+
return renderTerminalStatus(status, pathOf(req), undefined, nonce);
|
|
520
|
+
}
|
|
521
|
+
const documentHeaders = new Headers(headers);
|
|
522
|
+
if (headers !== undefined) {
|
|
523
|
+
documentHeaders.set("vary", varyOnDataHeader(documentHeaders.get("vary")));
|
|
524
|
+
}
|
|
525
|
+
// A cookie queued before the signal makes the status page specific to this visitor.
|
|
526
|
+
if (personalized)
|
|
527
|
+
documentHeaders.set("cache-control", PRIVATE_NO_STORE);
|
|
528
|
+
return renderTerminalStatus(status, pathOf(req), documentHeaders, nonce);
|
|
268
529
|
};
|
|
269
530
|
const reportLoaderError = (route, req, params, err) => {
|
|
270
531
|
if (options.onLoaderError === undefined)
|
|
@@ -276,9 +537,9 @@ export function createPageRequestExecutor(options) {
|
|
|
276
537
|
// A faulty reporter must never break error rendering.
|
|
277
538
|
}
|
|
278
539
|
};
|
|
279
|
-
const handleDataOrDocument = async (req, env, route, params, mod, data, layoutData, layoutModules, layoutRetained, boundaryStates, assemblyCache) => {
|
|
540
|
+
const handleDataOrDocument = async (req, env, route, params, mod, data, layoutData, layoutModules, layoutRetained, boundaryStates, assemblyCache, responseHeaders, personalized) => {
|
|
280
541
|
if (isStatusSignal(data))
|
|
281
|
-
return renderStatusSignal(req, env, data);
|
|
542
|
+
return renderStatusSignal(req, env, data, personalized);
|
|
282
543
|
if (isControlFlow(data))
|
|
283
544
|
return data;
|
|
284
545
|
if (req.headers.get(DATA_HEADER) !== null) {
|
|
@@ -305,18 +566,23 @@ export function createPageRequestExecutor(options) {
|
|
|
305
566
|
retained: layoutRetained,
|
|
306
567
|
...(boundarySplit === undefined ? {} : { boundaries: boundarySplit.forClient }),
|
|
307
568
|
};
|
|
308
|
-
if (allDeferred.length === 0)
|
|
309
|
-
return Response.json(payload ?? null);
|
|
569
|
+
if (allDeferred.length === 0) {
|
|
570
|
+
return Response.json(payload ?? null, { headers: DATA_RESPONSE_HEADERS });
|
|
571
|
+
}
|
|
310
572
|
return new Response(ndjsonStream(payload, allDeferred), {
|
|
311
|
-
headers: {
|
|
573
|
+
headers: {
|
|
574
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
575
|
+
...DATA_RESPONSE_HEADERS,
|
|
576
|
+
},
|
|
312
577
|
});
|
|
313
578
|
}
|
|
579
|
+
const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env);
|
|
314
580
|
const { chain, head } = resolveChainAndHead(layoutModules, mod, {
|
|
315
581
|
data,
|
|
316
582
|
params,
|
|
317
583
|
origin: originOf(req),
|
|
318
|
-
|
|
319
|
-
|
|
584
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
585
|
+
}, serverLeafOf(route, mod));
|
|
320
586
|
try {
|
|
321
587
|
return await renderPageResult({
|
|
322
588
|
adapter,
|
|
@@ -328,38 +594,168 @@ export function createPageRequestExecutor(options) {
|
|
|
328
594
|
params,
|
|
329
595
|
path: pathOf(req),
|
|
330
596
|
search: searchChainOf(layoutModules, mod, req),
|
|
597
|
+
matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
|
|
331
598
|
hydrate: mod.hydrate !== false,
|
|
332
599
|
...(layoutData !== undefined ? { layoutData } : {}),
|
|
333
600
|
...(boundaryStates !== undefined ? { boundaries: boundaryStates } : {}),
|
|
334
601
|
...preloadOf(route.id),
|
|
335
602
|
...stylesOf(route.id),
|
|
336
|
-
|
|
337
|
-
...(
|
|
338
|
-
|
|
603
|
+
...prerenderedHandover,
|
|
604
|
+
...(responseHeaders === undefined ? {} : { headers: responseHeaders }),
|
|
605
|
+
// A response that sets a cookie is one visitor's: it never advertises ISR freshness.
|
|
606
|
+
...(mod.revalidate !== undefined && !personalized && options.cacheChannel?.enabled === true
|
|
607
|
+
? { revalidate: mod.revalidate }
|
|
608
|
+
: {}),
|
|
609
|
+
...(mod.revalidateTags !== undefined &&
|
|
610
|
+
!personalized &&
|
|
611
|
+
options.cacheChannel?.enabled === true
|
|
612
|
+
? {
|
|
613
|
+
revalidateTags: routeTags(mod.revalidateTags, { params, url: new URL(req.url) }, route.id),
|
|
614
|
+
}
|
|
615
|
+
: {}),
|
|
339
616
|
...(mod.islandScripts !== undefined ? { islandScripts: mod.islandScripts } : {}),
|
|
340
617
|
...(nonce === undefined ? {} : { nonce }),
|
|
618
|
+
...cspOption,
|
|
341
619
|
...titleOption,
|
|
342
620
|
...(assemblyCache === undefined ? {} : { assemblyCache }),
|
|
343
621
|
});
|
|
344
622
|
}
|
|
345
623
|
catch (err) {
|
|
346
624
|
if (isStatusSignal(err))
|
|
347
|
-
return renderStatusSignal(req, env, err);
|
|
625
|
+
return renderStatusSignal(req, env, err, personalized);
|
|
348
626
|
if (isControlFlow(err))
|
|
349
627
|
throw err;
|
|
350
628
|
reportLoaderError(route, req, { ...params }, err);
|
|
351
629
|
const errorId = boundaryFor(route, err);
|
|
352
630
|
if (errorId === undefined)
|
|
353
631
|
throw err;
|
|
354
|
-
return renderError(route, errorId, withDuplicateInstanceHint(err), nonce);
|
|
632
|
+
return renderError(route, errorId, withDuplicateInstanceHint(err), personalized, nonce);
|
|
355
633
|
}
|
|
356
634
|
};
|
|
635
|
+
/** What a failed loader answers with, once a status signal has been ruled out. */
|
|
636
|
+
const renderLoaderFailure = async (route, req, env, params, err, personalized) => {
|
|
637
|
+
if (isControlFlow(err))
|
|
638
|
+
throw err;
|
|
639
|
+
reportLoaderError(route, req, params, err);
|
|
640
|
+
const errorId = boundaryFor(route, err);
|
|
641
|
+
if (errorId === undefined)
|
|
642
|
+
throw err;
|
|
643
|
+
if (req.headers.get(DATA_HEADER) !== null) {
|
|
644
|
+
return new Response("Internal Server Error", {
|
|
645
|
+
status: 500,
|
|
646
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return renderError(route, errorId, withDuplicateInstanceHint(err), personalized, options.nonce === undefined ? undefined : await resolveNonce(req, env));
|
|
650
|
+
};
|
|
651
|
+
/**
|
|
652
|
+
* Render a nested `_404` inside its layouts. `layoutModules` and `layoutData` are exactly the
|
|
653
|
+
* layouts at or above the page, their gates already passed.
|
|
654
|
+
*/
|
|
655
|
+
const renderNestedNotFound = async (req, env, id, page, params, layoutModules, layoutData, personalized, signalHeaders) => {
|
|
656
|
+
// The page can show what a layout loaded for this visitor, and a 404 is storable by default.
|
|
657
|
+
const isPrivate = personalized || layoutData !== undefined;
|
|
658
|
+
if (req.headers.get(DATA_HEADER) !== null) {
|
|
659
|
+
// No status header: the page has no client chunk, so the navigation falls back to loading the
|
|
660
|
+
// URL as a document, which renders it here.
|
|
661
|
+
const headers = new Headers(signalHeaders);
|
|
662
|
+
headers.set("cache-control", PRIVATE_NO_STORE);
|
|
663
|
+
headers.set("vary", varyOnDataHeader(headers.get("vary")));
|
|
664
|
+
return new Response(null, { status: 404, headers });
|
|
665
|
+
}
|
|
666
|
+
const mod = await page.load();
|
|
667
|
+
const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env);
|
|
668
|
+
const { chain, head } = resolveChainAndHead(layoutModules, mod, {
|
|
669
|
+
data: null,
|
|
670
|
+
params,
|
|
671
|
+
origin: originOf(req),
|
|
672
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
673
|
+
});
|
|
674
|
+
let headers;
|
|
675
|
+
if (signalHeaders !== undefined || isPrivate) {
|
|
676
|
+
headers = new Headers(signalHeaders);
|
|
677
|
+
if (signalHeaders !== undefined)
|
|
678
|
+
headers.set("vary", varyOnDataHeader(headers.get("vary")));
|
|
679
|
+
if (isPrivate)
|
|
680
|
+
headers.set("cache-control", PRIVATE_NO_STORE);
|
|
681
|
+
}
|
|
682
|
+
return renderPageResult({
|
|
683
|
+
adapter,
|
|
684
|
+
chain,
|
|
685
|
+
data: null,
|
|
686
|
+
head,
|
|
687
|
+
clientEntry,
|
|
688
|
+
routeId: id,
|
|
689
|
+
params,
|
|
690
|
+
path: pathOf(req),
|
|
691
|
+
search: searchChainOf(layoutModules, mod, req),
|
|
692
|
+
matchChain: matchChainOf(page.layoutIds, layoutModules, id, mod),
|
|
693
|
+
status: 404,
|
|
694
|
+
hydrate: false,
|
|
695
|
+
...(layoutData !== undefined ? { layoutData } : {}),
|
|
696
|
+
...stylesOf(id),
|
|
697
|
+
...(headers === undefined ? {} : { headers }),
|
|
698
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
699
|
+
...cspOption,
|
|
700
|
+
...titleOption,
|
|
701
|
+
});
|
|
702
|
+
};
|
|
703
|
+
/** The nearest nested `_404` for the route's own `notFound()`, else `undefined` (root page). */
|
|
704
|
+
const renderRouteNotFound = async (req, env, route, params, signal, run, personalized) => {
|
|
705
|
+
const id = route.notFoundIds?.at(-1);
|
|
706
|
+
if (id === undefined || run === undefined)
|
|
707
|
+
return undefined;
|
|
708
|
+
if (signal[STATUS_SIGNAL].status !== 404 || layoutErrorId(signal) !== undefined) {
|
|
709
|
+
return undefined;
|
|
710
|
+
}
|
|
711
|
+
const page = manifest.notFounds?.[id];
|
|
712
|
+
if (page === undefined)
|
|
713
|
+
return undefined;
|
|
714
|
+
try {
|
|
715
|
+
await run.pending;
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
// The page's layouts are the leading ones of the route's: its directory contains the route.
|
|
721
|
+
const kept = page.layoutIds.length;
|
|
722
|
+
return renderNestedNotFound(req, env, id, page, params, run.modules.slice(0, kept), run.layoutData?.slice(0, kept), personalized, signal[STATUS_SIGNAL].headers);
|
|
723
|
+
};
|
|
357
724
|
const renderNotFound = async (request, env, path) => {
|
|
358
725
|
const page = manifest.statusPages?.["404"] ?? manifest.notFound;
|
|
359
726
|
if (page === undefined)
|
|
360
727
|
return renderTerminalStatus(404, path);
|
|
361
728
|
return renderTerminalStatus(404, path, undefined, options.nonce === undefined ? undefined : await resolveNonce(request, env));
|
|
362
729
|
};
|
|
730
|
+
/**
|
|
731
|
+
* Run a page handler with the request's `ctx.set`, sealed once the handler settles - whatever it
|
|
732
|
+
* answered with - so a write that arrives after the response was decided throws. A request that
|
|
733
|
+
* queued a cookie answers with a response no shared cache may store, returned or thrown. A redirect
|
|
734
|
+
* answering a data request, returned or thrown, reaches the client router intact.
|
|
735
|
+
*/
|
|
736
|
+
const withResponseControls = (run) => async (c) => {
|
|
737
|
+
const controls = new PageResponseControls(c);
|
|
738
|
+
try {
|
|
739
|
+
const outcome = await run(c, controls);
|
|
740
|
+
const answer = dataRedirect(c.req, outcome) ?? outcome;
|
|
741
|
+
return controls.personalized ? privateOutcome(answer) : answer;
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
if (!isControlFlow(err))
|
|
745
|
+
throw err;
|
|
746
|
+
const answer = dataRedirect(c.req, err);
|
|
747
|
+
// Returned rather than rethrown: the serving app sends a thrown `Response` exactly as it is,
|
|
748
|
+
// without the cookies the request queued.
|
|
749
|
+
if (controls.personalized)
|
|
750
|
+
return privateOutcome(answer ?? err);
|
|
751
|
+
if (answer !== undefined)
|
|
752
|
+
return answer;
|
|
753
|
+
throw err;
|
|
754
|
+
}
|
|
755
|
+
finally {
|
|
756
|
+
controls.seal();
|
|
757
|
+
}
|
|
758
|
+
};
|
|
363
759
|
return {
|
|
364
760
|
get: (route) => {
|
|
365
761
|
const is404Fallback = options.staticFallbacks?.[route.pattern] === "404";
|
|
@@ -382,28 +778,37 @@ export function createPageRequestExecutor(options) {
|
|
|
382
778
|
}
|
|
383
779
|
return assemblySlot;
|
|
384
780
|
};
|
|
385
|
-
return async (c) => {
|
|
781
|
+
return withResponseControls(async (c, controls) => {
|
|
386
782
|
if (is404Fallback && !prerenderedSet.has(urlPartsFor(c.req).pathname)) {
|
|
387
783
|
return renderNotFound(c.req, c.env, pathOf(c.req));
|
|
388
784
|
}
|
|
389
785
|
const mod = await route.load();
|
|
390
786
|
const draft = await draftFlag(c.req);
|
|
787
|
+
// Unpublished content is the editor's alone: whatever cache-control a loader sets, no cache keeps it.
|
|
788
|
+
if (draft)
|
|
789
|
+
controls.personalize();
|
|
391
790
|
let data;
|
|
392
791
|
let layoutData;
|
|
393
792
|
let boundaryStates;
|
|
394
793
|
let layoutModules;
|
|
395
794
|
let layoutRetained = [];
|
|
795
|
+
let responseHeaders;
|
|
796
|
+
const requestApi = apiFor(c);
|
|
797
|
+
let run;
|
|
396
798
|
try {
|
|
397
799
|
const ctx = {
|
|
398
800
|
params: c.params,
|
|
399
801
|
request: c.req,
|
|
400
802
|
req: c.req,
|
|
401
|
-
api,
|
|
803
|
+
api: requestApi,
|
|
402
804
|
env: c.env,
|
|
403
805
|
draft,
|
|
404
806
|
search: loaderSearch(mod.searchSchema, c.req),
|
|
807
|
+
set: controls.scope(PAGE_SCOPE),
|
|
405
808
|
};
|
|
406
|
-
|
|
809
|
+
if (route.middlewareIds !== undefined)
|
|
810
|
+
await runMiddleware(route, ctx, controls);
|
|
811
|
+
run = await runLayoutChain(route, ctx, controls, retainContextOf(c.req));
|
|
407
812
|
layoutModules = run.modules;
|
|
408
813
|
layoutRetained = run.retained;
|
|
409
814
|
const boundaryDefinitions = [
|
|
@@ -418,20 +823,21 @@ export function createPageRequestExecutor(options) {
|
|
|
418
823
|
: startDynamicBoundaries(boundaryDefinitions, {
|
|
419
824
|
request: c.req,
|
|
420
825
|
params: c.params,
|
|
421
|
-
api,
|
|
826
|
+
api: requestApi,
|
|
422
827
|
env: c.env,
|
|
423
828
|
draft,
|
|
424
829
|
search: effectiveSearch,
|
|
425
830
|
signal: c.req.signal,
|
|
426
831
|
});
|
|
427
832
|
const staticBoundaryPromise = boundaryDefinitions.length === 0
|
|
428
|
-
?
|
|
429
|
-
: resolveStaticBoundaries(boundaryDefinitions, { phase: "build"
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
staticBoundaryPromise
|
|
434
|
-
|
|
833
|
+
? undefined
|
|
834
|
+
: resolveStaticBoundaries(boundaryDefinitions, { phase: "build" }, options.staticBoundaryCache);
|
|
835
|
+
const pageResult = mod.loader ? mod.loader({ ...ctx, search: effectiveSearch }) : null;
|
|
836
|
+
const [pageData, , staticBoundaries] = run.pending === undefined && staticBoundaryPromise === undefined
|
|
837
|
+
? [await pageResult, undefined, undefined]
|
|
838
|
+
: staticBoundaryPromise === undefined
|
|
839
|
+
? await Promise.all([pageResult, run.pending])
|
|
840
|
+
: await Promise.all([pageResult, run.pending, staticBoundaryPromise]);
|
|
435
841
|
data = pageData;
|
|
436
842
|
layoutData = run.layoutData;
|
|
437
843
|
if (boundaryBatch !== undefined) {
|
|
@@ -446,70 +852,85 @@ export function createPageRequestExecutor(options) {
|
|
|
446
852
|
else if (staticBoundaries !== undefined) {
|
|
447
853
|
boundaryStates = staticBoundaries;
|
|
448
854
|
}
|
|
855
|
+
// Inside the loader `try`: a header the response cannot carry is a loader error, rendered
|
|
856
|
+
// by the same boundary as one.
|
|
857
|
+
responseHeaders = controls.documentHeaders();
|
|
449
858
|
}
|
|
450
859
|
catch (err) {
|
|
451
|
-
if (isStatusSignal(err))
|
|
452
|
-
return renderStatusSignal(c.req, c.env, err);
|
|
453
|
-
if (isControlFlow(err))
|
|
454
|
-
throw err;
|
|
455
|
-
reportLoaderError(route, c.req, c.params, err);
|
|
456
|
-
const errorId = boundaryFor(route, err);
|
|
457
|
-
if (errorId === undefined)
|
|
458
|
-
throw err;
|
|
459
|
-
if (c.req.headers.get(DATA_HEADER) !== null) {
|
|
460
|
-
return new Response("Internal Server Error", {
|
|
461
|
-
status: 500,
|
|
462
|
-
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
463
|
-
});
|
|
860
|
+
if (isStatusSignal(err)) {
|
|
861
|
+
return ((await renderRouteNotFound(c.req, c.env, route, c.params, err, run, controls.personalized)) ?? renderStatusSignal(c.req, c.env, err, controls.personalized));
|
|
464
862
|
}
|
|
465
|
-
return
|
|
863
|
+
return renderLoaderFailure(route, c.req, c.env, c.params, err, controls.personalized);
|
|
466
864
|
}
|
|
467
|
-
|
|
468
|
-
|
|
865
|
+
if (isStatusSignal(data)) {
|
|
866
|
+
const nested = await renderRouteNotFound(c.req, c.env, route, c.params, data, run, controls.personalized);
|
|
867
|
+
if (nested !== undefined)
|
|
868
|
+
return nested;
|
|
869
|
+
}
|
|
870
|
+
return handleDataOrDocument(c.req, c.env, route, c.params, mod, data, layoutData, layoutModules ?? (await loadLayoutModules(route)), layoutRetained, boundaryStates, layoutModules === undefined ? undefined : assemblyCacheFor(mod, layoutModules), responseHeaders, controls.personalized);
|
|
871
|
+
});
|
|
469
872
|
},
|
|
470
|
-
post: (route) => async (c) => {
|
|
873
|
+
post: (route) => withResponseControls(async (c, controls) => {
|
|
471
874
|
const mod = await route.load();
|
|
472
875
|
const draft = await draftFlag(c.req);
|
|
876
|
+
if (draft)
|
|
877
|
+
controls.personalize();
|
|
473
878
|
if (mod.action === undefined) {
|
|
474
879
|
return new Response("Method Not Allowed", {
|
|
475
880
|
status: 405,
|
|
476
881
|
headers: { allow: "GET", "content-type": "text/plain; charset=utf-8" },
|
|
477
882
|
});
|
|
478
883
|
}
|
|
884
|
+
const requestApi = apiFor(c);
|
|
479
885
|
const actionContext = {
|
|
480
886
|
params: c.params,
|
|
481
887
|
request: c.req,
|
|
482
888
|
req: c.req,
|
|
483
|
-
api,
|
|
889
|
+
api: requestApi,
|
|
484
890
|
env: c.env,
|
|
485
891
|
draft,
|
|
486
892
|
search: loaderSearch(mod.searchSchema, c.req),
|
|
893
|
+
set: controls.scope(ACTION_SCOPE),
|
|
487
894
|
};
|
|
488
895
|
const isDataRequest = c.req.headers.get(DATA_HEADER) !== null;
|
|
489
896
|
let layoutModules;
|
|
490
897
|
let result;
|
|
491
898
|
try {
|
|
492
|
-
|
|
899
|
+
if (route.middlewareIds !== undefined)
|
|
900
|
+
await runMiddleware(route, actionContext, controls);
|
|
901
|
+
layoutModules = await runLayoutGates(route, actionContext, controls);
|
|
493
902
|
result = await mod.action(actionContext);
|
|
494
903
|
}
|
|
495
904
|
catch (err) {
|
|
496
905
|
if (isControlFlow(err))
|
|
497
|
-
return
|
|
906
|
+
return err;
|
|
498
907
|
throw err;
|
|
499
908
|
}
|
|
500
909
|
const isRevalidate = result !== null && typeof result === "object" && "__nifraRevalidate" in result;
|
|
501
910
|
const actionResult = isRevalidate ? result.data : result;
|
|
502
911
|
const revalidateHeader = isRevalidate
|
|
503
|
-
? {
|
|
912
|
+
? {
|
|
913
|
+
[REVALIDATE_HEADER]: result.__nifraRevalidate.join(","),
|
|
914
|
+
}
|
|
504
915
|
: {};
|
|
505
916
|
if (isControlFlow(actionResult))
|
|
506
|
-
return
|
|
917
|
+
return actionResult;
|
|
507
918
|
if (isDataRequest) {
|
|
919
|
+
// Headers shape the document only, but a bad one fails here too, so a navigation and a full
|
|
920
|
+
// page load agree on whether the action's response is valid.
|
|
921
|
+
controls.commit();
|
|
508
922
|
const { forClient, deferred } = prepareDeferred(actionResult);
|
|
509
|
-
if (deferred.length === 0)
|
|
510
|
-
return Response.json(actionResult ?? null, {
|
|
923
|
+
if (deferred.length === 0) {
|
|
924
|
+
return Response.json(actionResult ?? null, {
|
|
925
|
+
headers: { ...DATA_RESPONSE_HEADERS, ...revalidateHeader },
|
|
926
|
+
});
|
|
927
|
+
}
|
|
511
928
|
return new Response(ndjsonStream(forClient, deferred), {
|
|
512
|
-
headers: {
|
|
929
|
+
headers: {
|
|
930
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
931
|
+
...DATA_RESPONSE_HEADERS,
|
|
932
|
+
...revalidateHeader,
|
|
933
|
+
},
|
|
513
934
|
});
|
|
514
935
|
}
|
|
515
936
|
const search = searchChainOf(layoutModules, mod, c.req);
|
|
@@ -518,18 +939,21 @@ export function createPageRequestExecutor(options) {
|
|
|
518
939
|
params: c.params,
|
|
519
940
|
request: c.req,
|
|
520
941
|
req: c.req,
|
|
521
|
-
api,
|
|
942
|
+
api: requestApi,
|
|
522
943
|
env: c.env,
|
|
523
944
|
draft,
|
|
524
945
|
search,
|
|
946
|
+
set: controls.scope(PAGE_SCOPE),
|
|
525
947
|
})
|
|
526
948
|
: null;
|
|
949
|
+
const responseHeaders = controls.documentHeaders();
|
|
950
|
+
const nonce = options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env);
|
|
527
951
|
const { chain, head } = resolveChainAndHead(layoutModules, mod, {
|
|
528
952
|
data,
|
|
529
953
|
params: c.params,
|
|
530
954
|
origin: originOf(c.req),
|
|
531
|
-
|
|
532
|
-
|
|
955
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
956
|
+
}, serverLeafOf(route, mod));
|
|
533
957
|
return renderPageResult({
|
|
534
958
|
adapter,
|
|
535
959
|
chain,
|
|
@@ -541,15 +965,57 @@ export function createPageRequestExecutor(options) {
|
|
|
541
965
|
params: c.params,
|
|
542
966
|
path: pathOf(c.req),
|
|
543
967
|
search,
|
|
968
|
+
matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
|
|
544
969
|
hydrate: mod.hydrate !== false,
|
|
545
970
|
...preloadOf(route.id),
|
|
546
971
|
...stylesOf(route.id),
|
|
547
|
-
|
|
972
|
+
...prerenderedHandover,
|
|
973
|
+
...(responseHeaders === undefined ? {} : { headers: responseHeaders }),
|
|
548
974
|
...(nonce === undefined ? {} : { nonce }),
|
|
975
|
+
...cspOption,
|
|
549
976
|
...titleOption,
|
|
550
977
|
});
|
|
551
|
-
},
|
|
552
|
-
|
|
978
|
+
}),
|
|
979
|
+
fallback: withResponseControls(async (c, controls) => {
|
|
980
|
+
const match = matchNotFoundScope?.(pathOf(c.req)) ?? null;
|
|
981
|
+
const target = match === null ? undefined : notFoundTargets[Number(match.routeId)];
|
|
982
|
+
if (match === null || target === undefined) {
|
|
983
|
+
return renderNotFound(c.req, c.env, pathOf(c.req));
|
|
984
|
+
}
|
|
985
|
+
const params = {};
|
|
986
|
+
for (let i = 0; i < target.names.length; i++) {
|
|
987
|
+
params[target.names[i]] = match.params[`p${i}`];
|
|
988
|
+
}
|
|
989
|
+
const mod = await target.page.load();
|
|
990
|
+
const draft = await draftFlag(c.req);
|
|
991
|
+
if (draft)
|
|
992
|
+
controls.personalize();
|
|
993
|
+
let run;
|
|
994
|
+
try {
|
|
995
|
+
const ctx = {
|
|
996
|
+
params,
|
|
997
|
+
request: c.req,
|
|
998
|
+
req: c.req,
|
|
999
|
+
api: apiFor(c),
|
|
1000
|
+
env: c.env,
|
|
1001
|
+
draft,
|
|
1002
|
+
search: loaderSearch(mod.searchSchema, c.req),
|
|
1003
|
+
set: controls.scope(PAGE_SCOPE),
|
|
1004
|
+
};
|
|
1005
|
+
if (target.route.middlewareIds !== undefined) {
|
|
1006
|
+
await runMiddleware(target.route, ctx, controls);
|
|
1007
|
+
}
|
|
1008
|
+
run = await runLayoutChain(target.route, ctx, controls);
|
|
1009
|
+
await run.pending;
|
|
1010
|
+
}
|
|
1011
|
+
catch (err) {
|
|
1012
|
+
if (isStatusSignal(err)) {
|
|
1013
|
+
return renderStatusSignal(c.req, c.env, err, controls.personalized);
|
|
1014
|
+
}
|
|
1015
|
+
return renderLoaderFailure(target.route, c.req, c.env, params, err, controls.personalized);
|
|
1016
|
+
}
|
|
1017
|
+
return renderNestedNotFound(c.req, c.env, target.id, target.page, params, run.modules, run.layoutData, controls.personalized);
|
|
1018
|
+
}),
|
|
553
1019
|
};
|
|
554
1020
|
}
|
|
555
1021
|
//# sourceMappingURL=page-execution.js.map
|