@ecopages/core 0.2.0-beta.27 → 0.2.0-beta.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 (70) hide show
  1. package/README.md +1 -1
  2. package/package.json +42 -26
  3. package/src/eco/eco.js +2 -2
  4. package/src/eco/eco.utils.d.ts +1 -1
  5. package/src/eco/eco.utils.js +1 -1
  6. package/src/hmr/client/hmr-runtime.js +1 -1
  7. package/src/hmr/hmr-file-change-prep.d.ts +1 -1
  8. package/src/hmr/hmr-file-change-prep.js +1 -1
  9. package/src/plugins/processor.d.ts +8 -0
  10. package/src/route-renderer/GRAPH.md +11 -11
  11. package/src/route-renderer/README.md +11 -6
  12. package/src/route-renderer/orchestration/{document-shell-render.service.d.ts → document-shell/document-shell-render.service.d.ts} +19 -2
  13. package/src/route-renderer/orchestration/{document-shell-render.service.js → document-shell/document-shell-render.service.js} +28 -0
  14. package/src/route-renderer/orchestration/{layout-shell-props.service.d.ts → document-shell/layout-shell-props.service.d.ts} +1 -1
  15. package/src/route-renderer/orchestration/{component-render-context.d.ts → foreign-child/component-render-context.d.ts} +1 -1
  16. package/src/route-renderer/orchestration/{component-render-context.js → foreign-child/component-render-context.js} +1 -1
  17. package/src/route-renderer/orchestration/{render-output.utils.d.ts → foreign-child/foreign-child-output.utils.d.ts} +1 -7
  18. package/src/route-renderer/orchestration/{render-output.utils.js → foreign-child/foreign-child-output.utils.js} +1 -26
  19. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.d.ts → foreign-child/foreign-subtree-execution.service.d.ts} +5 -5
  20. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.js → foreign-child/foreign-subtree-execution.service.js} +2 -2
  21. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts +19 -0
  22. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js +35 -0
  23. package/src/route-renderer/orchestration/integration-renderer.d.ts +4 -46
  24. package/src/route-renderer/orchestration/integration-renderer.js +53 -157
  25. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.d.ts +75 -0
  26. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.js +177 -0
  27. package/src/route-renderer/orchestration/{component-graph-collectors.d.ts → ownership-graph/component-graph-collectors.d.ts} +4 -4
  28. package/src/route-renderer/orchestration/{component-graph-collectors.js → ownership-graph/component-graph-collectors.js} +1 -1
  29. package/src/route-renderer/orchestration/{component-graph.d.ts → ownership-graph/component-graph.d.ts} +1 -3
  30. package/src/route-renderer/orchestration/{component-graph.js → ownership-graph/component-graph.js} +0 -2
  31. package/src/route-renderer/orchestration/{ownership-validation.service.d.ts → ownership-graph/ownership-validation.service.d.ts} +2 -2
  32. package/src/route-renderer/orchestration/{ownership-validation.service.js → ownership-graph/ownership-validation.service.js} +3 -4
  33. package/src/route-renderer/orchestration/{global-injector-assets.service.d.ts → page-browser-graph/global-injector-assets.service.d.ts} +3 -3
  34. package/src/route-renderer/orchestration/{global-injector-assets.service.js → page-browser-graph/global-injector-assets.service.js} +2 -2
  35. package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.d.ts → page-browser-graph/page-browser-graph-contribution.loader.d.ts} +1 -1
  36. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts +12 -0
  37. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js +29 -0
  38. package/src/route-renderer/orchestration/{page-browser-graph-session.d.ts → page-browser-graph/page-browser-graph-session.d.ts} +3 -3
  39. package/src/route-renderer/orchestration/{page-browser-graph-session.js → page-browser-graph/page-browser-graph-session.js} +1 -1
  40. package/src/route-renderer/orchestration/{page-browser-graph.service.d.ts → page-browser-graph/page-browser-graph.service.d.ts} +3 -3
  41. package/src/route-renderer/orchestration/{page-browser-graph.service.js → page-browser-graph/page-browser-graph.service.js} +3 -3
  42. package/src/route-renderer/orchestration/{processed-asset-dedupe.d.ts → page-browser-graph/processed-asset-dedupe.d.ts} +1 -1
  43. package/src/route-renderer/orchestration/{integration-route-render-adapter.d.ts → route-pipeline/integration-route-render-adapter.d.ts} +3 -3
  44. package/src/route-renderer/orchestration/{integration-route-render-adapter.js → route-pipeline/integration-route-render-adapter.js} +1 -1
  45. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.d.ts +6 -0
  46. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.js +27 -0
  47. package/src/route-renderer/orchestration/{route-html-finalization.service.d.ts → route-pipeline/route-html-finalization.service.d.ts} +2 -2
  48. package/src/route-renderer/orchestration/{route-prepared-options.builder.d.ts → route-pipeline/route-prepared-options.builder.d.ts} +3 -3
  49. package/src/route-renderer/orchestration/{route-prepared-options.builder.js → route-pipeline/route-prepared-options.builder.js} +2 -2
  50. package/src/route-renderer/orchestration/{route-prepared-options.utils.js → route-pipeline/route-prepared-options.utils.js} +1 -1
  51. package/src/route-renderer/orchestration/{route-render-orchestrator.d.ts → route-pipeline/route-render-orchestrator.d.ts} +6 -6
  52. package/src/route-renderer/orchestration/{route-render-orchestrator.js → route-pipeline/route-render-orchestrator.js} +8 -5
  53. package/src/route-renderer/orchestration/string-markup-renderer.js +1 -1
  54. package/src/services/html/html-transformer.service.js +1 -1
  55. package/src/services/invalidation/development-invalidation.service.d.ts +5 -0
  56. package/src/services/invalidation/development-invalidation.service.js +8 -9
  57. package/src/static-site-generator/production-page-browser-graph-prebuild.js +1 -1
  58. package/src/types/internal-types.d.ts +1 -1
  59. package/src/types/public-types.d.ts +1 -1
  60. package/src/watchers/project-watcher.d.ts +3 -3
  61. package/src/watchers/project-watcher.js +4 -4
  62. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +0 -1
  63. package/src/route-renderer/orchestration/declared-ownership-graph.js +0 -8
  64. package/src/route-renderer/orchestration/template-serialization.d.ts +0 -38
  65. package/src/route-renderer/orchestration/template-serialization.js +0 -45
  66. /package/src/route-renderer/orchestration/{layout-shell-props.service.js → document-shell/layout-shell-props.service.js} +0 -0
  67. /package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.js → page-browser-graph/page-browser-graph-contribution.loader.js} +0 -0
  68. /package/src/route-renderer/orchestration/{processed-asset-dedupe.js → page-browser-graph/processed-asset-dedupe.js} +0 -0
  69. /package/src/route-renderer/orchestration/{route-html-finalization.service.js → route-pipeline/route-html-finalization.service.js} +0 -0
  70. /package/src/route-renderer/orchestration/{route-prepared-options.utils.d.ts → route-pipeline/route-prepared-options.utils.d.ts} +0 -0
