@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
|
@@ -10,21 +10,24 @@ import {
|
|
|
10
10
|
status as statusResult,
|
|
11
11
|
} from "@nifrajs/core/server"
|
|
12
12
|
import {
|
|
13
|
+
type BoundaryRegistration,
|
|
13
14
|
type BoundaryRequestCtx,
|
|
14
15
|
type BoundaryStates,
|
|
15
16
|
boundaryDescriptors,
|
|
16
17
|
resolveStaticBoundaries,
|
|
18
|
+
type ShellCtx,
|
|
17
19
|
type StaticBoundaryCache,
|
|
18
20
|
startDynamicBoundaries,
|
|
19
21
|
} from "../boundary.ts"
|
|
20
22
|
import type { CspPolicy } from "../csp.ts"
|
|
21
23
|
import { type CssLoadingMode, DEFAULT_CSS_LOADING, normalizeCssLoading } from "../css-contract.ts"
|
|
22
|
-
import { defer, ndjsonStream, prepareDeferred } from "../deferred.ts"
|
|
24
|
+
import { type Deferred, defer, ndjsonStream, prepareDeferred } from "../deferred.ts"
|
|
23
25
|
import { isDraftEnabled } from "../draft.ts"
|
|
24
26
|
import { routeTags } from "../isr.ts"
|
|
25
27
|
import type {
|
|
26
28
|
LayoutEntry,
|
|
27
29
|
LoaderContext,
|
|
30
|
+
LoaderResponseControls,
|
|
28
31
|
Manifest,
|
|
29
32
|
Meta,
|
|
30
33
|
MetaArgs,
|
|
@@ -35,10 +38,14 @@ import type {
|
|
|
35
38
|
} from "../manifest.ts"
|
|
36
39
|
import type { NonceResolver } from "../nonce.ts"
|
|
37
40
|
import type { MatchChain, PageLocale, PageLocales, RenderAdapter } from "../render-seam.ts"
|
|
41
|
+
import { SWAP_HEADER } from "../render-seam.ts"
|
|
38
42
|
import {
|
|
39
43
|
createMatcher,
|
|
40
44
|
DATA_HEADER,
|
|
45
|
+
HOLE_FILL_HEADER,
|
|
46
|
+
KEEP_HOLES_HEADER,
|
|
41
47
|
NAV_FROM_HEADER,
|
|
48
|
+
PREFETCH_HEADER,
|
|
42
49
|
REDIRECT_HEADER,
|
|
43
50
|
RETAIN_HEADER,
|
|
44
51
|
REVALIDATE_HEADER,
|
|
@@ -46,25 +53,36 @@ import {
|
|
|
46
53
|
} from "../router.ts"
|
|
47
54
|
import { searchOf, searchOfChain } from "../search.ts"
|
|
48
55
|
import { mergeHeads, resolveChainMeta } from "./head-merge.ts"
|
|
56
|
+
import { keepAliveTable, slotDepths } from "./keep-alive-table.ts"
|
|
57
|
+
import {
|
|
58
|
+
holeOwner,
|
|
59
|
+
type LiveEnvelope,
|
|
60
|
+
type LiveFinish,
|
|
61
|
+
type LiveLoad,
|
|
62
|
+
type LiveRouteMatch,
|
|
63
|
+
} from "./live-contract.ts"
|
|
49
64
|
import { prepareModules } from "./module-preparation.ts"
|
|
65
|
+
import {
|
|
66
|
+
type HoleLive,
|
|
67
|
+
isHole,
|
|
68
|
+
type PprIntegration,
|
|
69
|
+
type PprRequest,
|
|
70
|
+
serverHoleComponent,
|
|
71
|
+
} from "./ppr/contract.ts"
|
|
50
72
|
import {
|
|
51
73
|
EMPTY_RETAIN,
|
|
52
|
-
type HeadersLike,
|
|
53
74
|
isControlFlow,
|
|
54
75
|
isResponseResult,
|
|
55
|
-
|
|
76
|
+
isRevalidateResult,
|
|
56
77
|
type LoadedLayoutModules,
|
|
57
78
|
layoutErrorId,
|
|
58
|
-
notFound,
|
|
59
79
|
type RenderAssemblyCache,
|
|
60
80
|
type RenderedPage,
|
|
61
81
|
type RenderPageOptions,
|
|
62
82
|
type RevalidateResult,
|
|
63
83
|
redirect,
|
|
64
84
|
renderPageResult,
|
|
65
|
-
STATUS_SIGNAL,
|
|
66
85
|
STATUS_TEXT,
|
|
67
|
-
type StatusSignal,
|
|
68
86
|
scopeParams,
|
|
69
87
|
tagLayoutError,
|
|
70
88
|
withDuplicateInstanceHint,
|
|
@@ -82,6 +100,13 @@ import {
|
|
|
82
100
|
privateOutcome,
|
|
83
101
|
varyOnDataHeader,
|
|
84
102
|
} from "./response-controls.ts"
|
|
103
|
+
import {
|
|
104
|
+
type HeadersLike,
|
|
105
|
+
isStatusSignal,
|
|
106
|
+
notFound,
|
|
107
|
+
STATUS_SIGNAL,
|
|
108
|
+
type StatusSignal,
|
|
109
|
+
} from "./status-page.ts"
|
|
85
110
|
|
|
86
111
|
export type { NonceResolver } from "../nonce.ts"
|
|
87
112
|
|
|
@@ -101,13 +126,145 @@ export interface PageRouteContext<Env = unknown> {
|
|
|
101
126
|
}
|
|
102
127
|
|
|
103
128
|
/** Whether `c` is a client navigation's data request. On Node, `req.headers` builds every header. */
|
|
129
|
+
// The slot name is echoed in a response header, so only a plain identifier is honored.
|
|
130
|
+
const SLOT_NAME = /^[A-Za-z][\w-]{0,63}$/
|
|
131
|
+
|
|
132
|
+
/** The slot an `x-nifra-swap` request names, when it is a valid slot name. */
|
|
133
|
+
function swapSlotOf(req: Request): string | undefined {
|
|
134
|
+
const name = req.headers.get(SWAP_HEADER)
|
|
135
|
+
return name !== null && SLOT_NAME.test(name) ? name : undefined
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The `swapSlot` render option for `req`, empty when it is not a swap request. */
|
|
139
|
+
function swapOption(req: Request): { readonly swapSlot?: string } {
|
|
140
|
+
const swapSlot = swapSlotOf(req)
|
|
141
|
+
return swapSlot === undefined ? {} : { swapSlot }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const headerOf = (c: PageRouteContext<unknown>, name: string): string | null =>
|
|
145
|
+
c.header !== undefined ? c.header(name) : c.req.headers.get(name)
|
|
146
|
+
|
|
104
147
|
function isDataRequest(c: PageRouteContext<unknown>): boolean {
|
|
105
|
-
return (c
|
|
148
|
+
return headerOf(c, DATA_HEADER) !== null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The live owners of one response's holes, settled once the last hole that ran does. */
|
|
152
|
+
interface HoleOwners {
|
|
153
|
+
readonly kept: readonly string[]
|
|
154
|
+
readonly loaded: Promise<readonly string[]>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A data request's hole refresh: the hosts the client holds, which reload as fills, the holes it
|
|
159
|
+
* keeps, and in a live load each hole's owner. A name the route does not declare is a 400.
|
|
160
|
+
*/
|
|
161
|
+
function holeRefresh(
|
|
162
|
+
c: PageRouteContext<unknown>,
|
|
163
|
+
request: PprRequest,
|
|
164
|
+
live: LiveLoad | undefined,
|
|
165
|
+
): { readonly request: PprRequest; readonly owners?: HoleOwners } | Response {
|
|
166
|
+
if (!isDataRequest(c)) return { request }
|
|
167
|
+
const declared = new Set(request.holes.map((hole) => hole.name))
|
|
168
|
+
const namesOf = (header: string): Set<string> | undefined => {
|
|
169
|
+
const value = headerOf(c, header)
|
|
170
|
+
const names = new Set<string>()
|
|
171
|
+
if (value === null) return names
|
|
172
|
+
for (const name of value.split(",")) {
|
|
173
|
+
const trimmed = name.trim()
|
|
174
|
+
if (declared.has(trimmed)) names.add(trimmed)
|
|
175
|
+
// A live load's names come from a grant this server sealed and the page's own client: one
|
|
176
|
+
// naming a hole an older build declared is skipped.
|
|
177
|
+
else if (live === undefined) return undefined
|
|
178
|
+
}
|
|
179
|
+
return names
|
|
180
|
+
}
|
|
181
|
+
const kept = namesOf(KEEP_HOLES_HEADER)
|
|
182
|
+
const fill = namesOf(HOLE_FILL_HEADER)
|
|
183
|
+
if (kept === undefined || fill === undefined) {
|
|
184
|
+
return new Response("Bad Request: the route has no such hole", {
|
|
185
|
+
status: 400,
|
|
186
|
+
headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" },
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
const refreshed: PprRequest = {
|
|
190
|
+
...request,
|
|
191
|
+
...(fill.size === 0 ? {} : { fill }),
|
|
192
|
+
...(kept.size === 0 ? {} : { kept }),
|
|
193
|
+
}
|
|
194
|
+
if (live === undefined) return { request: refreshed }
|
|
195
|
+
const routeId = request.routeId
|
|
196
|
+
let finish: (loaded: readonly string[]) => void = () => {}
|
|
197
|
+
const loaded = new Promise<readonly string[]>((resolve) => {
|
|
198
|
+
finish = resolve
|
|
199
|
+
})
|
|
200
|
+
const holeLive: HoleLive = {
|
|
201
|
+
collector: (hole) => live.collector(holeOwner(hole, routeId)),
|
|
202
|
+
seal: (hole) => live.seal(holeOwner(hole, routeId)),
|
|
203
|
+
done: (names) => finish(names.map((hole) => holeOwner(hole, routeId))),
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
request: { ...refreshed, live: holeLive },
|
|
207
|
+
owners: { kept: [...kept].map((hole) => holeOwner(hole, routeId)), loaded },
|
|
208
|
+
}
|
|
106
209
|
}
|
|
107
210
|
|
|
108
211
|
/** The page slot of an `ssr = false` route with no `HydrateFallback`: a component that renders nothing. */
|
|
109
212
|
const EMPTY_LEAF = (): null => null
|
|
110
213
|
|
|
214
|
+
/** `ctx.live` outside a live load: keys are declared for nobody. */
|
|
215
|
+
const NO_LIVE = (): void => {}
|
|
216
|
+
|
|
217
|
+
const headersSent = (): never => {
|
|
218
|
+
throw new TypeError(
|
|
219
|
+
"[nifra/web] ppr: a hole's server component cannot set a cookie: the shell already sent the headers",
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A hole's response headers went out with the shell, so its server component sets none. */
|
|
224
|
+
const SENT_CONTROLS: LoaderResponseControls = Object.freeze({
|
|
225
|
+
headers: Object.freeze({}),
|
|
226
|
+
cookie: headersSent,
|
|
227
|
+
deleteCookie: headersSent,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The envelope's `live` member. With holes running it trails them: one grant, issued once the last hole
|
|
232
|
+
* settled, holding each hole that loaded and each the client kept.
|
|
233
|
+
*/
|
|
234
|
+
const liveEnvelopeOf = (
|
|
235
|
+
live: LiveLoad,
|
|
236
|
+
owners: LiveFinish,
|
|
237
|
+
holes: HoleOwners | undefined,
|
|
238
|
+
): Promise<LiveEnvelope> | Deferred<LiveEnvelope> =>
|
|
239
|
+
holes === undefined
|
|
240
|
+
? live.finish(owners)
|
|
241
|
+
: defer(
|
|
242
|
+
holes.loaded.then((loaded) =>
|
|
243
|
+
live.finish({
|
|
244
|
+
...owners,
|
|
245
|
+
fresh: [...owners.fresh, ...loaded],
|
|
246
|
+
retained: [...owners.retained, ...holes.kept],
|
|
247
|
+
}),
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
/** Run one owner's loader with its `ctx.live`, closing the collector once the loader settles. */
|
|
252
|
+
const runLiveLoader = (
|
|
253
|
+
live: LiveLoad,
|
|
254
|
+
owner: string,
|
|
255
|
+
loader: (ctx: LoaderContext) => unknown,
|
|
256
|
+
ctx: LoaderContext,
|
|
257
|
+
): Promise<unknown> => {
|
|
258
|
+
let value: unknown
|
|
259
|
+
try {
|
|
260
|
+
value = loader({ ...ctx, live: live.collector(owner) })
|
|
261
|
+
} catch (err) {
|
|
262
|
+
live.seal(owner)
|
|
263
|
+
return Promise.reject(err)
|
|
264
|
+
}
|
|
265
|
+
return Promise.resolve(value).finally(() => live.seal(owner))
|
|
266
|
+
}
|
|
267
|
+
|
|
111
268
|
/** The request-scoping seam of an in-process `api` (see `inProcessClient`), if it carries one. */
|
|
112
269
|
function platformBinderOf(api: unknown): BackendPlatformBinder | undefined {
|
|
113
270
|
if ((typeof api !== "object" && typeof api !== "function") || api === null) return undefined
|
|
@@ -244,6 +401,18 @@ export interface PageExecutionOptions<Env = unknown> {
|
|
|
244
401
|
readonly csp?: CspPolicy
|
|
245
402
|
/** Set once a cache wrapper attaches; until then a route's freshness and tags stay off the response. */
|
|
246
403
|
readonly cacheChannel?: { readonly enabled: boolean }
|
|
404
|
+
/**
|
|
405
|
+
* Run, or hand to the cache wrapper, what an action's `revalidate()` made stale. A promise is awaited
|
|
406
|
+
* before the action answers; `undefined` means nothing runs it. Never throws, never changes the answer.
|
|
407
|
+
*/
|
|
408
|
+
readonly invalidate?: (
|
|
409
|
+
revalidation: RevalidateResult<unknown>,
|
|
410
|
+
request: Request,
|
|
411
|
+
) => Promise<void> | undefined
|
|
412
|
+
/** The live load a request belongs to; only set when the app configures `live`. */
|
|
413
|
+
readonly liveLoadOf?: ((request: Request) => LiveLoad | undefined) | undefined
|
|
414
|
+
/** The seed a rendered document collects its keys into; only set when the app configures `live`. */
|
|
415
|
+
readonly liveSeedOf?: ((request: Request, routeId: string) => LiveLoad | undefined) | undefined
|
|
247
416
|
readonly onLoaderError?: (
|
|
248
417
|
error: unknown,
|
|
249
418
|
ctx: {
|
|
@@ -255,6 +424,8 @@ export interface PageExecutionOptions<Env = unknown> {
|
|
|
255
424
|
/** Logs a failure an `_error` boundary answered. Without a boundary the error propagates and the
|
|
256
425
|
* server logs it, so this runs only where the server never sees the throw. */
|
|
257
426
|
readonly logBoundaryError?: (error: unknown, request: Request) => void
|
|
427
|
+
/** Experimental partial prerendering: routes with a `shellLoader` stream a shared shell, then holes. */
|
|
428
|
+
readonly ppr?: PprIntegration
|
|
258
429
|
}
|
|
259
430
|
|
|
260
431
|
type PageResponse = Response | ResponseResult | RenderedPage
|
|
@@ -264,6 +435,8 @@ export interface PageRequestExecutor<Env = unknown> {
|
|
|
264
435
|
post(route: RouteEntry): (ctx: PageRouteContext<Env>) => Promise<PageResponse>
|
|
265
436
|
/** Answer a URL no route matched: the nearest nested `_404` for it, else the root one. */
|
|
266
437
|
fallback(ctx: PageRouteContext<Env>): Promise<PageResponse>
|
|
438
|
+
/** The route a `pathname + search` renders, with its layout chain. */
|
|
439
|
+
matchRoute(path: string): LiveRouteMatch | null
|
|
267
440
|
}
|
|
268
441
|
|
|
269
442
|
/** One URL pattern a nested `_404` answers, as the executor indexes it. */
|
|
@@ -355,11 +528,13 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
355
528
|
const { i18n } = options
|
|
356
529
|
const pageLocales = new Map(i18n?.locales.locales.map((locale) => [locale.key, locale]))
|
|
357
530
|
/** The request's locale for a document, with the served locales the client names locales from. */
|
|
358
|
-
const
|
|
531
|
+
const localeFor = (key: string): { locale?: PageLocale; locales?: PageLocales } => {
|
|
359
532
|
if (i18n === undefined) return {}
|
|
360
|
-
const locale = pageLocales.get(
|
|
533
|
+
const locale = pageLocales.get(key)
|
|
361
534
|
return locale === undefined ? { locales: i18n.locales } : { locale, locales: i18n.locales }
|
|
362
535
|
}
|
|
536
|
+
const localeOption = (req: Request): { locale?: PageLocale; locales?: PageLocales } =>
|
|
537
|
+
i18n === undefined ? {} : localeFor(i18n.localeOf(pathOf(req)))
|
|
363
538
|
/** Whether a route sits under a `[lang]` or `[[lang]]` directory, which the locale routing checks. */
|
|
364
539
|
const localeGuarded = (route: RouteEntry): boolean => {
|
|
365
540
|
if (i18n === undefined) return false
|
|
@@ -372,6 +547,52 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
372
547
|
manifest.routes.map((route) => ({ routeId: route.id, pattern: route.pattern })),
|
|
373
548
|
)
|
|
374
549
|
const routeById = new Map(manifest.routes.map((route) => [route.id, route]))
|
|
550
|
+
const liveLoadOf = options.liveLoadOf
|
|
551
|
+
const liveSeedOf = options.liveSeedOf
|
|
552
|
+
|
|
553
|
+
/** A live load holds one admission slot from before its middleware until its body is complete. */
|
|
554
|
+
const admitLive =
|
|
555
|
+
(run: (c: PageRouteContext<Env>, controls: PageResponseControls) => Promise<PageResponse>) =>
|
|
556
|
+
async (c: PageRouteContext<Env>, controls: PageResponseControls): Promise<PageResponse> => {
|
|
557
|
+
const live = liveLoadOf?.(c.req)
|
|
558
|
+
if (live === undefined) return run(c, controls)
|
|
559
|
+
const refused = live.admit()
|
|
560
|
+
if (refused !== undefined) return refused
|
|
561
|
+
let outcome: PageResponse
|
|
562
|
+
try {
|
|
563
|
+
outcome = await run(c, controls)
|
|
564
|
+
} catch (err) {
|
|
565
|
+
live.release()
|
|
566
|
+
throw err
|
|
567
|
+
}
|
|
568
|
+
if (outcome instanceof Response) return live.settle(outcome)
|
|
569
|
+
live.release()
|
|
570
|
+
return outcome
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Which owners a successful live load ran and which it kept, by their wire ids. */
|
|
574
|
+
const liveOwners = (
|
|
575
|
+
route: RouteEntry,
|
|
576
|
+
modules: LoadedLayoutModules | undefined,
|
|
577
|
+
layoutData: readonly unknown[] | undefined,
|
|
578
|
+
retained: readonly number[],
|
|
579
|
+
mod: RouteModule,
|
|
580
|
+
skipPage: boolean,
|
|
581
|
+
ctx: LoaderContext,
|
|
582
|
+
): LiveFinish => {
|
|
583
|
+
const fresh: string[] = []
|
|
584
|
+
const kept: string[] = []
|
|
585
|
+
for (let i = 0; i < (modules?.length ?? 0); i++) {
|
|
586
|
+
if (modules?.[i]?.loader === undefined) continue
|
|
587
|
+
const owner = `L:${route.layoutIds[i]}`
|
|
588
|
+
if (retained.includes(i)) kept.push(owner)
|
|
589
|
+
// A layout that answered with a status declared keys for data nobody is shown.
|
|
590
|
+
else if (!isStatusSignal(layoutData?.[i])) fresh.push(owner)
|
|
591
|
+
}
|
|
592
|
+
if (skipPage) kept.push(`P:${route.id}`)
|
|
593
|
+
else if (mod.loader !== undefined) fresh.push(`P:${route.id}`)
|
|
594
|
+
return { fresh, retained: kept, ctx }
|
|
595
|
+
}
|
|
375
596
|
|
|
376
597
|
// A nested `_404` answers an unmatched URL from inside the catch-all, never as a route of its own:
|
|
377
598
|
// a registered `/admin/*` would take URLs from a route the router only reaches by backtracking.
|
|
@@ -419,6 +640,19 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
419
640
|
? Promise.resolve(false)
|
|
420
641
|
: isDraftEnabled(req, options.draftSecret)
|
|
421
642
|
|
|
643
|
+
// Where the browser's keep-alive view puts slot wrappers, so the document it hydrates has them:
|
|
644
|
+
// nothing outside an app with a `keepAlive` route.
|
|
645
|
+
const keepTable = keepAliveTable(manifest)
|
|
646
|
+
const slotsFor = (
|
|
647
|
+
layoutIds: readonly string[],
|
|
648
|
+
routeId: string,
|
|
649
|
+
): { slots?: readonly number[] } => {
|
|
650
|
+
// Every page of a keep-alive app renders through the adapter's keep-alive root, wrappers or none,
|
|
651
|
+
// as the browser hydrates it.
|
|
652
|
+
if (keepTable === undefined) return {}
|
|
653
|
+
return { slots: slotDepths(keepTable, layoutIds, routeId) }
|
|
654
|
+
}
|
|
655
|
+
|
|
422
656
|
const preloadOf = (id: string): { preload?: readonly string[] } => {
|
|
423
657
|
const chunks = options.routePreload?.[id]
|
|
424
658
|
return chunks ? { preload: chunks } : {}
|
|
@@ -521,6 +755,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
521
755
|
controls: PageResponseControls,
|
|
522
756
|
retain?: RetainContext,
|
|
523
757
|
gated?: GatedLayouts,
|
|
758
|
+
live?: LiveLoad,
|
|
524
759
|
): Promise<LayoutRun> => {
|
|
525
760
|
if (route.layoutIds.length === 0) return emptyLayoutRun
|
|
526
761
|
const modules = gated?.modules ?? (await loadLayoutModules(route))
|
|
@@ -552,14 +787,25 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
552
787
|
params: scopeParams(ctx.params, route.layoutParams?.[i]),
|
|
553
788
|
set: controls.scope(i),
|
|
554
789
|
}
|
|
790
|
+
const layoutId = route.layoutIds[i] as string
|
|
555
791
|
if (modules[i]?.gate === true) {
|
|
556
|
-
results[i] =
|
|
792
|
+
results[i] =
|
|
793
|
+
gated !== undefined
|
|
794
|
+
? gated.gates[i]
|
|
795
|
+
: await runGate(
|
|
796
|
+
route,
|
|
797
|
+
i,
|
|
798
|
+
live === undefined
|
|
799
|
+
? loader
|
|
800
|
+
: (gateCtx) => runLiveLoader(live, `L:${layoutId}`, loader, gateCtx),
|
|
801
|
+
scoped,
|
|
802
|
+
)
|
|
557
803
|
continue
|
|
558
804
|
}
|
|
559
|
-
const layoutId = route.layoutIds[i] as string
|
|
560
805
|
const index = i
|
|
561
806
|
try {
|
|
562
|
-
const value =
|
|
807
|
+
const value =
|
|
808
|
+
live === undefined ? loader(scoped) : runLiveLoader(live, `L:${layoutId}`, loader, scoped)
|
|
563
809
|
// Scalars settle synchronously. Objects keep await's intrinsic promise/thenable
|
|
564
810
|
// assimilation: property-presence and prototype probes are unsafe for proxies.
|
|
565
811
|
if (value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
@@ -792,6 +1038,12 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
792
1038
|
const dirOfId = (id: string, suffix: string): string =>
|
|
793
1039
|
id === suffix ? "" : id.slice(0, id.length - suffix.length - 1)
|
|
794
1040
|
|
|
1041
|
+
/** The server's error log for one request, where a rejected `defer()` value is written. */
|
|
1042
|
+
const deferredLogOf = (req: Request): ((error: unknown) => void) | undefined => {
|
|
1043
|
+
const log = options.logBoundaryError
|
|
1044
|
+
return log === undefined ? undefined : (error) => log(error, req)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
795
1047
|
const boundaryFor = (route: RouteEntry, err: unknown): string | undefined => {
|
|
796
1048
|
const ids = route.errorIds ?? []
|
|
797
1049
|
const failing = layoutErrorId(err)
|
|
@@ -859,6 +1111,8 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
859
1111
|
routeId: errorId,
|
|
860
1112
|
status: 500,
|
|
861
1113
|
hydrate: false,
|
|
1114
|
+
signal: req.signal,
|
|
1115
|
+
logError: deferredLogOf(req),
|
|
862
1116
|
...(layoutData === undefined ? {} : { layoutData }),
|
|
863
1117
|
...(personalized || layoutData !== undefined ? { headers: PRIVATE_PAGE_HEADERS } : {}),
|
|
864
1118
|
...(nonce === undefined ? {} : { nonce }),
|
|
@@ -952,6 +1206,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
952
1206
|
draft,
|
|
953
1207
|
search: loaderSearch(page.searchSchema, c.req),
|
|
954
1208
|
set: controls.scope(PAGE_SCOPE),
|
|
1209
|
+
live: NO_LIVE,
|
|
955
1210
|
}
|
|
956
1211
|
if (route.middlewareIds !== undefined) await runMiddleware(route, ctx, controls)
|
|
957
1212
|
const run = await runLayoutChain(route, ctx, controls)
|
|
@@ -1007,6 +1262,8 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1007
1262
|
search: searchChainOf(modules, loaded, req),
|
|
1008
1263
|
path: pathOf(req),
|
|
1009
1264
|
status,
|
|
1265
|
+
signal: req.signal,
|
|
1266
|
+
logError: deferredLogOf(req),
|
|
1010
1267
|
// The browser renders this page inside the root layout, so a page that had to render alone
|
|
1011
1268
|
// stays server-only, as a nested `_404` does.
|
|
1012
1269
|
...(wraps && chain === undefined ? { hydrate: false } : {}),
|
|
@@ -1021,6 +1278,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1021
1278
|
...copyOption,
|
|
1022
1279
|
...localeOption(req),
|
|
1023
1280
|
...titleOption,
|
|
1281
|
+
...slotsFor(layoutIds, routeId),
|
|
1024
1282
|
}
|
|
1025
1283
|
if (chain === undefined) return renderPageResult(statusPage)
|
|
1026
1284
|
try {
|
|
@@ -1091,6 +1349,8 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1091
1349
|
responseHeaders: Record<string, string> | undefined,
|
|
1092
1350
|
personalized: boolean,
|
|
1093
1351
|
serverComponents?: import("../server-components/types.ts").ComponentState,
|
|
1352
|
+
live?: LiveEnvelope | Deferred<LiveEnvelope>,
|
|
1353
|
+
liveSeed?: LiveEnvelope,
|
|
1094
1354
|
): Promise<PageResponse> => {
|
|
1095
1355
|
// Every layout loader settled before the page rendered, so a status or error page can reuse them.
|
|
1096
1356
|
const settled: LayoutChainSource = async (_page, count) =>
|
|
@@ -1113,8 +1373,14 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1113
1373
|
...deferred,
|
|
1114
1374
|
...(boundarySplit === undefined ? [] : boundarySplit.deferred),
|
|
1115
1375
|
]
|
|
1376
|
+
// Deferred when holes run: their owners are known only once the last one settles.
|
|
1377
|
+
const liveSplit = live === undefined ? undefined : prepareDeferred(live, allDeferred.length)
|
|
1378
|
+
if (liveSplit !== undefined) allDeferred.push(...liveSplit.deferred)
|
|
1116
1379
|
const payload =
|
|
1117
|
-
layoutData === undefined &&
|
|
1380
|
+
layoutData === undefined &&
|
|
1381
|
+
boundarySplit === undefined &&
|
|
1382
|
+
serverComponents === undefined &&
|
|
1383
|
+
live === undefined
|
|
1118
1384
|
? pageSplit.forClient
|
|
1119
1385
|
: {
|
|
1120
1386
|
v: 1 as const,
|
|
@@ -1123,16 +1389,20 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1123
1389
|
retained: layoutRetained,
|
|
1124
1390
|
...(boundarySplit === undefined ? {} : { boundaries: boundarySplit.forClient }),
|
|
1125
1391
|
...(serverComponents === undefined ? {} : { serverComponents }),
|
|
1392
|
+
...(liveSplit === undefined ? {} : { live: liveSplit.forClient }),
|
|
1126
1393
|
}
|
|
1127
1394
|
if (allDeferred.length === 0) {
|
|
1128
1395
|
return Response.json(payload ?? null, { headers: DATA_RESPONSE_HEADERS })
|
|
1129
1396
|
}
|
|
1130
|
-
return new Response(
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1397
|
+
return new Response(
|
|
1398
|
+
ndjsonStream(payload, allDeferred, undefined, req.signal, deferredLogOf(req)),
|
|
1399
|
+
{
|
|
1400
|
+
headers: {
|
|
1401
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
1402
|
+
...DATA_RESPONSE_HEADERS,
|
|
1403
|
+
},
|
|
1134
1404
|
},
|
|
1135
|
-
|
|
1405
|
+
)
|
|
1136
1406
|
}
|
|
1137
1407
|
|
|
1138
1408
|
const nonce = options.nonce === undefined ? undefined : await resolveNonce(req, env)
|
|
@@ -1164,13 +1434,19 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1164
1434
|
search: searchChainOf(layoutModules, mod, req),
|
|
1165
1435
|
matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
|
|
1166
1436
|
hydrate: mod.hydrate !== false,
|
|
1437
|
+
signal: req.signal,
|
|
1438
|
+
logError: deferredLogOf(req),
|
|
1167
1439
|
...preloadOf(route.id),
|
|
1168
1440
|
...stylesOf(route.id),
|
|
1169
1441
|
...appDocumentOptions,
|
|
1170
1442
|
...localeOption(req),
|
|
1171
1443
|
}
|
|
1172
1444
|
if (serverComponents !== undefined) page.serverComponents = serverComponents
|
|
1445
|
+
if (liveSeed?.grant !== undefined) page.live = liveSeed
|
|
1173
1446
|
if (progressiveActionData.has(req)) page.actionData = progressiveActionData.get(req)
|
|
1447
|
+
const swapSlot = swapSlotOf(req)
|
|
1448
|
+
if (swapSlot !== undefined) page.swapSlot = swapSlot
|
|
1449
|
+
if (keepTable !== undefined) Object.assign(page, slotsFor(route.layoutIds, route.id))
|
|
1174
1450
|
if (layoutData !== undefined) page.layoutData = layoutData
|
|
1175
1451
|
if (boundaryStates !== undefined) page.boundaries = boundaryStates
|
|
1176
1452
|
if (responseHeaders !== undefined) page.headers = responseHeaders
|
|
@@ -1293,6 +1569,8 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1293
1569
|
matchChain: matchChainOf(page.layoutIds, layoutModules, id, mod),
|
|
1294
1570
|
status: 404,
|
|
1295
1571
|
hydrate: false,
|
|
1572
|
+
signal: req.signal,
|
|
1573
|
+
logError: deferredLogOf(req),
|
|
1296
1574
|
...localeOption(req),
|
|
1297
1575
|
...(layoutData !== undefined ? { layoutData } : {}),
|
|
1298
1576
|
...stylesOf(id),
|
|
@@ -1382,6 +1660,113 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1382
1660
|
}
|
|
1383
1661
|
}
|
|
1384
1662
|
|
|
1663
|
+
const pprRequestFor = (
|
|
1664
|
+
c: PageRouteContext<Env>,
|
|
1665
|
+
route: RouteEntry,
|
|
1666
|
+
mod: RouteModule,
|
|
1667
|
+
layouts: LoadedLayoutModules,
|
|
1668
|
+
boundaries: readonly BoundaryRegistration[],
|
|
1669
|
+
shellLoader: NonNullable<RouteModule["shellLoader"]>,
|
|
1670
|
+
requestApi: unknown,
|
|
1671
|
+
draft: boolean,
|
|
1672
|
+
search: Record<string, unknown>,
|
|
1673
|
+
): PprRequest => {
|
|
1674
|
+
const path = urlPartsFor(c.req).pathname
|
|
1675
|
+
const locale = i18n?.localeOf(path)
|
|
1676
|
+
const params = c.params
|
|
1677
|
+
const shellOrigin = options.ppr?.origin
|
|
1678
|
+
const variations =
|
|
1679
|
+
options.ppr?.variationsOf(mod.shellVariations, urlPartsFor(c.req).search) ?? {}
|
|
1680
|
+
const shellSearch = options.ppr?.variationQuery(variations) ?? ""
|
|
1681
|
+
// What a shared entry's tags read: its key's parts, never the Host or an undeclared query, which
|
|
1682
|
+
// the first visitor of a key could set so a later purge misses the entry.
|
|
1683
|
+
const shellUrl = (): URL => {
|
|
1684
|
+
const url = new URL(shellOrigin ?? "http://shell.invalid")
|
|
1685
|
+
url.pathname = path
|
|
1686
|
+
url.search = shellSearch
|
|
1687
|
+
return url
|
|
1688
|
+
}
|
|
1689
|
+
// The unbound `api`: a shell is built for every visitor, so no visitor's platform reaches it.
|
|
1690
|
+
const shellContext: Omit<ShellCtx, "signal"> =
|
|
1691
|
+
shellOrigin === undefined
|
|
1692
|
+
? { phase: "shell", params, locale, variations, api }
|
|
1693
|
+
: { phase: "shell", params, locale, variations, api, origin: shellOrigin }
|
|
1694
|
+
return {
|
|
1695
|
+
routeId: route.id,
|
|
1696
|
+
path,
|
|
1697
|
+
locale,
|
|
1698
|
+
variations,
|
|
1699
|
+
request: c.req,
|
|
1700
|
+
holes: boundaries.filter(isHole),
|
|
1701
|
+
holeContext: { request: c.req, params, api: requestApi, env: c.env, draft, search },
|
|
1702
|
+
shellContext,
|
|
1703
|
+
revalidate: mod.revalidate,
|
|
1704
|
+
waitUntil: c.waitUntil,
|
|
1705
|
+
tags: () =>
|
|
1706
|
+
mod.revalidateTags === undefined
|
|
1707
|
+
? []
|
|
1708
|
+
: routeTags(mod.revalidateTags, { params, url: shellUrl() }, route.id),
|
|
1709
|
+
holeTags: (hole) =>
|
|
1710
|
+
hole.revalidateTags === undefined
|
|
1711
|
+
? []
|
|
1712
|
+
: routeTags(hole.revalidateTags, { params, url: shellUrl() }, `${route.id}#${hole.name}`),
|
|
1713
|
+
loadShell: async (signal) => shellLoader({ ...shellContext, signal }),
|
|
1714
|
+
renderShellDocument: async (shellData, states) => {
|
|
1715
|
+
const statics = boundaries.some((boundary) => boundary.mode === "static")
|
|
1716
|
+
? await resolveStaticBoundaries(
|
|
1717
|
+
boundaries,
|
|
1718
|
+
{ phase: "build" },
|
|
1719
|
+
options.staticBoundaryCache,
|
|
1720
|
+
)
|
|
1721
|
+
: undefined
|
|
1722
|
+
const { chain, head } = resolveChainAndHead(
|
|
1723
|
+
layouts,
|
|
1724
|
+
undefined,
|
|
1725
|
+
mod,
|
|
1726
|
+
// A Host header is the client's to choose, so a shared shell's meta gets the configured one.
|
|
1727
|
+
{ data: shellData, pageData: shellData, params, origin: shellOrigin ?? "" },
|
|
1728
|
+
serverLeafOf(route, mod),
|
|
1729
|
+
)
|
|
1730
|
+
// Nothing here reads the request beyond its pathname: no origin, search, nonce or headers.
|
|
1731
|
+
const page: DocumentInput = {
|
|
1732
|
+
adapter,
|
|
1733
|
+
chain,
|
|
1734
|
+
data: shellData,
|
|
1735
|
+
head,
|
|
1736
|
+
clientEntry,
|
|
1737
|
+
routeId: route.id,
|
|
1738
|
+
params,
|
|
1739
|
+
path,
|
|
1740
|
+
search: searchOfChain(
|
|
1741
|
+
[...layouts.map((m) => m.searchSchema), mod.searchSchema],
|
|
1742
|
+
shellSearch === "" ? "" : `?${shellSearch}`,
|
|
1743
|
+
),
|
|
1744
|
+
matchChain: matchChainOf(route.layoutIds, layouts, route.id, mod),
|
|
1745
|
+
hydrate: mod.hydrate !== false,
|
|
1746
|
+
boundaries: { ...statics, ...states },
|
|
1747
|
+
...preloadOf(route.id),
|
|
1748
|
+
...stylesOf(route.id),
|
|
1749
|
+
...appDocumentOptions,
|
|
1750
|
+
...(locale === undefined ? {} : localeFor(locale)),
|
|
1751
|
+
...slotsFor(route.layoutIds, route.id),
|
|
1752
|
+
}
|
|
1753
|
+
if (mod.islandScripts !== undefined) page.islandScripts = mod.islandScripts
|
|
1754
|
+
const result = await renderPageResult(page)
|
|
1755
|
+
// A buffered page hands over its string: a Response round trip to read it back cost more.
|
|
1756
|
+
const rendered = result.toNodeBody?.() ?? result.toResponse()
|
|
1757
|
+
if (rendered.status !== 200) {
|
|
1758
|
+
throw new Error(
|
|
1759
|
+
`[nifra/web] ppr: the shell of "${route.id}" rendered status ${rendered.status}`,
|
|
1760
|
+
)
|
|
1761
|
+
}
|
|
1762
|
+
if (rendered instanceof Response) return rendered.text()
|
|
1763
|
+
return typeof rendered.body === "string"
|
|
1764
|
+
? rendered.body
|
|
1765
|
+
: new TextDecoder().decode(rendered.body)
|
|
1766
|
+
},
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1385
1770
|
return {
|
|
1386
1771
|
get: (route) => {
|
|
1387
1772
|
const is404Fallback = options.staticFallbacks?.[route.pattern] === "404"
|
|
@@ -1414,7 +1799,11 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1414
1799
|
return slot
|
|
1415
1800
|
}
|
|
1416
1801
|
|
|
1417
|
-
|
|
1802
|
+
const page = async (
|
|
1803
|
+
c: PageRouteContext<Env>,
|
|
1804
|
+
controls: PageResponseControls,
|
|
1805
|
+
): Promise<PageResponse> => {
|
|
1806
|
+
let live = liveLoadOf?.(c.req)
|
|
1418
1807
|
const componentRuntime = options.serverComponents
|
|
1419
1808
|
const componentOwner = componentRuntime?.owns(route.id) === true
|
|
1420
1809
|
let componentRequest:
|
|
@@ -1431,6 +1820,17 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1431
1820
|
}
|
|
1432
1821
|
const loaded = loadModule(route)
|
|
1433
1822
|
const mod = cacheModules && !(loaded instanceof Promise) ? loaded : await loaded
|
|
1823
|
+
// A document's loaders declare their keys into a seed, which the page exchanges for a grant
|
|
1824
|
+
// once its socket opens, instead of loading itself again. Partially prerendered pages, whose
|
|
1825
|
+
// holes settle after the shell, and server-component routes load as before.
|
|
1826
|
+
const seeding =
|
|
1827
|
+
live === undefined &&
|
|
1828
|
+
liveSeedOf !== undefined &&
|
|
1829
|
+
!componentOwner &&
|
|
1830
|
+
componentRequest === undefined &&
|
|
1831
|
+
(options.ppr === undefined || mod.shellLoader === undefined) &&
|
|
1832
|
+
!isDataRequest(c)
|
|
1833
|
+
if (seeding) live = liveSeedOf(c.req, route.id)
|
|
1434
1834
|
const draft = await draftFlag(c.req)
|
|
1435
1835
|
// Unpublished content is the editor's alone: whatever cache-control a loader sets, no cache keeps it.
|
|
1436
1836
|
if (draft) controls.personalize()
|
|
@@ -1443,6 +1843,9 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1443
1843
|
let componentState: import("../server-components/types.ts").ComponentState | undefined
|
|
1444
1844
|
const requestApi = apiFor(c)
|
|
1445
1845
|
let run: LayoutRun | undefined
|
|
1846
|
+
let skipPage = false
|
|
1847
|
+
let liveCtx: LoaderContext
|
|
1848
|
+
let holeOwners: HoleOwners | undefined
|
|
1446
1849
|
try {
|
|
1447
1850
|
const ctx: LoaderContext = {
|
|
1448
1851
|
params: c.params,
|
|
@@ -1453,15 +1856,26 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1453
1856
|
draft,
|
|
1454
1857
|
search: loaderSearch(mod.searchSchema, c.req),
|
|
1455
1858
|
set: controls.scope(PAGE_SCOPE),
|
|
1859
|
+
live: NO_LIVE,
|
|
1456
1860
|
}
|
|
1457
1861
|
if (route.middlewareIds !== undefined || localeGuarded(route)) {
|
|
1458
1862
|
await runMiddleware(route, ctx, controls)
|
|
1459
1863
|
}
|
|
1864
|
+
// After middleware, before any loader: a grant issued to another viewer keeps nothing.
|
|
1865
|
+
const retains = live === undefined || (await live.bind(ctx))
|
|
1866
|
+
skipPage =
|
|
1867
|
+
live !== undefined &&
|
|
1868
|
+
retains &&
|
|
1869
|
+
!componentOwner &&
|
|
1870
|
+
live.retainPage &&
|
|
1871
|
+
live.routeId === route.id
|
|
1460
1872
|
run = await runLayoutChain(
|
|
1461
1873
|
route,
|
|
1462
1874
|
ctx,
|
|
1463
1875
|
controls,
|
|
1464
|
-
componentOwner ? undefined : retainContextOf(c),
|
|
1876
|
+
componentOwner || !retains ? undefined : retainContextOf(c),
|
|
1877
|
+
undefined,
|
|
1878
|
+
live,
|
|
1465
1879
|
)
|
|
1466
1880
|
layoutModules = run.modules
|
|
1467
1881
|
layoutRetained = run.retained
|
|
@@ -1474,8 +1888,89 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1474
1888
|
: []
|
|
1475
1889
|
if (boundaryDefinitions.length > 0) boundaryDescriptors(boundaryDefinitions)
|
|
1476
1890
|
const effectiveSearch = searchChainOf(run.modules, mod, c.req)
|
|
1891
|
+
const ppr = options.ppr
|
|
1892
|
+
const shellLoader =
|
|
1893
|
+
componentRequest === undefined && !componentOwner ? mod.shellLoader : undefined
|
|
1894
|
+
let pprRequest: PprRequest | undefined
|
|
1895
|
+
// How a document that renders whole gets its holes: complete, unless no shell exists yet.
|
|
1896
|
+
let wholeHoles: "settled" | "deferred" = "settled"
|
|
1897
|
+
if (ppr !== undefined && shellLoader !== undefined) {
|
|
1898
|
+
ppr.assertRoute(route, run.modules, boundaryDefinitions)
|
|
1899
|
+
pprRequest = pprRequestFor(
|
|
1900
|
+
c,
|
|
1901
|
+
route,
|
|
1902
|
+
mod,
|
|
1903
|
+
run.modules,
|
|
1904
|
+
boundaryDefinitions,
|
|
1905
|
+
shellLoader,
|
|
1906
|
+
requestApi,
|
|
1907
|
+
draft,
|
|
1908
|
+
effectiveSearch,
|
|
1909
|
+
)
|
|
1910
|
+
const refresh = holeRefresh(c, pprRequest, live)
|
|
1911
|
+
if (refresh instanceof Response) return refresh
|
|
1912
|
+
pprRequest = refresh.request
|
|
1913
|
+
holeOwners = refresh.owners
|
|
1914
|
+
const components = options.serverComponents
|
|
1915
|
+
const renderHole = components?.hosts?.(route.id) === true ? components.hole : undefined
|
|
1916
|
+
if (
|
|
1917
|
+
renderHole === undefined &&
|
|
1918
|
+
pprRequest.holes.some((hole) => serverHoleComponent(hole.render) !== undefined)
|
|
1919
|
+
) {
|
|
1920
|
+
throw new Error(
|
|
1921
|
+
`[nifra/web] ppr: route "${route.id}" has a hole that shows a server component; list the component under createServerComponents({ holes }) for this route`,
|
|
1922
|
+
)
|
|
1923
|
+
}
|
|
1924
|
+
if (renderHole !== undefined) {
|
|
1925
|
+
const loaderCtx = ctx
|
|
1926
|
+
pprRequest = {
|
|
1927
|
+
...pprRequest,
|
|
1928
|
+
// The owner path's context, but with the hole's signal and no response to change.
|
|
1929
|
+
component: (name, component, input, signal) =>
|
|
1930
|
+
renderHole.call(
|
|
1931
|
+
components,
|
|
1932
|
+
route.id,
|
|
1933
|
+
component,
|
|
1934
|
+
name,
|
|
1935
|
+
input,
|
|
1936
|
+
Object.create(
|
|
1937
|
+
c,
|
|
1938
|
+
Object.getOwnPropertyDescriptors({
|
|
1939
|
+
...loaderCtx,
|
|
1940
|
+
search: effectiveSearch,
|
|
1941
|
+
set: SENT_CONTROLS,
|
|
1942
|
+
signal,
|
|
1943
|
+
data: undefined,
|
|
1944
|
+
layoutData: [],
|
|
1945
|
+
}),
|
|
1946
|
+
),
|
|
1947
|
+
),
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
// A draft is the editor's alone, so it never reads or fills the shared shell.
|
|
1951
|
+
if (!draft && !isDataRequest(c)) {
|
|
1952
|
+
const streamed = await ppr.stream(pprRequest, () => controls.documentHeaders())
|
|
1953
|
+
if (streamed instanceof Response) return streamed
|
|
1954
|
+
wholeHoles = streamed
|
|
1955
|
+
}
|
|
1956
|
+
// Its holes are one visitor's, so no cache may store the page they render into.
|
|
1957
|
+
controls.personalize()
|
|
1958
|
+
}
|
|
1959
|
+
// Every PPR hole runs through the PPR server, so each has admission and its own deadline.
|
|
1960
|
+
const holeStates =
|
|
1961
|
+
pprRequest === undefined || ppr === undefined
|
|
1962
|
+
? undefined
|
|
1963
|
+
: ppr.server.holeStates(
|
|
1964
|
+
pprRequest,
|
|
1965
|
+
!isDataRequest(c)
|
|
1966
|
+
? wholeHoles
|
|
1967
|
+
: c.req.headers.get(PREFETCH_HEADER) === null
|
|
1968
|
+
? "deferred"
|
|
1969
|
+
: "omitted",
|
|
1970
|
+
c.req.signal,
|
|
1971
|
+
)
|
|
1477
1972
|
const boundaryBatch =
|
|
1478
|
-
boundaryDefinitions.length === 0
|
|
1973
|
+
boundaryDefinitions.length === 0 || holeStates !== undefined
|
|
1479
1974
|
? undefined
|
|
1480
1975
|
: startDynamicBoundaries(boundaryDefinitions, {
|
|
1481
1976
|
request: c.req,
|
|
@@ -1485,6 +1980,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1485
1980
|
draft,
|
|
1486
1981
|
search: effectiveSearch,
|
|
1487
1982
|
signal: c.req.signal,
|
|
1983
|
+
live: NO_LIVE,
|
|
1488
1984
|
} satisfies BoundaryRequestCtx)
|
|
1489
1985
|
const staticBoundaryPromise =
|
|
1490
1986
|
boundaryDefinitions.length === 0
|
|
@@ -1494,7 +1990,20 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1494
1990
|
{ phase: "build" },
|
|
1495
1991
|
options.staticBoundaryCache,
|
|
1496
1992
|
)
|
|
1497
|
-
const pageResult =
|
|
1993
|
+
const pageResult =
|
|
1994
|
+
pprRequest !== undefined && ppr !== undefined
|
|
1995
|
+
? // A key with no shell yet must not build one through its data either.
|
|
1996
|
+
draft || wholeHoles === "deferred"
|
|
1997
|
+
? pprRequest.loadShell(c.req.signal)
|
|
1998
|
+
: ppr.shellData(pprRequest, c.req.signal)
|
|
1999
|
+
: mod.loader === undefined || skipPage
|
|
2000
|
+
? null
|
|
2001
|
+
: live === undefined
|
|
2002
|
+
? mod.loader({ ...ctx, search: effectiveSearch })
|
|
2003
|
+
: runLiveLoader(live, `P:${route.id}`, mod.loader, {
|
|
2004
|
+
...ctx,
|
|
2005
|
+
search: effectiveSearch,
|
|
2006
|
+
})
|
|
1498
2007
|
const [pageData, , staticBoundaries] =
|
|
1499
2008
|
run.pending === undefined && staticBoundaryPromise === undefined
|
|
1500
2009
|
? ([await pageResult, undefined, undefined] as const)
|
|
@@ -1502,7 +2011,9 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1502
2011
|
? await Promise.all([pageResult, run.pending])
|
|
1503
2012
|
: await Promise.all([pageResult, run.pending, staticBoundaryPromise])
|
|
1504
2013
|
data = pageData
|
|
1505
|
-
|
|
2014
|
+
// A gate on a PPR route runs for its redirect or refusal only. Its data reaches no response,
|
|
2015
|
+
// so a soft navigation renders the layouts exactly as the shared shell did.
|
|
2016
|
+
layoutData = pprRequest === undefined ? run.layoutData : undefined
|
|
1506
2017
|
if (componentOwner) {
|
|
1507
2018
|
for (let i = 0; i < (layoutData?.length ?? 0); i++) {
|
|
1508
2019
|
const outcome = layoutData?.[i]
|
|
@@ -1531,8 +2042,15 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1531
2042
|
options.nonce === undefined ? undefined : await resolveNonce(c.req, c.env),
|
|
1532
2043
|
)
|
|
1533
2044
|
}
|
|
1534
|
-
if (
|
|
1535
|
-
|
|
2045
|
+
if (holeStates !== undefined) {
|
|
2046
|
+
// The static resolver names every boundary, leaving the dynamic ones to the holes.
|
|
2047
|
+
boundaryStates = { ...staticBoundaries, ...(await holeStates) }
|
|
2048
|
+
} else if (boundaryBatch !== undefined) {
|
|
2049
|
+
// Each resolver returns a state for every boundary and marks the other's modes
|
|
2050
|
+
// `unresolved`, so a slot takes its state from the resolver of its own mode.
|
|
2051
|
+
const streamed = { ...boundaryBatch.initial }
|
|
2052
|
+
for (const [name, state] of Object.entries(staticBoundaries ?? {}))
|
|
2053
|
+
if (state.mode === "static") streamed[name] = state
|
|
1536
2054
|
for (const entry of boundaryBatch.pending) {
|
|
1537
2055
|
const initial = streamed[entry.name]
|
|
1538
2056
|
if (initial !== undefined)
|
|
@@ -1545,6 +2063,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1545
2063
|
// Inside the loader `try`: a header the response cannot carry is a loader error, rendered
|
|
1546
2064
|
// by the same boundary as one.
|
|
1547
2065
|
responseHeaders = controls.documentHeaders()
|
|
2066
|
+
liveCtx = ctx
|
|
1548
2067
|
} catch (err) {
|
|
1549
2068
|
if (componentRequest !== undefined) return componentRuntime!.failure(err)
|
|
1550
2069
|
if (isStatusSignal(err)) {
|
|
@@ -1591,9 +2110,26 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1591
2110
|
)
|
|
1592
2111
|
if (nested !== undefined) return nested
|
|
1593
2112
|
}
|
|
2113
|
+
const isData = isDataRequest(c)
|
|
2114
|
+
const liveEnvelope =
|
|
2115
|
+
live === undefined || (!isData && !seeding) || isStatusSignal(data) || isControlFlow(data)
|
|
2116
|
+
? undefined
|
|
2117
|
+
: await liveEnvelopeOf(
|
|
2118
|
+
live,
|
|
2119
|
+
liveOwners(
|
|
2120
|
+
route,
|
|
2121
|
+
layoutModules,
|
|
2122
|
+
layoutData,
|
|
2123
|
+
layoutRetained,
|
|
2124
|
+
mod,
|
|
2125
|
+
skipPage,
|
|
2126
|
+
liveCtx,
|
|
2127
|
+
),
|
|
2128
|
+
holeOwners,
|
|
2129
|
+
)
|
|
1594
2130
|
return handleDataOrDocument(
|
|
1595
2131
|
c.req,
|
|
1596
|
-
|
|
2132
|
+
isData,
|
|
1597
2133
|
c.env,
|
|
1598
2134
|
route,
|
|
1599
2135
|
c.params,
|
|
@@ -1607,8 +2143,13 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1607
2143
|
responseHeaders,
|
|
1608
2144
|
controls.personalized,
|
|
1609
2145
|
componentState,
|
|
2146
|
+
seeding ? undefined : liveEnvelope,
|
|
2147
|
+
seeding && liveEnvelope !== undefined && !("__nifra_deferred" in liveEnvelope)
|
|
2148
|
+
? liveEnvelope
|
|
2149
|
+
: undefined,
|
|
1610
2150
|
)
|
|
1611
|
-
}
|
|
2151
|
+
}
|
|
2152
|
+
return withResponseControls(liveLoadOf === undefined ? page : admitLive(page))
|
|
1612
2153
|
},
|
|
1613
2154
|
|
|
1614
2155
|
post: (route) =>
|
|
@@ -1659,6 +2200,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1659
2200
|
draft,
|
|
1660
2201
|
search: loaderSearch(mod.searchSchema, c.req),
|
|
1661
2202
|
set: controls.scope(ACTION_SCOPE),
|
|
2203
|
+
live: NO_LIVE,
|
|
1662
2204
|
}
|
|
1663
2205
|
let gated: GatedLayouts
|
|
1664
2206
|
let result: unknown
|
|
@@ -1672,16 +2214,17 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1672
2214
|
if (isControlFlow(err)) return err
|
|
1673
2215
|
throw err
|
|
1674
2216
|
}
|
|
1675
|
-
const
|
|
1676
|
-
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
:
|
|
2217
|
+
const revalidation = isRevalidateResult(result) ? result : undefined
|
|
2218
|
+
const actionResult = revalidation === undefined ? result : revalidation.data
|
|
2219
|
+
const revalidateHeader: Record<string, string> =
|
|
2220
|
+
revalidation === undefined
|
|
2221
|
+
? {}
|
|
2222
|
+
: { [REVALIDATE_HEADER]: revalidation.__nifraRevalidate.join(",") }
|
|
2223
|
+
// The write is done, whatever the action answers with (a redirect included): what it made
|
|
2224
|
+
// stale is invalidated before the answer leaves.
|
|
2225
|
+
const settling =
|
|
2226
|
+
revalidation === undefined ? undefined : options.invalidate?.(revalidation, c.req)
|
|
2227
|
+
if (settling !== undefined) await settling
|
|
1685
2228
|
if (isControlFlow(actionResult)) return actionResult
|
|
1686
2229
|
if (isDataRequest) {
|
|
1687
2230
|
// Headers shape the document only, but a bad one fails here too, so a navigation and a full
|
|
@@ -1693,13 +2236,16 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1693
2236
|
headers: { ...DATA_RESPONSE_HEADERS, ...revalidateHeader },
|
|
1694
2237
|
})
|
|
1695
2238
|
}
|
|
1696
|
-
return new Response(
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
2239
|
+
return new Response(
|
|
2240
|
+
ndjsonStream(forClient, deferred, undefined, c.req.signal, deferredLogOf(c.req)),
|
|
2241
|
+
{
|
|
2242
|
+
headers: {
|
|
2243
|
+
"content-type": "application/x-ndjson; charset=utf-8",
|
|
2244
|
+
...DATA_RESPONSE_HEADERS,
|
|
2245
|
+
...revalidateHeader,
|
|
2246
|
+
},
|
|
1701
2247
|
},
|
|
1702
|
-
|
|
2248
|
+
)
|
|
1703
2249
|
}
|
|
1704
2250
|
if (options.serverComponents?.owns(route.id)) {
|
|
1705
2251
|
if (options.rerenderOwnerGET === undefined) throw new Error("component_contract")
|
|
@@ -1768,6 +2314,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1768
2314
|
draft,
|
|
1769
2315
|
search,
|
|
1770
2316
|
set: controls.scope(PAGE_SCOPE),
|
|
2317
|
+
live: NO_LIVE,
|
|
1771
2318
|
})
|
|
1772
2319
|
: null
|
|
1773
2320
|
const [pageData] =
|
|
@@ -1837,7 +2384,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1837
2384
|
},
|
|
1838
2385
|
serverLeafOf(route, mod),
|
|
1839
2386
|
)
|
|
1840
|
-
|
|
2387
|
+
const page: DocumentInput = {
|
|
1841
2388
|
adapter,
|
|
1842
2389
|
chain,
|
|
1843
2390
|
data,
|
|
@@ -1850,6 +2397,8 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1850
2397
|
search,
|
|
1851
2398
|
matchChain: matchChainOf(route.layoutIds, layoutModules, route.id, mod),
|
|
1852
2399
|
hydrate: mod.hydrate !== false,
|
|
2400
|
+
signal: c.req.signal,
|
|
2401
|
+
logError: deferredLogOf(c.req),
|
|
1853
2402
|
...layoutProps,
|
|
1854
2403
|
...preloadOf(route.id),
|
|
1855
2404
|
...stylesOf(route.id),
|
|
@@ -1860,9 +2409,19 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1860
2409
|
...(nonce === undefined ? {} : { nonce }),
|
|
1861
2410
|
...cspOption,
|
|
1862
2411
|
...titleOption,
|
|
1863
|
-
|
|
2412
|
+
...swapOption(c.req),
|
|
2413
|
+
...slotsFor(route.layoutIds, route.id),
|
|
2414
|
+
}
|
|
2415
|
+
if (mod.islandScripts !== undefined) page.islandScripts = mod.islandScripts
|
|
2416
|
+
return renderPageResult(page)
|
|
1864
2417
|
}),
|
|
1865
2418
|
|
|
2419
|
+
matchRoute: (path) => {
|
|
2420
|
+
const matched = matchManifestRoute(path)
|
|
2421
|
+
const route = matched === null ? undefined : routeById.get(matched.routeId)
|
|
2422
|
+
return route === undefined ? null : { routeId: route.id, layoutIds: route.layoutIds }
|
|
2423
|
+
},
|
|
2424
|
+
|
|
1866
2425
|
fallback: withResponseControls(async (c, controls) => {
|
|
1867
2426
|
const match = matchNotFoundScope?.(pathOf(c.req)) ?? null
|
|
1868
2427
|
const target = match === null ? undefined : notFoundTargets[Number(match.routeId)]
|
|
@@ -1887,6 +2446,7 @@ export function createPageRequestExecutor<Env = unknown>(
|
|
|
1887
2446
|
draft,
|
|
1888
2447
|
search: loaderSearch(mod.searchSchema, c.req),
|
|
1889
2448
|
set: controls.scope(PAGE_SCOPE),
|
|
2449
|
+
live: NO_LIVE,
|
|
1890
2450
|
}
|
|
1891
2451
|
if (target.route.middlewareIds !== undefined) {
|
|
1892
2452
|
await runMiddleware(target.route, ctx, controls)
|