@nifrajs/web 4.0.0 → 5.0.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 +11 -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 +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +49 -26
- 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-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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- 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/page-execution.d.ts +6 -0
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +377 -108
- package/dist/internal/page-execution.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 +1 -0
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +28 -5
- 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 +8 -0
- package/dist/internal/route-path.d.ts.map +1 -0
- package/dist/internal/route-path.js +8 -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 +105 -19
- 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 +21 -16
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +73 -21
- 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/zone-graph.d.ts +4 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +54 -14
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +58 -2
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +296 -39
- 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 +28 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +14 -3
- 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 +3 -1
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +4 -0
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +10 -2
- package/dist/plugins/zone-guard.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 +2 -0
- 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 +39 -46
- package/dist/router.js.map +1 -1
- 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 +77 -17
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +40 -0
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +170 -6
- package/dist/zones.js.map +1 -1
- package/package.json +97 -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 -23
- package/src/deferred.ts +27 -9
- package/src/dev-feed.ts +24 -5
- 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 +3 -2
- 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/page-execution.ts +506 -108
- package/src/internal/progressive-cookies.ts +60 -0
- package/src/internal/public-env.ts +14 -0
- package/src/internal/render-document.ts +26 -11
- package/src/internal/response-controls.ts +31 -14
- package/src/internal/revive-dates.ts +35 -0
- package/src/internal/route-path.ts +8 -0
- package/src/internal/secret-scan.ts +104 -17
- package/src/internal/server-boundary.ts +29 -5
- package/src/internal/web-app.ts +116 -37
- package/src/internal/wire-date.ts +16 -0
- package/src/internal/zone-graph.ts +59 -19
- package/src/isr.ts +351 -43
- package/src/llms-txt.ts +32 -3
- package/src/manifest.ts +43 -7
- package/src/open-resolved.ts +64 -0
- package/src/plugins/kit.ts +31 -11
- package/src/plugins/vite-leak-guard.ts +5 -1
- package/src/plugins/zone-guard.ts +11 -2
- package/src/public-dir.ts +27 -8
- package/src/query.ts +107 -26
- package/src/render-seam.ts +2 -0
- package/src/route-types.ts +34 -7
- package/src/router.ts +58 -55
- 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 +68 -17
- package/src/zones.ts +208 -7
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import type { ComponentClientAdapter, ComponentClientRoot, CurrentComponent } from "./client.ts"
|
|
2
|
+
import { ServerComponentError } from "./error.ts"
|
|
3
|
+
import { validateComponentTag } from "./html-policy.ts"
|
|
4
|
+
import { type ClientIsland, isClientIsland } from "./islands.ts"
|
|
5
|
+
import { guardComponentProps } from "./props.ts"
|
|
6
|
+
import type { ComponentIsland, ComponentState } from "./types.ts"
|
|
7
|
+
|
|
8
|
+
export interface NativeIslandRoot {
|
|
9
|
+
readonly ready?: Promise<void>
|
|
10
|
+
update(props: Record<string, unknown>, current: CurrentComponent): void | Promise<void>
|
|
11
|
+
dispose(): void | Promise<void>
|
|
12
|
+
}
|
|
13
|
+
export interface NativeIslandRuntime {
|
|
14
|
+
hydrate(
|
|
15
|
+
host: Element,
|
|
16
|
+
contract: ClientIsland,
|
|
17
|
+
record: ComponentIsland,
|
|
18
|
+
current: CurrentComponent,
|
|
19
|
+
): NativeIslandRoot | Promise<NativeIslandRoot>
|
|
20
|
+
}
|
|
21
|
+
export interface NativeRangeOptions {
|
|
22
|
+
readonly adapter: Exclude<ComponentState["adapter"], "react">
|
|
23
|
+
readonly islands: readonly ClientIsland[]
|
|
24
|
+
readonly runtime: NativeIslandRuntime
|
|
25
|
+
/** A narrowly scoped Trusted Types policy's createHTML, when the application requires TT. */
|
|
26
|
+
readonly trustedHTML?: (html: string) => string | { toString(): string }
|
|
27
|
+
}
|
|
28
|
+
interface Prepared {
|
|
29
|
+
fragment: DocumentFragment
|
|
30
|
+
islands: readonly ComponentIsland[]
|
|
31
|
+
contracts: ReadonlyMap<string, ClientIsland>
|
|
32
|
+
nested: ReadonlyMap<string, Element>
|
|
33
|
+
}
|
|
34
|
+
interface MountedIsland {
|
|
35
|
+
readonly host: Element
|
|
36
|
+
readonly root: NativeIslandRoot
|
|
37
|
+
readonly record: ComponentIsland
|
|
38
|
+
}
|
|
39
|
+
function failure(): ServerComponentError {
|
|
40
|
+
return new ServerComponentError("component_protocol", 400)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Parse inertly and reject executable content before it can reach a live document. */
|
|
44
|
+
export function parseComponentHTML(
|
|
45
|
+
html: string,
|
|
46
|
+
context: Element,
|
|
47
|
+
trustedHTML?: NativeRangeOptions["trustedHTML"],
|
|
48
|
+
): DocumentFragment {
|
|
49
|
+
const range = document.createRange()
|
|
50
|
+
range.selectNodeContents(context)
|
|
51
|
+
// A TT policy only authorizes inert parsing here. Validation still precedes insertion.
|
|
52
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
53
|
+
const source = (trustedHTML === undefined ? html : trustedHTML(html)) as string
|
|
54
|
+
const inert = new DOMParser().parseFromString(source, "text/html")
|
|
55
|
+
validateHTML(inert)
|
|
56
|
+
const fragment = range.createContextualFragment(source)
|
|
57
|
+
validateHTML(fragment)
|
|
58
|
+
return fragment
|
|
59
|
+
}
|
|
60
|
+
function validateHTML(root: ParentNode): void {
|
|
61
|
+
for (const element of root.querySelectorAll("*")) {
|
|
62
|
+
validateComponentTag(element.localName, [...element.attributes])
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function hosts(fragment: ParentNode, islands: readonly ComponentIsland[]): Map<string, Element> {
|
|
66
|
+
const map = new Map<string, Element>()
|
|
67
|
+
for (const host of fragment.querySelectorAll("[data-nifra-island]")) {
|
|
68
|
+
let parent = host.parentElement
|
|
69
|
+
let nested = false
|
|
70
|
+
while (parent !== null && parent !== fragment) {
|
|
71
|
+
if (parent.hasAttribute("data-nifra-component")) {
|
|
72
|
+
nested = true
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
parent = parent.parentElement
|
|
76
|
+
}
|
|
77
|
+
if (nested) continue
|
|
78
|
+
const id = host.getAttribute("data-nifra-island")
|
|
79
|
+
if (
|
|
80
|
+
id === null ||
|
|
81
|
+
map.has(id) ||
|
|
82
|
+
host.localName !== "nifra-island" ||
|
|
83
|
+
(host.parentElement?.closest("nifra-island") !== null &&
|
|
84
|
+
host.parentElement?.closest("nifra-island") !== undefined)
|
|
85
|
+
)
|
|
86
|
+
throw failure()
|
|
87
|
+
map.set(id, host)
|
|
88
|
+
}
|
|
89
|
+
if (map.size !== islands.length || islands.some((record) => !map.has(record.renderId)))
|
|
90
|
+
throw failure()
|
|
91
|
+
return map
|
|
92
|
+
}
|
|
93
|
+
class NativeRange implements ComponentClientRoot {
|
|
94
|
+
readonly #host: Element
|
|
95
|
+
readonly #options: NativeRangeOptions
|
|
96
|
+
#mounted = new Map<string, MountedIsland>()
|
|
97
|
+
#disposed = false
|
|
98
|
+
#pending = Promise.resolve()
|
|
99
|
+
constructor(host: Element, options: NativeRangeOptions) {
|
|
100
|
+
this.#host = host
|
|
101
|
+
this.#options = options
|
|
102
|
+
}
|
|
103
|
+
async initialize(prepared: Prepared, current: CurrentComponent): Promise<void> {
|
|
104
|
+
const actual = hosts(this.#host, prepared.islands)
|
|
105
|
+
try {
|
|
106
|
+
for (const record of prepared.islands) {
|
|
107
|
+
if (!current()) throw failure()
|
|
108
|
+
const host = actual.get(record.renderId)!
|
|
109
|
+
const contract = prepared.contracts.get(record.reference)!
|
|
110
|
+
const root = await this.#options.runtime.hydrate(host, contract, record, current)
|
|
111
|
+
await root.ready
|
|
112
|
+
if (!current()) {
|
|
113
|
+
await root.dispose()
|
|
114
|
+
throw failure()
|
|
115
|
+
}
|
|
116
|
+
this.#mounted.set(record.renderId, { host, root, record })
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
await this.dispose()
|
|
120
|
+
throw error
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
commit(prepared: Prepared, current: CurrentComponent): Promise<void> {
|
|
124
|
+
const pending = this.#pending.then(() => this.#commit(prepared, current))
|
|
125
|
+
this.#pending = pending.catch(() => {
|
|
126
|
+
/* The calling refresh owns the failure; later generations can proceed. */
|
|
127
|
+
})
|
|
128
|
+
return pending
|
|
129
|
+
}
|
|
130
|
+
async #commit(prepared: Prepared, current: CurrentComponent): Promise<void> {
|
|
131
|
+
if (!current() || this.#disposed) return
|
|
132
|
+
const nextHosts = hosts(prepared.fragment, prepared.islands)
|
|
133
|
+
const retainedChildren = new Map<string, Element>()
|
|
134
|
+
for (const host of this.#host.querySelectorAll("[data-nifra-component]")) {
|
|
135
|
+
const id = host.getAttribute("data-nifra-component")
|
|
136
|
+
if (id !== null && prepared.nested.has(id)) retainedChildren.set(id, host)
|
|
137
|
+
}
|
|
138
|
+
const next = new Map<string, MountedIsland>()
|
|
139
|
+
const active = document.activeElement
|
|
140
|
+
const selection =
|
|
141
|
+
active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement
|
|
142
|
+
? ([active.selectionStart, active.selectionEnd, active.selectionDirection] as const)
|
|
143
|
+
: undefined
|
|
144
|
+
const acquired: NativeIslandRoot[] = []
|
|
145
|
+
const slots: Array<{ host: Element; fragment: DocumentFragment }> = []
|
|
146
|
+
try {
|
|
147
|
+
for (const record of prepared.islands) {
|
|
148
|
+
const previous = this.#mounted.get(record.renderId)
|
|
149
|
+
const placeholder = nextHosts.get(record.renderId)!
|
|
150
|
+
if (
|
|
151
|
+
previous !== undefined &&
|
|
152
|
+
previous.record.reference === record.reference &&
|
|
153
|
+
previous.record.key === record.key
|
|
154
|
+
) {
|
|
155
|
+
for (const name of new Set([
|
|
156
|
+
...Object.keys(previous.record.slots ?? {}),
|
|
157
|
+
...Object.keys(record.slots ?? {}),
|
|
158
|
+
])) {
|
|
159
|
+
const slot = [...previous.host.querySelectorAll("nifra-slot[data-nifra-slot]")].find(
|
|
160
|
+
(element) => element.getAttribute("data-nifra-slot") === name,
|
|
161
|
+
)
|
|
162
|
+
if (slot === undefined) throw failure()
|
|
163
|
+
slots.push({
|
|
164
|
+
host: slot,
|
|
165
|
+
fragment: parseComponentHTML(
|
|
166
|
+
record.slots?.[name] ?? "",
|
|
167
|
+
slot,
|
|
168
|
+
this.#options.trustedHTML,
|
|
169
|
+
),
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
next.set(record.renderId, { ...previous, record })
|
|
173
|
+
} else {
|
|
174
|
+
const root = await this.#options.runtime.hydrate(
|
|
175
|
+
placeholder,
|
|
176
|
+
prepared.contracts.get(record.reference)!,
|
|
177
|
+
record,
|
|
178
|
+
current,
|
|
179
|
+
)
|
|
180
|
+
acquired.push(root)
|
|
181
|
+
await root.ready
|
|
182
|
+
if (!current() || this.#disposed) {
|
|
183
|
+
await Promise.all(acquired.map((root) => root.dispose()))
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
next.set(record.renderId, { host: placeholder, root, record })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (!current() || this.#disposed) {
|
|
190
|
+
await Promise.all(acquired.map((root) => root.dispose()))
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
const removed = [...this.#mounted.values()].filter(
|
|
194
|
+
(previous) => next.get(previous.record.renderId)?.root !== previous.root,
|
|
195
|
+
)
|
|
196
|
+
// No await inside this transaction: cancellation cannot detach a live retained root.
|
|
197
|
+
for (const [id, previous] of this.#mounted)
|
|
198
|
+
if (next.get(id)?.root === previous.root) nextHosts.get(id)!.replaceWith(previous.host)
|
|
199
|
+
for (const [id, child] of retainedChildren) prepared.nested.get(id)!.replaceWith(child)
|
|
200
|
+
for (const slot of slots) slot.host.replaceChildren(slot.fragment)
|
|
201
|
+
this.#host.replaceChildren(prepared.fragment)
|
|
202
|
+
this.#mounted = next
|
|
203
|
+
const updates: Array<void | Promise<void>> = []
|
|
204
|
+
for (const record of prepared.islands) {
|
|
205
|
+
if (!current() || this.#disposed) return
|
|
206
|
+
const previous = next.get(record.renderId)
|
|
207
|
+
if (previous !== undefined && !acquired.includes(previous.root))
|
|
208
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
209
|
+
updates.push(previous.root.update(record.props as Record<string, unknown>, current))
|
|
210
|
+
}
|
|
211
|
+
await Promise.all(updates)
|
|
212
|
+
if (active instanceof HTMLElement && active.isConnected && current()) {
|
|
213
|
+
active.focus({ preventScroll: true })
|
|
214
|
+
if (
|
|
215
|
+
selection !== undefined &&
|
|
216
|
+
(active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement) &&
|
|
217
|
+
selection[0] !== null &&
|
|
218
|
+
selection[1] !== null
|
|
219
|
+
)
|
|
220
|
+
active.setSelectionRange(selection[0], selection[1], selection[2] ?? undefined)
|
|
221
|
+
}
|
|
222
|
+
await Promise.all(removed.map((island) => island.root.dispose()))
|
|
223
|
+
} catch (error) {
|
|
224
|
+
await Promise.all(
|
|
225
|
+
acquired
|
|
226
|
+
.filter((root) => ![...this.#mounted.values()].some((island) => island.root === root))
|
|
227
|
+
.map((root) => root.dispose()),
|
|
228
|
+
)
|
|
229
|
+
if (current()) await this.dispose()
|
|
230
|
+
throw error
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
update(): never {
|
|
234
|
+
throw new ServerComponentError("component_contract")
|
|
235
|
+
}
|
|
236
|
+
async dispose(): Promise<void> {
|
|
237
|
+
if (this.#disposed) return
|
|
238
|
+
this.#disposed = true
|
|
239
|
+
try {
|
|
240
|
+
await Promise.all([...this.#mounted.values()].map((island) => island.root.dispose()))
|
|
241
|
+
} finally {
|
|
242
|
+
this.#mounted.clear()
|
|
243
|
+
this.#host.replaceChildren()
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export function nativeComponentClient(options: NativeRangeOptions): ComponentClientAdapter {
|
|
248
|
+
const contracts = new Map<string, ClientIsland>()
|
|
249
|
+
for (const contract of options.islands) {
|
|
250
|
+
if (
|
|
251
|
+
!isClientIsland(contract) ||
|
|
252
|
+
contract.reference === undefined ||
|
|
253
|
+
contracts.has(contract.reference)
|
|
254
|
+
)
|
|
255
|
+
throw new ServerComponentError("component_contract")
|
|
256
|
+
contracts.set(contract.reference, contract)
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
name: options.adapter,
|
|
260
|
+
async prepare(record, current, host, context) {
|
|
261
|
+
const islands: ComponentIsland[] = []
|
|
262
|
+
for (const island of record.islands ?? []) {
|
|
263
|
+
const contract = contracts.get(island.reference)
|
|
264
|
+
if (
|
|
265
|
+
contract === undefined ||
|
|
266
|
+
Object.keys(island.slots ?? {}).some((name) => !contract.slots.includes(name))
|
|
267
|
+
)
|
|
268
|
+
throw failure()
|
|
269
|
+
const props = await guardComponentProps(contract.props, island.props)
|
|
270
|
+
if (!current()) throw failure()
|
|
271
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
272
|
+
islands.push({ ...island, props: props as ComponentIsland["props"] })
|
|
273
|
+
}
|
|
274
|
+
const parseContext = host ?? document.createElement("nifra-component")
|
|
275
|
+
const fragment = parseComponentHTML(record.html, parseContext, options.trustedHTML)
|
|
276
|
+
hosts(fragment, islands)
|
|
277
|
+
const nested = new Map<string, Element>()
|
|
278
|
+
for (const child of fragment.querySelectorAll("[data-nifra-component]")) {
|
|
279
|
+
if (child.parentElement?.closest("[data-nifra-component]") != null) continue
|
|
280
|
+
const id = child.getAttribute("data-nifra-component")
|
|
281
|
+
if (
|
|
282
|
+
id === null ||
|
|
283
|
+
nested.has(id) ||
|
|
284
|
+
!(context?.records ?? []).some(
|
|
285
|
+
(item) => item.instance === id && item.parent === record.instance,
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
throw failure()
|
|
289
|
+
nested.set(id, child)
|
|
290
|
+
}
|
|
291
|
+
return { fragment, islands, contracts, nested } satisfies Prepared
|
|
292
|
+
},
|
|
293
|
+
async hydrate(host, _record, prepared, current) {
|
|
294
|
+
const range = new NativeRange(host, options)
|
|
295
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
296
|
+
await range.initialize(prepared as Prepared, current)
|
|
297
|
+
return range
|
|
298
|
+
},
|
|
299
|
+
async create(host, _record, prepared, current) {
|
|
300
|
+
if (!current()) throw failure()
|
|
301
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
302
|
+
host.replaceChildren((prepared as Prepared).fragment)
|
|
303
|
+
const range = new NativeRange(host, options)
|
|
304
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
305
|
+
await range.initialize(prepared as Prepared, current)
|
|
306
|
+
return range
|
|
307
|
+
},
|
|
308
|
+
update(root, _record, prepared, current) {
|
|
309
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Preparation validates the complete record and owned DOM topology; only this adapter creates the prepared plan and native range.
|
|
310
|
+
return (root as NativeRange).commit(prepared as Prepared, current)
|
|
311
|
+
},
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ComponentController } from "./client.ts"
|
|
2
|
+
|
|
3
|
+
export const componentControllers = new Set<ComponentController>()
|
|
4
|
+
let channel: BroadcastChannel | undefined
|
|
5
|
+
function connectInvalidation(): void {
|
|
6
|
+
if (
|
|
7
|
+
typeof window === "undefined" ||
|
|
8
|
+
typeof BroadcastChannel === "undefined" ||
|
|
9
|
+
channel !== undefined
|
|
10
|
+
)
|
|
11
|
+
return
|
|
12
|
+
channel = new BroadcastChannel("nifra-component-scope-v1")
|
|
13
|
+
channel.onmessage = (event: MessageEvent<unknown>) => {
|
|
14
|
+
if (event.data === 1)
|
|
15
|
+
for (const controller of componentControllers)
|
|
16
|
+
void controller.invalidateScope().catch(() => controller.reportError("component_failed"))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function invalidateComponentScope(): Promise<void> {
|
|
20
|
+
await Promise.all([...componentControllers].map((controller) => controller.invalidateScope()))
|
|
21
|
+
channel?.postMessage(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerComponentController(controller: ComponentController): void {
|
|
25
|
+
componentControllers.add(controller)
|
|
26
|
+
connectInvalidation()
|
|
27
|
+
}
|
|
28
|
+
export function unregisterComponentController(controller: ComponentController): void {
|
|
29
|
+
componentControllers.delete(controller)
|
|
30
|
+
if (componentControllers.size === 0) {
|
|
31
|
+
channel?.close()
|
|
32
|
+
channel = undefined
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { COMPONENT_ERROR_CODES, type ComponentErrorCode, ServerComponentError } from "./error.ts"
|
|
2
|
+
import { COMPONENT_LIMITS } from "./protocol.ts"
|
|
3
|
+
import { readComponentBytes } from "./stream.ts"
|
|
4
|
+
import { componentValue } from "./value.ts"
|
|
5
|
+
|
|
6
|
+
/** Read a small terminal envelope; an ordinary owner error keeps its HTTP status and safe code. */
|
|
7
|
+
export async function componentResponseFailure(
|
|
8
|
+
response: Response,
|
|
9
|
+
signal: AbortSignal,
|
|
10
|
+
): Promise<ServerComponentError> {
|
|
11
|
+
if (
|
|
12
|
+
response.body === null ||
|
|
13
|
+
response.headers.get("content-type")?.split(";")[0]?.trim() !== "application/json"
|
|
14
|
+
)
|
|
15
|
+
return new ServerComponentError("component_failed", response.status)
|
|
16
|
+
let value: unknown
|
|
17
|
+
try {
|
|
18
|
+
value = componentValue(
|
|
19
|
+
JSON.parse(
|
|
20
|
+
new TextDecoder("utf-8", { fatal: true }).decode(
|
|
21
|
+
await readComponentBytes(response.body, signal, COMPONENT_LIMITS.headerBytes),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
COMPONENT_LIMITS.headerBytes,
|
|
25
|
+
)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof ServerComponentError) throw error
|
|
28
|
+
return new ServerComponentError("component_failed", response.status)
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
31
|
+
return new ServerComponentError("component_failed", response.status)
|
|
32
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The bounded error envelope and exact error-code vocabulary are checked before constructing the typed failure.
|
|
33
|
+
const envelope = value as Record<string, unknown>
|
|
34
|
+
if (
|
|
35
|
+
Object.keys(envelope).length !== 2 ||
|
|
36
|
+
envelope.v !== 1 ||
|
|
37
|
+
typeof envelope.error !== "string" ||
|
|
38
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The bounded error envelope and exact error-code vocabulary are checked before constructing the typed failure.
|
|
39
|
+
!(COMPONENT_ERROR_CODES as readonly string[]).includes(envelope.error)
|
|
40
|
+
)
|
|
41
|
+
return new ServerComponentError("component_failed", response.status)
|
|
42
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: The bounded error envelope and exact error-code vocabulary are checked before constructing the typed failure.
|
|
43
|
+
return new ServerComponentError(envelope.error as ComponentErrorCode, response.status)
|
|
44
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ClientRouter } from "../router.ts"
|
|
2
|
+
import type { ComponentController } from "./client.ts"
|
|
3
|
+
import { componentControllers } from "./registry.ts"
|
|
4
|
+
|
|
5
|
+
/** Optional router integration; imported only by opted-in application entries. */
|
|
6
|
+
export function bindComponentRouter(
|
|
7
|
+
router: Pick<ClientRouter, "snapshot" | "subscribe">,
|
|
8
|
+
): () => void {
|
|
9
|
+
let previous = router.snapshot()
|
|
10
|
+
return router.subscribe(() => {
|
|
11
|
+
const next = router.snapshot()
|
|
12
|
+
if (next.pending && next.pendingPath !== previous.pendingPath)
|
|
13
|
+
for (const controller of componentControllers) controller.cancelPending()
|
|
14
|
+
if (
|
|
15
|
+
!next.pending &&
|
|
16
|
+
(next.serverComponents !== previous.serverComponents || next.path !== previous.path)
|
|
17
|
+
) {
|
|
18
|
+
for (const controller of componentControllers) {
|
|
19
|
+
const task =
|
|
20
|
+
next.serverComponents === undefined
|
|
21
|
+
? controller.navigate()
|
|
22
|
+
: controller.adopt(next.serverComponents, next.path)
|
|
23
|
+
void task.catch(() => controller.reportError("component_failed"))
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
previous = next
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Refresh only after the existing mutation transport reports success. */
|
|
31
|
+
export async function refreshAfterComponentMutation<T extends { readonly ok: boolean }>(
|
|
32
|
+
mutation: Promise<T>,
|
|
33
|
+
controller: ComponentController,
|
|
34
|
+
instances: readonly string[],
|
|
35
|
+
): Promise<T> {
|
|
36
|
+
const result = await mutation
|
|
37
|
+
if (result.ok)
|
|
38
|
+
await Promise.all([...new Set(instances)].map((instance) => controller.refresh(instance)))
|
|
39
|
+
return result
|
|
40
|
+
}
|