@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,111 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@nifrajs/core/server"
|
|
2
|
+
import type { ComponentKey, ComponentRecord, ServerComponentHandle } from "./types.ts"
|
|
3
|
+
|
|
4
|
+
const contractBrand = Symbol.for("nifra.components.island")
|
|
5
|
+
const viewBrand = Symbol.for("nifra.components.view")
|
|
6
|
+
export interface ClientIsland<Props = unknown, Component = unknown> {
|
|
7
|
+
readonly reference?: string
|
|
8
|
+
readonly props: StandardSchemaV1<unknown, Props>
|
|
9
|
+
readonly component: Component
|
|
10
|
+
readonly slots: readonly string[]
|
|
11
|
+
readonly [contractBrand]: true
|
|
12
|
+
}
|
|
13
|
+
type NativeComponentProps<Component> = Component extends abstract new (
|
|
14
|
+
...args: never[]
|
|
15
|
+
) => { $props: infer Props }
|
|
16
|
+
? Props
|
|
17
|
+
: Component extends abstract new (
|
|
18
|
+
...args: never[]
|
|
19
|
+
) => { props: infer Props }
|
|
20
|
+
? Props
|
|
21
|
+
: Component extends (...args: infer Args) => unknown
|
|
22
|
+
? Args extends []
|
|
23
|
+
? object
|
|
24
|
+
: // Optional legacy context is not a public props alternative (Preact types it as any).
|
|
25
|
+
Args extends [unknown, infer Props, ...unknown[]]
|
|
26
|
+
? Args[0] | Props
|
|
27
|
+
: Args[0]
|
|
28
|
+
: never
|
|
29
|
+
type CompatibleComponent<Props, Component> =
|
|
30
|
+
Props extends NativeComponentProps<Component>
|
|
31
|
+
? unknown
|
|
32
|
+
: { readonly __nifraIslandPropsMustMatchComponent: never }
|
|
33
|
+
|
|
34
|
+
export function defineClientIsland<Props, Component>(
|
|
35
|
+
definition: {
|
|
36
|
+
readonly props: StandardSchemaV1<unknown, Props>
|
|
37
|
+
readonly component: Component
|
|
38
|
+
readonly slots?: readonly string[]
|
|
39
|
+
} & CompatibleComponent<NoInfer<Props>, NoInfer<Component>>,
|
|
40
|
+
reference?: string,
|
|
41
|
+
): ClientIsland<Props, Component> {
|
|
42
|
+
const slots = definition.slots ?? []
|
|
43
|
+
if (
|
|
44
|
+
slots.length > 16 ||
|
|
45
|
+
new Set(slots).size !== slots.length ||
|
|
46
|
+
slots.some(
|
|
47
|
+
(name) =>
|
|
48
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(name) || ["ref", "key", "constructor"].includes(name),
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
throw new Error("component_contract")
|
|
52
|
+
const contract: ClientIsland<Props, Component> = {
|
|
53
|
+
props: definition.props,
|
|
54
|
+
component: definition.component,
|
|
55
|
+
slots: Object.freeze([...slots]),
|
|
56
|
+
[contractBrand]: true,
|
|
57
|
+
}
|
|
58
|
+
if (reference !== undefined) Object.assign(contract, { reference })
|
|
59
|
+
return Object.freeze(contract)
|
|
60
|
+
}
|
|
61
|
+
export interface IslandInstance {
|
|
62
|
+
readonly contract: ClientIsland
|
|
63
|
+
readonly key: ComponentKey
|
|
64
|
+
readonly props: unknown
|
|
65
|
+
readonly slots: Readonly<Record<string, string>>
|
|
66
|
+
}
|
|
67
|
+
export function clientIsland<Props>(
|
|
68
|
+
contract: ClientIsland<Props>,
|
|
69
|
+
key: ComponentKey,
|
|
70
|
+
props: NoInfer<Props>,
|
|
71
|
+
slots: Readonly<Record<string, string>> = {},
|
|
72
|
+
): IslandInstance {
|
|
73
|
+
return { contract, key, props, slots }
|
|
74
|
+
}
|
|
75
|
+
export interface NativeServerView {
|
|
76
|
+
readonly component: unknown
|
|
77
|
+
readonly islands: Readonly<Record<string, IslandInstance>>
|
|
78
|
+
readonly ranges: Readonly<Record<string, ComponentRecord>>
|
|
79
|
+
readonly [viewBrand]: true
|
|
80
|
+
}
|
|
81
|
+
/** Server-only native view rendered by the selected framework's own renderer. */
|
|
82
|
+
export function nativeServerView(
|
|
83
|
+
component: unknown,
|
|
84
|
+
islands: Readonly<Record<string, IslandInstance>> = {},
|
|
85
|
+
ranges: Readonly<Record<string, ComponentRecord>> = {},
|
|
86
|
+
): NativeServerView {
|
|
87
|
+
return { component, islands, ranges, [viewBrand]: true }
|
|
88
|
+
}
|
|
89
|
+
export function serverComponentChild(
|
|
90
|
+
children: readonly ComponentRecord[],
|
|
91
|
+
component: Pick<ServerComponentHandle, "reference">,
|
|
92
|
+
key: ComponentKey,
|
|
93
|
+
): ComponentRecord {
|
|
94
|
+
const matches = children.filter(
|
|
95
|
+
(record) => record.reference === component.reference && record.key === key,
|
|
96
|
+
)
|
|
97
|
+
if (matches.length !== 1) throw new Error("component_contract")
|
|
98
|
+
return matches[0]!
|
|
99
|
+
}
|
|
100
|
+
export function isNativeServerView(value: unknown): value is NativeServerView {
|
|
101
|
+
return (
|
|
102
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before inspecting the private symbol brand created by this module.
|
|
103
|
+
typeof value === "object" && value !== null && (value as NativeServerView)[viewBrand] === true
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
export function isClientIsland(value: unknown): value is ClientIsland {
|
|
107
|
+
return (
|
|
108
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before inspecting the private symbol brand created by this module.
|
|
109
|
+
typeof value === "object" && value !== null && (value as ClientIsland)[contractBrand] === true
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isClientIsland, isNativeServerView } from "./islands.ts"
|
|
2
|
+
import { guardComponentProps } from "./props.ts"
|
|
3
|
+
import { COMPONENT_LIMITS, componentKey } from "./protocol.ts"
|
|
4
|
+
import { componentToken } from "./state.ts"
|
|
5
|
+
import type { ComponentIsland, ServerComponentRenderAdapter } from "./types.ts"
|
|
6
|
+
|
|
7
|
+
export type NativeRender = (
|
|
8
|
+
component: unknown,
|
|
9
|
+
props: Record<string, unknown>,
|
|
10
|
+
renderId: string,
|
|
11
|
+
) => string | Promise<string>
|
|
12
|
+
export function nativeServerComponents(
|
|
13
|
+
name: "vue" | "svelte" | "solid" | "preact",
|
|
14
|
+
render: NativeRender,
|
|
15
|
+
): ServerComponentRenderAdapter {
|
|
16
|
+
return {
|
|
17
|
+
name,
|
|
18
|
+
version: 1,
|
|
19
|
+
async render(view, props, context) {
|
|
20
|
+
if (typeof view !== "function") throw new Error("component_contract")
|
|
21
|
+
const value: unknown = await view({ props, children: context.children ?? [] })
|
|
22
|
+
if (!isNativeServerView(value)) throw new Error("component_contract")
|
|
23
|
+
const entries = Object.entries(value.islands)
|
|
24
|
+
if (entries.length > COMPONENT_LIMITS.instances) throw new Error("component_limit")
|
|
25
|
+
const seen = new Set<string>()
|
|
26
|
+
const islands: ComponentIsland[] = []
|
|
27
|
+
const markup: Record<string, { readonly html: string; readonly renderId: string }> =
|
|
28
|
+
Object.create(null)
|
|
29
|
+
for (const [slot, instance] of entries) {
|
|
30
|
+
if (
|
|
31
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(slot) ||
|
|
32
|
+
!isClientIsland(instance.contract) ||
|
|
33
|
+
!componentToken(instance.contract.reference) ||
|
|
34
|
+
!componentKey(instance.key) ||
|
|
35
|
+
Object.keys(instance.slots).some((name) => !instance.contract.slots.includes(name))
|
|
36
|
+
)
|
|
37
|
+
throw new Error("component_contract")
|
|
38
|
+
const digest = await crypto.subtle.digest(
|
|
39
|
+
"SHA-256",
|
|
40
|
+
new TextEncoder().encode(
|
|
41
|
+
JSON.stringify([context.instance, instance.contract.reference, instance.key]),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
const renderId = `i_${[...new Uint8Array(digest)].map((n) => n.toString(16).padStart(2, "0")).join("")}`
|
|
45
|
+
if (seen.has(renderId)) throw new Error("component_contract")
|
|
46
|
+
seen.add(renderId)
|
|
47
|
+
const projected = await guardComponentProps(instance.contract.props, instance.props)
|
|
48
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The closed schema guard and bounded JSON validator produced this public props object before native rendering.
|
|
49
|
+
const guarded = projected as Record<string, unknown> // Closed schema guard and JSON check own this boundary.
|
|
50
|
+
const slots = Object.fromEntries(
|
|
51
|
+
instance.contract.slots.map((name) => {
|
|
52
|
+
const html = instance.slots[name] ?? ""
|
|
53
|
+
if (typeof html !== "string") throw new Error("component_contract")
|
|
54
|
+
return [name, `<nifra-slot data-nifra-slot="${name}">${html}</nifra-slot>`]
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
57
|
+
const html = await render(instance.contract.component, { ...guarded, slots }, renderId)
|
|
58
|
+
if (context.signal.aborted) throw new Error("component_cancelled")
|
|
59
|
+
markup[slot] = {
|
|
60
|
+
html: `<nifra-island data-nifra-island="${renderId}">${html}</nifra-island>`,
|
|
61
|
+
renderId,
|
|
62
|
+
}
|
|
63
|
+
islands.push({
|
|
64
|
+
reference: instance.contract.reference,
|
|
65
|
+
key: instance.key,
|
|
66
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The closed schema guard and bounded JSON validator produced this public props object before native rendering.
|
|
67
|
+
props: projected as ComponentIsland["props"],
|
|
68
|
+
renderId,
|
|
69
|
+
slots: instance.slots,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
const ranges: Record<string, { html: string; instance: string }> = Object.create(null)
|
|
73
|
+
const rangeIds = new Set<string>()
|
|
74
|
+
for (const [name, child] of Object.entries(value.ranges)) {
|
|
75
|
+
if (
|
|
76
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(name) ||
|
|
77
|
+
child.parent !== context.instance ||
|
|
78
|
+
!(context.children ?? []).includes(child) ||
|
|
79
|
+
rangeIds.has(child.instance)
|
|
80
|
+
)
|
|
81
|
+
throw new Error("component_contract")
|
|
82
|
+
rangeIds.add(child.instance)
|
|
83
|
+
ranges[name] = {
|
|
84
|
+
instance: child.instance,
|
|
85
|
+
html:
|
|
86
|
+
'<nifra-component data-nifra-component="' +
|
|
87
|
+
child.instance +
|
|
88
|
+
'">' +
|
|
89
|
+
child.html +
|
|
90
|
+
"</nifra-component>",
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const html = await render(
|
|
94
|
+
value.component,
|
|
95
|
+
{ props, islands: markup, ranges },
|
|
96
|
+
`${context.instance}-view`,
|
|
97
|
+
)
|
|
98
|
+
return { html, islands }
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Resolve only a declared target inside the island or component that owns the calling node. */
|
|
2
|
+
export function componentPortalTarget(source: Element, target: Element): Element {
|
|
3
|
+
const owner = source.closest("[data-nifra-island], [data-nifra-component]")
|
|
4
|
+
const name = target.getAttribute("data-nifra-portal")
|
|
5
|
+
if (
|
|
6
|
+
owner === null ||
|
|
7
|
+
name === null ||
|
|
8
|
+
!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(name) ||
|
|
9
|
+
!owner.contains(target) ||
|
|
10
|
+
target.closest("[data-nifra-island], [data-nifra-component]") !== owner
|
|
11
|
+
)
|
|
12
|
+
throw new Error("component_contract")
|
|
13
|
+
return target
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const cleanups = new WeakMap<Element, Set<() => Promise<void>>>()
|
|
17
|
+
|
|
18
|
+
/** Native portal actions register cleanup with the island that owns their target. */
|
|
19
|
+
export function ownComponentPortal(
|
|
20
|
+
source: Element,
|
|
21
|
+
target: Element,
|
|
22
|
+
cleanup: () => void | Promise<void>,
|
|
23
|
+
): () => Promise<void> {
|
|
24
|
+
componentPortalTarget(source, target)
|
|
25
|
+
const owner = source.closest("[data-nifra-island], [data-nifra-component]")!
|
|
26
|
+
const entries = cleanups.get(owner) ?? new Set<() => Promise<void>>()
|
|
27
|
+
let pending: Promise<void> | undefined
|
|
28
|
+
const dispose = () => {
|
|
29
|
+
entries.delete(dispose)
|
|
30
|
+
pending ??= Promise.resolve().then(cleanup)
|
|
31
|
+
return pending
|
|
32
|
+
}
|
|
33
|
+
entries.add(dispose)
|
|
34
|
+
cleanups.set(owner, entries)
|
|
35
|
+
return dispose
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function disposeComponentPortals(owner: Element): Promise<void> {
|
|
39
|
+
const entries = cleanups.get(owner)
|
|
40
|
+
cleanups.delete(owner)
|
|
41
|
+
if (entries !== undefined) await Promise.all([...entries].map((cleanup) => cleanup()))
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@nifrajs/core/schema"
|
|
2
|
+
import { outputGuard, runGuard } from "../internal/output-guard.ts"
|
|
3
|
+
import { assertComponentSchema } from "./schema.ts"
|
|
4
|
+
import { componentValue } from "./value.ts"
|
|
5
|
+
|
|
6
|
+
const guards = new WeakMap<object, ReturnType<typeof outputGuard>>()
|
|
7
|
+
|
|
8
|
+
export async function guardComponentProps(
|
|
9
|
+
schema: StandardSchemaV1,
|
|
10
|
+
value: unknown,
|
|
11
|
+
): Promise<unknown> {
|
|
12
|
+
let guard = guards.get(schema)
|
|
13
|
+
if (guard === undefined) {
|
|
14
|
+
assertComponentSchema(schema)
|
|
15
|
+
guard = outputGuard(schema, "client component", "props")
|
|
16
|
+
guards.set(schema, guard)
|
|
17
|
+
}
|
|
18
|
+
return componentValue(await runGuard(guard, value, "client component props"))
|
|
19
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { ServerComponentError } from "./error.ts"
|
|
2
|
+
import type { ComponentKey, ComponentValue } from "./types.ts"
|
|
3
|
+
import { componentValue } from "./value.ts"
|
|
4
|
+
|
|
5
|
+
export const COMPONENT_HEADER = "x-nifra-component"
|
|
6
|
+
export const COMPONENT_MEDIA_TYPE = "application/vnd.nifra.component-v1"
|
|
7
|
+
export const COMPONENT_PROTOCOL = 1
|
|
8
|
+
export const COMPONENT_LIMITS = Object.freeze({
|
|
9
|
+
headerBytes: 2048,
|
|
10
|
+
encodedHeaderBytes: 2732,
|
|
11
|
+
inputBytes: 1024,
|
|
12
|
+
propsBytes: 65536,
|
|
13
|
+
outputBytes: 1048576,
|
|
14
|
+
documentBytes: 4194304,
|
|
15
|
+
frameBytes: 262144,
|
|
16
|
+
depth: 16,
|
|
17
|
+
instances: 128,
|
|
18
|
+
requestLoads: 8,
|
|
19
|
+
activeLoads: 64,
|
|
20
|
+
queuedLoads: 128,
|
|
21
|
+
executionMs: 10000,
|
|
22
|
+
idleMs: 5000,
|
|
23
|
+
operationMs: 15000,
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export interface ComponentRequest {
|
|
27
|
+
readonly v: 1
|
|
28
|
+
readonly build: string
|
|
29
|
+
readonly reference: string
|
|
30
|
+
readonly key: ComponentKey
|
|
31
|
+
readonly parent: string | null
|
|
32
|
+
readonly generation: number
|
|
33
|
+
readonly routeEpoch: number
|
|
34
|
+
readonly authEpoch: number
|
|
35
|
+
readonly input: ComponentValue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const fields = [
|
|
39
|
+
"v",
|
|
40
|
+
"build",
|
|
41
|
+
"reference",
|
|
42
|
+
"key",
|
|
43
|
+
"parent",
|
|
44
|
+
"generation",
|
|
45
|
+
"routeEpoch",
|
|
46
|
+
"authEpoch",
|
|
47
|
+
"input",
|
|
48
|
+
]
|
|
49
|
+
const token = /^[A-Za-z0-9_-]{1,128}$/
|
|
50
|
+
const encoder = new TextEncoder()
|
|
51
|
+
const decoder = new TextDecoder("utf-8", { fatal: true })
|
|
52
|
+
|
|
53
|
+
export function componentKey(value: unknown): value is ComponentKey {
|
|
54
|
+
return (
|
|
55
|
+
(typeof value === "string" &&
|
|
56
|
+
encoder.encode(value).length > 0 &&
|
|
57
|
+
encoder.encode(value).length <= 128) ||
|
|
58
|
+
(typeof value === "number" && Number.isSafeInteger(value))
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function parseComponentRequest(value: unknown): ComponentRequest {
|
|
63
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
64
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
65
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object shape is checked before field inspection; every field is validated before a request is returned.
|
|
66
|
+
const object = value as Record<string, unknown>
|
|
67
|
+
if (
|
|
68
|
+
Object.keys(object).length !== fields.length ||
|
|
69
|
+
fields.some((name) => !Object.hasOwn(object, name)) ||
|
|
70
|
+
object.v !== 1 ||
|
|
71
|
+
typeof object.build !== "string" ||
|
|
72
|
+
!token.test(object.build) ||
|
|
73
|
+
typeof object.reference !== "string" ||
|
|
74
|
+
!token.test(object.reference) ||
|
|
75
|
+
!componentKey(object.key) ||
|
|
76
|
+
(object.parent !== null && (typeof object.parent !== "string" || !token.test(object.parent)))
|
|
77
|
+
) {
|
|
78
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
79
|
+
}
|
|
80
|
+
const counter = (value: unknown): number => {
|
|
81
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
|
|
82
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
83
|
+
return value
|
|
84
|
+
}
|
|
85
|
+
const input = componentValue(object.input, COMPONENT_LIMITS.inputBytes)
|
|
86
|
+
return {
|
|
87
|
+
v: 1,
|
|
88
|
+
build: object.build,
|
|
89
|
+
reference: object.reference,
|
|
90
|
+
key: object.key,
|
|
91
|
+
parent: object.parent,
|
|
92
|
+
generation: counter(object.generation),
|
|
93
|
+
routeEpoch: counter(object.routeEpoch),
|
|
94
|
+
authEpoch: counter(object.authEpoch),
|
|
95
|
+
input,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function base64url(bytes: Uint8Array): string {
|
|
100
|
+
let binary = ""
|
|
101
|
+
for (const byte of bytes) binary += String.fromCharCode(byte)
|
|
102
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "")
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function componentRequestHeader(value: ComponentRequest): string {
|
|
106
|
+
const request = parseComponentRequest(value)
|
|
107
|
+
const bytes = encoder.encode(JSON.stringify(request))
|
|
108
|
+
if (bytes.length > COMPONENT_LIMITS.headerBytes)
|
|
109
|
+
throw new ServerComponentError("component_limit", 431)
|
|
110
|
+
return base64url(bytes)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function readComponentRequest(request: Request): ComponentRequest | undefined {
|
|
114
|
+
const encoded = request.headers.get(COMPONENT_HEADER)
|
|
115
|
+
if (encoded === null) return undefined
|
|
116
|
+
if (
|
|
117
|
+
request.method !== "GET" ||
|
|
118
|
+
request.headers.has("x-nifra-data") ||
|
|
119
|
+
request.headers.has("x-nifra-retain") ||
|
|
120
|
+
request.headers.has("x-nifra-revalidate") ||
|
|
121
|
+
request.headers.has("x-nifra-from")
|
|
122
|
+
)
|
|
123
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
124
|
+
if (encoded.length > COMPONENT_LIMITS.encodedHeaderBytes)
|
|
125
|
+
throw new ServerComponentError("component_limit", 431)
|
|
126
|
+
if (!/^[A-Za-z0-9_-]+$/.test(encoded) || encoded.length % 4 === 1)
|
|
127
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
128
|
+
const origin = new URL(request.url).origin
|
|
129
|
+
if (
|
|
130
|
+
request.headers.get("sec-fetch-site") !== "same-origin" ||
|
|
131
|
+
(request.headers.has("origin") && request.headers.get("origin") !== origin)
|
|
132
|
+
) {
|
|
133
|
+
throw new ServerComponentError("component_denied", 403)
|
|
134
|
+
}
|
|
135
|
+
let decoded: unknown
|
|
136
|
+
try {
|
|
137
|
+
const binary = atob(encoded.replaceAll("-", "+").replaceAll("_", "/"))
|
|
138
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0))
|
|
139
|
+
if (bytes.length > COMPONENT_LIMITS.headerBytes)
|
|
140
|
+
throw new ServerComponentError("component_limit", 431)
|
|
141
|
+
if (base64url(bytes) !== encoded) throw new ServerComponentError("component_protocol", 400)
|
|
142
|
+
decoded = JSON.parse(decoder.decode(bytes))
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error instanceof ServerComponentError) throw error
|
|
145
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
146
|
+
}
|
|
147
|
+
return parseComponentRequest(decoded)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const COMPONENT_RESPONSE_HEADERS = Object.freeze({
|
|
151
|
+
"content-type": COMPONENT_MEDIA_TYPE,
|
|
152
|
+
"cache-control": "private, no-store",
|
|
153
|
+
"x-content-type-options": "nosniff",
|
|
154
|
+
vary: "x-nifra-component, x-nifra-data",
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
export function componentFailure(error: unknown): Response {
|
|
158
|
+
const failure =
|
|
159
|
+
error instanceof ServerComponentError ? error : new ServerComponentError("component_failed")
|
|
160
|
+
return Response.json(
|
|
161
|
+
{ v: 1, error: failure.code },
|
|
162
|
+
{
|
|
163
|
+
status: failure.status === 499 ? 409 : failure.status,
|
|
164
|
+
headers: { ...COMPONENT_RESPONSE_HEADERS, "content-type": "application/json" },
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
}
|