package/README.md CHANGED
@@ -267,7 +267,7 @@ Use these entrypoints when implementing integrations, processors, or source tran
267
267
  - `@ecopages/core/plugins/integration-plugin`
268
268
  - `@ecopages/core/plugins/processor`
269
269
  - `@ecopages/core/plugins/source-transform`
270
- - `@ecopages/core/route-renderer/integration-renderer`
270
+ - `@ecopages/core/route-renderer/orchestration/integration-renderer`
271
271
  - `@ecopages/core/services/asset-processing-service`
272
272
  - `@ecopages/core/hmr/hmr-strategy`
273
273
  - `@ecopages/core/integrations/ghtml`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.27",
3
+ "version": "0.2.0-beta.28",
4
4
  "description": "Core package for Ecopages",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -17,7 +17,7 @@
17
17
  "directory": "packages/core"
18
18
  },
19
19
  "dependencies": {
20
- "@ecopages/file-system": "0.2.0-beta.27",
20
+ "@ecopages/file-system": "0.2.0-beta.28",
21
21
  "@ecopages/logger": "^0.2.3",
22
22
  "@ecopages/scripts-injector": "^0.1.5",
23
23
  "@oxc-project/runtime": "0.134.0",
@@ -44,11 +44,11 @@
44
44
  "types": "./src/adapters/create-app.d.ts",
45
45
  "default": "./src/adapters/create-app.js"
46
46
  },
