@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,663 @@
|
|
|
1
|
+
import { ServerComponentError } from "./error.ts"
|
|
2
|
+
import { readComponentFrames } from "./frames.ts"
|
|
3
|
+
import {
|
|
4
|
+
COMPONENT_HEADER,
|
|
5
|
+
COMPONENT_LIMITS,
|
|
6
|
+
COMPONENT_MEDIA_TYPE,
|
|
7
|
+
componentRequestHeader,
|
|
8
|
+
} from "./protocol.ts"
|
|
9
|
+
import { registerComponentController, unregisterComponentController } from "./registry.ts"
|
|
10
|
+
import { componentResponseFailure } from "./response.ts"
|
|
11
|
+
import { BoundedComponentTasks } from "./scheduler.ts"
|
|
12
|
+
import { parseComponentState } from "./state.ts"
|
|
13
|
+
import { readComponentBytes } from "./stream.ts"
|
|
14
|
+
import type { ComponentKey, ComponentRecord, ComponentState, ServerComponent } from "./types.ts"
|
|
15
|
+
import { componentInputKey, componentValue } from "./value.ts"
|
|
16
|
+
|
|
17
|
+
declare const __NIFRA_COMPONENT_BUILD__: string
|
|
18
|
+
|
|
19
|
+
export type CurrentComponent = () => boolean
|
|
20
|
+
export interface ComponentClientRoot {
|
|
21
|
+
update(record: ComponentRecord, current: CurrentComponent): void | Promise<void>
|
|
22
|
+
dispose(): void | Promise<void>
|
|
23
|
+
}
|
|
24
|
+
export interface ComponentClientAdapter {
|
|
25
|
+
readonly name: ComponentState["adapter"]
|
|
26
|
+
prepare(
|
|
27
|
+
record: ComponentRecord,
|
|
28
|
+
current: CurrentComponent,
|
|
29
|
+
host?: Element,
|
|
30
|
+
context?: { readonly records: readonly ComponentRecord[] },
|
|
31
|
+
): unknown | Promise<unknown>
|
|
32
|
+
hydrate(
|
|
33
|
+
host: Element,
|
|
34
|
+
record: ComponentRecord,
|
|
35
|
+
prepared: unknown,
|
|
36
|
+
current: CurrentComponent,
|
|
37
|
+
): ComponentClientRoot | Promise<ComponentClientRoot>
|
|
38
|
+
create?(
|
|
39
|
+
host: Element,
|
|
40
|
+
record: ComponentRecord,
|
|
41
|
+
prepared: unknown,
|
|
42
|
+
current: CurrentComponent,
|
|
43
|
+
): ComponentClientRoot | Promise<ComponentClientRoot>
|
|
44
|
+
update(
|
|
45
|
+
root: ComponentClientRoot,
|
|
46
|
+
record: ComponentRecord,
|
|
47
|
+
prepared: unknown,
|
|
48
|
+
current: CurrentComponent,
|
|
49
|
+
): void | Promise<void>
|
|
50
|
+
}
|
|
51
|
+
export interface ComponentControllerOptions {
|
|
52
|
+
readonly state: ComponentState
|
|
53
|
+
readonly adapter: ComponentClientAdapter
|
|
54
|
+
readonly ownerUrl?: string
|
|
55
|
+
readonly fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
|
56
|
+
readonly navigate?: (url: string) => void
|
|
57
|
+
readonly onError?: (code: string) => void
|
|
58
|
+
readonly reload?: () => void
|
|
59
|
+
}
|
|
60
|
+
interface Mounted {
|
|
61
|
+
readonly host: Element
|
|
62
|
+
record: ComponentRecord
|
|
63
|
+
generation: number
|
|
64
|
+
abort: AbortController
|
|
65
|
+
root?: ComponentClientRoot
|
|
66
|
+
ready: Promise<void>
|
|
67
|
+
}
|
|
68
|
+
function sameRecord(a: ComponentRecord, b: ComponentRecord): boolean {
|
|
69
|
+
return (
|
|
70
|
+
a.instance === b.instance &&
|
|
71
|
+
a.reference === b.reference &&
|
|
72
|
+
a.key === b.key &&
|
|
73
|
+
a.parent === b.parent &&
|
|
74
|
+
componentInputKey(a.input) === componentInputKey(b.input)
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
function cancelled(): ServerComponentError {
|
|
78
|
+
return new ServerComponentError("component_cancelled", 499)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class ComponentController {
|
|
82
|
+
#state: ComponentState
|
|
83
|
+
#source: ComponentState | undefined
|
|
84
|
+
#ownerUrl: string | undefined
|
|
85
|
+
get state(): ComponentState {
|
|
86
|
+
return this.#state
|
|
87
|
+
}
|
|
88
|
+
reportError(code: string): void {
|
|
89
|
+
this.#options.onError?.(code)
|
|
90
|
+
}
|
|
91
|
+
readonly #options: ComponentControllerOptions
|
|
92
|
+
readonly #mounted = new Map<string, Mounted>()
|
|
93
|
+
// Structural outlet anchors survive logout; their private records and native roots do not.
|
|
94
|
+
readonly #restorable = new Map<string, Element>()
|
|
95
|
+
readonly #teardown = new WeakMap<Element, Promise<void>>()
|
|
96
|
+
readonly #mountWaiters = new Map<string, Set<() => void>>()
|
|
97
|
+
readonly #tasks = new BoundedComponentTasks(4, COMPONENT_LIMITS.instances)
|
|
98
|
+
#routeEpoch = 0
|
|
99
|
+
#authEpoch = 0
|
|
100
|
+
#adoption = 0
|
|
101
|
+
#disposed = false
|
|
102
|
+
constructor(options: ComponentControllerOptions) {
|
|
103
|
+
this.#state = parseComponentState(options.state)
|
|
104
|
+
this.#options = options
|
|
105
|
+
this.#source = options.state
|
|
106
|
+
this.#ownerUrl = options.ownerUrl
|
|
107
|
+
if (this.state.adapter !== options.adapter.name)
|
|
108
|
+
throw new ServerComponentError("component_version", 409)
|
|
109
|
+
if (
|
|
110
|
+
typeof __NIFRA_COMPONENT_BUILD__ === "string" &&
|
|
111
|
+
this.state.build !== __NIFRA_COMPONENT_BUILD__
|
|
112
|
+
) {
|
|
113
|
+
this.#versionReload()
|
|
114
|
+
this.#state = { ...this.#state, records: [] }
|
|
115
|
+
this.#source = undefined
|
|
116
|
+
throw new ServerComponentError("component_version", 409)
|
|
117
|
+
}
|
|
118
|
+
registerComponentController(this)
|
|
119
|
+
}
|
|
120
|
+
async mount(host: Element, instance: string, fresh = false): Promise<void> {
|
|
121
|
+
await this.#mount(host, instance, fresh)
|
|
122
|
+
}
|
|
123
|
+
async #mount(host: Element, instance: string, fresh: boolean): Promise<void> {
|
|
124
|
+
await this.#teardown.get(host)
|
|
125
|
+
if (this.#disposed || this.#mounted.has(instance))
|
|
126
|
+
throw new ServerComponentError("component_contract")
|
|
127
|
+
const record = this.state.records.find((record) => record.instance === instance)
|
|
128
|
+
if (record === undefined) throw new ServerComponentError("component_unavailable", 404)
|
|
129
|
+
const mounted: Mounted = {
|
|
130
|
+
host,
|
|
131
|
+
record,
|
|
132
|
+
generation: 0,
|
|
133
|
+
abort: new AbortController(),
|
|
134
|
+
ready: Promise.resolve(),
|
|
135
|
+
}
|
|
136
|
+
this.#mounted.set(instance, mounted)
|
|
137
|
+
this.#restorable.delete(instance)
|
|
138
|
+
const signal = AbortSignal.any([
|
|
139
|
+
mounted.abort.signal,
|
|
140
|
+
AbortSignal.timeout(COMPONENT_LIMITS.operationMs),
|
|
141
|
+
])
|
|
142
|
+
const valid = this.#current(mounted)
|
|
143
|
+
const current = () => valid() && !signal.aborted
|
|
144
|
+
mounted.ready = this.#tasks
|
|
145
|
+
.run(signal, async () => {
|
|
146
|
+
const prepared = await this.#options.adapter.prepare(record, current, host, this.state)
|
|
147
|
+
if (!current()) throw cancelled()
|
|
148
|
+
const mount = fresh ? this.#options.adapter.create : this.#options.adapter.hydrate
|
|
149
|
+
if (mount === undefined) throw new ServerComponentError("component_contract")
|
|
150
|
+
const root = await mount.call(this.#options.adapter, host, record, prepared, current)
|
|
151
|
+
if (!current()) {
|
|
152
|
+
await root.dispose()
|
|
153
|
+
throw cancelled()
|
|
154
|
+
}
|
|
155
|
+
mounted.root = root
|
|
156
|
+
})
|
|
157
|
+
.then(async () => {
|
|
158
|
+
if (current()) await this.#syncChildren(mounted, this.#state.records, current)
|
|
159
|
+
})
|
|
160
|
+
for (const ready of this.#mountWaiters.get(instance) ?? []) ready()
|
|
161
|
+
this.#mountWaiters.delete(instance)
|
|
162
|
+
try {
|
|
163
|
+
await mounted.ready
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (this.#mounted.get(instance) === mounted) await this.unmount(instance)
|
|
166
|
+
throw error
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async whenReady(instance: string): Promise<void> {
|
|
170
|
+
const mounted = this.#mounted.get(instance)
|
|
171
|
+
if (mounted !== undefined) {
|
|
172
|
+
await mounted.ready
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
if (this.#disposed || !this.state.records.some((record) => record.instance === instance))
|
|
176
|
+
throw new ServerComponentError("component_unavailable", 404)
|
|
177
|
+
await new Promise<void>((resolve, reject) => {
|
|
178
|
+
const waiters = this.#mountWaiters.get(instance) ?? new Set<() => void>()
|
|
179
|
+
const ready = () => {
|
|
180
|
+
clearTimeout(timer)
|
|
181
|
+
waiters.delete(ready)
|
|
182
|
+
const mounted = this.#mounted.get(instance)
|
|
183
|
+
if (mounted === undefined) reject(cancelled())
|
|
184
|
+
else mounted.ready.then(resolve, reject)
|
|
185
|
+
}
|
|
186
|
+
const timer = setTimeout(() => {
|
|
187
|
+
waiters.delete(ready)
|
|
188
|
+
reject(new ServerComponentError("component_timeout", 504))
|
|
189
|
+
}, COMPONENT_LIMITS.operationMs)
|
|
190
|
+
waiters.add(ready)
|
|
191
|
+
this.#mountWaiters.set(instance, waiters)
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
#current(mounted: Mounted): CurrentComponent {
|
|
195
|
+
const generation = mounted.generation
|
|
196
|
+
const route = this.#routeEpoch
|
|
197
|
+
const auth = this.#authEpoch
|
|
198
|
+
return () =>
|
|
199
|
+
!this.#disposed &&
|
|
200
|
+
!mounted.abort.signal.aborted &&
|
|
201
|
+
this.#mounted.get(mounted.record.instance) === mounted &&
|
|
202
|
+
mounted.generation === generation &&
|
|
203
|
+
this.#routeEpoch === route &&
|
|
204
|
+
this.#authEpoch === auth
|
|
205
|
+
}
|
|
206
|
+
async refresh(instance: string, ownerUrl?: string): Promise<boolean> {
|
|
207
|
+
const mounted = this.#mounted.get(instance)
|
|
208
|
+
if (mounted === undefined || this.#disposed) return false
|
|
209
|
+
try {
|
|
210
|
+
await mounted.ready
|
|
211
|
+
} catch {
|
|
212
|
+
return false
|
|
213
|
+
}
|
|
214
|
+
if (this.#mounted.get(instance) !== mounted || this.#disposed) return false
|
|
215
|
+
// A newer child intent must not be overwritten by an older in-flight ancestor subtree.
|
|
216
|
+
let parent = mounted.record.parent
|
|
217
|
+
while (parent !== null) {
|
|
218
|
+
const ancestor = this.#mounted.get(parent)
|
|
219
|
+
if (ancestor === undefined) break
|
|
220
|
+
ancestor.abort.abort()
|
|
221
|
+
ancestor.abort = new AbortController()
|
|
222
|
+
ancestor.generation++
|
|
223
|
+
parent = ancestor.record.parent
|
|
224
|
+
}
|
|
225
|
+
mounted.abort.abort()
|
|
226
|
+
mounted.abort = new AbortController()
|
|
227
|
+
mounted.generation++
|
|
228
|
+
for (const child of this.#descendants(instance)) {
|
|
229
|
+
child.abort.abort()
|
|
230
|
+
child.abort = new AbortController()
|
|
231
|
+
child.generation++
|
|
232
|
+
}
|
|
233
|
+
const valid = this.#current(mounted)
|
|
234
|
+
const request = {
|
|
235
|
+
v: 1 as const,
|
|
236
|
+
build: this.state.build,
|
|
237
|
+
reference: mounted.record.reference,
|
|
238
|
+
key: mounted.record.key,
|
|
239
|
+
parent: mounted.record.parent,
|
|
240
|
+
input: mounted.record.input,
|
|
241
|
+
generation: mounted.generation,
|
|
242
|
+
routeEpoch: this.#routeEpoch,
|
|
243
|
+
authEpoch: this.#authEpoch,
|
|
244
|
+
}
|
|
245
|
+
const owner = new URL(ownerUrl ?? this.#ownerUrl ?? location.href, location.href)
|
|
246
|
+
if (owner.origin !== location.origin) throw new ServerComponentError("component_denied", 403)
|
|
247
|
+
const signal = AbortSignal.any([
|
|
248
|
+
mounted.abort.signal,
|
|
249
|
+
AbortSignal.timeout(COMPONENT_LIMITS.operationMs),
|
|
250
|
+
])
|
|
251
|
+
const current = () => valid() && !signal.aborted
|
|
252
|
+
let updating = false
|
|
253
|
+
try {
|
|
254
|
+
const committed = await this.#tasks.run(signal, async () => {
|
|
255
|
+
const response = await (this.#options.fetch ?? fetch)(owner, {
|
|
256
|
+
method: "GET",
|
|
257
|
+
credentials: "same-origin",
|
|
258
|
+
cache: "no-store",
|
|
259
|
+
redirect: "manual",
|
|
260
|
+
signal,
|
|
261
|
+
headers: {
|
|
262
|
+
[COMPONENT_HEADER]: componentRequestHeader(request),
|
|
263
|
+
accept: COMPONENT_MEDIA_TYPE,
|
|
264
|
+
},
|
|
265
|
+
})
|
|
266
|
+
if (!current()) return false
|
|
267
|
+
if (
|
|
268
|
+
response.type === "opaqueredirect" ||
|
|
269
|
+
(response.status >= 300 && response.status < 400)
|
|
270
|
+
) {
|
|
271
|
+
this.#options.navigate?.(owner.href)
|
|
272
|
+
if (this.#options.navigate === undefined) location.assign(owner.href)
|
|
273
|
+
return false
|
|
274
|
+
}
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
if ([401, 403, 404].includes(response.status)) {
|
|
277
|
+
this.#options.onError?.("component_denied")
|
|
278
|
+
await this.invalidateScope()
|
|
279
|
+
return false
|
|
280
|
+
}
|
|
281
|
+
const failure = await componentResponseFailure(response, signal)
|
|
282
|
+
if (failure.code === "component_version") {
|
|
283
|
+
this.#versionReload()
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
throw failure
|
|
287
|
+
}
|
|
288
|
+
if (response.headers.has("x-nifra-component-control")) {
|
|
289
|
+
if (response.body === null) throw new ServerComponentError("component_protocol", 400)
|
|
290
|
+
let value: unknown
|
|
291
|
+
try {
|
|
292
|
+
value = JSON.parse(
|
|
293
|
+
new TextDecoder("utf-8", { fatal: true }).decode(
|
|
294
|
+
await readComponentBytes(response.body, signal, COMPONENT_LIMITS.headerBytes),
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
} catch {
|
|
298
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
299
|
+
}
|
|
300
|
+
const checked = componentValue(value, COMPONENT_LIMITS.headerBytes)
|
|
301
|
+
if (typeof checked !== "object" || checked === null || Array.isArray(checked))
|
|
302
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
303
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Only this adapter produces its prepared tree and native root; component records and public props are validated before native preparation.
|
|
304
|
+
const control = checked as Record<string, unknown>
|
|
305
|
+
if (
|
|
306
|
+
Object.keys(control).length !== 2 ||
|
|
307
|
+
control.v !== 1 ||
|
|
308
|
+
typeof control.control !== "object" ||
|
|
309
|
+
control.control === null ||
|
|
310
|
+
Array.isArray(control.control)
|
|
311
|
+
)
|
|
312
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
313
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Only this adapter produces its prepared tree and native root; component records and public props are validated before native preparation.
|
|
314
|
+
const terminal = control.control as Record<string, unknown>
|
|
315
|
+
if (
|
|
316
|
+
Object.keys(terminal).length !== 1 ||
|
|
317
|
+
typeof terminal.status !== "number" ||
|
|
318
|
+
!Number.isSafeInteger(terminal.status) ||
|
|
319
|
+
terminal.status < 200 ||
|
|
320
|
+
terminal.status > 299 ||
|
|
321
|
+
response.headers.get("x-nifra-component-control") !== String(terminal.status)
|
|
322
|
+
)
|
|
323
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
324
|
+
if (current()) {
|
|
325
|
+
this.#options.navigate?.(owner.href)
|
|
326
|
+
if (this.#options.navigate === undefined) location.assign(owner.href)
|
|
327
|
+
}
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
if (
|
|
331
|
+
response.headers.get("content-type")?.split(";")[0]?.trim() !== COMPONENT_MEDIA_TYPE ||
|
|
332
|
+
response.body === null
|
|
333
|
+
)
|
|
334
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
335
|
+
const decoded = await readComponentFrames(response.body, signal)
|
|
336
|
+
const next = parseComponentState(
|
|
337
|
+
decoded.value,
|
|
338
|
+
COMPONENT_LIMITS.outputBytes,
|
|
339
|
+
mounted.record.parent,
|
|
340
|
+
)
|
|
341
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Only this adapter produces its prepared tree and native root; component records and public props are validated before native preparation.
|
|
342
|
+
const meta = componentValue(decoded.metadata, COMPONENT_LIMITS.headerBytes) as Record<
|
|
343
|
+
string,
|
|
344
|
+
unknown
|
|
345
|
+
>
|
|
346
|
+
const expected = {
|
|
347
|
+
...request,
|
|
348
|
+
input: undefined,
|
|
349
|
+
adapter: next.adapter,
|
|
350
|
+
owner: next.owner,
|
|
351
|
+
scope: next.scope,
|
|
352
|
+
instance: next.records[0]?.instance,
|
|
353
|
+
}
|
|
354
|
+
const keys = Object.keys(expected).filter((key) => key !== "input")
|
|
355
|
+
if (
|
|
356
|
+
typeof meta !== "object" ||
|
|
357
|
+
meta === null ||
|
|
358
|
+
Array.isArray(meta) ||
|
|
359
|
+
Object.keys(meta).length !== keys.length ||
|
|
360
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Only this adapter produces its prepared tree and native root; component records and public props are validated before native preparation.
|
|
361
|
+
keys.some((key) => meta[key] !== expected[key as keyof typeof expected]) ||
|
|
362
|
+
next.build !== this.state.build ||
|
|
363
|
+
next.adapter !== this.state.adapter ||
|
|
364
|
+
next.owner !== this.state.owner ||
|
|
365
|
+
next.records.length < 1 ||
|
|
366
|
+
!sameRecord(mounted.record, next.records[0]!)
|
|
367
|
+
)
|
|
368
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
369
|
+
const incoming = new Map(next.records.map((record) => [record.instance, record]))
|
|
370
|
+
for (const record of next.records.slice(1)) {
|
|
371
|
+
let parent = record.parent
|
|
372
|
+
let depth = 0
|
|
373
|
+
while (parent !== instance) {
|
|
374
|
+
if (parent === null || ++depth >= 16 || !incoming.has(parent))
|
|
375
|
+
throw new ServerComponentError("component_protocol", 400)
|
|
376
|
+
parent = incoming.get(parent)!.parent
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (next.scope !== this.state.scope) {
|
|
380
|
+
this.#options.onError?.("component_denied")
|
|
381
|
+
await this.invalidateScope()
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
if (!current()) return false
|
|
385
|
+
const record = next.records[0]!
|
|
386
|
+
const prepared = await this.#options.adapter.prepare(record, current, mounted.host, next)
|
|
387
|
+
if (!current() || signal.aborted) return false
|
|
388
|
+
if (mounted.root === undefined) throw new ServerComponentError("component_contract")
|
|
389
|
+
updating = true
|
|
390
|
+
await this.#options.adapter.update(mounted.root, record, prepared, current)
|
|
391
|
+
if (!current()) return false
|
|
392
|
+
const replaced = recordSubtree(this.#state.records, instance)
|
|
393
|
+
this.#state = {
|
|
394
|
+
...this.#state,
|
|
395
|
+
records: [
|
|
396
|
+
...this.#state.records.filter((item) => !replaced.has(item.instance)),
|
|
397
|
+
...next.records,
|
|
398
|
+
],
|
|
399
|
+
}
|
|
400
|
+
mounted.record = record
|
|
401
|
+
return true
|
|
402
|
+
})
|
|
403
|
+
if (committed && current()) await this.#syncChildren(mounted, this.#state.records, current)
|
|
404
|
+
return committed && current()
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (!current() || signal.aborted) return false
|
|
407
|
+
const failure =
|
|
408
|
+
error instanceof ServerComponentError ? error : new ServerComponentError("component_failed")
|
|
409
|
+
this.#options.onError?.(failure.code)
|
|
410
|
+
if (updating) await this.unmount(instance)
|
|
411
|
+
throw failure
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
#versionReload(): void {
|
|
415
|
+
const key = `nifra-component-reload:${this.state.build}`
|
|
416
|
+
let used = false
|
|
417
|
+
try {
|
|
418
|
+
used = sessionStorage.getItem(key) === "1"
|
|
419
|
+
if (!used) sessionStorage.setItem(key, "1")
|
|
420
|
+
} catch {
|
|
421
|
+
used = reloadedBuilds.has(this.state.build)
|
|
422
|
+
}
|
|
423
|
+
reloadedBuilds.add(this.state.build)
|
|
424
|
+
if (used) {
|
|
425
|
+
this.#options.onError?.("component_version")
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
this.cancelPending()
|
|
429
|
+
if (this.#options.reload !== undefined) this.#options.reload()
|
|
430
|
+
else location.reload()
|
|
431
|
+
}
|
|
432
|
+
/** Abort networking immediately while retaining the tree until verified navigation arrives. */
|
|
433
|
+
cancelPending(): void {
|
|
434
|
+
this.#adoption++
|
|
435
|
+
this.#routeEpoch++
|
|
436
|
+
for (const mounted of this.#mounted.values()) {
|
|
437
|
+
mounted.abort.abort()
|
|
438
|
+
mounted.abort = new AbortController()
|
|
439
|
+
mounted.generation++
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async adopt(state: ComponentState, ownerUrl?: string): Promise<void> {
|
|
443
|
+
if (ownerUrl !== undefined) this.#ownerUrl = ownerUrl
|
|
444
|
+
if (this.#disposed || state === this.#source) return
|
|
445
|
+
const next = parseComponentState(state)
|
|
446
|
+
if (next.build !== this.state.build || next.adapter !== this.state.adapter) {
|
|
447
|
+
this.#versionReload()
|
|
448
|
+
return
|
|
449
|
+
}
|
|
450
|
+
const targets = new Map(
|
|
451
|
+
[...this.#restorable].map(([id, host]) => [id, { host, mounted: this.#mounted.get(id) }]),
|
|
452
|
+
)
|
|
453
|
+
for (const mounted of this.#mounted.values())
|
|
454
|
+
if (mounted.record.parent === null || !this.#mounted.has(mounted.record.parent))
|
|
455
|
+
targets.set(mounted.record.instance, { host: mounted.host, mounted })
|
|
456
|
+
this.cancelPending()
|
|
457
|
+
const adoption = this.#adoption
|
|
458
|
+
const changedScope = next.owner !== this.state.owner || next.scope !== this.state.scope
|
|
459
|
+
if (changedScope) await this.#clearScope()
|
|
460
|
+
if (this.#disposed || adoption !== this.#adoption) return
|
|
461
|
+
this.#state = next
|
|
462
|
+
this.#source = state
|
|
463
|
+
this.#restorable.clear()
|
|
464
|
+
for (const [id, target] of targets) {
|
|
465
|
+
if (this.#disposed || adoption !== this.#adoption) return
|
|
466
|
+
const mounted = target.mounted
|
|
467
|
+
const record = next.records.find((item) => item.instance === id)
|
|
468
|
+
if (record === undefined) {
|
|
469
|
+
await this.unmount(id)
|
|
470
|
+
continue
|
|
471
|
+
}
|
|
472
|
+
if (mounted !== undefined && !changedScope && !sameRecord(mounted.record, record))
|
|
473
|
+
await this.unmount(id)
|
|
474
|
+
try {
|
|
475
|
+
await mounted?.ready
|
|
476
|
+
} catch {
|
|
477
|
+
/* Cancelled initial hydration owns its error and can retry from verified state. */
|
|
478
|
+
}
|
|
479
|
+
if (this.#disposed || adoption !== this.#adoption) return
|
|
480
|
+
if (!this.#mounted.has(id)) {
|
|
481
|
+
try {
|
|
482
|
+
await this.#mount(target.host, id, true)
|
|
483
|
+
} catch (error) {
|
|
484
|
+
if (adoption === this.#adoption) throw error
|
|
485
|
+
}
|
|
486
|
+
continue
|
|
487
|
+
}
|
|
488
|
+
if (mounted === undefined || this.#mounted.get(id) !== mounted) continue
|
|
489
|
+
const current = this.#current(mounted)
|
|
490
|
+
if (!current()) continue
|
|
491
|
+
const prepared = await this.#options.adapter.prepare(record, current, mounted.host, next)
|
|
492
|
+
if (!current() || mounted.root === undefined) continue
|
|
493
|
+
await this.#options.adapter.update(mounted.root, record, prepared, current)
|
|
494
|
+
if (current()) {
|
|
495
|
+
mounted.record = record
|
|
496
|
+
await this.#syncChildren(mounted, next.records, current)
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
#descendants(instance: string): Mounted[] {
|
|
501
|
+
const ids = new Set([instance])
|
|
502
|
+
const descendants: Mounted[] = []
|
|
503
|
+
for (let depth = 0; depth < 16; depth++)
|
|
504
|
+
for (const mounted of this.#mounted.values()) {
|
|
505
|
+
if (
|
|
506
|
+
mounted.record.parent !== null &&
|
|
507
|
+
ids.has(mounted.record.parent) &&
|
|
508
|
+
!ids.has(mounted.record.instance)
|
|
509
|
+
) {
|
|
510
|
+
ids.add(mounted.record.instance)
|
|
511
|
+
descendants.push(mounted)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return descendants
|
|
515
|
+
}
|
|
516
|
+
async #syncChildren(
|
|
517
|
+
parent: Mounted,
|
|
518
|
+
records: readonly ComponentRecord[],
|
|
519
|
+
current: CurrentComponent,
|
|
520
|
+
): Promise<void> {
|
|
521
|
+
if (typeof parent.host.querySelectorAll !== "function") return
|
|
522
|
+
const targets = new Map<string, Element>()
|
|
523
|
+
for (const host of parent.host.querySelectorAll("[data-nifra-component]")) {
|
|
524
|
+
if (host.parentElement?.closest("[data-nifra-component]") !== parent.host) continue
|
|
525
|
+
const id = host.getAttribute("data-nifra-component")
|
|
526
|
+
if (
|
|
527
|
+
id === null ||
|
|
528
|
+
targets.has(id) ||
|
|
529
|
+
!records.some(
|
|
530
|
+
(record) => record.instance === id && record.parent === parent.record.instance,
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
throw new ServerComponentError("component_contract")
|
|
534
|
+
targets.set(id, host)
|
|
535
|
+
}
|
|
536
|
+
for (const child of [...this.#mounted.values()].filter(
|
|
537
|
+
(item) => item.record.parent === parent.record.instance,
|
|
538
|
+
)) {
|
|
539
|
+
const record = records.find((item) => item.instance === child.record.instance)
|
|
540
|
+
if (
|
|
541
|
+
record === undefined ||
|
|
542
|
+
targets.get(child.record.instance) !== child.host ||
|
|
543
|
+
!sameRecord(child.record, record)
|
|
544
|
+
)
|
|
545
|
+
await this.unmount(child.record.instance)
|
|
546
|
+
}
|
|
547
|
+
for (const [id, host] of targets) {
|
|
548
|
+
if (!current()) return
|
|
549
|
+
const record = records.find((item) => item.instance === id)!
|
|
550
|
+
const child = this.#mounted.get(id)
|
|
551
|
+
if (child === undefined) {
|
|
552
|
+
await this.#mount(host, id, host.childNodes.length === 0)
|
|
553
|
+
continue
|
|
554
|
+
}
|
|
555
|
+
await child.ready
|
|
556
|
+
const valid = this.#current(child)
|
|
557
|
+
const prepared = await this.#options.adapter.prepare(
|
|
558
|
+
record,
|
|
559
|
+
() => current() && valid(),
|
|
560
|
+
host,
|
|
561
|
+
{ records },
|
|
562
|
+
)
|
|
563
|
+
if (!current() || !valid() || child.root === undefined) return
|
|
564
|
+
await this.#options.adapter.update(child.root, record, prepared, () => current() && valid())
|
|
565
|
+
if (current() && valid()) {
|
|
566
|
+
child.record = record
|
|
567
|
+
await this.#syncChildren(child, records, () => current() && valid())
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async unmount(instance: string): Promise<void> {
|
|
572
|
+
this.#restorable.delete(instance)
|
|
573
|
+
await this.#unmount(instance)
|
|
574
|
+
}
|
|
575
|
+
async #unmount(instance: string): Promise<void> {
|
|
576
|
+
const mounted = this.#mounted.get(instance)
|
|
577
|
+
if (mounted === undefined) return
|
|
578
|
+
this.#mounted.delete(instance)
|
|
579
|
+
mounted.abort.abort()
|
|
580
|
+
const children = [...this.#mounted.values()]
|
|
581
|
+
.filter((child) => child.record.parent === instance)
|
|
582
|
+
.map((child) => this.#unmount(child.record.instance))
|
|
583
|
+
const teardown = (async () => {
|
|
584
|
+
await Promise.all(children)
|
|
585
|
+
await mounted.ready.catch(() => {
|
|
586
|
+
/* Unmounted hydration cannot publish; its primary error belongs to mount. */
|
|
587
|
+
})
|
|
588
|
+
await mounted.root?.dispose()
|
|
589
|
+
})()
|
|
590
|
+
this.#teardown.set(mounted.host, teardown)
|
|
591
|
+
try {
|
|
592
|
+
await teardown
|
|
593
|
+
} finally {
|
|
594
|
+
if (this.#teardown.get(mounted.host) === teardown) this.#teardown.delete(mounted.host)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async #clearScope(preserveOutlets = false): Promise<void> {
|
|
598
|
+
this.#authEpoch++
|
|
599
|
+
if (preserveOutlets) {
|
|
600
|
+
for (const mounted of this.#mounted.values())
|
|
601
|
+
if (mounted.record.parent === null || !this.#mounted.has(mounted.record.parent))
|
|
602
|
+
this.#restorable.set(mounted.record.instance, mounted.host)
|
|
603
|
+
} else this.#restorable.clear()
|
|
604
|
+
this.#state = { ...this.#state, records: [] }
|
|
605
|
+
this.#source = undefined
|
|
606
|
+
await Promise.all([...this.#mounted.keys()].map((instance) => this.#unmount(instance)))
|
|
607
|
+
}
|
|
608
|
+
async invalidateScope(): Promise<void> {
|
|
609
|
+
this.#adoption++
|
|
610
|
+
await this.#clearScope(true)
|
|
611
|
+
}
|
|
612
|
+
async navigate(): Promise<void> {
|
|
613
|
+
this.cancelPending()
|
|
614
|
+
await this.#clearScope()
|
|
615
|
+
}
|
|
616
|
+
async dispose(): Promise<void> {
|
|
617
|
+
this.#disposed = true
|
|
618
|
+
unregisterComponentController(this)
|
|
619
|
+
for (const waiters of this.#mountWaiters.values()) for (const ready of waiters) ready()
|
|
620
|
+
this.#mountWaiters.clear()
|
|
621
|
+
await this.navigate()
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const reloadedBuilds = new Set<string>()
|
|
625
|
+
|
|
626
|
+
function recordSubtree(records: readonly ComponentRecord[], instance: string): Set<string> {
|
|
627
|
+
const ids = new Set([instance])
|
|
628
|
+
for (let depth = 0; depth < 16; depth++)
|
|
629
|
+
for (const record of records)
|
|
630
|
+
if (record.parent !== null && ids.has(record.parent)) ids.add(record.instance)
|
|
631
|
+
return ids
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export function resolveComponentRecord<Input>(
|
|
635
|
+
state: ComponentState,
|
|
636
|
+
component: Pick<ServerComponent<Input>, "reference" | "input">,
|
|
637
|
+
key: ComponentKey,
|
|
638
|
+
input: NoInfer<Input>,
|
|
639
|
+
parent: string | null = null,
|
|
640
|
+
): ComponentRecord {
|
|
641
|
+
const checked = parseComponentState(state)
|
|
642
|
+
const serialized = componentInputKey(componentValue(input, COMPONENT_LIMITS.inputBytes))
|
|
643
|
+
const matches = checked.records.filter(
|
|
644
|
+
(record) =>
|
|
645
|
+
record.reference === component.reference &&
|
|
646
|
+
record.key === key &&
|
|
647
|
+
record.parent === parent &&
|
|
648
|
+
componentInputKey(record.input) === serialized,
|
|
649
|
+
)
|
|
650
|
+
if (matches.length !== 1) throw new ServerComponentError("component_unavailable", 404)
|
|
651
|
+
return matches[0]!
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export { invalidateComponentScope } from "./registry.ts"
|
|
655
|
+
export { bindComponentRouter, refreshAfterComponentMutation } from "./router-integration.ts"
|
|
656
|
+
export { parseComponentState } from "./state.ts"
|
|
657
|
+
export type {
|
|
658
|
+
ComponentKey,
|
|
659
|
+
ComponentRecord,
|
|
660
|
+
ComponentState,
|
|
661
|
+
ServerComponent,
|
|
662
|
+
ServerComponentHandle,
|
|
663
|
+
} from "./types.ts"
|