@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
package/src/internal/web-app.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type BackendEvidenceProvider,
|
|
2
3
|
type BackendMount,
|
|
3
|
-
type
|
|
4
|
+
type BackendWebSocketMountHandler,
|
|
5
|
+
NIFRA_BACKEND_EVIDENCE,
|
|
4
6
|
NIFRA_BACKEND_MOUNT,
|
|
7
|
+
NIFRA_BACKEND_WS_MOUNT,
|
|
8
|
+
NIFRA_BACKEND_WS_RUNTIME,
|
|
9
|
+
preRouteMountPaths,
|
|
5
10
|
} from "@nifrajs/core/mount"
|
|
6
|
-
import type {
|
|
11
|
+
import type { MountableApp, MountOptions } from "@nifrajs/core/server"
|
|
7
12
|
import { type ServerOptions, server } from "@nifrajs/core/server"
|
|
13
|
+
import { type CspPolicy, cspNonceResolver, DOCUMENT_POLICY, isCspPolicy } from "../csp.ts"
|
|
8
14
|
import type { CssLoadingMode } from "../css-contract.ts"
|
|
15
|
+
import { CACHE_CHANNEL } from "../isr.ts"
|
|
9
16
|
import { generateLlmsTxt } from "../llms-txt.ts"
|
|
10
17
|
import type { Manifest } from "../manifest.ts"
|
|
11
18
|
import type { RenderAdapter } from "../render-seam.ts"
|
|
19
|
+
import { PRERENDERED_LIST_PATH } from "../router.ts"
|
|
20
|
+
import { formatShadowedPages, shadowedPages } from "./mount-shadow.ts"
|
|
12
21
|
import { createPageRequestExecutor, type NonceResolver } from "./page-execution.ts"
|
|
13
|
-
import { urlPartsFor } from "./request-url.ts"
|
|
14
22
|
|
|
15
23
|
export type { NonceResolver } from "./page-execution.ts"
|
|
16
24
|
export interface CreateWebAppOptions<Env = unknown> {
|
|
@@ -28,6 +36,13 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
28
36
|
* policy's allowed sources are app-specific.
|
|
29
37
|
*/
|
|
30
38
|
readonly nonce?: NonceResolver<Env>
|
|
39
|
+
/**
|
|
40
|
+
* A hash-based Content-Security-Policy from `createCspPolicy`, instead of `nonce`. A document then
|
|
41
|
+
* carries a nonce only when it needs one (it `defer()`s a value, or `meta` names the nonce in
|
|
42
|
+
* `unsafeInlineScript`); every other page is nonce-free, gets the same CSP header on every request,
|
|
43
|
+
* and stays cacheable by `withISR` or a CDN. `meta` still receives a nonce to name.
|
|
44
|
+
*/
|
|
45
|
+
readonly csp?: CspPolicy
|
|
31
46
|
/**
|
|
32
47
|
* Options for the underlying `server()` - `requestTimeoutMs`, `admission`, `gracefulSignals`, and
|
|
33
48
|
* the rest of {@link ServerOptions}.
|
|
@@ -89,14 +104,26 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
89
104
|
* mount interface from `@nifrajs/core/mount`; `createWebApp` also serves that backend over HTTP at
|
|
90
105
|
* {@link apiPrefix} (default `/api`): a request whose pathname starts with the prefix is dispatched
|
|
91
106
|
* before page routing with the same `env`/`waitUntil` platform context, and the backend's `Response`
|
|
92
|
-
* is returned untouched. The mount runs in `nifra dev` too. Pass `apiPrefix: ""` to disable it.
|
|
107
|
+
* is returned untouched. The mount runs in `nifra dev` too. Pass `apiPrefix: ""` to disable it.
|
|
108
|
+
*
|
|
109
|
+
* **Request-scoped loader calls.** While a page renders, its loaders, actions and boundaries get a
|
|
110
|
+
* `ctx.api` bound to that request's platform identity: a backend handler reached through it sees the
|
|
111
|
+
* visitor's `c.clientIp` (derived under this app's `server.clientIp` trust declaration), `c.env` and
|
|
112
|
+
* `c.waitUntil`, so a per-caller rate limit or audit field keys on the real visitor. `c.clientIp` is
|
|
113
|
+
* the supported way to read the caller - identity travels in the platform, so rebuilding a `Request`
|
|
114
|
+
* does not lose it. Headers do NOT travel: the page request's `cookie` and `authorization` are never
|
|
115
|
+
* copied, so a loader call is anonymous unless the loader passes headers on the call itself. */
|
|
93
116
|
readonly api?: unknown
|
|
94
117
|
/** HTTP path prefix the {@link api} backend is auto-mounted at (default `"/api"`). A request whose
|
|
95
118
|
* pathname is exactly the prefix or starts with `prefix + "/"` is dispatched to the backend before
|
|
96
119
|
* page routing; the backend therefore defines its routes at the **full** path (`server().post("/api/
|
|
97
120
|
* sync", …)`), matching the in-process `inProcessClient` call sites. Set to `""` to disable the
|
|
98
121
|
* auto-mount entirely (the app serves pages only and `api` stays a loader-only `ctx.api`). Mounting
|
|
99
|
-
* is also a no-op when `api` does not expose the symbol mount.
|
|
122
|
+
* is also a no-op when `api` does not expose the symbol mount.
|
|
123
|
+
*
|
|
124
|
+
* A page file under the prefix (`routes/api/report.tsx`) could never render - the backend answers
|
|
125
|
+
* first and its 404 is final - so `createWebApp` throws, naming the file. The same holds for every
|
|
126
|
+
* other pre-route mount. */
|
|
100
127
|
readonly apiPrefix?: string
|
|
101
128
|
/**
|
|
102
129
|
* Strip {@link apiPrefix} from the pathname before dispatching to `api` (default `false`).
|
|
@@ -118,14 +145,16 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
118
145
|
*
|
|
119
146
|
* Tried longest-path-first and BEFORE the `api` mount, so a mount at `/api/auth` wins over a backend
|
|
120
147
|
* at `/api` no matter which was declared first. `stripPrefix` is the per-mount form of {@link apiStrip}:
|
|
121
|
-
* leave it off to pass the full path through.
|
|
148
|
+
* leave it off to pass the full path through. `opaque` states why a mount whose routes nifra cannot
|
|
149
|
+
* read (better-auth's handler) is outside assurance; it is then listed as a known gap.
|
|
122
150
|
*/
|
|
123
151
|
readonly mounts?: ReadonlyArray<{
|
|
124
152
|
readonly path: string
|
|
125
|
-
readonly app:
|
|
126
|
-
fetch(request: Request, platform?: Platform<Env>): Response | Promise<Response>
|
|
127
|
-
}
|
|
153
|
+
readonly app: MountableApp<Env>
|
|
128
154
|
readonly stripPrefix?: boolean
|
|
155
|
+
readonly priority?: number
|
|
156
|
+
readonly fallbackOn?: 404
|
|
157
|
+
readonly opaque?: string
|
|
129
158
|
}>
|
|
130
159
|
/** Secret for **draft / preview mode** (see `enableDraft`). When set, a request carrying a valid
|
|
131
160
|
* signed `__nifra_draft` cookie gets `ctx.draft === true` in loaders/actions (else always `false`).
|
|
@@ -152,14 +181,19 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
152
181
|
*/
|
|
153
182
|
readonly cssLoading?: CssLoadingMode
|
|
154
183
|
/** SSG: the prerendered-path set (e.g. `enumerateStaticRoutes(routes).paths` or the build's
|
|
155
|
-
* `prerendered.json`).
|
|
156
|
-
* into a prerendered route fetches its static `_data.json` instead of hitting the worker.
|
|
184
|
+
* `prerendered.json`). Handed over as `window.__NIFRA_PRERENDERED__` on every page so a client soft-nav
|
|
185
|
+
* into a prerendered route fetches its static `_data.json` instead of hitting the worker. A set over
|
|
186
|
+
* 4 KB of JSON is served once from `/__nifra/prerendered.json?v=<version>`, cacheable for good, and
|
|
187
|
+
* pages hand over only that URL; `prerenderRoutes` writes the file into a static output. */
|
|
157
188
|
readonly prerenderedPaths?: readonly string[]
|
|
158
189
|
/** Publish the project's `AGENTS.md` inside `/llms.txt` and `/llms-full.txt`. **Off by default**:
|
|
159
190
|
* those endpoints are public and unauthenticated, while `AGENTS.md` is a repo file written for the
|
|
160
191
|
* team - unreleased feature names, internal hostnames, and "don't touch X yet" notes live there
|
|
161
192
|
* routinely. Turn it on only for a repo whose guidelines you would publish as a page. */
|
|
162
193
|
readonly publishLocalGuidelines?: boolean
|
|
194
|
+
/** Serve `/llms.txt` and `/llms-full.txt` (default `true`): the page routes, and the backend routes
|
|
195
|
+
* when the backend is mounted over HTTP at {@link apiPrefix}. `false` registers neither path. */
|
|
196
|
+
readonly llmsTxt?: boolean
|
|
163
197
|
/** SSG: per dynamic route pattern, its `getStaticPaths` `fallback` (from `enumerateStaticRoutes` or
|
|
164
198
|
* the build's `prerendered.json`). A route mapped to `"404"` rejects any path NOT in
|
|
165
199
|
* `prerenderedPaths` with the 404 page - the unlisted path simply doesn't exist. `"ssr"` (the
|
|
@@ -184,14 +218,6 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
184
218
|
) => void
|
|
185
219
|
}
|
|
186
220
|
|
|
187
|
-
/** The handler context fields createWebApp uses - a structural subset of nifra's `Context`. */
|
|
188
|
-
interface RouteContext<Env = unknown> {
|
|
189
|
-
readonly params: Record<string, string>
|
|
190
|
-
readonly req: Request
|
|
191
|
-
/** Platform bindings (Workers env), forwarded to each route's loader/action as `args.env`. */
|
|
192
|
-
readonly env: Env
|
|
193
|
-
}
|
|
194
|
-
|
|
195
221
|
/**
|
|
196
222
|
* Re-issue `request` with `prefix` removed from its pathname.
|
|
197
223
|
*
|
|
@@ -205,32 +231,47 @@ interface RouteContext<Env = unknown> {
|
|
|
205
231
|
* The `Request` is rebuilt rather than mutated (`url` is read-only), preserving method, headers, body,
|
|
206
232
|
* and duplex streaming - the body is passed through unread, so a large or streamed upload is untouched.
|
|
207
233
|
*/
|
|
208
|
-
function stripMountPrefix(request: Request, prefix: string): Request {
|
|
209
|
-
const url = new URL(request.url)
|
|
210
|
-
const rest = url.pathname.slice(prefix.length)
|
|
211
|
-
url.pathname = rest === "" ? "/" : rest
|
|
212
|
-
// `url.href`, not the `URL`: this type set only declares the string and Request overloads.
|
|
213
|
-
return new Request(url.href, request)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
234
|
/**
|
|
217
235
|
* Resolve the explicit symbol-keyed backend mount interface. The symbol seam forwards platform
|
|
218
236
|
* context without making web depend on client.
|
|
219
237
|
*/
|
|
220
|
-
function backendMountOf<Env>(api: unknown):
|
|
238
|
+
function backendMountOf<Env>(api: unknown): MountableApp<Env> | undefined {
|
|
221
239
|
if ((typeof api !== "object" && typeof api !== "function") || api === null) return undefined
|
|
222
240
|
const explicit = (api as Partial<BackendMount<Env>>)[NIFRA_BACKEND_MOUNT]
|
|
223
241
|
if (typeof explicit !== "function") return undefined
|
|
224
|
-
|
|
242
|
+
const websocket = (api as Partial<BackendMount<Env>>)[NIFRA_BACKEND_WS_MOUNT]
|
|
243
|
+
const runtime = (api as Partial<BackendMount<Env>>)[NIFRA_BACKEND_WS_RUNTIME]
|
|
244
|
+
const evidence = (api as Partial<BackendMount<Env>>)[NIFRA_BACKEND_EVIDENCE]
|
|
245
|
+
const mount: {
|
|
246
|
+
fetch: MountableApp<Env>["fetch"]
|
|
247
|
+
resolveWebSocketUpgrade?: BackendWebSocketMountHandler<Env>
|
|
248
|
+
[NIFRA_BACKEND_WS_RUNTIME]?: NonNullable<BackendMount<Env>[typeof NIFRA_BACKEND_WS_RUNTIME]>
|
|
249
|
+
[NIFRA_BACKEND_EVIDENCE]?: BackendEvidenceProvider
|
|
250
|
+
} = {
|
|
251
|
+
fetch: (request, platform) => explicit.call(api, request, platform),
|
|
252
|
+
}
|
|
253
|
+
if (typeof websocket === "function") {
|
|
254
|
+
mount.resolveWebSocketUpgrade = (request, platform) => websocket.call(api, request, platform)
|
|
255
|
+
}
|
|
256
|
+
if (typeof runtime === "function") mount[NIFRA_BACKEND_WS_RUNTIME] = runtime
|
|
257
|
+
if (typeof evidence === "function") mount[NIFRA_BACKEND_EVIDENCE] = evidence
|
|
258
|
+
return mount
|
|
225
259
|
}
|
|
226
260
|
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
261
|
+
function evidenceProviderOf(value: unknown): BackendEvidenceProvider | undefined {
|
|
262
|
+
if ((typeof value !== "object" && typeof value !== "function") || value === null) return undefined
|
|
263
|
+
const provider = (value as { [NIFRA_BACKEND_EVIDENCE]?: unknown })[NIFRA_BACKEND_EVIDENCE]
|
|
264
|
+
return typeof provider === "function" ? (provider as BackendEvidenceProvider) : undefined
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function mountPathPrefix(path: string): string {
|
|
268
|
+
const withoutWildcard = path.endsWith("/*") ? path.slice(0, -2) : path
|
|
269
|
+
if (withoutWildcard === "" || withoutWildcard === "/") return ""
|
|
270
|
+
// Index-scan instead of `/\/+$/`: mount paths can be library input, and the
|
|
271
|
+
// unanchored-start trailing-run replacement can backtrack quadratically.
|
|
272
|
+
let end = withoutWildcard.length
|
|
273
|
+
while (end > 0 && withoutWildcard.charCodeAt(end - 1) === 47 /* '/' */) end--
|
|
274
|
+
return withoutWildcard.slice(0, end)
|
|
234
275
|
}
|
|
235
276
|
|
|
236
277
|
/**
|
|
@@ -251,6 +292,12 @@ export function createWebApp<Env = unknown>(
|
|
|
251
292
|
options: CreateWebAppOptions<Env>,
|
|
252
293
|
): ReturnType<typeof server<Env>> {
|
|
253
294
|
const { adapter, manifest, clientEntry, title, api } = options
|
|
295
|
+
if (options.csp !== undefined && options.nonce !== undefined) {
|
|
296
|
+
throw new TypeError("[nifra/web] createWebApp takes `csp` or `nonce`, not both")
|
|
297
|
+
}
|
|
298
|
+
if (options.csp !== undefined && !isCspPolicy(options.csp)) {
|
|
299
|
+
throw new TypeError("[nifra/web] createWebApp `csp` must come from createCspPolicy()")
|
|
300
|
+
}
|
|
254
301
|
// Seed the context with the declared `Env` so `app.fetch(req, { env })` / `toFetchHandler(app)` type
|
|
255
302
|
// the platform bindings (see the `createWebApp` doc). The runtime `env` still arrives per-request via
|
|
256
303
|
// `app.fetch(req, { env })`; this is a compile-time-only seed (`server<Env>()` casts, doesn't store).
|
|
@@ -259,43 +306,60 @@ export function createWebApp<Env = unknown>(
|
|
|
259
306
|
// `beforeHandle`, assurance evidence) actually bind to them - see the `use` option. A caller
|
|
260
307
|
// cannot do this after the fact: by the time this function returns, the routes are declared.
|
|
261
308
|
options.use?.(app)
|
|
309
|
+
// A resolver created by `createNonceResolver({ header })` carries a paired response hook. Register it
|
|
310
|
+
// after caller middleware so its request-specific CSP value is the last default applied to a page.
|
|
311
|
+
const nonceResponse = options.nonce?.onResponse
|
|
312
|
+
if (nonceResponse !== undefined) app.onResponse(nonceResponse)
|
|
313
|
+
// How this app's documents meet a CSP, for wrappers that cache them (`withISR` warns on "nonce").
|
|
314
|
+
const documentPolicy =
|
|
315
|
+
options.csp !== undefined ? "hash" : options.nonce !== undefined ? "nonce" : undefined
|
|
316
|
+
if (documentPolicy !== undefined) {
|
|
317
|
+
Object.defineProperty(app, DOCUMENT_POLICY, { value: documentPolicy })
|
|
318
|
+
}
|
|
319
|
+
const cacheChannel = { enabled: false }
|
|
320
|
+
Object.defineProperty(app, CACHE_CHANNEL, {
|
|
321
|
+
value: () => {
|
|
322
|
+
cacheChannel.enabled = true
|
|
323
|
+
},
|
|
324
|
+
})
|
|
262
325
|
// Auto-mount the in-process backend over HTTP at `apiPrefix` (default `/api`), BEFORE page routing.
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
326
|
+
// Core's pre-route mount seam runs before the page wildcard `/*`; parent request hooks run first,
|
|
327
|
+
// child request hooks run inside the mount, and parent response hooks still wrap the result.
|
|
328
|
+
// The mount handles every HTTP method and preserves backend 404 responses instead of letting the
|
|
329
|
+
// page catch-all render them. We dispatch the same `Request` object (its body unread), so
|
|
330
|
+
// streamed/large bodies pass through untouched. The backend defines full prefixed paths
|
|
331
|
+
// (`server().post("/api/sync", …)`) unless `apiStrip` is enabled. `apiPrefix: ""` opts out;
|
|
332
|
+
// a non-mountable `api` remains loader-only and pages-only apps keep the no-mount fast path.
|
|
270
333
|
const apiPrefix = options.apiPrefix ?? "/api"
|
|
271
334
|
const apiStrip = options.apiStrip === true
|
|
272
335
|
const mountedApi = backendMountOf<Env>(api)
|
|
273
336
|
// Longest path first, so a more specific mount (`/api/auth`) is tried before a broader one (`/api`)
|
|
274
337
|
// regardless of the order they were declared in.
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
// Exactly the prefix (`/api`) or a sub-path (`/api/…`) - NOT a sibling like `/apixyz` that merely
|
|
292
|
-
// shares the prefix as a string head. Dispatch the original `req` (body intact) to the backend.
|
|
293
|
-
if (mountedApi !== undefined && apiPrefix !== "" && underPrefix(pathname, apiPrefix)) {
|
|
294
|
-
return mountedApi(apiStrip ? stripMountPrefix(req, apiPrefix) : req, platform)
|
|
295
|
-
}
|
|
296
|
-
return undefined // not an API path → continue to page routing
|
|
297
|
-
})
|
|
338
|
+
const configuredMounts: readonly MountOptions<Env>[] = Object.freeze(
|
|
339
|
+
(options.mounts ?? []).map(
|
|
340
|
+
(mount): MountOptions<Env> => ({
|
|
341
|
+
path: mount.path,
|
|
342
|
+
app: mount.app,
|
|
343
|
+
...(mount.stripPrefix === undefined ? {} : { stripPrefix: mount.stripPrefix }),
|
|
344
|
+
...(mount.priority === undefined ? {} : { priority: mount.priority }),
|
|
345
|
+
...(mount.fallbackOn === undefined ? {} : { fallbackOn: mount.fallbackOn }),
|
|
346
|
+
...(mount.opaque === undefined ? {} : { opaque: mount.opaque }),
|
|
347
|
+
}),
|
|
348
|
+
),
|
|
349
|
+
)
|
|
350
|
+
const mounts: Array<MountOptions<Env>> = [...configuredMounts]
|
|
351
|
+
if (mountedApi !== undefined && apiPrefix !== "") {
|
|
352
|
+
mounts.push({ path: apiPrefix, app: mountedApi, stripPrefix: apiStrip })
|
|
298
353
|
}
|
|
354
|
+
for (const mount of mounts) app.mount(mount)
|
|
355
|
+
// Read back the server's own mount table, so a mount added in `use` counts as well: a page under any
|
|
356
|
+
// pre-route mount is unreachable, and serving it as a silent 404 hides that.
|
|
357
|
+
const shadowed = shadowedPages(manifest, preRouteMountPaths(app))
|
|
358
|
+
if (shadowed.length > 0) throw new Error(`[nifra/web] ${formatShadowedPages(shadowed)}`)
|
|
359
|
+
const ownPatterns = new Set(manifest.routes.map((route) => route.pattern))
|
|
360
|
+
const prerenderedList = ownPatterns.has(PRERENDERED_LIST_PATH)
|
|
361
|
+
? undefined
|
|
362
|
+
: servedPrerenderedList(options.prerenderedPaths)
|
|
299
363
|
const pageExecutor = createPageRequestExecutor<Env>({
|
|
300
364
|
adapter,
|
|
301
365
|
manifest,
|
|
@@ -310,38 +374,152 @@ export function createWebApp<Env = unknown>(
|
|
|
310
374
|
...(options.prerenderedPaths === undefined
|
|
311
375
|
? {}
|
|
312
376
|
: { prerenderedPaths: options.prerenderedPaths }),
|
|
377
|
+
...(prerenderedList === undefined ? {} : { prerenderedListUrl: prerenderedList.url }),
|
|
313
378
|
...(options.staticFallbacks === undefined ? {} : { staticFallbacks: options.staticFallbacks }),
|
|
314
379
|
...(options.staticBoundaryCache === undefined
|
|
315
380
|
? {}
|
|
316
381
|
: { staticBoundaryCache: options.staticBoundaryCache }),
|
|
317
382
|
...(options.nonce === undefined ? {} : { nonce: options.nonce }),
|
|
383
|
+
...(options.csp === undefined
|
|
384
|
+
? {}
|
|
385
|
+
: { nonce: cspNonceResolver(options.csp) as NonceResolver<Env>, csp: options.csp }),
|
|
318
386
|
...(options.onLoaderError === undefined ? {} : { onLoaderError: options.onLoaderError }),
|
|
387
|
+
cacheChannel,
|
|
319
388
|
})
|
|
320
389
|
|
|
321
390
|
for (const route of manifest.routes) {
|
|
322
391
|
app.register("GET", route.pattern, undefined, pageExecutor.get(route))
|
|
323
392
|
app.register("POST", route.pattern, undefined, pageExecutor.post(route))
|
|
324
393
|
}
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
app
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
394
|
+
// llms.txt & llms-full.txt describe what a visitor can reach: the pages, and the backend only when it
|
|
395
|
+
// is served over HTTP - a backend the loaders call in-process has no public surface to describe. An
|
|
396
|
+
// app route at either path takes precedence, and each text is built once per app.
|
|
397
|
+
if (options.llmsTxt !== false) {
|
|
398
|
+
const llmsOptions = { includeLocalGuidelines: options.publishLocalGuidelines === true }
|
|
399
|
+
const publicApi = mountedApi !== undefined && apiPrefix !== "" ? api : undefined
|
|
400
|
+
for (const [path, full] of [
|
|
401
|
+
["/llms.txt", false],
|
|
402
|
+
["/llms-full.txt", true],
|
|
403
|
+
] as const) {
|
|
404
|
+
if (ownPatterns.has(path)) continue
|
|
405
|
+
let text: Promise<string> | undefined
|
|
406
|
+
app.register("GET", path, undefined, async () => {
|
|
407
|
+
text ??= generateLlmsTxt(full, manifest.routes, publicApi, llmsOptions).catch((error) => {
|
|
408
|
+
text = undefined
|
|
409
|
+
throw error
|
|
410
|
+
})
|
|
411
|
+
return new Response(await text, {
|
|
412
|
+
headers: { "content-type": "text/plain; charset=utf-8" },
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
}
|
|
333
417
|
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
418
|
+
if (prerenderedList !== undefined) {
|
|
419
|
+
const { body, version } = prerenderedList
|
|
420
|
+
app.register(
|
|
421
|
+
"GET",
|
|
422
|
+
PRERENDERED_LIST_PATH,
|
|
423
|
+
undefined,
|
|
424
|
+
(c: { readonly req: Request }) =>
|
|
425
|
+
new Response(body, {
|
|
426
|
+
headers: {
|
|
427
|
+
"content-type": "application/json",
|
|
428
|
+
// Only the versioned URL pages hand over is immutable; a bare request gets today's list.
|
|
429
|
+
"cache-control":
|
|
430
|
+
new URL(c.req.url).searchParams.get("v") === version
|
|
431
|
+
? "public, max-age=31536000, immutable"
|
|
432
|
+
: "no-cache",
|
|
433
|
+
},
|
|
434
|
+
}),
|
|
435
|
+
)
|
|
436
|
+
}
|
|
340
437
|
|
|
341
|
-
// Wildcard catch-all: unmatched paths render `_404` (404), or a plain text 404 if
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
438
|
+
// Wildcard catch-all: unmatched paths render the nearest `_404` (404), or a plain text 404 if
|
|
439
|
+
// absent.
|
|
440
|
+
app.register("GET", "/*", undefined, pageExecutor.fallback)
|
|
441
|
+
|
|
442
|
+
let evidenceComposing = false
|
|
443
|
+
const evidenceProvider: BackendEvidenceProvider = async () => {
|
|
444
|
+
if (evidenceComposing) {
|
|
445
|
+
throw new Error("[nifra/web] cyclic composed assurance evidence mount")
|
|
446
|
+
}
|
|
447
|
+
evidenceComposing = true
|
|
448
|
+
try {
|
|
449
|
+
const { composeProjectEvidence, snapshotProjectEvidence } = await import(
|
|
450
|
+
"@nifrajs/core/evidence"
|
|
451
|
+
)
|
|
452
|
+
const parts: Array<{
|
|
453
|
+
readonly evidence: import("@nifrajs/core/evidence").ProjectEvidenceSnapshot
|
|
454
|
+
readonly pathPrefix?: string
|
|
455
|
+
}> = [{ evidence: snapshotProjectEvidence(app) }]
|
|
456
|
+
for (const mount of configuredMounts) {
|
|
457
|
+
const provider = evidenceProviderOf(mount.app)
|
|
458
|
+
if (provider === undefined) {
|
|
459
|
+
// A declared opaque mount reaches the snapshot as a known gap, through this app's own
|
|
460
|
+
// mount reflection above.
|
|
461
|
+
if (mount.opaque !== undefined) continue
|
|
462
|
+
throw new Error(
|
|
463
|
+
`[nifra/web] cannot compose assurance evidence: mount "${mount.path}" has no token-only evidence provider - declare why it is not analyzed with { opaque: "<reason>" }`,
|
|
464
|
+
)
|
|
465
|
+
}
|
|
466
|
+
parts.push({
|
|
467
|
+
evidence: await provider.call(mount.app),
|
|
468
|
+
...(mount.stripPrefix === true ? { pathPrefix: mountPathPrefix(mount.path) } : {}),
|
|
469
|
+
})
|
|
470
|
+
}
|
|
471
|
+
if (mountedApi !== undefined && apiPrefix !== "") {
|
|
472
|
+
const provider = evidenceProviderOf(api)
|
|
473
|
+
if (provider === undefined) {
|
|
474
|
+
throw new Error(
|
|
475
|
+
`[nifra/web] cannot compose assurance evidence: auto-mounted API at "${apiPrefix}" has no token-only evidence provider`,
|
|
476
|
+
)
|
|
477
|
+
}
|
|
478
|
+
parts.push({
|
|
479
|
+
evidence: await provider.call(api),
|
|
480
|
+
...(apiStrip ? { pathPrefix: mountPathPrefix(apiPrefix) } : {}),
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
return composeProjectEvidence(parts)
|
|
484
|
+
} finally {
|
|
485
|
+
evidenceComposing = false
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
Object.defineProperty(app, NIFRA_BACKEND_EVIDENCE, {
|
|
489
|
+
configurable: false,
|
|
490
|
+
enumerable: false,
|
|
491
|
+
value: evidenceProvider,
|
|
492
|
+
writable: false,
|
|
493
|
+
})
|
|
345
494
|
|
|
346
495
|
return app
|
|
347
496
|
}
|
|
497
|
+
|
|
498
|
+
/** Resolve one composed, token-only evidence snapshot for a web app and its mounted surfaces. */
|
|
499
|
+
export async function webProjectEvidence(
|
|
500
|
+
source: unknown,
|
|
501
|
+
): Promise<import("@nifrajs/core/evidence").ProjectEvidenceSnapshot> {
|
|
502
|
+
const provider = evidenceProviderOf(source)
|
|
503
|
+
if (provider === undefined) {
|
|
504
|
+
throw new TypeError("webProjectEvidence(): expected an app created by createWebApp")
|
|
505
|
+
}
|
|
506
|
+
return provider.call(source)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Up to this many characters of JSON, the prerendered set rides inline in each page's handover.
|
|
510
|
+
const INLINE_PRERENDERED_CHARS = 4096
|
|
511
|
+
|
|
512
|
+
/** The prerendered set as a served, content-versioned list, when it is too large to inline. */
|
|
513
|
+
function servedPrerenderedList(
|
|
514
|
+
paths: readonly string[] | undefined,
|
|
515
|
+
): { readonly body: string; readonly version: string; readonly url: string } | undefined {
|
|
516
|
+
if (paths === undefined || paths.length === 0) return undefined
|
|
517
|
+
const body = JSON.stringify(paths)
|
|
518
|
+
if (body.length <= INLINE_PRERENDERED_CHARS) return undefined
|
|
519
|
+
// FNV-1a: a version that changes with the list is all the URL needs; a stale cached list only
|
|
520
|
+
// costs a fallback to the dynamic data request.
|
|
521
|
+
let hash = 0x811c9dc5
|
|
522
|
+
for (let i = 0; i < body.length; i++) hash = Math.imul(hash ^ body.charCodeAt(i), 0x01000193)
|
|
523
|
+
const version = (hash >>> 0).toString(16).padStart(8, "0")
|
|
524
|
+
return { body, version, url: `${PRERENDERED_LIST_PATH}?v=${version}` }
|
|
525
|
+
}
|