@ecopages/core 0.2.0-alpha.12 → 0.2.0-alpha.13

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +7 -28
  2. package/README.md +5 -4
  3. package/package.json +2 -2
  4. package/src/adapters/bun/hmr-manager.js +2 -2
  5. package/src/adapters/node/node-hmr-manager.js +2 -2
  6. package/src/adapters/node/server-adapter.d.ts +2 -2
  7. package/src/adapters/node/server-adapter.js +5 -5
  8. package/src/build/build-adapter.d.ts +7 -6
  9. package/src/build/build-adapter.js +6 -7
  10. package/src/eco/eco.js +15 -6
  11. package/src/eco/eco.utils.d.ts +1 -1
  12. package/src/eco/eco.utils.js +5 -1
  13. package/src/hmr/hmr-strategy.d.ts +2 -2
  14. package/src/integrations/ghtml/ghtml-renderer.d.ts +6 -1
  15. package/src/integrations/ghtml/ghtml-renderer.js +29 -28
  16. package/src/plugins/integration-plugin.d.ts +1 -24
  17. package/src/plugins/integration-plugin.js +0 -14
  18. package/src/route-renderer/GRAPH.md +54 -84
  19. package/src/route-renderer/README.md +11 -22
  20. package/src/route-renderer/orchestration/component-render-context.d.ts +33 -84
  21. package/src/route-renderer/orchestration/component-render-context.js +30 -108
  22. package/src/route-renderer/orchestration/integration-renderer.d.ts +219 -96
  23. package/src/route-renderer/orchestration/integration-renderer.js +416 -236
  24. package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +93 -0
  25. package/src/route-renderer/orchestration/queued-boundary-runtime.service.js +155 -0
  26. package/src/route-renderer/orchestration/render-execution.service.d.ts +8 -71
  27. package/src/route-renderer/orchestration/render-execution.service.js +28 -115
  28. package/src/route-renderer/orchestration/render-output.utils.d.ts +6 -0
  29. package/src/route-renderer/orchestration/render-output.utils.js +25 -0
  30. package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -9
  31. package/src/route-renderer/orchestration/render-preparation.service.js +3 -34
  32. package/src/route-renderer/page-loading/dependency-resolver.js +6 -1
  33. package/src/route-renderer/page-loading/page-module-loader.d.ts +1 -2
  34. package/src/route-renderer/page-loading/page-module-loader.js +0 -2
  35. package/src/router/client/navigation-coordinator.js +2 -2
  36. package/src/router/server/fs-router-scanner.js +6 -1
  37. package/src/services/runtime-state/dev-graph.service.d.ts +5 -5
  38. package/src/services/runtime-state/dev-graph.service.js +10 -10
  39. package/src/types/public-types.d.ts +2 -5
  40. package/src/eco/component-render-context.d.ts +0 -2
  41. package/src/eco/component-render-context.js +0 -12
  42. package/src/route-renderer/component-graph/component-graph-executor.d.ts +0 -33
  43. package/src/route-renderer/component-graph/component-graph-executor.js +0 -30
  44. package/src/route-renderer/component-graph/component-graph.d.ts +0 -53
  45. package/src/route-renderer/component-graph/component-graph.js +0 -94
  46. package/src/route-renderer/component-graph/component-marker.d.ts +0 -52
  47. package/src/route-renderer/component-graph/component-marker.js +0 -44
  48. package/src/route-renderer/component-graph/component-reference.d.ts +0 -10
  49. package/src/route-renderer/component-graph/component-reference.js +0 -34
  50. package/src/route-renderer/component-graph/marker-graph-resolver.d.ts +0 -79
  51. package/src/route-renderer/component-graph/marker-graph-resolver.js +0 -117
@@ -1,48 +1,4 @@
1
- import { createComponentMarker, parseComponentMarkers } from "../component-graph/component-marker.js";
2
- import { getComponentReference } from "../component-graph/component-reference.js";
3
1
  import { addTriggerAttribute, isThenable, wrapWithScriptsInjector } from "./render-output.utils.js";
