@nifrajs/web 5.5.1 → 5.7.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/CHANGELOG.md +243 -0
- package/README.md +21 -11
- package/dist/boundary.d.ts +67 -7
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +6 -4
- package/dist/boundary.js.map +1 -1
- package/dist/build-plan.d.ts +3 -0
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +2 -0
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +9 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +10 -0
- package/dist/build.js.map +1 -1
- package/dist/client.d.ts +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +174 -23
- package/dist/client.js.map +1 -1
- package/dist/csp.d.ts +8 -0
- package/dist/csp.d.ts.map +1 -1
- package/dist/csp.js +11 -2
- package/dist/csp.js.map +1 -1
- package/dist/deferred.d.ts +6 -4
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +10 -6
- package/dist/deferred.js.map +1 -1
- package/dist/dev-feed.d.ts.map +1 -1
- package/dist/dev-feed.js +11 -2
- package/dist/dev-feed.js.map +1 -1
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +13 -6
- package/dist/dev-session.js.map +1 -1
- package/dist/dev.d.ts +4 -1
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +12 -5
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic-catalog.d.ts +4 -0
- package/dist/diagnostic-catalog.d.ts.map +1 -1
- package/dist/diagnostic-catalog.js +48 -0
- package/dist/diagnostic-catalog.js.map +1 -1
- package/dist/diagnostic.d.ts +1 -1
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +1 -1
- package/dist/diagnostic.js.map +1 -1
- package/dist/fn.d.ts +5 -3
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +20 -5
- package/dist/fn.js.map +1 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +13 -3
- package/dist/fs.js.map +1 -1
- package/dist/identity.d.ts +22 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +60 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/cdn/cloudflare.d.ts +2 -1
- package/dist/internal/cdn/cloudflare.d.ts.map +1 -1
- package/dist/internal/cdn/cloudflare.js +2 -1
- package/dist/internal/cdn/cloudflare.js.map +1 -1
- package/dist/internal/certified-components.d.ts +1 -1
- package/dist/internal/certified-components.js +1 -1
- package/dist/internal/codegen.d.ts +11 -10
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +73 -10
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/dev-websocket.d.ts +73 -0
- package/dist/internal/dev-websocket.d.ts.map +1 -0
- package/dist/internal/dev-websocket.js +145 -0
- package/dist/internal/dev-websocket.js.map +1 -0
- package/dist/internal/hole-runtime.d.ts +66 -0
- package/dist/internal/hole-runtime.d.ts.map +1 -0
- package/dist/internal/hole-runtime.js +211 -0
- package/dist/internal/hole-runtime.js.map +1 -0
- package/dist/internal/identity-epoch.d.ts +50 -0
- package/dist/internal/identity-epoch.d.ts.map +1 -0
- package/dist/internal/identity-epoch.js +180 -0
- package/dist/internal/identity-epoch.js.map +1 -0
- package/dist/internal/invalidation-contract.d.ts +23 -0
- package/dist/internal/invalidation-contract.d.ts.map +1 -0
- package/dist/internal/invalidation-contract.js +11 -0
- package/dist/internal/invalidation-contract.js.map +1 -0
- package/dist/internal/invalidation.d.ts +62 -0
- package/dist/internal/invalidation.d.ts.map +1 -0
- package/dist/internal/invalidation.js +149 -0
- package/dist/internal/invalidation.js.map +1 -0
- package/dist/internal/island-bindings.d.ts +51 -0
- package/dist/internal/island-bindings.d.ts.map +1 -0
- package/dist/internal/island-bindings.js +147 -0
- package/dist/internal/island-bindings.js.map +1 -0
- package/dist/internal/island-list.d.ts +26 -0
- package/dist/internal/island-list.d.ts.map +1 -0
- package/dist/internal/island-list.js +78 -0
- package/dist/internal/island-list.js.map +1 -0
- package/dist/internal/island-mount.d.ts +4 -0
- package/dist/internal/island-mount.d.ts.map +1 -0
- package/dist/internal/island-mount.js +66 -0
- package/dist/internal/island-mount.js.map +1 -0
- package/dist/internal/island-owner.d.ts +5 -0
- package/dist/internal/island-owner.d.ts.map +1 -0
- package/dist/internal/island-owner.js +20 -0
- package/dist/internal/island-owner.js.map +1 -0
- package/dist/internal/island-resource.d.ts +30 -0
- package/dist/internal/island-resource.d.ts.map +1 -0
- package/dist/internal/island-resource.js +57 -0
- package/dist/internal/island-resource.js.map +1 -0
- package/dist/internal/island-signals.d.ts +51 -0
- package/dist/internal/island-signals.d.ts.map +1 -0
- package/dist/internal/island-signals.js +220 -0
- package/dist/internal/island-signals.js.map +1 -0
- package/dist/internal/keep-alive-table.d.ts +23 -0
- package/dist/internal/keep-alive-table.d.ts.map +1 -0
- package/dist/internal/keep-alive-table.js +53 -0
- package/dist/internal/keep-alive-table.js.map +1 -0
- package/dist/internal/live-contract.d.ts +101 -0
- package/dist/internal/live-contract.d.ts.map +1 -0
- package/dist/internal/live-contract.js +5 -0
- package/dist/internal/live-contract.js.map +1 -0
- package/dist/internal/live-queries.d.ts +22 -0
- package/dist/internal/live-queries.d.ts.map +1 -0
- package/dist/internal/live-queries.js +54 -0
- package/dist/internal/live-queries.js.map +1 -0
- package/dist/internal/page-events.d.ts +10 -0
- package/dist/internal/page-events.d.ts.map +1 -0
- package/dist/internal/page-events.js +26 -0
- package/dist/internal/page-events.js.map +1 -0
- package/dist/internal/page-execution.d.ts +16 -1
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +432 -31
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts +3 -0
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +28 -5
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/ppr/admission.d.ts +83 -0
- package/dist/internal/ppr/admission.d.ts.map +1 -0
- package/dist/internal/ppr/admission.js +221 -0
- package/dist/internal/ppr/admission.js.map +1 -0
- package/dist/internal/ppr/canary.d.ts +24 -0
- package/dist/internal/ppr/canary.d.ts.map +1 -0
- package/dist/internal/ppr/canary.js +102 -0
- package/dist/internal/ppr/canary.js.map +1 -0
- package/dist/internal/ppr/compile-shell.d.ts +39 -0
- package/dist/internal/ppr/compile-shell.d.ts.map +1 -0
- package/dist/internal/ppr/compile-shell.js +298 -0
- package/dist/internal/ppr/compile-shell.js.map +1 -0
- package/dist/internal/ppr/contract.d.ts +177 -0
- package/dist/internal/ppr/contract.d.ts.map +1 -0
- package/dist/internal/ppr/contract.js +80 -0
- package/dist/internal/ppr/contract.js.map +1 -0
- package/dist/internal/ppr/frames.d.ts +71 -0
- package/dist/internal/ppr/frames.d.ts.map +1 -0
- package/dist/internal/ppr/frames.js +119 -0
- package/dist/internal/ppr/frames.js.map +1 -0
- package/dist/internal/ppr/integration.d.ts +19 -0
- package/dist/internal/ppr/integration.d.ts.map +1 -0
- package/dist/internal/ppr/integration.js +106 -0
- package/dist/internal/ppr/integration.js.map +1 -0
- package/dist/internal/ppr/options.d.ts +96 -0
- package/dist/internal/ppr/options.d.ts.map +1 -0
- package/dist/internal/ppr/options.js +125 -0
- package/dist/internal/ppr/options.js.map +1 -0
- package/dist/internal/ppr/route-rules.d.ts +12 -0
- package/dist/internal/ppr/route-rules.d.ts.map +1 -0
- package/dist/internal/ppr/route-rules.js +53 -0
- package/dist/internal/ppr/route-rules.js.map +1 -0
- package/dist/internal/ppr/runtime.d.ts +29 -0
- package/dist/internal/ppr/runtime.d.ts.map +1 -0
- package/dist/internal/ppr/runtime.js +35 -0
- package/dist/internal/ppr/runtime.js.map +1 -0
- package/dist/internal/ppr/server.d.ts +29 -0
- package/dist/internal/ppr/server.d.ts.map +1 -0
- package/dist/internal/ppr/server.js +1409 -0
- package/dist/internal/ppr/server.js.map +1 -0
- package/dist/internal/ppr/shell-store.d.ts +39 -0
- package/dist/internal/ppr/shell-store.d.ts.map +1 -0
- package/dist/internal/ppr/shell-store.js +106 -0
- package/dist/internal/ppr/shell-store.js.map +1 -0
- package/dist/internal/prerendered-pages.d.ts +1 -1
- package/dist/internal/prerendered-pages.d.ts.map +1 -1
- package/dist/internal/prerendered-pages.js +7 -4
- package/dist/internal/prerendered-pages.js.map +1 -1
- package/dist/internal/present.d.ts +11 -0
- package/dist/internal/present.d.ts.map +1 -0
- package/dist/internal/present.js +13 -0
- package/dist/internal/present.js.map +1 -0
- package/dist/internal/query-digest.d.ts +9 -0
- package/dist/internal/query-digest.d.ts.map +1 -0
- package/dist/internal/query-digest.js +23 -0
- package/dist/internal/query-digest.js.map +1 -0
- package/dist/internal/query-runtime.d.ts +6 -1
- package/dist/internal/query-runtime.d.ts.map +1 -1
- package/dist/internal/query-runtime.js +9 -0
- package/dist/internal/query-runtime.js.map +1 -1
- package/dist/internal/query-seam.d.ts +29 -0
- package/dist/internal/query-seam.d.ts.map +1 -0
- package/dist/internal/query-seam.js +23 -0
- package/dist/internal/query-seam.js.map +1 -0
- package/dist/internal/render-document.d.ts +34 -64
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +82 -64
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-abort.d.ts +8 -0
- package/dist/internal/request-abort.d.ts.map +1 -0
- package/dist/internal/request-abort.js +22 -0
- package/dist/internal/request-abort.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +2 -0
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +8 -1
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/share-data.d.ts +10 -0
- package/dist/internal/share-data.d.ts.map +1 -0
- package/dist/internal/share-data.js +135 -0
- package/dist/internal/share-data.js.map +1 -0
- package/dist/internal/status-page.d.ts +68 -0
- package/dist/internal/status-page.d.ts.map +1 -0
- package/dist/internal/status-page.js +66 -0
- package/dist/internal/status-page.js.map +1 -0
- package/dist/internal/store-signature.d.ts +15 -0
- package/dist/internal/store-signature.d.ts.map +1 -0
- package/dist/internal/store-signature.js +114 -0
- package/dist/internal/store-signature.js.map +1 -0
- package/dist/internal/vite-plugin-compat.d.ts +2 -2
- package/dist/internal/vite-plugin-compat.js +2 -2
- package/dist/internal/web-app.d.ts +26 -0
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +79 -3
- package/dist/internal/web-app.js.map +1 -1
- package/dist/islands-swap.d.ts +41 -0
- package/dist/islands-swap.d.ts.map +1 -0
- package/dist/islands-swap.js +270 -0
- package/dist/islands-swap.js.map +1 -0
- package/dist/islands.d.ts +86 -24
- package/dist/islands.d.ts.map +1 -1
- package/dist/islands.js +138 -78
- package/dist/islands.js.map +1 -1
- package/dist/isr.d.ts +67 -13
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +247 -99
- package/dist/isr.js.map +1 -1
- package/dist/keep-alive.d.ts +147 -0
- package/dist/keep-alive.d.ts.map +1 -0
- package/dist/keep-alive.js +1102 -0
- package/dist/keep-alive.js.map +1 -0
- package/dist/live/bus.d.ts +70 -0
- package/dist/live/bus.d.ts.map +1 -0
- package/dist/live/bus.js +55 -0
- package/dist/live/bus.js.map +1 -0
- package/dist/live/core.d.ts +25 -0
- package/dist/live/core.d.ts.map +1 -0
- package/dist/live/core.js +29 -0
- package/dist/live/core.js.map +1 -0
- package/dist/live/history.d.ts +38 -0
- package/dist/live/history.d.ts.map +1 -0
- package/dist/live/history.js +104 -0
- package/dist/live/history.js.map +1 -0
- package/dist/live/hub-protocol.d.ts +46 -0
- package/dist/live/hub-protocol.d.ts.map +1 -0
- package/dist/live/hub-protocol.js +108 -0
- package/dist/live/hub-protocol.js.map +1 -0
- package/dist/live/hub.d.ts +185 -0
- package/dist/live/hub.d.ts.map +1 -0
- package/dist/live/hub.js +544 -0
- package/dist/live/hub.js.map +1 -0
- package/dist/live/keyring.d.ts +92 -0
- package/dist/live/keyring.d.ts.map +1 -0
- package/dist/live/keyring.js +265 -0
- package/dist/live/keyring.js.map +1 -0
- package/dist/live/read-capped.d.ts +6 -0
- package/dist/live/read-capped.d.ts.map +1 -0
- package/dist/live/read-capped.js +42 -0
- package/dist/live/read-capped.js.map +1 -0
- package/dist/live/revocations.d.ts +52 -0
- package/dist/live/revocations.d.ts.map +1 -0
- package/dist/live/revocations.js +116 -0
- package/dist/live/revocations.js.map +1 -0
- package/dist/live/settings.d.ts +14 -0
- package/dist/live/settings.d.ts.map +1 -0
- package/dist/live/settings.js +38 -0
- package/dist/live/settings.js.map +1 -0
- package/dist/live-client.d.ts +34 -0
- package/dist/live-client.d.ts.map +1 -0
- package/dist/live-client.js +878 -0
- package/dist/live-client.js.map +1 -0
- package/dist/live-hub.d.ts +130 -0
- package/dist/live-hub.d.ts.map +1 -0
- package/dist/live-hub.js +507 -0
- package/dist/live-hub.js.map +1 -0
- package/dist/live-redis.d.ts +60 -0
- package/dist/live-redis.d.ts.map +1 -0
- package/dist/live-redis.js +212 -0
- package/dist/live-redis.js.map +1 -0
- package/dist/live.d.ts +150 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +972 -0
- package/dist/live.js.map +1 -0
- package/dist/manifest.d.ts +113 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +109 -5
- package/dist/manifest.js.map +1 -1
- package/dist/navigation.d.ts +9 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +13 -3
- package/dist/navigation.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts +6 -0
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
- package/dist/plugins/vite-zone-guard.js +8 -2
- package/dist/plugins/vite-zone-guard.js.map +1 -1
- package/dist/ppr.d.ts +73 -0
- package/dist/ppr.d.ts.map +1 -0
- package/dist/ppr.js +76 -0
- package/dist/ppr.js.map +1 -0
- package/dist/query.d.ts +8 -13
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +112 -18
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +32 -0
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +7 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-types.d.ts.map +1 -1
- package/dist/route-types.js +2 -2
- package/dist/route-types.js.map +1 -1
- package/dist/router.d.ts +72 -4
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +416 -129
- package/dist/router.js.map +1 -1
- package/dist/server-components/build.d.ts.map +1 -1
- package/dist/server-components/build.js +3 -3
- package/dist/server-components/build.js.map +1 -1
- package/dist/server-components/client.d.ts +23 -1
- package/dist/server-components/client.d.ts.map +1 -1
- package/dist/server-components/client.js +92 -5
- package/dist/server-components/client.js.map +1 -1
- package/dist/server-components/definition.d.ts.map +1 -1
- package/dist/server-components/definition.js +2 -1
- package/dist/server-components/definition.js.map +1 -1
- package/dist/server-components/hole.d.ts +8 -0
- package/dist/server-components/hole.d.ts.map +1 -0
- package/dist/server-components/hole.js +20 -0
- package/dist/server-components/hole.js.map +1 -0
- package/dist/server-components/html-policy.d.ts.map +1 -1
- package/dist/server-components/html-policy.js +62 -4
- package/dist/server-components/html-policy.js.map +1 -1
- package/dist/server-components/html-scan.d.ts +17 -0
- package/dist/server-components/html-scan.d.ts.map +1 -0
- package/dist/server-components/html-scan.js +180 -0
- package/dist/server-components/html-scan.js.map +1 -0
- package/dist/server-components/html.d.ts.map +1 -1
- package/dist/server-components/html.js +19 -4
- package/dist/server-components/html.js.map +1 -1
- package/dist/server-components/router-integration.d.ts +4 -1
- package/dist/server-components/router-integration.d.ts.map +1 -1
- package/dist/server-components/router-integration.js +23 -2
- package/dist/server-components/router-integration.js.map +1 -1
- package/dist/server-components/runtime.d.ts +6 -1
- package/dist/server-components/runtime.d.ts.map +1 -1
- package/dist/server-components/runtime.js +88 -29
- package/dist/server-components/runtime.js.map +1 -1
- package/dist/server-components/state.d.ts +3 -1
- package/dist/server-components/state.d.ts.map +1 -1
- package/dist/server-components/state.js +50 -38
- package/dist/server-components/state.js.map +1 -1
- package/dist/server-components/types.d.ts +9 -0
- package/dist/server-components/types.d.ts.map +1 -1
- package/dist/server-components/vite.d.ts.map +1 -1
- package/dist/server-components/vite.js +26 -9
- package/dist/server-components/vite.js.map +1 -1
- package/dist/vite.d.ts +5 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +58 -4
- package/dist/vite.js.map +1 -1
- package/package.json +48 -8
- package/src/boundary.ts +77 -5
- package/src/build-plan.ts +3 -0
- package/src/build-vite.ts +2 -0
- package/src/build.ts +20 -1
- package/src/client.ts +183 -23
- package/src/csp.ts +16 -2
- package/src/deferred.ts +11 -5
- package/src/dev-feed.ts +11 -2
- package/src/dev-session.ts +22 -11
- package/src/dev.ts +29 -8
- package/src/diagnostic-catalog.ts +53 -0
- package/src/diagnostic.ts +1 -0
- package/src/fn.ts +24 -7
- package/src/fs.ts +22 -8
- package/src/identity.ts +61 -0
- package/src/index.ts +20 -4
- package/src/internal/cdn/cloudflare.ts +2 -1
- package/src/internal/certified-components.ts +1 -1
- package/src/internal/codegen.ts +95 -9
- package/src/internal/dev-websocket.ts +230 -0
- package/src/internal/hole-runtime.ts +286 -0
- package/src/internal/identity-epoch.ts +239 -0
- package/src/internal/invalidation-contract.ts +35 -0
- package/src/internal/invalidation.ts +213 -0
- package/src/internal/island-bindings.ts +191 -0
- package/src/internal/island-list.ts +92 -0
- package/src/internal/island-mount.ts +68 -0
- package/src/internal/island-owner.ts +20 -0
- package/src/internal/island-resource.ts +76 -0
- package/src/internal/island-signals.ts +249 -0
- package/src/internal/keep-alive-table.ts +72 -0
- package/src/internal/live-contract.ts +111 -0
- package/src/internal/live-queries.ts +60 -0
- package/src/internal/page-events.ts +27 -0
- package/src/internal/page-execution.ts +605 -45
- package/src/internal/parity.ts +35 -6
- package/src/internal/ppr/admission.ts +300 -0
- package/src/internal/ppr/canary.ts +122 -0
- package/src/internal/ppr/compile-shell.ts +379 -0
- package/src/internal/ppr/contract.ts +265 -0
- package/src/internal/ppr/frames.ts +170 -0
- package/src/internal/ppr/integration.ts +137 -0
- package/src/internal/ppr/options.ts +221 -0
- package/src/internal/ppr/route-rules.ts +77 -0
- package/src/internal/ppr/runtime.ts +39 -0
- package/src/internal/ppr/server.ts +1766 -0
- package/src/internal/ppr/shell-store.ts +152 -0
- package/src/internal/prerendered-pages.ts +7 -4
- package/src/internal/present.ts +20 -0
- package/src/internal/query-digest.ts +34 -0
- package/src/internal/query-runtime.ts +14 -1
- package/src/internal/query-seam.ts +48 -0
- package/src/internal/render-document.ts +158 -113
- package/src/internal/request-abort.ts +26 -0
- package/src/internal/server-boundary.ts +10 -1
- package/src/internal/share-data.ts +136 -0
- package/src/internal/status-page.ts +95 -0
- package/src/internal/store-signature.ts +149 -0
- package/src/internal/vite-plugin-compat.ts +2 -2
- package/src/internal/web-app.ts +123 -3
- package/src/islands-swap.ts +283 -0
- package/src/islands.ts +169 -72
- package/src/isr.ts +348 -106
- package/src/keep-alive.ts +1354 -0
- package/src/live/bus.ts +104 -0
- package/src/live/core.ts +52 -0
- package/src/live/history.ts +109 -0
- package/src/live/hub-protocol.ts +133 -0
- package/src/live/hub.ts +620 -0
- package/src/live/keyring.ts +384 -0
- package/src/live/read-capped.ts +36 -0
- package/src/live/revocations.ts +155 -0
- package/src/live/settings.ts +46 -0
- package/src/live-client.ts +1011 -0
- package/src/live-hub.ts +658 -0
- package/src/live-redis.ts +284 -0
- package/src/live.ts +1257 -0
- package/src/manifest.ts +274 -6
- package/src/navigation.ts +14 -2
- package/src/plugins/vite-zone-guard.ts +15 -2
- package/src/ppr.ts +153 -0
- package/src/query.ts +147 -20
- package/src/render-seam.ts +37 -0
- package/src/route-types.ts +3 -1
- package/src/router.ts +534 -132
- package/src/server-components/build.ts +9 -3
- package/src/server-components/client.ts +89 -5
- package/src/server-components/definition.ts +2 -1
- package/src/server-components/hole.ts +23 -0
- package/src/server-components/html-policy.ts +63 -4
- package/src/server-components/html-scan.ts +159 -0
- package/src/server-components/html.ts +18 -4
- package/src/server-components/router-integration.ts +21 -2
- package/src/server-components/runtime.ts +114 -31
- package/src/server-components/state.ts +66 -44
- package/src/server-components/types.ts +16 -0
- package/src/server-components/vite.ts +33 -14
- package/src/vite.ts +75 -6
- package/dist/nano.d.ts +0 -125
- package/dist/nano.d.ts.map +0 -1
- package/dist/nano.js +0 -190
- package/dist/nano.js.map +0 -1
- package/src/nano.ts +0 -246
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The terminal-status helpers and their brand, free of server imports: `@nifrajs/web/client` re-exports
|
|
3
|
+
* them for route helpers the browser also loads, which the root entry would drag `node:crypto` into.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Derived from the `Headers` constructor rather than the DOM lib's `HeadersInit`: `@nifrajs/web`
|
|
7
|
+
* is consumed from DOM-free programs (the root tsconfig runs `types: ["bun"]`), and naming the DOM
|
|
8
|
+
* type here would break their build. Same workaround as `SSEInit.headers` in core. */
|
|
9
|
+
export type HeadersLike = ConstructorParameters<typeof Headers>[0]
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Brand marking a `Response` as a **terminal-status signal** rather than a response to serve verbatim.
|
|
13
|
+
*
|
|
14
|
+
* A loader that throws a bare `Response` has always been passed straight through - that is how
|
|
15
|
+
* `throw redirect(...)` works, and apps rely on it. So "render the `_404` boundary at 404" cannot be
|
|
16
|
+
* expressed by throwing a plain `Response`: the framework would have no way to tell it apart from a
|
|
17
|
+
* hand-rolled body the app wants served exactly as written. The brand is that distinction, and
|
|
18
|
+
* checking it *before* the pass-through is what keeps every existing throw working unchanged.
|
|
19
|
+
*
|
|
20
|
+
* `Symbol.for` matches core's `RESPONSE_RESULT`: a registry symbol survives two copies of this module,
|
|
21
|
+
* which a `unique symbol` would not - and a duplicate-install turning a 404 into a raw 404 body would
|
|
22
|
+
* be a maddening bug to trace.
|
|
23
|
+
*/
|
|
24
|
+
export const STATUS_SIGNAL = Symbol.for("nifra.web.status-signal")
|
|
25
|
+
|
|
26
|
+
export interface StatusSignal extends Response {
|
|
27
|
+
readonly [STATUS_SIGNAL]: { readonly status: number; readonly headers?: HeadersLike }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isStatusSignal(value: unknown): value is StatusSignal {
|
|
31
|
+
return value instanceof Response && STATUS_SIGNAL in value
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function statusSignal(status: number, options: StatusPageOptions): never {
|
|
35
|
+
if (!Number.isInteger(status) || status < 400 || status > 599) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`[nifra/web] statusPage(${JSON.stringify(status)}) must be an integer 4xx or 5xx status. Use redirect() for 3xx, and return data for a successful render.`,
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
const response = new Response(null, { status })
|
|
41
|
+
// Non-enumerable so the brand never lands in a structured clone or a JSON log of the response.
|
|
42
|
+
Object.defineProperty(response, STATUS_SIGNAL, {
|
|
43
|
+
value: { status, ...(options.headers !== undefined ? { headers: options.headers } : {}) },
|
|
44
|
+
enumerable: false,
|
|
45
|
+
})
|
|
46
|
+
throw response
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Options shared by {@link notFound}, {@link gone}, and {@link statusPage}. */
|
|
50
|
+
export interface StatusPageOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Extra headers for the rendered response - `cache-control` above all.
|
|
53
|
+
*
|
|
54
|
+
* The defaults differ by status for a reason: a 404 may be a race with publication, so it wants a
|
|
55
|
+
* short TTL, while a 410 is a promise that the URL is permanently gone and can be cached hard.
|
|
56
|
+
* Getting this wrong is the difference between a crawler dropping a dead URL and re-fetching it
|
|
57
|
+
* for weeks.
|
|
58
|
+
*/
|
|
59
|
+
readonly headers?: HeadersLike
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render the nearest `_404` page at status **404**. `throw` it from a loader when the record does not
|
|
64
|
+
* exist.
|
|
65
|
+
*
|
|
66
|
+
* This is the fix for the soft 404: a matched route whose loader finds nothing has otherwise no way to
|
|
67
|
+
* answer 404, so the path of least resistance is to return empty data and render "not found" inside a
|
|
68
|
+
* **200**. That looks correct in a browser and is invisible in review, which is why it ships and stays
|
|
69
|
+
* shipped - while search engines penalise it and keep the dead URL indexed.
|
|
70
|
+
*
|
|
71
|
+
* Returns `never`: these throw, so a loader narrows without a redundant `return`, and the type states
|
|
72
|
+
* the thing the "loaders `throw` redirect, actions `return` it" rule already trips people on.
|
|
73
|
+
*/
|
|
74
|
+
export function notFound(options: StatusPageOptions = {}): never {
|
|
75
|
+
return statusSignal(404, options)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render a terminal page at status **410 Gone**. `throw` it from a loader for a record that existed and
|
|
80
|
+
* was deliberately removed - a withdrawn listing, a deleted post.
|
|
81
|
+
*
|
|
82
|
+
* 410 is not a pedantic 404: it tells a crawler to **drop** the URL rather than re-fetch it for weeks
|
|
83
|
+
* on the assumption the 404 was transient. Uses `_410.tsx` if the app has one, otherwise `_404`.
|
|
84
|
+
*/
|
|
85
|
+
export function gone(options: StatusPageOptions = {}): never {
|
|
86
|
+
return statusSignal(410, options)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render a terminal page at any 4xx/5xx status - the escape hatch behind {@link notFound} and
|
|
91
|
+
* {@link gone} (402, 451, …). Uses `_<status>.tsx` if present, otherwise `_404`.
|
|
92
|
+
*/
|
|
93
|
+
export function statusPage(status: number, options: StatusPageOptions = {}): never {
|
|
94
|
+
return statusSignal(status, options)
|
|
95
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signatures on entries kept in a store other processes share (Workers KV, Redis). Whoever can write
|
|
3
|
+
* such a store could hand every visitor a page this app never built, so each entry carries an
|
|
4
|
+
* HMAC-SHA-256 under the app's secret, and an entry that fails the check reads as a miss.
|
|
5
|
+
*/
|
|
6
|
+
import type * as NodeCrypto from "node:crypto"
|
|
7
|
+
import { timingSafeEqual } from "./timing-safe-equal.ts"
|
|
8
|
+
|
|
9
|
+
const TEXT = new TextEncoder()
|
|
10
|
+
const MAC_HEX = /^[0-9a-f]{64}$/
|
|
11
|
+
// Code units of signed text a signer remembers as proven. A hot page then costs a comparison per
|
|
12
|
+
// hit, not a hash: about 1.5 ns a byte to hash against a fraction of that to compare.
|
|
13
|
+
const MAX_PROVEN_CHARS = 2 * 1024 * 1024
|
|
14
|
+
const MAX_SIGNERS = 4
|
|
15
|
+
|
|
16
|
+
/** Below this, a secret is too short to sign with. */
|
|
17
|
+
export const MIN_STORE_SECRET_LENGTH = 32
|
|
18
|
+
|
|
19
|
+
export const isStoreSecret = (value: unknown): value is string =>
|
|
20
|
+
typeof value === "string" && value.length >= MIN_STORE_SECRET_LENGTH
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Signs a list of strings. Each part is hashed behind its length, so no two lists hash the same
|
|
24
|
+
* bytes, whatever the parts hold.
|
|
25
|
+
*/
|
|
26
|
+
export interface StoreSigner {
|
|
27
|
+
/** The parts' MAC, as lowercase hex. */
|
|
28
|
+
sign(parts: readonly string[]): Promise<string>
|
|
29
|
+
/** Whether `mac` is the parts' MAC under this secret. Anything but 64 lowercase hex digits is not. */
|
|
30
|
+
verify(parts: readonly string[], mac: unknown): Promise<boolean>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type CreateHmac = typeof NodeCrypto.createHmac
|
|
34
|
+
|
|
35
|
+
interface Hasher {
|
|
36
|
+
sign(parts: readonly string[]): Promise<string>
|
|
37
|
+
/** `mac` is already 64 lowercase hex digits. */
|
|
38
|
+
matches(parts: readonly string[], mac: string): Promise<boolean>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Looked up, never imported: a Worker without nodejs_compat cannot load node:crypto. Where it exists
|
|
42
|
+
// it is the cheaper path: Web Crypto answers on another thread, about 20 us a call on Bun, Node and
|
|
43
|
+
// Deno, more than hashing a 16 KB page.
|
|
44
|
+
function nodeCreateHmac(): CreateHmac | undefined {
|
|
45
|
+
const createHmac = globalThis.process?.getBuiltinModule?.("node:crypto")?.createHmac
|
|
46
|
+
return typeof createHmac === "function" ? createHmac : undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sameParts = (a: readonly string[], b: readonly string[]): boolean =>
|
|
50
|
+
a.length === b.length && a.every((part, at) => part === b[at])
|
|
51
|
+
|
|
52
|
+
const lengthOf = (parts: readonly string[]): number =>
|
|
53
|
+
parts.reduce((sum, part) => sum + part.length, 0)
|
|
54
|
+
|
|
55
|
+
// One signer per secret for the process, so a handler built per request (a Worker reads its bindings
|
|
56
|
+
// in `fetch`) still finds the pages earlier requests proved.
|
|
57
|
+
const signers = new Map<string, StoreSigner>()
|
|
58
|
+
|
|
59
|
+
export function storeSigner(secret: string): StoreSigner {
|
|
60
|
+
const existing = signers.get(secret)
|
|
61
|
+
if (existing !== undefined) return existing
|
|
62
|
+
const signer = provingSigner(hasher(secret))
|
|
63
|
+
for (const oldest of signers.keys()) {
|
|
64
|
+
if (signers.size < MAX_SIGNERS) break
|
|
65
|
+
signers.delete(oldest)
|
|
66
|
+
}
|
|
67
|
+
signers.set(secret, signer)
|
|
68
|
+
return signer
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function provingSigner(hashing: Hasher): StoreSigner {
|
|
72
|
+
// MAC to the parts this signer signed or verified, oldest first.
|
|
73
|
+
const proven = new Map<string, readonly string[]>()
|
|
74
|
+
let provenChars = 0
|
|
75
|
+
const remember = (mac: string, parts: readonly string[]): void => {
|
|
76
|
+
const length = lengthOf(parts)
|
|
77
|
+
if (length > MAX_PROVEN_CHARS / 8) return
|
|
78
|
+
const had = proven.get(mac)
|
|
79
|
+
if (had !== undefined) {
|
|
80
|
+
proven.delete(mac)
|
|
81
|
+
provenChars -= lengthOf(had)
|
|
82
|
+
}
|
|
83
|
+
proven.set(mac, parts)
|
|
84
|
+
provenChars += length
|
|
85
|
+
for (const [oldest, kept] of proven) {
|
|
86
|
+
if (provenChars <= MAX_PROVEN_CHARS) break
|
|
87
|
+
proven.delete(oldest)
|
|
88
|
+
provenChars -= lengthOf(kept)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
async sign(parts) {
|
|
93
|
+
const mac = await hashing.sign(parts)
|
|
94
|
+
remember(mac, parts)
|
|
95
|
+
return mac
|
|
96
|
+
},
|
|
97
|
+
async verify(parts, mac) {
|
|
98
|
+
if (typeof mac !== "string" || !MAC_HEX.test(mac)) return false
|
|
99
|
+
const known = proven.get(mac)
|
|
100
|
+
if (known !== undefined && sameParts(known, parts)) return true
|
|
101
|
+
if (!(await hashing.matches(parts, mac))) return false
|
|
102
|
+
remember(mac, parts)
|
|
103
|
+
return true
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function hasher(secret: string): Hasher {
|
|
109
|
+
const createHmac = nodeCreateHmac()
|
|
110
|
+
if (createHmac !== undefined) {
|
|
111
|
+
const hex = (parts: readonly string[]): string => {
|
|
112
|
+
const hmac = createHmac("sha256", secret)
|
|
113
|
+
for (const part of parts) hmac.update(`${part.length}:`).update(part)
|
|
114
|
+
return hmac.digest("hex")
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
sign: async (parts) => hex(parts),
|
|
118
|
+
matches: async (parts, mac) => timingSafeEqual(hex(parts), mac),
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
let signingKey: Promise<CryptoKey> | undefined
|
|
122
|
+
const key = (): Promise<CryptoKey> => {
|
|
123
|
+
signingKey ??= crypto.subtle.importKey(
|
|
124
|
+
"raw",
|
|
125
|
+
TEXT.encode(secret),
|
|
126
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
127
|
+
false,
|
|
128
|
+
["sign", "verify"],
|
|
129
|
+
)
|
|
130
|
+
return signingKey
|
|
131
|
+
}
|
|
132
|
+
const bytes = (parts: readonly string[]): Uint8Array<ArrayBuffer> =>
|
|
133
|
+
TEXT.encode(parts.map((part) => `${part.length}:${part}`).join(""))
|
|
134
|
+
return {
|
|
135
|
+
async sign(parts) {
|
|
136
|
+
const mac = new Uint8Array(await crypto.subtle.sign("HMAC", await key(), bytes(parts)))
|
|
137
|
+
let hex = ""
|
|
138
|
+
for (const byte of mac) hex += byte.toString(16).padStart(2, "0")
|
|
139
|
+
return hex
|
|
140
|
+
},
|
|
141
|
+
async matches(parts, mac) {
|
|
142
|
+
const expected = new Uint8Array(mac.length / 2)
|
|
143
|
+
for (let at = 0; at < expected.length; at++) {
|
|
144
|
+
expected[at] = Number.parseInt(mac.slice(at * 2, at * 2 + 2), 16)
|
|
145
|
+
}
|
|
146
|
+
return crypto.subtle.verify("HMAC", await key(), expected, bytes(parts))
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -23,8 +23,8 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
23
23
|
* Only a plugin's `config` hook output is rewritten, never mutated, and only under rolldown-vite.
|
|
24
24
|
* Non-rolldown Vite is passed through verbatim.
|
|
25
25
|
*
|
|
26
|
-
* FLATTEN FIRST: a
|
|
27
|
-
* returns `[vite:react-babel, vite:react-refresh]`, and it's `
|
|
26
|
+
* FLATTEN FIRST: a plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react@4.x`'s `react()`
|
|
27
|
+
* returns `[vite:react-babel, vite:react-refresh]`, and it's `vite:react-babel`'s `config` hook that emits
|
|
28
28
|
* the offending `optimizeDeps.rollupOptions.jsx`. `nifra.config.ts` writes `vitePlugins = [react()]`, so the
|
|
29
29
|
* plugin list arrives NESTED (`[[babel, refresh]]`). Without flattening, `.map` sees the inner array (which
|
|
30
30
|
* has no `config`), leaves it untouched, and Vite - which flattens plugin arrays itself before running them
|
package/src/internal/web-app.ts
CHANGED
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
NIFRA_BACKEND_WS_RUNTIME,
|
|
11
11
|
preRouteMountPaths,
|
|
12
12
|
} from "@nifrajs/core/mount"
|
|
13
|
+
import { withResponseObserver } from "@nifrajs/core/response-observer"
|
|
13
14
|
import type { MountableApp, MountOptions } from "@nifrajs/core/server"
|
|
14
|
-
import { type ServerOptions, server } from "@nifrajs/core/server"
|
|
15
|
+
import { replacedRequestOf, type ServerOptions, server } from "@nifrajs/core/server"
|
|
15
16
|
import { type CspPolicy, cspNonceResolver, DOCUMENT_POLICY, isCspPolicy } from "../csp.ts"
|
|
16
17
|
import type { CssLoadingMode } from "../css-contract.ts"
|
|
17
18
|
import {
|
|
@@ -26,9 +27,14 @@ import type { Manifest } from "../manifest.ts"
|
|
|
26
27
|
import type { RenderAdapter } from "../render-seam.ts"
|
|
27
28
|
import { createMatcher, PRERENDERED_LIST_PATH } from "../router.ts"
|
|
28
29
|
import type { EvidenceAncestry } from "./evidence-traversal.ts"
|
|
30
|
+
import { identityEpochHook, type KeepAliveServerOptions } from "./identity-epoch.ts"
|
|
31
|
+
import { INVALIDATION, type InvalidationOp, invalidationOf } from "./invalidation-contract.ts"
|
|
32
|
+
import { LIVE_ATTACH, type LiveAttachment, type LiveRuntimeHandle } from "./live-contract.ts"
|
|
29
33
|
import { formatShadowedPages, shadowedPages } from "./mount-shadow.ts"
|
|
30
34
|
import { createPageRequestExecutor, type NonceResolver, type PageI18n } from "./page-execution.ts"
|
|
35
|
+
import { PPR_SERVER, PPR_SETUP, type PprSetup } from "./ppr/contract.ts"
|
|
31
36
|
import { type PrerenderedPages, servePrerenderedPages } from "./prerendered-pages.ts"
|
|
37
|
+
import type { RevalidateResult } from "./render-document.ts"
|
|
32
38
|
import { requestPattern } from "./route-path.ts"
|
|
33
39
|
|
|
34
40
|
export type { NonceResolver } from "./page-execution.ts"
|
|
@@ -101,6 +107,15 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
101
107
|
* and stays cacheable by `withISR` or a CDN. `meta` still receives a nonce to name.
|
|
102
108
|
*/
|
|
103
109
|
readonly csp?: CspPolicy
|
|
110
|
+
/** Experimental and unstable: features whose API may change in any release. */
|
|
111
|
+
readonly experimental?: {
|
|
112
|
+
/**
|
|
113
|
+
* Partial prerendering, from `partialPrerendering()` in `@nifrajs/web/ppr`. A route whose backend
|
|
114
|
+
* half exports `shellLoader` streams a shell shared by every visitor, then each `dynamic` boundary
|
|
115
|
+
* as it settles. Needs `csp` (hashes), not `nonce`.
|
|
116
|
+
*/
|
|
117
|
+
readonly ppr?: PprSetup
|
|
118
|
+
}
|
|
104
119
|
/**
|
|
105
120
|
* Options for the underlying `server()` - `requestTimeoutMs`, `admission`, `gracefulSignals`, and
|
|
106
121
|
* the rest of {@link ServerOptions}.
|
|
@@ -221,6 +236,11 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
221
236
|
* signed `__nifra_draft` cookie gets `ctx.draft === true` in loaders/actions (else always `false`).
|
|
222
237
|
* Pair with `withISR({ draftSecret })` so editors bypass the cache. Omit to disable draft mode. */
|
|
223
238
|
readonly draftSecret?: string
|
|
239
|
+
/**
|
|
240
|
+
* Live data refresh, from `live()` in `@nifrajs/web/live`. Loaders declare keys with `ctx.live()`;
|
|
241
|
+
* the app answers live loads at the runtime's load endpoint. Omit and nothing live is loaded.
|
|
242
|
+
*/
|
|
243
|
+
readonly live?: LiveRuntimeHandle
|
|
224
244
|
/** Per-route chunk URLs (`buildClient`'s `BuildManifest.routes`) - `routeId → [layout chunks…, own
|
|
225
245
|
* chunk]`. When present, each page `modulepreload`s its matched route's chunks alongside the entry,
|
|
226
246
|
* so the route code downloads in parallel (no entry→route-chunk waterfall). Omit ⇒ entry-only. */
|
|
@@ -276,6 +296,15 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
276
296
|
* server entry, before its static files too. A refused path answers the policy's refusal.
|
|
277
297
|
*/
|
|
278
298
|
readonly hostPolicy?: HostPolicy
|
|
299
|
+
/**
|
|
300
|
+
* Which cookies say who is signed in, for an app with a `keepAlive` route. A response that changes
|
|
301
|
+
* or deletes one rotates the readable identity epoch cookie, and every tab drops the pages it kept
|
|
302
|
+
* for the previous visitor. The session cookies of `@nifrajs/auth` and Auth.js count under their
|
|
303
|
+
* default names, Better Auth's under any `cookiePrefix`; name any other in `identityCookies`. An app
|
|
304
|
+
* run by `nifra dev` and `nifra build` exports this from `backend/framework.ts`. Development reports, once per
|
|
305
|
+
* name, an `HttpOnly` cookie that changes and is in neither list (`NIFRA_KEEPALIVE_IDENTITY_COOKIE`).
|
|
306
|
+
*/
|
|
307
|
+
readonly keepAlive?: KeepAliveServerOptions
|
|
279
308
|
/**
|
|
280
309
|
* The files the build emitted, by request path: `buildAssets` from the generated server manifest.
|
|
281
310
|
* A {@link hostPolicy} rule's `buildAssets` token matches exactly these. `nifra dev` serves the
|
|
@@ -330,6 +359,16 @@ export interface CreateWebAppOptions<Env = unknown> {
|
|
|
330
359
|
* The `Request` is rebuilt rather than mutated (`url` is read-only), preserving method, headers, body,
|
|
331
360
|
* and duplex streaming - the body is passed through unread, so a large or streamed upload is untouched.
|
|
332
361
|
*/
|
|
362
|
+
/** Publish what a write named, for an app with `live` and no cache wrapper to run the pipeline. */
|
|
363
|
+
async function publishOwn(
|
|
364
|
+
op: InvalidationOp,
|
|
365
|
+
request: Request,
|
|
366
|
+
publish: (keys: readonly string[]) => Promise<void>,
|
|
367
|
+
): Promise<void> {
|
|
368
|
+
const { settleInvalidation } = await import("./invalidation.ts")
|
|
369
|
+
await settleInvalidation(op, { publish }, request, undefined)
|
|
370
|
+
}
|
|
371
|
+
|
|
333
372
|
/**
|
|
334
373
|
* Resolve the explicit symbol-keyed backend mount interface. The symbol seam forwards platform
|
|
335
374
|
* context without making web depend on client.
|
|
@@ -421,15 +460,23 @@ export function createWebApp<Env = unknown>(
|
|
|
421
460
|
}
|
|
422
461
|
app.use(hostPolicyGate(options.hostPolicy, options.buildAssets))
|
|
423
462
|
}
|
|
463
|
+
// Before every app hook: a live load becomes the target page's GET here, so each hook, middleware,
|
|
464
|
+
// gate and loader sees that path once, as a navigation would.
|
|
465
|
+
const liveAttachment: LiveAttachment | undefined = options.live?.[LIVE_ATTACH]({
|
|
466
|
+
matchRoute: (path) => pageExecutor.matchRoute(path),
|
|
467
|
+
app,
|
|
468
|
+
})
|
|
469
|
+
if (liveAttachment !== undefined) app.onRequest(liveAttachment.onRequest)
|
|
424
470
|
// Before the mount hooks and before every page route, so the order-scoped hooks (`derive`,
|
|
425
471
|
// `beforeHandle`, assurance evidence) actually bind to them - see the `use` option. A caller
|
|
426
472
|
// cannot do this after the fact: by the time this function returns, the routes are declared.
|
|
427
473
|
options.use?.(app)
|
|
428
474
|
if (options.serverComponents !== undefined) {
|
|
429
475
|
const runtime = options.serverComponents
|
|
476
|
+
// A route whose holes show server components is one visitor's too.
|
|
430
477
|
const match = createMatcher(
|
|
431
478
|
manifest.routes
|
|
432
|
-
.filter((route) => runtime.owns(route.id))
|
|
479
|
+
.filter((route) => runtime.owns(route.id) || runtime.hosts?.(route.id) === true)
|
|
433
480
|
.map((route) => ({ routeId: route.id, pattern: route.pattern })),
|
|
434
481
|
)
|
|
435
482
|
const privateRoute = (request: Request): boolean =>
|
|
@@ -473,6 +520,44 @@ export function createWebApp<Env = unknown>(
|
|
|
473
520
|
cacheChannel.enabled = true
|
|
474
521
|
},
|
|
475
522
|
})
|
|
523
|
+
// One owner per write. A cache wrapper that opened the channel runs the whole pipeline and takes
|
|
524
|
+
// the operation here, by the request it passed in; without one, the app publishes the write's keys
|
|
525
|
+
// and tags itself, and a write with nothing to publish needs nothing at all.
|
|
526
|
+
const handedOver = new WeakMap<Request, InvalidationOp>()
|
|
527
|
+
const live = options.live
|
|
528
|
+
const publish =
|
|
529
|
+
live === undefined ? undefined : (keys: readonly string[]) => live.publish(...keys)
|
|
530
|
+
Object.defineProperty(app, INVALIDATION, {
|
|
531
|
+
value: {
|
|
532
|
+
take: (request: Request): InvalidationOp | undefined => {
|
|
533
|
+
const op = handedOver.get(request)
|
|
534
|
+
handedOver.delete(request)
|
|
535
|
+
return op
|
|
536
|
+
},
|
|
537
|
+
publish,
|
|
538
|
+
},
|
|
539
|
+
})
|
|
540
|
+
const invalidate = (
|
|
541
|
+
revalidation: RevalidateResult<unknown>,
|
|
542
|
+
request: Request,
|
|
543
|
+
): Promise<void> | undefined => {
|
|
544
|
+
if (cacheChannel.enabled) {
|
|
545
|
+
const op = invalidationOf(revalidation)
|
|
546
|
+
// Every request in a hook's replacement chain: the wrapper holds the first one.
|
|
547
|
+
for (
|
|
548
|
+
let held: Request | undefined = request;
|
|
549
|
+
held !== undefined;
|
|
550
|
+
held = replacedRequestOf(held)
|
|
551
|
+
)
|
|
552
|
+
handedOver.set(held, op)
|
|
553
|
+
return undefined
|
|
554
|
+
}
|
|
555
|
+
// Paths alone refresh only the submitting page, which the response header already does.
|
|
556
|
+
const named = revalidation.__nifraInvalidate
|
|
557
|
+
if (publish === undefined || named === undefined || named.keys.length + named.tags.length === 0)
|
|
558
|
+
return undefined
|
|
559
|
+
return publishOwn(invalidationOf(revalidation), request, publish)
|
|
560
|
+
}
|
|
476
561
|
// Mount the in-process backend over HTTP at `apiPrefix`, when one is given, BEFORE page routing.
|
|
477
562
|
// Core's pre-route mount seam runs before the page wildcard `/*`; parent request hooks run first,
|
|
478
563
|
// child request hooks run inside the mount, and parent response hooks still wrap the result.
|
|
@@ -511,6 +596,14 @@ export function createWebApp<Env = unknown>(
|
|
|
511
596
|
const prerenderedList = ownPatterns.has(PRERENDERED_LIST_PATH)
|
|
512
597
|
? undefined
|
|
513
598
|
: servedPrerenderedList(options.prerenderedPaths)
|
|
599
|
+
const ppr = options.experimental?.ppr?.[PPR_SETUP]({
|
|
600
|
+
adapter,
|
|
601
|
+
// biome-ignore lint/plugin/noConditionalEmptyObjectSpread: partial prerendering lists its scripts by hash only under an app's own policy.
|
|
602
|
+
...(options.csp === undefined ? {} : { csp: options.csp }),
|
|
603
|
+
// The content-hashed entry changes with every client build; the store is per process.
|
|
604
|
+
deployment: clientEntry,
|
|
605
|
+
perRequestNonce: options.nonce !== undefined,
|
|
606
|
+
})
|
|
514
607
|
const pageExecutor = createPageRequestExecutor<Env>({
|
|
515
608
|
...(options.cacheRouteModules === undefined
|
|
516
609
|
? {}
|
|
@@ -558,7 +651,18 @@ export function createWebApp<Env = unknown>(
|
|
|
558
651
|
...(options.onLoaderError === undefined ? {} : { onLoaderError: options.onLoaderError }),
|
|
559
652
|
logBoundaryError: boundaryErrorLog(options.server),
|
|
560
653
|
cacheChannel,
|
|
654
|
+
// biome-ignore lint/plugin/noConditionalEmptyObjectSpread: the executor takes a route's shell path only when partial prerendering is set up.
|
|
655
|
+
...(ppr === undefined ? {} : { ppr }),
|
|
656
|
+
invalidate,
|
|
657
|
+
liveLoadOf: liveAttachment?.loadOf,
|
|
658
|
+
liveSeedOf: liveAttachment?.seedOf,
|
|
561
659
|
})
|
|
660
|
+
if (ppr !== undefined) {
|
|
661
|
+
Object.defineProperty(app, PPR_SERVER, { value: ppr.server })
|
|
662
|
+
app.register("POST", ppr.cancelPath, undefined, (c: { readonly req: Request }) =>
|
|
663
|
+
ppr.cancel(c.req),
|
|
664
|
+
)
|
|
665
|
+
}
|
|
562
666
|
|
|
563
667
|
// A nonce app's documents authorize their scripts per request, which a file rendered once cannot.
|
|
564
668
|
const prerendered =
|
|
@@ -573,7 +677,8 @@ export function createWebApp<Env = unknown>(
|
|
|
573
677
|
app.register(
|
|
574
678
|
"GET",
|
|
575
679
|
pattern,
|
|
576
|
-
options.serverComponents?.owns(route.id)
|
|
680
|
+
options.serverComponents?.owns(route.id) ||
|
|
681
|
+
options.serverComponents?.hosts?.(route.id) === true
|
|
577
682
|
? { capabilities: options.serverComponents.capabilities(route.id) }
|
|
578
683
|
: undefined,
|
|
579
684
|
prerendered === undefined ? render : prerendered(render),
|
|
@@ -632,6 +737,21 @@ export function createWebApp<Env = unknown>(
|
|
|
632
737
|
// and the router refuses a second `/*` under another param name.
|
|
633
738
|
if (!ownsRootCatchAll) app.register("GET", "/*", undefined, pageExecutor.fallback)
|
|
634
739
|
|
|
740
|
+
// Last, after every hook the caller's `use` added: a cookie any of them sets is seen here.
|
|
741
|
+
if (manifest.routes.some((route) => route.keepAlive === true)) {
|
|
742
|
+
// As for error details: only a run that says it is development or test reports, and an unset
|
|
743
|
+
// NODE_ENV (an unbuilt `bun app.ts`, a Worker) counts as production.
|
|
744
|
+
const nodeEnv = typeof process === "undefined" ? undefined : process.env?.NODE_ENV
|
|
745
|
+
app.use(
|
|
746
|
+
withResponseObserver({
|
|
747
|
+
onResponseHeaders: identityEpochHook(
|
|
748
|
+
options.keepAlive ?? {},
|
|
749
|
+
nodeEnv === "development" || nodeEnv === "test",
|
|
750
|
+
),
|
|
751
|
+
}),
|
|
752
|
+
)
|
|
753
|
+
}
|
|
754
|
+
|
|
635
755
|
const evidenceProvider: ComposedEvidenceProvider = async (passed) => {
|
|
636
756
|
const { traverseEvidence } = await import("./evidence-traversal.ts")
|
|
637
757
|
return traverseEvidence(evidenceProvider, passed, async (ancestry) => {
|