@ecopages/core 0.2.0-alpha.26 → 0.2.0-alpha.28

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 (106) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +63 -7
  3. package/package.json +8 -94
  4. package/src/adapters/bun/create-app.d.ts +1 -0
  5. package/src/adapters/bun/create-app.js +39 -2
  6. package/src/adapters/bun/hmr-manager.d.ts +1 -13
  7. package/src/adapters/bun/hmr-manager.js +1 -22
  8. package/src/adapters/bun/server-adapter.js +23 -4
  9. package/src/adapters/node/node-hmr-manager.d.ts +2 -14
  10. package/src/adapters/node/node-hmr-manager.js +2 -23
  11. package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
  12. package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
  13. package/src/adapters/shared/explicit-static-route-matcher.d.ts +5 -2
  14. package/src/adapters/shared/explicit-static-route-matcher.js +14 -16
  15. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +7 -10
  16. package/src/adapters/shared/file-route-middleware-pipeline.js +2 -11
  17. package/src/adapters/shared/fs-server-response-factory.d.ts +13 -9
  18. package/src/adapters/shared/fs-server-response-factory.js +10 -26
  19. package/src/adapters/shared/fs-server-response-matcher.d.ts +14 -6
  20. package/src/adapters/shared/fs-server-response-matcher.js +67 -28
  21. package/src/adapters/shared/render-context.d.ts +2 -2
  22. package/src/adapters/shared/server-adapter.d.ts +21 -10
  23. package/src/adapters/shared/server-adapter.js +171 -132
  24. package/src/adapters/shared/server-route-handler.d.ts +2 -2
  25. package/src/adapters/shared/server-route-handler.js +1 -1
  26. package/src/adapters/shared/server-static-builder.d.ts +4 -4
  27. package/src/config/README.md +1 -1
  28. package/src/config/config-builder.d.ts +2 -2
  29. package/src/config/config-builder.js +0 -5
  30. package/src/dev/host-runtime.d.ts +10 -0
  31. package/src/dev/host-runtime.js +24 -0
  32. package/src/eco/eco.js +7 -7
  33. package/src/eco/eco.types.d.ts +3 -3
  34. package/src/errors/index.d.ts +1 -0
  35. package/src/errors/index.js +3 -1
  36. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
  37. package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -4
  38. package/src/integrations/ghtml/ghtml-renderer.js +1 -7
  39. package/src/plugins/eco-component-meta-plugin.js +0 -1
  40. package/src/plugins/integration-plugin.d.ts +14 -18
  41. package/src/plugins/integration-plugin.js +14 -21
  42. package/src/plugins/processor.d.ts +2 -0
  43. package/src/plugins/processor.js +6 -1
  44. package/src/route-renderer/GRAPH.md +81 -289
  45. package/src/route-renderer/README.md +67 -105
  46. package/src/route-renderer/orchestration/component-render-context.d.ts +24 -18
  47. package/src/route-renderer/orchestration/component-render-context.js +14 -14
  48. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
  49. package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
  50. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
  51. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
  52. package/src/route-renderer/orchestration/integration-renderer.d.ts +96 -136
  53. package/src/route-renderer/orchestration/integration-renderer.js +280 -303
  54. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
  55. package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
  56. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
  57. package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
  58. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
  59. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.js → queued-foreign-subtree-resolution.service.js} +28 -25
  60. package/src/route-renderer/orchestration/render-output.utils.d.ts +3 -3
  61. package/src/route-renderer/orchestration/render-output.utils.js +6 -6
  62. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
  63. package/src/route-renderer/orchestration/{render-preparation.service.js → route-render-orchestrator.js} +132 -108
  64. package/src/route-renderer/page-loading/component-dependency-collection.js +8 -1
  65. package/src/route-renderer/page-loading/dependency-resolver.js +5 -7
  66. package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +1 -1
  67. package/src/route-renderer/page-loading/page-dependency-bundling.js +41 -19
  68. package/src/route-renderer/route-renderer.d.ts +28 -26
  69. package/src/route-renderer/route-renderer.js +4 -27
  70. package/src/router/README.md +16 -19
  71. package/src/router/server/route-registry.d.ts +78 -0
  72. package/src/router/server/route-registry.js +262 -0
  73. package/src/services/README.md +1 -2
  74. package/src/services/assets/asset-processing-service/assets.types.d.ts +3 -0
  75. package/src/services/assets/asset-processing-service/index.d.ts +1 -0
  76. package/src/services/assets/asset-processing-service/index.js +1 -0
  77. package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
  78. package/src/services/assets/asset-processing-service/page-package.js +74 -0
  79. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +4 -4
  80. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +6 -3
  81. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +9 -3
  82. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +4 -2
  83. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -1
  84. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +3 -1
  85. package/src/services/module-loading/node-bootstrap-plugin.js +15 -3
  86. package/src/static-site-generator/static-site-generator.d.ts +20 -21
  87. package/src/static-site-generator/static-site-generator.js +107 -140
  88. package/src/types/internal-types.d.ts +13 -12
  89. package/src/types/public-types.d.ts +46 -36
  90. package/src/watchers/project-watcher.test-helpers.js +5 -5
  91. package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
  92. package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
  93. package/src/route-renderer/orchestration/page-packaging.service.d.ts +0 -16
  94. package/src/route-renderer/orchestration/page-packaging.service.js +0 -66
  95. package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
  96. package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
  97. package/src/route-renderer/orchestration/render-execution.service.js +0 -106
  98. package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
  99. package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
  100. package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
  101. package/src/router/server/fs-router-scanner.d.ts +0 -41
  102. package/src/router/server/fs-router-scanner.js +0 -161
  103. package/src/router/server/fs-router.d.ts +0 -26
  104. package/src/router/server/fs-router.js +0 -100
  105. package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
  106. package/src/services/runtime-state/runtime-specifier-registry.service.js +0 -37
