@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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { relative } from "node:path"
|
|
3
|
+
import type * as TS from "typescript"
|
|
4
|
+
import { ts } from "./typescript.ts"
|
|
5
|
+
|
|
6
|
+
export interface CompiledComponentModule {
|
|
7
|
+
readonly code: string
|
|
8
|
+
readonly references: Readonly<Record<string, string>>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function componentReference(file: string, root: string, name: string): string {
|
|
12
|
+
return (
|
|
13
|
+
"sc_" +
|
|
14
|
+
createHash("sha256")
|
|
15
|
+
.update(`${relative(root, file).replaceAll("\\", "/")}#${name}`)
|
|
16
|
+
.digest("hex")
|
|
17
|
+
.slice(0, 32)
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function sourceFile(source: string, file: string): TS.SourceFile {
|
|
22
|
+
return ts.createSourceFile(
|
|
23
|
+
file,
|
|
24
|
+
source,
|
|
25
|
+
ts.ScriptTarget.Latest,
|
|
26
|
+
true,
|
|
27
|
+
/\.[jt]sx$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function modifier(node: TS.Node, kind: TS.SyntaxKind): boolean {
|
|
32
|
+
return (
|
|
33
|
+
ts.canHaveModifiers(node) &&
|
|
34
|
+
(ts.getModifiers(node)?.some((item) => item.kind === kind) ?? false)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function literal(node: TS.Expression): unknown {
|
|
39
|
+
if (
|
|
40
|
+
ts.isAsExpression(node) ||
|
|
41
|
+
ts.isSatisfiesExpression(node) ||
|
|
42
|
+
ts.isParenthesizedExpression(node)
|
|
43
|
+
)
|
|
44
|
+
return literal(node.expression)
|
|
45
|
+
if (ts.isStringLiteral(node)) return node.text
|
|
46
|
+
if (!ts.isObjectLiteralExpression(node)) throw new Error("component_pipeline_literal")
|
|
47
|
+
const result: Record<string, unknown> = Object.create(null)
|
|
48
|
+
for (const member of node.properties) {
|
|
49
|
+
if (
|
|
50
|
+
!ts.isPropertyAssignment(member) ||
|
|
51
|
+
!(ts.isIdentifier(member.name) || ts.isStringLiteral(member.name))
|
|
52
|
+
)
|
|
53
|
+
throw new Error("component_pipeline_literal")
|
|
54
|
+
if (
|
|
55
|
+
Object.hasOwn(result, member.name.text) ||
|
|
56
|
+
["__proto__", "prototype", "constructor"].includes(member.name.text)
|
|
57
|
+
)
|
|
58
|
+
throw new Error("component_pipeline_literal")
|
|
59
|
+
result[member.name.text] = literal(member.initializer)
|
|
60
|
+
}
|
|
61
|
+
return result
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The `componentPipeline` a config module exports as a plain literal, read without running the
|
|
65
|
+
* module, or `undefined` when it exports none or one that is not a literal. */
|
|
66
|
+
export function staticComponentPipeline(source: string, file: string): unknown {
|
|
67
|
+
let pipeline: unknown
|
|
68
|
+
for (const statement of sourceFile(source, file).statements) {
|
|
69
|
+
if (!ts.isVariableStatement(statement) || !modifier(statement, ts.SyntaxKind.ExportKeyword))
|
|
70
|
+
continue
|
|
71
|
+
for (const variable of statement.declarationList.declarations) {
|
|
72
|
+
if (
|
|
73
|
+
!ts.isIdentifier(variable.name) ||
|
|
74
|
+
variable.name.text !== "componentPipeline" ||
|
|
75
|
+
variable.initializer === undefined
|
|
76
|
+
)
|
|
77
|
+
continue
|
|
78
|
+
try {
|
|
79
|
+
pipeline = literal(variable.initializer)
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return pipeline
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function directive(file: TS.SourceFile, value: string): boolean {
|
|
89
|
+
let found = false
|
|
90
|
+
const visit = (node: TS.Node): void => {
|
|
91
|
+
if (ts.isSourceFile(node) || ts.isBlock(node)) {
|
|
92
|
+
for (const statement of node.statements) {
|
|
93
|
+
if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) break
|
|
94
|
+
if (statement.expression.text === value) found = true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
ts.forEachChild(node, visit)
|
|
98
|
+
}
|
|
99
|
+
visit(file)
|
|
100
|
+
return found
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function exportedValues(file: TS.SourceFile): readonly string[] {
|
|
104
|
+
const names = new Set<string>()
|
|
105
|
+
for (const statement of file.statements) {
|
|
106
|
+
if (ts.isExportAssignment(statement)) {
|
|
107
|
+
names.add("default")
|
|
108
|
+
continue
|
|
109
|
+
}
|
|
110
|
+
if (ts.isExportDeclaration(statement)) {
|
|
111
|
+
if (statement.isTypeOnly) continue
|
|
112
|
+
if (statement.exportClause === undefined || !ts.isNamedExports(statement.exportClause))
|
|
113
|
+
throw new Error(
|
|
114
|
+
"[nifra/components] Native client modules require explicit, schema-registered component exports",
|
|
115
|
+
)
|
|
116
|
+
for (const element of statement.exportClause.elements)
|
|
117
|
+
if (!element.isTypeOnly) names.add(element.name.text)
|
|
118
|
+
}
|
|
119
|
+
if (!modifier(statement, ts.SyntaxKind.ExportKeyword)) continue
|
|
120
|
+
if (modifier(statement, ts.SyntaxKind.DefaultKeyword)) {
|
|
121
|
+
names.add("default")
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
if (ts.isVariableStatement(statement)) {
|
|
125
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
126
|
+
if (!ts.isIdentifier(declaration.name))
|
|
127
|
+
throw new Error("[nifra/components] Destructured component exports are unsupported")
|
|
128
|
+
names.add(declaration.name.text)
|
|
129
|
+
}
|
|
130
|
+
} else if (
|
|
131
|
+
ts.isFunctionDeclaration(statement) ||
|
|
132
|
+
ts.isClassDeclaration(statement) ||
|
|
133
|
+
ts.isEnumDeclaration(statement)
|
|
134
|
+
) {
|
|
135
|
+
if (statement.name !== undefined) names.add(statement.name.text)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return [...names]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function compileComponentModule(
|
|
142
|
+
source: string,
|
|
143
|
+
file: string,
|
|
144
|
+
root: string,
|
|
145
|
+
environment: string,
|
|
146
|
+
kind: "server" | "island" = "server",
|
|
147
|
+
): CompiledComponentModule {
|
|
148
|
+
const ast = sourceFile(source, file)
|
|
149
|
+
if (directive(ast, "use server"))
|
|
150
|
+
throw new Error(
|
|
151
|
+
"[nifra/components] Native use-server actions are unsupported; use a validated serverFn in a *.fn.ts module",
|
|
152
|
+
)
|
|
153
|
+
const factories = new Set<string>()
|
|
154
|
+
const namespaces = new Set<string>()
|
|
155
|
+
for (const statement of ast.statements) {
|
|
156
|
+
if (
|
|
157
|
+
!ts.isImportDeclaration(statement) ||
|
|
158
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
159
|
+
statement.moduleSpecifier.text !==
|
|
160
|
+
(kind === "server"
|
|
161
|
+
? "@nifrajs/web/server-components"
|
|
162
|
+
: "@nifrajs/web/server-components/islands")
|
|
163
|
+
)
|
|
164
|
+
continue
|
|
165
|
+
const bindings = statement.importClause?.namedBindings
|
|
166
|
+
if (bindings !== undefined && ts.isNamedImports(bindings)) {
|
|
167
|
+
for (const element of bindings.elements)
|
|
168
|
+
if (
|
|
169
|
+
(element.propertyName ?? element.name).text ===
|
|
170
|
+
(kind === "server" ? "defineServerComponent" : "defineClientIsland")
|
|
171
|
+
)
|
|
172
|
+
factories.add(element.name.text)
|
|
173
|
+
} else if (bindings !== undefined && ts.isNamespaceImport(bindings))
|
|
174
|
+
namespaces.add(bindings.name.text)
|
|
175
|
+
}
|
|
176
|
+
const definitions = new Map<string, TS.CallExpression>()
|
|
177
|
+
for (const statement of ast.statements) {
|
|
178
|
+
if (!ts.isVariableStatement(statement)) continue
|
|
179
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
180
|
+
let expression = declaration.initializer
|
|
181
|
+
while (
|
|
182
|
+
expression !== undefined &&
|
|
183
|
+
(ts.isAsExpression(expression) ||
|
|
184
|
+
ts.isSatisfiesExpression(expression) ||
|
|
185
|
+
ts.isParenthesizedExpression(expression))
|
|
186
|
+
)
|
|
187
|
+
expression = expression.expression
|
|
188
|
+
if (
|
|
189
|
+
!ts.isIdentifier(declaration.name) ||
|
|
190
|
+
expression === undefined ||
|
|
191
|
+
!ts.isCallExpression(expression)
|
|
192
|
+
)
|
|
193
|
+
continue
|
|
194
|
+
const callee = expression.expression
|
|
195
|
+
if (
|
|
196
|
+
(ts.isIdentifier(callee) && factories.has(callee.text)) ||
|
|
197
|
+
(ts.isPropertyAccessExpression(callee) &&
|
|
198
|
+
ts.isIdentifier(callee.expression) &&
|
|
199
|
+
namespaces.has(callee.expression.text) &&
|
|
200
|
+
callee.name.text === (kind === "server" ? "defineServerComponent" : "defineClientIsland"))
|
|
201
|
+
) {
|
|
202
|
+
if (expression.arguments.length !== 1)
|
|
203
|
+
throw new Error(
|
|
204
|
+
"[nifra/components] Component references are generated by the compiler; remove the handwritten reference",
|
|
205
|
+
)
|
|
206
|
+
definitions.set(declaration.name.text, expression)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const references: Record<string, string> = Object.create(null)
|
|
211
|
+
for (const statement of ast.statements) {
|
|
212
|
+
if (ts.isVariableStatement(statement) && modifier(statement, ts.SyntaxKind.ExportKeyword)) {
|
|
213
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
214
|
+
if (!ts.isIdentifier(declaration.name) || !definitions.has(declaration.name.text))
|
|
215
|
+
throw new Error(
|
|
216
|
+
"[nifra/components] Only named defineServerComponent handles may be exported from *.component.server modules",
|
|
217
|
+
)
|
|
218
|
+
references[declaration.name.text] = componentReference(file, root, declaration.name.text)
|
|
219
|
+
}
|
|
220
|
+
} else if (ts.isExportDeclaration(statement) && !statement.isTypeOnly) {
|
|
221
|
+
if (
|
|
222
|
+
statement.moduleSpecifier !== undefined ||
|
|
223
|
+
statement.exportClause === undefined ||
|
|
224
|
+
!ts.isNamedExports(statement.exportClause)
|
|
225
|
+
)
|
|
226
|
+
throw new Error("[nifra/components] Re-export component handles through a separate barrel")
|
|
227
|
+
for (const element of statement.exportClause.elements) {
|
|
228
|
+
if (element.isTypeOnly) continue
|
|
229
|
+
const local = (element.propertyName ?? element.name).text
|
|
230
|
+
if (!definitions.has(local))
|
|
231
|
+
throw new Error("[nifra/components] A component module exported an unregistered value")
|
|
232
|
+
references[element.name.text] = componentReference(file, root, local)
|
|
233
|
+
}
|
|
234
|
+
} else if (
|
|
235
|
+
ts.isExportAssignment(statement) ||
|
|
236
|
+
(modifier(statement, ts.SyntaxKind.ExportKeyword) &&
|
|
237
|
+
!ts.isInterfaceDeclaration(statement) &&
|
|
238
|
+
!ts.isTypeAliasDeclaration(statement))
|
|
239
|
+
) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
"[nifra/components] Export named defineServerComponent handles from *.component.server modules",
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (Object.keys(references).length === 0)
|
|
246
|
+
throw new Error("[nifra/components] A component module has no recognized server component")
|
|
247
|
+
if (environment === "client" && kind === "server") {
|
|
248
|
+
return {
|
|
249
|
+
references,
|
|
250
|
+
code: Object.entries(references)
|
|
251
|
+
.map(
|
|
252
|
+
([name, reference]) =>
|
|
253
|
+
`export const ${name} = Object.freeze({ reference: ${JSON.stringify(reference)} });`,
|
|
254
|
+
)
|
|
255
|
+
.join("\n"),
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let code = source
|
|
259
|
+
for (const [name, call] of [...definitions].sort((a, b) => b[1].end - a[1].end)) {
|
|
260
|
+
const argument = call.arguments[0]
|
|
261
|
+
if (argument === undefined)
|
|
262
|
+
throw new Error("[nifra/components] Missing server component definition")
|
|
263
|
+
const at = argument.end
|
|
264
|
+
code =
|
|
265
|
+
code.slice(0, at) +
|
|
266
|
+
", " +
|
|
267
|
+
JSON.stringify(componentReference(file, root, name)) +
|
|
268
|
+
code.slice(at)
|
|
269
|
+
}
|
|
270
|
+
return { code, references }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function guardCreateElementCalls(source: string, file: string): string {
|
|
274
|
+
const ast = sourceFile(source, file)
|
|
275
|
+
const namespaces = new Set<string>()
|
|
276
|
+
const factories = new Set<string>()
|
|
277
|
+
for (const statement of ast.statements) {
|
|
278
|
+
if (
|
|
279
|
+
!ts.isImportDeclaration(statement) ||
|
|
280
|
+
!ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
281
|
+
statement.moduleSpecifier.text !== "react"
|
|
282
|
+
)
|
|
283
|
+
continue
|
|
284
|
+
if (statement.importClause?.name !== undefined) namespaces.add(statement.importClause.name.text)
|
|
285
|
+
const bindings = statement.importClause?.namedBindings
|
|
286
|
+
if (bindings !== undefined && ts.isNamespaceImport(bindings)) namespaces.add(bindings.name.text)
|
|
287
|
+
if (bindings !== undefined && ts.isNamedImports(bindings))
|
|
288
|
+
for (const element of bindings.elements) {
|
|
289
|
+
if ((element.propertyName ?? element.name).text === "createElement")
|
|
290
|
+
factories.add(element.name.text)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const changes: Array<{ start: number; end: number }> = []
|
|
294
|
+
const visit = (node: TS.Node): void => {
|
|
295
|
+
if (ts.isCallExpression(node)) {
|
|
296
|
+
const expression = node.expression
|
|
297
|
+
const named = ts.isIdentifier(expression) && factories.has(expression.text)
|
|
298
|
+
const member =
|
|
299
|
+
ts.isPropertyAccessExpression(expression) &&
|
|
300
|
+
ts.isIdentifier(expression.expression) &&
|
|
301
|
+
namespaces.has(expression.expression.text) &&
|
|
302
|
+
expression.name.text === "createElement"
|
|
303
|
+
const computed =
|
|
304
|
+
ts.isElementAccessExpression(expression) &&
|
|
305
|
+
ts.isIdentifier(expression.expression) &&
|
|
306
|
+
namespaces.has(expression.expression.text) &&
|
|
307
|
+
ts.isStringLiteral(expression.argumentExpression) &&
|
|
308
|
+
expression.argumentExpression.text === "createElement"
|
|
309
|
+
if (named || member || computed)
|
|
310
|
+
changes.push({ start: expression.getStart(ast), end: expression.end })
|
|
311
|
+
}
|
|
312
|
+
ts.forEachChild(node, visit)
|
|
313
|
+
}
|
|
314
|
+
visit(ast)
|
|
315
|
+
if (changes.length === 0) return source
|
|
316
|
+
const identifier = `__nifra_guardedCreateElement_${createHash("sha256").update(file).digest("hex").slice(0, 12)}`
|
|
317
|
+
let code = source
|
|
318
|
+
for (const change of changes.sort((a, b) => b.start - a.start))
|
|
319
|
+
code = code.slice(0, change.start) + identifier + code.slice(change.end)
|
|
320
|
+
return (
|
|
321
|
+
`import { guardedCreateElement as ${identifier} } from "@nifrajs/web-react/server-components/guard";\n` +
|
|
322
|
+
code
|
|
323
|
+
)
|
|
324
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { ServerComponentError } from "./error.ts"
|
|
2
|
+
import { validateComponentHTML } from "./html.ts"
|
|
3
|
+
import { COMPONENT_LIMITS, componentKey } from "./protocol.ts"
|
|
4
|
+
import type { ComponentIsland, ComponentRecord, ComponentState } from "./types.ts"
|
|
5
|
+
import { componentValue } from "./value.ts"
|
|
6
|
+
|
|
7
|
+
const token = /^[A-Za-z0-9_-]{1,128}$/
|
|
8
|
+
function object(
|
|
9
|
+
value: unknown,
|
|
10
|
+
fields: readonly string[],
|
|
11
|
+
optional: readonly string[] = [],
|
|
12
|
+
): Record<string, unknown> {
|
|
13
|
+
if (
|
|
14
|
+
typeof value !== "object" ||
|
|
15
|
+
value === null ||
|
|
16
|
+
Array.isArray(value) ||
|
|
17
|
+
fields.some((field) => !Object.hasOwn(value, field)) ||
|
|
18
|
+
Object.keys(value).some((field) => !fields.includes(field) && !optional.includes(field))
|
|
19
|
+
)
|
|
20
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
21
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
|
|
22
|
+
return value as Record<string, unknown>
|
|
23
|
+
}
|
|
24
|
+
export function componentToken(value: unknown): value is string {
|
|
25
|
+
return typeof value === "string" && token.test(value)
|
|
26
|
+
}
|
|
27
|
+
function island(value: unknown): ComponentIsland {
|
|
28
|
+
const item = object(value, ["reference", "key", "props", "renderId"], ["slots"])
|
|
29
|
+
if (!componentToken(item.reference) || !componentToken(item.renderId) || !componentKey(item.key))
|
|
30
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
31
|
+
if (
|
|
32
|
+
item.slots !== undefined &&
|
|
33
|
+
(typeof item.slots !== "object" ||
|
|
34
|
+
item.slots === null ||
|
|
35
|
+
Array.isArray(item.slots) ||
|
|
36
|
+
Object.entries(item.slots).some(
|
|
37
|
+
([key, html]) => !/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(key) || typeof html !== "string",
|
|
38
|
+
))
|
|
39
|
+
)
|
|
40
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
41
|
+
if (item.slots !== undefined)
|
|
42
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
|
|
43
|
+
for (const html of Object.values(item.slots)) validateComponentHTML(html as string)
|
|
44
|
+
const result: ComponentIsland = {
|
|
45
|
+
reference: item.reference,
|
|
46
|
+
key: item.key,
|
|
47
|
+
renderId: item.renderId,
|
|
48
|
+
props: componentValue(item.props),
|
|
49
|
+
}
|
|
50
|
+
if (item.slots !== undefined) Object.assign(result, { slots: item.slots })
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
export function parseComponentState(
|
|
54
|
+
value: unknown,
|
|
55
|
+
maxBytes: number = COMPONENT_LIMITS.documentBytes,
|
|
56
|
+
externalParent?: string | null,
|
|
57
|
+
): ComponentState {
|
|
58
|
+
const bounded = componentValue(value, maxBytes)
|
|
59
|
+
const state = object(bounded, ["v", "build", "adapter", "owner", "scope", "records"])
|
|
60
|
+
if (
|
|
61
|
+
state.v !== 1 ||
|
|
62
|
+
!componentToken(state.build) ||
|
|
63
|
+
!componentToken(state.owner) ||
|
|
64
|
+
!componentToken(state.scope) ||
|
|
65
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
|
|
66
|
+
!["react", "vue", "svelte", "solid", "preact"].includes(state.adapter as string) ||
|
|
67
|
+
!Array.isArray(state.records) ||
|
|
68
|
+
state.records.length > COMPONENT_LIMITS.instances
|
|
69
|
+
)
|
|
70
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
71
|
+
const ids = new Set<string>()
|
|
72
|
+
const islandIds = new Set<string>()
|
|
73
|
+
let instances = state.records.length
|
|
74
|
+
const records: ComponentRecord[] = state.records.map((value: unknown) => {
|
|
75
|
+
const record = object(
|
|
76
|
+
value,
|
|
77
|
+
["instance", "reference", "key", "parent", "input", "html"],
|
|
78
|
+
["payload", "islands"],
|
|
79
|
+
)
|
|
80
|
+
if (
|
|
81
|
+
!componentToken(record.instance) ||
|
|
82
|
+
ids.has(record.instance) ||
|
|
83
|
+
!componentToken(record.reference) ||
|
|
84
|
+
!componentKey(record.key) ||
|
|
85
|
+
(record.parent !== null && !componentToken(record.parent)) ||
|
|
86
|
+
typeof record.html !== "string"
|
|
87
|
+
)
|
|
88
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
89
|
+
ids.add(record.instance)
|
|
90
|
+
validateComponentHTML(record.html)
|
|
91
|
+
if (
|
|
92
|
+
record.islands !== undefined &&
|
|
93
|
+
(!Array.isArray(record.islands) || record.islands.length > COMPONENT_LIMITS.instances)
|
|
94
|
+
)
|
|
95
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
96
|
+
const islands =
|
|
97
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
|
|
98
|
+
record.islands === undefined ? undefined : (record.islands as unknown[]).map(island)
|
|
99
|
+
for (const island of islands ?? []) {
|
|
100
|
+
if (islandIds.has(island.renderId)) throw new ServerComponentError("component_protocol", 400)
|
|
101
|
+
islandIds.add(island.renderId)
|
|
102
|
+
}
|
|
103
|
+
instances += islands?.length ?? 0
|
|
104
|
+
if (instances > COMPONENT_LIMITS.instances)
|
|
105
|
+
throw new ServerComponentError("component_limit", 413)
|
|
106
|
+
const result: ComponentRecord = {
|
|
107
|
+
instance: record.instance,
|
|
108
|
+
reference: record.reference,
|
|
109
|
+
key: record.key,
|
|
110
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
|
|
111
|
+
parent: record.parent as string | null,
|
|
112
|
+
html: record.html,
|
|
113
|
+
input: componentValue(record.input, COMPONENT_LIMITS.inputBytes),
|
|
114
|
+
}
|
|
115
|
+
if (record.payload !== undefined)
|
|
116
|
+
Object.assign(result, {
|
|
117
|
+
payload: componentValue(record.payload, COMPONENT_LIMITS.outputBytes),
|
|
118
|
+
})
|
|
119
|
+
if (islands !== undefined) Object.assign(result, { islands })
|
|
120
|
+
return result
|
|
121
|
+
})
|
|
122
|
+
// Refreshes contain a single child and may name its already mounted parent. Documents carry the full forest.
|
|
123
|
+
for (const record of records) {
|
|
124
|
+
const chain = new Set([record.instance])
|
|
125
|
+
let parent = record.parent
|
|
126
|
+
while (parent !== null) {
|
|
127
|
+
if (!ids.has(parent) && parent === externalParent) break
|
|
128
|
+
if (chain.has(parent) || !ids.has(parent) || chain.size >= 16)
|
|
129
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
130
|
+
chain.add(parent)
|
|
131
|
+
parent = records.find((item) => item.instance === parent)?.parent ?? null
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
v: 1,
|
|
136
|
+
build: state.build,
|
|
137
|
+
owner: state.owner,
|
|
138
|
+
scope: state.scope,
|
|
139
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; each typed field follows its closed vocabulary check.
|
|
140
|
+
adapter: state.adapter as ComponentState["adapter"],
|
|
141
|
+
records,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ServerComponentError } from "./error.ts"
|
|
2
|
+
import { COMPONENT_LIMITS } from "./protocol.ts"
|
|
3
|
+
|
|
4
|
+
export function bytesStream(bytes: Uint8Array): ReadableStream<Uint8Array> {
|
|
5
|
+
return new ReadableStream({
|
|
6
|
+
start(controller) {
|
|
7
|
+
controller.enqueue(bytes)
|
|
8
|
+
controller.close()
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The bounded snapshot is shared by SSR and hydration; it never tees into an unread branch. */
|
|
14
|
+
export async function readComponentBytes(
|
|
15
|
+
stream: ReadableStream<Uint8Array>,
|
|
16
|
+
signal: AbortSignal,
|
|
17
|
+
limit: number = COMPONENT_LIMITS.outputBytes,
|
|
18
|
+
): Promise<Uint8Array> {
|
|
19
|
+
const reader = stream.getReader()
|
|
20
|
+
const chunks: Uint8Array[] = []
|
|
21
|
+
let size = 0
|
|
22
|
+
const cancel = () => {
|
|
23
|
+
void reader.cancel().catch(() => {
|
|
24
|
+
/* Keep the primary read failure. */
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
signal.addEventListener("abort", cancel, { once: true })
|
|
28
|
+
try {
|
|
29
|
+
while (true) {
|
|
30
|
+
if (signal.aborted) throw new ServerComponentError("component_cancelled", 499)
|
|
31
|
+
const result = await reader.read()
|
|
32
|
+
if (result.done) break
|
|
33
|
+
if (result.value.length === 0) throw new ServerComponentError("component_protocol", 400)
|
|
34
|
+
size += result.value.length
|
|
35
|
+
if (size > limit) throw new ServerComponentError("component_limit", 413)
|
|
36
|
+
chunks.push(result.value)
|
|
37
|
+
}
|
|
38
|
+
if (signal.aborted) throw new ServerComponentError("component_cancelled", 499)
|
|
39
|
+
const bytes = new Uint8Array(size)
|
|
40
|
+
let offset = 0
|
|
41
|
+
for (const chunk of chunks) {
|
|
42
|
+
bytes.set(chunk, offset)
|
|
43
|
+
offset += chunk.length
|
|
44
|
+
}
|
|
45
|
+
return bytes
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await reader.cancel().catch(() => {
|
|
48
|
+
/* Keep the primary read failure. */
|
|
49
|
+
})
|
|
50
|
+
throw error
|
|
51
|
+
} finally {
|
|
52
|
+
signal.removeEventListener("abort", cancel)
|
|
53
|
+
reader.releaseLock()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function encodeComponentBytes(bytes: Uint8Array): string {
|
|
58
|
+
let binary = ""
|
|
59
|
+
for (const byte of bytes) binary += String.fromCharCode(byte)
|
|
60
|
+
return btoa(binary)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function decodeComponentBytes(value: unknown): Uint8Array {
|
|
64
|
+
if (
|
|
65
|
+
typeof value !== "string" ||
|
|
66
|
+
value.length > Math.ceil(COMPONENT_LIMITS.outputBytes / 3) * 4 ||
|
|
67
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)
|
|
68
|
+
)
|
|
69
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
70
|
+
return Uint8Array.from(atob(value), (item) => item.charCodeAt(0))
|
|
71
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { validateComponentHTML } from "./html.ts"
|
|
2
|
+
|
|
3
|
+
interface TrustedHTMLFactory {
|
|
4
|
+
createPolicy(
|
|
5
|
+
name: string,
|
|
6
|
+
rules: { createHTML(html: string): string },
|
|
7
|
+
): { createHTML(html: string): unknown }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Explicit opt-in for native framework HTML sinks. Every accepted string is tokenized first. */
|
|
11
|
+
export function installComponentTrustedTypesPolicy(): void {
|
|
12
|
+
// Trusted Types is not yet declared in all supported TypeScript DOM libraries.
|
|
13
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion lint/plugin/noChainedTypeAssertions: Trusted Types is optional on globalThis and absent from some DOM type libraries; the policy still validates HTML before insertion.
|
|
14
|
+
const factory = (globalThis as unknown as { trustedTypes?: TrustedHTMLFactory }).trustedTypes
|
|
15
|
+
factory?.createPolicy("default", {
|
|
16
|
+
createHTML(html) {
|
|
17
|
+
validateComponentHTML(html)
|
|
18
|
+
return html
|
|
19
|
+
},
|
|
20
|
+
})
|
|
21
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@nifrajs/core/server"
|
|
2
|
+
import type { LoaderContext } from "../manifest.ts"
|
|
3
|
+
|
|
4
|
+
export type ComponentKey = string | number
|
|
5
|
+
export type ComponentValue =
|
|
6
|
+
| null
|
|
7
|
+
| boolean
|
|
8
|
+
| number
|
|
9
|
+
| string
|
|
10
|
+
| readonly ComponentValue[]
|
|
11
|
+
| {
|
|
12
|
+
readonly [key: string]: ComponentValue
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ServerComponentContext extends LoaderContext {
|
|
16
|
+
readonly signal: AbortSignal
|
|
17
|
+
readonly data: unknown
|
|
18
|
+
readonly layoutData: readonly unknown[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ServerComponentDefinition<Input, Props, View> {
|
|
22
|
+
readonly input: StandardSchemaV1<unknown, Input>
|
|
23
|
+
readonly props: StandardSchemaV1<unknown, Props>
|
|
24
|
+
readonly authorize: (ctx: ServerComponentContext, input: Input) => void | Promise<void>
|
|
25
|
+
readonly load: (ctx: ServerComponentContext, input: Input) => unknown | Promise<unknown>
|
|
26
|
+
readonly view: (args: {
|
|
27
|
+
readonly props: Props
|
|
28
|
+
readonly children?: readonly ComponentRecord[]
|
|
29
|
+
}) => View | Promise<View>
|
|
30
|
+
readonly capabilities?: readonly string[]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ServerComponent<Input = unknown, Props = unknown, View = unknown> {
|
|
34
|
+
/** Compiler-generated opaque reference; application authors do not assign module URLs. */
|
|
35
|
+
readonly reference?: string
|
|
36
|
+
readonly view: (args: {
|
|
37
|
+
readonly props: Props
|
|
38
|
+
readonly children?: readonly ComponentRecord[]
|
|
39
|
+
}) => View | Promise<View>
|
|
40
|
+
readonly input: StandardSchemaV1<unknown, Input>
|
|
41
|
+
readonly props: StandardSchemaV1<unknown, Props>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PreparedServerComponent<Props = unknown, View = unknown> {
|
|
45
|
+
readonly props: Props
|
|
46
|
+
readonly view: (args: {
|
|
47
|
+
readonly props: Props
|
|
48
|
+
readonly children?: readonly ComponentRecord[]
|
|
49
|
+
}) => View | Promise<View>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ComponentRenderContext {
|
|
53
|
+
readonly reference: string
|
|
54
|
+
readonly instance: string
|
|
55
|
+
readonly signal: AbortSignal
|
|
56
|
+
readonly nonce?: string
|
|
57
|
+
readonly children?: readonly ComponentRecord[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ComponentRender {
|
|
61
|
+
readonly html: string
|
|
62
|
+
readonly payload?: ComponentValue
|
|
63
|
+
readonly islands?: readonly ComponentIsland[]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ComponentIsland {
|
|
67
|
+
readonly reference: string
|
|
68
|
+
readonly key: ComponentKey
|
|
69
|
+
readonly props: ComponentValue
|
|
70
|
+
readonly renderId: string
|
|
71
|
+
readonly slots?: Readonly<Record<string, string>>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ServerComponentRenderAdapter {
|
|
75
|
+
readonly name: "react" | "vue" | "svelte" | "solid" | "preact"
|
|
76
|
+
readonly version: 1
|
|
77
|
+
render(
|
|
78
|
+
view: unknown,
|
|
79
|
+
props: unknown,
|
|
80
|
+
context: ComponentRenderContext,
|
|
81
|
+
): ComponentRender | Promise<ComponentRender>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ComponentInstance {
|
|
85
|
+
readonly component: ServerComponentHandle
|
|
86
|
+
readonly key: ComponentKey
|
|
87
|
+
readonly input: unknown
|
|
88
|
+
readonly parent?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ComponentOwner {
|
|
92
|
+
readonly components: readonly ServerComponentHandle[]
|
|
93
|
+
readonly capabilities?: readonly string[]
|
|
94
|
+
readonly instances: (
|
|
95
|
+
ctx: ServerComponentContext,
|
|
96
|
+
) => readonly ComponentInstance[] | Promise<readonly ComponentInstance[]>
|
|
97
|
+
/** A public, non-secret token supplied by the application's auth integration. */
|
|
98
|
+
readonly scope: "anonymous" | ((ctx: ServerComponentContext) => string | Promise<string>)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface ServerComponentHandle {
|
|
102
|
+
readonly reference?: string
|
|
103
|
+
readonly input: StandardSchemaV1
|
|
104
|
+
readonly props: StandardSchemaV1
|
|
105
|
+
readonly view: (args: never) => unknown
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ComponentRecord extends ComponentRender {
|
|
109
|
+
readonly instance: string
|
|
110
|
+
readonly reference: string
|
|
111
|
+
readonly key: ComponentKey
|
|
112
|
+
readonly parent: string | null
|
|
113
|
+
readonly input: ComponentValue
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface ComponentState {
|
|
117
|
+
readonly v: 1
|
|
118
|
+
readonly build: string
|
|
119
|
+
readonly adapter: "react" | "vue" | "svelte" | "solid" | "preact"
|
|
120
|
+
readonly owner: string
|
|
121
|
+
readonly scope: string
|
|
122
|
+
readonly records: readonly ComponentRecord[]
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Optional seam passed to createWebApp; disabled applications have no runtime import of this feature. */
|
|
126
|
+
export interface ServerComponentRuntime {
|
|
127
|
+
owns(routeId: string): boolean
|
|
128
|
+
capabilities(routeId: string): readonly string[]
|
|
129
|
+
classify(request: Request, routeId: string): import("./protocol.ts").ComponentRequest | undefined
|
|
130
|
+
execute(
|
|
131
|
+
routeId: string,
|
|
132
|
+
context: ServerComponentContext,
|
|
133
|
+
selection?: import("./protocol.ts").ComponentRequest,
|
|
134
|
+
nonce?: string,
|
|
135
|
+
): Promise<ComponentState>
|
|
136
|
+
response(state: ComponentState, selection: import("./protocol.ts").ComponentRequest): Response
|
|
137
|
+
failure(error: unknown): Response
|
|
138
|
+
}
|