@nifrajs/web 3.3.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 +35 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +89 -35
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +18 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +97 -17
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +72 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +411 -98
- 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 +21 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +226 -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/css-contract.d.ts +21 -0
- package/dist/css-contract.d.ts.map +1 -0
- package/dist/css-contract.js +28 -0
- package/dist/css-contract.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 +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -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 +3 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +151 -30
- 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 +69 -0
- package/dist/internal/page-execution.d.ts.map +1 -0
- package/dist/internal/page-execution.js +1021 -0
- package/dist/internal/page-execution.js.map +1 -0
- 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 +37 -21
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +104 -65
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-url.d.ts +3 -0
- package/dist/internal/request-url.d.ts.map +1 -0
- package/dist/internal/request-url.js +14 -0
- package/dist/internal/request-url.js.map +1 -0
- 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 +56 -8
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +215 -772
- 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 +301 -39
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +3 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +37 -9
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +221 -14
- 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 +131 -48
- 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/trusted-html.d.ts +34 -0
- package/dist/trusted-html.d.ts.map +1 -0
- package/dist/trusted-html.js +35 -0
- package/dist/trusted-html.js.map +1 -0
- 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 +113 -36
- package/src/build-vite.ts +124 -15
- package/src/build.ts +535 -102
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +251 -18
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -0
- package/src/css-contract.ts +44 -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 +57 -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 +180 -29
- 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 +1371 -0
- 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 +153 -73
- package/src/internal/request-url.ts +14 -0
- 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 +290 -879
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +366 -39
- package/src/llms-txt.ts +44 -8
- package/src/manifest.ts +671 -17
- 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 +135 -49
- 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/trusted-html.ts +44 -0
- 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
|
@@ -0,0 +1,1021 @@
|
|
|
1
|
+
import { NIFRA_BACKEND_BIND_PLATFORM, NIFRA_PLATFORM_CLIENT_IP_DERIVED, } from "@nifrajs/core/mount";
|
|
2
|
+
import { status as statusResult } from "@nifrajs/core/server";
|
|
3
|
+
import { boundaryDescriptors, resolveStaticBoundaries, startDynamicBoundaries, } from "../boundary.js";
|
|
4
|
+
import { DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.js";
|
|
5
|
+
import { defer, ndjsonStream, prepareDeferred } from "../deferred.js";
|
|
6
|
+
import { isDraftEnabled } from "../draft.js";
|
|
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";
|
|
9
|
+
import { searchOf, searchOfChain } from "../search.js";
|
|
10
|
+
import { mergeHeads, resolveMeta } from "./head-merge.js";
|
|
11
|
+
import { EMPTY_RETAIN, isControlFlow, isResponseResult, isStatusSignal, layoutErrorId, renderPageResult, STATUS_SIGNAL, STATUS_TEXT, scopeParams, tagLayoutError, withDuplicateInstanceHint, } from "./render-document.js";
|
|
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
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Own the page request program behind one private seam.
|
|
101
|
+
*
|
|
102
|
+
* `createWebApp` remains responsible for app construction, mount ordering, and route registration.
|
|
103
|
+
* This module owns the page-specific contract: context, layout execution, control-flow precedence,
|
|
104
|
+
* data/document assembly, and error/status rendering. GET and POST retain their distinct ordering
|
|
105
|
+
* (ordinary layout data may run after an action; a gate must run before it) while sharing the same
|
|
106
|
+
* outcome and render decisions.
|
|
107
|
+
*/
|
|
108
|
+
export function createPageRequestExecutor(options) {
|
|
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));
|
|
115
|
+
const cssLoading = normalizeCssLoading(options.cssLoading ?? DEFAULT_CSS_LOADING);
|
|
116
|
+
const titleOption = options.title === undefined ? {} : { title: options.title };
|
|
117
|
+
const cspOption = options.csp === undefined ? {} : { csp: options.csp };
|
|
118
|
+
const prerenderedSet = new Set(options.prerenderedPaths ?? []);
|
|
119
|
+
const prerenderedHandover = options.prerenderedListUrl === undefined
|
|
120
|
+
? { prerenderedPaths: options.prerenderedPaths ?? [] }
|
|
121
|
+
: { prerenderedListUrl: options.prerenderedListUrl };
|
|
122
|
+
const matchManifestRoute = createMatcher(manifest.routes.map((route) => ({ routeId: route.id, pattern: route.pattern })));
|
|
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);
|
|
160
|
+
const draftFlag = (req) => options.draftSecret === undefined
|
|
161
|
+
? Promise.resolve(false)
|
|
162
|
+
: isDraftEnabled(req, options.draftSecret);
|
|
163
|
+
const preloadOf = (id) => {
|
|
164
|
+
const chunks = options.routePreload?.[id];
|
|
165
|
+
return chunks ? { preload: chunks } : {};
|
|
166
|
+
};
|
|
167
|
+
const resolveNonce = (request, env) => options.nonce === undefined ? undefined : options.nonce({ request, env });
|
|
168
|
+
const stylesOf = (id) => {
|
|
169
|
+
const perRoute = options.routeStyles?.[id];
|
|
170
|
+
const styles = perRoute ?? options.styles;
|
|
171
|
+
if (styles === undefined || styles.length === 0)
|
|
172
|
+
return {};
|
|
173
|
+
return {
|
|
174
|
+
styles,
|
|
175
|
+
...(cssLoading === "deferred" ? { cssLoading } : {}),
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
// Load a route's layout modules once for the current execution. The render path needs each whole
|
|
179
|
+
// module for its component + meta, while the data path also needs loader/gate/boundary declarations.
|
|
180
|
+
const loadLayoutModules = async (route) => {
|
|
181
|
+
const modules = await Promise.all(route.layoutIds.map((id) => manifest.layouts[id].load()));
|
|
182
|
+
for (let i = 0; i < modules.length; i++) {
|
|
183
|
+
if (modules[i].action === undefined)
|
|
184
|
+
continue;
|
|
185
|
+
const id = route.layoutIds[i];
|
|
186
|
+
throw new Error(`[nifra/web] "${manifest.layouts[id].file}" exports an \`action\`, but layouts do not run one - only route files do. Move the mutation into the route that submits to it.`);
|
|
187
|
+
}
|
|
188
|
+
return modules;
|
|
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
|
+
};
|
|
197
|
+
/** Run layout loaders, awaiting gates before starting the page loader and retaining only safe data. */
|
|
198
|
+
const runLayoutChain = async (route, ctx, controls, retain) => {
|
|
199
|
+
if (route.layoutIds.length === 0)
|
|
200
|
+
return emptyLayoutRun;
|
|
201
|
+
const modules = await loadLayoutModules(route);
|
|
202
|
+
if (!modules.some((m) => m.loader !== undefined)) {
|
|
203
|
+
return { modules, layoutData: undefined, retained: [], pending: undefined };
|
|
204
|
+
}
|
|
205
|
+
const results = new Array(modules.length).fill(null);
|
|
206
|
+
const retained = [];
|
|
207
|
+
const pending = [];
|
|
208
|
+
for (let i = 0; i < modules.length; i++) {
|
|
209
|
+
const layout = modules[i];
|
|
210
|
+
const loader = layout.loader;
|
|
211
|
+
if (loader === undefined)
|
|
212
|
+
continue;
|
|
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
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const scoped = {
|
|
227
|
+
...ctx,
|
|
228
|
+
params: scopeParams(ctx.params, route.layoutParams?.[i]),
|
|
229
|
+
set: controls.scope(i),
|
|
230
|
+
};
|
|
231
|
+
if (modules[i]?.gate === true) {
|
|
232
|
+
try {
|
|
233
|
+
results[i] = await loader(scoped);
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
throw tagLayoutError(err, route.layoutIds[i]);
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const layoutId = route.layoutIds[i];
|
|
241
|
+
const index = i;
|
|
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
|
+
})());
|
|
255
|
+
}
|
|
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
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
modules,
|
|
266
|
+
layoutData: results,
|
|
267
|
+
retained,
|
|
268
|
+
pending: pending.length === 0 ? undefined : pending.length === 1 ? pending[0] : Promise.all(pending),
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
/** Run only authorization gates before an action; ordinary layout data runs after a native action. */
|
|
272
|
+
const runLayoutGates = async (route, ctx, controls) => {
|
|
273
|
+
const modules = await loadLayoutModules(route);
|
|
274
|
+
for (let i = 0; i < modules.length; i++) {
|
|
275
|
+
const mod = modules[i];
|
|
276
|
+
if (mod?.gate !== true || mod.loader === undefined)
|
|
277
|
+
continue;
|
|
278
|
+
try {
|
|
279
|
+
await mod.loader({
|
|
280
|
+
...ctx,
|
|
281
|
+
params: scopeParams(ctx.params, route.layoutParams?.[i]),
|
|
282
|
+
set: controls.scope(i),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (err) {
|
|
286
|
+
throw tagLayoutError(err, route.layoutIds[i]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return modules;
|
|
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
|
+
};
|
|
323
|
+
const retainedIndices = (header) => {
|
|
324
|
+
if (header === null || header === "")
|
|
325
|
+
return EMPTY_RETAIN;
|
|
326
|
+
const out = new Set();
|
|
327
|
+
for (const part of header.split(",")) {
|
|
328
|
+
if (!/^\d+$/.test(part))
|
|
329
|
+
continue;
|
|
330
|
+
out.add(Number(part));
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
};
|
|
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) => {
|
|
339
|
+
if (req.headers.get(DATA_HEADER) === null)
|
|
340
|
+
return undefined;
|
|
341
|
+
const requested = retainedIndices(req.headers.get(RETAIN_HEADER));
|
|
342
|
+
const from = req.headers.get(NAV_FROM_HEADER);
|
|
343
|
+
if (requested.size === 0 || from === null)
|
|
344
|
+
return undefined;
|
|
345
|
+
let toUrl;
|
|
346
|
+
let fromUrl;
|
|
347
|
+
try {
|
|
348
|
+
toUrl = new URL(req.url);
|
|
349
|
+
fromUrl = new URL(from, toUrl);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
if (fromUrl.origin !== toUrl.origin)
|
|
355
|
+
return undefined;
|
|
356
|
+
const previousMatch = matchManifestRoute(fromUrl.pathname + fromUrl.search);
|
|
357
|
+
if (previousMatch === null)
|
|
358
|
+
return undefined;
|
|
359
|
+
const previous = routeById.get(previousMatch.routeId);
|
|
360
|
+
if (previous === undefined)
|
|
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;
|
|
390
|
+
};
|
|
391
|
+
const loaderSearch = (searchSchema, request) => searchOf(searchSchema, urlPartsFor(request).search);
|
|
392
|
+
const searchChainOf = (layoutMods, mod, request) => searchOfChain([...layoutMods.map((m) => m.searchSchema), mod.searchSchema], urlPartsFor(request).search);
|
|
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];
|
|
405
|
+
const heads = [
|
|
406
|
+
...layoutModules.map((m) => resolveMeta(m.meta, metaArgs)),
|
|
407
|
+
resolveMeta(page.meta, metaArgs),
|
|
408
|
+
];
|
|
409
|
+
return { chain, head: mergeHeads(heads) };
|
|
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
|
+
});
|
|
416
|
+
const dirOfId = (id, suffix) => id === suffix ? "" : id.slice(0, id.length - suffix.length - 1);
|
|
417
|
+
const boundaryFor = (route, err) => {
|
|
418
|
+
const ids = route.errorIds ?? [];
|
|
419
|
+
const failing = layoutErrorId(err);
|
|
420
|
+
if (failing === undefined)
|
|
421
|
+
return ids.at(-1);
|
|
422
|
+
const layoutDir = dirOfId(failing, "_layout");
|
|
423
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
424
|
+
const errDir = dirOfId(ids[i], "_error");
|
|
425
|
+
if (errDir === "" || errDir === layoutDir || layoutDir.startsWith(`${errDir}/`))
|
|
426
|
+
return ids[i];
|
|
427
|
+
}
|
|
428
|
+
return undefined;
|
|
429
|
+
};
|
|
430
|
+
const originOf = (req) => {
|
|
431
|
+
try {
|
|
432
|
+
return new URL(req.url).origin;
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
return "";
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const pathOf = (req) => {
|
|
439
|
+
try {
|
|
440
|
+
const parts = urlPartsFor(req);
|
|
441
|
+
return parts.pathname + parts.search;
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return "/";
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
const renderError = async (route, errorId, err, personalized, nonce) => {
|
|
448
|
+
const errDir = dirOfId(errorId, "_error");
|
|
449
|
+
const keptLayoutIds = route.layoutIds.filter((id) => {
|
|
450
|
+
const ld = dirOfId(id, "_layout");
|
|
451
|
+
return ld === "" || ld === errDir || errDir.startsWith(`${ld}/`);
|
|
452
|
+
});
|
|
453
|
+
const layouts = await Promise.all(keptLayoutIds.map((id) => manifest.layouts[id].load().then((m) => m.default)));
|
|
454
|
+
const { default: errComp } = await (manifest.errors?.[errorId]).load();
|
|
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";
|
|
458
|
+
return renderPageResult({
|
|
459
|
+
adapter,
|
|
460
|
+
chain: [...layouts, errComp],
|
|
461
|
+
data: production
|
|
462
|
+
? { name: "Error", message: "Internal Server Error" }
|
|
463
|
+
: { name: e.name, message: e.message },
|
|
464
|
+
clientEntry,
|
|
465
|
+
routeId: errorId,
|
|
466
|
+
status: 500,
|
|
467
|
+
hydrate: false,
|
|
468
|
+
...(personalized ? { headers: PRIVATE_PAGE_HEADERS } : {}),
|
|
469
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
470
|
+
...cspOption,
|
|
471
|
+
...titleOption,
|
|
472
|
+
});
|
|
473
|
+
};
|
|
474
|
+
const renderTerminalStatus = async (status, path, extraHeaders, nonce) => {
|
|
475
|
+
const page = manifest.statusPages?.[String(status)] ?? manifest.notFound;
|
|
476
|
+
if (page === undefined) {
|
|
477
|
+
const headers = new Headers(extraHeaders);
|
|
478
|
+
headers.set("content-type", "text/plain; charset=utf-8");
|
|
479
|
+
return new Response(STATUS_TEXT[status] ?? "Error", { status, headers });
|
|
480
|
+
}
|
|
481
|
+
const routeId = manifest.statusPages?.[String(status)] !== undefined ? `_${status}` : "_404";
|
|
482
|
+
const loaded = (await page.load());
|
|
483
|
+
return renderPageResult({
|
|
484
|
+
adapter,
|
|
485
|
+
chain: [loaded.default],
|
|
486
|
+
data: null,
|
|
487
|
+
clientEntry,
|
|
488
|
+
routeId,
|
|
489
|
+
matchChain: { ids: [routeId], handles: [loaded.handle] },
|
|
490
|
+
...(path !== undefined
|
|
491
|
+
? {
|
|
492
|
+
search: searchOf(loaded.searchSchema, path.includes("?") ? path.slice(path.indexOf("?")) : ""),
|
|
493
|
+
}
|
|
494
|
+
: {}),
|
|
495
|
+
...(path !== undefined ? { path } : {}),
|
|
496
|
+
status,
|
|
497
|
+
...(extraHeaders !== undefined ? { headers: extraHeaders } : {}),
|
|
498
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
499
|
+
...cspOption,
|
|
500
|
+
...preloadOf(routeId),
|
|
501
|
+
...stylesOf(routeId),
|
|
502
|
+
...prerenderedHandover,
|
|
503
|
+
...titleOption,
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
const renderStatusSignal = async (req, env, signal, personalized) => {
|
|
507
|
+
const { status, headers } = signal[STATUS_SIGNAL];
|
|
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.
|
|
511
|
+
const responseHeaders = new Headers(headers);
|
|
512
|
+
responseHeaders.set(STATUS_HEADER, String(status));
|
|
513
|
+
responseHeaders.set("cache-control", PRIVATE_NO_STORE);
|
|
514
|
+
responseHeaders.set("vary", varyOnDataHeader(responseHeaders.get("vary")));
|
|
515
|
+
return new Response(null, { status, headers: responseHeaders });
|
|
516
|
+
}
|
|
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);
|
|
529
|
+
};
|
|
530
|
+
const reportLoaderError = (route, req, params, err) => {
|
|
531
|
+
if (options.onLoaderError === undefined)
|
|
532
|
+
return;
|
|
533
|
+
try {
|
|
534
|
+
options.onLoaderError(err, { request: req, params, route: route.pattern });
|
|
535
|
+
}
|
|
536
|
+
catch {
|
|
537
|
+
// A faulty reporter must never break error rendering.
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
const handleDataOrDocument = async (req, env, route, params, mod, data, layoutData, layoutModules, layoutRetained, boundaryStates, assemblyCache, responseHeaders, personalized) => {
|
|
541
|
+
if (isStatusSignal(data))
|
|
542
|
+
return renderStatusSignal(req, env, data, personalized);
|
|
543
|
+
if (isControlFlow(data))
|
|
544
|
+
return data;
|
|
545
|
+
if (req.headers.get(DATA_HEADER) !== null) {
|
|
546
|
+
const pageSplit = prepareDeferred(data);
|
|
547
|
+
const layoutSplits = [];
|
|
548
|
+
let offset = pageSplit.deferred.length;
|
|
549
|
+
for (const entry of layoutData ?? []) {
|
|
550
|
+
const split = prepareDeferred(entry, offset);
|
|
551
|
+
layoutSplits.push(split);
|
|
552
|
+
offset += split.deferred.length;
|
|
553
|
+
}
|
|
554
|
+
const deferred = [...pageSplit.deferred, ...layoutSplits.flatMap((split) => split.deferred)];
|
|
555
|
+
const boundarySplit = boundaryStates === undefined ? undefined : prepareDeferred(boundaryStates, deferred.length);
|
|
556
|
+
const allDeferred = [
|
|
557
|
+
...deferred,
|
|
558
|
+
...(boundarySplit === undefined ? [] : boundarySplit.deferred),
|
|
559
|
+
];
|
|
560
|
+
const payload = layoutData === undefined && boundarySplit === undefined
|
|
561
|
+
? pageSplit.forClient
|
|
562
|
+
: {
|
|
563
|
+
v: 1,
|
|
564
|
+
data: pageSplit.forClient,
|
|
565
|
+
layoutData: layoutSplits.map((split) => split.forClient),
|
|
566
|
+
retained: layoutRetained,
|
|
567
|
+
...(boundarySplit === undefined ? {} : { boundaries: boundarySplit.forClient }),
|
|
568
|
+
};
|
|
569
|
+
if (allDeferred.length === 0) {
|
|
570
|
+
return Response.json(payload ?? null, { headers: DATA_RESPONSE_HEADERS });
|
|
571
|
+
}
|
|
572
|
+
return new Response(ndjsonStream(payload, allDeferred), {
|
|
573
|
+
headers: {
|
|
574
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
575
|
+
...DATA_RESPONSE_HEADERS,
|
|
576
|
+
},
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env);
|
|
580
|
+
const { chain, head } = resolveChainAndHead(layoutModules, mod, {
|
|
581
|
+
data,
|
|
582
|
+
params,
|
|
583
|
+
origin: originOf(req),
|
|
584
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
585
|
+
}, serverLeafOf(route, mod));
|
|
586
|
+
try {
|
|
587
|
+
return await renderPageResult({
|
|
588
|
+
adapter,
|
|
589
|
+
chain,
|
|
590
|
+
data,
|
|
591
|
+
head,
|
|
592
|
+
clientEntry,
|
|
593
|
+
routeId: route.id,
|
|
594
|
+
params,
|
|
595
|
+
path: pathOf(req),
|
|
596
|
+
search: searchChainOf(layoutModules, mod, req),
|
|
597
|
+
matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
|
|
598
|
+
hydrate: mod.hydrate !== false,
|
|
599
|
+
...(layoutData !== undefined ? { layoutData } : {}),
|
|
600
|
+
...(boundaryStates !== undefined ? { boundaries: boundaryStates } : {}),
|
|
601
|
+
...preloadOf(route.id),
|
|
602
|
+
...stylesOf(route.id),
|
|
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
|
+
: {}),
|
|
616
|
+
...(mod.islandScripts !== undefined ? { islandScripts: mod.islandScripts } : {}),
|
|
617
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
618
|
+
...cspOption,
|
|
619
|
+
...titleOption,
|
|
620
|
+
...(assemblyCache === undefined ? {} : { assemblyCache }),
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
if (isStatusSignal(err))
|
|
625
|
+
return renderStatusSignal(req, env, err, personalized);
|
|
626
|
+
if (isControlFlow(err))
|
|
627
|
+
throw err;
|
|
628
|
+
reportLoaderError(route, req, { ...params }, err);
|
|
629
|
+
const errorId = boundaryFor(route, err);
|
|
630
|
+
if (errorId === undefined)
|
|
631
|
+
throw err;
|
|
632
|
+
return renderError(route, errorId, withDuplicateInstanceHint(err), personalized, nonce);
|
|
633
|
+
}
|
|
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
|
+
};
|
|
724
|
+
const renderNotFound = async (request, env, path) => {
|
|
725
|
+
const page = manifest.statusPages?.["404"] ?? manifest.notFound;
|
|
726
|
+
if (page === undefined)
|
|
727
|
+
return renderTerminalStatus(404, path);
|
|
728
|
+
return renderTerminalStatus(404, path, undefined, options.nonce === undefined ? undefined : await resolveNonce(request, env));
|
|
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
|
+
};
|
|
759
|
+
return {
|
|
760
|
+
get: (route) => {
|
|
761
|
+
const is404Fallback = options.staticFallbacks?.[route.pattern] === "404";
|
|
762
|
+
let assemblySlot;
|
|
763
|
+
let assemblySlotMod;
|
|
764
|
+
let assemblySlotLayouts;
|
|
765
|
+
const assemblyCacheFor = (mod, layouts) => {
|
|
766
|
+
if (typeof mod.meta === "function")
|
|
767
|
+
return undefined;
|
|
768
|
+
for (const layout of layouts)
|
|
769
|
+
if (typeof layout.meta === "function")
|
|
770
|
+
return undefined;
|
|
771
|
+
const layoutsUnchanged = assemblySlotLayouts !== undefined &&
|
|
772
|
+
assemblySlotLayouts.length === layouts.length &&
|
|
773
|
+
assemblySlotLayouts.every((layout, index) => layout === layouts[index]);
|
|
774
|
+
if (assemblySlotMod !== mod || !layoutsUnchanged) {
|
|
775
|
+
assemblySlot = {};
|
|
776
|
+
assemblySlotMod = mod;
|
|
777
|
+
assemblySlotLayouts = layouts;
|
|
778
|
+
}
|
|
779
|
+
return assemblySlot;
|
|
780
|
+
};
|
|
781
|
+
return withResponseControls(async (c, controls) => {
|
|
782
|
+
if (is404Fallback && !prerenderedSet.has(urlPartsFor(c.req).pathname)) {
|
|
783
|
+
return renderNotFound(c.req, c.env, pathOf(c.req));
|
|
784
|
+
}
|
|
785
|
+
const mod = await route.load();
|
|
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();
|
|
790
|
+
let data;
|
|
791
|
+
let layoutData;
|
|
792
|
+
let boundaryStates;
|
|
793
|
+
let layoutModules;
|
|
794
|
+
let layoutRetained = [];
|
|
795
|
+
let responseHeaders;
|
|
796
|
+
const requestApi = apiFor(c);
|
|
797
|
+
let run;
|
|
798
|
+
try {
|
|
799
|
+
const ctx = {
|
|
800
|
+
params: c.params,
|
|
801
|
+
request: c.req,
|
|
802
|
+
req: c.req,
|
|
803
|
+
api: requestApi,
|
|
804
|
+
env: c.env,
|
|
805
|
+
draft,
|
|
806
|
+
search: loaderSearch(mod.searchSchema, c.req),
|
|
807
|
+
set: controls.scope(PAGE_SCOPE),
|
|
808
|
+
};
|
|
809
|
+
if (route.middlewareIds !== undefined)
|
|
810
|
+
await runMiddleware(route, ctx, controls);
|
|
811
|
+
run = await runLayoutChain(route, ctx, controls, retainContextOf(c.req));
|
|
812
|
+
layoutModules = run.modules;
|
|
813
|
+
layoutRetained = run.retained;
|
|
814
|
+
const boundaryDefinitions = [
|
|
815
|
+
...run.modules.flatMap((layout) => layout.boundaries ?? []),
|
|
816
|
+
...(mod.boundaries ?? []),
|
|
817
|
+
];
|
|
818
|
+
if (boundaryDefinitions.length > 0)
|
|
819
|
+
boundaryDescriptors(boundaryDefinitions);
|
|
820
|
+
const effectiveSearch = searchChainOf(run.modules, mod, c.req);
|
|
821
|
+
const boundaryBatch = boundaryDefinitions.length === 0
|
|
822
|
+
? undefined
|
|
823
|
+
: startDynamicBoundaries(boundaryDefinitions, {
|
|
824
|
+
request: c.req,
|
|
825
|
+
params: c.params,
|
|
826
|
+
api: requestApi,
|
|
827
|
+
env: c.env,
|
|
828
|
+
draft,
|
|
829
|
+
search: effectiveSearch,
|
|
830
|
+
signal: c.req.signal,
|
|
831
|
+
});
|
|
832
|
+
const staticBoundaryPromise = boundaryDefinitions.length === 0
|
|
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]);
|
|
841
|
+
data = pageData;
|
|
842
|
+
layoutData = run.layoutData;
|
|
843
|
+
if (boundaryBatch !== undefined) {
|
|
844
|
+
const streamed = { ...boundaryBatch.initial, ...(staticBoundaries ?? {}) };
|
|
845
|
+
for (const entry of boundaryBatch.pending) {
|
|
846
|
+
const initial = streamed[entry.name];
|
|
847
|
+
if (initial !== undefined)
|
|
848
|
+
streamed[entry.name] = { ...initial, data: defer(entry.promise) };
|
|
849
|
+
}
|
|
850
|
+
boundaryStates = streamed;
|
|
851
|
+
}
|
|
852
|
+
else if (staticBoundaries !== undefined) {
|
|
853
|
+
boundaryStates = staticBoundaries;
|
|
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();
|
|
858
|
+
}
|
|
859
|
+
catch (err) {
|
|
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));
|
|
862
|
+
}
|
|
863
|
+
return renderLoaderFailure(route, c.req, c.env, c.params, err, controls.personalized);
|
|
864
|
+
}
|
|
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
|
+
});
|
|
872
|
+
},
|
|
873
|
+
post: (route) => withResponseControls(async (c, controls) => {
|
|
874
|
+
const mod = await route.load();
|
|
875
|
+
const draft = await draftFlag(c.req);
|
|
876
|
+
if (draft)
|
|
877
|
+
controls.personalize();
|
|
878
|
+
if (mod.action === undefined) {
|
|
879
|
+
return new Response("Method Not Allowed", {
|
|
880
|
+
status: 405,
|
|
881
|
+
headers: { allow: "GET", "content-type": "text/plain; charset=utf-8" },
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
const requestApi = apiFor(c);
|
|
885
|
+
const actionContext = {
|
|
886
|
+
params: c.params,
|
|
887
|
+
request: c.req,
|
|
888
|
+
req: c.req,
|
|
889
|
+
api: requestApi,
|
|
890
|
+
env: c.env,
|
|
891
|
+
draft,
|
|
892
|
+
search: loaderSearch(mod.searchSchema, c.req),
|
|
893
|
+
set: controls.scope(ACTION_SCOPE),
|
|
894
|
+
};
|
|
895
|
+
const isDataRequest = c.req.headers.get(DATA_HEADER) !== null;
|
|
896
|
+
let layoutModules;
|
|
897
|
+
let result;
|
|
898
|
+
try {
|
|
899
|
+
if (route.middlewareIds !== undefined)
|
|
900
|
+
await runMiddleware(route, actionContext, controls);
|
|
901
|
+
layoutModules = await runLayoutGates(route, actionContext, controls);
|
|
902
|
+
result = await mod.action(actionContext);
|
|
903
|
+
}
|
|
904
|
+
catch (err) {
|
|
905
|
+
if (isControlFlow(err))
|
|
906
|
+
return err;
|
|
907
|
+
throw err;
|
|
908
|
+
}
|
|
909
|
+
const isRevalidate = result !== null && typeof result === "object" && "__nifraRevalidate" in result;
|
|
910
|
+
const actionResult = isRevalidate ? result.data : result;
|
|
911
|
+
const revalidateHeader = isRevalidate
|
|
912
|
+
? {
|
|
913
|
+
[REVALIDATE_HEADER]: result.__nifraRevalidate.join(","),
|
|
914
|
+
}
|
|
915
|
+
: {};
|
|
916
|
+
if (isControlFlow(actionResult))
|
|
917
|
+
return actionResult;
|
|
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();
|
|
922
|
+
const { forClient, deferred } = prepareDeferred(actionResult);
|
|
923
|
+
if (deferred.length === 0) {
|
|
924
|
+
return Response.json(actionResult ?? null, {
|
|
925
|
+
headers: { ...DATA_RESPONSE_HEADERS, ...revalidateHeader },
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
return new Response(ndjsonStream(forClient, deferred), {
|
|
929
|
+
headers: {
|
|
930
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
931
|
+
...DATA_RESPONSE_HEADERS,
|
|
932
|
+
...revalidateHeader,
|
|
933
|
+
},
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
const search = searchChainOf(layoutModules, mod, c.req);
|
|
937
|
+
const data = mod.loader
|
|
938
|
+
? await mod.loader({
|
|
939
|
+
params: c.params,
|
|
940
|
+
request: c.req,
|
|
941
|
+
req: c.req,
|
|
942
|
+
api: requestApi,
|
|
943
|
+
env: c.env,
|
|
944
|
+
draft,
|
|
945
|
+
search,
|
|
946
|
+
set: controls.scope(PAGE_SCOPE),
|
|
947
|
+
})
|
|
948
|
+
: null;
|
|
949
|
+
const responseHeaders = controls.documentHeaders();
|
|
950
|
+
const nonce = options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env);
|
|
951
|
+
const { chain, head } = resolveChainAndHead(layoutModules, mod, {
|
|
952
|
+
data,
|
|
953
|
+
params: c.params,
|
|
954
|
+
origin: originOf(c.req),
|
|
955
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
956
|
+
}, serverLeafOf(route, mod));
|
|
957
|
+
return renderPageResult({
|
|
958
|
+
adapter,
|
|
959
|
+
chain,
|
|
960
|
+
data,
|
|
961
|
+
actionData: actionResult,
|
|
962
|
+
head,
|
|
963
|
+
clientEntry,
|
|
964
|
+
routeId: route.id,
|
|
965
|
+
params: c.params,
|
|
966
|
+
path: pathOf(c.req),
|
|
967
|
+
search,
|
|
968
|
+
matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
|
|
969
|
+
hydrate: mod.hydrate !== false,
|
|
970
|
+
...preloadOf(route.id),
|
|
971
|
+
...stylesOf(route.id),
|
|
972
|
+
...prerenderedHandover,
|
|
973
|
+
...(responseHeaders === undefined ? {} : { headers: responseHeaders }),
|
|
974
|
+
...(nonce === undefined ? {} : { nonce }),
|
|
975
|
+
...cspOption,
|
|
976
|
+
...titleOption,
|
|
977
|
+
});
|
|
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
|
+
}),
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
//# sourceMappingURL=page-execution.js.map
|