@@ -0,0 +1,24 @@
1
+ import type { OwnershipPlan, OwnershipValidationError, EcoComponent } from '../../types/public-types.js';
2
+ type OwnershipPlanBuildInput = {
3
+ routeFile: string;
4
+ currentIntegrationName: string;
5
+ HtmlTemplate: EcoComponent;
6
+ Layout?: EcoComponent;
7
+ Page: EcoComponent;
8
+ validationErrors?: OwnershipValidationError[];
9
+ };
10
+ /**
11
+ * Builds a declared ownership plan from the component dependency graph.
12
+ *
13
+ * The plan reflects the declared component dependency graph for one route after
14
+ * route-root ownership validation has already run. It records ownership shape,
15
+ * foreign-edge counts, and any validation errors supplied by an earlier
16
+ * validation pass.
17
+ */
18
+ export declare class OwnershipPlanningService {
19
+ /**
20
+ * Builds the structural ownership plan for one route render.
21
+ */
22
+ buildPlan(input: OwnershipPlanBuildInput): OwnershipPlan;
23
+ }
24
+ export {};
@@ -0,0 +1,63 @@
1
+ import { mapDeclaredOwnershipGraph } from "./declared-ownership-graph.js";
2
+ class OwnershipPlanningService {
3
+ /**
4
+ * Builds the structural ownership plan for one route render.
5
+ */
6
+ buildPlan(input) {
7
+ const validationErrors = input.validationErrors ?? [];
8
+ const rendererNames = /* @__PURE__ */ new Set([input.currentIntegrationName]);
9
+ let foreignEdgeCount = 0;
10
+ const roots = [
11
+ { component: input.HtmlTemplate, source: "html-template" },
12
+ ...input.Layout ? [{ component: input.Layout, source: "layout" }] : [],
13
+ { component: input.Page, source: "page" }
14
+ ];
15
+ const children = mapDeclaredOwnershipGraph({
16
+ roots,
17
+ currentIntegrationName: input.currentIntegrationName,
18
+ mapNode: ({ component, source, integrationName, componentId, isForeignToParent }, children2) => {
19
+ const componentMeta = component.config?.__eco;
20
+ rendererNames.add(integrationName);
21
+ if (isForeignToParent) {
22
+ foreignEdgeCount += 1;
23
+ }
24
+ return {
25
+ id: componentId,
26
+ source,
27
+ ownership: {
28
+ integrationName,
29
+ componentId,
30
+ componentFile: componentMeta?.file,
31
+ isPageEntry: source === "page",
32
+ isForeignToParent
33
+ },
34
+ children: children2,
35
+ declaredDependenciesValid: true
36
+ };
37
+ }
38
+ });
39
+ const root = {
40
+ id: `route:${input.routeFile}`,
41
+ source: "route",
42
+ ownership: {
43
+ integrationName: input.currentIntegrationName,
44
+ componentId: `route:${input.routeFile}`,
45
+ componentFile: input.routeFile,
46
+ isPageEntry: false,
47
+ isForeignToParent: false
48
+ },
49
+ children,
50
+ declaredDependenciesValid: validationErrors.length === 0
51
+ };
52
+ return {
53
+ root,
54
+ rendererNames: Array.from(rendererNames),
55
+ foreignEdgeCount,
56
+ hasValidationErrors: validationErrors.length > 0,
57
+ validationErrors
58
+ };
59
+ }
60
+ }
61
+ export {
62
+ OwnershipPlanningService
63
+ };
@@ -0,0 +1,29 @@
1
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
+ import type { OwnershipPlanNodeSource, OwnershipValidationError, EcoComponent } from '../../types/public-types.js';
3
+ type OwnershipValidationInput = {
4
+ currentIntegrationName: string;
5
+ roots: Array<{
6
+ component: EcoComponent;
7
+ source: Extract<OwnershipPlanNodeSource, 'page' | 'layout' | 'html-template'>;
8
+ }>;
9
+ };
10
+ /**
11
+ * Validates foreign ownership as soon as the route root graph is loaded.
12
+ *
13
+ * This check runs before route data and dependency preparation so ownership and
14
+ * metadata problems are surfaced from the loaded component graph itself rather
15
+ * than being entangled with ownership-plan construction.
16
+ */
17
+ export declare class OwnershipValidationService {
18
+ private readonly appConfig;
19
+ /**
20
+ * Creates the ownership validator for one finalized app config.
21
+ */
22
+ constructor(appConfig: EcoPagesAppConfig);
23
+ /**
24
+ * Validates foreign ownership edges reachable from the supplied route roots.
25
+ */
26
+ validate(input: OwnershipValidationInput): OwnershipValidationError[];
27
+ private isRegisteredIntegration;
28
+ }
29
+ export {};
@@ -0,0 +1,53 @@
1
+ import { mapDeclaredOwnershipGraph } from "./declared-ownership-graph.js";
2
+ class OwnershipValidationService {
3
+ appConfig;
4
+ /**
5
+ * Creates the ownership validator for one finalized app config.
6
+ */
7
+ constructor(appConfig) {
8
+ this.appConfig = appConfig;
9
+ }
10
+ /**
11
+ * Validates foreign ownership edges reachable from the supplied route roots.
12
+ */
13
+ validate(input) {
14
+ return mapDeclaredOwnershipGraph({
15
+ roots: input.roots,
16
+ currentIntegrationName: input.currentIntegrationName,
17
+ mapNode: ({ component, integrationName, componentId, isForeignToParent }, children) => {
18
+ const componentMeta = component.config?.__eco;
19
+ const errors = children.flat();
20
+ if (!isForeignToParent) {
21
+ return errors;
22
+ }
23
+ if (!componentMeta) {
24
+ errors.push({
25
+ code: "MISSING_COMPONENT_METADATA",
26
+ message: `[ecopages] Foreign child "${componentId}" must provide stable __eco metadata so ownership diagnostics stay actionable. Declared dependencies must include all possible foreign children.`,
27
+ componentId,
28
+ integrationName
29
+ });
30
+ }
31
+ if (!this.isRegisteredIntegration(integrationName, input.currentIntegrationName)) {
32
+ errors.push({
33
+ code: "UNKNOWN_INTEGRATION_OWNER",
34
+ message: `[ecopages] Foreign child "${componentId}" references unknown integration owner "${integrationName}". Declared dependencies must include all possible foreign children and those integrations must be registered.`,
35
+ componentId,
36
+ componentFile: componentMeta?.file,
37
+ integrationName
38
+ });
39
+ }
40
+ return errors;
41
+ }
42
+ }).flat();
43
+ }
44
+ isRegisteredIntegration(integrationName, currentIntegrationName) {
45
+ if (integrationName === currentIntegrationName) {
46
+ return true;
47
+ }
48
+ return this.appConfig.integrations.some((integration) => integration.name === integrationName);
49
+ }
50
+ }
51
+ export {
52
+ OwnershipValidationService
53
+ };
@@ -0,0 +1,90 @@
1
+ import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
2
+ import type { BaseIntegrationContext, ForeignSubtreeRenderPayload, ComponentRenderInput, EcoComponent } from '../../types/public-types.js';
3
+ import type { ForeignChildRuntime } from './component-render-context.js';
4
+ export type QueuedForeignChildDecisionInput = {
5
+ currentIntegration: string;
6
+ targetIntegration?: string;
7
+ component: EcoComponent;
8
+ props: Record<string, unknown>;
9
+ };
10
+ export type QueuedForeignSubtreeResolution = {
11
+ token: string;
12
+ component: EcoComponent;
13
+ props: Record<string, unknown>;
14
+ componentInstanceId: string;
15
+ };
16
+ /**
17
+ * Shared mutable state for one renderer-owned queued foreign-subtree runtime.
18
+ *
19
+ * Renderers that cannot resolve foreign children inline can enqueue transport
20
+ * tokens during their initial render, then resolve those tokens against the
21
+ * owning renderer before returning final HTML.
22
+ */
23
+ export type QueuedForeignSubtreeResolutionContext = {
24
+ rendererCache: Map<string, unknown>;
25
+ componentInstanceScope?: string;
26
+ nextForeignSubtreeId: number;
27
+ queuedResolutions: QueuedForeignSubtreeResolution[];
28
+ };
29
+ type QueuedForeignSubtreeIntegrationContext = BaseIntegrationContext & Record<string, unknown>;
30
+ type QueuedForeignSubtreeChildRenderResult = {
31
+ assets: ProcessedAsset[];
32
+ html?: string;
33
+ };
34
+ /**
35
+ * Lower-level queue orchestration for renderer-owned foreign-child runtimes that emit
36
+ * temporary transport tokens during one render pass.
37
+ *
38
+ * The service keeps three responsibilities in one place:
39
+ * - storing per-render queue state on the active integration context
40
+ * - creating a `ForeignChildRuntime` that enqueues foreign children
41
+ * - resolving queued tokens back through the owning renderer before final HTML
42
+ * leaves the current renderer
43
+ *
44
+ * The deeper Foreign Subtree execution module composes this queue helper with
45
+ * renderer-cache delegation and active render-context execution. This service
46
+ * stays focused on queue bookkeeping, recursion, cycle detection, and asset merging.
47
+ */
48
+ export declare class QueuedForeignSubtreeResolutionService {
49
+ /**
50
+ * Reads the queued foreign-subtree runtime state previously attached to one render.
51
+ */
52
+ getRuntimeContext<TContext extends QueuedForeignSubtreeResolutionContext>(input: ComponentRenderInput, runtimeContextKey: string): TContext | undefined;
53
+ /**
54
+ * Creates the runtime hook used by `runWithComponentRenderContext()` for one
55
+ * renderer-owned queue.
56
+ *
57
+ * When the renderer decides a foreign child must be handed off, the runtime returns
58
+ * a resolved transport token instead of rendering the foreign component inline.
59
+ */
60
+ createRuntime<TContext extends QueuedForeignSubtreeResolutionContext>(options: {
61
+ renderInput: ComponentRenderInput;
62
+ rendererCache: Map<string, unknown>;
63
+ runtimeContextKey: string;
64
+ tokenPrefix: string;
65
+ shouldQueueForeignChild: (input: QueuedForeignChildDecisionInput) => boolean;
66
+ createRuntimeContext?: (integrationContext: QueuedForeignSubtreeIntegrationContext, rendererCache: Map<string, unknown>) => TContext;
67
+ }): ForeignChildRuntime;
68
+ /**
69
+ * Resolves every queued transport token in one renderer-owned HTML fragment.
70
+ *
71
+ * The caller supplies framework-specific child rendering, while this service
72
+ * handles recursive token replacement, cycle detection, root-attribute
73
+ * application, and merged asset collection.
74
+ */
75
+ resolveQueuedHtml<TContext extends QueuedForeignSubtreeResolutionContext>(options: {
76
+ html: string;
77
+ runtimeContext?: TContext;
78
+ queueLabel: string;
79
+ renderQueuedChildren: (children: unknown, runtimeContext: TContext, queuedResolutionsByToken: Map<string, QueuedForeignSubtreeResolution>, resolveToken: (token: string) => Promise<string>) => Promise<QueuedForeignSubtreeChildRenderResult>;
80
+ resolveForeignSubtree: (input: ComponentRenderInput, rendererCache: Map<string, unknown>) => Promise<ForeignSubtreeRenderPayload | undefined>;
81
+ applyAttributesToFirstElement: (html: string, attributes: Record<string, string>) => string;
82
+ dedupeProcessedAssets: (assets: ProcessedAsset[]) => ProcessedAsset[];
83
+ }): Promise<{
84
+ assets: ProcessedAsset[];
85
+ html: string;
86
+ }>;
87
+ private createForeignSubtreeToken;
88
+ private ensureRuntimeContext;
89
+ }
90
+ export {};
@@ -1,6 +1,6 @@
1
- class QueuedBoundaryRuntimeService {
1
+ class QueuedForeignSubtreeResolutionService {
2
2
  /**
3
- * Reads the queued boundary runtime state previously attached to one render.
3
+ * Reads the queued foreign-subtree runtime state previously attached to one render.
4
4
  */
5
5
  getRuntimeContext(input, runtimeContextKey) {
6
6
  const integrationContext = input.integrationContext;
@@ -14,23 +14,23 @@ class QueuedBoundaryRuntimeService {
14
14
  * Creates the runtime hook used by `runWithComponentRenderContext()` for one
15
15
  * renderer-owned queue.
16
16
  *
17
- * When the renderer decides a boundary must be handed off, the runtime returns
17
+ * When the renderer decides a foreign child must be handed off, the runtime returns
18
18
  * a resolved transport token instead of rendering the foreign component inline.
19
19
  */
20
20
  createRuntime(options) {
21
21
  const runtimeContext = this.ensureRuntimeContext(options);
22
- const interceptBoundary = (input) => {
23
- if (!options.shouldQueueBoundary(input)) {
22
+ const interceptForeignChild = (input) => {
23
+ if (!options.shouldQueueForeignChild(input)) {
24
24
  return { kind: "inline" };
25
25
  }
26
- runtimeContext.nextBoundaryId += 1;
27
- const boundaryId = runtimeContext.nextBoundaryId;
28
- const token = this.createBoundaryToken(options.tokenPrefix, runtimeContext, boundaryId);
26
+ runtimeContext.nextForeignSubtreeId += 1;
27
+ const foreignSubtreeId = runtimeContext.nextForeignSubtreeId;
28
+ const token = this.createForeignSubtreeToken(options.tokenPrefix, runtimeContext, foreignSubtreeId);
29
29
  runtimeContext.queuedResolutions.push({
30
30
  token,
31
31
  component: input.component,
32
32
  props: { ...input.props },
33
- componentInstanceId: runtimeContext.componentInstanceScope ? `${runtimeContext.componentInstanceScope}_n_${boundaryId}` : `n_${boundaryId}`
33
+ componentInstanceId: runtimeContext.componentInstanceScope ? `${runtimeContext.componentInstanceScope}_n_${foreignSubtreeId}` : `n_${foreignSubtreeId}`
34
34
  });
35
35
  return {
36
36
  kind: "resolved",
@@ -38,8 +38,8 @@ class QueuedBoundaryRuntimeService {
38
38
  };
39
39
  };
40
40
  return {
41
- interceptBoundary,
42
- interceptBoundarySync: interceptBoundary
41
+ interceptForeignChild,
42
+ interceptForeignChildSync: interceptForeignChild
43
43
  };
44
44
  }
45
45
  /**
@@ -71,7 +71,7 @@ class QueuedBoundaryRuntimeService {
71
71
  }
72
72
  if (resolvingTokens.has(token)) {
73
73
  throw new Error(
74
- `[ecopages] ${options.queueLabel} boundary queue contains a cycle or unresolved dependency links.`
74
+ `[ecopages] ${options.queueLabel} foreign-subtree queue contains a cycle or unresolved dependency links.`
75
75
  );
76
76
  }
77
77
  resolvingTokens.add(token);
@@ -85,7 +85,7 @@ class QueuedBoundaryRuntimeService {
85
85
  if (renderedChildren.assets.length > 0) {
86
86
  collectedAssets.push(...renderedChildren.assets);
87
87
  }
88
- const boundaryRender = await options.resolveBoundary(
88
+ const foreignSubtreeRender = await options.resolveForeignSubtree(
89
89
  {
90
90
  component: resolution.component,
91
91
  props: { ...resolution.props },
@@ -97,15 +97,18 @@ class QueuedBoundaryRuntimeService {
97
97
  },
98
98
  runtimeContext.rendererCache
99
99
  );
100
- if (!boundaryRender) {
100
+ if (!foreignSubtreeRender) {
101
101
  throw new Error(
102
- `[ecopages] ${options.queueLabel} queued boundary could not resolve its owning renderer.`
102
+ `[ecopages] ${options.queueLabel} queued foreign subtree could not resolve its owning renderer.`
103
103
  );
104
104
  }
105
- if ((boundaryRender.assets?.length ?? 0) > 0) {
106
- collectedAssets.push(...boundaryRender.assets ?? []);
105
+ if ((foreignSubtreeRender.assets?.length ?? 0) > 0) {
106
+ collectedAssets.push(...foreignSubtreeRender.assets ?? []);
107
107
  }
108
- const resolvedHtml2 = boundaryRender.attachmentPolicy.kind === "first-element" && boundaryRender.rootAttributes ? options.applyAttributesToFirstElement(boundaryRender.html, boundaryRender.rootAttributes) : boundaryRender.html;
108
+ const resolvedHtml2 = foreignSubtreeRender.attachmentPolicy.kind === "first-element" && foreignSubtreeRender.rootAttributes ? options.applyAttributesToFirstElement(
109
+ foreignSubtreeRender.html,
110
+ foreignSubtreeRender.rootAttributes
111
+ ) : foreignSubtreeRender.html;
109
112
  resolvedHtmlByToken.set(token, resolvedHtml2);
110
113
  return resolvedHtml2;
111
114
  } finally {
@@ -124,13 +127,13 @@ class QueuedBoundaryRuntimeService {
124
127
  html: resolvedHtml
125
128
  };
126
129
  }
127
- createBoundaryToken(tokenPrefix, runtimeContext, boundaryId) {
128
- return `${tokenPrefix}${runtimeContext.componentInstanceScope ?? "root"}__${boundaryId}__`;
130
+ createForeignSubtreeToken(tokenPrefix, runtimeContext, foreignSubtreeId) {
131
+ return `${tokenPrefix}${runtimeContext.componentInstanceScope ?? "root"}__${foreignSubtreeId}__`;
129
132
  }
130
133
  ensureRuntimeContext(options) {
131
134
  let integrationContext;
132
- if (typeof options.boundaryInput.integrationContext === "object" && options.boundaryInput.integrationContext !== null) {
133
- integrationContext = options.boundaryInput.integrationContext;
135
+ if (typeof options.renderInput.integrationContext === "object" && options.renderInput.integrationContext !== null) {
136
+ integrationContext = options.renderInput.integrationContext;
134
137
  } else {
135
138
  integrationContext = {};
136
139
  }
@@ -139,17 +142,17 @@ class QueuedBoundaryRuntimeService {
139
142
  integrationContext[options.runtimeContextKey] = options.createRuntimeContext?.(integrationContext, options.rendererCache) ?? {
140
143
  rendererCache: options.rendererCache,
141
144
  componentInstanceScope: integrationContext.componentInstanceId,
142
- nextBoundaryId: 0,
145
+ nextForeignSubtreeId: 0,
143
146
  queuedResolutions: []
144
147
  };
145
148
  } else {
146
149
  existingRuntimeContext.rendererCache = options.rendererCache;
147
150
  }
148
151
  integrationContext.rendererCache = options.rendererCache;
149
- options.boundaryInput.integrationContext = integrationContext;
152
+ options.renderInput.integrationContext = integrationContext;
150
153
  return integrationContext[options.runtimeContextKey];
151
154
  }
152
155
  }
153
156
  export {
154
- QueuedBoundaryRuntimeService
157
+ QueuedForeignSubtreeResolutionService
155
158
  };
@@ -58,9 +58,9 @@ export declare function wrapWithScriptsInjector<T extends TemplateContentShape>(
58
58
  export declare function wrapWithScriptsInjector<T extends MarkupNodeLikeShape>(content: T, lazyGroups: ResolvedLazyScriptGroups): T;
59
59
  export declare function wrapWithScriptsInjector(content: unknown, lazyGroups: ResolvedLazyScriptGroups): string | TemplateContentShape | MarkupNodeLikeShape;
60
60
  export declare function decodeHtmlEntities(value: string): string;
61
- export declare function normalizeBoundaryArtifactHtml(html: string): string;
62
- export declare function inspectBoundaryArtifactHtml(html: string): {
63
- hasUnresolvedBoundaryArtifacts: boolean;
61
+ export declare function normalizeUnresolvedMarkerArtifactHtml(html: string): string;
62
+ export declare function inspectUnresolvedMarkerArtifactHtml(html: string): {
63
+ hasUnresolvedMarkerArtifacts: boolean;
64
64
  normalizedHtml: string;
65
65
  };
66
66
  export {};
@@ -148,24 +148,24 @@ function decodeHtmlEntities(value) {
148
148
  } while (decoded !== previous);
149
149
  return decoded;
150
150
  }
151
- function normalizeBoundaryArtifactHtml(html) {
151
+ function normalizeUnresolvedMarkerArtifactHtml(html) {
152
152
  return html.replace(
153
153
  /&(?:amp;)?lt;eco-marker\b[\s\S]*?&(?:amp;)?gt;&(?:amp;)?lt;\/eco-marker&(?:amp;)?gt;/g,
154
154
  (marker) => decodeHtmlEntities(marker)
155
155
  );
156
156
  }
157
- function inspectBoundaryArtifactHtml(html) {
158
- const normalizedHtml = normalizeBoundaryArtifactHtml(html);
157
+ function inspectUnresolvedMarkerArtifactHtml(html) {
158
+ const normalizedHtml = normalizeUnresolvedMarkerArtifactHtml(html);
159
159
  return {
160
160
  normalizedHtml,
161
- hasUnresolvedBoundaryArtifacts: normalizedHtml.includes("<eco-marker")
161
+ hasUnresolvedMarkerArtifacts: normalizedHtml.includes("<eco-marker")
162
162
  };
163
163
  }
164
164
  export {
165
165
  addTriggerAttribute,
166
166
  decodeHtmlEntities,
167
- inspectBoundaryArtifactHtml,
167
+ inspectUnresolvedMarkerArtifactHtml,
168
168
  isThenable,
169
- normalizeBoundaryArtifactHtml,
169
+ normalizeUnresolvedMarkerArtifactHtml,
170
170
  wrapWithScriptsInjector
171
171
  };
@@ -0,0 +1,120 @@
1
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
+ import type { ComponentRenderResult, EcoComponent, EcoPageComponent, EcoPageFile, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
3
+ import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
4
+ import { OwnershipValidationService } from './ownership-validation.service.js';
5
+ import { OwnershipPlanningService } from './ownership-planning.service.js';
6
+ export type RouteRenderOrchestratorResolvedInputs = {
7
+ Page: EcoPageFile['default'] | EcoPageComponent<any>;
8
+ HtmlTemplate: EcoComponent<HtmlTemplateProps>;
9
+ Layout?: EcoComponent;
10
+ props: Record<string, unknown>;
11
+ metadata: PageMetadataProps;
12
+ integrationSpecificProps: Record<string, unknown>;
13
+ };
14
+ export type RouteRenderOrchestratorResolvedAssets = {
15
+ resolvedDependencies: ProcessedAsset[];
16
+ pageBrowserGraph?: PageBrowserGraphResult;
17
+ };
18
+ /**
19
+ * Structural HTML work applied after the route body has been fully resolved.
20
+ *
21
+ * The shared route flow only needs to know whether a post-render HTML step
22
+ * exists. When `finalizeHtml` is absent, the captured body can be reused as-is.
23
+ */
24
+ export type RouteHtmlFinalization = {
25
+ finalizeHtml?(html: string): string;
26
+ };
27
+ export interface RouteRenderOrchestratorAdapter<C> {
28
+ /**
29
+ * Name of the owning Integration for the current route render.
30
+ */
31
+ readonly name: string;
32
+ /**
33
+ * Loads the Integration-owned route inputs needed for one Page render.
34
+ */
35
+ resolveRouteRenderInputs(routeOptions: RouteRendererOptions): Promise<RouteRenderOrchestratorResolvedInputs>;
36
+ /**
37
+ * Resolves route-owned assets needed before Integration rendering starts.
38
+ */
39
+ resolveRouteAssets(input: {
40
+ routeOptions: RouteRendererOptions;
41
+ components: (EcoComponent | Partial<EcoComponent>)[];
42
+ }): Promise<RouteRenderOrchestratorResolvedAssets>;
43
+ /**
44
+ * Resolves the optional page-root render through the foreign-child-aware component contract.
45
+ */
46
+ resolveRoutePageComponentRender(input: {
47
+ Page: EcoComponent;
48
+ Layout?: EcoComponent;
49
+ props: Record<string, unknown>;
50
+ routeOptions: RouteRendererOptions;
51
+ }): Promise<ComponentRenderResult | undefined>;
52
+ /**
53
+ * Executes the Integration-specific route render.
54
+ */
55
+ renderRouteBody(renderOptions: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
56
+ /**
57
+ * Returns the structural Html finalization plan for one prepared route render.
58
+ */
59
+ getRouteHtmlFinalization(renderOptions: IntegrationRendererRenderOptions<C>): RouteHtmlFinalization;
60
+ /**
61
+ * Runs SSR-policy response transformation and returns the body value exposed to callers.
62
+ */
63
+ transformRouteResponse(response: Response): Promise<RouteRendererBody>;
64
+ }
65
+ /**
66
+ * Captured route-render output in both replayable body and string HTML forms.
67
+ */
68
+ export interface CapturedHtmlRenderResult {
69
+ body: RouteRendererBody;
70
+ html: string;
71
+ }
72
+ /**
73
+ * Optional app-scoped collaborators used by the route render orchestrator.
74
+ */
75
+ export interface RouteRenderOrchestratorDependencies {
76
+ ownershipPlanningService?: OwnershipPlanningService;
77
+ ownershipValidationService?: OwnershipValidationService;
78
+ }
79
+ /**
80
+ * Owns one route render from normalized module loading through final HTML output.
81
+ *
82
+ * This orchestrator keeps route rendering as one app-scoped unit while still
83
+ * delegating integration-specific behavior through the adapter seam. It owns
84
+ * route-root validation, dependency aggregation, page package creation, and the
85
+ * final HTML/body handling that happens after the integration render returns.
86
+ */
87
+ export declare class RouteRenderOrchestrator {
88
+ private readonly appConfig;
89
+ private readonly assetProcessingService;
90
+ private readonly ownershipPlanningService;
91
+ private readonly ownershipValidationService;
92
+ constructor(appConfig: EcoPagesAppConfig, assetProcessingService: AssetProcessingService, dependencies?: RouteRenderOrchestratorDependencies);
93
+ /**
94
+ * Builds normalized route render options before the integration render runs.
95
+ *
96
+ * This preparation step validates route-root ownership, resolves page data,
97
+ * collects processed assets, captures optional page-root render metadata, and
98
+ * produces the page package consumed by downstream HTML transformation.
99
+ */
100
+ prepareRenderOptions<C = unknown>(routeOptions: RouteRendererOptions, adapter: RouteRenderOrchestratorAdapter<C>): Promise<IntegrationRendererRenderOptions<C>>;
101
+ /**
102
+ * Captures one route render body as HTML while preserving a replayable body value.
103
+ */
104
+ captureHtmlRender(render: () => Promise<RouteRendererBody>): Promise<CapturedHtmlRenderResult>;
105
+ /**
106
+ * Executes the full route-render flow and returns the final body plus cache strategy.
107
+ */
108
+ execute<C = unknown>(options: RouteRendererOptions, adapter: RouteRenderOrchestratorAdapter<C>): Promise<RouteRenderResult>;
109
+ /**
110
+ * Executes the route-render finalization path for already prepared render options.
111
+ */
112
+ executePrepared<C = unknown>(renderOptions: IntegrationRendererRenderOptions<C>, adapter: RouteRenderOrchestratorAdapter<C>): Promise<RouteRenderResult>;
113
+ private captureRenderedBody;
114
+ private collectResolvedTriggers;
115
+ private collectUsedIntegrationDependencies;
116
+ private collectIntegrationNames;
117
+ private buildGlobalInjectorAssets;
118
+ private buildEagerSsrLazyAssets;
119
+ private collectEagerSsrLazyDependencies;
120
+ }