4
- function isMarkerSerializableNodeLike(value) {
5
- return typeof value === "object" && value !== null && "nodeType" in value;
6
- }
7
- function serializeDeferredNodeLike(node) {
8
- if (typeof node.outerHTML === "string") {
9
- return node.outerHTML;
10
- }
11
- if (node.nodeType === 3) {
12
- return node.textContent ?? "";
13
- }
14
- if (Array.isArray(node.childNodes)) {
15
- return node.childNodes.map((child) => serializeDeferredNodeLike(child) ?? "").join("");
16
- }
17
- return node.textContent ?? void 0;
18
- }
19
- function serializeDeferredChildren(value, seen = /* @__PURE__ */ new Set(), serializeDeferredValue) {
20
- if (typeof value === "string") {
21
- return value;
22
- }
23
- if (typeof value === "number" || typeof value === "bigint") {
24
- return String(value);
25
- }
26
- if (typeof value === "boolean" || value == null) {
27
- return "";
28
- }
29
- if (Array.isArray(value)) {
30
- return value.map((item) => serializeDeferredChildren(item, seen, serializeDeferredValue) ?? "").join("");
31
- }
32
- if (isMarkerSerializableNodeLike(value)) {
33
- return serializeDeferredNodeLike(value);
34
- }
35
- if (serializeDeferredValue) {
36
- const serializedTemplate = serializeDeferredValue(
37
- value,
38
- (templateValue) => serializeDeferredChildren(templateValue, seen, serializeDeferredValue)
39
- );
40
- if (serializedTemplate !== void 0) {
41
- return serializedTemplate;
42
- }
43
- }
44
- return void 0;
45
- }
46
2
  class ComponentRenderOutputRuntime {
47
3
  finalizeComponentRender(component, content) {
48
4
  const lazyTriggers = component.config?._resolvedLazyTriggers;
@@ -74,48 +30,36 @@ class ContextualComponentRenderRuntime extends ComponentRenderOutputRuntime {
74
30
  super();
75
31
  this.context = context;
76
32
  }
33
+ applyBoundaryInterceptionResult(result) {
34
+ if (result.kind === "resolved") {
35
+ return result.value;
36
+ }
37
+ return void 0;
38
+ }
77
39
  /**
78
- * Emits one deferred boundary marker when boundary policy selects `defer`.
40
+ * Resolves one boundary interception through the active runtime.
79
41
  *
80
- * Serialized child HTML is stored in `propsByRef`. If that HTML already contains
81
- * nested `eco-marker` nodes, their ids are also captured in `slotChildrenByRef`
82
- * so the downstream graph resolver can stitch the correct resolved children back
83
- * into the parent boundary.
42
+ * The runtime may choose inline rendering or immediate resolved output.
84
43
  */
85
- tryRenderDeferredBoundary(input) {
86
- const shouldEmitMarker = this.context.boundaryContext.decideBoundaryRender({
44
+ interceptBoundary(input) {
45
+ const boundaryRuntimeInput = {
87
46
  currentIntegration: this.context.currentIntegration,
88
47
  targetIntegration: input.targetIntegration,
89
- component: input.component
90
- }) === "defer";
91
- if (!shouldEmitMarker) {
92
- return void 0;
93
- }
94
- const nodeId = createNodeId(this.context);
95
- const propsRef = createPropsRef(this.context);
96
- const componentRef = getComponentReference(input.component);
97
- const storedProps = { ...input.props };
98
- const serializedChildren = serializeDeferredChildren(
99
- input.props.children,
100
- void 0,
101
- this.context.serializeDeferredValue
102
- );
103
- this.context.propsByRef[propsRef] = serializedChildren === void 0 ? storedProps : { ...storedProps, children: serializedChildren };
104
- let slotRef;
105
- if (typeof serializedChildren === "string" && serializedChildren.includes("<eco-marker")) {
106
- const childMarkers = parseComponentMarkers(serializedChildren);
107
- if (childMarkers.length > 0) {
108
- slotRef = createSlotRef(this.context);
109
- this.context.slotChildrenByRef[slotRef] = childMarkers.map((marker) => marker.nodeId);
48
+ component: input.component,
49
+ props: input.props
50
+ };
51
+ const asyncInterception = this.context.boundaryRuntime?.interceptBoundary?.(boundaryRuntimeInput);
52
+ if (asyncInterception !== void 0) {
53
+ if (isThenable(asyncInterception)) {
54
+ return asyncInterception.then((result) => this.applyBoundaryInterceptionResult(result));
110
55
  }
56
+ return this.applyBoundaryInterceptionResult(asyncInterception);
111
57
  }
112
- return createComponentMarker({
113
- nodeId,
114
- integration: input.targetIntegration,
115
- componentRef,
116
- propsRef,
117
- slotRef
118
- });
58
+ const syncInterception = this.context.boundaryRuntime?.interceptBoundarySync?.(boundaryRuntimeInput);
59
+ if (syncInterception === void 0) {
60
+ return void 0;
61
+ }
62
+ return this.applyBoundaryInterceptionResult(syncInterception);
119
63
  }
120
64
  }
121
65
  const GLOBAL_COMPONENT_RENDER_CONTEXT_STATE_KEY = "__ECOPAGES_COMPONENT_RENDER_CONTEXT_STATE__";
@@ -162,21 +106,9 @@ function getComponentRenderContext() {
162
106
  const state = getComponentRenderContextState();
163
107
  return state.nodeContextStorage?.getStore() ?? state.contextStack[state.contextStack.length - 1];
164
108
  }
165
- function createNodeId(context) {
166
- context.nextNodeId += 1;
167
- return `n_${context.nextNodeId}`;
168
- }
169
- function createPropsRef(context) {
170
- context.nextPropsRefId += 1;
171
- return `p_${context.nextPropsRefId}`;
172
- }
173
- function createSlotRef(context) {
174
- context.nextSlotRefId += 1;
175
- return `s_${context.nextSlotRefId}`;
176
- }
177
109
  const componentRenderOutputRuntime = new ComponentRenderOutputRuntime();
178
- function tryRenderDeferredBoundary(input) {
179
- return getComponentRenderContext()?.tryRenderDeferredBoundary(input);
110
+ function interceptComponentBoundary(input) {
111
+ return getComponentRenderContext()?.interceptBoundary(input);
180
112
  }
181
113
  function finalizeComponentRender(component, content) {
182
114
  const renderContext = getComponentRenderContext();
@@ -185,16 +117,10 @@ function finalizeComponentRender(component, content) {
185
117
  async function runWithComponentRenderContext(input, render) {
186
118
  const context = {
187
119
  currentIntegration: input.currentIntegration,
188
- boundaryContext: input.boundaryContext,
189
- serializeDeferredValue: input.serializeDeferredValue,
190
- nextNodeId: 0,
191
- nextPropsRefId: 0,
192
- nextSlotRefId: 0,
193
- propsByRef: {},
194
- slotChildrenByRef: {}
120
+ boundaryRuntime: input.boundaryRuntime
195
121
  };
196
122
  const runtime = new ContextualComponentRenderRuntime(context);
197
- context.tryRenderDeferredBoundary = (deferredInput) => runtime.tryRenderDeferredBoundary(deferredInput);
123
+ context.interceptBoundary = (deferredInput) => runtime.interceptBoundary(deferredInput);
198
124
  context.finalizeComponentRender = (component, content) => runtime.finalizeComponentRender(component, content);
199
125
  const storage = await getContextStorage();
200
126
  let value;
@@ -210,16 +136,12 @@ async function runWithComponentRenderContext(input, render) {
210
136
  }
211
137
  }
212
138
  return {
213
- value,
214
- graphContext: {
215
- propsByRef: context.propsByRef,
216
- slotChildrenByRef: context.slotChildrenByRef
217
- }
139
+ value
218
140
  };
219
141
  }
220
142
  export {
221
143
  finalizeComponentRender,
222
144
  getComponentRenderContext,
223
- runWithComponentRenderContext,
224
- tryRenderDeferredBoundary
145
+ interceptComponentBoundary,
146
+ runWithComponentRenderContext
225
147
  };
@@ -10,21 +10,14 @@ import { HtmlTransformerService } from '../../services/html/html-transformer.ser
10
10
  import { HttpError } from '../../errors/http-error.js';
11
11
  import { DependencyResolverService } from '../page-loading/dependency-resolver.js';
12
12
  import { PageModuleLoaderService } from '../page-loading/page-module-loader.js';
13
- import { MarkerGraphResolver } from '../component-graph/marker-graph-resolver.js';
14
- import { RenderExecutionService, type RenderExecutionGraphContext } from './render-execution.service.js';
13
+ import { RenderExecutionService } from './render-execution.service.js';
15
14
  import { RenderPreparationService } from './render-preparation.service.js';
16
- import type { BoundaryRenderDecisionInput, ComponentRenderBoundaryContext } from './component-render-context.js';
17
- import type { DeferredTemplateSerializer } from './template-serialization.js';
18
- export interface FinalizeCapturedHtmlRenderOptions {
19
- html: string;
20
- componentsToResolve: EcoComponent[];
21
- graphContext: RenderExecutionGraphContext;
22
- partial?: boolean;
23
- componentRootAttributes?: Record<string, string>;
24
- documentAttributes?: Record<string, string>;
25
- mergeAssets?: boolean;
26
- transformHtml?: boolean;
27
- }
15
+ import type { ComponentBoundaryRuntime } from './component-render-context.js';
16
+ import { QueuedBoundaryRuntimeService, type QueuedBoundaryResolution, type QueuedBoundaryRuntimeContext } from './queued-boundary-runtime.service.js';
17
+ type BoundaryRenderDecisionInput = {
18
+ currentIntegration: string;
19
+ targetIntegration?: string;
20
+ };
28
21
  /**
29
22
  * Context for renderToResponse method.
30
23
  */
@@ -39,18 +32,6 @@ export interface RenderToResponseContext {
39
32
  * The class is designed to be extended by specific integration renderers.
40
33
  */
41
34
  export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
42
- /**
43
- * Integration-owned serializers for deferred template payloads that may cross
44
- * mixed-renderer boundaries before final HTML assembly.
45
- *
46
- * @remarks
47
- * Declare framework-specific template shape adapters here when the integration
48
- * can emit deferred child payloads that core must serialize generically.
49
- * The base renderer registers these serializers automatically during
50
- * construction, so integrations should prefer this colocated declaration over
51
- * side-effect imports or ad hoc bootstrap registration.
52
- */
53
- static deferredTemplateSerializers?: readonly DeferredTemplateSerializer[];
54
35
  abstract name: string;
55
36
  protected appConfig: EcoPagesAppConfig;
56
37
  protected assetProcessingService: AssetProcessingService;
@@ -62,11 +43,38 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
62
43
  protected runtimeOrigin: string;
63
44
  protected dependencyResolverService: DependencyResolverService;
64
45
  protected pageModuleLoaderService: PageModuleLoaderService;
65
- protected markerGraphResolver: MarkerGraphResolver;
66
46
  protected renderPreparationService: RenderPreparationService;
67
47
  protected renderExecutionService: RenderExecutionService;
68
- protected deferredTemplateValueSerializer?: (value: unknown, serializeValue: (value: unknown) => string | undefined) => string | undefined;
48
+ protected readonly queuedBoundaryRuntimeService: QueuedBoundaryRuntimeService;
69
49
  protected DOC_TYPE: string;
50
+ /**
51
+ * Reads the execution-scoped foreign renderer cache from one boundary input.
52
+ *
53
+ * Shared page/layout/document shell helpers pass one cache through
54
+ * `integrationContext` so repeated delegation to the same foreign integration
55
+ * can reuse a single initialized renderer instance during one render flow.
56
+ * The cache is deliberately scoped to the current render execution rather than
57
+ * stored on the renderer, which avoids leaking mutable integration state across
58
+ * requests while still preventing redundant renderer initialization.
59
+ *
60
+ * @param integrationContext - Optional boundary context carried with one render input.
61
+ * @returns The current execution cache when present.
62
+ */
63
+ private getBoundaryRendererCache;
64
+ private getRegisteredBoundaryOwner;
65
+ /**
66
+ * Attaches an execution-scoped foreign renderer cache to one boundary input.
67
+ *
68
+ * Foreign-owned page, layout, or document shells may delegate several times in
69
+ * the same render flow. Threading the cache through `integrationContext`
70
+ * preserves renderer reuse without changing the public boundary input contract.
71
+ * Existing integration-specific context is preserved and augmented.
72
+ *
73
+ * @param input - Original boundary render input.
74
+ * @param rendererCache - Execution-scoped renderer cache to propagate.
75
+ * @returns Boundary input augmented with the shared renderer cache.
76
+ */
77
+ private withBoundaryRendererCache;
70
78
  protected getRendererModuleValue(key: string): unknown;
71
79
  protected getRendererModuleString(key: string): string | undefined;
72
80
  protected getRendererBootstrapDependencies(partial?: boolean): ProcessedAsset[];
@@ -100,6 +108,139 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
100
108
  * @returns Resolved processed assets
101
109
  */
102
110
  protected prepareViewDependencies(view: EcoComponent, layout?: EcoComponent): Promise<ProcessedAsset[]>;
111
+ /**
112
+ * Merges component-scoped assets into the active HTML transformer state.
113
+ *
114
+ * Explicit page, layout, and document shell composition can produce assets at
115
+ * each boundary. This helper deduplicates those groups and folds them back into
116
+ * the transformer so downstream HTML finalization sees one canonical asset set.
117
+ *
118
+ * @param assetGroups - Optional groups of processed assets to merge.
119
+ * @returns The deduplicated asset subset contributed by this merge operation.
120
+ */
121
+ protected appendProcessedDependencies(...assetGroups: Array<readonly ProcessedAsset[] | undefined>): ProcessedAsset[];
122
+ /**
123
+ * Resolves metadata for explicit view rendering.
124
+ *
125
+ * When a view declares a `metadata()` function, that contract owns the final
126
+ * metadata for the explicit render. Otherwise the app-level default metadata is
127
+ * reused so explicit routes and page-module routes share the same fallback.
128
+ *
129
+ * @param view - View component being rendered.
130
+ * @param props - Props passed to the view.
131
+ * @returns Resolved metadata for the final document shell.
132
+ */
133
+ protected resolveViewMetadata<P>(view: EcoComponent<P>, props: P): Promise<PageMetadataProps>;
134
+ /**
135
+ * Renders one explicit view response in partial mode.
136
+ *
137
+ * Same-integration views can optionally stream or render inline via the caller's
138
+ * `renderInline()` hook. Once a view may cross integration boundaries, this
139
+ * helper routes the render through `renderComponentBoundary()` instead so mixed
140
+ * shells can reuse the execution-scoped renderer cache and resolve nested
141
+ * foreign ownership before the partial response is returned.
142
+ *
143
+ * @param input - View render options for the partial response.
144
+ * @returns HTML response for the partial render.
145
+ */
146
+ protected renderPartialViewResponse<P>(input: {
147
+ view: EcoComponent<P>;
148
+ props: P;
149
+ ctx: RenderToResponseContext;
150
+ renderInline?: () => Promise<BodyInit>;
151
+ transformHtml?: (html: string) => string;
152
+ }): Promise<Response>;
153
+ /**
154
+ * Renders an explicit view through optional layout and document shells.
155
+ *
156
+ * This helper is the shared explicit-route path for string-oriented and mixed
157
+ * integrations. It prepares view dependencies, resolves metadata, and composes
158
+ * view, layout, and html template boundaries with one execution-scoped renderer
159
+ * cache so repeated foreign shell delegation can reuse initialized renderers
160
+ * during the same render flow.
161
+ *
162
+ * @param input - View, props, and optional layout metadata for the render.
163
+ * @returns HTML response for the explicit view render.
164
+ */
165
+ protected renderViewWithDocumentShell<P>(input: {
166
+ view: EcoComponent<P>;
167
+ props: P;
168
+ ctx: RenderToResponseContext;
169
+ layout?: EcoComponent;
170
+ }): Promise<Response>;
171
+ /**
172
+ * Renders a route page through optional layout and document shells.
173
+ *
174
+ * Route rendering and explicit view rendering now share the same boundary-owned
175
+ * shell composition model. This helper composes page, layout, and html template
176
+ * boundaries while threading one execution-scoped renderer cache through every
177
+ * delegated boundary so foreign shell ownership remains stable and renderer
178
+ * initialization is reused inside the current request.
179
+ *
180
+ * @param input - Page, layout, document, and metadata inputs for the route render.
181
+ * @returns Final serialized document HTML including the doctype prefix.
182
+ */
183
+ protected renderPageWithDocumentShell(input: {
184
+ page: {
185
+ component: EcoComponent;
186
+ props: Record<string, unknown>;
187
+ };
188
+ layout?: {
189
+ component: EcoComponent;
190
+ props?: Record<string, unknown>;
191
+ };
192
+ htmlTemplate: EcoComponent;
193
+ metadata: PageMetadataProps;
194
+ pageProps: Record<string, unknown>;
195
+ documentProps?: Record<string, unknown>;
196
+ transformDocumentHtml?: (html: string) => string;
197
+ }): Promise<string>;
198
+ /**
199
+ * Renders one string-first component boundary and collects its assets.
200
+ *
201
+ * String-oriented integrations frequently share the same boundary contract:
202
+ * pass serialized children through props, coerce the render result to HTML, and
203
+ * attach any component-scoped dependencies. This helper centralizes that flow
204
+ * so integrations can opt into shared orchestration without repeating the same
205
+ * boundary boilerplate.
206
+ *
207
+ * @param input - Boundary render input.
208
+ * @param component - String-oriented component implementation to execute.
209
+ * @returns Structured component render result for orchestration paths.
210
+ */
211
+ protected renderStringComponentBoundary(input: ComponentRenderInput, component: (props: Record<string, unknown>) => Promise<EcoPagesElement> | EcoPagesElement): Promise<ComponentRenderResult>;
212
+ protected getBoundaryTokenPrefix(): string;
213
+ protected getBoundaryRuntimeContextKey(): string;
214
+ protected getQueuedBoundaryRuntime<TContext extends QueuedBoundaryRuntimeContext>(input: ComponentRenderInput, runtimeContextKey?: string): TContext | undefined;
215
+ protected resolveQueuedBoundaryTokens(html: string, queuedResolutionsByToken: Map<string, QueuedBoundaryResolution>, resolveToken: (token: string) => Promise<string>): Promise<string>;
216
+ protected createQueuedBoundaryRuntime<TContext extends QueuedBoundaryRuntimeContext>(options: {
217
+ boundaryInput: ComponentRenderInput;
218
+ rendererCache: Map<string, IntegrationRenderer<any>>;
219
+ runtimeContextKey?: string;
220
+ tokenPrefix?: string;
221
+ createRuntimeContext?: (integrationContext: {
222
+ rendererCache?: Map<string, unknown>;
223
+ componentInstanceId?: string;
224
+ [key: string]: unknown;
225
+ }, rendererCache: Map<string, unknown>) => TContext;
226
+ }): ComponentBoundaryRuntime;
227
+ protected resolveRendererOwnedQueuedBoundaryHtml<TContext extends QueuedBoundaryRuntimeContext>(options: {
228
+ html: string;
229
+ runtimeContext?: TContext;
230
+ queueLabel: string;
231
+ renderQueuedChildren: (children: unknown, runtimeContext: TContext, queuedResolutionsByToken: Map<string, QueuedBoundaryResolution>, resolveToken: (token: string) => Promise<string>) => Promise<{
232
+ assets: ProcessedAsset[];
233
+ html?: string;
234
+ }>;
235
+ }): Promise<{
236
+ assets: ProcessedAsset[];
237
+ html: string;
238
+ }>;
239
+ /**
240
+ * Renders a string-first component, then resolves any queued foreign
241
+ * boundaries before returning final component HTML.
242
+ */
243
+ protected renderStringComponentBoundaryWithQueuedForeignBoundaries(input: ComponentRenderInput, component: (props: Record<string, unknown>) => Promise<EcoPagesElement> | EcoPagesElement): Promise<ComponentRenderResult>;
103
244
  constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, rendererModules, runtimeOrigin, }: {
104
245
  appConfig: EcoPagesAppConfig;
105
246
  assetProcessingService: AssetProcessingService;
@@ -228,7 +369,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
228
369
  Page: EcoPageFile['default'] | EcoPageComponent<any>;
229
370
  getStaticProps?: GetStaticProps<Record<string, unknown>>;
230
371
  getMetadata?: GetMetadata;
231
- componentGraphContext?: IntegrationRendererRenderOptions['componentGraphContext'];
232
372
  integrationSpecificProps: Record<string, unknown>;
233
373
  }>;
234
374
  /**
@@ -246,11 +386,10 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
246
386
  *
247
387
  * Execution flow:
248
388
  * 1. Build normalized render options (`prepareRenderOptions`).
249
- * 2. Render once inside component render context to capture marker graph refs.
250
- * 3. Merge captured refs with optional explicit page-module graph context.
251
- * 4. Resolve any `eco-marker` graph bottom-up and merge produced assets.
252
- * 5. Optionally apply root attributes for page/component root boundaries.
253
- * 6. Run HTML transformer with final dependency set.
389
+ * 2. Render the route body once.
390
+ * 3. Reject unresolved route-level boundary artifacts.
391
+ * 4. Optionally apply root attributes for page/component root boundaries.
392
+ * 5. Run HTML transformer with final dependency set.
254
393
  *
255
394
  * Stream-safety note: the first render result is normalized to a string once,
256
395
  * then the pipeline continues with that immutable HTML value to avoid disturbed
@@ -261,23 +400,19 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
261
400
  */
262
401
  execute(options: RouteRendererOptions): Promise<RouteRenderResult>;
263
402
  /**
264
- * Captures a render pass as immutable HTML along with the graph context needed
265
- * for deferred marker resolution.
403
+ * Finalizes already-resolved HTML for explicit renderer-owned paths.
266
404
  *
267
- * This is the shared entry point for direct `renderToResponse()` flows that
268
- * need the same component graph capture semantics as route execution without
269
- * going through `prepareRenderOptions()`.
405
+ * This keeps document and root-attribute stamping plus HTML transformation
406
+ * available after a renderer has completed nested boundary resolution without
407
+ * routing back through shared route execution.
270
408
  */
271
- protected captureHtmlRender(render: () => Promise<RouteRendererBody>): Promise<{
409
+ protected finalizeResolvedHtml(options: {
272
410
  html: string;
273
- graphContext: RenderExecutionGraphContext;
274
- }>;
275
- /**
276
- * Finalizes previously captured HTML by resolving deferred markers, merging
277
- * any emitted assets, stamping optional attributes, and optionally running the
278
- * HTML transformer for full-document flows.
279
- */
280
- protected finalizeCapturedHtmlRender(options: FinalizeCapturedHtmlRenderOptions): Promise<string>;
411
+ partial?: boolean;
412
+ componentRootAttributes?: Record<string, string>;
413
+ documentAttributes?: Record<string, string>;
414
+ transformHtml?: boolean;
415
+ }): Promise<string>;
281
416
  /**
282
417
  * Returns document-level attributes to stamp onto the rendered `<html>` tag.
283
418
  *
@@ -285,30 +420,6 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
285
420
  * other runtime coordination markers without relying on script sniffing.
286
421
  */
287
422
  protected getDocumentAttributes(_renderOptions: IntegrationRendererRenderOptions<C>): Record<string, string> | undefined;
288
- /**
289
- * Resolves all `eco-marker` placeholders in rendered HTML using integration
290
- * dispatch and bottom-up graph execution.
291
- *
292
- * Responsibility split:
293
- * - core decodes markers into component refs, props, slot children, and target
294
- * integration dispatch
295
- * - the selected integration renderer performs the actual component render via
296
- * `renderComponent()`
297
- *
298
- * Resolver callback behavior per marker:
299
- * - resolve component definition by `componentRef`
300
- * - resolve serialized props by `propsRef`
301
- * - stitch resolved child HTML when `slotRef` is present
302
- * - dispatch to target integration `renderComponent`
303
- * - collect produced assets and apply root attributes when attachable
304
- *
305
- * @param options.html HTML that may still contain marker tokens.
306
- * @param options.componentsToResolve Component set used to build component ref registry.
307
- * @param options.graphContext Props/slot linkage captured during render.
308
- * @returns Resolved HTML plus any component-scoped assets produced while resolving nodes.
309
- * @throws Error when marker component refs or props refs cannot be resolved.
310
- */
311
- private resolveMarkerGraphHtml;
312
423
  /**
313
424
  * Returns a renderer instance for a given integration name.
314
425
  *
@@ -328,17 +439,26 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
328
439
  * @returns The rendered body.
329
440
  */
330
441
  abstract render(options: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
442
+ protected resolveBoundaryInOwningRenderer(input: ComponentRenderInput, rendererCache: Map<string, IntegrationRenderer<any>>): Promise<ComponentRenderResult | undefined>;
443
+ /**
444
+ * Renders one component under this integration's boundary runtime and resolves
445
+ * any nested foreign boundaries captured during that render.
446
+ *
447
+ * Without this wrapper, a component tree with foreign-owned descendants would
448
+ * render them with no active boundary runtime, which bypasses the owning
449
+ * renderer's nested-boundary handoff.
450
+ */
451
+ renderComponentBoundary(input: ComponentRenderInput): Promise<ComponentRenderResult>;
452
+ private normalizeComponentBoundaryRender;
453
+ protected normalizeBoundaryArtifactHtml(html: string): string;
331
454
  /**
332
- * Renders one deferred marker-graph node under this integration's boundary
333
- * context so nested cross-integration children can continue to defer while the
334
- * graph is being resolved bottom-up.
455
+ * Returns whether the component dependency tree crosses into another
456
+ * integration.
335
457
  *
336
- * Without this wrapper, resolving a deferred React or Kita node would render
337
- * any nested foreign components with no active render context, causing them to
338
- * fall back to inline escaped HTML instead of emitting the next marker layer.
458
+ * This keeps boundary-runtime setup narrow: same-integration trees can render
459
+ * directly without paying the queue orchestration cost.
339
460
  */
340
- renderComponentForMarkerGraph(input: ComponentRenderInput): Promise<ComponentRenderResult>;
341
- protected shouldWrapMarkerGraphComponent(component: EcoComponent): boolean;
461
+ protected hasForeignBoundaryDescendants(component: EcoComponent): boolean;
342
462
  /**
343
463
  * Render a view directly to a Response object.
344
464
  * Used for explicit routing where views are rendered from route handlers.
@@ -355,7 +475,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
355
475
  * Default behavior delegates to `renderToResponse` in partial mode and wraps
356
476
  * the resulting HTML into the `ComponentRenderResult` contract.
357
477
  *
358
- * In marker resolution, this method is the integration-owned step that turns an
478
+ * In boundary resolution, this method is the integration-owned step that turns an
359
479
  * already-resolved deferred boundary into concrete HTML, assets, and optional
360
480
  * root attributes.
361
481
  *
@@ -363,7 +483,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
363
483
  * root attributes, integration-specific hydration metadata).
364
484
  *
365
485
  * @param input Component render request.
366
- * @returns Structured render result used by marker/page orchestration.
486
+ * @returns Structured render result used by component/page orchestration.
367
487
  */
368
488
  renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
369
489
  /**
@@ -382,25 +502,28 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
382
502
  */
383
503
  protected buildRouteRenderAssets(_file: string): Promise<ProcessedAsset[]> | undefined;
384
504
  /**
385
- * Builds the narrow boundary policy facade injected into component render
386
- * context for this render pass.
387
- *
388
- * `eco.component()` consumes this facade without knowing about integration
389
- * registries or plugin instances.
505
+ * Creates the per-render boundary runtime adopted by the shared component
506
+ * render context.
390
507
  *
391
- * @returns Boundary policy context for the active integration renderer.
508
+ * Real mixed-integration renderers should override this and keep foreign
509
+ * boundary resolution inside their own renderer-owned queue. The base runtime
510
+ * fails fast when a renderer crosses into a foreign owner without providing its
511
+ * own handoff mechanism.
392
512
  */
393
- protected getComponentRenderBoundaryContext(): ComponentRenderBoundaryContext;
513
+ protected createComponentBoundaryRuntime(_options: {
514
+ boundaryInput: ComponentRenderInput;
515
+ rendererCache: Map<string, IntegrationRenderer<any>>;
516
+ }): ComponentBoundaryRuntime;
394
517
  /**
395
- * Resolves whether a component boundary should be deferred by consulting the
396
- * target integration plugin.
518
+ * Resolves whether a boundary should leave the current render pass and be
519
+ * resolved by its owning renderer.
397
520
  *
398
- * Boundaries targeting the current integration always render inline. Cross-
399
- * integration boundaries delegate the decision to the target integration's
400
- * `shouldDeferComponentBoundary()` policy.
521
+ * Boundaries owned by the current integration always render inline. Foreign-
522
+ * owned boundaries must be handed off by a renderer-owned runtime.
401
523
  *
402
524
  * @param input Boundary metadata for the active render pass.
403
- * @returns `true` when the boundary should emit a marker; otherwise `false`.
525
+ * @returns `true` when the boundary should leave the current pass; otherwise `false`.
404
526
  */
405
- protected shouldDeferComponentBoundary(input: BoundaryRenderDecisionInput): boolean;
527
+ protected shouldResolveBoundaryInOwningRenderer(input: BoundaryRenderDecisionInput): boolean;
406
528
  }
529
+ export {};