@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
package/src/build.ts
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
import { sanitizeOutputNames } from "./chunk-names.ts"
|
|
55
55
|
import { type CssLoadingMode, normalizeCssCodeSplit, normalizeCssLoading } from "./css-contract.ts"
|
|
56
56
|
import { discoverRoutes } from "./fs.ts"
|
|
57
|
-
import { generateClientEntry, generateServerManifest } from "./index.ts"
|
|
57
|
+
import { generateClientEntry, generateServerManifest, type LiveClientEntry } from "./index.ts"
|
|
58
58
|
import { missingClientEntry } from "./internal/codegen.ts"
|
|
59
59
|
import { loadCopySplit } from "./internal/copy-split.ts"
|
|
60
60
|
import { assertDisposableDir } from "./internal/disposable-dir.ts"
|
|
@@ -208,6 +208,11 @@ export interface BuildClientOptions {
|
|
|
208
208
|
* every table whole; the server entry passes the manifest's `copyChunks` to `createWebApp`.
|
|
209
209
|
*/
|
|
210
210
|
readonly copySplit?: boolean
|
|
211
|
+
/**
|
|
212
|
+
* The server runs `createWebApp({ live })`: the client entry sends route loads through its live load
|
|
213
|
+
* endpoint and keeps what is on screen fresh: the runtime's `clientEntry`.
|
|
214
|
+
*/
|
|
215
|
+
readonly live?: LiveClientEntry | undefined
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
/**
|
|
@@ -479,6 +484,7 @@ export async function buildClient(options: BuildClientOptions): Promise<BuildMan
|
|
|
479
484
|
clientModule,
|
|
480
485
|
resolve,
|
|
481
486
|
copyChunks: copySplit !== undefined,
|
|
487
|
+
live: options.live,
|
|
482
488
|
})}`,
|
|
483
489
|
)
|
|
484
490
|
|
|
@@ -1331,6 +1337,8 @@ export const SERVER_ENTRY_OPTIONS = [
|
|
|
1331
1337
|
"nonce",
|
|
1332
1338
|
"i18n",
|
|
1333
1339
|
"hostPolicy",
|
|
1340
|
+
"live",
|
|
1341
|
+
"keepAlive",
|
|
1334
1342
|
] as const
|
|
1335
1343
|
export type ServerEntryOption = (typeof SERVER_ENTRY_OPTIONS)[number]
|
|
1336
1344
|
/** Each importable option mapped to the specifier of the module that exports it. */
|
|
@@ -1894,6 +1902,8 @@ export interface BuildTargetOptions {
|
|
|
1894
1902
|
/** Trust the edge platform's client-address header for `c.clientIp` (`cloudflare`, `vercel`). Off by
|
|
1895
1903
|
* default, leaving an edge app with no caller address. See {@link ServerEntryClientIp}. */
|
|
1896
1904
|
readonly clientIp?: ServerEntryClientIp
|
|
1905
|
+
/** The client entry for a server built with `optionImports.live`: that runtime's `clientEntry`. */
|
|
1906
|
+
readonly live?: LiveClientEntry | undefined
|
|
1897
1907
|
}
|
|
1898
1908
|
|
|
1899
1909
|
/** Minimal app surface `buildTarget`'s static path needs - a fetch handler (a built `createWebApp`). */
|
|
@@ -1949,6 +1959,7 @@ export const bunBundler: Bundler = {
|
|
|
1949
1959
|
...(input.publicDir !== undefined ? { publicDir: input.publicDir } : {}),
|
|
1950
1960
|
...(input.publicEnvPrefix !== undefined ? { publicEnvPrefix: input.publicEnvPrefix } : {}),
|
|
1951
1961
|
...(input.secretExemptions !== undefined ? { secretExemptions: input.secretExemptions } : {}),
|
|
1962
|
+
live: input.live,
|
|
1952
1963
|
})
|
|
1953
1964
|
},
|
|
1954
1965
|
buildServer: (input) =>
|
|
@@ -2053,6 +2064,13 @@ export async function buildTargetWith(
|
|
|
2053
2064
|
bundler: Bundler,
|
|
2054
2065
|
): Promise<BuildTargetResult> {
|
|
2055
2066
|
const { routesDir, outDir, workDir } = options
|
|
2067
|
+
// One without the other ships a client that posts to an endpoint the server lacks, or a live server
|
|
2068
|
+
// no client ever watches.
|
|
2069
|
+
if ((options.optionImports?.live === undefined) !== (options.live === undefined)) {
|
|
2070
|
+
throw new Error(
|
|
2071
|
+
"[nifra/web] buildTarget: `optionImports.live` and `live` go together - the server's runtime and the client entry that reaches it",
|
|
2072
|
+
)
|
|
2073
|
+
}
|
|
2056
2074
|
const requestedCssCodeSplit =
|
|
2057
2075
|
options.cssCodeSplit === undefined ? undefined : normalizeCssCodeSplit(options.cssCodeSplit)
|
|
2058
2076
|
const requestedCssLoading =
|
|
@@ -2101,6 +2119,7 @@ export async function buildTargetWith(
|
|
|
2101
2119
|
...(requestedCssCodeSplit !== undefined ? { cssCodeSplit: requestedCssCodeSplit } : {}),
|
|
2102
2120
|
...(requestedCssLoading !== undefined ? { cssLoading: requestedCssLoading } : {}),
|
|
2103
2121
|
root: resolvePath(dirname(routesDir)),
|
|
2122
|
+
live: options.live,
|
|
2104
2123
|
})
|
|
2105
2124
|
// Bun's client builder has no CSS-loading field, but the server-side document policy is bundler
|
|
2106
2125
|
// independent. Carry the requested policy into the result so static prerender callbacks and the
|
package/src/client.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* a full page load). DOM-only - never imported on the server, so the store stays SSR-safe.
|
|
6
6
|
*/
|
|
7
7
|
import { trustedHeadAttributes } from "./internal/head-attributes.ts"
|
|
8
|
+
import { setPresenter } from "./internal/present.ts"
|
|
8
9
|
import { appUrl } from "./internal/route-path.ts"
|
|
9
10
|
import { EXECUTABLE_SCRIPT_TYPES, INERT_SCRIPT_TYPES } from "./internal/script-types.ts"
|
|
10
11
|
import type { Meta } from "./manifest.ts"
|
|
@@ -35,6 +36,7 @@ export function currentDocumentNonce(): string | undefined {
|
|
|
35
36
|
return script.getAttribute("nonce") ?? undefined
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
export { identityChanged } from "./identity.ts"
|
|
38
40
|
/**
|
|
39
41
|
* Everything the generated client entry needs, re-exported here so that entry imports ONE module and
|
|
40
42
|
* that module is DOM-only. It used to take these from `@nifrajs/web`, whose graph includes `renderPage`
|
|
@@ -43,7 +45,11 @@ export function currentDocumentNonce(): string | undefined {
|
|
|
43
45
|
* before hydrating. The fix is for the entry not to name the server module in the first place.
|
|
44
46
|
*/
|
|
45
47
|
export { mergeHeads, resolveChainMeta, resolveMeta } from "./internal/head-merge.ts"
|
|
48
|
+
// For route helpers shared by a loader and a component, which the browser loads too: imported from the
|
|
49
|
+
// root, they put its `node:crypto` import into Vite dev's browser deps on every page.
|
|
50
|
+
export { gone, notFound, statusPage } from "./internal/status-page.ts"
|
|
46
51
|
export {
|
|
52
|
+
closeBoundary,
|
|
47
53
|
getBrowserNavigate,
|
|
48
54
|
IDLE_BLOCKER,
|
|
49
55
|
registerBlocker,
|
|
@@ -209,6 +215,10 @@ export function waitForStyles(options: WaitForStylesOptions = {}): Promise<void>
|
|
|
209
215
|
return Promise.all(promises).then(() => undefined)
|
|
210
216
|
}
|
|
211
217
|
|
|
218
|
+
// How long a navigation's view transition waits for the frame that captures the page before its
|
|
219
|
+
// animation is skipped: several frames at any refresh rate, and short enough to go unnoticed.
|
|
220
|
+
const CAPTURE_DEADLINE_MS = 100
|
|
221
|
+
|
|
212
222
|
/**
|
|
213
223
|
* Resolve `href` against this page and keep it only when history may move there without a load: same
|
|
214
224
|
* scheme and host as the document, or null. Compared by protocol + host rather than `URL.origin`,
|
|
@@ -255,6 +265,48 @@ export function installHistory(
|
|
|
255
265
|
let reversing = false
|
|
256
266
|
// The path the router was last seen loading, so a redirect it follows shows up as a change.
|
|
257
267
|
let expected: string | undefined
|
|
268
|
+
// The entry holding the page last shown without an intercept boundary, so closing a boundary opened
|
|
269
|
+
// over it can go back there. Cleared once that entry is replaced or cut off by a push.
|
|
270
|
+
let pageEntry: number | undefined = index
|
|
271
|
+
// Before writing the entry at `index` (replace) or pushing past it, which drops every later entry.
|
|
272
|
+
const writingEntry = (mode: "push" | "replace"): void => {
|
|
273
|
+
if (mode === "replace" ? pageEntry === index : pageEntry !== undefined && pageEntry > index)
|
|
274
|
+
pageEntry = undefined
|
|
275
|
+
}
|
|
276
|
+
// Every entry this document owns carries its index, the first one included, so an entry without one
|
|
277
|
+
// is one the browser pushed itself: a same-page fragment link, which this router leaves native.
|
|
278
|
+
// Number it as the push it was, or index deltas (a guard's reversal, closeBoundary) count wrong.
|
|
279
|
+
const numbered = (): boolean => {
|
|
280
|
+
const current: unknown = history.state
|
|
281
|
+
return (
|
|
282
|
+
typeof current === "object" &&
|
|
283
|
+
current !== null &&
|
|
284
|
+
"nifraIndex" in current &&
|
|
285
|
+
typeof current.nifraIndex === "number"
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
const stampEntry = (): void => {
|
|
289
|
+
const current: unknown = history.state
|
|
290
|
+
history.replaceState(
|
|
291
|
+
Object.assign({}, typeof current === "object" ? current : null, { nifraIndex: index }),
|
|
292
|
+
"",
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
const adoptNativeEntry = (): boolean => {
|
|
296
|
+
if (numbered()) return false
|
|
297
|
+
writingEntry("push")
|
|
298
|
+
index += 1
|
|
299
|
+
stampEntry()
|
|
300
|
+
here = location.pathname + location.search + (location.hash ?? "")
|
|
301
|
+
return true
|
|
302
|
+
}
|
|
303
|
+
// An opaque or file: document routes nothing, so its history is left alone. A refused write (a
|
|
304
|
+
// browser's rate limit) only leaves fragment entries uncounted, as before.
|
|
305
|
+
if (location.origin !== "null" && location.protocol !== "file:" && !numbered()) {
|
|
306
|
+
try {
|
|
307
|
+
stampEntry()
|
|
308
|
+
} catch {}
|
|
309
|
+
}
|
|
258
310
|
type Registration = {
|
|
259
311
|
readonly shouldBlock: BlockerFunction
|
|
260
312
|
readonly emit: (blocker: Blocker) => void
|
|
@@ -281,29 +333,60 @@ export function installHistory(
|
|
|
281
333
|
return document.getElementById(id)
|
|
282
334
|
}
|
|
283
335
|
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
336
|
+
// A navigation loads first and hands only its page swap here, so where the browser has View
|
|
337
|
+
// Transitions the page keeps painting, pending UI included, while the data loads. The browser runs
|
|
338
|
+
// the swap once a frame has captured the page; one drawing no frames while it reports itself visible
|
|
339
|
+
// (an occluded window) would hold it forever, so the deadline skips the animation, running the swap.
|
|
287
340
|
type ViewTransition = {
|
|
288
341
|
readonly ready: Promise<unknown>
|
|
289
342
|
readonly finished: Promise<unknown>
|
|
290
343
|
readonly updateCallbackDone: Promise<unknown>
|
|
344
|
+
skipTransition(): void
|
|
291
345
|
}
|
|
292
346
|
type ViewTransitionDocument = Document & {
|
|
293
347
|
startViewTransition?: (cb: () => unknown) => ViewTransition
|
|
294
348
|
}
|
|
295
|
-
|
|
349
|
+
setPresenter((swap) => {
|
|
296
350
|
const doc = document as ViewTransitionDocument
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
351
|
+
const start = doc.startViewTransition
|
|
352
|
+
if (typeof start !== "function") {
|
|
353
|
+
swap()
|
|
354
|
+
return Promise.resolve()
|
|
300
355
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
356
|
+
return new Promise((resolve, reject) => {
|
|
357
|
+
let swapped = false
|
|
358
|
+
const vt = start.call(doc, () => {
|
|
359
|
+
swapped = true
|
|
360
|
+
try {
|
|
361
|
+
swap()
|
|
362
|
+
resolve()
|
|
363
|
+
} catch (error) {
|
|
364
|
+
reject(error)
|
|
365
|
+
}
|
|
366
|
+
})
|
|
367
|
+
setTimeout(() => {
|
|
368
|
+
if (!swapped) vt.skipTransition()
|
|
369
|
+
}, CAPTURE_DEADLINE_MS)
|
|
370
|
+
// A newer navigation's transition, or the deadline, skips this one and rejects its promises -
|
|
371
|
+
// InvalidStateError or AbortError by engine. The swap still runs, so these are expected.
|
|
372
|
+
for (const p of [vt.ready, vt.finished, vt.updateCallbackDone]) p.catch(() => {})
|
|
373
|
+
})
|
|
374
|
+
})
|
|
375
|
+
// A navigation that loads takes the page from a partially prerendered first document, which then
|
|
376
|
+
// stops filling its holes and cancels the ones still loading on the server. `navigate` publishes
|
|
377
|
+
// its `pendingPath` before its first await; a client-only search change, or a boundary already
|
|
378
|
+
// showing its target, loads nothing, so the holes still pending keep filling.
|
|
379
|
+
const navigateFrom = (routePath: string, from: string): Promise<void> => {
|
|
380
|
+
const run = router.navigate(routePath).catch((error) => leave(error, from))
|
|
381
|
+
const ppr: unknown = Reflect.get(globalThis, "__nifraPpr")
|
|
382
|
+
if (
|
|
383
|
+
router.snapshot().pendingPath === routePath &&
|
|
384
|
+
typeof ppr === "function" &&
|
|
385
|
+
"leave" in ppr &&
|
|
386
|
+
typeof ppr.leave === "function"
|
|
387
|
+
)
|
|
388
|
+
ppr.leave()
|
|
389
|
+
return run
|
|
307
390
|
}
|
|
308
391
|
|
|
309
392
|
// Parse a path into the `{ pathname, search, hash }` a `BlockerFunction` decides on.
|
|
@@ -364,6 +447,7 @@ export function installHistory(
|
|
|
364
447
|
// whole object on back/forward for free.
|
|
365
448
|
const entryState =
|
|
366
449
|
state !== undefined ? { nifraIndex: index, nifraState: state } : { nifraIndex: index }
|
|
450
|
+
writingEntry(mode)
|
|
367
451
|
if (mode === "replace") {
|
|
368
452
|
// Replace the current entry (same index) with the new URL. The entry we're leaving is discarded, so
|
|
369
453
|
// its scroll isn't worth saving; the new route scrolls to its fragment target, else the top.
|
|
@@ -381,7 +465,7 @@ export function installHistory(
|
|
|
381
465
|
here = url.pathname + url.search + url.hash
|
|
382
466
|
expected = routePath
|
|
383
467
|
// The data layer fetches by path+search; the #hash is client-only (never sent to the server).
|
|
384
|
-
|
|
468
|
+
void navigateFrom(routePath, path)
|
|
385
469
|
settle()
|
|
386
470
|
}
|
|
387
471
|
|
|
@@ -417,7 +501,15 @@ export function installHistory(
|
|
|
417
501
|
}
|
|
418
502
|
go(to, navOptions?.replace === true ? "replace" : "push", navOptions?.state)
|
|
419
503
|
}
|
|
420
|
-
|
|
504
|
+
// Back through the entries the boundary added to the page underneath; without that entry, replace
|
|
505
|
+
// the boundary's URL with the page's.
|
|
506
|
+
const closeBoundary = (name: string): void => {
|
|
507
|
+
const intercepted = router.snapshot().intercepted
|
|
508
|
+
if (intercepted === undefined || intercepted.boundary !== name) return
|
|
509
|
+
if (pageEntry !== undefined && pageEntry < index) history.go(pageEntry - index)
|
|
510
|
+
else go(intercepted.pagePath, "replace")
|
|
511
|
+
}
|
|
512
|
+
setBrowserNavigate(navigate, router, closeBoundary)
|
|
421
513
|
|
|
422
514
|
// Publish the guard registry through the DOM-free bridge (`@nifrajs/web`'s `registerBlocker`, which an
|
|
423
515
|
// adapter's `useBlocker` calls). One slot, latest registration wins; the unregister clears it only if
|
|
@@ -515,10 +607,12 @@ export function installHistory(
|
|
|
515
607
|
reversing = false
|
|
516
608
|
return
|
|
517
609
|
}
|
|
610
|
+
adoptNativeEntry()
|
|
518
611
|
const newIndex = (history.state as { nifraIndex?: number } | null)?.nifraIndex ?? 0
|
|
519
612
|
const dest = location.pathname + location.search + (location.hash ?? "")
|
|
520
613
|
const delta = newIndex - index
|
|
521
|
-
|
|
614
|
+
// A fragment entry the browser just pushed is not a move between entries to reverse.
|
|
615
|
+
if (delta !== 0 && guard(dest, () => history.go(delta))) {
|
|
522
616
|
reversing = true
|
|
523
617
|
history.go(-delta)
|
|
524
618
|
return
|
|
@@ -528,7 +622,7 @@ export function installHistory(
|
|
|
528
622
|
pendingScroll = { pos: scrollOf(history.state) }
|
|
529
623
|
const routePath = location.pathname + location.search
|
|
530
624
|
expected = routePath
|
|
531
|
-
|
|
625
|
+
void navigateFrom(routePath, dest)
|
|
532
626
|
settle()
|
|
533
627
|
}
|
|
534
628
|
|
|
@@ -555,6 +649,7 @@ export function installHistory(
|
|
|
555
649
|
// form post back to the page it was on adds nothing.
|
|
556
650
|
const follow = (path: string): void => {
|
|
557
651
|
if (path !== location.pathname + location.search) {
|
|
652
|
+
writingEntry(expected === undefined ? "push" : "replace")
|
|
558
653
|
if (expected === undefined) {
|
|
559
654
|
history.replaceState({ ...(history.state ?? {}), nifraScroll: [scrollX, scrollY] }, "")
|
|
560
655
|
index += 1
|
|
@@ -573,13 +668,15 @@ export function installHistory(
|
|
|
573
668
|
// A settle with no navigation (a submit's revalidation) scrolls nothing. Either way the rendered
|
|
574
669
|
// page is then scanned for links to warm.
|
|
575
670
|
const settled = (): void => {
|
|
576
|
-
const { pending, pendingPath } = router.snapshot()
|
|
671
|
+
const { pending, pendingPath, intercepted } = router.snapshot()
|
|
577
672
|
if (pending) {
|
|
578
673
|
if (pendingPath !== undefined && pendingPath !== expected) follow(pendingPath)
|
|
579
674
|
return
|
|
580
675
|
}
|
|
581
676
|
expected = undefined
|
|
582
|
-
|
|
677
|
+
if (intercepted === undefined) pageEntry = index
|
|
678
|
+
// A boundary opened over the page leaves the page where it was scrolled.
|
|
679
|
+
const target = intercepted === undefined ? pendingScroll : null
|
|
583
680
|
pendingScroll = null
|
|
584
681
|
requestAnimationFrame(() => {
|
|
585
682
|
if (target !== null && "hash" in target) {
|
|
@@ -599,16 +696,20 @@ export function installHistory(
|
|
|
599
696
|
document.addEventListener("pointerover", onPrefetch)
|
|
600
697
|
document.addEventListener("focusin", onPrefetch)
|
|
601
698
|
window.addEventListener("popstate", onPopState)
|
|
699
|
+
// Where a fragment link fires no popstate, its entry is still numbered here.
|
|
700
|
+
window.addEventListener("hashchange", adoptNativeEntry)
|
|
602
701
|
window.addEventListener("beforeunload", onBeforeUnload)
|
|
603
702
|
return () => {
|
|
604
703
|
unsubscribe()
|
|
605
704
|
inView?.disconnect()
|
|
606
705
|
setBrowserNavigate(undefined) // stop routing `useNavigate` to a torn-down router
|
|
607
706
|
setBlockerController(undefined) // stop routing `useBlocker` to a torn-down registry
|
|
707
|
+
setPresenter(undefined)
|
|
608
708
|
document.removeEventListener("click", onClick)
|
|
609
709
|
document.removeEventListener("pointerover", onPrefetch)
|
|
610
710
|
document.removeEventListener("focusin", onPrefetch)
|
|
611
711
|
window.removeEventListener("popstate", onPopState)
|
|
712
|
+
window.removeEventListener("hashchange", adoptNativeEntry)
|
|
612
713
|
window.removeEventListener("beforeunload", onBeforeUnload)
|
|
613
714
|
}
|
|
614
715
|
}
|
|
@@ -693,6 +794,47 @@ export function applyHead(head: Meta, path?: string): void {
|
|
|
693
794
|
}
|
|
694
795
|
}
|
|
695
796
|
|
|
797
|
+
/**
|
|
798
|
+
* The route path a form with no action posts to while an intercept boundary holds a target. The
|
|
799
|
+
* document URL is then the target's, but the form belongs to whichever route rendered it: the target
|
|
800
|
+
* when it sits inside the boundary's `data-nifra-boundary="<name>"` element, otherwise the page
|
|
801
|
+
* underneath. `undefined` when the form names its action, or no boundary holds a target.
|
|
802
|
+
*/
|
|
803
|
+
function implicitActionOwner(
|
|
804
|
+
router: ClientRouter,
|
|
805
|
+
form: HTMLFormElement,
|
|
806
|
+
actionFrom: Element,
|
|
807
|
+
): string | undefined {
|
|
808
|
+
const { intercepted, path } = router.snapshot()
|
|
809
|
+
if (intercepted === undefined) return undefined
|
|
810
|
+
const named = Element.prototype.getAttribute.call(actionFrom, actionAttribute(actionFrom, form))
|
|
811
|
+
if (named !== null && named.trim() !== "") return undefined
|
|
812
|
+
// Any ancestor marking the open boundary owns it, so a marker nested inside the modal (another
|
|
813
|
+
// boundary's) does not hand the form to the page. Boundary names are validated identifiers; the
|
|
814
|
+
// check keeps the selector literal regardless.
|
|
815
|
+
const inside =
|
|
816
|
+
BOUNDARY_NAME.test(intercepted.boundary) &&
|
|
817
|
+
Element.prototype.closest.call(form, `[data-nifra-boundary="${intercepted.boundary}"]`) !== null
|
|
818
|
+
return inside ? path : intercepted.pagePath
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const BOUNDARY_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/
|
|
822
|
+
|
|
823
|
+
const actionAttribute = (actionFrom: Element, form: HTMLFormElement): string =>
|
|
824
|
+
actionFrom === form ? "action" : "formaction"
|
|
825
|
+
|
|
826
|
+
// Point the implicit action at `href` for one native submission; the returned function puts the
|
|
827
|
+
// attribute back, so a form that outlives the boundary (one in a layout) keeps posting to its page.
|
|
828
|
+
function pointAction(actionFrom: Element, form: HTMLFormElement, href: string): () => void {
|
|
829
|
+
const name = actionAttribute(actionFrom, form)
|
|
830
|
+
const previous = Element.prototype.getAttribute.call(actionFrom, name)
|
|
831
|
+
Element.prototype.setAttribute.call(actionFrom, name, href)
|
|
832
|
+
return () => {
|
|
833
|
+
if (previous === null) Element.prototype.removeAttribute.call(actionFrom, name)
|
|
834
|
+
else Element.prototype.setAttribute.call(actionFrom, name, previous)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
696
838
|
// A control named like a form property shadows it (`<input name="target">` makes `form.target` that
|
|
697
839
|
// input), so the form's own state is read through the prototype accessors.
|
|
698
840
|
function formProperty(form: HTMLFormElement, name: "action" | "method" | "target"): string {
|
|
@@ -720,14 +862,26 @@ export function installForms(router: ClientRouter): () => void {
|
|
|
720
862
|
const method = submitter?.hasAttribute("formmethod")
|
|
721
863
|
? submitter.formMethod
|
|
722
864
|
: formProperty(form, "method")
|
|
723
|
-
|
|
865
|
+
const actionFrom = submitter?.hasAttribute("formaction") ? submitter : form
|
|
866
|
+
const owner = implicitActionOwner(router, form, actionFrom)
|
|
867
|
+
if (method.toLowerCase() !== "post") {
|
|
868
|
+
// GET forms submit natively. The browser reads the action after this event, so one the page
|
|
869
|
+
// rendered under a boundary is pointed at the page for this submission, then restored.
|
|
870
|
+
if (owner !== undefined)
|
|
871
|
+
setTimeout(pointAction(actionFrom, form, new URL(appUrl(owner), location.href).href), 0)
|
|
872
|
+
return
|
|
873
|
+
}
|
|
724
874
|
const target = submitter?.hasAttribute("formtarget")
|
|
725
875
|
? submitter.formTarget
|
|
726
876
|
: formProperty(form, "target")
|
|
727
877
|
if (target !== "" && target !== "_self") return // it loads in another browsing context
|
|
728
878
|
// Resolved (or the current document URL if unset).
|
|
729
879
|
const url = sameDocumentUrl(
|
|
730
|
-
|
|
880
|
+
owner !== undefined
|
|
881
|
+
? appUrl(owner)
|
|
882
|
+
: actionFrom === submitter
|
|
883
|
+
? submitter.formAction
|
|
884
|
+
: formProperty(form, "action"),
|
|
731
885
|
)
|
|
732
886
|
if (url === null) return
|
|
733
887
|
if (router.match(url.pathname) === null) return // not an app route → native submit
|
|
@@ -742,9 +896,15 @@ export function installForms(router: ClientRouter): () => void {
|
|
|
742
896
|
// The server answered: load the page that shows the result rather than run the action again.
|
|
743
897
|
if (to !== undefined) location.assign(to)
|
|
744
898
|
else {
|
|
745
|
-
// Nothing reached the server - post natively, through the same button.
|
|
899
|
+
// Nothing reached the server - post natively, through the same button, to the same route.
|
|
746
900
|
native.add(form)
|
|
747
|
-
|
|
901
|
+
// requestSubmit reads the action synchronously, so the attribute is restored right after.
|
|
902
|
+
const restore = owner === undefined ? undefined : pointAction(actionFrom, form, url.href)
|
|
903
|
+
try {
|
|
904
|
+
HTMLFormElement.prototype.requestSubmit.call(form, submitter)
|
|
905
|
+
} finally {
|
|
906
|
+
restore?.()
|
|
907
|
+
}
|
|
748
908
|
}
|
|
749
909
|
})
|
|
750
910
|
}
|
package/src/csp.ts
CHANGED
|
@@ -109,8 +109,9 @@ const inlineScriptBodies = (html: string): string[] => {
|
|
|
109
109
|
|
|
110
110
|
const ENCODER = new TextEncoder()
|
|
111
111
|
|
|
112
|
-
/** A CSP hash source for one inline script body: `'sha256-<base64 of the UTF-8 bytes>'`.
|
|
113
|
-
|
|
112
|
+
/** A CSP hash source for one inline script body: `'sha256-<base64 of the UTF-8 bytes>'`. Shared with
|
|
113
|
+
* the PPR runtime; `index.ts` does not re-export it. */
|
|
114
|
+
export async function hashSource(body: string): Promise<string> {
|
|
114
115
|
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", ENCODER.encode(body)))
|
|
115
116
|
let binary = ""
|
|
116
117
|
for (const byte of digest) binary += String.fromCharCode(byte)
|
|
@@ -158,6 +159,19 @@ const preparedSources = (policy: CspPolicy, adapter: RenderAdapter): AdapterSour
|
|
|
158
159
|
return known
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
/**
|
|
163
|
+
* The policy value for a partially prerendered document: the adapter's hash sources plus `extra`
|
|
164
|
+
* (the PPR runtime and invoker hashes), with no nonce. Prepare the policy for `adapter` first.
|
|
165
|
+
*/
|
|
166
|
+
export function cspHeaderWithSources(
|
|
167
|
+
policy: CspPolicy,
|
|
168
|
+
adapter: RenderAdapter,
|
|
169
|
+
extra: string,
|
|
170
|
+
): string | undefined {
|
|
171
|
+
const { sources } = preparedSources(policy, adapter)
|
|
172
|
+
return policy[CSP_POLICY].header({ sources: sources === "" ? extra : `${sources} ${extra}` })
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
/**
|
|
162
176
|
* The nonce one document carries under `policy`: `undefined` unless the document needs one -
|
|
163
177
|
* `needsNonce` (a deferred value or a head tag naming the nonce), or an adapter whose hydration head
|
package/src/deferred.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { plainJsonCodec, type TransportCodec } from "@nifrajs/core/transport-codec"
|
|
9
9
|
import { isDeferredFree } from "./internal/deferred-free.ts"
|
|
10
|
+
import { reportDeferredRejection } from "./internal/request-abort.ts"
|
|
10
11
|
import { parsePageData } from "./internal/revive-dates.ts"
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -171,15 +172,19 @@ const NDJSON_ENCODER = new TextEncoder()
|
|
|
171
172
|
* `{__nifra_deferred: id}` placeholders (`forClient`), then one line per deferred as its promise
|
|
172
173
|
* settles - `{"i": id, "v": value}` on resolve, `{"i": id, "e": "deferred_error"}` on reject (a
|
|
173
174
|
* rejection is data, not a stream error; the opaque code never leaks the raw reason). Closes when all
|
|
174
|
-
* settle. The client (`
|
|
175
|
+
* settle. The client (`fetchRouteData`) returns the
|
|
175
176
|
* data after line 1 and settles `<Await>`'s markers as the resolution lines arrive. `JSON.stringify`
|
|
176
177
|
* escapes newlines in values, so each line is NDJSON-safe; no HTML escaping (this is a fetch body,
|
|
177
|
-
* parsed with `JSON.parse`, never injected into markup).
|
|
178
|
+
* parsed with `JSON.parse`, never injected into markup). A rejection is logged to `logError`, the
|
|
179
|
+
* app's error log (the console without one), unless the request `signal` aborting caused it: that is
|
|
180
|
+
* the client leaving, not a failure.
|
|
178
181
|
*/
|
|
179
182
|
export function ndjsonStream(
|
|
180
183
|
forClient: unknown,
|
|
181
184
|
deferred: ReadonlyArray<{ readonly id: number; readonly promise: Promise<unknown> }>,
|
|
182
185
|
codec: TransportCodec = plainJsonCodec,
|
|
186
|
+
signal?: AbortSignal,
|
|
187
|
+
logError?: (error: unknown) => void,
|
|
183
188
|
): ReadableStream<Uint8Array> {
|
|
184
189
|
// A client that goes away cancels the body; a value settling after that has nowhere to go.
|
|
185
190
|
let cancelled = false
|
|
@@ -194,7 +199,7 @@ export function ndjsonStream(
|
|
|
194
199
|
} catch (err) {
|
|
195
200
|
// Redact: stream a stable opaque code, never the raw error text; log the real reason
|
|
196
201
|
// server-side. The client's <Await> errorFallback receives this code.
|
|
197
|
-
|
|
202
|
+
reportDeferredRejection(signal, err, logError)
|
|
198
203
|
line = codec.encode({ i: d.id, e: DEFERRED_ERROR_CODE })
|
|
199
204
|
}
|
|
200
205
|
if (!cancelled) controller.enqueue(NDJSON_ENCODER.encode(`${line}\n`))
|
|
@@ -319,7 +324,7 @@ function markersFromPlaceholders(data: unknown, pending: Pending): unknown {
|
|
|
319
324
|
}
|
|
320
325
|
|
|
321
326
|
/**
|
|
322
|
-
* Client side of {@link ndjsonStream} (used by the router's `
|
|
327
|
+
* Client side of {@link ndjsonStream} (used by the router's `fetchRouteData` on a soft nav): read
|
|
323
328
|
* the NDJSON body into a data object whose deferred markers settle as resolution lines arrive.
|
|
324
329
|
* Returns after **line 1** so the router can apply the critical data + render immediately; the
|
|
325
330
|
* markers' promises settle/reject in the background. If the stream ends (or `signal` aborts) with
|
|
@@ -335,7 +340,8 @@ export async function parseNdjsonData(
|
|
|
335
340
|
let buffer = ""
|
|
336
341
|
// `{ once: true }` + explicit removal in the drain's `finally` so a reused long-lived signal doesn't
|
|
337
342
|
// accumulate listeners across navigations (a per-nav signal would be GC'd, but don't rely on it).
|
|
338
|
-
|
|
343
|
+
// A body the abort has already errored (WebKit errors it first) rejects the cancel.
|
|
344
|
+
const onAbort = (): void => void reader.cancel().catch(() => {})
|
|
339
345
|
if (signal) signal.addEventListener("abort", onAbort, { once: true })
|
|
340
346
|
let ended = false
|
|
341
347
|
// Every way out, a malformed line included, detaches from the signal and the body: a source left
|
package/src/dev-feed.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
buildDiagnostic,
|
|
37
37
|
type Diagnostic,
|
|
38
38
|
type DiagnosticFrame,
|
|
39
|
+
isConsoleDiagnostic,
|
|
39
40
|
} from "./diagnostic.ts"
|
|
40
41
|
import { createRedactor } from "./internal/secret-scan.ts"
|
|
41
42
|
import { originalPosition, ssrSourceMaps } from "./internal/source-map.ts"
|
|
@@ -1140,8 +1141,16 @@ const ssrRemap = (
|
|
|
1140
1141
|
export function captureInto(feed: DevFeed): () => void {
|
|
1141
1142
|
return installCapture({
|
|
1142
1143
|
feedId: feed.id,
|
|
1143
|
-
line: (level, message, request) =>
|
|
1144
|
-
feed.recordLog(level, message, { requestId: request?.requestId })
|
|
1144
|
+
line: (level, message, request) => {
|
|
1145
|
+
feed.recordLog(level, message, { requestId: request?.requestId })
|
|
1146
|
+
// A diagnostic the server only logs (no throw reaches the feed) still becomes an entry.
|
|
1147
|
+
if ((level === "warn" || level === "error") && isConsoleDiagnostic(message)) {
|
|
1148
|
+
const warning = new Error(message)
|
|
1149
|
+
warning.name = "Warning"
|
|
1150
|
+
warning.stack = `Warning: ${message}`
|
|
1151
|
+
feed.recordError(warning, { category: "ssr", requestId: request?.requestId })
|
|
1152
|
+
}
|
|
1153
|
+
},
|
|
1145
1154
|
structured: (entry, request) => {
|
|
1146
1155
|
const level = isDevLogLevel(entry.level) ? entry.level : "log"
|
|
1147
1156
|
feed.recordLog(level, structuredText(entry), { requestId: request?.requestId })
|
package/src/dev-session.ts
CHANGED
|
@@ -44,7 +44,13 @@ import {
|
|
|
44
44
|
} from "./dev-feed.ts"
|
|
45
45
|
import { DEV_INDICATOR_MAX_ISSUES, DEV_INDICATOR_SOURCE, type DevIssue } from "./dev-indicator.ts"
|
|
46
46
|
import { createSourceMapper } from "./dev-sourcemap.ts"
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
fixPrompts,
|
|
49
|
+
isConsoleDiagnostic,
|
|
50
|
+
isHydrationMismatch,
|
|
51
|
+
LAST_ERROR_PATH,
|
|
52
|
+
promptPath,
|
|
53
|
+
} from "./diagnostic.ts"
|
|
48
54
|
import { diagnosticHeadline } from "./diagnostic-prompt.ts"
|
|
49
55
|
import { timingSafeEqual } from "./internal/timing-safe-equal.ts"
|
|
50
56
|
import { ISR_STATUS_HEADER } from "./isr.ts"
|
|
@@ -439,16 +445,21 @@ export function createDevSession(options: DevSessionOptions): DevSession {
|
|
|
439
445
|
if (event.kind === "console") {
|
|
440
446
|
feed.recordLog(event.level, event.message, meta)
|
|
441
447
|
if (event.error !== undefined) return (await recordBrowserError(event.error, meta)).entry
|
|
442
|
-
// React 18, Vue and Svelte report a mismatch through the console rather than by throwing
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
isHydrationMismatch(event.message)
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
)
|
|
451
|
-
|
|
448
|
+
// React 18, Vue and Svelte report a mismatch through the console rather than by throwing, as
|
|
449
|
+
// nifra's own browser diagnostics do.
|
|
450
|
+
if (event.level === "error" || event.level === "warn") {
|
|
451
|
+
const name = isHydrationMismatch(event.message)
|
|
452
|
+
? "HydrationMismatch"
|
|
453
|
+
: isConsoleDiagnostic(event.message)
|
|
454
|
+
? "Warning"
|
|
455
|
+
: undefined
|
|
456
|
+
if (name !== undefined) {
|
|
457
|
+
const recorded = await recordBrowserError(
|
|
458
|
+
{ name, message: event.message, stack: "" },
|
|
459
|
+
meta,
|
|
460
|
+
)
|
|
461
|
+
return recorded.entry
|
|
462
|
+
}
|
|
452
463
|
}
|
|
453
464
|
return undefined
|
|
454
465
|
}
|