@nifrajs/web 4.0.2 → 5.1.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/README.md +19 -0
- package/dist/build-vite.d.ts +19 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +31 -10
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +8 -10
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +90 -31
- package/dist/build.js.map +1 -1
- package/dist/cdn.d.ts +8 -0
- package/dist/cdn.d.ts.map +1 -1
- package/dist/cdn.js +14 -15
- package/dist/cdn.js.map +1 -1
- package/dist/client.d.ts +6 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +46 -18
- package/dist/client.js.map +1 -1
- package/dist/deferred.d.ts +2 -2
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +28 -7
- package/dist/deferred.js.map +1 -1
- package/dist/dev-feed.d.ts.map +1 -1
- package/dist/dev-feed.js +24 -5
- package/dist/dev-feed.js.map +1 -1
- package/dist/dev-port.d.ts +3 -2
- package/dist/dev-port.d.ts.map +1 -1
- package/dist/dev-port.js +2 -6
- package/dist/dev-port.js.map +1 -1
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +4 -14
- package/dist/dev-session.js.map +1 -1
- package/dist/dev.d.ts +7 -17
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +13 -1
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +8 -5
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +30 -13
- package/dist/draft.js.map +1 -1
- package/dist/fonts-google.d.ts.map +1 -1
- package/dist/fonts-google.js +7 -0
- package/dist/fonts-google.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/codegen.d.ts +1 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +56 -29
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/disposable-dir.d.ts +8 -0
- package/dist/internal/disposable-dir.d.ts.map +1 -0
- package/dist/internal/disposable-dir.js +25 -0
- package/dist/internal/disposable-dir.js.map +1 -0
- package/dist/internal/entry-chunks.d.ts +9 -0
- package/dist/internal/entry-chunks.d.ts.map +1 -0
- package/dist/internal/entry-chunks.js +15 -0
- package/dist/internal/entry-chunks.js.map +1 -0
- package/dist/internal/evidence-traversal.d.ts +12 -0
- package/dist/internal/evidence-traversal.d.ts.map +1 -0
- package/dist/internal/evidence-traversal.js +48 -0
- package/dist/internal/evidence-traversal.js.map +1 -0
- package/dist/internal/head-merge.d.ts +6 -0
- package/dist/internal/head-merge.d.ts.map +1 -1
- package/dist/internal/head-merge.js +9 -0
- package/dist/internal/head-merge.js.map +1 -1
- package/dist/internal/js-string.d.ts +2 -0
- package/dist/internal/js-string.d.ts.map +1 -1
- package/dist/internal/js-string.js +5 -1
- package/dist/internal/js-string.js.map +1 -1
- package/dist/internal/mount-shadow.js +1 -1
- package/dist/internal/mount-shadow.js.map +1 -1
- package/dist/internal/output-guard.d.ts +7 -0
- package/dist/internal/output-guard.d.ts.map +1 -1
- package/dist/internal/output-guard.js +80 -8
- package/dist/internal/output-guard.js.map +1 -1
- package/dist/internal/package-entries.d.ts +34 -0
- package/dist/internal/package-entries.d.ts.map +1 -0
- package/dist/internal/package-entries.js +324 -0
- package/dist/internal/package-entries.js.map +1 -0
- package/dist/internal/page-execution.d.ts +6 -0
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +437 -114
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +15 -2
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +8 -0
- package/dist/internal/private-env.d.ts.map +1 -1
- package/dist/internal/private-env.js +25 -10
- package/dist/internal/private-env.js.map +1 -1
- package/dist/internal/progressive-cookies.d.ts +14 -0
- package/dist/internal/progressive-cookies.d.ts.map +1 -0
- package/dist/internal/progressive-cookies.js +43 -0
- package/dist/internal/progressive-cookies.js.map +1 -0
- package/dist/internal/public-env.d.ts +2 -0
- package/dist/internal/public-env.d.ts.map +1 -0
- package/dist/internal/public-env.js +13 -0
- package/dist/internal/public-env.js.map +1 -0
- package/dist/internal/render-document.d.ts +5 -44
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +51 -63
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/response-controls.d.ts +2 -0
- package/dist/internal/response-controls.d.ts.map +1 -1
- package/dist/internal/response-controls.js +26 -16
- package/dist/internal/response-controls.js.map +1 -1
- package/dist/internal/revive-dates.d.ts +16 -0
- package/dist/internal/revive-dates.d.ts.map +1 -0
- package/dist/internal/revive-dates.js +32 -0
- package/dist/internal/revive-dates.js.map +1 -0
- package/dist/internal/route-path.d.ts +14 -0
- package/dist/internal/route-path.d.ts.map +1 -0
- package/dist/internal/route-path.js +19 -0
- package/dist/internal/route-path.js.map +1 -0
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +190 -20
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +33 -5
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/web-app.d.ts +25 -18
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +81 -24
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/wire-date.d.ts +3 -0
- package/dist/internal/wire-date.d.ts.map +1 -0
- package/dist/internal/wire-date.js +14 -0
- package/dist/internal/wire-date.js.map +1 -0
- package/dist/internal/without-query.d.ts +4 -0
- package/dist/internal/without-query.d.ts.map +1 -0
- package/dist/internal/without-query.js +9 -0
- package/dist/internal/without-query.js.map +1 -0
- package/dist/internal/zone-graph.d.ts +4 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +71 -20
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +59 -3
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +311 -41
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +2 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +29 -3
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +37 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +4 -0
- package/dist/manifest.js.map +1 -1
- package/dist/open-resolved.d.ts +18 -0
- package/dist/open-resolved.d.ts.map +1 -0
- package/dist/open-resolved.js +62 -0
- package/dist/open-resolved.js.map +1 -0
- package/dist/plugins/kit.d.ts +1 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +26 -11
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +9 -1
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +84 -7
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
- package/dist/plugins/vite-zone-guard.js +17 -21
- package/dist/plugins/vite-zone-guard.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +44 -3
- package/dist/plugins/zone-guard.js.map +1 -1
- package/dist/prerender.d.ts +5 -1
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +41 -19
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +8 -1
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +17 -6
- package/dist/public-dir.js.map +1 -1
- package/dist/query.d.ts +13 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +89 -26
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +25 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +1 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-types.d.ts.map +1 -1
- package/dist/route-types.js +20 -9
- package/dist/route-types.js.map +1 -1
- package/dist/router.d.ts +9 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -50
- package/dist/router.js.map +1 -1
- package/dist/seo.d.ts +49 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +48 -0
- package/dist/seo.js.map +1 -0
- package/dist/server-components/build-info.d.ts +3 -0
- package/dist/server-components/build-info.d.ts.map +1 -0
- package/dist/server-components/build-info.js +7 -0
- package/dist/server-components/build-info.js.map +1 -0
- package/dist/server-components/build-target.d.ts +12 -0
- package/dist/server-components/build-target.d.ts.map +1 -0
- package/dist/server-components/build-target.js +153 -0
- package/dist/server-components/build-target.js.map +1 -0
- package/dist/server-components/build.d.ts +24 -0
- package/dist/server-components/build.d.ts.map +1 -0
- package/dist/server-components/build.js +366 -0
- package/dist/server-components/build.js.map +1 -0
- package/dist/server-components/bundle-proof.d.ts +3 -0
- package/dist/server-components/bundle-proof.d.ts.map +1 -0
- package/dist/server-components/bundle-proof.js +23 -0
- package/dist/server-components/bundle-proof.js.map +1 -0
- package/dist/server-components/client.d.ts +46 -0
- package/dist/server-components/client.d.ts.map +1 -0
- package/dist/server-components/client.js +594 -0
- package/dist/server-components/client.js.map +1 -0
- package/dist/server-components/definition.d.ts +11 -0
- package/dist/server-components/definition.d.ts.map +1 -0
- package/dist/server-components/definition.js +94 -0
- package/dist/server-components/definition.js.map +1 -0
- package/dist/server-components/dev-http.d.ts +5 -0
- package/dist/server-components/dev-http.d.ts.map +1 -0
- package/dist/server-components/dev-http.js +71 -0
- package/dist/server-components/dev-http.js.map +1 -0
- package/dist/server-components/error.d.ts +9 -0
- package/dist/server-components/error.d.ts.map +1 -0
- package/dist/server-components/error.js +24 -0
- package/dist/server-components/error.js.map +1 -0
- package/dist/server-components/frames.d.ts +7 -0
- package/dist/server-components/frames.d.ts.map +1 -0
- package/dist/server-components/frames.js +169 -0
- package/dist/server-components/frames.js.map +1 -0
- package/dist/server-components/html-policy.d.ts +6 -0
- package/dist/server-components/html-policy.d.ts.map +1 -0
- package/dist/server-components/html-policy.js +75 -0
- package/dist/server-components/html-policy.js.map +1 -0
- package/dist/server-components/html.d.ts +4 -0
- package/dist/server-components/html.d.ts.map +1 -0
- package/dist/server-components/html.js +67 -0
- package/dist/server-components/html.js.map +1 -0
- package/dist/server-components/islands.d.ts +44 -0
- package/dist/server-components/islands.d.ts.map +1 -0
- package/dist/server-components/islands.js +42 -0
- package/dist/server-components/islands.js.map +1 -0
- package/dist/server-components/native.d.ts +4 -0
- package/dist/server-components/native.d.ts.map +1 -0
- package/dist/server-components/native.js +81 -0
- package/dist/server-components/native.js.map +1 -0
- package/dist/server-components/portal.d.ts +6 -0
- package/dist/server-components/portal.d.ts.map +1 -0
- package/dist/server-components/portal.js +35 -0
- package/dist/server-components/portal.js.map +1 -0
- package/dist/server-components/props.d.ts +3 -0
- package/dist/server-components/props.d.ts.map +1 -0
- package/dist/server-components/props.js +14 -0
- package/dist/server-components/props.js.map +1 -0
- package/dist/server-components/protocol.d.ts +44 -0
- package/dist/server-components/protocol.d.ts.map +1 -0
- package/dist/server-components/protocol.js +139 -0
- package/dist/server-components/protocol.js.map +1 -0
- package/dist/server-components/range.d.ts +24 -0
- package/dist/server-components/range.d.ts.map +1 -0
- package/dist/server-components/range.js +263 -0
- package/dist/server-components/range.js.map +1 -0
- package/dist/server-components/registry.d.ts +6 -0
- package/dist/server-components/registry.d.ts.map +1 -0
- package/dist/server-components/registry.js +30 -0
- package/dist/server-components/registry.js.map +1 -0
- package/dist/server-components/response.d.ts +4 -0
- package/dist/server-components/response.d.ts.map +1 -0
- package/dist/server-components/response.js +32 -0
- package/dist/server-components/response.js.map +1 -0
- package/dist/server-components/router-integration.d.ts +9 -0
- package/dist/server-components/router-integration.d.ts.map +1 -0
- package/dist/server-components/router-integration.js +29 -0
- package/dist/server-components/router-integration.js.map +1 -0
- package/dist/server-components/runtime.d.ts +14 -0
- package/dist/server-components/runtime.d.ts.map +1 -0
- package/dist/server-components/runtime.js +276 -0
- package/dist/server-components/runtime.js.map +1 -0
- package/dist/server-components/scheduler.d.ts +14 -0
- package/dist/server-components/scheduler.d.ts.map +1 -0
- package/dist/server-components/scheduler.js +74 -0
- package/dist/server-components/scheduler.js.map +1 -0
- package/dist/server-components/schema.d.ts +4 -0
- package/dist/server-components/schema.d.ts.map +1 -0
- package/dist/server-components/schema.js +77 -0
- package/dist/server-components/schema.js.map +1 -0
- package/dist/server-components/server-entry.d.ts +12 -0
- package/dist/server-components/server-entry.d.ts.map +1 -0
- package/dist/server-components/server-entry.js +90 -0
- package/dist/server-components/server-entry.js.map +1 -0
- package/dist/server-components/source.d.ts +15 -0
- package/dist/server-components/source.d.ts.map +1 -0
- package/dist/server-components/source.js +269 -0
- package/dist/server-components/source.js.map +1 -0
- package/dist/server-components/state.d.ts +4 -0
- package/dist/server-components/state.d.ts.map +1 -0
- package/dist/server-components/state.js +123 -0
- package/dist/server-components/state.js.map +1 -0
- package/dist/server-components/stream.d.ts +6 -0
- package/dist/server-components/stream.d.ts.map +1 -0
- package/dist/server-components/stream.js +70 -0
- package/dist/server-components/stream.js.map +1 -0
- package/dist/server-components/trusted-html.d.ts +3 -0
- package/dist/server-components/trusted-html.d.ts.map +1 -0
- package/dist/server-components/trusted-html.js +14 -0
- package/dist/server-components/trusted-html.js.map +1 -0
- package/dist/server-components/types.d.ts +107 -0
- package/dist/server-components/types.d.ts.map +1 -0
- package/dist/server-components/types.js +2 -0
- package/dist/server-components/types.js.map +1 -0
- package/dist/server-components/typescript.d.ts +11 -0
- package/dist/server-components/typescript.d.ts.map +1 -0
- package/dist/server-components/typescript.js +28 -0
- package/dist/server-components/typescript.js.map +1 -0
- package/dist/server-components/value.d.ts +5 -0
- package/dist/server-components/value.d.ts.map +1 -0
- package/dist/server-components/value.js +77 -0
- package/dist/server-components/value.js.map +1 -0
- package/dist/server-components/vite.d.ts +12 -0
- package/dist/server-components/vite.d.ts.map +1 -0
- package/dist/server-components/vite.js +197 -0
- package/dist/server-components/vite.js.map +1 -0
- package/dist/server-components.d.ts +5 -0
- package/dist/server-components.d.ts.map +1 -0
- package/dist/server-components.js +4 -0
- package/dist/server-components.js.map +1 -0
- package/dist/service-worker.d.ts +8 -1
- package/dist/service-worker.d.ts.map +1 -1
- package/dist/service-worker.js +20 -6
- package/dist/service-worker.js.map +1 -1
- package/dist/vite.d.ts +12 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +7 -1
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +64 -2
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +331 -28
- package/dist/zones.js.map +1 -1
- package/package.json +102 -4
- package/src/build-vite.ts +31 -9
- package/src/build.ts +109 -31
- package/src/cdn.ts +18 -12
- package/src/client.ts +48 -17
- package/src/deferred.ts +27 -9
- package/src/dev-feed.ts +24 -5
- package/src/dev-port.ts +4 -10
- package/src/dev-session.ts +4 -13
- package/src/dev.ts +29 -1
- package/src/draft.ts +35 -14
- package/src/fonts-google.ts +10 -0
- package/src/index.ts +5 -6
- package/src/internal/codegen.ts +60 -29
- package/src/internal/disposable-dir.ts +24 -0
- package/src/internal/entry-chunks.ts +18 -0
- package/src/internal/evidence-traversal.ts +57 -0
- package/src/internal/head-merge.ts +16 -0
- package/src/internal/js-string.ts +6 -1
- package/src/internal/mount-shadow.ts +1 -1
- package/src/internal/output-guard.ts +88 -7
- package/src/internal/package-entries.ts +331 -0
- package/src/internal/page-execution.ts +573 -114
- package/src/internal/parity.ts +15 -2
- package/src/internal/private-env.ts +27 -13
- package/src/internal/progressive-cookies.ts +60 -0
- package/src/internal/public-env.ts +14 -0
- package/src/internal/render-document.ts +66 -89
- package/src/internal/response-controls.ts +31 -14
- package/src/internal/revive-dates.ts +35 -0
- package/src/internal/route-path.ts +24 -0
- package/src/internal/secret-scan.ts +190 -18
- package/src/internal/server-boundary.ts +29 -5
- package/src/internal/web-app.ts +126 -42
- package/src/internal/wire-date.ts +16 -0
- package/src/internal/without-query.ts +8 -0
- package/src/internal/zone-graph.ts +78 -25
- package/src/isr.ts +364 -45
- package/src/llms-txt.ts +32 -3
- package/src/manifest.ts +38 -4
- package/src/open-resolved.ts +64 -0
- package/src/plugins/kit.ts +31 -11
- package/src/plugins/vite-leak-guard.ts +104 -8
- package/src/plugins/vite-zone-guard.ts +21 -19
- package/src/plugins/zone-guard.ts +41 -2
- package/src/prerender.ts +39 -18
- package/src/public-dir.ts +27 -8
- package/src/query.ts +107 -26
- package/src/render-seam.ts +30 -2
- package/src/route-types.ts +34 -7
- package/src/router.ts +66 -59
- package/src/seo.ts +68 -0
- package/src/server-components/build-info.ts +11 -0
- package/src/server-components/build-target.ts +194 -0
- package/src/server-components/build.ts +419 -0
- package/src/server-components/bundle-proof.ts +33 -0
- package/src/server-components/client.ts +663 -0
- package/src/server-components/definition.ts +125 -0
- package/src/server-components/dev-http.ts +75 -0
- package/src/server-components/error.ts +24 -0
- package/src/server-components/frames.ts +181 -0
- package/src/server-components/html-policy.ts +94 -0
- package/src/server-components/html.ts +81 -0
- package/src/server-components/islands.ts +111 -0
- package/src/server-components/native.ts +101 -0
- package/src/server-components/portal.ts +42 -0
- package/src/server-components/props.ts +19 -0
- package/src/server-components/protocol.ts +167 -0
- package/src/server-components/range.ts +313 -0
- package/src/server-components/registry.ts +34 -0
- package/src/server-components/response.ts +44 -0
- package/src/server-components/router-integration.ts +40 -0
- package/src/server-components/runtime.ts +376 -0
- package/src/server-components/scheduler.ts +84 -0
- package/src/server-components/schema.ts +69 -0
- package/src/server-components/server-entry.ts +98 -0
- package/src/server-components/source.ts +324 -0
- package/src/server-components/state.ts +143 -0
- package/src/server-components/stream.ts +71 -0
- package/src/server-components/trusted-html.ts +21 -0
- package/src/server-components/types.ts +138 -0
- package/src/server-components/typescript.ts +39 -0
- package/src/server-components/value.ts +82 -0
- package/src/server-components/vite.ts +242 -0
- package/src/server-components.ts +32 -0
- package/src/service-worker.ts +24 -7
- package/src/vite.ts +8 -1
- package/src/zones.ts +418 -32
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
import { statSync } from "node:fs"
|
|
11
11
|
import { dirname, isAbsolute, relative, resolve } from "node:path"
|
|
12
12
|
import type { BunPlugin } from "bun"
|
|
13
|
+
import { installedManifest } from "../internal/package-entries.ts"
|
|
13
14
|
import { privateEnvDenial } from "../internal/private-env.ts"
|
|
15
|
+
import { withoutQuery } from "../internal/without-query.ts"
|
|
14
16
|
import {
|
|
15
17
|
BACKEND_ONLY_MARKER,
|
|
16
18
|
browserDenial,
|
|
17
19
|
createZoneClassifier,
|
|
20
|
+
specifierPackage,
|
|
18
21
|
type ZoneClassifier,
|
|
19
22
|
} from "../zones.ts"
|
|
20
23
|
|
|
@@ -58,6 +61,40 @@ export interface ZoneGuardOptions {
|
|
|
58
61
|
/** Zones whose code runs in a browser, so may read only public environment variables. */
|
|
59
62
|
const BROWSER_CODE = new Set<string>(["route-frontend", "frontend", "shared"])
|
|
60
63
|
|
|
64
|
+
const loaderFor = (file: string): "ts" | "tsx" | "js" | "jsx" | undefined => {
|
|
65
|
+
const extension = /\.[cm]?([jt]sx?)$/.exec(file)?.[1]
|
|
66
|
+
return extension === "ts" || extension === "tsx" || extension === "js" || extension === "jsx"
|
|
67
|
+
? extension
|
|
68
|
+
: undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Why browser code at `file` may not make one of its package imports: one naming an entry its package
|
|
73
|
+
* declares backend-only, or one its per-entry map leaves out. A path cannot tell two entries that share
|
|
74
|
+
* a file apart, so the dev server judges the specifier here, where it is written.
|
|
75
|
+
*/
|
|
76
|
+
function entryDenial(classifier: ZoneClassifier, file: string, source: string): string | undefined {
|
|
77
|
+
const loader = loaderFor(file)
|
|
78
|
+
if (loader === undefined) return undefined
|
|
79
|
+
let imports: ReadonlyArray<{ readonly path: string }>
|
|
80
|
+
try {
|
|
81
|
+
imports = new Bun.Transpiler({ loader }).scanImports(source)
|
|
82
|
+
} catch {
|
|
83
|
+
// Source Bun cannot read fails its own load with the parse error.
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
for (const { path } of imports) {
|
|
87
|
+
const name = specifierPackage(path)
|
|
88
|
+
const manifest = name === undefined ? undefined : installedManifest(dirname(file), name)
|
|
89
|
+
if (manifest === undefined) continue
|
|
90
|
+
const classification = classifier.classify(manifest, path)
|
|
91
|
+
if (classification.entry === undefined) continue
|
|
92
|
+
const reason = browserDenial(classification, path)
|
|
93
|
+
if (reason !== undefined) return `it imports "${path}": ${reason}`
|
|
94
|
+
}
|
|
95
|
+
return undefined
|
|
96
|
+
}
|
|
97
|
+
|
|
61
98
|
export function zoneGuardPlugin(options: ZoneGuardOptions): BunPlugin {
|
|
62
99
|
const classifier = options.classifier ?? createZoneClassifier(options)
|
|
63
100
|
const display = (file: string): string => {
|
|
@@ -86,14 +123,16 @@ export function zoneGuardPlugin(options: ZoneGuardOptions): BunPlugin {
|
|
|
86
123
|
BROWSER_CODE.has(classification.zone)
|
|
87
124
|
) {
|
|
88
125
|
const source = await Bun.file(args.path).text()
|
|
89
|
-
reason =
|
|
126
|
+
reason =
|
|
127
|
+
privateEnvDenial(args.path, source, options.publicEnvPrefix ?? "PUBLIC_") ??
|
|
128
|
+
entryDenial(classifier, args.path, source)
|
|
90
129
|
}
|
|
91
130
|
return reason === undefined ? undefined : refuse(args.path, reason)
|
|
92
131
|
})
|
|
93
132
|
build.onResolve({ filter: NON_SOURCE_PATH }, (args) => {
|
|
94
133
|
// An entry point (a dev server's HTML page) is the build's own choice, not an import.
|
|
95
134
|
if (args.importer === "") return undefined
|
|
96
|
-
const file = resolve(dirname(args.importer), args.path
|
|
135
|
+
const file = resolve(dirname(args.importer), withoutQuery(args.path))
|
|
97
136
|
// No such file means an extensionless import of a dotted source name (`./date.utils`): Bun resolves
|
|
98
137
|
// it to the source file, which onLoad judges. Judging the bare path would refuse a file never loaded.
|
|
99
138
|
if (!isFile(file)) return undefined
|
package/src/prerender.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* routes are not supported.
|
|
10
10
|
*/
|
|
11
11
|
import { mkdirSync, writeFileSync } from "node:fs"
|
|
12
|
-
import { dirname, resolve, sep } from "node:path"
|
|
12
|
+
import { dirname, join, resolve, sep } from "node:path"
|
|
13
13
|
import { assertNoSecrets, buildEnvironment, type SecretExemption } from "./internal/secret-scan.ts"
|
|
14
14
|
import { fillRoutePattern, type RouteEntry } from "./manifest.ts"
|
|
15
15
|
import { DATA_HEADER, PRERENDERED_GLOBAL, PRERENDERED_LIST_PATH } from "./router.ts"
|
|
@@ -70,10 +70,32 @@ function trimTrailingSlashes(path: string): string {
|
|
|
70
70
|
return path.slice(0, end)
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* One request-path segment as the file name a static host looks up, which is the segment decoded, or
|
|
75
|
+
* `undefined` when no file name maps back to it: malformed percent-encoding, `.` or `..`, or a decoded
|
|
76
|
+
* `/`, `\` or NUL.
|
|
77
|
+
*/
|
|
78
|
+
function fileSegment(segment: string): string | undefined {
|
|
79
|
+
let decoded: string
|
|
80
|
+
try {
|
|
81
|
+
decoded = decodeURIComponent(segment)
|
|
82
|
+
} catch {
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
if (decoded === "." || decoded === ".." || /[/\\\0]/.test(decoded)) return undefined
|
|
86
|
+
return decoded
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Map a route path to its output file: `/` → `index.html`, `/a/b` → `a/b/index.html`. Segments are
|
|
91
|
+
* written decoded (`/caf%C3%A9` → `café/index.html`) because a static host decodes the request path
|
|
92
|
+
* before it looks for a file; a segment with no such file name stays as written.
|
|
93
|
+
*/
|
|
74
94
|
export function htmlFileFor(pattern: string): string {
|
|
75
95
|
const trimmed = trimTrailingSlashes(pattern.replace(/^\/+/, ""))
|
|
76
|
-
|
|
96
|
+
if (trimmed === "") return "index.html"
|
|
97
|
+
const segments = trimmed.split("/").map((segment) => fileSegment(segment) ?? segment)
|
|
98
|
+
return `${segments.join("/")}/index.html`
|
|
77
99
|
}
|
|
78
100
|
|
|
79
101
|
// The handover of a page whose prerendered set is served as a list rather than inlined.
|
|
@@ -119,6 +141,16 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
119
141
|
// static (one path) and dynamic (one path per getStaticPaths entry) routes. A failed render is a skip;
|
|
120
142
|
// a credential in the output fails the whole prerender.
|
|
121
143
|
const renderPath = async (path: string): Promise<void> => {
|
|
144
|
+
// Refused before rendering: a segment no file name maps back to, or a file outside `outDir`.
|
|
145
|
+
const file = htmlFileFor(path)
|
|
146
|
+
const segments = trimTrailingSlashes(path.replace(/^\/+/, "")).split("/")
|
|
147
|
+
const abs = segments.every((s) => s === "" || fileSegment(s) !== undefined)
|
|
148
|
+
? outputPath(outRoot, file)
|
|
149
|
+
: undefined
|
|
150
|
+
if (abs === undefined) {
|
|
151
|
+
skipped.push({ path, reason: "unsafe output path" })
|
|
152
|
+
return
|
|
153
|
+
}
|
|
122
154
|
const res = await options.app.fetch(new Request(`${origin}${path}`))
|
|
123
155
|
if (!res.ok) {
|
|
124
156
|
// A failed render is a per-route skip, not a build failure - surfaced for the caller to log.
|
|
@@ -127,12 +159,6 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
127
159
|
}
|
|
128
160
|
const html = await res.text()
|
|
129
161
|
listUrl ??= PRERENDERED_LIST_URL.exec(html)?.[1]
|
|
130
|
-
const file = htmlFileFor(path)
|
|
131
|
-
const abs = outputPath(outRoot, file)
|
|
132
|
-
if (abs === undefined) {
|
|
133
|
-
skipped.push({ path, reason: "unsafe output path" })
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
162
|
assertClean(file, html)
|
|
137
163
|
mkdirSync(dirname(abs), { recursive: true })
|
|
138
164
|
writeFileSync(abs, html)
|
|
@@ -150,15 +176,10 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
150
176
|
!(dataRes.headers.get("content-type") ?? "").includes("application/x-ndjson")
|
|
151
177
|
) {
|
|
152
178
|
dataFile = dataFileFor(path)
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} else {
|
|
158
|
-
const data = await dataRes.text()
|
|
159
|
-
assertClean(dataFile, data)
|
|
160
|
-
writeFileSync(dataAbs, data)
|
|
161
|
-
}
|
|
179
|
+
const data = await dataRes.text()
|
|
180
|
+
assertClean(dataFile, data)
|
|
181
|
+
// Beside `index.html`, so inside `outDir` whenever it is.
|
|
182
|
+
writeFileSync(join(dirname(abs), "_data.json"), data)
|
|
162
183
|
} else {
|
|
163
184
|
await dataRes.body?.cancel() // not emitting (non-OK or NDJSON) - release the stream
|
|
164
185
|
}
|
package/src/public-dir.ts
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* Distinct from `publicPath` in `build.ts`, which is the URL prefix for content-hashed bundle chunks.
|
|
13
13
|
* The names collide and the concepts do not: `publicPath` never covers user-authored files.
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
16
|
-
import { open, realpath } from "node:fs/promises"
|
|
15
|
+
import { realpath } from "node:fs/promises"
|
|
17
16
|
import { extname, normalize, resolve, sep } from "node:path"
|
|
18
17
|
import { Readable } from "node:stream"
|
|
19
18
|
import { parseByteRange } from "@nifrajs/core/range"
|
|
20
19
|
import { pathnameOf } from "@nifrajs/core/server"
|
|
20
|
+
import { openResolved } from "./open-resolved.ts"
|
|
21
21
|
|
|
22
22
|
/** How long each subtree may be cached. Content-hashed bundle output can be immutable; a
|
|
23
23
|
* user-authored file keeps its name across deploys, so it gets a day and a revalidation. */
|
|
@@ -31,8 +31,15 @@ export interface PublicDirCache {
|
|
|
31
31
|
export interface ServePublicDirOptions {
|
|
32
32
|
/** Absolute path of the directory to serve. */
|
|
33
33
|
readonly dir: string
|
|
34
|
-
/** URL prefix whose files are content-hashed and may be cached immutably (default `"/assets/"`).
|
|
34
|
+
/** URL prefix whose files are content-hashed and may be cached immutably (default `"/assets/"`).
|
|
35
|
+
* Ignored when `hashedFiles` is set. */
|
|
35
36
|
readonly hashedPrefix?: string
|
|
37
|
+
/**
|
|
38
|
+
* Encoded URL paths of the content-hashed files, such as a build manifest's `assets`. When set, only
|
|
39
|
+
* these are cached immutably and every other file gets the `assets` policy, so a user-authored file
|
|
40
|
+
* that happens to sit under the hashed prefix still revalidates.
|
|
41
|
+
*/
|
|
42
|
+
readonly hashedFiles?: ReadonlySet<string>
|
|
36
43
|
readonly cache?: PublicDirCache
|
|
37
44
|
/** Optional encoded URL-path allowlist. When present, route misses avoid a filesystem probe. */
|
|
38
45
|
readonly files?: ReadonlySet<string>
|
|
@@ -166,6 +173,7 @@ export function servePublicDir(
|
|
|
166
173
|
const root = resolve(options.dir)
|
|
167
174
|
const rootReal = realpath(root).catch(() => undefined)
|
|
168
175
|
const hashedPrefix = options.hashedPrefix ?? "/assets/"
|
|
176
|
+
const hashedFiles = options.hashedFiles
|
|
169
177
|
const hashed = options.cache?.hashed ?? IMMUTABLE
|
|
170
178
|
const assets = options.cache?.assets ?? ONE_DAY
|
|
171
179
|
|
|
@@ -192,16 +200,27 @@ export function servePublicDir(
|
|
|
192
200
|
}
|
|
193
201
|
// Open the canonical path once and stream from that descriptor. Reopening the pathname after
|
|
194
202
|
// realpath containment checks would let a writable public tree swap a symlink between validation
|
|
195
|
-
// and the actual read.
|
|
196
|
-
//
|
|
197
|
-
let handle: Awaited<ReturnType<typeof
|
|
203
|
+
// and the actual read. openResolved also refuses the name once any component of it became a
|
|
204
|
+
// symlink; the descriptor then pins the bytes for the lifetime of the response.
|
|
205
|
+
let handle: Awaited<ReturnType<typeof openResolved>> | undefined
|
|
198
206
|
try {
|
|
199
|
-
|
|
207
|
+
try {
|
|
208
|
+
handle = await openResolved(resolvedFile)
|
|
209
|
+
} catch {
|
|
210
|
+
// A name refused or gone since realpath falls through, as a failed realpath does.
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
200
213
|
const stat = await handle.stat()
|
|
201
214
|
if (!stat.isFile()) return undefined
|
|
202
215
|
const size = stat.size
|
|
203
216
|
const headers = new Headers({
|
|
204
|
-
"cache-control":
|
|
217
|
+
"cache-control": (
|
|
218
|
+
hashedFiles === undefined
|
|
219
|
+
? pathname.startsWith(hashedPrefix)
|
|
220
|
+
: hashedFiles.has(pathname)
|
|
221
|
+
)
|
|
222
|
+
? hashed
|
|
223
|
+
: assets,
|
|
205
224
|
// Advertised unconditionally: a client that never sees `accept-ranges` will not attempt a seek,
|
|
206
225
|
// so a video or audio file under `public/` is scrubbable only once this header is present.
|
|
207
226
|
"accept-ranges": "bytes",
|
package/src/query.ts
CHANGED
|
@@ -111,6 +111,12 @@ export interface DehydratedState {
|
|
|
111
111
|
readonly data: unknown
|
|
112
112
|
readonly updatedAt: number
|
|
113
113
|
}>
|
|
114
|
+
/** The successful infinite queries, pages and page params. */
|
|
115
|
+
readonly infiniteQueries?: ReadonlyArray<{
|
|
116
|
+
readonly key: unknown
|
|
117
|
+
readonly data: InfiniteData<unknown, unknown>
|
|
118
|
+
readonly updatedAt: number
|
|
119
|
+
}>
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
/** The keyed query cache. One per app (a binding registers it like the router). */
|
|
@@ -125,8 +131,9 @@ export interface QueryClient {
|
|
|
125
131
|
fn: (pageParam: P) => Promise<T>,
|
|
126
132
|
options: InfiniteQueryOptions<T, P>,
|
|
127
133
|
) => InfiniteQueryHandle<T, P>
|
|
128
|
-
/** Mark matching cached queries stale and refetch the **mounted** ones
|
|
129
|
-
* keys match by **prefix** (`["todo"]` ⇒ every `["todo", …]`); other keys
|
|
134
|
+
/** Mark matching cached queries, infinite ones included, stale and refetch the **mounted** ones
|
|
135
|
+
* (subscribers > 0). Array keys match by **prefix** (`["todo"]` ⇒ every `["todo", …]`); other keys
|
|
136
|
+
* match exactly. */
|
|
130
137
|
invalidateQueries: (keyOrPrefix: unknown) => void
|
|
131
138
|
/** Read a cached query's data (`undefined` if absent or not yet successful). Synchronous, no fetch. */
|
|
132
139
|
getQueryData: <T>(key: unknown) => T | undefined
|
|
@@ -218,17 +225,24 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
218
225
|
|
|
219
226
|
// Lazily evict entries whose GC timer has elapsed (no subscribers), then enforce the hard cap by
|
|
220
227
|
// dropping the oldest-inserted unsubscribed entries. Called on every `query()` - no background timer.
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
const sweepEntries = (
|
|
229
|
+
entries: Map<string, { readonly subscribers: number; readonly gcAt: number | undefined }>,
|
|
230
|
+
t: number,
|
|
231
|
+
): void => {
|
|
232
|
+
for (const [h, e] of entries) {
|
|
233
|
+
if (e.subscribers === 0 && e.gcAt !== undefined && t >= e.gcAt) entries.delete(h)
|
|
225
234
|
}
|
|
226
|
-
if (
|
|
227
|
-
for (const [h, e] of
|
|
228
|
-
if (
|
|
229
|
-
if (e.subscribers === 0)
|
|
235
|
+
if (entries.size <= max) return
|
|
236
|
+
for (const [h, e] of entries) {
|
|
237
|
+
if (entries.size <= max) break
|
|
238
|
+
if (e.subscribers === 0) entries.delete(h) // oldest-inserted unsubscribed first (Map order)
|
|
230
239
|
}
|
|
231
240
|
}
|
|
241
|
+
const sweep = (): void => {
|
|
242
|
+
const t = now()
|
|
243
|
+
sweepEntries(cache, t)
|
|
244
|
+
sweepEntries(infinite, t)
|
|
245
|
+
}
|
|
232
246
|
|
|
233
247
|
// Build a cache entry + its handle. `fn`/`staleTime` are mutated in place on re-bind (closures change
|
|
234
248
|
// between renders). All the fetch/dedup/generation machinery lives here so query(), prefetchQuery(),
|
|
@@ -348,6 +362,9 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
348
362
|
entry.generation++
|
|
349
363
|
if (entry.subscribers > 0) void entry.handle.refetch().catch(() => {}) // refresh mounted now (best-effort)
|
|
350
364
|
}
|
|
365
|
+
for (const entry of infinite.values()) {
|
|
366
|
+
if (keyMatchesPrefix(entry.key, keyOrPrefix)) entry.invalidate()
|
|
367
|
+
}
|
|
351
368
|
}
|
|
352
369
|
|
|
353
370
|
const getQueryData = <T>(key: unknown): T | undefined => {
|
|
@@ -392,7 +409,17 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
392
409
|
if (entry.state.status === "success")
|
|
393
410
|
queries.push({ key: entry.key, data: entry.state.data, updatedAt: entry.state.updatedAt })
|
|
394
411
|
}
|
|
395
|
-
|
|
412
|
+
const infiniteQueries: Array<{
|
|
413
|
+
key: unknown
|
|
414
|
+
data: InfiniteData<unknown, unknown>
|
|
415
|
+
updatedAt: number
|
|
416
|
+
}> = []
|
|
417
|
+
for (const entry of infinite.values()) {
|
|
418
|
+
const { status, data, updatedAt } = entry.handle.snapshot()
|
|
419
|
+
if (status === "success" && data !== undefined)
|
|
420
|
+
infiniteQueries.push({ key: entry.key, data, updatedAt })
|
|
421
|
+
}
|
|
422
|
+
return { queries, infiniteQueries }
|
|
396
423
|
}
|
|
397
424
|
|
|
398
425
|
const hydrate = (state: DehydratedState): void => {
|
|
@@ -409,6 +436,14 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
409
436
|
if (entry.state.status !== "success" || q.updatedAt > entry.state.updatedAt)
|
|
410
437
|
entry.setState(successState(q.data, q.updatedAt))
|
|
411
438
|
}
|
|
439
|
+
// An infinite entry needs its page options before it can exist, so a snapshot for a key not yet
|
|
440
|
+
// queried waits here until `infiniteQuery` creates it.
|
|
441
|
+
for (const q of state.infiniteQueries ?? []) {
|
|
442
|
+
const h = hashQueryKey(q.key)
|
|
443
|
+
const existing = infinite.get(h)
|
|
444
|
+
if (existing === undefined) seeded.set(h, q)
|
|
445
|
+
else existing.seed(q.data, q.updatedAt)
|
|
446
|
+
}
|
|
412
447
|
}
|
|
413
448
|
|
|
414
449
|
// --- Infinite (paged) queries -----------------------------------------------------------------
|
|
@@ -427,19 +462,33 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
427
462
|
)
|
|
428
463
|
return existing.handle as InfiniteQueryHandle<T, P>
|
|
429
464
|
}
|
|
430
|
-
const created = makeInfinite<T, P>(fn, opts)
|
|
465
|
+
const created = makeInfinite<T, P>(key, fn, opts)
|
|
431
466
|
infinite.set(h, created as InfiniteEntry<unknown, unknown>)
|
|
467
|
+
const snapshot = seeded.get(h)
|
|
468
|
+
if (snapshot !== undefined) {
|
|
469
|
+
seeded.delete(h)
|
|
470
|
+
created.seed(snapshot.data, snapshot.updatedAt)
|
|
471
|
+
}
|
|
432
472
|
return created.handle
|
|
433
473
|
}
|
|
434
474
|
|
|
435
475
|
// Infinite entries live in their own map (their data shape + fetch model differ from plain queries).
|
|
436
476
|
interface InfiniteEntry<T, P> {
|
|
477
|
+
readonly key: unknown
|
|
437
478
|
handle: InfiniteQueryHandle<T, P>
|
|
438
479
|
rebind: (fn: (p: P) => Promise<T>, opts: InfiniteQueryOptions<T, P>) => void
|
|
480
|
+
/** Mark stale, drop an in-flight fetch's result, and refetch when mounted. */
|
|
481
|
+
invalidate: () => void
|
|
482
|
+
/** Take a dehydrated snapshot unless the entry already holds fresher data. */
|
|
483
|
+
seed: (data: InfiniteData<unknown, unknown>, updatedAt: number) => void
|
|
484
|
+
subscribers: number
|
|
485
|
+
gcAt: number | undefined
|
|
439
486
|
}
|
|
440
487
|
const infinite = new Map<string, InfiniteEntry<unknown, unknown>>()
|
|
488
|
+
const seeded = new Map<string, NonNullable<DehydratedState["infiniteQueries"]>[number]>()
|
|
441
489
|
|
|
442
490
|
const makeInfinite = <T, P>(
|
|
491
|
+
key: unknown,
|
|
443
492
|
fetchPage: (pageParam: P) => Promise<T>,
|
|
444
493
|
initialOpts: InfiniteQueryOptions<T, P>,
|
|
445
494
|
): InfiniteEntry<T, P> => {
|
|
@@ -447,6 +496,7 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
447
496
|
let opts = initialOpts
|
|
448
497
|
let state: QueryState<InfiniteData<T, P>> = PENDING as QueryState<InfiniteData<T, P>>
|
|
449
498
|
let generation = 0
|
|
499
|
+
let invalidated = false
|
|
450
500
|
let promise: Promise<InfiniteData<T, P>> | undefined
|
|
451
501
|
const listeners = new Set<() => void>()
|
|
452
502
|
const emit = (): void => {
|
|
@@ -475,7 +525,8 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
475
525
|
data.pageParams,
|
|
476
526
|
)
|
|
477
527
|
}
|
|
478
|
-
const isStale = (): boolean =>
|
|
528
|
+
const isStale = (): boolean =>
|
|
529
|
+
invalidated || now() - state.updatedAt >= (opts.staleTime ?? defaultStaleTime)
|
|
479
530
|
|
|
480
531
|
// Run a paged operation under a fresh generation, guarding against a superseding op (like `run`).
|
|
481
532
|
const runPaged = (build: () => Promise<InfiniteData<T, P>>): Promise<InfiniteData<T, P>> => {
|
|
@@ -486,6 +537,7 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
486
537
|
try {
|
|
487
538
|
const data = await build()
|
|
488
539
|
if (gen !== generation) return data
|
|
540
|
+
invalidated = false
|
|
489
541
|
setState(successState(data, now()))
|
|
490
542
|
return data
|
|
491
543
|
} catch (error) {
|
|
@@ -511,9 +563,13 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
511
563
|
const handle: InfiniteQueryHandle<T, P> = {
|
|
512
564
|
snapshot: () => state,
|
|
513
565
|
subscribe: (listener) => {
|
|
566
|
+
entry.subscribers++
|
|
567
|
+
entry.gcAt = undefined
|
|
514
568
|
listeners.add(listener)
|
|
515
569
|
return () => {
|
|
516
570
|
listeners.delete(listener)
|
|
571
|
+
entry.subscribers--
|
|
572
|
+
if (entry.subscribers === 0) entry.gcAt = now() + gcTime
|
|
517
573
|
}
|
|
518
574
|
},
|
|
519
575
|
fetch: () => fetchFirst(false),
|
|
@@ -558,13 +614,30 @@ export function createQueryClient(options: QueryClientOptions): QueryClient {
|
|
|
558
614
|
return p !== undefined && p !== null
|
|
559
615
|
},
|
|
560
616
|
}
|
|
561
|
-
|
|
562
|
-
|
|
617
|
+
const entry: InfiniteEntry<T, P> = {
|
|
618
|
+
key,
|
|
619
|
+
handle,
|
|
563
620
|
rebind: (nextFn, nextOpts) => {
|
|
564
621
|
fn = nextFn
|
|
565
622
|
opts = nextOpts
|
|
566
623
|
},
|
|
567
|
-
|
|
624
|
+
invalidate: () => {
|
|
625
|
+
invalidated = true
|
|
626
|
+
// A fetch begun before this point read pre-mutation data: it may not publish, and a new fetch
|
|
627
|
+
// must not join it.
|
|
628
|
+
generation++
|
|
629
|
+
promise = undefined
|
|
630
|
+
if (entry.subscribers > 0) void handle.refetch().catch(() => {})
|
|
631
|
+
},
|
|
632
|
+
seed: (data, updatedAt) => {
|
|
633
|
+
if (state.status === "success" && state.updatedAt >= updatedAt) return
|
|
634
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: the snapshot was dehydrated from an entry under this key, whose pages and params are T and P
|
|
635
|
+
setState(successState(data as InfiniteData<T, P>, updatedAt))
|
|
636
|
+
},
|
|
637
|
+
subscribers: 0,
|
|
638
|
+
gcAt: undefined,
|
|
639
|
+
}
|
|
640
|
+
return entry
|
|
568
641
|
}
|
|
569
642
|
|
|
570
643
|
return {
|
|
@@ -609,8 +682,10 @@ export interface MutationCallbacks<TData, TVariables> {
|
|
|
609
682
|
export interface MutationHandle<TData, TVariables> {
|
|
610
683
|
snapshot: () => MutationState<TData, TVariables>
|
|
611
684
|
subscribe: (listener: () => void) => () => void
|
|
612
|
-
/** Run the mutation. Resolves to the data (and runs onSuccess/onSettled) or rejects (onError/onSettled)
|
|
613
|
-
*
|
|
685
|
+
/** Run the mutation. Resolves to the data (and runs onSuccess/onSettled) or rejects (onError/onSettled);
|
|
686
|
+
* a throwing `onMutate` fails it the same way. A throwing `onSuccess` or `onSettled` after the write
|
|
687
|
+
* rejects the call but leaves the state `success` and never calls `onError`. The latest concurrent
|
|
688
|
+
* call wins the published state (older results are dropped). */
|
|
614
689
|
mutate: (variables: TVariables) => Promise<TData>
|
|
615
690
|
/** Rebind the latest `fn`/callbacks (closures change between renders) without losing state. */
|
|
616
691
|
rebind: (
|
|
@@ -667,21 +742,27 @@ export function createMutation<TData, TVariables>(
|
|
|
667
742
|
mutate: async (variables) => {
|
|
668
743
|
const mine = ++latest
|
|
669
744
|
setState({ status: "pending", data: undefined, error: undefined, variables })
|
|
670
|
-
|
|
671
|
-
// needless microtask before the mutationFn runs (and complicates single-flight timing).
|
|
672
|
-
if (cbs.onMutate !== undefined) await cbs.onMutate(variables)
|
|
745
|
+
let data: TData
|
|
673
746
|
try {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (cbs.
|
|
677
|
-
|
|
678
|
-
return data
|
|
747
|
+
// Only await a callback when one is actually supplied - an `await undefined` would insert a
|
|
748
|
+
// needless microtask before the mutationFn runs (and complicates single-flight timing).
|
|
749
|
+
if (cbs.onMutate !== undefined) await cbs.onMutate(variables)
|
|
750
|
+
data = await mutationFn(variables)
|
|
679
751
|
} catch (error) {
|
|
680
752
|
if (mine === latest) setState({ status: "error", data: undefined, error, variables })
|
|
681
753
|
if (cbs.onError !== undefined) await cbs.onError(error, variables)
|
|
682
754
|
if (cbs.onSettled !== undefined) await cbs.onSettled(undefined, error, variables)
|
|
683
755
|
throw error
|
|
684
756
|
}
|
|
757
|
+
if (mine === latest) setState({ status: "success", data, error: undefined, variables })
|
|
758
|
+
// The write happened: a success callback that throws rejects the call, but is no failure of the
|
|
759
|
+
// write, so it never reaches onError (which would roll back what the server kept).
|
|
760
|
+
try {
|
|
761
|
+
if (cbs.onSuccess !== undefined) await cbs.onSuccess(data, variables)
|
|
762
|
+
} finally {
|
|
763
|
+
if (cbs.onSettled !== undefined) await cbs.onSettled(data, undefined, variables)
|
|
764
|
+
}
|
|
765
|
+
return data
|
|
685
766
|
},
|
|
686
767
|
}
|
|
687
768
|
}
|
package/src/render-seam.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { Submission } from "./router.ts"
|
|
|
12
12
|
|
|
13
13
|
/** The data handed to a route component. */
|
|
14
14
|
export interface RenderProps {
|
|
15
|
+
readonly serverComponents?: import("./server-components/types.ts").ComponentState
|
|
15
16
|
readonly data: unknown
|
|
16
17
|
/** Per-layout loader data, aligned index-for-index with the layout prefix of `chain`. */
|
|
17
18
|
readonly layoutData?: readonly unknown[]
|
|
@@ -61,10 +62,24 @@ export interface UIMatch {
|
|
|
61
62
|
readonly handle: unknown
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Markup a render produced for outside the app root, which an adapter that sets
|
|
67
|
+
* {@link RenderAdapter.documentParts} appends to. Each part must be complete before the render
|
|
68
|
+
* settles: `renderToString` resolving, or `renderToStream` returning its stream.
|
|
69
|
+
*/
|
|
70
|
+
export interface DocumentParts {
|
|
71
|
+
/** Placed at the end of `<head>`. Svelte's `<svelte:head>` lands here. */
|
|
72
|
+
head: string
|
|
73
|
+
/** Placed first in `<body>`, before the app root. Vue's `<Teleport to="body">` lands here. */
|
|
74
|
+
body: string
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
/** Per-document options for {@link RenderAdapter.renderToStream}. */
|
|
65
78
|
export interface RenderStreamOptions {
|
|
66
79
|
/** The document's CSP nonce, for every executable `<script>` the framework streams. */
|
|
67
80
|
readonly nonce?: string
|
|
81
|
+
/** Present only for an adapter that sets {@link RenderAdapter.documentParts}. */
|
|
82
|
+
readonly parts?: DocumentParts
|
|
68
83
|
}
|
|
69
84
|
|
|
70
85
|
/**
|
|
@@ -83,10 +98,22 @@ export interface RenderAdapter {
|
|
|
83
98
|
props: RenderProps,
|
|
84
99
|
options?: RenderStreamOptions,
|
|
85
100
|
): ReadableStream<Uint8Array> | Promise<ReadableStream<Uint8Array>>
|
|
86
|
-
/**
|
|
87
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Render the chain to a complete HTML string when no content is deferred. `options.nonce` is the
|
|
103
|
+
* document's CSP nonce, for any inline script the renderer emits (Solid's hydration data).
|
|
104
|
+
*/
|
|
105
|
+
renderToString?(
|
|
106
|
+
chain: readonly unknown[],
|
|
107
|
+
props: RenderProps,
|
|
108
|
+
options?: RenderStreamOptions,
|
|
109
|
+
): string | Promise<string>
|
|
88
110
|
/** Per-document bootstrap markup injected into `<head>` for client hydration. */
|
|
89
111
|
hydrationHead(nonce?: string): string
|
|
112
|
+
/**
|
|
113
|
+
* Set when a render can produce markup that belongs outside the app root. Every render is then
|
|
114
|
+
* passed `options.parts` to fill; an adapter without it never is, and pays nothing for the seam.
|
|
115
|
+
*/
|
|
116
|
+
readonly documentParts?: boolean
|
|
90
117
|
}
|
|
91
118
|
|
|
92
119
|
/** Loads a module through the dev server's module graph rather than the runtime's resolver. */
|
|
@@ -123,5 +150,6 @@ export const ROUTE_GLOBAL = "__NIFRA_ROUTE__"
|
|
|
123
150
|
export const ACTION_GLOBAL = "__NIFRA_ACTION__"
|
|
124
151
|
/** Dynamic-boundary states for hydration; absent when a route declares no boundaries. */
|
|
125
152
|
export const BOUNDARY_GLOBAL = "__NIFRA_BOUNDARIES__"
|
|
153
|
+
export const COMPONENT_GLOBAL = "__NIFRA_COMPONENTS__"
|
|
126
154
|
/** Marker attribute used to find a non-default hydration container. */
|
|
127
155
|
export const ROOT_ATTRIBUTE = "data-nifra-root"
|
package/src/route-types.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "node:fs"
|
|
31
31
|
import { dirname, join, relative } from "node:path"
|
|
32
32
|
import { codeUnitOrder } from "./internal/code-unit-order.ts"
|
|
33
|
+
import { jsStringLiteral } from "./internal/js-string.ts"
|
|
33
34
|
import {
|
|
34
35
|
buildManifest,
|
|
35
36
|
isMiddlewareFile,
|
|
@@ -85,6 +86,8 @@ function routeFiles(manifest: Manifest): readonly RouteFile[] {
|
|
|
85
86
|
const PARAM = /^\[([A-Za-z_][A-Za-z0-9_]*)\]$/
|
|
86
87
|
const OPTIONAL = /^\[\[([A-Za-z_][A-Za-z0-9_]*)\]\]$/
|
|
87
88
|
const CATCH_ALL = /^\[\.\.\.([A-Za-z_][A-Za-z0-9_]*)\]$/
|
|
89
|
+
// A `[name]` inside a part-literal segment such as `post-[id]` or `[lang]-feed.xml`.
|
|
90
|
+
const EMBEDDED_PARAM = /\[([A-Za-z_][A-Za-z0-9_]*)\]/g
|
|
88
91
|
|
|
89
92
|
/** The params a route file's URL carries: its directories', plus its own name's for a page. */
|
|
90
93
|
function paramsType(file: string, kind: Kind): string {
|
|
@@ -96,10 +99,20 @@ function paramsType(file: string, kind: Kind): string {
|
|
|
96
99
|
const optional = OPTIONAL.exec(segment)
|
|
97
100
|
const name = optional?.[1] ?? PARAM.exec(segment)?.[1] ?? CATCH_ALL.exec(segment)?.[1]
|
|
98
101
|
if (name !== undefined) fields.push(`readonly ${name}${optional === null ? "" : "?"}: string`)
|
|
102
|
+
else
|
|
103
|
+
for (const embedded of segment.matchAll(EMBEDDED_PARAM))
|
|
104
|
+
fields.push(`readonly ${embedded[1]}: string`)
|
|
99
105
|
}
|
|
100
106
|
return fields.length === 0 ? "Record<string, never>" : `{ ${fields.join("; ")} }`
|
|
101
107
|
}
|
|
102
108
|
|
|
109
|
+
// A file name may hold a line terminator, which would end the `//` header comment it is quoted in.
|
|
110
|
+
const commentSafe = (text: string): string =>
|
|
111
|
+
text.replace(
|
|
112
|
+
/[\n\r\u2028\u2029]/g,
|
|
113
|
+
(char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`,
|
|
114
|
+
)
|
|
115
|
+
|
|
103
116
|
/** A module specifier for `target` from a file in `fromDir`, without a script extension. */
|
|
104
117
|
function specifier(fromDir: string, target: string): string {
|
|
105
118
|
let path = posix(relative(fromDir, target))
|
|
@@ -121,16 +134,24 @@ function routeTypesModule(
|
|
|
121
134
|
const backend = route.backend === undefined ? undefined : join(routesDir, route.backend)
|
|
122
135
|
// Only a script module's exports are visible to TypeScript; a .svelte/.vue/.mdx one is an ambient shim.
|
|
123
136
|
const frontend = /\.[jt]sx?$/.test(route.file) ? join(routesDir, route.file) : undefined
|
|
124
|
-
const lines = [
|
|
137
|
+
const lines = [
|
|
138
|
+
`// Generated by nifra from ${commentSafe(label)}. Do not edit: \`nifra types\` rewrites it.`,
|
|
139
|
+
]
|
|
125
140
|
if (register !== undefined)
|
|
126
141
|
lines.push(`/// <reference path="${posix(relative(dir, register))}" />`)
|
|
127
|
-
lines.push(
|
|
128
|
-
|
|
142
|
+
lines.push(
|
|
143
|
+
'import type { OutputOf, RouteActionReturn, RouteLoaderArgs, RouteLoaderReturn, SearchSchemaOf } from "@nifrajs/client"',
|
|
144
|
+
)
|
|
145
|
+
lines.push(
|
|
146
|
+
route.kind === "page"
|
|
147
|
+
? 'import type { MetaArgs as WebMetaArgs, RenderProps } from "@nifrajs/web"'
|
|
148
|
+
: 'import type { MetaArgs as WebMetaArgs } from "@nifrajs/web"',
|
|
149
|
+
)
|
|
129
150
|
lines.push("")
|
|
130
151
|
if (backend !== undefined)
|
|
131
|
-
lines.push(`type Backend = typeof import(
|
|
152
|
+
lines.push(`type Backend = typeof import(${jsStringLiteral(specifier(dir, backend))})`)
|
|
132
153
|
if (frontend !== undefined)
|
|
133
|
-
lines.push(`type Frontend = typeof import(
|
|
154
|
+
lines.push(`type Frontend = typeof import(${jsStringLiteral(specifier(dir, frontend))})`)
|
|
134
155
|
if (backend !== undefined || frontend !== undefined) lines.push("")
|
|
135
156
|
const output = (name: string) => (backend === undefined ? "null" : `OutputOf<Backend, "${name}">`)
|
|
136
157
|
lines.push(
|
|
@@ -145,6 +166,12 @@ function routeTypesModule(
|
|
|
145
166
|
` export type LoaderArgs = RouteLoaderArgs<Params${frontend === undefined ? "" : ", SearchSchemaOf<Frontend>"}>`,
|
|
146
167
|
" /** The context the backend half's action receives. */",
|
|
147
168
|
" export type ActionArgs = LoaderArgs",
|
|
169
|
+
" /** The backend half's loader. Annotate it, `export const loader: Route.Loader = ...`, and what it returns is checked against `loaderOutput`. */",
|
|
170
|
+
` export type Loader = (args: LoaderArgs) => RouteLoaderReturn<${backend === undefined ? "null" : "Backend"}>`,
|
|
171
|
+
" /** The backend half's action. Annotate it, `export const action: Route.Action = ...`, and what it returns is checked against `actionOutput`. */",
|
|
172
|
+
` export type Action = (args: ActionArgs) => RouteActionReturn<${backend === undefined ? "null" : "Backend"}>`,
|
|
173
|
+
" /** What this module's `meta` function receives: its own loader data as `data`. */",
|
|
174
|
+
" export type MetaArgs = WebMetaArgs<LoaderData>",
|
|
148
175
|
)
|
|
149
176
|
if (route.kind === "page") {
|
|
150
177
|
lines.push(
|
|
@@ -170,8 +197,8 @@ function routeTypesModule(
|
|
|
170
197
|
|
|
171
198
|
function registerModule(out: string, backendApp: string, label: string): string {
|
|
172
199
|
return [
|
|
173
|
-
`// Generated by nifra from ${label}. Do not edit: \`nifra types\` rewrites it.`,
|
|
174
|
-
`import type { backend } from
|
|
200
|
+
`// Generated by nifra from ${commentSafe(label)}. Do not edit: \`nifra types\` rewrites it.`,
|
|
201
|
+
`import type { backend } from ${jsStringLiteral(specifier(dirname(out), backendApp))}`,
|
|
175
202
|
"",
|
|
176
203
|
'declare module "@nifrajs/client" {',
|
|
177
204
|
" interface Register {",
|