@nifrajs/web 5.5.0 → 5.6.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 +162 -0
- 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 +9 -0
- package/dist/build.js.map +1 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +140 -12
- 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 +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- 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 +65 -8
- 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 +179 -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 +96 -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 +14 -1
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +415 -31
- package/dist/internal/page-execution.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 +32 -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 +1407 -0
- package/dist/internal/ppr/server.js.map +1 -0
- package/dist/internal/ppr/shell-store.d.ts +38 -0
- package/dist/internal/ppr/shell-store.d.ts.map +1 -0
- package/dist/internal/ppr/shell-store.js +90 -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/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 +28 -1
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +83 -18
- 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/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 +25 -0
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +78 -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 +1069 -0
- package/dist/keep-alive.js.map +1 -0
- package/dist/live/bus.d.ts +56 -0
- package/dist/live/bus.d.ts.map +1 -0
- package/dist/live/bus.js +32 -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 +181 -0
- package/dist/live/hub.d.ts.map +1 -0
- package/dist/live/hub.js +539 -0
- package/dist/live/hub.js.map +1 -0
- package/dist/live/keyring.d.ts +87 -0
- package/dist/live/keyring.d.ts.map +1 -0
- package/dist/live/keyring.js +260 -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 +840 -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 +48 -0
- package/dist/live-redis.d.ts.map +1 -0
- package/dist/live-redis.js +201 -0
- package/dist/live-redis.js.map +1 -0
- package/dist/live.d.ts +94 -0
- package/dist/live.d.ts.map +1 -0
- package/dist/live.js +869 -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 +30 -0
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +5 -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 +374 -99
- 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/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 +86 -28
- 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/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 +19 -1
- package/src/client.ts +147 -10
- 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 +19 -0
- package/src/internal/cdn/cloudflare.ts +2 -1
- package/src/internal/certified-components.ts +1 -1
- package/src/internal/codegen.ts +87 -8
- package/src/internal/dev-websocket.ts +230 -0
- package/src/internal/hole-runtime.ts +286 -0
- package/src/internal/identity-epoch.ts +235 -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 +106 -0
- package/src/internal/live-queries.ts +60 -0
- package/src/internal/page-events.ts +27 -0
- package/src/internal/page-execution.ts +579 -40
- 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 +36 -0
- package/src/internal/ppr/server.ts +1764 -0
- package/src/internal/ppr/shell-store.ts +139 -0
- package/src/internal/prerendered-pages.ts +7 -4
- 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 +149 -22
- 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/store-signature.ts +149 -0
- package/src/internal/vite-plugin-compat.ts +2 -2
- package/src/internal/web-app.ts +121 -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 +1315 -0
- package/src/live/bus.ts +77 -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 +615 -0
- package/src/live/keyring.ts +379 -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 +973 -0
- package/src/live-hub.ts +658 -0
- package/src/live-redis.ts +259 -0
- package/src/live.ts +1096 -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 +35 -0
- package/src/route-types.ts +3 -1
- package/src/router.ts +494 -101
- package/src/server-components/build.ts +9 -3
- package/src/server-components/client.ts +89 -5
- 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 +112 -30
- package/src/server-components/state.ts +66 -44
- package/src/server-components/types.ts +16 -0
- 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
package/src/manifest.ts
CHANGED
|
@@ -12,7 +12,14 @@ import {
|
|
|
12
12
|
paramConstraint,
|
|
13
13
|
} from "@nifrajs/core/pattern"
|
|
14
14
|
import type { CookieOptions, ResponseResult, StandardSchemaV1 } from "@nifrajs/core/server"
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
BoundaryDescriptor,
|
|
17
|
+
BoundaryRegistration,
|
|
18
|
+
BoundaryRequestCtx,
|
|
19
|
+
SharedHoleCtx,
|
|
20
|
+
ShellCtx,
|
|
21
|
+
StaticCtx,
|
|
22
|
+
} from "./boundary.ts"
|
|
16
23
|
import { guardChannel, outputGuard } from "./internal/output-guard.ts"
|
|
17
24
|
import type { RevalidateTags } from "./isr.ts"
|
|
18
25
|
|
|
@@ -60,6 +67,12 @@ export interface LoaderContext {
|
|
|
60
67
|
readonly search: Record<string, unknown>
|
|
61
68
|
/** Response headers and cookies for this page request. */
|
|
62
69
|
readonly set: LoaderResponseControls
|
|
70
|
+
/**
|
|
71
|
+
* Declare what this loader's data depends on, as keys in the cache-tag charset. In a live load
|
|
72
|
+
* (`createWebApp({ live })`) the page reloads this data when one of them changes; elsewhere it does
|
|
73
|
+
* nothing. Keys count only when the loader resolves, and must be declared before it does.
|
|
74
|
+
*/
|
|
75
|
+
readonly live: (...keys: string[]) => void
|
|
63
76
|
}
|
|
64
77
|
|
|
65
78
|
/** A route's optional data loader: params/request in, data out. */
|
|
@@ -157,10 +170,23 @@ export type ClientAction = (
|
|
|
157
170
|
args: ClientActionArgs,
|
|
158
171
|
) => ClientActionResult | undefined | Promise<ClientActionResult | undefined>
|
|
159
172
|
|
|
173
|
+
/** A page's `keepAlive` export as an object. */
|
|
174
|
+
export interface KeepAliveOptions {
|
|
175
|
+
/** Reload the page's data when it is shown again (default `true`). A page marked stale while it was
|
|
176
|
+
* hidden reloads anyway. */
|
|
177
|
+
readonly revalidate?: boolean
|
|
178
|
+
/** Longest the page may stay hidden before it is dropped, in milliseconds (default 10 minutes). */
|
|
179
|
+
readonly maxAge?: number
|
|
180
|
+
}
|
|
181
|
+
|
|
160
182
|
/** Client hooks populated by the generated route entry after a route chunk loads. */
|
|
161
183
|
export interface ClientRouteHooks {
|
|
162
184
|
readonly clientLoader?: ClientLoader
|
|
163
185
|
readonly clientAction?: ClientAction
|
|
186
|
+
/** The page's `shareData` export: `false` gives every reload of it fresh data. */
|
|
187
|
+
readonly shareData?: boolean
|
|
188
|
+
/** The page's `keepAlive` export: the page stays mounted after a visitor leaves it. */
|
|
189
|
+
readonly keepAlive?: true | KeepAliveOptions
|
|
164
190
|
/** Neutral boundary descriptors needed for soft-navigation interception. A boundary's loader lives in
|
|
165
191
|
* the route's backend half, so the browser never learns whether one exists. */
|
|
166
192
|
readonly boundaries?: ReadonlyArray<Omit<BoundaryDescriptor, "hasLoad">>
|
|
@@ -354,6 +380,19 @@ export interface RouteModule {
|
|
|
354
380
|
readonly loaderOutput?: StandardSchemaV1
|
|
355
381
|
/** Backend half only: the schema of what {@link action} sends back, held to the same rule. */
|
|
356
382
|
readonly actionOutput?: StandardSchemaV1
|
|
383
|
+
/**
|
|
384
|
+
* Experimental, backend half only: the loader of a partially prerendered page's shared shell. It
|
|
385
|
+
* receives no request, so its output is the same for every visitor and the shell is cached.
|
|
386
|
+
*/
|
|
387
|
+
readonly shellLoader?: (ctx: ShellCtx) => unknown
|
|
388
|
+
/** Experimental, backend half only: the schema of what {@link shellLoader} returns. */
|
|
389
|
+
readonly shellOutput?: StandardSchemaV1
|
|
390
|
+
/**
|
|
391
|
+
* Experimental, backend half only: query parameters that select a different shell, each with its
|
|
392
|
+
* closed set of values (`{ search: { color: ["red", "blue"] } }`). Any other parameter, or a value
|
|
393
|
+
* outside its set, builds the default shell; holes still read the whole query.
|
|
394
|
+
*/
|
|
395
|
+
readonly shellVariations?: ShellVariations
|
|
357
396
|
/** Optional client-only loader. It runs after hydration and on client navigations. */
|
|
358
397
|
readonly clientLoader?: ClientLoader
|
|
359
398
|
/** Optional client-only submit wrapper. The server action remains mandatory for mutations. */
|
|
@@ -374,6 +413,25 @@ export interface RouteModule {
|
|
|
374
413
|
* Any other key change revalidates as usual - so omitting a data-affecting key here can never serve
|
|
375
414
|
* stale data, it only forgoes the optimization. Client-only: the server always renders fresh. */
|
|
376
415
|
readonly searchClientKeys?: readonly string[]
|
|
416
|
+
/**
|
|
417
|
+
* `false` gives every client reload of this page (an action's revalidation, `invalidate()`, a live
|
|
418
|
+
* refresh) fresh data. By default a reload keeps the identity of the parts of `data` and the layouts'
|
|
419
|
+
* data that it did not change, so an unchanged row keeps its DOM and component state, and a renderer
|
|
420
|
+
* that memoizes on identity skips it. Not every unchanged row, though: an array whose first 32
|
|
421
|
+
* elements all changed is taken whole, later unchanged elements included, so code that needs a row's
|
|
422
|
+
* identity must not count on it. Opt out for a page whose code compares `data` against its value
|
|
423
|
+
* from before the reload, or whose rows re-render whatever their identity, which then pay the
|
|
424
|
+
* comparison for nothing. Client-only.
|
|
425
|
+
*/
|
|
426
|
+
readonly shareData?: boolean
|
|
427
|
+
/**
|
|
428
|
+
* Keep this page mounted after the visitor leaves it, so back and forward show it again as it was
|
|
429
|
+
* left: its component state, form fields, inner scroll offsets and focus. At most three pages are
|
|
430
|
+
* kept per tab, each for at most 10 minutes; a kept page is shown again only by a back or forward to
|
|
431
|
+
* its own history entry, and its data reloads in place. A sign-in or sign-out drops every kept page.
|
|
432
|
+
* Client-only.
|
|
433
|
+
*/
|
|
434
|
+
readonly keepAlive?: true | KeepAliveOptions
|
|
377
435
|
readonly meta?: MetaInput
|
|
378
436
|
/**
|
|
379
437
|
* Opt this route out of nifra's full-document client hydration. The server still renders the full
|
|
@@ -454,8 +512,58 @@ export interface BoundaryLoaderEntry {
|
|
|
454
512
|
readonly load: NonNullable<BoundaryRegistration["load"]>
|
|
455
513
|
/** The boundary data's output schema. Required whenever `load` returns data. */
|
|
456
514
|
readonly output?: StandardSchemaV1
|
|
515
|
+
/**
|
|
516
|
+
* A `shared` boundary's freshness in seconds, required for one and refused for any other: an older
|
|
517
|
+
* entry is served while one rebuild runs. Write the entry inline, so the build can find it.
|
|
518
|
+
*/
|
|
519
|
+
readonly revalidate?: number
|
|
520
|
+
/** A `shared` boundary's tags, as a route's `revalidateTags`: purging one drops its entries. */
|
|
521
|
+
readonly revalidateTags?: RevalidateTags
|
|
457
522
|
}
|
|
458
523
|
|
|
524
|
+
type DeclaredBoundaryOf<Frontend> = Frontend extends {
|
|
525
|
+
readonly boundaries: readonly (infer Declared)[]
|
|
526
|
+
}
|
|
527
|
+
? Declared
|
|
528
|
+
: never
|
|
529
|
+
type BoundaryNameOf<Declared> = Declared extends { readonly name: infer Name extends string }
|
|
530
|
+
? Name
|
|
531
|
+
: never
|
|
532
|
+
// A hole needs an entry unless the page gives it a `load` of its own.
|
|
533
|
+
type HoleNameOf<Declared> = Declared extends { readonly load: (...args: never[]) => unknown }
|
|
534
|
+
? never
|
|
535
|
+
: Declared extends { readonly mode: "dynamic" | "shared" }
|
|
536
|
+
? BoundaryNameOf<Declared>
|
|
537
|
+
: never
|
|
538
|
+
type LoaderEntryOf<Declared> = Declared extends { readonly mode: "shared" }
|
|
539
|
+
? Omit<BoundaryLoaderEntry, "load" | "revalidate"> & {
|
|
540
|
+
readonly load: (ctx: SharedHoleCtx) => unknown
|
|
541
|
+
readonly revalidate: number
|
|
542
|
+
}
|
|
543
|
+
: Omit<BoundaryLoaderEntry, "load" | "revalidate" | "revalidateTags"> & {
|
|
544
|
+
readonly load: (
|
|
545
|
+
ctx: Declared extends { readonly mode: "static" } ? StaticCtx : BoundaryRequestCtx,
|
|
546
|
+
) => unknown
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* The `boundaryLoaders` a backend half may export, from the module types of both halves: an entry
|
|
551
|
+
* names a boundary the page declares, and a route with a `shellLoader` needs one for each hole.
|
|
552
|
+
* Unchecked when TypeScript cannot read the page (`.svelte`, `.vue`) or its boundary names are not
|
|
553
|
+
* literal types, which `as const` on each boundary gives.
|
|
554
|
+
*/
|
|
555
|
+
export type BoundaryLoadersOf<Frontend, Backend> = unknown extends Frontend
|
|
556
|
+
? Readonly<Record<string, BoundaryLoaderEntry>>
|
|
557
|
+
: string extends BoundaryNameOf<DeclaredBoundaryOf<Frontend>>
|
|
558
|
+
? Readonly<Record<string, BoundaryLoaderEntry>>
|
|
559
|
+
: {
|
|
560
|
+
readonly [Declared in DeclaredBoundaryOf<Frontend> as BoundaryNameOf<Declared>]?: LoaderEntryOf<Declared>
|
|
561
|
+
} & (Backend extends { readonly shellLoader: unknown }
|
|
562
|
+
? {
|
|
563
|
+
readonly [Declared in DeclaredBoundaryOf<Frontend> as HoleNameOf<Declared>]: LoaderEntryOf<Declared>
|
|
564
|
+
}
|
|
565
|
+
: unknown)
|
|
566
|
+
|
|
459
567
|
/** A layout (or `_404`/`_error`) entry: its source file (for client codegen) + a lazy loader.
|
|
460
568
|
*
|
|
461
569
|
* A `_layout.tsx` may export `meta` (static {@link Meta} or a function of the loader data + params,
|
|
@@ -506,6 +614,9 @@ export interface RouteEntry {
|
|
|
506
614
|
readonly loadingIds?: readonly string[]
|
|
507
615
|
/** The frontend half: the file the browser bundle imports. */
|
|
508
616
|
readonly file: string
|
|
617
|
+
/** Set when the frontend half exports `keepAlive` (found by a scan of its source): its page renders
|
|
618
|
+
* inside the slot wrappers a kept page needs, on the server and in the browser. */
|
|
619
|
+
readonly keepAlive?: true
|
|
509
620
|
/** The backend half (`x.backend.ts`), when the route has one. Server-only. */
|
|
510
621
|
readonly backend?: string
|
|
511
622
|
/** Both halves, merged and checked. */
|
|
@@ -629,12 +740,101 @@ export const FRONTEND_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
|
|
|
629
740
|
"HydrateFallback",
|
|
630
741
|
"searchSchema",
|
|
631
742
|
"searchClientKeys",
|
|
743
|
+
"shareData",
|
|
744
|
+
"keepAlive",
|
|
632
745
|
"clientLoader",
|
|
633
746
|
"clientAction",
|
|
634
747
|
"boundaries",
|
|
635
748
|
"ssr",
|
|
636
749
|
])
|
|
637
750
|
|
|
751
|
+
/** Query parameters that select a shell variant, each with its closed set of values. */
|
|
752
|
+
export interface ShellVariations {
|
|
753
|
+
readonly search?: Readonly<Record<string, readonly string[]>>
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const VARIATION_NAME = /^[A-Za-z0-9_.-]{1,64}$/
|
|
757
|
+
const MAX_VARIATION_KEYS = 8
|
|
758
|
+
const MAX_VARIATION_VALUES = 32
|
|
759
|
+
const MAX_VARIATION_VALUE_LENGTH = 128
|
|
760
|
+
|
|
761
|
+
/** A closed, small set, so no client can mint unbounded shell keys. */
|
|
762
|
+
function assertShellVariations(backend: string, value: unknown): void {
|
|
763
|
+
const fail = (why: string): never => {
|
|
764
|
+
throw routeError(`"${backend}" exports shellVariations ${why}`)
|
|
765
|
+
}
|
|
766
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
767
|
+
fail("that is not an object")
|
|
768
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: checked just above to be a non-null object that is not an array.
|
|
769
|
+
const record = value as Readonly<Record<string, unknown>>
|
|
770
|
+
for (const key of Object.keys(record))
|
|
771
|
+
if (key !== "search") fail(`with "${key}"; only "search" is read`)
|
|
772
|
+
const search = record.search
|
|
773
|
+
if (search === undefined) return
|
|
774
|
+
if (typeof search !== "object" || search === null || Array.isArray(search))
|
|
775
|
+
fail("whose search is not an object of value lists")
|
|
776
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: checked just above to be a non-null object that is not an array.
|
|
777
|
+
const entries = Object.entries(search as Readonly<Record<string, unknown>>)
|
|
778
|
+
if (entries.length > MAX_VARIATION_KEYS) fail(`with more than ${MAX_VARIATION_KEYS} parameters`)
|
|
779
|
+
for (const [name, values] of entries) {
|
|
780
|
+
if (!VARIATION_NAME.test(name))
|
|
781
|
+
fail(`with a parameter name "${name}" outside ${VARIATION_NAME.source}`)
|
|
782
|
+
if (!Array.isArray(values) || values.length === 0 || values.length > MAX_VARIATION_VALUES)
|
|
783
|
+
fail(`whose "${name}" is not a list of 1 to ${MAX_VARIATION_VALUES} values`)
|
|
784
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: checked just above to be an array.
|
|
785
|
+
const list = values as readonly unknown[]
|
|
786
|
+
if (
|
|
787
|
+
new Set(list).size !== list.length ||
|
|
788
|
+
list.some(
|
|
789
|
+
(item) =>
|
|
790
|
+
typeof item !== "string" || item.length === 0 || item.length > MAX_VARIATION_VALUE_LENGTH,
|
|
791
|
+
)
|
|
792
|
+
)
|
|
793
|
+
fail(
|
|
794
|
+
`whose "${name}" values are not distinct strings of 1 to ${MAX_VARIATION_VALUE_LENGTH} characters`,
|
|
795
|
+
)
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// A year: past it, an entry may as well never refresh.
|
|
800
|
+
const MAX_SHARED_REVALIDATE_SECONDS = 365 * 24 * 60 * 60
|
|
801
|
+
|
|
802
|
+
/** A `shared` boundary's entry carries its own freshness; any other boundary's carries none. */
|
|
803
|
+
function assertSharedEntry(
|
|
804
|
+
backend: string,
|
|
805
|
+
name: string,
|
|
806
|
+
shared: boolean,
|
|
807
|
+
entry: { readonly revalidate?: unknown; readonly revalidateTags?: unknown },
|
|
808
|
+
): void {
|
|
809
|
+
const key = `boundaryLoaders["${name}"]`
|
|
810
|
+
if (!shared) {
|
|
811
|
+
if (entry.revalidate !== undefined || entry.revalidateTags !== undefined) {
|
|
812
|
+
throw routeError(
|
|
813
|
+
`${key} in "${backend}" has revalidate or revalidateTags, which only a shared boundary's entry takes`,
|
|
814
|
+
)
|
|
815
|
+
}
|
|
816
|
+
return
|
|
817
|
+
}
|
|
818
|
+
const { revalidate, revalidateTags } = entry
|
|
819
|
+
if (
|
|
820
|
+
typeof revalidate !== "number" ||
|
|
821
|
+
!Number.isInteger(revalidate) ||
|
|
822
|
+
revalidate < 1 ||
|
|
823
|
+
revalidate > MAX_SHARED_REVALIDATE_SECONDS
|
|
824
|
+
) {
|
|
825
|
+
throw routeError(
|
|
826
|
+
`${key} in "${backend}" is for a shared boundary, so it needs revalidate: a whole number of seconds from 1 to ${MAX_SHARED_REVALIDATE_SECONDS}`,
|
|
827
|
+
)
|
|
828
|
+
}
|
|
829
|
+
if (
|
|
830
|
+
revalidateTags !== undefined &&
|
|
831
|
+
typeof revalidateTags !== "function" &&
|
|
832
|
+
!(Array.isArray(revalidateTags) && revalidateTags.every((tag) => typeof tag === "string"))
|
|
833
|
+
) {
|
|
834
|
+
throw routeError(`${key}.revalidateTags in "${backend}" must be a list of tags or a function`)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
638
838
|
/** Exports only the server or the build reads. They live in the route's `x.backend.ts`. */
|
|
639
839
|
export const BACKEND_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
|
|
640
840
|
"loader",
|
|
@@ -652,6 +852,9 @@ export const BACKEND_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
|
|
|
652
852
|
"gate",
|
|
653
853
|
"shouldRevalidate",
|
|
654
854
|
"allowedOrigins",
|
|
855
|
+
"shellLoader",
|
|
856
|
+
"shellOutput",
|
|
857
|
+
"shellVariations",
|
|
655
858
|
])
|
|
656
859
|
|
|
657
860
|
const routeError = (message: string): Error => new Error(`[nifra/web] ${message}`)
|
|
@@ -719,6 +922,12 @@ export function mergeRouteHalves(
|
|
|
719
922
|
const cached = mergedHalves.get(front)
|
|
720
923
|
if (cached !== undefined && cached.back === back) return cached.module
|
|
721
924
|
assertFrontendHalf(file, front as unknown as Readonly<Record<string, unknown>>)
|
|
925
|
+
const shared = front.boundaries?.find((boundary) => boundary.mode === "shared")
|
|
926
|
+
if (shared !== undefined && back?.shellLoader === undefined) {
|
|
927
|
+
throw routeError(
|
|
928
|
+
`boundary "${shared.name}" in "${file}" is shared, which only a partially prerendered page has: its backend half must export shellLoader`,
|
|
929
|
+
)
|
|
930
|
+
}
|
|
722
931
|
if (back === undefined || backend === undefined) {
|
|
723
932
|
mergedHalves.set(front, { back, module: front })
|
|
724
933
|
return front
|
|
@@ -731,12 +940,30 @@ export function mergeRouteHalves(
|
|
|
731
940
|
action,
|
|
732
941
|
loaderOutput,
|
|
733
942
|
actionOutput,
|
|
943
|
+
shellLoader,
|
|
944
|
+
shellOutput,
|
|
734
945
|
...server
|
|
735
946
|
} = back
|
|
736
|
-
|
|
947
|
+
if (shellLoader !== undefined && loader !== undefined) {
|
|
948
|
+
throw routeError(
|
|
949
|
+
`"${backend}" exports both shellLoader and loader. A partially prerendered page's per-visitor data comes from its dynamic boundaries' loaders`,
|
|
950
|
+
)
|
|
951
|
+
}
|
|
952
|
+
if (server.shellVariations !== undefined) {
|
|
953
|
+
if (shellLoader === undefined)
|
|
954
|
+
throw routeError(`"${backend}" exports shellVariations but no shellLoader for them to vary`)
|
|
955
|
+
assertShellVariations(backend, server.shellVariations)
|
|
956
|
+
}
|
|
957
|
+
if (shellLoader !== undefined && server.prerender === true) {
|
|
958
|
+
throw routeError(
|
|
959
|
+
`"${backend}" exports shellLoader with prerender = true. A page with no per-visitor part needs no shellLoader`,
|
|
960
|
+
)
|
|
961
|
+
}
|
|
962
|
+
const guarded: { loader?: unknown; action?: unknown; shellLoader?: unknown } = {}
|
|
737
963
|
for (const [name, fn, schema, schemaName] of [
|
|
738
964
|
["loader", loader, loaderOutput, "loaderOutput"],
|
|
739
965
|
["action", action, actionOutput, "actionOutput"],
|
|
966
|
+
["shellLoader", shellLoader, shellOutput, "shellOutput"],
|
|
740
967
|
] as const) {
|
|
741
968
|
if (fn === undefined) {
|
|
742
969
|
if (schema !== undefined) {
|
|
@@ -759,11 +986,20 @@ export function mergeRouteHalves(
|
|
|
759
986
|
throw routeError(`"${backend}" exports boundaryLoaders that is not an object of loaders`)
|
|
760
987
|
}
|
|
761
988
|
const loaders = boundaryLoaders as Readonly<
|
|
762
|
-
Record<
|
|
989
|
+
Record<
|
|
990
|
+
string,
|
|
991
|
+
{
|
|
992
|
+
readonly load?: unknown
|
|
993
|
+
readonly output?: unknown
|
|
994
|
+
readonly revalidate?: unknown
|
|
995
|
+
readonly revalidateTags?: unknown
|
|
996
|
+
}
|
|
997
|
+
>
|
|
763
998
|
>
|
|
764
|
-
const declared = new
|
|
999
|
+
const declared = new Map((front.boundaries ?? []).map((boundary) => [boundary.name, boundary]))
|
|
765
1000
|
for (const [name, entry] of Object.entries(loaders)) {
|
|
766
|
-
|
|
1001
|
+
const boundary = declared.get(name)
|
|
1002
|
+
if (boundary === undefined) {
|
|
767
1003
|
throw routeError(
|
|
768
1004
|
`"${backend}" has boundaryLoaders["${name}"], but "${file}" declares no boundary named "${name}"`,
|
|
769
1005
|
)
|
|
@@ -771,6 +1007,7 @@ export function mergeRouteHalves(
|
|
|
771
1007
|
if (typeof entry?.load !== "function") {
|
|
772
1008
|
throw routeError(`boundaryLoaders["${name}"].load in "${backend}" must be a function`)
|
|
773
1009
|
}
|
|
1010
|
+
assertSharedEntry(backend, name, boundary.mode === "shared", entry)
|
|
774
1011
|
}
|
|
775
1012
|
boundaries = front.boundaries?.map((boundary) => {
|
|
776
1013
|
const entry = loaders[boundary.name]
|
|
@@ -784,7 +1021,16 @@ export function mergeRouteHalves(
|
|
|
784
1021
|
: outputGuard(entry.output, `"${backend}"`, `${key}.output`),
|
|
785
1022
|
missing: `${key}.output in "${backend}"`,
|
|
786
1023
|
})
|
|
787
|
-
|
|
1024
|
+
const merged =
|
|
1025
|
+
boundary.mode === "shared"
|
|
1026
|
+
? {
|
|
1027
|
+
...boundary,
|
|
1028
|
+
load,
|
|
1029
|
+
revalidate: entry.revalidate,
|
|
1030
|
+
revalidateTags: entry.revalidateTags,
|
|
1031
|
+
}
|
|
1032
|
+
: { ...boundary, load }
|
|
1033
|
+
return merged as BoundaryRegistration
|
|
788
1034
|
})
|
|
789
1035
|
}
|
|
790
1036
|
const module = {
|
|
@@ -1024,6 +1270,25 @@ const ancestorDirs = (file: string): string[] => {
|
|
|
1024
1270
|
return dirs
|
|
1025
1271
|
}
|
|
1026
1272
|
|
|
1273
|
+
/** Options for {@link buildManifest}. */
|
|
1274
|
+
export interface BuildManifestOptions {
|
|
1275
|
+
/** Route files (as in `files`) whose frontend half exports `keepAlive`: see {@link exportsKeepAlive}. */
|
|
1276
|
+
readonly keepAlive?: readonly string[]
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// A declaration or an export list naming `keepAlive`, at the start of a line: a comment or a string
|
|
1280
|
+
// that mentions one does not start a line with `export`.
|
|
1281
|
+
const KEEP_ALIVE_EXPORT =
|
|
1282
|
+
/^[ \t]*export[ \t]+(?:const|let|var)[ \t]+keepAlive\b|^[ \t]*export[ \t]*\{[^}]*\bkeepAlive\b[^}]*\}/m
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Whether a route file's source exports `keepAlive`, read without running it: the server and the
|
|
1286
|
+
* client build both need the answer for every route, before any route module has loaded. A `.svelte`
|
|
1287
|
+
* or `.vue` file exports it from its module script the same way. An export the scan cannot see (an
|
|
1288
|
+
* `export *`) keeps nothing.
|
|
1289
|
+
*/
|
|
1290
|
+
export const exportsKeepAlive = (source: string): boolean => KEEP_ALIVE_EXPORT.test(source)
|
|
1291
|
+
|
|
1027
1292
|
/**
|
|
1028
1293
|
* Build a manifest from route file paths (relative to the routes dir) + an `importer` that
|
|
1029
1294
|
* turns a path into a lazy module loader. Pure - no fs. Throws at boot (the loud-and-early
|
|
@@ -1036,7 +1301,9 @@ const ancestorDirs = (file: string): string[] => {
|
|
|
1036
1301
|
export function buildManifest(
|
|
1037
1302
|
files: readonly string[],
|
|
1038
1303
|
importer: (file: string) => () => Promise<RouteModule>,
|
|
1304
|
+
options: BuildManifestOptions = {},
|
|
1039
1305
|
): Manifest {
|
|
1306
|
+
const keepAliveFiles = new Set(options.keepAlive)
|
|
1040
1307
|
const layoutDirs = new Set<string>()
|
|
1041
1308
|
const layouts: Record<string, LayoutEntry> = {}
|
|
1042
1309
|
const errorDirs = new Set<string>()
|
|
@@ -1202,6 +1469,7 @@ export function buildManifest(
|
|
|
1202
1469
|
...(notFoundIds.length > 0 ? { notFoundIds } : {}),
|
|
1203
1470
|
...(loadingIds.length > 0 ? { loadingIds } : {}),
|
|
1204
1471
|
file,
|
|
1472
|
+
...(keepAliveFiles.has(file) ? { keepAlive: true } : {}),
|
|
1205
1473
|
...(backend === undefined ? {} : { backend }),
|
|
1206
1474
|
load,
|
|
1207
1475
|
})
|
package/src/navigation.ts
CHANGED
|
@@ -126,15 +126,27 @@ export type BrowserNavigate = (to: string | number, options?: NavigateOptions) =
|
|
|
126
126
|
// teardown). Module-scoped: one app per page; absent on the server and before hydration.
|
|
127
127
|
let browserNavigate: BrowserNavigate | undefined
|
|
128
128
|
let browserRouter: ClientRouter | undefined
|
|
129
|
+
let browserCloseBoundary: ((name: string) => void) | undefined
|
|
129
130
|
|
|
130
|
-
/** Register (or clear, with `undefined`) the browser navigate and its
|
|
131
|
-
* `installHistory`. Not for app use. */
|
|
131
|
+
/** Register (or clear, with `undefined`) the browser navigate, its router and its boundary close -
|
|
132
|
+
* called by `installHistory`. Not for app use. */
|
|
132
133
|
export function setBrowserNavigate(
|
|
133
134
|
navigate: BrowserNavigate | undefined,
|
|
134
135
|
router?: ClientRouter,
|
|
136
|
+
close?: (name: string) => void,
|
|
135
137
|
): void {
|
|
136
138
|
browserNavigate = navigate
|
|
137
139
|
browserRouter = router
|
|
140
|
+
browserCloseBoundary = close
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Close the intercept boundary `name` and return to the page underneath it: back through the history
|
|
145
|
+
* entries the boundary added when the page's entry is there, otherwise by replacing the boundary's URL
|
|
146
|
+
* with the page's. A no-op on the server, before hydration, and when `name` holds no target.
|
|
147
|
+
*/
|
|
148
|
+
export function closeBoundary(name: string): void {
|
|
149
|
+
browserCloseBoundary?.(name)
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
/** The mounted client router, or `undefined` on the server / before `installHistory` has run. */
|
|
@@ -33,6 +33,12 @@ export interface ViteZoneGuardOptions {
|
|
|
33
33
|
readonly generatedFiles?: readonly string[]
|
|
34
34
|
/** The public-env prefix browser code may read (default `"PUBLIC_"`). */
|
|
35
35
|
readonly publicEnvPrefix?: string
|
|
36
|
+
/**
|
|
37
|
+
* The coordinated compiler runs in this build and replaces each plain browser import of a
|
|
38
|
+
* `*.component.server.*` file with the component's reference, so such an import may resolve. Its
|
|
39
|
+
* file is still refused if it is ever loaded into the browser build.
|
|
40
|
+
*/
|
|
41
|
+
readonly componentReferences?: boolean
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
/** Zones whose code runs in a browser, so may read only public environment variables. */
|
|
@@ -223,10 +229,17 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
|
|
|
223
229
|
if (!isClient(this, resolveOptions?.ssr)) return null
|
|
224
230
|
const importerFile = withoutQuery(importer)
|
|
225
231
|
if (!isAbsolute(importerFile)) return null
|
|
232
|
+
// The compiler replaces a plain import of a component with its reference; a query form such as
|
|
233
|
+
// `?raw` would ship the file itself.
|
|
234
|
+
const referenced = options.componentReferences === true && withoutQuery(source) === source
|
|
235
|
+
const denial = (classification: Classification, specifier?: string) =>
|
|
236
|
+
referenced && classification.zone === "component"
|
|
237
|
+
? undefined
|
|
238
|
+
: browserDenial(classification, specifier)
|
|
226
239
|
if (source.startsWith(".")) {
|
|
227
240
|
const target = resolve(dirname(importerFile), withoutQuery(source))
|
|
228
241
|
if (isFile(target)) {
|
|
229
|
-
const reason =
|
|
242
|
+
const reason = denial(classifier.classify(target))
|
|
230
243
|
if (reason !== undefined) refuse(this, target, reason, importerFile)
|
|
231
244
|
}
|
|
232
245
|
return null
|
|
@@ -240,7 +253,7 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
|
|
|
240
253
|
const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true })
|
|
241
254
|
const target = resolved === null || resolved.external ? undefined : withoutQuery(resolved.id)
|
|
242
255
|
if (target !== undefined && isAbsolute(target) && isFile(target)) {
|
|
243
|
-
const reason =
|
|
256
|
+
const reason = denial(classifier.classify(target, source), source)
|
|
244
257
|
if (reason !== undefined) refuse(this, target, reason, importerFile)
|
|
245
258
|
}
|
|
246
259
|
return null
|
package/src/ppr.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web/ppr` - experimental and unstable: partial prerendering.
|
|
3
|
+
*
|
|
4
|
+
* A route whose backend half exports `shellLoader` streams a shell shared by every visitor, then each
|
|
5
|
+
* `dynamic` boundary as it settles. Turn it on with `createWebApp({ experimental: { ppr:
|
|
6
|
+
* partialPrerendering({ shellChecks }) } })`, where `shellChecks` is the record `compileShells` returns
|
|
7
|
+
* for this deployment; a route left out of it renders whole. `pprShells(app)` reaches the shell cache,
|
|
8
|
+
* for `createInvalidator`'s `dataCaches` and for metrics. The server lives in this module, so an app
|
|
9
|
+
* that never imports it ships none of it. Backend-only: a browser build that reaches this module fails.
|
|
10
|
+
*/
|
|
11
|
+
import "@nifrajs/web/backend-only"
|
|
12
|
+
import type { SharedHoleCtx, ShellCtx } from "./boundary.ts"
|
|
13
|
+
import type { ShellCheckRecord } from "./internal/ppr/compile-shell.ts"
|
|
14
|
+
import {
|
|
15
|
+
PPR_SERVER,
|
|
16
|
+
PPR_SETUP,
|
|
17
|
+
type PprServer,
|
|
18
|
+
type PprServerStats,
|
|
19
|
+
type PprSetup,
|
|
20
|
+
} from "./internal/ppr/contract.ts"
|
|
21
|
+
import { createPprIntegration } from "./internal/ppr/integration.ts"
|
|
22
|
+
import { type PprOptions, resolvePprOptions } from "./internal/ppr/options.ts"
|
|
23
|
+
import { isStoreSecret, MIN_STORE_SECRET_LENGTH } from "./internal/store-signature.ts"
|
|
24
|
+
import type { CacheStore } from "./isr.ts"
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
type CanaryCarrier,
|
|
28
|
+
checkShellCanaries,
|
|
29
|
+
type ShellCanaryFinding,
|
|
30
|
+
type ShellCanaryOptions,
|
|
31
|
+
} from "./internal/ppr/canary.ts"
|
|
32
|
+
export {
|
|
33
|
+
type CompileShellsOptions,
|
|
34
|
+
compileShells,
|
|
35
|
+
ShellCheckError,
|
|
36
|
+
type ShellCheckRecord,
|
|
37
|
+
} from "./internal/ppr/compile-shell.ts"
|
|
38
|
+
export { PPR_DEFAULTS, type PprOptions } from "./internal/ppr/options.ts"
|
|
39
|
+
export type { PprSetup, SharedHoleCtx, ShellCtx }
|
|
40
|
+
|
|
41
|
+
export interface PprConfig {
|
|
42
|
+
/** From `compileShells` for this deployment; `"unchecked-development"` is refused in production. */
|
|
43
|
+
readonly shellChecks: ShellCheckRecord | "unchecked-development"
|
|
44
|
+
/** Overrides for the defaults in `PPR_DEFAULTS`. */
|
|
45
|
+
readonly options?: PprOptions
|
|
46
|
+
/**
|
|
47
|
+
* The deployment's public origin, such as `https://shop.example`, given to `shellLoader` and to the
|
|
48
|
+
* shell's `meta` as `origin`. Without it both get `""`: a shell is shared, so no visitor's Host
|
|
49
|
+
* header may supply it.
|
|
50
|
+
*/
|
|
51
|
+
readonly origin?: string
|
|
52
|
+
/**
|
|
53
|
+
* A store the app's processes share, such as `new KVCacheStore(env.SHELLS)`, behind each process's
|
|
54
|
+
* own shells and shared hole frames: a process that has not built one reads it from here. Needs
|
|
55
|
+
* `storeSecret` and checked shells. A route whose tags the store cannot drop keeps its entries
|
|
56
|
+
* in-process.
|
|
57
|
+
*/
|
|
58
|
+
readonly store?: CacheStore
|
|
59
|
+
/**
|
|
60
|
+
* Signs every entry written to `store`, so a store other services can write cannot hand visitors
|
|
61
|
+
* a page this app did not build. At least 32 characters, the same in every process.
|
|
62
|
+
*/
|
|
63
|
+
readonly storeSecret?: string
|
|
64
|
+
/**
|
|
65
|
+
* The platform's name for this deployment, such as a commit or a version id. Shells are already
|
|
66
|
+
* kept apart by the client build and by each route's checked shell code; this adds to that.
|
|
67
|
+
*/
|
|
68
|
+
readonly deployment?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** `value` is exactly an http(s) origin: a scheme, a host and an optional port, nothing after. */
|
|
72
|
+
function isOrigin(value: string): boolean {
|
|
73
|
+
let url: URL
|
|
74
|
+
try {
|
|
75
|
+
url = new URL(value)
|
|
76
|
+
} catch {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
return (url.protocol === "https:" || url.protocol === "http:") && url.origin === value
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Experimental: partial prerendering for `createWebApp({ experimental: { ppr } })`. Options are
|
|
84
|
+
* checked here, at startup; each app the value is given to gets its own shell cache.
|
|
85
|
+
*/
|
|
86
|
+
export function partialPrerendering(config: PprConfig): PprSetup {
|
|
87
|
+
const options = resolvePprOptions(config.options)
|
|
88
|
+
const { origin, shellChecks, store, storeSecret, deployment } = config
|
|
89
|
+
if (origin !== undefined && !isOrigin(origin)) {
|
|
90
|
+
throw new TypeError(
|
|
91
|
+
`[nifra/web] experimental.ppr.origin must be an http(s) origin such as "https://shop.example", with no path; got ${JSON.stringify(origin)}`,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
if (deployment !== undefined && (typeof deployment !== "string" || deployment === "")) {
|
|
95
|
+
throw new TypeError("[nifra/web] experimental.ppr.deployment must be a non-empty string")
|
|
96
|
+
}
|
|
97
|
+
if (store !== undefined) {
|
|
98
|
+
if (shellChecks === "unchecked-development") {
|
|
99
|
+
throw new TypeError(
|
|
100
|
+
"[nifra/web] experimental.ppr.store needs the build's shell check records: an unchecked shell is never shared",
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
if (!isStoreSecret(storeSecret)) {
|
|
104
|
+
throw new TypeError(
|
|
105
|
+
`[nifra/web] experimental.ppr.store needs storeSecret, at least ${MIN_STORE_SECRET_LENGTH} characters, to sign the shells it shares`,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
[PPR_SETUP]: (environment) =>
|
|
111
|
+
createPprIntegration(options, shellChecks, origin, environment, {
|
|
112
|
+
store,
|
|
113
|
+
storeSecret,
|
|
114
|
+
deployment,
|
|
115
|
+
}),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Types a backend half's `shellLoader` from its context; returns it unchanged. */
|
|
120
|
+
export const defineShellLoader = <Data>(
|
|
121
|
+
loader: (ctx: ShellCtx) => Data | Promise<Data>,
|
|
122
|
+
): ((ctx: ShellCtx) => Data | Promise<Data>) => loader
|
|
123
|
+
|
|
124
|
+
/** Admission, shedding and unsent-byte counters of one app's partially prerendered responses. */
|
|
125
|
+
export type PprStats = PprServerStats
|
|
126
|
+
|
|
127
|
+
/** One app's shell cache. */
|
|
128
|
+
export interface PprShells {
|
|
129
|
+
/**
|
|
130
|
+
* Drop every shell and shared hole frame whose `revalidateTags` listed `tag`, here and in the
|
|
131
|
+
* shared store; a build of either running now is not kept.
|
|
132
|
+
*/
|
|
133
|
+
invalidateTag(tag: string): Promise<void>
|
|
134
|
+
stats(): PprStats
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const isPprServer = (value: unknown): value is PprServer =>
|
|
138
|
+
typeof value === "object" && value !== null && "invalidateTag" in value && "stats" in value
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The shell cache of an app made with `experimental.ppr`. Pass it in `createInvalidator`'s
|
|
142
|
+
* `dataCaches` so a purge by tag also drops the shells built from the purged data.
|
|
143
|
+
*/
|
|
144
|
+
export function pprShells(app: object): PprShells {
|
|
145
|
+
const server: unknown = Reflect.get(app, PPR_SERVER)
|
|
146
|
+
if (!isPprServer(server)) {
|
|
147
|
+
throw new TypeError("[nifra/web/ppr] pprShells: this app was created without experimental.ppr")
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
invalidateTag: (tag) => server.invalidateTag(tag),
|
|
151
|
+
stats: () => server.stats(),
|
|
152
|
+
}
|
|
153
|
+
}
|