47
- "./route-renderer/integration-renderer": {
47
+ "./route-renderer/orchestration/integration-renderer": {
48
48
  "types": "./src/route-renderer/orchestration/integration-renderer.d.ts",
49
49
  "default": "./src/route-renderer/orchestration/integration-renderer.js"
50
50
  },
51
- "./route-renderer/string-markup-renderer": {
51
+ "./route-renderer/orchestration/string-markup-renderer": {
52
52
  "types": "./src/route-renderer/orchestration/string-markup-renderer.d.ts",
53
53
  "default": "./src/route-renderer/orchestration/string-markup-renderer.js"
54
54
  },
@@ -56,18 +56,26 @@
56
56
  "types": "./src/route-renderer/route-renderer.d.ts",
57
57
  "default": "./src/route-renderer/route-renderer.js"
58
58
  },
59
- "./route-renderer/component-render-context": {
60
- "import": "./src/route-renderer/orchestration/component-render-context.js",
61
- "types": "./src/route-renderer/orchestration/component-render-context.d.ts",
62
- "default": "./src/route-renderer/orchestration/component-render-context.js"
59
+ "./route-renderer/orchestration/foreign-child/component-render-context": {
60
+ "import": "./src/route-renderer/orchestration/foreign-child/component-render-context.js",
61
+ "types": "./src/route-renderer/orchestration/foreign-child/component-render-context.d.ts",
62
+ "default": "./src/route-renderer/orchestration/foreign-child/component-render-context.js"
63
63
  },
64
- "./route-renderer/orchestration/foreign-subtree-execution.service": {
65
- "types": "./src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts",
66
- "default": "./src/route-renderer/orchestration/foreign-subtree-execution.service.js"
64
+ "./route-renderer/orchestration/foreign-child/foreign-subtree-execution.service": {
65
+ "types": "./src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.d.ts",
66
+ "default": "./src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js"
67
67
  },
68
- "./route-renderer/orchestration/document-shell-render.service": {
69
- "types": "./src/route-renderer/orchestration/document-shell-render.service.d.ts",
70
- "default": "./src/route-renderer/orchestration/document-shell-render.service.js"
68
+ "./route-renderer/orchestration/document-shell/document-shell-render.service": {
69
+ "types": "./src/route-renderer/orchestration/document-shell/document-shell-render.service.d.ts",
70
+ "default": "./src/route-renderer/orchestration/document-shell/document-shell-render.service.js"
71
+ },
72
+ "./route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils": {
73
+ "types": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts",
74
+ "default": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js"
75
+ },
76
+ "./route-renderer/orchestration/foreign-child/owning-renderer-resolution": {
77
+ "types": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts",
78
+ "default": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js"
71
79
  },
72
80
  "./router/navigation-coordinator": {
73
81
  "types": "./src/router/client/navigation-coordinator.d.ts",
@@ -269,11 +277,11 @@
269
277
  "types": "./src/adapters/create-app.d.ts",
270
278
  "default": "./src/adapters/create-app.js"
271
279
  },
272
- "./route-renderer/integration-renderer.ts": {
280
+ "./route-renderer/orchestration/integration-renderer.ts": {
273
281
  "types": "./src/route-renderer/orchestration/integration-renderer.d.ts",
274
282
  "default": "./src/route-renderer/orchestration/integration-renderer.js"
275
283
  },
276
- "./route-renderer/string-markup-renderer.ts": {
284
+ "./route-renderer/orchestration/string-markup-renderer.ts": {
277
285
  "types": "./src/route-renderer/orchestration/string-markup-renderer.d.ts",
278
286
  "default": "./src/route-renderer/orchestration/string-markup-renderer.js"
279
287
  },
@@ -281,18 +289,26 @@
281
289
  "types": "./src/route-renderer/route-renderer.d.ts",
282
290
  "default": "./src/route-renderer/route-renderer.js"
283
291
  },
284
- "./route-renderer/component-render-context.ts": {
285
- "import": "./src/route-renderer/orchestration/component-render-context.js",
286
- "types": "./src/route-renderer/orchestration/component-render-context.d.ts",
287
- "default": "./src/route-renderer/orchestration/component-render-context.js"
292
+ "./route-renderer/orchestration/foreign-child/component-render-context.ts": {
293
+ "import": "./src/route-renderer/orchestration/foreign-child/component-render-context.js",
294
+ "types": "./src/route-renderer/orchestration/foreign-child/component-render-context.d.ts",
295
+ "default": "./src/route-renderer/orchestration/foreign-child/component-render-context.js"
296
+ },
297
+ "./route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.ts": {
298
+ "types": "./src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.d.ts",
299
+ "default": "./src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.js"
300
+ },
301
+ "./route-renderer/orchestration/document-shell/document-shell-render.service.ts": {
302
+ "types": "./src/route-renderer/orchestration/document-shell/document-shell-render.service.d.ts",
303
+ "default": "./src/route-renderer/orchestration/document-shell/document-shell-render.service.js"
288
304
  },
289
- "./route-renderer/orchestration/foreign-subtree-execution.service.ts": {
290
- "types": "./src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts",
291
- "default": "./src/route-renderer/orchestration/foreign-subtree-execution.service.js"
305
+ "./route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.ts": {
306
+ "types": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts",
307
+ "default": "./src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js"
292
308
  },
293
- "./route-renderer/orchestration/document-shell-render.service.ts": {
294
- "types": "./src/route-renderer/orchestration/document-shell-render.service.d.ts",
295
- "default": "./src/route-renderer/orchestration/document-shell-render.service.js"
309
+ "./route-renderer/orchestration/foreign-child/owning-renderer-resolution.ts": {
310
+ "types": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts",
311
+ "default": "./src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js"
296
312
  },
297
313
  "./router/navigation-coordinator.ts": {
298
314
  "types": "./src/router/client/navigation-coordinator.d.ts",
package/src/eco/eco.js CHANGED
@@ -2,8 +2,8 @@ import {
2
2
  finalizeComponentRender,
3
3
  getComponentRenderContext,
4
4
  interceptForeignChild
5
- } from "../route-renderer/orchestration/component-render-context.js";
6
- import { isThenable } from "../route-renderer/orchestration/render-output.utils.js";
5
+ } from "../route-renderer/orchestration/foreign-child/component-render-context.js";
6
+ import { isThenable } from "../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js";
7
7
  import { applyPageLayoutConfig, mergeLayoutDependencies, normalizePageLayouts } from "./page-layout-normalization.js";
8
8
  function createComponentFactory(options) {
9
9
  const integrationName = options.integration ?? options.__eco?.integration;
@@ -1 +1 @@
1
- export { addTriggerAttribute, isThenable, wrapWithScriptsInjector, } from '../route-renderer/orchestration/render-output.utils.js';
1
+ export { addTriggerAttribute, isThenable, wrapWithScriptsInjector, } from '../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js';
@@ -2,7 +2,7 @@ import {
2
2
  addTriggerAttribute,
3
3
  isThenable,
4
4
  wrapWithScriptsInjector
5
- } from "../route-renderer/orchestration/render-output.utils.js";
5
+ } from "../route-renderer/orchestration/foreign-child/foreign-child-output.utils.js";
6
6
  export {
7
7
  addTriggerAttribute,
8
8
  isThenable,
@@ -88,7 +88,7 @@ import { getEcoNavigationRuntime } from "../../router/client/navigation-coordina
88
88
  }
89
89
  await new Promise((resolve) => {
90
90
  const startedAt = performance.now();
91
- const timeoutMs = 2e3;
91
+ const timeoutMs = 5e3;
92
92
  const poll = () => {
93
93
  if (!navigationRuntime.hasPendingNavigationTransaction()) {
94
94
  resolve();
@@ -1,5 +1,5 @@
1
1
  import type { EcoPagesAppConfig } from '../types/internal-types.js';
2
- import { type AffectedGraphIdentity } from '../route-renderer/orchestration/page-browser-graph-session.js';
2
+ import { type AffectedGraphIdentity } from '../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js';
3
3
  export type HmrFileChangePreparation = {
4
4
  affectedGraphIdentities: AffectedGraphIdentity[];
5
5
  invalidatedGraphCount: number;
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
  import {
3
3
  getAffectedPageBrowserGraphIdentities,
4
4
  invalidatePageBrowserGraphSession
5
- } from "../route-renderer/orchestration/page-browser-graph-session.js";
5
+ } from "../route-renderer/orchestration/page-browser-graph/page-browser-graph-session.js";
6
6
  function prepareHmrFileChange(appConfig, filePath) {
7
7
  const resolvedFilePath = path.resolve(filePath);
8
8
  const affectedGraphIdentities = getAffectedPageBrowserGraphIdentities(appConfig, resolvedFilePath);
@@ -19,6 +19,14 @@ export interface ProcessorWatchContext {
19
19
  }
20
20
  export interface ProcessorWatchConfig {
21
21
  paths: string[];
22
+ /**
23
+ * File extensions that trigger watch callbacks (`onCreate`, `onChange`, `onDelete`).
24
+ *
25
+ * @remarks
26
+ * Watch extensions drive notifications only. They do not declare asset ownership.
27
+ * Ownership requires {@link ProcessorConfig.capabilities} and controls whether dev
28
+ * invalidation skips server modules and HMR for processor-handled assets.
29
+ */
22
30
  extensions?: string[];
23
31
  onCreate?: (ctx: ProcessorWatchContext) => Promise<void>;
24
32
  onChange?: (ctx: ProcessorWatchContext) => Promise<void>;
@@ -85,7 +85,7 @@ flowchart TD
85
85
  B -- Yes --> C[return resolved renderer-owned output]
86
86
  B -- No --> D[queue foreign-subtree token]
87
87
  D --> E[finish current render pass]
88
- E --> F[QueuedForeignSubtreeResolutionService resolveQueuedHtml]
88
+ E --> F[ForeignSubtreeExecutionService resolveQueuedHtml]
89
89
  F --> G[resolve nested queued tokens first]
90
90
  G --> H[dispatch foreign subtree to owning renderer]
91
91
  H --> I[merge emitted assets and root attributes]
@@ -115,12 +115,12 @@ flowchart TD
115
115
  The most useful reading order is:
116
116
 
117
117
  1. `route-renderer.ts`
118
- 2. `orchestration/route-render-orchestrator.ts`
118
+ 2. `orchestration/route-pipeline/route-render-orchestrator.ts`
119
119
  3. `orchestration/integration-renderer.ts`
120
- 4. `orchestration/ownership-validation.service.ts`
121
- 5. `orchestration/component-graph.ts`
122
- 6. `orchestration/component-render-context.ts`
123
- 7. `orchestration/foreign-subtree-execution.service.ts`
120
+ 4. `orchestration/ownership-graph/ownership-validation.service.ts`
121
+ 5. `orchestration/ownership-graph/component-graph.ts`
122
+ 6. `orchestration/foreign-child/component-render-context.ts`
123
+ 7. `orchestration/foreign-child/foreign-subtree-execution.service.ts`
124
124
  8. `page-loading/page-module-loader.ts`
125
125
  9. `page-loading/dependency-resolver.ts`
126
126
  10. `eco/eco.ts`
@@ -128,12 +128,12 @@ The most useful reading order is:
128
128
  ## 7) Key Files
129
129
 
130
130
  - `packages/core/src/route-renderer/route-renderer.ts`
131
- - `packages/core/src/route-renderer/orchestration/route-render-orchestrator.ts`
131
+ - `packages/core/src/route-renderer/orchestration/route-pipeline/route-render-orchestrator.ts`
132
132
  - `packages/core/src/route-renderer/orchestration/integration-renderer.ts`
133
- - `packages/core/src/route-renderer/orchestration/ownership-validation.service.ts`
134
- - `packages/core/src/route-renderer/orchestration/component-graph.ts`
135
- - `packages/core/src/route-renderer/orchestration/component-render-context.ts`
136
- - `packages/core/src/route-renderer/orchestration/foreign-subtree-execution.service.ts`
133
+ - `packages/core/src/route-renderer/orchestration/ownership-graph/ownership-validation.service.ts`
134
+ - `packages/core/src/route-renderer/orchestration/ownership-graph/component-graph.ts`
135
+ - `packages/core/src/route-renderer/orchestration/foreign-child/component-render-context.ts`
136
+ - `packages/core/src/route-renderer/orchestration/foreign-child/foreign-subtree-execution.service.ts`
137
137
  - `packages/core/src/route-renderer/page-loading/page-module-loader.ts`
138
138
  - `packages/core/src/route-renderer/page-loading/dependency-resolver.ts`
139
139
  - `packages/core/src/eco/eco.ts`
@@ -50,13 +50,18 @@ See [`../build/README.md`](../build/README.md) for the JSX ownership helper spli
50
50
 
51
51
  ### `orchestration/`
52
52
 
53
- - `route-render-orchestrator.ts`: one route render from page-module loading through final HTML output
53
+ Root renderer classes:
54
+
54
55
  - `integration-renderer.ts`: abstract base class for route rendering, explicit view rendering, and foreign-child delegation
55
- - `ownership-validation.service.ts`: up-front ownership validation for route roots and declared descendants
56
- - `foreign-subtree-execution.service.ts`: foreign-child execution policy, queue token resolution, and `toForeignSubtreeRenderPayload()`
57
- - `document-shell-render.service.ts`: shared page/layout/html shell composition and attribute stamping helpers
58
- - `component-render-context.ts`: active render context used by `eco.component()` to intercept foreign children
59
- - `render-output.utils.ts`: unresolved artifact normalization and marker inspection
56
+ - `string-markup-renderer.ts`: shared base for string-markup integrations
57
+
58
+ Domain folders:
59
+
60
+ - `ownership-graph/`: component graph walk/collect and up-front ownership validation
61
+ - `foreign-child/`: active render context, foreign-subtree execution, deferred template serialization
62
+ - `document-shell/`: shared page/layout/html shell composition and attribute stamping helpers
63
+ - `route-pipeline/`: route preparation, final HTML capture, marker-artifact enforcement
64
+ - `page-browser-graph/`: browser asset graph build, session cache, and injector assets
60
65
 
61
66
  ### `page-loading/`
62
67
 
@@ -1,5 +1,6 @@
1
- import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, PageMetadataProps } from '../../types/public-types.js';
2
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, PageMetadataProps } from '../../../types/public-types.js';
2
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
3
+ import { HtmlTransformerService, type HtmlDocumentContribution } from '../../../services/html/html-transformer.service.js';
3
4
  export type DocumentShellLayoutInput = {
4
5
  component: EcoComponent;
5
6
  props?: Record<string, unknown>;
@@ -88,3 +89,19 @@ export declare function applyDocumentShellAttributeStamping(html: string, stampi
88
89
  * Builds the final serialized document HTML for one route page render.
89
90
  */
90
91
  export declare function renderPageDocumentShell(dependencies: DocumentShellRenderDependencies, input: DocumentShellPageRenderInput, docType: string): Promise<string>;
92
+ /**
93
+ * Finalizes already-resolved HTML for explicit renderer-owned paths.
94
+ *
95
+ * @remarks
96
+ * Callers append renderer bootstrap dependencies before invoking this helper.
97
+ * Stamps document/root attributes and runs HTML transformation after nested
98
+ * foreign-subtree resolution without routing back through shared route execution.
99
+ */
100
+ export declare function finalizeDocumentShellHtml(htmlTransformer: HtmlTransformerService, options: {
101
+ html: string;
102
+ partial?: boolean;
103
+ componentRootAttributes?: Record<string, string>;
104
+ documentAttributes?: Record<string, string>;
105
+ transformHtml?: boolean;
106
+ htmlContributions?: HtmlDocumentContribution[];
107
+ }): Promise<string>;
@@ -1,3 +1,6 @@
1
+ import {
2
+ HtmlTransformerService
3
+ } from "../../../services/html/html-transformer.service.js";
1
4
  function resolveDocumentShellLayouts(input) {
2
5
  if (input.layouts && input.layouts.length > 0) {
3
6
  return input.layouts;
@@ -112,9 +115,34 @@ async function renderPageDocumentShell(dependencies, input, docType) {
112
115
  const documentHtml = input.transformDocumentHtml ? input.transformDocumentHtml(composedDocumentHtml) : composedDocumentHtml;
113
116
  return `${docType}${documentHtml}`;
114
117
  }
118
+ async function finalizeDocumentShellHtml(htmlTransformer, options) {
119
+ const html = applyDocumentShellAttributeStamping(
120
+ options.html,
121
+ {
122
+ applyAttributesToFirstBodyElement: (nextHtml, attributes) => htmlTransformer.applyAttributesToFirstBodyElement(nextHtml, attributes),
123
+ applyAttributesToHtmlElement: (nextHtml, attributes) => htmlTransformer.applyAttributesToHtmlElement(nextHtml, attributes)
124
+ },
125
+ {
126
+ componentRootAttributes: options.componentRootAttributes,
127
+ documentAttributes: options.documentAttributes
128
+ }
129
+ );
130
+ const shouldTransform = options.transformHtml ?? !options.partial;
131
+ if (!shouldTransform) {
132
+ return html;
133
+ }
134
+ const transformedResponse = await htmlTransformer.transform(
135
+ new Response(html, {
136
+ headers: { "Content-Type": "text/html" }
137
+ }),
138
+ options.htmlContributions
139
+ );
140
+ return await transformedResponse.text();
141
+ }
115
142
  export {
116
143
  applyDocumentShellAttributeStamping,
117
144
  composeDocumentShell,
118
145
  composeSequentialLayoutChildren,
146
+ finalizeDocumentShellHtml,
119
147
  renderPageDocumentShell
120
148
  };
@@ -1,4 +1,4 @@
1
- import type { EcoPageLayoutEntry, LayoutPropsContext } from '../../types/public-types.js';
1
+ import type { EcoPageLayoutEntry, LayoutPropsContext } from '../../../types/public-types.js';
2
2
  export type LayoutShellPropsContext = LayoutPropsContext & {
3
3
  pageProps?: Record<string, unknown>;
4
4
  };
@@ -1,4 +1,4 @@
1
- import type { EcoComponent } from '../../types/public-types.js';
1
+ import type { EcoComponent } from '../../../types/public-types.js';
2
2
  /**
3
3
  * Result returned by a renderer-owned foreign-child runtime.
4
4
  *
@@ -1,4 +1,4 @@
1
- import { addTriggerAttribute, isThenable, wrapWithScriptsInjector } from "./render-output.utils.js";
1
+ import { addTriggerAttribute, isThenable, wrapWithScriptsInjector } from "./foreign-child-output.utils.js";
2
2
  class ComponentRenderOutputRuntime {
3
3
  finalizeComponentRender(component, content) {
4
4
  const lazyTriggers = component.config?._resolvedLazyTriggers;
@@ -1,4 +1,4 @@
1
- import type { EcoComponent } from '../../types/public-types.js';
1
+ import type { EcoComponent } from '../../../types/public-types.js';
2
2
  type ResolvedLazyScriptGroups = Readonly<NonNullable<EcoComponent['config']>['_resolvedLazyScripts']>;
3
3
  type TemplateContentShape = {
4
4
  strings: readonly string[];
@@ -73,10 +73,4 @@ export declare function wrapWithScriptsInjector(content: string, lazyGroups: Res
73
73
  export declare function wrapWithScriptsInjector<T extends TemplateContentShape>(content: T, lazyGroups: ResolvedLazyScriptGroups): T | string;
74
74
  export declare function wrapWithScriptsInjector<T extends MarkupNodeLikeShape>(content: T, lazyGroups: ResolvedLazyScriptGroups): T;
75
75
  export declare function wrapWithScriptsInjector(content: unknown, lazyGroups: ResolvedLazyScriptGroups): string | TemplateContentShape | MarkupNodeLikeShape;
76
- export declare function decodeHtmlEntities(value: string): string;
77
- export declare function normalizeUnresolvedMarkerArtifactHtml(html: string): string;
78
- export declare function inspectUnresolvedMarkerArtifactHtml(html: string): {
79
- hasUnresolvedMarkerArtifacts: boolean;
80
- normalizedHtml: string;
81
- };
82
76
  export {};
@@ -1,4 +1,4 @@
1
- import { buildInjectorMapScript } from "../../eco/lazy-injector-map.js";
1
+ import { buildInjectorMapScript } from "../../../eco/lazy-injector-map.js";
2
2
  function cloneTemplateStrings(strings, firstString) {
3
3
  const nextStrings = [...strings];
4
4
  nextStrings[0] = firstString;
@@ -170,36 +170,11 @@ function wrapWithScriptsInjector(content, lazyGroups) {
170
170
  }
171
171
  return wrapWithScriptsInjectorMarkup(String(content), injectorMapScript);
172
172
  }
173
- function decodeHtmlEntities(value) {
174
- let decoded = value;
175
- let previous;
176
- do {
177
- previous = decoded;
178
- decoded = decoded.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&#x27;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
179
- } while (decoded !== previous);
180
- return decoded;
181
- }
182
- function normalizeUnresolvedMarkerArtifactHtml(html) {
183
- return html.replace(
184
- /&(?:amp;)?lt;eco-marker\b[\s\S]*?&(?:amp;)?gt;&(?:amp;)?lt;\/eco-marker&(?:amp;)?gt;/g,
185
- (marker) => decodeHtmlEntities(marker)
186
- );
187
- }
188
- function inspectUnresolvedMarkerArtifactHtml(html) {
189
- const normalizedHtml = normalizeUnresolvedMarkerArtifactHtml(html);
190
- return {
191
- normalizedHtml,
192
- hasUnresolvedMarkerArtifacts: normalizedHtml.includes("<eco-marker")
193
- };
194
- }
195
173
  export {
196
174
  addTriggerAttribute,
197
175
  assertForeignChildrenNotOpaque,
198
- decodeHtmlEntities,
199
- inspectUnresolvedMarkerArtifactHtml,
200
176
  isMarkupNodeLike,
201
177
  isOpaqueForeignChildValue,
202
178
  isThenable,
203
- normalizeUnresolvedMarkerArtifactHtml,
204
179
  wrapWithScriptsInjector
205
180
  };
@@ -1,5 +1,5 @@
1
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
2
- import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, ForeignSubtreeRenderPayload } from '../../types/public-types.js';
1
+ import type { ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
2
+ import type { BaseIntegrationContext, ComponentRenderInput, ComponentRenderResult, EcoComponent, ForeignSubtreeRenderPayload } from '../../../types/public-types.js';
3
3
  import { type ForeignChildRuntime } from './component-render-context.js';
4
4
  export type QueuedForeignChildDecisionInput = {
5
5
  currentIntegration: string;
@@ -46,7 +46,7 @@ export interface ForeignSubtreeExecutionRenderOptions {
46
46
  renderInput: ComponentRenderInput;
47
47
  rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>;
48
48
  }): ForeignChildRuntime;
49
- getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
49
+ getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): Promise<ForeignSubtreeExecutionOwningRenderer>;
50
50
  }
51
51
  export interface ForeignSubtreeQueuedRuntimeOptions<TContext extends QueuedForeignSubtreeResolutionContext> {
52
52
  renderInput: ComponentRenderInput;
@@ -61,7 +61,7 @@ export interface ForeignSubtreeStringQueuedHtmlOptions {
61
61
  html: string;
62
62
  runtimeContextKey: string;
63
63
  queueLabel: string;
64
- getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
64
+ getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): Promise<ForeignSubtreeExecutionOwningRenderer>;
65
65
  applyAttributesToFirstElement(html: string, attributes: Record<string, string>): string;
66
66
  dedupeProcessedAssets(assets: ProcessedAsset[]): ProcessedAsset[];
67
67
  }
@@ -75,7 +75,7 @@ export interface ForeignSubtreeQueuedHtmlOptions<TContext extends QueuedForeignS
75
75
  children?: unknown;
76
76
  html?: string;
77
77
  }>;
78
- getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): ForeignSubtreeExecutionOwningRenderer;
78
+ getOwningRenderer(integrationName: string, rendererCache: Map<string, ForeignSubtreeExecutionOwningRenderer>): Promise<ForeignSubtreeExecutionOwningRenderer>;
79
79
  applyAttributesToFirstElement(html: string, attributes: Record<string, string>): string;
80
80
  dedupeProcessedAssets(assets: ProcessedAsset[]): ProcessedAsset[];
81
81
  }
@@ -2,7 +2,7 @@ import {
2
2
  getComponentRenderContext,
3
3
  runWithComponentRenderContext
4
4
  } from "./component-render-context.js";
5
- import { assertForeignChildrenNotOpaque, isMarkupNodeLike } from "./render-output.utils.js";
5
+ import { assertForeignChildrenNotOpaque, isMarkupNodeLike } from "./foreign-child-output.utils.js";
6
6
  function toForeignSubtreeRenderPayload(result) {
7
7
  return {
8
8
  html: result.html,
@@ -313,7 +313,7 @@ class ForeignSubtreeExecutionService {
313
313
  if (!foreignOwnerIntegrationName) {
314
314
  return void 0;
315
315
  }
316
- const owningRenderer = options.getOwningRenderer(foreignOwnerIntegrationName, options.rendererCache);
316
+ const owningRenderer = await options.getOwningRenderer(foreignOwnerIntegrationName, options.rendererCache);
317
317
  if (owningRenderer.name === options.currentIntegrationName) {
318
318
  return void 0;
319
319
  }
@@ -0,0 +1,19 @@
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { ForeignSubtreeExecutionOwningRenderer } from './foreign-subtree-execution.service.js';
3
+ export type OwningRendererResolutionInput = {
4
+ appConfig: EcoPagesAppConfig;
5
+ runtimeOrigin: string;
6
+ currentIntegrationName: string;
7
+ currentRenderer: ForeignSubtreeExecutionOwningRenderer;
8
+ integrationName: string;
9
+ cache: Map<string, ForeignSubtreeExecutionOwningRenderer>;
10
+ };
11
+ export declare function getForeignSubtreeTokenPrefix(integrationName: string): string;
12
+ export declare function getForeignSubtreeResolutionContextKey(integrationName: string): string;
13
+ /**
14
+ * Resolves an owning integration renderer for a foreign-child handoff.
15
+ *
16
+ * Reuses the execution-scoped cache, activates the target runtime when needed,
17
+ * and initializes the plugin renderer once per integration name.
18
+ */
19
+ export declare function resolveOwningIntegrationRenderer(input: OwningRendererResolutionInput): Promise<ForeignSubtreeExecutionOwningRenderer>;
@@ -0,0 +1,35 @@
1
+ import { ensureIntegrationRuntimeReady } from "../../../build/app-build-manifest-runtime.js";
2
+ import { invariant } from "../../../utils/invariant.js";
3
+ function getForeignSubtreeTokenPrefix(integrationName) {
4
+ return `__${integrationName}_foreign_subtree__`;
5
+ }
6
+ function getForeignSubtreeResolutionContextKey(integrationName) {
7
+ return `__${integrationName}_foreign_subtree_runtime__`;
8
+ }
9
+ async function resolveOwningIntegrationRenderer(input) {
10
+ const { integrationName, cache } = input;
11
+ if (cache.has(integrationName)) {
12
+ return cache.get(integrationName);
13
+ }
14
+ if (integrationName === input.currentIntegrationName) {
15
+ cache.set(integrationName, input.currentRenderer);
16
+ return input.currentRenderer;
17
+ }
18
+ await ensureIntegrationRuntimeReady({
19
+ appConfig: input.appConfig,
20
+ integrationName,
21
+ runtimeOrigin: input.runtimeOrigin
22
+ });
23
+ const integrationPlugin = input.appConfig.integrations.find((integration) => integration.name === integrationName);
24
+ invariant(!!integrationPlugin, `[ecopages] Integration not found for foreign owner: ${integrationName}`);
25
+ const renderer = integrationPlugin.initializeRenderer({
26
+ rendererModules: input.appConfig.runtime?.rendererModuleContext
27
+ });
28
+ cache.set(integrationName, renderer);
29
+ return renderer;
30
+ }
31
+ export {
32
+ getForeignSubtreeResolutionContextKey,
33
+ getForeignSubtreeTokenPrefix,
34
+ resolveOwningIntegrationRenderer
35
+ };