@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
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import type { ResolvedPprOptions } from "./options.ts"
|
|
2
|
+
|
|
3
|
+
/** Which admission level denied an invocation, checked in this order. */
|
|
4
|
+
export type DenialLevel = "stop" | "process" | "boundary"
|
|
5
|
+
|
|
6
|
+
/** How one hole invocation ended, as its response sees it. */
|
|
7
|
+
export type HoleRun<T> =
|
|
8
|
+
| { readonly ok: true; readonly value: T }
|
|
9
|
+
| {
|
|
10
|
+
readonly ok: false
|
|
11
|
+
readonly reason: "denied" | "failed" | "aborted"
|
|
12
|
+
readonly error?: unknown
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PprAdmissionStats {
|
|
16
|
+
/** Responses holding a hole-work slot. */
|
|
17
|
+
readonly slotsInUse: number
|
|
18
|
+
/** Every invocation whose promise has not settled, abandoned ones included (`ppr.inflight_loaders`). */
|
|
19
|
+
readonly pending: number
|
|
20
|
+
/** The abandoned subset of {@link pending} (`ppr.abandoned_inflight`). */
|
|
21
|
+
readonly abandoned: number
|
|
22
|
+
readonly pendingBy: Readonly<Record<string, number>>
|
|
23
|
+
readonly abandonedBy: Readonly<Record<string, number>>
|
|
24
|
+
readonly denied: Readonly<Record<DenialLevel, number>>
|
|
25
|
+
/** Responses that found no free slot and invoked no hole (`ppr.shed`). */
|
|
26
|
+
readonly shed: number
|
|
27
|
+
/** True while the global admission stop holds (`ppr.admission_stopped`). */
|
|
28
|
+
readonly admissionStopped: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type AdmissionOptions = Pick<
|
|
32
|
+
ResolvedPprOptions,
|
|
33
|
+
| "boundaryAbandonedTrip"
|
|
34
|
+
| "processAbandonedTrip"
|
|
35
|
+
| "admissionStop"
|
|
36
|
+
| "abandonGraceMs"
|
|
37
|
+
| "maxHoleWork"
|
|
38
|
+
>
|
|
39
|
+
|
|
40
|
+
/** The clock and timer admission reads; injected by tests so no test waits on the wall clock. */
|
|
41
|
+
export interface AdmissionTimers {
|
|
42
|
+
now(): number
|
|
43
|
+
setTimeout(callback: () => void, ms: number): unknown
|
|
44
|
+
clearTimeout(handle: unknown): void
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SYSTEM_TIMERS: AdmissionTimers = {
|
|
48
|
+
now: () => performance.now(),
|
|
49
|
+
setTimeout: (callback, ms) => setTimeout(callback, ms),
|
|
50
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the handle came from this object's own setTimeout.
|
|
51
|
+
clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const watchers = new WeakMap<AbortSignal, Set<() => void>>()
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Calls `onAbort` when `signal` aborts, until the returned function is called. A signal gets one
|
|
58
|
+
* native listener that every watcher shares: adding and removing a native listener per hole
|
|
59
|
+
* invocation cost more under load than the invocation's own bookkeeping.
|
|
60
|
+
*/
|
|
61
|
+
export function watchAbort(signal: AbortSignal, onAbort: () => void): () => void {
|
|
62
|
+
let watching = watchers.get(signal)
|
|
63
|
+
if (watching === undefined) {
|
|
64
|
+
const created = new Set<() => void>()
|
|
65
|
+
watching = created
|
|
66
|
+
watchers.set(signal, created)
|
|
67
|
+
signal.addEventListener(
|
|
68
|
+
"abort",
|
|
69
|
+
() => {
|
|
70
|
+
for (const watcher of created) watcher()
|
|
71
|
+
},
|
|
72
|
+
{ once: true },
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
const set = watching
|
|
76
|
+
set.add(onAbort)
|
|
77
|
+
return () => {
|
|
78
|
+
set.delete(onAbort)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A boundary's count, kept once made: an invocation changes its own count with no map lookup. */
|
|
83
|
+
interface Count {
|
|
84
|
+
n: number
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const countOf = (counts: Map<string, Count>, key: string): Count => {
|
|
88
|
+
let count = counts.get(key)
|
|
89
|
+
if (count === undefined) {
|
|
90
|
+
count = { n: 0 }
|
|
91
|
+
counts.set(key, count)
|
|
92
|
+
}
|
|
93
|
+
return count
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const nonZero = (counts: Map<string, Count>): Record<string, number> => {
|
|
97
|
+
const out: Record<string, number> = {}
|
|
98
|
+
for (const [key, count] of counts) if (count.n !== 0) out[key] = count.n
|
|
99
|
+
return out
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Bounds the hole work a process runs without letting one bad loader starve the rest.
|
|
104
|
+
*
|
|
105
|
+
* An invocation is abandoned once its signal aborted more than `abandonGraceMs` ago and its promise
|
|
106
|
+
* is still pending; it stays counted until the promise settles. Only abandoned invocations count
|
|
107
|
+
* toward a denial level, so a well-behaved boundary is denied only at the global stop. Each response
|
|
108
|
+
* holds one hole-work slot from before its first invocation until every invocation has settled,
|
|
109
|
+
* been denied or been abandoned, which bounds live invocations by `32 * maxHoleWork`.
|
|
110
|
+
*/
|
|
111
|
+
export class HoleAdmission {
|
|
112
|
+
readonly #options: AdmissionOptions
|
|
113
|
+
readonly #onLateSettle: ((key: string, msAfterAbort: number) => void) | undefined
|
|
114
|
+
readonly #timers: AdmissionTimers
|
|
115
|
+
readonly #pendingBy = new Map<string, Count>()
|
|
116
|
+
readonly #abandonedBy = new Map<string, Count>()
|
|
117
|
+
readonly #denied: Record<DenialLevel, number> = { stop: 0, process: 0, boundary: 0 }
|
|
118
|
+
#pending = 0
|
|
119
|
+
#abandoned = 0
|
|
120
|
+
#slotsInUse = 0
|
|
121
|
+
#shed = 0
|
|
122
|
+
|
|
123
|
+
constructor(
|
|
124
|
+
options: AdmissionOptions,
|
|
125
|
+
onLateSettle?: (key: string, msAfterAbort: number) => void,
|
|
126
|
+
timers: AdmissionTimers = SYSTEM_TIMERS,
|
|
127
|
+
) {
|
|
128
|
+
this.#options = options
|
|
129
|
+
this.#onLateSettle = onLateSettle
|
|
130
|
+
this.#timers = timers
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A hole-work slot for one response, or `undefined` when none is free (the response is shed). */
|
|
134
|
+
slot(): HoleSlot | undefined {
|
|
135
|
+
if (this.#slotsInUse >= this.#options.maxHoleWork) {
|
|
136
|
+
this.#shed++
|
|
137
|
+
return undefined
|
|
138
|
+
}
|
|
139
|
+
this.#slotsInUse++
|
|
140
|
+
return new HoleSlot(this, () => this.#slotsInUse--)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The level that denies boundary `key` now, or `undefined` when it is admitted. */
|
|
144
|
+
denial(key: string): DenialLevel | undefined {
|
|
145
|
+
if (this.#abandoned >= this.#options.admissionStop) return "stop"
|
|
146
|
+
const mine = this.#abandonedBy.get(key)?.n ?? 0
|
|
147
|
+
if (this.#abandoned >= this.#options.processAbandonedTrip && mine > 0) return "process"
|
|
148
|
+
if (mine >= this.#options.boundaryAbandonedTrip) return "boundary"
|
|
149
|
+
return undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** @internal Counts one denial. */
|
|
153
|
+
countDenied(level: DenialLevel): void {
|
|
154
|
+
this.#denied[level]++
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @internal Runs one admitted invocation, counted until its promise settles. Once its signal has
|
|
159
|
+
* been aborted for `abandonGraceMs` it is abandoned and `onDone` fires; `onDone` fires again when
|
|
160
|
+
* it settles. The result settles with the loader, or at once when the signal aborts first: a
|
|
161
|
+
* response never waits on a loader that ignores its signal. One promise and one abort watcher per
|
|
162
|
+
* invocation; a chain of them cost more than the loaders the bench page runs.
|
|
163
|
+
*/
|
|
164
|
+
invoke<T>(
|
|
165
|
+
key: string,
|
|
166
|
+
signal: AbortSignal,
|
|
167
|
+
load: () => T | Promise<T>,
|
|
168
|
+
onDone: () => void,
|
|
169
|
+
watch: (onAbort: () => void) => () => void,
|
|
170
|
+
): Promise<HoleRun<T>> {
|
|
171
|
+
this.#pending++
|
|
172
|
+
const pending = countOf(this.#pendingBy, key)
|
|
173
|
+
pending.n++
|
|
174
|
+
return new Promise<HoleRun<T>>((resolve) => {
|
|
175
|
+
let answered = false
|
|
176
|
+
const answer = (run: HoleRun<T>): void => {
|
|
177
|
+
if (answered) return
|
|
178
|
+
answered = true
|
|
179
|
+
resolve(run)
|
|
180
|
+
}
|
|
181
|
+
let abandoned: Count | undefined
|
|
182
|
+
let abortedAt: number | undefined
|
|
183
|
+
let timer: unknown
|
|
184
|
+
const onAbort = (): void => {
|
|
185
|
+
abortedAt = this.#timers.now()
|
|
186
|
+
timer = this.#timers.setTimeout(() => {
|
|
187
|
+
abandoned = countOf(this.#abandonedBy, key)
|
|
188
|
+
abandoned.n++
|
|
189
|
+
this.#abandoned++
|
|
190
|
+
onDone()
|
|
191
|
+
}, this.#options.abandonGraceMs)
|
|
192
|
+
answer({ ok: false, reason: "aborted", error: signal.reason })
|
|
193
|
+
}
|
|
194
|
+
let unwatch: (() => void) | undefined
|
|
195
|
+
if (signal.aborted) onAbort()
|
|
196
|
+
else unwatch = watch(onAbort)
|
|
197
|
+
const settle = (): void => {
|
|
198
|
+
unwatch?.()
|
|
199
|
+
if (timer !== undefined) this.#timers.clearTimeout(timer)
|
|
200
|
+
this.#pending--
|
|
201
|
+
pending.n--
|
|
202
|
+
if (abandoned !== undefined) {
|
|
203
|
+
this.#abandoned--
|
|
204
|
+
abandoned.n--
|
|
205
|
+
}
|
|
206
|
+
if (abortedAt !== undefined) {
|
|
207
|
+
const late = this.#timers.now() - abortedAt
|
|
208
|
+
if (late > this.#options.abandonGraceMs) this.#onLateSettle?.(key, late)
|
|
209
|
+
}
|
|
210
|
+
onDone()
|
|
211
|
+
}
|
|
212
|
+
// `then` turns a synchronous throw into this invocation's rejection.
|
|
213
|
+
Promise.resolve()
|
|
214
|
+
.then(load)
|
|
215
|
+
.then(
|
|
216
|
+
(value) => {
|
|
217
|
+
settle()
|
|
218
|
+
answer({ ok: true, value })
|
|
219
|
+
},
|
|
220
|
+
(error: unknown) => {
|
|
221
|
+
settle()
|
|
222
|
+
answer({ ok: false, reason: signal.aborted ? "aborted" : "failed", error })
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
stats(): PprAdmissionStats {
|
|
229
|
+
return {
|
|
230
|
+
slotsInUse: this.#slotsInUse,
|
|
231
|
+
pending: this.#pending,
|
|
232
|
+
abandoned: this.#abandoned,
|
|
233
|
+
pendingBy: nonZero(this.#pendingBy),
|
|
234
|
+
abandonedBy: nonZero(this.#abandonedBy),
|
|
235
|
+
denied: { ...this.#denied },
|
|
236
|
+
shed: this.#shed,
|
|
237
|
+
admissionStopped: this.#abandoned >= this.#options.admissionStop,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** One response's hole-work slot. Released exactly once. */
|
|
243
|
+
export class HoleSlot {
|
|
244
|
+
readonly #admission: HoleAdmission
|
|
245
|
+
readonly #release: () => void
|
|
246
|
+
#outstanding = 0
|
|
247
|
+
#sealed = false
|
|
248
|
+
#released = false
|
|
249
|
+
|
|
250
|
+
constructor(admission: HoleAdmission, release: () => void) {
|
|
251
|
+
this.#admission = admission
|
|
252
|
+
this.#release = release
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Invoke one hole loader under admission. Denied, it is never invoked. Otherwise the result
|
|
257
|
+
* settles with the loader, or at once when `signal` aborts while the loader is still pending.
|
|
258
|
+
* `watch` reports that abort; the caller that aborts the signal itself passes its own.
|
|
259
|
+
*/
|
|
260
|
+
run<T>(
|
|
261
|
+
key: string,
|
|
262
|
+
signal: AbortSignal,
|
|
263
|
+
load: () => T | Promise<T>,
|
|
264
|
+
watch: (onAbort: () => void) => () => void = (onAbort) => watchAbort(signal, onAbort),
|
|
265
|
+
): Promise<HoleRun<T>> {
|
|
266
|
+
if (this.#sealed) throw new Error("[nifra/web] ppr: a sealed hole slot cannot start a hole")
|
|
267
|
+
if (signal.aborted) return Promise.resolve({ ok: false, reason: "aborted" })
|
|
268
|
+
const level = this.#admission.denial(key)
|
|
269
|
+
if (level !== undefined) {
|
|
270
|
+
this.#admission.countDenied(level)
|
|
271
|
+
return Promise.resolve({ ok: false, reason: "denied" })
|
|
272
|
+
}
|
|
273
|
+
this.#outstanding++
|
|
274
|
+
let done = false
|
|
275
|
+
return this.#admission.invoke(
|
|
276
|
+
key,
|
|
277
|
+
signal,
|
|
278
|
+
load,
|
|
279
|
+
() => {
|
|
280
|
+
if (done) return
|
|
281
|
+
done = true
|
|
282
|
+
this.#outstanding--
|
|
283
|
+
this.#maybeRelease()
|
|
284
|
+
},
|
|
285
|
+
watch,
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** No more holes will start for this response; the slot frees once the started ones are done. */
|
|
290
|
+
seal(): void {
|
|
291
|
+
this.#sealed = true
|
|
292
|
+
this.#maybeRelease()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#maybeRelease(): void {
|
|
296
|
+
if (this.#released || !this.#sealed || this.#outstanding > 0) return
|
|
297
|
+
this.#released = true
|
|
298
|
+
this.#release()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { MAX_SHELL_AFTER_REQUESTS } from "./options.ts"
|
|
2
|
+
|
|
3
|
+
/** Where a canary value went into the request whose shell carried it. */
|
|
4
|
+
export type CanaryCarrier = "cookie" | "authorization" | "header" | "query"
|
|
5
|
+
|
|
6
|
+
export interface ShellCanaryFinding {
|
|
7
|
+
readonly path: string
|
|
8
|
+
readonly via: CanaryCarrier
|
|
9
|
+
/** The shared hole whose frame carried it; absent when the shell did. */
|
|
10
|
+
readonly hole?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ShellCanaryOptions {
|
|
14
|
+
/** The origin the requests are made against. Default `http://canary.invalid`. */
|
|
15
|
+
readonly origin?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const CARRIERS: readonly CanaryCarrier[] = ["cookie", "authorization", "header", "query"]
|
|
19
|
+
|
|
20
|
+
/** Where a streamed document's per-response part starts: everything before it is the shared shell. */
|
|
21
|
+
const REQUEST_SCRIPT = '<script type="application/json" id="nifra-ppr-req">'
|
|
22
|
+
const SCRIPT_END = "</script>"
|
|
23
|
+
|
|
24
|
+
/** Requests per path: enough for a shell to be built, then for each shared hole's entry. */
|
|
25
|
+
const MAX_ATTEMPTS = 2 * MAX_SHELL_AFTER_REQUESTS
|
|
26
|
+
|
|
27
|
+
/** The JSON text of the inert script element opened by `opener`, or `undefined` without one. */
|
|
28
|
+
const scriptText = (html: string, opener: string): string | undefined => {
|
|
29
|
+
const start = html.indexOf(opener)
|
|
30
|
+
if (start === -1) return undefined
|
|
31
|
+
const end = html.indexOf(SCRIPT_END, start + opener.length)
|
|
32
|
+
return end === -1 ? undefined : html.slice(start + opener.length, end)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const parsed = (text: string | undefined): unknown => {
|
|
36
|
+
if (text === undefined) return undefined
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(text)
|
|
39
|
+
} catch {
|
|
40
|
+
return undefined
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The shared holes' frames a response carries, by name, when every one carries its entry. */
|
|
45
|
+
function sharedFrames(html: string): ReadonlyMap<string, string> | undefined {
|
|
46
|
+
const request = parsed(scriptText(html, REQUEST_SCRIPT))
|
|
47
|
+
const names =
|
|
48
|
+
typeof request === "object" && request !== null ? Reflect.get(request, "shared") : undefined
|
|
49
|
+
const frames = new Map<string, string>()
|
|
50
|
+
if (!Array.isArray(names)) return frames
|
|
51
|
+
for (const name of names) {
|
|
52
|
+
if (typeof name !== "string") return undefined
|
|
53
|
+
const text = scriptText(html, `<script type="application/json" id="nifra-ppr:${name}">`)
|
|
54
|
+
const frame = parsed(text)
|
|
55
|
+
// An error frame holds nothing built: the entry is not there yet.
|
|
56
|
+
if (text === undefined || typeof frame !== "object" || frame === null) return undefined
|
|
57
|
+
if (Reflect.get(frame, "error") === true) return undefined
|
|
58
|
+
frames.set(name, text)
|
|
59
|
+
}
|
|
60
|
+
return frames
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const randomHex = (): string =>
|
|
64
|
+
Array.from(crypto.getRandomValues(new Uint8Array(12)), (byte) =>
|
|
65
|
+
byte.toString(16).padStart(2, "0"),
|
|
66
|
+
).join("")
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The canary check. Each path is requested through the app's own middleware and gates with a
|
|
70
|
+
* unique value in a cookie, an `Authorization` header, a custom header and the query, until a
|
|
71
|
+
* response carries a shell (`shellAfterRequests` decides which request builds it) and a built frame
|
|
72
|
+
* for each shared hole, and those are scanned for every value the path's requests carried. It
|
|
73
|
+
* catches visitor data the app keeps in module state, which no import check sees. Run it against a
|
|
74
|
+
* fresh app before it serves traffic: a shell or frame already cached was built without the canaries.
|
|
75
|
+
*/
|
|
76
|
+
export async function checkShellCanaries(
|
|
77
|
+
app: { fetch(request: Request): Response | Promise<Response> },
|
|
78
|
+
paths: readonly string[],
|
|
79
|
+
options: ShellCanaryOptions = {},
|
|
80
|
+
): Promise<readonly ShellCanaryFinding[]> {
|
|
81
|
+
const findings: ShellCanaryFinding[] = []
|
|
82
|
+
for (const path of paths) {
|
|
83
|
+
const sent: Array<Record<CanaryCarrier, string>> = []
|
|
84
|
+
// A route that renders whole every time reports nothing.
|
|
85
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
86
|
+
const values: Record<CanaryCarrier, string> = {
|
|
87
|
+
cookie: `nifra-canary-${randomHex()}`,
|
|
88
|
+
authorization: `nifra-canary-${randomHex()}`,
|
|
89
|
+
header: `nifra-canary-${randomHex()}`,
|
|
90
|
+
query: `nifra-canary-${randomHex()}`,
|
|
91
|
+
}
|
|
92
|
+
sent.push(values)
|
|
93
|
+
const url = new URL(path, options.origin ?? "http://canary.invalid")
|
|
94
|
+
url.searchParams.set("nifra_canary", values.query)
|
|
95
|
+
const response = await app.fetch(
|
|
96
|
+
new Request(url.href, {
|
|
97
|
+
headers: {
|
|
98
|
+
cookie: `nifra_canary=${values.cookie}`,
|
|
99
|
+
authorization: `Bearer ${values.authorization}`,
|
|
100
|
+
"x-nifra-canary": values.header,
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
const html = await response.text()
|
|
105
|
+
const end = html.indexOf(REQUEST_SCRIPT)
|
|
106
|
+
// No shared shell yet: this request rendered whole, so nothing it saw is shared.
|
|
107
|
+
if (end === -1) continue
|
|
108
|
+
const frames = sharedFrames(html)
|
|
109
|
+
// A shared hole whose entry is still being built: a later request gets it.
|
|
110
|
+
if (frames === undefined && attempt < MAX_ATTEMPTS - 1) continue
|
|
111
|
+
const shell = html.slice(0, end)
|
|
112
|
+
const carried = (text: string, via: CanaryCarrier): boolean =>
|
|
113
|
+
sent.some((earlier) => text.includes(earlier[via]))
|
|
114
|
+
for (const via of CARRIERS) if (carried(shell, via)) findings.push({ path, via })
|
|
115
|
+
for (const [hole, frame] of frames ?? []) {
|
|
116
|
+
for (const via of CARRIERS) if (carried(frame, via)) findings.push({ path, via, hole })
|
|
117
|
+
}
|
|
118
|
+
break
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return findings
|
|
122
|
+
}
|