@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/client.ts
CHANGED
|
@@ -35,6 +35,7 @@ export function currentDocumentNonce(): string | undefined {
|
|
|
35
35
|
return script.getAttribute("nonce") ?? undefined
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export { identityChanged } from "./identity.ts"
|
|
38
39
|
/**
|
|
39
40
|
* Everything the generated client entry needs, re-exported here so that entry imports ONE module and
|
|
40
41
|
* that module is DOM-only. It used to take these from `@nifrajs/web`, whose graph includes `renderPage`
|
|
@@ -44,6 +45,7 @@ export function currentDocumentNonce(): string | undefined {
|
|
|
44
45
|
*/
|
|
45
46
|
export { mergeHeads, resolveChainMeta, resolveMeta } from "./internal/head-merge.ts"
|
|
46
47
|
export {
|
|
48
|
+
closeBoundary,
|
|
47
49
|
getBrowserNavigate,
|
|
48
50
|
IDLE_BLOCKER,
|
|
49
51
|
registerBlocker,
|
|
@@ -255,6 +257,48 @@ export function installHistory(
|
|
|
255
257
|
let reversing = false
|
|
256
258
|
// The path the router was last seen loading, so a redirect it follows shows up as a change.
|
|
257
259
|
let expected: string | undefined
|
|
260
|
+
// The entry holding the page last shown without an intercept boundary, so closing a boundary opened
|
|
261
|
+
// over it can go back there. Cleared once that entry is replaced or cut off by a push.
|
|
262
|
+
let pageEntry: number | undefined = index
|
|
263
|
+
// Before writing the entry at `index` (replace) or pushing past it, which drops every later entry.
|
|
264
|
+
const writingEntry = (mode: "push" | "replace"): void => {
|
|
265
|
+
if (mode === "replace" ? pageEntry === index : pageEntry !== undefined && pageEntry > index)
|
|
266
|
+
pageEntry = undefined
|
|
267
|
+
}
|
|
268
|
+
// Every entry this document owns carries its index, the first one included, so an entry without one
|
|
269
|
+
// is one the browser pushed itself: a same-page fragment link, which this router leaves native.
|
|
270
|
+
// Number it as the push it was, or index deltas (a guard's reversal, closeBoundary) count wrong.
|
|
271
|
+
const numbered = (): boolean => {
|
|
272
|
+
const current: unknown = history.state
|
|
273
|
+
return (
|
|
274
|
+
typeof current === "object" &&
|
|
275
|
+
current !== null &&
|
|
276
|
+
"nifraIndex" in current &&
|
|
277
|
+
typeof current.nifraIndex === "number"
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
const stampEntry = (): void => {
|
|
281
|
+
const current: unknown = history.state
|
|
282
|
+
history.replaceState(
|
|
283
|
+
Object.assign({}, typeof current === "object" ? current : null, { nifraIndex: index }),
|
|
284
|
+
"",
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
const adoptNativeEntry = (): boolean => {
|
|
288
|
+
if (numbered()) return false
|
|
289
|
+
writingEntry("push")
|
|
290
|
+
index += 1
|
|
291
|
+
stampEntry()
|
|
292
|
+
here = location.pathname + location.search + (location.hash ?? "")
|
|
293
|
+
return true
|
|
294
|
+
}
|
|
295
|
+
// An opaque or file: document routes nothing, so its history is left alone. A refused write (a
|
|
296
|
+
// browser's rate limit) only leaves fragment entries uncounted, as before.
|
|
297
|
+
if (location.origin !== "null" && location.protocol !== "file:" && !numbered()) {
|
|
298
|
+
try {
|
|
299
|
+
stampEntry()
|
|
300
|
+
} catch {}
|
|
301
|
+
}
|
|
258
302
|
type Registration = {
|
|
259
303
|
readonly shouldBlock: BlockerFunction
|
|
260
304
|
readonly emit: (blocker: Blocker) => void
|
|
@@ -292,6 +336,23 @@ export function installHistory(
|
|
|
292
336
|
type ViewTransitionDocument = Document & {
|
|
293
337
|
startViewTransition?: (cb: () => unknown) => ViewTransition
|
|
294
338
|
}
|
|
339
|
+
// A navigation that loads takes the page from a partially prerendered first document, which then
|
|
340
|
+
// stops filling its holes and cancels the ones still loading on the server. `navigate` publishes
|
|
341
|
+
// its `pendingPath` before its first await; a client-only search change, or a boundary already
|
|
342
|
+
// showing its target, loads nothing, so the holes still pending keep filling.
|
|
343
|
+
const navigateFrom = (routePath: string, from: string): Promise<void> => {
|
|
344
|
+
const run = router.navigate(routePath).catch((error) => leave(error, from))
|
|
345
|
+
const ppr: unknown = Reflect.get(globalThis, "__nifraPpr")
|
|
346
|
+
if (
|
|
347
|
+
router.snapshot().pendingPath === routePath &&
|
|
348
|
+
typeof ppr === "function" &&
|
|
349
|
+
"leave" in ppr &&
|
|
350
|
+
typeof ppr.leave === "function"
|
|
351
|
+
)
|
|
352
|
+
ppr.leave()
|
|
353
|
+
return run
|
|
354
|
+
}
|
|
355
|
+
|
|
295
356
|
const transition = (run: () => Promise<void>): void => {
|
|
296
357
|
const doc = document as ViewTransitionDocument
|
|
297
358
|
if (typeof doc.startViewTransition !== "function") {
|
|
@@ -364,6 +425,7 @@ export function installHistory(
|
|
|
364
425
|
// whole object on back/forward for free.
|
|
365
426
|
const entryState =
|
|
366
427
|
state !== undefined ? { nifraIndex: index, nifraState: state } : { nifraIndex: index }
|
|
428
|
+
writingEntry(mode)
|
|
367
429
|
if (mode === "replace") {
|
|
368
430
|
// Replace the current entry (same index) with the new URL. The entry we're leaving is discarded, so
|
|
369
431
|
// its scroll isn't worth saving; the new route scrolls to its fragment target, else the top.
|
|
@@ -381,7 +443,7 @@ export function installHistory(
|
|
|
381
443
|
here = url.pathname + url.search + url.hash
|
|
382
444
|
expected = routePath
|
|
383
445
|
// The data layer fetches by path+search; the #hash is client-only (never sent to the server).
|
|
384
|
-
transition(() =>
|
|
446
|
+
transition(() => navigateFrom(routePath, path))
|
|
385
447
|
settle()
|
|
386
448
|
}
|
|
387
449
|
|
|
@@ -417,7 +479,15 @@ export function installHistory(
|
|
|
417
479
|
}
|
|
418
480
|
go(to, navOptions?.replace === true ? "replace" : "push", navOptions?.state)
|
|
419
481
|
}
|
|
420
|
-
|
|
482
|
+
// Back through the entries the boundary added to the page underneath; without that entry, replace
|
|
483
|
+
// the boundary's URL with the page's.
|
|
484
|
+
const closeBoundary = (name: string): void => {
|
|
485
|
+
const intercepted = router.snapshot().intercepted
|
|
486
|
+
if (intercepted === undefined || intercepted.boundary !== name) return
|
|
487
|
+
if (pageEntry !== undefined && pageEntry < index) history.go(pageEntry - index)
|
|
488
|
+
else go(intercepted.pagePath, "replace")
|
|
489
|
+
}
|
|
490
|
+
setBrowserNavigate(navigate, router, closeBoundary)
|
|
421
491
|
|
|
422
492
|
// Publish the guard registry through the DOM-free bridge (`@nifrajs/web`'s `registerBlocker`, which an
|
|
423
493
|
// adapter's `useBlocker` calls). One slot, latest registration wins; the unregister clears it only if
|
|
@@ -515,10 +585,12 @@ export function installHistory(
|
|
|
515
585
|
reversing = false
|
|
516
586
|
return
|
|
517
587
|
}
|
|
588
|
+
adoptNativeEntry()
|
|
518
589
|
const newIndex = (history.state as { nifraIndex?: number } | null)?.nifraIndex ?? 0
|
|
519
590
|
const dest = location.pathname + location.search + (location.hash ?? "")
|
|
520
591
|
const delta = newIndex - index
|
|
521
|
-
|
|
592
|
+
// A fragment entry the browser just pushed is not a move between entries to reverse.
|
|
593
|
+
if (delta !== 0 && guard(dest, () => history.go(delta))) {
|
|
522
594
|
reversing = true
|
|
523
595
|
history.go(-delta)
|
|
524
596
|
return
|
|
@@ -528,7 +600,7 @@ export function installHistory(
|
|
|
528
600
|
pendingScroll = { pos: scrollOf(history.state) }
|
|
529
601
|
const routePath = location.pathname + location.search
|
|
530
602
|
expected = routePath
|
|
531
|
-
transition(() =>
|
|
603
|
+
transition(() => navigateFrom(routePath, dest))
|
|
532
604
|
settle()
|
|
533
605
|
}
|
|
534
606
|
|
|
@@ -555,6 +627,7 @@ export function installHistory(
|
|
|
555
627
|
// form post back to the page it was on adds nothing.
|
|
556
628
|
const follow = (path: string): void => {
|
|
557
629
|
if (path !== location.pathname + location.search) {
|
|
630
|
+
writingEntry(expected === undefined ? "push" : "replace")
|
|
558
631
|
if (expected === undefined) {
|
|
559
632
|
history.replaceState({ ...(history.state ?? {}), nifraScroll: [scrollX, scrollY] }, "")
|
|
560
633
|
index += 1
|
|
@@ -573,13 +646,15 @@ export function installHistory(
|
|
|
573
646
|
// A settle with no navigation (a submit's revalidation) scrolls nothing. Either way the rendered
|
|
574
647
|
// page is then scanned for links to warm.
|
|
575
648
|
const settled = (): void => {
|
|
576
|
-
const { pending, pendingPath } = router.snapshot()
|
|
649
|
+
const { pending, pendingPath, intercepted } = router.snapshot()
|
|
577
650
|
if (pending) {
|
|
578
651
|
if (pendingPath !== undefined && pendingPath !== expected) follow(pendingPath)
|
|
579
652
|
return
|
|
580
653
|
}
|
|
581
654
|
expected = undefined
|
|
582
|
-
|
|
655
|
+
if (intercepted === undefined) pageEntry = index
|
|
656
|
+
// A boundary opened over the page leaves the page where it was scrolled.
|
|
657
|
+
const target = intercepted === undefined ? pendingScroll : null
|
|
583
658
|
pendingScroll = null
|
|
584
659
|
requestAnimationFrame(() => {
|
|
585
660
|
if (target !== null && "hash" in target) {
|
|
@@ -599,6 +674,8 @@ export function installHistory(
|
|
|
599
674
|
document.addEventListener("pointerover", onPrefetch)
|
|
600
675
|
document.addEventListener("focusin", onPrefetch)
|
|
601
676
|
window.addEventListener("popstate", onPopState)
|
|
677
|
+
// Where a fragment link fires no popstate, its entry is still numbered here.
|
|
678
|
+
window.addEventListener("hashchange", adoptNativeEntry)
|
|
602
679
|
window.addEventListener("beforeunload", onBeforeUnload)
|
|
603
680
|
return () => {
|
|
604
681
|
unsubscribe()
|
|
@@ -609,6 +686,7 @@ export function installHistory(
|
|
|
609
686
|
document.removeEventListener("pointerover", onPrefetch)
|
|
610
687
|
document.removeEventListener("focusin", onPrefetch)
|
|
611
688
|
window.removeEventListener("popstate", onPopState)
|
|
689
|
+
window.removeEventListener("hashchange", adoptNativeEntry)
|
|
612
690
|
window.removeEventListener("beforeunload", onBeforeUnload)
|
|
613
691
|
}
|
|
614
692
|
}
|
|
@@ -693,6 +771,47 @@ export function applyHead(head: Meta, path?: string): void {
|
|
|
693
771
|
}
|
|
694
772
|
}
|
|
695
773
|
|
|
774
|
+
/**
|
|
775
|
+
* The route path a form with no action posts to while an intercept boundary holds a target. The
|
|
776
|
+
* document URL is then the target's, but the form belongs to whichever route rendered it: the target
|
|
777
|
+
* when it sits inside the boundary's `data-nifra-boundary="<name>"` element, otherwise the page
|
|
778
|
+
* underneath. `undefined` when the form names its action, or no boundary holds a target.
|
|
779
|
+
*/
|
|
780
|
+
function implicitActionOwner(
|
|
781
|
+
router: ClientRouter,
|
|
782
|
+
form: HTMLFormElement,
|
|
783
|
+
actionFrom: Element,
|
|
784
|
+
): string | undefined {
|
|
785
|
+
const { intercepted, path } = router.snapshot()
|
|
786
|
+
if (intercepted === undefined) return undefined
|
|
787
|
+
const named = Element.prototype.getAttribute.call(actionFrom, actionAttribute(actionFrom, form))
|
|
788
|
+
if (named !== null && named.trim() !== "") return undefined
|
|
789
|
+
// Any ancestor marking the open boundary owns it, so a marker nested inside the modal (another
|
|
790
|
+
// boundary's) does not hand the form to the page. Boundary names are validated identifiers; the
|
|
791
|
+
// check keeps the selector literal regardless.
|
|
792
|
+
const inside =
|
|
793
|
+
BOUNDARY_NAME.test(intercepted.boundary) &&
|
|
794
|
+
Element.prototype.closest.call(form, `[data-nifra-boundary="${intercepted.boundary}"]`) !== null
|
|
795
|
+
return inside ? path : intercepted.pagePath
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const BOUNDARY_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/
|
|
799
|
+
|
|
800
|
+
const actionAttribute = (actionFrom: Element, form: HTMLFormElement): string =>
|
|
801
|
+
actionFrom === form ? "action" : "formaction"
|
|
802
|
+
|
|
803
|
+
// Point the implicit action at `href` for one native submission; the returned function puts the
|
|
804
|
+
// attribute back, so a form that outlives the boundary (one in a layout) keeps posting to its page.
|
|
805
|
+
function pointAction(actionFrom: Element, form: HTMLFormElement, href: string): () => void {
|
|
806
|
+
const name = actionAttribute(actionFrom, form)
|
|
807
|
+
const previous = Element.prototype.getAttribute.call(actionFrom, name)
|
|
808
|
+
Element.prototype.setAttribute.call(actionFrom, name, href)
|
|
809
|
+
return () => {
|
|
810
|
+
if (previous === null) Element.prototype.removeAttribute.call(actionFrom, name)
|
|
811
|
+
else Element.prototype.setAttribute.call(actionFrom, name, previous)
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
696
815
|
// A control named like a form property shadows it (`<input name="target">` makes `form.target` that
|
|
697
816
|
// input), so the form's own state is read through the prototype accessors.
|
|
698
817
|
function formProperty(form: HTMLFormElement, name: "action" | "method" | "target"): string {
|
|
@@ -720,14 +839,26 @@ export function installForms(router: ClientRouter): () => void {
|
|
|
720
839
|
const method = submitter?.hasAttribute("formmethod")
|
|
721
840
|
? submitter.formMethod
|
|
722
841
|
: formProperty(form, "method")
|
|
723
|
-
|
|
842
|
+
const actionFrom = submitter?.hasAttribute("formaction") ? submitter : form
|
|
843
|
+
const owner = implicitActionOwner(router, form, actionFrom)
|
|
844
|
+
if (method.toLowerCase() !== "post") {
|
|
845
|
+
// GET forms submit natively. The browser reads the action after this event, so one the page
|
|
846
|
+
// rendered under a boundary is pointed at the page for this submission, then restored.
|
|
847
|
+
if (owner !== undefined)
|
|
848
|
+
setTimeout(pointAction(actionFrom, form, new URL(appUrl(owner), location.href).href), 0)
|
|
849
|
+
return
|
|
850
|
+
}
|
|
724
851
|
const target = submitter?.hasAttribute("formtarget")
|
|
725
852
|
? submitter.formTarget
|
|
726
853
|
: formProperty(form, "target")
|
|
727
854
|
if (target !== "" && target !== "_self") return // it loads in another browsing context
|
|
728
855
|
// Resolved (or the current document URL if unset).
|
|
729
856
|
const url = sameDocumentUrl(
|
|
730
|
-
|
|
857
|
+
owner !== undefined
|
|
858
|
+
? appUrl(owner)
|
|
859
|
+
: actionFrom === submitter
|
|
860
|
+
? submitter.formAction
|
|
861
|
+
: formProperty(form, "action"),
|
|
731
862
|
)
|
|
732
863
|
if (url === null) return
|
|
733
864
|
if (router.match(url.pathname) === null) return // not an app route → native submit
|
|
@@ -742,9 +873,15 @@ export function installForms(router: ClientRouter): () => void {
|
|
|
742
873
|
// The server answered: load the page that shows the result rather than run the action again.
|
|
743
874
|
if (to !== undefined) location.assign(to)
|
|
744
875
|
else {
|
|
745
|
-
// Nothing reached the server - post natively, through the same button.
|
|
876
|
+
// Nothing reached the server - post natively, through the same button, to the same route.
|
|
746
877
|
native.add(form)
|
|
747
|
-
|
|
878
|
+
// requestSubmit reads the action synchronously, so the attribute is restored right after.
|
|
879
|
+
const restore = owner === undefined ? undefined : pointAction(actionFrom, form, url.href)
|
|
880
|
+
try {
|
|
881
|
+
HTMLFormElement.prototype.requestSubmit.call(form, submitter)
|
|
882
|
+
} finally {
|
|
883
|
+
restore?.()
|
|
884
|
+
}
|
|
748
885
|
}
|
|
749
886
|
})
|
|
750
887
|
}
|
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
|
}
|
package/src/dev.ts
CHANGED
|
@@ -67,8 +67,15 @@ import {
|
|
|
67
67
|
import { createSsrGraph, type SsrGraph } from "./dev-ssr-graph.ts"
|
|
68
68
|
import { formatBuildLog } from "./diagnostic.ts"
|
|
69
69
|
import { discoverRoutes } from "./fs.ts"
|
|
70
|
-
import { DEFAULT_DEV_PORT, generateClientEntry } from "./index.ts"
|
|
70
|
+
import { DEFAULT_DEV_PORT, generateClientEntry, type LiveClientEntry } from "./index.ts"
|
|
71
71
|
import { DEV_ENTRY_FILE } from "./internal/dev-reserved.ts"
|
|
72
|
+
import {
|
|
73
|
+
type BunUpgradeServer,
|
|
74
|
+
bunDevWebSocket,
|
|
75
|
+
isWebSocketUpgrade,
|
|
76
|
+
type UpgradableApp,
|
|
77
|
+
upgradeOnBun,
|
|
78
|
+
} from "./internal/dev-websocket.ts"
|
|
72
79
|
import { DEV_HMR_ENV, DEV_ROOT_ENV, DEV_ROUTES_ENV } from "./plugins/kit.ts"
|
|
73
80
|
import { servePublicDir } from "./public-dir.ts"
|
|
74
81
|
|
|
@@ -76,7 +83,7 @@ export type { DevAppHooks } from "./dev-session.ts"
|
|
|
76
83
|
export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
77
84
|
|
|
78
85
|
/** Minimal app surface the dev server needs - `createWebApp(...)` satisfies it. */
|
|
79
|
-
interface FetchApp {
|
|
86
|
+
interface FetchApp extends UpgradableApp {
|
|
80
87
|
fetch(request: Request): Response | Promise<Response>
|
|
81
88
|
}
|
|
82
89
|
|
|
@@ -169,7 +176,8 @@ interface BunServeOptions {
|
|
|
169
176
|
readonly hostname: "127.0.0.1"
|
|
170
177
|
readonly development: { readonly hmr: boolean }
|
|
171
178
|
readonly routes: Record<string, HtmlBundle>
|
|
172
|
-
fetch(request: Request): Promise<Response>
|
|
179
|
+
fetch(request: Request, server: BunUpgradeServer): Promise<Response | undefined>
|
|
180
|
+
readonly websocket: typeof bunDevWebSocket
|
|
173
181
|
}
|
|
174
182
|
interface BunServerHandle {
|
|
175
183
|
readonly port: number
|
|
@@ -214,6 +222,7 @@ export function devHtml(entryHref: string): string {
|
|
|
214
222
|
interface WriteDevFilesOptions {
|
|
215
223
|
readonly routesDir: string
|
|
216
224
|
readonly clientModule: string
|
|
225
|
+
readonly live?: LiveClientEntry | undefined
|
|
217
226
|
readonly entryPath: string
|
|
218
227
|
readonly htmlPath: string
|
|
219
228
|
}
|
|
@@ -226,7 +235,7 @@ interface WriteDevFilesOptions {
|
|
|
226
235
|
* the way the runtime does, so a leading slash there would mean the filesystem root.
|
|
227
236
|
*/
|
|
228
237
|
export function writeDevFiles(options: WriteDevFilesOptions): void {
|
|
229
|
-
const { routesDir, clientModule, entryPath, htmlPath } = options
|
|
238
|
+
const { routesDir, clientModule, entryPath, htmlPath, live } = options
|
|
230
239
|
mkdirSync(dirname(entryPath), { recursive: true })
|
|
231
240
|
const manifest = discoverRoutes(routesDir)
|
|
232
241
|
const entryDir = dirname(entryPath)
|
|
@@ -244,7 +253,10 @@ export function writeDevFiles(options: WriteDevFilesOptions): void {
|
|
|
244
253
|
// already been passed to createRoot()") and only then does the callback get to reload - trading an
|
|
245
254
|
// accurate warning for a real error. Editing a component-only module never reaches this path: Bun
|
|
246
255
|
// applies React Fast Refresh there and state is preserved.
|
|
247
|
-
writeIfChanged(
|
|
256
|
+
writeIfChanged(
|
|
257
|
+
entryPath,
|
|
258
|
+
generateClientEntry(manifest, { clientModule, resolve: toSpecifier, live }),
|
|
259
|
+
)
|
|
248
260
|
const entryHref = `./${relative(dirname(htmlPath), entryPath).replaceAll("\\", "/")}`
|
|
249
261
|
writeIfChanged(htmlPath, devHtml(entryHref))
|
|
250
262
|
}
|
|
@@ -355,7 +367,7 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
355
367
|
? async (): Promise<undefined> => undefined
|
|
356
368
|
: servePublicDir({ dir: resolve(publicDir) })
|
|
357
369
|
|
|
358
|
-
writeDevFiles({ routesDir, clientModule, entryPath, htmlPath })
|
|
370
|
+
writeDevFiles({ routesDir, clientModule, entryPath, htmlPath, live: options.live })
|
|
359
371
|
// Importing the generated HTML is what hands it to Bun's bundler. A runtime-computed path is fine:
|
|
360
372
|
// Bun resolves and bundles the HTML, and everything it references, at import time.
|
|
361
373
|
const htmlModule = (await import(htmlPath)) as { default: HtmlBundle }
|
|
@@ -486,7 +498,8 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
486
498
|
development: { hmr: true },
|
|
487
499
|
// The probe route is the ONLY path Bun owns; everything else falls through to nifra's SSR.
|
|
488
500
|
routes: { [PROBE_PATH]: htmlModule.default },
|
|
489
|
-
|
|
501
|
+
websocket: bunDevWebSocket,
|
|
502
|
+
fetch: async (req: Request, bun: BunUpgradeServer): Promise<Response | undefined> => {
|
|
490
503
|
const url = new URL(req.url)
|
|
491
504
|
const host = req.headers.get("host") ?? url.host
|
|
492
505
|
if (!isLoopbackHost(host) && !allowedHosts.has(hostName(host)))
|
|
@@ -494,6 +507,14 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
494
507
|
`[nifra/web/dev] ${hostName(host)} is not a loopback host; add it to allowedHosts if this server should answer it.\n`,
|
|
495
508
|
{ status: 403, headers: { "content-type": "text/plain; charset=utf-8" } },
|
|
496
509
|
)
|
|
510
|
+
if (isWebSocketUpgrade(req)) {
|
|
511
|
+
const answer = await upgradeOnBun(
|
|
512
|
+
await appForRequest((await currentEntry()).src),
|
|
513
|
+
req,
|
|
514
|
+
bun,
|
|
515
|
+
)
|
|
516
|
+
if (answer !== "pass") return answer
|
|
517
|
+
}
|
|
497
518
|
// The stable client-entry URL. Pages reference THIS; it redirects to whichever hashed chunk Bun
|
|
498
519
|
// is serving at the moment the browser asks. A redirect rather than a proxy so the module's final
|
|
499
520
|
// URL is Bun's own - its HMR client derives its socket and module identity from `import.meta.url`,
|
|
@@ -554,7 +575,7 @@ export async function createDevServer(options: DevServerOptions): Promise<DevSer
|
|
|
554
575
|
syncWatchedFiles(false)
|
|
555
576
|
session.markChange()
|
|
556
577
|
try {
|
|
557
|
-
writeDevFiles({ routesDir, clientModule, entryPath, htmlPath })
|
|
578
|
+
writeDevFiles({ routesDir, clientModule, entryPath, htmlPath, live: options.live })
|
|
558
579
|
} catch (err) {
|
|
559
580
|
// A half-saved file can fail the scan mid-edit; the next change re-runs this.
|
|
560
581
|
console.error("[nifra/web/dev] client entry regeneration failed:", err)
|
|
@@ -17,6 +17,8 @@ interface CatalogEntry {
|
|
|
17
17
|
readonly fix: string
|
|
18
18
|
readonly docsAnchor: string
|
|
19
19
|
readonly options?: readonly FixOption[]
|
|
20
|
+
/** Reported by a console warning, not thrown: the dev feed records the warning as an entry. */
|
|
21
|
+
readonly console?: true
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -191,6 +193,52 @@ export const DIAGNOSTIC_CATALOG: readonly CatalogEntry[] = [
|
|
|
191
193
|
fix: "Read the reason in the line: a 401 or 403 means the token lacks purge permission for this zone, project or service; a 5xx or network_error is retried on its own.",
|
|
192
194
|
docsAnchor: "errors#cdn-purge-failed",
|
|
193
195
|
},
|
|
196
|
+
{
|
|
197
|
+
code: "NIFRA_KEEPALIVE_IDENTITY_COOKIE",
|
|
198
|
+
match: (_n, m) => m.includes("NIFRA_KEEPALIVE_IDENTITY_COOKIE"),
|
|
199
|
+
console: true,
|
|
200
|
+
cause:
|
|
201
|
+
"The app keeps pages alive, and a response changed an HttpOnly cookie nifra does not know as a sign-in cookie. If that cookie says who is signed in, a sign-in or sign-out through it leaves the previous visitor's kept pages in every tab, and back shows them.",
|
|
202
|
+
fix: "Name the cookie in createWebApp({ keepAlive: { identityCookies } }) if it carries the session, or in keepAlive.ignoreCookies if it does not.",
|
|
203
|
+
docsAnchor: "errors#keepalive-identity-cookie",
|
|
204
|
+
options: [
|
|
205
|
+
{
|
|
206
|
+
label: "It is the session cookie",
|
|
207
|
+
fix: 'Add it to createWebApp({ keepAlive: { identityCookies: ["<name>"] } }): a response that changes or deletes it rotates the identity epoch, and every tab drops its kept pages.',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
label: "It says nothing about the visitor",
|
|
211
|
+
fix: 'Add it to createWebApp({ keepAlive: { ignoreCookies: ["<name>"] } }) so development stops reporting it.',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
label: "Identity lives in a token",
|
|
215
|
+
fix: "When the session is a bearer token in client storage, call identityChanged() from @nifrajs/web/client at sign-in and sign-out; it drops kept pages in every tab.",
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
code: "NIFRA_KEEPALIVE_DIRECT_CHILD_CSS",
|
|
221
|
+
match: (_n, m) => m.includes("NIFRA_KEEPALIVE_DIRECT_CHILD_CSS"),
|
|
222
|
+
console: true,
|
|
223
|
+
cause:
|
|
224
|
+
"A keepAlive page renders inside two display:contents wrappers, so a selector that reached its top-level element as a direct child (or by its position among siblings) no longer matches it.",
|
|
225
|
+
fix: "Rewrite the selector the message names: a descendant combinator, the wrapper as the parent, or a class on the page's root element.",
|
|
226
|
+
docsAnchor: "errors#keepalive-direct-child-css",
|
|
227
|
+
options: [
|
|
228
|
+
{
|
|
229
|
+
label: "Use a descendant combinator",
|
|
230
|
+
fix: "Write `main section` instead of `main > section`: the wrappers sit between the two.",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
label: "Target the wrapper",
|
|
234
|
+
fix: "Write `[data-nifra-page] > section`: the inner wrapper is the page's parent while it is shown.",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
label: "Put a class on the page root",
|
|
238
|
+
fix: "Give the page's root element a class and select it directly, instead of by its parent or its position.",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
194
242
|
{
|
|
195
243
|
code: "NIFRA_SCHEMA_PARSE",
|
|
196
244
|
match: (n, m) =>
|
|
@@ -215,6 +263,11 @@ const HYDRATION_MESSAGES: readonly RegExp[] = [
|
|
|
215
263
|
/Minified React error #(?:418|423|425)\b/,
|
|
216
264
|
]
|
|
217
265
|
|
|
266
|
+
/** True when a console `message` reports a catalog failure that is only ever logged, never thrown. */
|
|
267
|
+
export function isConsoleDiagnostic(message: string): boolean {
|
|
268
|
+
return DIAGNOSTIC_CATALOG.some((entry) => entry.console === true && entry.match("", message))
|
|
269
|
+
}
|
|
270
|
+
|
|
218
271
|
/** True when `message` is a framework's report of a server/browser render mismatch. */
|
|
219
272
|
export function isHydrationMismatch(message: string): boolean {
|
|
220
273
|
return HYDRATION_MESSAGES.some((pattern) => pattern.test(message))
|