@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/internal/codegen.ts
CHANGED
|
@@ -10,12 +10,14 @@ import {
|
|
|
10
10
|
DATA_GLOBAL,
|
|
11
11
|
HANDOVER_ID,
|
|
12
12
|
LAYOUT_DATA_GLOBAL,
|
|
13
|
+
LIVE_GLOBAL,
|
|
13
14
|
LOCALES_GLOBAL,
|
|
14
15
|
ROOT_ATTRIBUTE,
|
|
15
16
|
ROUTE_GLOBAL,
|
|
16
17
|
} from "../render-seam.ts"
|
|
17
18
|
import { PRERENDERED_GLOBAL } from "../router.ts"
|
|
18
19
|
import { jsStringLiteral, jsValueLiteral } from "./js-string.ts"
|
|
20
|
+
import { keepAliveTable } from "./keep-alive-table.ts"
|
|
19
21
|
|
|
20
22
|
/** The refusal for a server build given no client entry, shared by the codegen and `buildServer`. */
|
|
21
23
|
export function missingClientEntry(clientEntry: unknown): TypeError {
|
|
@@ -53,6 +55,22 @@ export interface GenerateClientEntryOptions {
|
|
|
53
55
|
/** The build moved copy tables into per-locale chunks: the handover carries the chunk URLs, and a
|
|
54
56
|
* navigation loads the copy of the URL it goes to beside the route's code. */
|
|
55
57
|
readonly copyChunks?: boolean
|
|
58
|
+
/**
|
|
59
|
+
* The server runs `createWebApp({ live })`: route loads go through its live load endpoint and the
|
|
60
|
+
* entry keeps what is on screen fresh (`@nifrajs/web/live-client`). Absent, the entry is exactly the
|
|
61
|
+
* non-live one.
|
|
62
|
+
*/
|
|
63
|
+
readonly live?: LiveClientEntry | undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The router as the entry names it: in a keep-alive app, the keep-alive layer over `router`.
|
|
67
|
+
const keepAround = (keep: unknown, router: string): string =>
|
|
68
|
+
keep === undefined ? `const router = ${router}` : `const router = __keep.attach(${router})`
|
|
69
|
+
|
|
70
|
+
/** A client entry for a live server: `{}`, or the endpoints when the runtime moves them. */
|
|
71
|
+
export interface LiveClientEntry {
|
|
72
|
+
readonly path?: string
|
|
73
|
+
readonly loadPath?: string
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
/**
|
|
@@ -65,11 +83,31 @@ export interface GenerateClientEntryOptions {
|
|
|
65
83
|
* Bun has no `import.meta.glob`, so file-based routing needs this. Write the result to a file and
|
|
66
84
|
* bundle it with `buildClient` / `Bun.build` (+ the adapter's transform).
|
|
67
85
|
*/
|
|
86
|
+
/** The client modules whose package has a keep-alive mount beside them, at `<package>/keep-alive`. */
|
|
87
|
+
const KEEP_ALIVE_ADAPTERS: readonly string[] = [
|
|
88
|
+
"@nifrajs/web-react/client",
|
|
89
|
+
"@nifrajs/web-svelte/client",
|
|
90
|
+
"@nifrajs/web-preact/client",
|
|
91
|
+
"@nifrajs/web-solid/client",
|
|
92
|
+
"@nifrajs/web-vue/client",
|
|
93
|
+
]
|
|
94
|
+
|
|
68
95
|
export function generateClientEntry(
|
|
69
96
|
manifest: Manifest,
|
|
70
97
|
options: GenerateClientEntryOptions,
|
|
71
98
|
): string {
|
|
72
|
-
const {
|
|
99
|
+
const { resolve } = options
|
|
100
|
+
const keep = keepAliveTable(manifest)
|
|
101
|
+
if (keep !== undefined && !KEEP_ALIVE_ADAPTERS.includes(options.clientModule)) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`[nifra/web] ${keep.routes.map((id) => `"${id}"`).join(", ")} export keepAlive, which needs the React, Preact, Solid, Svelte or Vue adapter; this app's client module is ${jsStringLiteral(options.clientModule)}. Remove the keepAlive export or switch the adapter.`,
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
// A keep-alive app mounts through the adapter's keep-alive module, which re-exports its client.
|
|
107
|
+
const clientModule =
|
|
108
|
+
keep === undefined
|
|
109
|
+
? options.clientModule
|
|
110
|
+
: options.clientModule.replace(/\/client$/, "/keep-alive")
|
|
73
111
|
|
|
74
112
|
const loaderRows: string[] = []
|
|
75
113
|
const patternRows: string[] = []
|
|
@@ -165,6 +203,13 @@ export function generateClientEntry(
|
|
|
165
203
|
)
|
|
166
204
|
}
|
|
167
205
|
|
|
206
|
+
const live = options.live
|
|
207
|
+
const liveFetch =
|
|
208
|
+
keep !== undefined
|
|
209
|
+
? ", fetchData: __keep.fetchData"
|
|
210
|
+
: live === undefined
|
|
211
|
+
? ""
|
|
212
|
+
: ", fetchData: __live.fetchData"
|
|
168
213
|
return `${[
|
|
169
214
|
// `/client`, never the root: the root's graph carries the server (renderPage, the static-file
|
|
170
215
|
// server), and Vite's dev server evaluates what it is given instead of tree-shaking it.
|
|
@@ -173,6 +218,8 @@ export function generateClientEntry(
|
|
|
173
218
|
...(options.serverComponents
|
|
174
219
|
? ['import { bindComponentRouter } from "@nifrajs/web/server-components/client"']
|
|
175
220
|
: []),
|
|
221
|
+
...(live === undefined ? [] : ['import { liveClient } from "@nifrajs/web/live-client"']),
|
|
222
|
+
...(keep === undefined ? [] : ['import { keepAliveClient } from "@nifrajs/web/keep-alive"']),
|
|
176
223
|
// Namespace import: `errorBoundary` is optional (an adapter may not export it). A namespace member
|
|
177
224
|
// access yields `undefined` if absent - unlike a named import, which would be a link error.
|
|
178
225
|
...(loadings.length > 0
|
|
@@ -235,7 +282,22 @@ export function generateClientEntry(
|
|
|
235
282
|
"const holds = {}",
|
|
236
283
|
// routeId → the ids and `handle` exports `useMatches` reports (layouts, then the page).
|
|
237
284
|
`const layoutIdsOf = ${jsValueLiteral(layoutIdsOf)}`,
|
|
285
|
+
...(live === undefined
|
|
286
|
+
? []
|
|
287
|
+
: [
|
|
288
|
+
`const __live = liveClient({ ${[
|
|
289
|
+
"layoutIdsOf",
|
|
290
|
+
...(live.path === undefined ? [] : [`path: ${jsStringLiteral(live.path)}`]),
|
|
291
|
+
...(live.loadPath === undefined ? [] : [`loadPath: ${jsStringLiteral(live.loadPath)}`]),
|
|
292
|
+
].join(", ")} })`,
|
|
293
|
+
]),
|
|
238
294
|
"const matchChains = {}",
|
|
295
|
+
...(keep === undefined
|
|
296
|
+
? []
|
|
297
|
+
: [
|
|
298
|
+
`const keepAliveTable = ${jsValueLiteral(keep)}`,
|
|
299
|
+
`const __keep = keepAliveClient({ table: keepAliveTable, routeHooks, matchChains${live === undefined ? "" : ", fetchData: __live.fetchData"} })`,
|
|
300
|
+
]),
|
|
239
301
|
`const ${options.copyChunks === true ? "loadRoute" : "loadModule"} = async (id) => {`,
|
|
240
302
|
" if (chains[id]) return",
|
|
241
303
|
" const mods = await loaders[id]()",
|
|
@@ -271,7 +333,7 @@ export function generateClientEntry(
|
|
|
271
333
|
// backend half, which no browser bundle contains.
|
|
272
334
|
" const boundaryMods = errorRouteIds.has(id) ? mods.slice(0, -1) : mods",
|
|
273
335
|
" const boundaries = boundaryMods.flatMap((m) => (m.boundaries ?? []).map((b) => ({ name: b.name, mode: b.mode, ...(b.errorId === undefined ? {} : { errorId: b.errorId }) })))",
|
|
274
|
-
|
|
336
|
+
` routeHooks[id] = { clientLoader: page.clientLoader, clientAction: page.clientAction, shareData: page.shareData, ${keep === undefined ? "" : "keepAlive: page.keepAlive, "}boundaries }`,
|
|
275
337
|
" if (page.ssr === false) holds[id] = [...chains[id].slice(0, -1), page.HydrateFallback ?? (() => null)]",
|
|
276
338
|
"}",
|
|
277
339
|
...(options.copyChunks === true
|
|
@@ -293,7 +355,11 @@ export function generateClientEntry(
|
|
|
293
355
|
"const handover = handovers[handovers.length - 1]",
|
|
294
356
|
"if (handover !== undefined) {",
|
|
295
357
|
' const state = JSON.parse(handover.textContent || "{}")',
|
|
296
|
-
` for (const key of ${jsValueLiteral(
|
|
358
|
+
` for (const key of ${jsValueLiteral([
|
|
359
|
+
...HANDOVER_GLOBALS,
|
|
360
|
+
...(options.copyChunks === true ? [COPY_CHUNKS_GLOBAL] : []),
|
|
361
|
+
...(live === undefined ? [] : [LIVE_GLOBAL]),
|
|
362
|
+
])}) {`,
|
|
297
363
|
" if (Object.prototype.hasOwnProperty.call(state, key)) Reflect.set(window, key, state[key])",
|
|
298
364
|
" }",
|
|
299
365
|
"}",
|
|
@@ -325,6 +391,8 @@ export function generateClientEntry(
|
|
|
325
391
|
` layoutData: window.${LAYOUT_DATA_GLOBAL} && window.${LAYOUT_DATA_GLOBAL}.map(mapDeferred),`,
|
|
326
392
|
` boundaries: mapDeferred(window.${BOUNDARY_GLOBAL}),`,
|
|
327
393
|
` serverComponents: window.${COMPONENT_GLOBAL},`,
|
|
394
|
+
// The seed the server rendered the page with: the live client exchanges it for its first grant.
|
|
395
|
+
...(live === undefined ? [] : [` live: window.${LIVE_GLOBAL},`]),
|
|
328
396
|
// actionData (only set after a form POST) is in the initial state so the binding hydrates
|
|
329
397
|
// consistently with the server-rendered markup; mapped through `mapDeferred` too so a deferred
|
|
330
398
|
// action's placeholders become registry markers (a no-op when the action didn't defer).
|
|
@@ -334,9 +402,18 @@ export function generateClientEntry(
|
|
|
334
402
|
...loadingLines,
|
|
335
403
|
// With `_loading` pages the store is wrapped: a slow navigation then names a loading chain (the
|
|
336
404
|
// shared layouts + the `_loading` component) for the mounted view to render in the meantime.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
405
|
+
keepAround(
|
|
406
|
+
keep,
|
|
407
|
+
loadings.length > 0
|
|
408
|
+
? `withLoading(createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks${liveFetch} }), { routes: loadingRoutes, modules: loadingModules, chains, searchSchemas, matchChains })`
|
|
409
|
+
: `createClientRouter({ patterns, initial, loadModule, statusRoutes${routerStatusOption}, searchClientKeys, routeHooks${liveFetch} })`,
|
|
410
|
+
),
|
|
411
|
+
// Kept pages hold the code they were rendered with: a hot update drops them.
|
|
412
|
+
...(keep === undefined
|
|
413
|
+
? []
|
|
414
|
+
: [
|
|
415
|
+
'if (import.meta.hot) for (const event of ["bun:afterUpdate", "vite:afterUpdate"]) import.meta.hot.on(event, () => router.dropKept("hot-update"))',
|
|
416
|
+
]),
|
|
340
417
|
...(options.serverComponents ? ["bindComponentRouter(router)"] : []),
|
|
341
418
|
"installHistory(router)",
|
|
342
419
|
"installForms(router)",
|
|
@@ -392,11 +469,13 @@ export function generateClientEntry(
|
|
|
392
469
|
" for (const listener of [...listeners]) listener()",
|
|
393
470
|
" }, 0)",
|
|
394
471
|
" }",
|
|
395
|
-
|
|
472
|
+
` mountRouter({ router: view, routes: chains, searchSchemas, matchChains, container: root${keep === undefined ? "" : ", keepAlive: keepAliveTable"} })`,
|
|
396
473
|
// Run the optional client loader only after the adapter has mounted the SSR tree. The initial
|
|
397
474
|
// server data is already in `window.__NIFRA_DATA__`, so `serverLoader()` reuses it and cannot
|
|
398
475
|
// duplicate the first request.
|
|
399
476
|
" router.hydrate().catch((error) => console.error('[nifra/web] clientLoader failed:', error))",
|
|
477
|
+
// Once mounted: the first live load and the socket must not race the hydration.
|
|
478
|
+
...(live === undefined ? [] : [" __live.attach(router)"]),
|
|
400
479
|
// Next frame: the adapter has committed its initial hydration, so handlers are attached. Flip the
|
|
401
480
|
// document to interactive (`data-nifra-hydrated` + `nifra:hydrated`) for apps to gate pre-hydration UI.
|
|
402
481
|
" requestAnimationFrame(signalHydrated)",
|
|
@@ -521,6 +600,13 @@ function moduleSpecifier(resolved: string): string {
|
|
|
521
600
|
// A route's namespace is not assignable to `RouteModule` under a strict `tsc` (a backend half has no
|
|
522
601
|
// `default`, a typed `meta` narrows its data), so the table holds plain objects and the cast happens at
|
|
523
602
|
// `buildManifest`'s boundary - the same cast `discoverRoutes` applies to its dynamic import.
|
|
603
|
+
// The routes the scan found exporting `keepAlive`, which a disk-less server cannot scan again.
|
|
604
|
+
const keepAliveArg = (manifest: Manifest): string => {
|
|
605
|
+
const files = [
|
|
606
|
+
...new Set(manifest.routes.filter((route) => route.keepAlive).map((route) => route.file)),
|
|
607
|
+
]
|
|
608
|
+
return files.length === 0 ? "" : `, { keepAlive: ${jsValueLiteral(files)} }`
|
|
609
|
+
}
|
|
524
610
|
const LAZY_IMPORTER = "(file) => loaders[file] as () => Promise<RouteModule>"
|
|
525
611
|
const EAGER_IMPORTER = "(file) => () => Promise.resolve(modules[file] as RouteModule)"
|
|
526
612
|
|
|
@@ -604,7 +690,7 @@ export function generateServerManifest(
|
|
|
604
690
|
cssLoadingLine,
|
|
605
691
|
...copyChunksLines,
|
|
606
692
|
...buildAssetsLines,
|
|
607
|
-
`export const manifest = buildManifest(Object.keys(loaders), ${LAZY_IMPORTER})`,
|
|
693
|
+
`export const manifest = buildManifest(Object.keys(loaders), ${LAZY_IMPORTER}${keepAliveArg(manifest)})`,
|
|
608
694
|
].join("\n")}\n`
|
|
609
695
|
}
|
|
610
696
|
// Eager: `import * as` per route (all bundled into the entry, parsed at boot). Index-based
|
|
@@ -625,6 +711,6 @@ export function generateServerManifest(
|
|
|
625
711
|
cssLoadingLine,
|
|
626
712
|
...copyChunksLines,
|
|
627
713
|
...buildAssetsLines,
|
|
628
|
-
`export const manifest = buildManifest(Object.keys(modules), ${EAGER_IMPORTER})`,
|
|
714
|
+
`export const manifest = buildManifest(Object.keys(modules), ${EAGER_IMPORTER}${keepAliveArg(manifest)})`,
|
|
629
715
|
].join("\n")}\n`
|
|
630
716
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's WebSocket routes under the dev servers. Neither dev server is the app's own listener, so
|
|
3
|
+
* neither gets the upgrades `app.listen()` and the adapters wire: each hands the handshake to the
|
|
4
|
+
* app's `resolveWebSocketUpgrade` seam - the route's `upgrade()` guard and Origin check included - and
|
|
5
|
+
* wires an accepted socket with the runtime's own `attach`, so a dev socket behaves as a served one.
|
|
6
|
+
*/
|
|
7
|
+
import type { WebSocketUpgradeOutcome } from "@nifrajs/core"
|
|
8
|
+
|
|
9
|
+
type Upgrade = Extract<WebSocketUpgradeOutcome, { readonly kind: "upgrade" }>
|
|
10
|
+
|
|
11
|
+
/** A nifra app's handshake seam; a plain `{ fetch }` handler has none and takes no upgrades. */
|
|
12
|
+
export interface UpgradableApp {
|
|
13
|
+
resolveWebSocketUpgrade?(
|
|
14
|
+
request: Request,
|
|
15
|
+
): WebSocketUpgradeOutcome | Promise<WebSocketUpgradeOutcome>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function isWebSocketUpgrade(request: Request): boolean {
|
|
19
|
+
return request.headers.get("upgrade")?.toLowerCase() === "websocket"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Listener = (event: never) => void
|
|
23
|
+
|
|
24
|
+
/** A standard-socket view `attach` wires its listeners to; the dev server feeds it events. */
|
|
25
|
+
function socketView(native: {
|
|
26
|
+
send(data: string | ArrayBufferView | ArrayBuffer): unknown
|
|
27
|
+
close(code?: number, reason?: string): void
|
|
28
|
+
readonly readyState: number
|
|
29
|
+
}) {
|
|
30
|
+
const listeners = new Map<string, Listener[]>()
|
|
31
|
+
return {
|
|
32
|
+
view: {
|
|
33
|
+
send: (data: string | ArrayBufferView | ArrayBuffer): void => {
|
|
34
|
+
native.send(data)
|
|
35
|
+
},
|
|
36
|
+
close: (code?: number, reason?: string) => native.close(code, reason),
|
|
37
|
+
get readyState() {
|
|
38
|
+
return native.readyState
|
|
39
|
+
},
|
|
40
|
+
addEventListener(type: string, listener: Listener): void {
|
|
41
|
+
listeners.set(type, [...(listeners.get(type) ?? []), listener])
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
emit(type: string, event: unknown): void {
|
|
45
|
+
for (const listener of listeners.get(type) ?? []) Reflect.apply(listener, undefined, [event])
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function attachView(view: ReturnType<typeof socketView>["view"], outcome: Upgrade): void {
|
|
51
|
+
outcome.attach(view, outcome.handler, outcome.data, {
|
|
52
|
+
openNow: true,
|
|
53
|
+
pubsub: outcome.pubsub,
|
|
54
|
+
maxPayloadBytes: outcome.maxPayloadBytes,
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- Bun ----------------------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
/** What a dev socket carries from its handshake to `open`, and the view its events go to after. */
|
|
61
|
+
export interface BunDevSocketData {
|
|
62
|
+
readonly outcome: Upgrade
|
|
63
|
+
emit?: (type: string, event: unknown) => void
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The part of Bun's `ServerWebSocket` used here. */
|
|
67
|
+
interface BunDevSocket {
|
|
68
|
+
send(data: string | ArrayBufferView | ArrayBuffer): unknown
|
|
69
|
+
close(code?: number, reason?: string): void
|
|
70
|
+
readonly readyState: number
|
|
71
|
+
readonly data: BunDevSocketData
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface BunUpgradeServer {
|
|
75
|
+
upgrade(request: Request, options: { data: BunDevSocketData }): boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** `Bun.serve`'s `websocket` option for a dev server whose upgrades go through {@link upgradeOnBun}. */
|
|
79
|
+
export const bunDevWebSocket = {
|
|
80
|
+
open(socket: BunDevSocket): void {
|
|
81
|
+
const { view, emit } = socketView(socket)
|
|
82
|
+
socket.data.emit = emit
|
|
83
|
+
attachView(view, socket.data.outcome)
|
|
84
|
+
},
|
|
85
|
+
message(socket: BunDevSocket, message: string | Uint8Array): void {
|
|
86
|
+
socket.data.emit?.("message", { data: message })
|
|
87
|
+
},
|
|
88
|
+
close(socket: BunDevSocket, code: number, reason: string): void {
|
|
89
|
+
socket.data.emit?.("close", { code, reason })
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run the handshake through the app. `undefined` when the socket was upgraded; a `Response` for a
|
|
95
|
+
* refusal; `"pass"` when no WebSocket route takes the path, for the dev server to answer as HTTP.
|
|
96
|
+
*/
|
|
97
|
+
export async function upgradeOnBun(
|
|
98
|
+
app: UpgradableApp,
|
|
99
|
+
request: Request,
|
|
100
|
+
server: BunUpgradeServer,
|
|
101
|
+
): Promise<Response | "pass" | undefined> {
|
|
102
|
+
if (app.resolveWebSocketUpgrade === undefined) return "pass"
|
|
103
|
+
const outcome = await app.resolveWebSocketUpgrade(request)
|
|
104
|
+
if (outcome.kind === "pass") return "pass"
|
|
105
|
+
if (outcome.kind === "reject") return outcome.response
|
|
106
|
+
return server.upgrade(request, { data: { outcome } })
|
|
107
|
+
? undefined
|
|
108
|
+
: new Response("upgrade_required", { status: 426 })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// --- Node (the Vite dev server) -------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
/** The part of a Node socket an upgrade writes to. */
|
|
114
|
+
export interface UpgradeSocket {
|
|
115
|
+
write(chunk: string): unknown
|
|
116
|
+
destroy(): void
|
|
117
|
+
on(event: "error", listener: () => void): unknown
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The part of the `ws` package's `WebSocketServer` used here (no `@types/ws` dependency). */
|
|
121
|
+
export interface WsServer {
|
|
122
|
+
handleUpgrade(
|
|
123
|
+
request: unknown,
|
|
124
|
+
socket: UpgradeSocket,
|
|
125
|
+
head: unknown,
|
|
126
|
+
done: (ws: WsSocket) => void,
|
|
127
|
+
): void
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The part of a `ws` socket used here. Its own events, not its `addEventListener`: Bun's `ws`
|
|
131
|
+
* shim hands text frames to that as bytes. The shim also gives a close reason as a string where
|
|
132
|
+
* `ws` gives bytes. */
|
|
133
|
+
interface WsSocket {
|
|
134
|
+
send(data: string | ArrayBufferView | ArrayBuffer): void
|
|
135
|
+
close(code?: number, reason?: string): void
|
|
136
|
+
readonly readyState: number
|
|
137
|
+
on(event: "message", listener: (data: string | Uint8Array, isBinary: boolean) => void): void
|
|
138
|
+
on(event: "close", listener: (code: number, reason: string | Uint8Array) => void): void
|
|
139
|
+
on(event: "error", listener: (error: Error) => void): void
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const TEXT = new TextDecoder()
|
|
143
|
+
const textOf = (data: string | Uint8Array): string =>
|
|
144
|
+
typeof data === "string" ? data : TEXT.decode(data)
|
|
145
|
+
|
|
146
|
+
/** A non-literal specifier: `ws` is an optional peer, typed structurally above. */
|
|
147
|
+
const WS_MODULE = "ws"
|
|
148
|
+
|
|
149
|
+
/** A noServer `ws` server from `specifier`; undefined when it cannot be loaded or has none. */
|
|
150
|
+
export async function wsServerFrom(specifier: string): Promise<WsServer | undefined> {
|
|
151
|
+
try {
|
|
152
|
+
const mod: {
|
|
153
|
+
WebSocketServer?: new (options: { noServer: true }) => WsServer
|
|
154
|
+
default?: { WebSocketServer?: new (options: { noServer: true }) => WsServer }
|
|
155
|
+
} = await import(specifier)
|
|
156
|
+
const Server = mod.WebSocketServer ?? mod.default?.WebSocketServer
|
|
157
|
+
return Server === undefined ? undefined : new Server({ noServer: true })
|
|
158
|
+
} catch {
|
|
159
|
+
return undefined
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let wsServer: Promise<WsServer | undefined> | undefined
|
|
164
|
+
|
|
165
|
+
/** One server for every dev upgrade. */
|
|
166
|
+
const loadWsServer = (): Promise<WsServer | undefined> => {
|
|
167
|
+
wsServer ??= wsServerFrom(WS_MODULE)
|
|
168
|
+
return wsServer
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Answer a Node `upgrade` event through the app. Resolves `false` when no WebSocket route takes the
|
|
173
|
+
* path, leaving the socket to the other listeners (Vite's HMR among them).
|
|
174
|
+
*/
|
|
175
|
+
export async function upgradeOnNode(
|
|
176
|
+
app: UpgradableApp,
|
|
177
|
+
request: Request,
|
|
178
|
+
nodeRequest: unknown,
|
|
179
|
+
socket: UpgradeSocket,
|
|
180
|
+
head: unknown,
|
|
181
|
+
wsServerOf: () => Promise<WsServer | undefined> = loadWsServer,
|
|
182
|
+
): Promise<boolean> {
|
|
183
|
+
if (app.resolveWebSocketUpgrade === undefined) return false
|
|
184
|
+
// A peer can reset the socket while the guard is awaited; with no listener that error ends the process.
|
|
185
|
+
socket.on("error", () => {})
|
|
186
|
+
let outcome: WebSocketUpgradeOutcome
|
|
187
|
+
try {
|
|
188
|
+
outcome = await app.resolveWebSocketUpgrade(request)
|
|
189
|
+
} catch {
|
|
190
|
+
await writeRefusal(socket, new Response("internal_error", { status: 500 }))
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
if (outcome.kind === "pass") return false
|
|
194
|
+
if (outcome.kind === "reject") {
|
|
195
|
+
await writeRefusal(socket, outcome.response)
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
const wss = await wsServerOf()
|
|
199
|
+
if (wss === undefined) {
|
|
200
|
+
await writeRefusal(
|
|
201
|
+
socket,
|
|
202
|
+
new Response("[nifra] install `ws` to serve app.ws() routes under the Vite dev server\n", {
|
|
203
|
+
status: 501,
|
|
204
|
+
}),
|
|
205
|
+
)
|
|
206
|
+
return true
|
|
207
|
+
}
|
|
208
|
+
const upgrade = outcome
|
|
209
|
+
wss.handleUpgrade(nodeRequest, socket, head, (ws) => {
|
|
210
|
+
const { view, emit } = socketView(ws)
|
|
211
|
+
ws.on("message", (data, isBinary) => emit("message", { data: isBinary ? data : textOf(data) }))
|
|
212
|
+
// Bun's `node:http` stops listening when a socket's close listener throws.
|
|
213
|
+
ws.on("close", (code, reason) => emit("close", { code, reason: textOf(reason) }))
|
|
214
|
+
ws.on("error", (error) => emit("error", error))
|
|
215
|
+
attachView(view, upgrade)
|
|
216
|
+
})
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** A refusal written to the raw socket, then closed. */
|
|
221
|
+
async function writeRefusal(socket: UpgradeSocket, response: Response): Promise<void> {
|
|
222
|
+
const body = await response.text()
|
|
223
|
+
let head = `HTTP/1.1 ${response.status} ${response.statusText || "Refused"}\r\n`
|
|
224
|
+
response.headers.forEach((value, key) => {
|
|
225
|
+
if (key !== "content-length" && key !== "connection") head += `${key}: ${value}\r\n`
|
|
226
|
+
})
|
|
227
|
+
head += `content-length: ${new TextEncoder().encode(body).byteLength}\r\nconnection: close\r\n\r\n`
|
|
228
|
+
socket.write(head + body)
|
|
229
|
+
socket.destroy()
|
|
230
|
+
}
|