@ecopages/core 0.2.0-beta.27 → 0.2.0-beta.29

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 (103) hide show
  1. package/README.md +1 -1
  2. package/package.json +42 -26
  3. package/src/adapters/abstract/application-adapter.d.ts +22 -4
  4. package/src/adapters/abstract/application-adapter.js +27 -4
  5. package/src/adapters/abstract/server-adapter.d.ts +10 -0
  6. package/src/adapters/bun/create-app.d.ts +1 -0
  7. package/src/adapters/bun/create-app.js +11 -4
  8. package/src/adapters/bun/server-adapter.js +1 -0
  9. package/src/adapters/node/create-app.d.ts +1 -0
  10. package/src/adapters/node/create-app.js +10 -3
  11. package/src/adapters/node/server-adapter.js +1 -0
  12. package/src/adapters/shared/fs-server-response-factory.d.ts +5 -0
  13. package/src/adapters/shared/fs-server-response-factory.js +20 -0
  14. package/src/adapters/shared/fs-server-response-matcher.d.ts +18 -0
  15. package/src/adapters/shared/fs-server-response-matcher.js +75 -15
  16. package/src/config/config-builder.d.ts +12 -2
  17. package/src/config/config-builder.js +41 -2
  18. package/src/config/constants.d.ts +1 -0
  19. package/src/config/constants.js +2 -1
  20. package/src/eco/eco.js +2 -2
  21. package/src/eco/eco.utils.d.ts +1 -1
  22. package/src/eco/eco.utils.js +1 -1
  23. package/src/hmr/client/hmr-runtime.js +1 -1
  24. package/src/hmr/hmr-file-change-prep.d.ts +1 -1
  25. package/src/hmr/hmr-file-change-prep.js +1 -1
  26. package/src/plugins/processor.d.ts +8 -0
  27. package/src/route-renderer/GRAPH.md +11 -11
  28. package/src/route-renderer/README.md +11 -6
  29. package/src/route-renderer/orchestration/{document-shell-render.service.d.ts → document-shell/document-shell-render.service.d.ts} +19 -2
  30. package/src/route-renderer/orchestration/{document-shell-render.service.js → document-shell/document-shell-render.service.js} +28 -0
  31. package/src/route-renderer/orchestration/{layout-shell-props.service.d.ts → document-shell/layout-shell-props.service.d.ts} +1 -1
  32. package/src/route-renderer/orchestration/{component-render-context.d.ts → foreign-child/component-render-context.d.ts} +1 -1
  33. package/src/route-renderer/orchestration/{component-render-context.js → foreign-child/component-render-context.js} +1 -1
  34. package/src/route-renderer/orchestration/{render-output.utils.d.ts → foreign-child/foreign-child-output.utils.d.ts} +1 -7
  35. package/src/route-renderer/orchestration/{render-output.utils.js → foreign-child/foreign-child-output.utils.js} +1 -26
  36. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.d.ts → foreign-child/foreign-subtree-execution.service.d.ts} +5 -5
  37. package/src/route-renderer/orchestration/{foreign-subtree-execution.service.js → foreign-child/foreign-subtree-execution.service.js} +2 -2
  38. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.d.ts +19 -0
  39. package/src/route-renderer/orchestration/foreign-child/owning-renderer-resolution.js +35 -0
  40. package/src/route-renderer/orchestration/integration-renderer.d.ts +4 -46
  41. package/src/route-renderer/orchestration/integration-renderer.js +53 -157
  42. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.d.ts +75 -0
  43. package/src/route-renderer/orchestration/integration-renderer.test-fixtures.js +177 -0
  44. package/src/route-renderer/orchestration/{component-graph-collectors.d.ts → ownership-graph/component-graph-collectors.d.ts} +4 -4
  45. package/src/route-renderer/orchestration/{component-graph-collectors.js → ownership-graph/component-graph-collectors.js} +1 -1
  46. package/src/route-renderer/orchestration/{component-graph.d.ts → ownership-graph/component-graph.d.ts} +1 -3
  47. package/src/route-renderer/orchestration/{component-graph.js → ownership-graph/component-graph.js} +0 -2
  48. package/src/route-renderer/orchestration/{ownership-validation.service.d.ts → ownership-graph/ownership-validation.service.d.ts} +2 -2
  49. package/src/route-renderer/orchestration/{ownership-validation.service.js → ownership-graph/ownership-validation.service.js} +3 -4
  50. package/src/route-renderer/orchestration/{global-injector-assets.service.d.ts → page-browser-graph/global-injector-assets.service.d.ts} +3 -3
  51. package/src/route-renderer/orchestration/{global-injector-assets.service.js → page-browser-graph/global-injector-assets.service.js} +2 -2
  52. package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.d.ts → page-browser-graph/page-browser-graph-contribution.loader.d.ts} +1 -1
  53. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.d.ts +12 -0
  54. package/src/route-renderer/orchestration/page-browser-graph/page-browser-graph-merge.utils.js +29 -0
  55. package/src/route-renderer/orchestration/{page-browser-graph-session.d.ts → page-browser-graph/page-browser-graph-session.d.ts} +3 -3
  56. package/src/route-renderer/orchestration/{page-browser-graph-session.js → page-browser-graph/page-browser-graph-session.js} +1 -1
  57. package/src/route-renderer/orchestration/{page-browser-graph.service.d.ts → page-browser-graph/page-browser-graph.service.d.ts} +3 -3
  58. package/src/route-renderer/orchestration/{page-browser-graph.service.js → page-browser-graph/page-browser-graph.service.js} +3 -3
  59. package/src/route-renderer/orchestration/{processed-asset-dedupe.d.ts → page-browser-graph/processed-asset-dedupe.d.ts} +1 -1
  60. package/src/route-renderer/orchestration/{integration-route-render-adapter.d.ts → route-pipeline/integration-route-render-adapter.d.ts} +3 -3
  61. package/src/route-renderer/orchestration/{integration-route-render-adapter.js → route-pipeline/integration-route-render-adapter.js} +1 -1
  62. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.d.ts +6 -0
  63. package/src/route-renderer/orchestration/route-pipeline/marker-artifact.utils.js +27 -0
  64. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.d.ts +15 -0
  65. package/src/route-renderer/orchestration/route-pipeline/robots-meta.contribution.js +36 -0
  66. package/src/route-renderer/orchestration/{route-html-finalization.service.d.ts → route-pipeline/route-html-finalization.service.d.ts} +6 -2
  67. package/src/route-renderer/orchestration/{route-html-finalization.service.js → route-pipeline/route-html-finalization.service.js} +5 -2
  68. package/src/route-renderer/orchestration/{route-prepared-options.builder.d.ts → route-pipeline/route-prepared-options.builder.d.ts} +3 -3
  69. package/src/route-renderer/orchestration/{route-prepared-options.builder.js → route-pipeline/route-prepared-options.builder.js} +8 -4
  70. package/src/route-renderer/orchestration/{route-prepared-options.utils.js → route-pipeline/route-prepared-options.utils.js} +1 -1
  71. package/src/route-renderer/orchestration/{route-render-orchestrator.d.ts → route-pipeline/route-render-orchestrator.d.ts} +6 -6
  72. package/src/route-renderer/orchestration/{route-render-orchestrator.js → route-pipeline/route-render-orchestrator.js} +8 -5
  73. package/src/route-renderer/orchestration/string-markup-renderer.js +1 -1
  74. package/src/services/html/html-transformer.service.js +1 -1
  75. package/src/services/invalidation/development-invalidation.service.d.ts +5 -0
  76. package/src/services/invalidation/development-invalidation.service.js +8 -9
  77. package/src/static-site-generator/README.md +9 -1
  78. package/src/static-site-generator/production-page-browser-graph-prebuild.js +1 -1
  79. package/src/static-site-generator/sitemap-routes.d.ts +14 -0
  80. package/src/static-site-generator/sitemap-routes.js +31 -0
  81. package/src/static-site-generator/sitemap.d.ts +15 -0
  82. package/src/static-site-generator/sitemap.js +33 -0
  83. package/src/static-site-generator/static-export-context.d.ts +9 -1
  84. package/src/static-site-generator/static-site-generator.d.ts +27 -6
  85. package/src/static-site-generator/static-site-generator.js +200 -50
  86. package/src/types/internal-types.d.ts +9 -2
  87. package/src/types/public-types.d.ts +78 -2
  88. package/src/utils/ecopages-route-info.d.ts +26 -0
  89. package/src/utils/ecopages-route-info.js +26 -0
  90. package/src/utils/html-escaping.d.ts +7 -0
  91. package/src/utils/html-escaping.js +5 -1
  92. package/src/utils/path-pattern.d.ts +23 -0
  93. package/src/utils/path-pattern.js +27 -0
  94. package/src/watchers/project-watcher.d.ts +3 -3
  95. package/src/watchers/project-watcher.js +4 -4
  96. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +0 -1
  97. package/src/route-renderer/orchestration/declared-ownership-graph.js +0 -8
  98. package/src/route-renderer/orchestration/template-serialization.d.ts +0 -38
  99. package/src/route-renderer/orchestration/template-serialization.js +0 -45
  100. /package/src/route-renderer/orchestration/{layout-shell-props.service.js → document-shell/layout-shell-props.service.js} +0 -0
  101. /package/src/route-renderer/orchestration/{page-browser-graph-contribution.loader.js → page-browser-graph/page-browser-graph-contribution.loader.js} +0 -0
  102. /package/src/route-renderer/orchestration/{processed-asset-dedupe.js → page-browser-graph/processed-asset-dedupe.js} +0 -0
  103. /package/src/route-renderer/orchestration/{route-prepared-options.utils.d.ts → route-pipeline/route-prepared-options.utils.d.ts} +0 -0
@@ -6,25 +6,31 @@ import { invariant } from "../../utils/invariant.js";
6
6
  import { HttpError } from "../../errors/http-error.js";
7
7
  import { DependencyResolverService } from "../page-loading/dependency-resolver.js";
8
8
  import { PageModuleLoaderService } from "../page-loading/page-module-loader.js";
9
- import { OwnershipValidationService } from "./ownership-validation.service.js";
10
- import { hasForeignChildDescendantsInGraph } from "./component-graph-collectors.js";
9
+ import { OwnershipValidationService } from "./ownership-graph/ownership-validation.service.js";
10
+ import { hasForeignChildDescendantsInGraph } from "./ownership-graph/component-graph-collectors.js";
11
11
  import {
12
12
  RouteRenderOrchestrator
13
- } from "./route-render-orchestrator.js";
14
- import { createIntegrationRouteRenderAdapter } from "./integration-route-render-adapter.js";
15
- import { loadPageBrowserGraphContribution } from "./page-browser-graph-contribution.loader.js";
16
- import { normalizeUnresolvedMarkerArtifactHtml, isMarkupNodeLike } from "./render-output.utils.js";
17
- import { ensureIntegrationRuntimeReady } from "../../build/app-build-manifest-runtime.js";
13
+ } from "./route-pipeline/route-render-orchestrator.js";
14
+ import { createIntegrationRouteRenderAdapter } from "./route-pipeline/integration-route-render-adapter.js";
15
+ import { loadPageBrowserGraphContribution } from "./page-browser-graph/page-browser-graph-contribution.loader.js";
16
+ import { normalizeUnresolvedMarkerArtifactHtml } from "./route-pipeline/marker-artifact.utils.js";
17
+ import { isMarkupNodeLike } from "./foreign-child/foreign-child-output.utils.js";
18
18
  import {
19
- ForeignSubtreeExecutionService
20
- } from "./foreign-subtree-execution.service.js";
21
- import { buildProcessedAssetDedupeKey } from "./processed-asset-dedupe.js";
19
+ getForeignSubtreeResolutionContextKey,
20
+ getForeignSubtreeTokenPrefix,
21
+ resolveOwningIntegrationRenderer
22
+ } from "./foreign-child/owning-renderer-resolution.js";
23
+ import { ensureIntegrationRuntimeReady } from "../../build/app-build-manifest-runtime.js";
24
+ import { ForeignSubtreeExecutionService } from "./foreign-child/foreign-subtree-execution.service.js";
22
25
  import {
23
- applyDocumentShellAttributeStamping,
24
26
  composeDocumentShell,
27
+ finalizeDocumentShellHtml,
25
28
  renderPageDocumentShell
26
- } from "./document-shell-render.service.js";
27
- import { resolveInnermostPageLayout, resolvePageLayoutComponents } from "./layout-shell-props.service.js";
29
+ } from "./document-shell/document-shell-render.service.js";
30
+ import {
31
+ resolveInnermostPageLayout,
32
+ resolvePageLayoutComponents
33
+ } from "./document-shell/layout-shell-props.service.js";
28
34
  class IntegrationRenderer {
29
35
  appConfig;
30
36
  assetProcessingService;
@@ -186,36 +192,6 @@ class IntegrationRenderer {
186
192
  )
187
193
  });
188
194
  }
189
- mergePageBrowserGraphIntoPagePackage(pageBrowserGraph) {
190
- if (!pageBrowserGraph) {
191
- return void 0;
192
- }
193
- const currentPagePackage = this.htmlTransformer.getPagePackage();
194
- const mergedPageBrowserGraph = currentPagePackage?.pageBrowserGraph ? {
195
- entryAssets: this.htmlTransformer.dedupeProcessedAssets([
196
- ...currentPagePackage.pageBrowserGraph.entryAssets,
197
- ...pageBrowserGraph.entryAssets
198
- ]),
199
- chunkAssets: this.htmlTransformer.dedupeProcessedAssets([
200
- ...currentPagePackage.pageBrowserGraph.chunkAssets,
201
- ...pageBrowserGraph.chunkAssets
202
- ])
203
- } : pageBrowserGraph;
204
- const pageBrowserGraphAssetKeys = new Set(
205
- [...mergedPageBrowserGraph.entryAssets, ...mergedPageBrowserGraph.chunkAssets].map(
206
- (asset) => buildProcessedAssetDedupeKey(asset)
207
- )
208
- );
209
- const baseAssets = currentPagePackage ? currentPagePackage.assets.filter(
210
- (asset) => !pageBrowserGraphAssetKeys.has(buildProcessedAssetDedupeKey(asset))
211
- ) : this.htmlTransformer.getProcessedDependencies();
212
- this.htmlTransformer.setPagePackage(
213
- createPagePackage(this.htmlTransformer.dedupeProcessedAssets(baseAssets), {
214
- pageBrowserGraph: mergedPageBrowserGraph
215
- })
216
- );
217
- return mergedPageBrowserGraph;
218
- }
219
195
  /**
220
196
  * Merges component-scoped assets into the active HTML transformer state.
221
197
  *
@@ -333,9 +309,11 @@ class IntegrationRenderer {
333
309
  }
334
310
  );
335
311
  const transformedDocumentHtml = input.transformDocumentHtml ? input.transformDocumentHtml(documentHtml) : documentHtml;
336
- const html = await this.finalizeResolvedHtml({
312
+ this.appendProcessedDependencies(this.getRendererBootstrapDependencies(false));
313
+ const html = await finalizeDocumentShellHtml(this.htmlTransformer, {
337
314
  html: `${this.DOC_TYPE}${transformedDocumentHtml}`,
338
- partial: false
315
+ partial: false,
316
+ htmlContributions: this.getHtmlDocumentContributions({ partial: false })
339
317
  });
340
318
  return this.createHtmlResponse(html, input.ctx);
341
319
  }
@@ -389,39 +367,6 @@ class IntegrationRenderer {
389
367
  assets
390
368
  };
391
369
  }
392
- getForeignSubtreeTokenPrefix() {
393
- return `__${this.name}_foreign_subtree__`;
394
- }
395
- getForeignSubtreeResolutionContextKey() {
396
- return `__${this.name}_foreign_subtree_runtime__`;
397
- }
398
- createQueuedForeignSubtreeExecutionRuntime(options) {
399
- return this.foreignSubtreeExecutionService.createQueuedRuntime({
400
- renderInput: options.renderInput,
401
- rendererCache: options.rendererCache,
402
- runtimeContextKey: options.runtimeContextKey ?? this.getForeignSubtreeResolutionContextKey(),
403
- tokenPrefix: options.tokenPrefix ?? this.getForeignSubtreeTokenPrefix(),
404
- createRuntimeContext: options.createRuntimeContext
405
- });
406
- }
407
- getQueuedForeignSubtreeResolutionContext(input) {
408
- return this.foreignSubtreeExecutionService.getQueuedRuntimeContext(
409
- input,
410
- this.getForeignSubtreeResolutionContextKey()
411
- );
412
- }
413
- async resolveQueuedForeignSubtreeHtml(html, runtimeContext, renderQueuedChildren, queueLabel = this.name) {
414
- return this.foreignSubtreeExecutionService.resolveQueuedHtml({
415
- currentIntegrationName: this.name,
416
- html,
417
- runtimeContext,
418
- queueLabel,
419
- getOwningRenderer: (integrationName, rendererCache) => this.getIntegrationRendererForName(integrationName, rendererCache),
420
- applyAttributesToFirstElement: (resolvedHtml, attributes) => this.htmlTransformer.applyAttributesToFirstElement(resolvedHtml, attributes),
421
- dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
422
- renderQueuedChildren
423
- });
424
- }
425
370
  /**
426
371
  * Renders a string-first component, then resolves any queued foreign
427
372
  * boundaries before returning final component HTML.
@@ -432,9 +377,16 @@ class IntegrationRenderer {
432
377
  currentIntegrationName: this.name,
433
378
  renderInput: input,
434
379
  html: componentRender.html,
435
- runtimeContextKey: this.getForeignSubtreeResolutionContextKey(),
380
+ runtimeContextKey: getForeignSubtreeResolutionContextKey(this.name),
436
381
  queueLabel: "String",
437
- getOwningRenderer: (integrationName, rendererCache) => this.getIntegrationRendererForName(integrationName, rendererCache),
382
+ getOwningRenderer: (integrationName, rendererCache) => resolveOwningIntegrationRenderer({
383
+ appConfig: this.appConfig,
384
+ runtimeOrigin: this.runtimeOrigin,
385
+ currentIntegrationName: this.name,
386
+ currentRenderer: this,
387
+ integrationName,
388
+ cache: rendererCache
389
+ }),
438
390
  applyAttributesToFirstElement: (html, attributes) => this.htmlTransformer.applyAttributesToFirstElement(html, attributes),
439
391
  dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets)
440
392
  });
@@ -559,7 +511,15 @@ class IntegrationRenderer {
559
511
  getDocumentAttributes: (renderOptions) => this.getDocumentAttributes(renderOptions),
560
512
  getHtmlDocumentContributions: (options) => this.getHtmlDocumentContributions(options),
561
513
  applyAttributesToHtmlElement: (html, attributes) => this.applyAttributesToHtmlElement(html, attributes),
562
- transformRouteResponse: (response, htmlContributions, pagePackage) => this.transformRouteResponse(response, htmlContributions, pagePackage)
514
+ transformRouteResponse: async (response, htmlContributions, pagePackage) => {
515
+ const resolvedPagePackage = this.htmlTransformer.getPagePackage() ?? pagePackage;
516
+ const transformedResponse = await this.htmlTransformer.transform(
517
+ response,
518
+ htmlContributions,
519
+ resolvedPagePackage
520
+ );
521
+ return transformedResponse.body ?? await transformedResponse.text();
522
+ }
563
523
  });
564
524
  }
565
525
  async resolveRouteRenderInputs(routeOptions) {
@@ -594,15 +554,6 @@ class IntegrationRenderer {
594
554
  async renderRouteBody(renderOptions) {
595
555
  return this.render(renderOptions);
596
556
  }
597
- async transformRouteResponse(response, htmlContributions, pagePackage) {
598
- const resolvedPagePackage = this.htmlTransformer.getPagePackage() ?? pagePackage;
599
- const transformedResponse = await this.htmlTransformer.transform(
600
- response,
601
- htmlContributions,
602
- resolvedPagePackage
603
- );
604
- return transformedResponse.body ?? await transformedResponse.text();
605
- }
606
557
  /**
607
558
  * Prepares the render options for the integration renderer.
608
559
  * It imports the page file, collects dependencies, and prepares the render options.
@@ -641,42 +592,6 @@ class IntegrationRenderer {
641
592
  return this.routeRenderOrchestrator.executePrepared(renderOptions, adapter);
642
593
  });
643
594
  }
644
- /**
645
- * Finalizes already-resolved HTML for explicit renderer-owned paths.
646
- *
647
- * This keeps document and root-attribute stamping plus HTML transformation
648
- * available after a renderer has completed nested foreign-subtree resolution without
649
- * routing back through shared route execution.
650
- */
651
- async finalizeResolvedHtml(options) {
652
- const rendererBootstrapDependencies = this.getRendererBootstrapDependencies(options.partial);
653
- this.appendProcessedDependencies(rendererBootstrapDependencies);
654
- let html = applyDocumentShellAttributeStamping(
655
- options.html,
656
- {
657
- applyAttributesToFirstBodyElement: (nextHtml, attributes) => this.htmlTransformer.applyAttributesToFirstBodyElement(nextHtml, attributes),
658
- applyAttributesToHtmlElement: (nextHtml, attributes) => this.htmlTransformer.applyAttributesToHtmlElement(nextHtml, attributes)
659
- },
660
- {
661
- componentRootAttributes: options.componentRootAttributes,
662
- documentAttributes: options.documentAttributes
663
- }
664
- );
665
- const shouldTransform = options.transformHtml ?? !options.partial;
666
- if (!shouldTransform) {
667
- return html;
668
- }
669
- const htmlContributions = options.htmlContributions ?? this.getHtmlDocumentContributions({
670
- partial: options.partial
671
- });
672
- const transformedResponse = await this.htmlTransformer.transform(
673
- new Response(html, {
674
- headers: { "Content-Type": "text/html" }
675
- }),
676
- htmlContributions
677
- );
678
- return await transformedResponse.text();
679
- }
680
595
  /**
681
596
  * Returns document-level attributes to stamp onto the rendered `<html>` tag.
682
597
  *
@@ -704,34 +619,6 @@ class IntegrationRenderer {
704
619
  getHtmlDocumentContributions(_options) {
705
620
  return void 0;
706
621
  }
707
- /**
708
- * Returns a renderer instance for a given integration name.
709
- *
710
- * Uses a per-execution cache to avoid repeated renderer initialization.
711
- *
712
- * @param integrationName Target integration name.
713
- * @param cache Render-pass renderer cache.
714
- * @returns Renderer for the requested integration.
715
- * @throws Error when no integration plugin matches `integrationName`.
716
- */
717
- getIntegrationRendererForName(integrationName, cache) {
718
- if (cache.has(integrationName)) {
719
- return cache.get(integrationName);
720
- }
721
- if (integrationName === this.name) {
722
- cache.set(integrationName, this);
723
- return this;
724
- }
725
- const integrationPlugin = this.appConfig.integrations.find(
726
- (integration) => integration.name === integrationName
727
- );
728
- invariant(!!integrationPlugin, `[ecopages] Integration not found for foreign owner: ${integrationName}`);
729
- const renderer = integrationPlugin.initializeRenderer({
730
- rendererModules: this.appConfig.runtime?.rendererModuleContext
731
- });
732
- cache.set(integrationName, renderer);
733
- return renderer;
734
- }
735
622
  /**
736
623
  * Renders one component under this integration's foreign-child runtime and resolves
737
624
  * any nested foreign children captured during that render.
@@ -751,7 +638,14 @@ class IntegrationRenderer {
751
638
  renderInput,
752
639
  rendererCache
753
640
  }),
754
- getOwningRenderer: (integrationName, rendererCache) => this.getIntegrationRendererForName(integrationName, rendererCache)
641
+ getOwningRenderer: (integrationName, rendererCache) => resolveOwningIntegrationRenderer({
642
+ appConfig: this.appConfig,
643
+ runtimeOrigin: this.runtimeOrigin,
644
+ currentIntegrationName: this.name,
645
+ currentRenderer: this,
646
+ integrationName,
647
+ cache: rendererCache
648
+ })
755
649
  });
756
650
  }
757
651
  normalizeComponentRenderOutput(result) {
@@ -838,9 +732,11 @@ class IntegrationRenderer {
838
732
  * context or a different foreign-child execution strategy.
839
733
  */
840
734
  createForeignChildRuntime(options) {
841
- return this.createQueuedForeignSubtreeExecutionRuntime({
735
+ return this.foreignSubtreeExecutionService.createQueuedRuntime({
842
736
  renderInput: options.renderInput,
843
- rendererCache: options.rendererCache
737
+ rendererCache: options.rendererCache,
738
+ runtimeContextKey: getForeignSubtreeResolutionContextKey(this.name),
739
+ tokenPrefix: getForeignSubtreeTokenPrefix(this.name)
844
740
  });
845
741
  }
846
742
  /**
@@ -0,0 +1,75 @@
1
+ import { IntegrationRenderer, type HtmlDocumentContribution, type RenderToResponseContext } from './integration-renderer.js';
2
+ import { getComponentRenderContext, type ForeignChildRuntime } from './foreign-child/component-render-context.js';
3
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
4
+ import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
5
+ import type { ComponentRenderInput, ComponentRenderResult, EcoPagesElement, IntegrationRendererRenderOptions, EcoPageFile, RouteRendererBody, RouteRendererOptions, EcoComponent, HtmlTemplateProps } from '../../types/public-types.js';
6
+ import type { PagePackageResult } from '../../types/public-types.js';
7
+ export declare function createUnresolvedMarkerArtifact(nodeId: string, componentRef: string, propsRef: string): string;
8
+ export declare function createMockIntegrationPlugin(plugin: {
9
+ name: string;
10
+ } & Record<string, unknown>): EcoPagesAppConfig['integrations'][number];
11
+ export declare const testAppConfig: EcoPagesAppConfig;
12
+ export declare const testAssetService: AssetProcessingService;
13
+ /**
14
+ * Concrete implementation with ed file loading for testing purposes.
15
+ */
16
+ export declare class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
17
+ name: string;
18
+ ForeignChildRuntimeCreationCount: number;
19
+ ForeignChildRenderCount: number;
20
+ UseFailFastForeignChildRuntime: boolean;
21
+ PageModule: EcoPageFile | null;
22
+ HtmlTemplate: EcoComponent<HtmlTemplateProps> | null;
23
+ RenderedBody: RouteRendererBody;
24
+ RenderBodyFactory: ((context: ReturnType<typeof getComponentRenderContext>) => RouteRendererBody | Promise<RouteRendererBody>) | null;
25
+ MockComponentRenderResult: ComponentRenderResult | null;
26
+ ImportedFiles: string[];
27
+ HtmlContributions: HtmlDocumentContribution[];
28
+ render(_options: IntegrationRendererRenderOptions<EcoPagesElement>): Promise<RouteRendererBody>;
29
+ renderComponent(_input: ComponentRenderInput): Promise<ComponentRenderResult>;
30
+ renderComponentWithForeignChildren(input: ComponentRenderInput): Promise<ComponentRenderResult>;
31
+ renderToResponse<P>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
32
+ protected importPageFile(_file: string): Promise<EcoPageFile>;
33
+ protected getHtmlTemplate(): Promise<EcoComponent<HtmlTemplateProps>>;
34
+ protected resolveDependencies(_components: (EcoComponent | Partial<EcoComponent>)[]): Promise<ProcessedAsset[]>;
35
+ testPrepareRenderOptions(options: RouteRendererOptions): Promise<IntegrationRendererRenderOptions<EcoPagesElement>>;
36
+ testShouldDelegateForeignChild(input: {
37
+ currentIntegration: string;
38
+ targetIntegration?: string;
39
+ }): boolean;
40
+ testHasForeignChildDescendants(component: EcoComponent): boolean;
41
+ testGetHtmlTemplate(): Promise<import("../../index.browser.js").EcoFunctionComponent<HtmlTemplateProps<EcoPagesElement>, any>>;
42
+ testBaseGetHtmlTemplate(): Promise<import("../../index.browser.js").EcoFunctionComponent<HtmlTemplateProps<EcoPagesElement>, any>>;
43
+ testGetRendererBootstrapDependencies(partial?: boolean): ProcessedAsset[];
44
+ testFinalizeDocumentShellHtml(options: {
45
+ html: string;
46
+ partial?: boolean;
47
+ htmlContributions?: HtmlDocumentContribution[];
48
+ }): Promise<string>;
49
+ testTransformRouteResponse(response: Response, htmlContributions?: HtmlDocumentContribution[], pagePackage?: PagePackageResult): Promise<RouteRendererBody>;
50
+ appendTestProcessedDependencies(assets: ProcessedAsset[]): ProcessedAsset[];
51
+ setTestPagePackage(pagePackage: PagePackageResult): void;
52
+ getTestPagePackage(): PagePackageResult | undefined;
53
+ getTestProcessedDependencies(): ProcessedAsset[];
54
+ testRenderPartialViewResponse<P>(input: {
55
+ view: EcoComponent<P>;
56
+ props: P;
57
+ ctx?: RenderToResponseContext;
58
+ renderInline?: () => Promise<BodyInit>;
59
+ transformHtml?: (html: string) => string;
60
+ }): Promise<Response>;
61
+ testRenderViewWithDocumentShell<P>(input: {
62
+ view: EcoComponent<P>;
63
+ props: P;
64
+ ctx?: RenderToResponseContext;
65
+ layout?: EcoComponent;
66
+ }): Promise<Response>;
67
+ testRenderForeignSubtree(input: ComponentRenderInput): Promise<import("../../index.browser.js").ForeignSubtreeRenderPayload>;
68
+ protected createForeignChildRuntime(options: {
69
+ renderInput: ComponentRenderInput;
70
+ rendererCache: Map<string, IntegrationRenderer<any>>;
71
+ }): ForeignChildRuntime;
72
+ protected getHtmlDocumentContributions(_options?: {
73
+ partial?: boolean;
74
+ }): HtmlDocumentContribution[] | undefined;
75
+ }
@@ -0,0 +1,177 @@
1
+ import { vi } from "vitest";
2
+ import { resolveInnermostPageLayout } from "./document-shell/layout-shell-props.service.js";
3
+ import { finalizeDocumentShellHtml } from "./document-shell/document-shell-render.service.js";
4
+ import {
5
+ IntegrationRenderer
6
+ } from "./integration-renderer.js";
7
+ import { getComponentRenderContext } from "./foreign-child/component-render-context.js";
8
+ import { toForeignSubtreeRenderPayload } from "./foreign-child/foreign-subtree-execution.service.js";
9
+ function createUnresolvedMarkerArtifact(nodeId, componentRef, propsRef) {
10
+ return `<eco-marker data-eco-node-id="${nodeId}" data-eco-component-ref="${componentRef}" data-eco-props-ref="${propsRef}"></eco-marker>`;
11
+ }
12
+ function createMockIntegrationPlugin(plugin) {
13
+ return {
14
+ setConfig: vi.fn(),
15
+ setRuntimeOrigin: vi.fn(),
16
+ setup: vi.fn(async () => {
17
+ }),
18
+ ...plugin
19
+ };
20
+ }
21
+ const testAppConfig = {
22
+ absolutePaths: {
23
+ pagesDir: "/app/pages",
24
+ htmlTemplatePath: "/app/index.ghtml.ts"
25
+ },
26
+ integrations: [],
27
+ defaultMetadata: {
28
+ title: "Default Title",
29
+ description: "Default Description"
30
+ },
31
+ srcDir: "/app"
32
+ };
33
+ const testAssetService = {
34
+ processDependencies: vi.fn(() => Promise.resolve([]))
35
+ };
36
+ class TestIntegrationRenderer extends IntegrationRenderer {
37
+ name = "test-renderer";
38
+ ForeignChildRuntimeCreationCount = 0;
39
+ ForeignChildRenderCount = 0;
40
+ UseFailFastForeignChildRuntime = false;
41
+ PageModule = null;
42
+ HtmlTemplate = null;
43
+ RenderedBody = "<html><body>Test Page</body></html>";
44
+ RenderBodyFactory = null;
45
+ MockComponentRenderResult = null;
46
+ ImportedFiles = [];
47
+ HtmlContributions = [];
48
+ async render(_options) {
49
+ if (this.RenderBodyFactory) {
50
+ return await this.RenderBodyFactory(getComponentRenderContext());
51
+ }
52
+ return this.RenderedBody;
53
+ }
54
+ async renderComponent(_input) {
55
+ if (this.MockComponentRenderResult) {
56
+ return this.MockComponentRenderResult;
57
+ }
58
+ return super.renderComponent(_input);
59
+ }
60
+ async renderComponentWithForeignChildren(input) {
61
+ this.ForeignChildRenderCount += 1;
62
+ return await super.renderComponentWithForeignChildren(input);
63
+ }
64
+ async renderToResponse(view, props, ctx) {
65
+ const viewFn = view;
66
+ const content = viewFn(props);
67
+ let body;
68
+ if (ctx.partial) {
69
+ body = content;
70
+ } else {
71
+ const Layout = resolveInnermostPageLayout(view.config?.layouts);
72
+ const children = Layout ? Layout({ children: content }) : content;
73
+ body = `<!DOCTYPE html><html><body>${children}</body></html>`;
74
+ }
75
+ const headers = new Headers({ "Content-Type": "text/html; charset=utf-8" });
76
+ if (ctx.headers) {
77
+ for (const [key, value] of Object.entries(ctx.headers)) {
78
+ headers.set(key, value);
79
+ }
80
+ }
81
+ return new Response(body, {
82
+ status: ctx.status ?? 200,
83
+ headers
84
+ });
85
+ }
86
+ async importPageFile(_file) {
87
+ this.ImportedFiles.push(_file);
88
+ if (!this.PageModule) throw new Error("Mock page module not set");
89
+ return this.PageModule;
90
+ }
91
+ async getHtmlTemplate() {
92
+ if (!this.HtmlTemplate) throw new Error("Mock HTML template not set");
93
+ return this.HtmlTemplate;
94
+ }
95
+ async resolveDependencies(_components) {
96
+ return [];
97
+ }
98
+ async testPrepareRenderOptions(options) {
99
+ return this.prepareRenderOptions(options);
100
+ }
101
+ testShouldDelegateForeignChild(input) {
102
+ return this.foreignSubtreeExecutionService.shouldDelegateForeignChild(input);
103
+ }
104
+ testHasForeignChildDescendants(component) {
105
+ return this.hasForeignChildDescendants(component);
106
+ }
107
+ async testGetHtmlTemplate() {
108
+ return this.getHtmlTemplate();
109
+ }
110
+ async testBaseGetHtmlTemplate() {
111
+ return super.getHtmlTemplate();
112
+ }
113
+ testGetRendererBootstrapDependencies(partial = false) {
114
+ return this.getRendererBootstrapDependencies(partial);
115
+ }
116
+ async testFinalizeDocumentShellHtml(options) {
117
+ this.appendProcessedDependencies(this.getRendererBootstrapDependencies(options.partial));
118
+ return finalizeDocumentShellHtml(this.htmlTransformer, {
119
+ html: options.html,
120
+ partial: options.partial,
121
+ htmlContributions: options.htmlContributions ?? this.getHtmlDocumentContributions({ partial: options.partial ?? false })
122
+ });
123
+ }
124
+ async testTransformRouteResponse(response, htmlContributions, pagePackage) {
125
+ const adapter = this.createRouteRenderOrchestratorAdapter();
126
+ return adapter.transformRouteResponse(response, htmlContributions, pagePackage);
127
+ }
128
+ appendTestProcessedDependencies(assets) {
129
+ return this.appendProcessedDependencies(assets);
130
+ }
131
+ setTestPagePackage(pagePackage) {
132
+ this.htmlTransformer.setPagePackage(pagePackage);
133
+ }
134
+ getTestPagePackage() {
135
+ return this.htmlTransformer.getPagePackage();
136
+ }
137
+ getTestProcessedDependencies() {
138
+ return this.htmlTransformer.getProcessedDependencies();
139
+ }
140
+ async testRenderPartialViewResponse(input) {
141
+ return this.renderPartialViewResponse({
142
+ view: input.view,
143
+ props: input.props,
144
+ ctx: input.ctx ?? { partial: true },
145
+ renderInline: input.renderInline,
146
+ transformHtml: input.transformHtml
147
+ });
148
+ }
149
+ async testRenderViewWithDocumentShell(input) {
150
+ return this.renderViewWithDocumentShell({
151
+ view: input.view,
152
+ props: input.props,
153
+ ctx: input.ctx ?? {},
154
+ layout: input.layout
155
+ });
156
+ }
157
+ async testRenderForeignSubtree(input) {
158
+ return toForeignSubtreeRenderPayload(await this.renderComponentWithForeignChildren(input));
159
+ }
160
+ createForeignChildRuntime(options) {
161
+ this.ForeignChildRuntimeCreationCount += 1;
162
+ if (this.UseFailFastForeignChildRuntime) {
163
+ return this.createFailFastForeignChildRuntime();
164
+ }
165
+ return super.createForeignChildRuntime(options);
166
+ }
167
+ getHtmlDocumentContributions(_options) {
168
+ return this.HtmlContributions;
169
+ }
170
+ }
171
+ export {
172
+ TestIntegrationRenderer,
173
+ createMockIntegrationPlugin,
174
+ createUnresolvedMarkerArtifact,
175
+ testAppConfig,
176
+ testAssetService
177
+ };
@@ -1,7 +1,7 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { EcoComponent, ResolvedLazyTrigger } from '../../types/public-types.js';
3
- import type { AssetDefinition } from '../../services/assets/asset-processing-service/index.js';
4
- import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { EcoComponent, ResolvedLazyTrigger } from '../../../types/public-types.js';
3
+ import type { AssetDefinition } from '../../../services/assets/asset-processing-service/index.js';
4
+ import type { AssetProcessingService, ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
5
5
  export declare function collectIntegrationNamesFromGraph(components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): Set<string>;
6
6
  export declare function collectResolvedLazyTriggersFromGraph(components: (EcoComponent | Partial<EcoComponent>)[], currentIntegrationName: string): ResolvedLazyTrigger[];
7
7
  export declare function hasForeignChildDescendantsInGraph(component: EcoComponent, currentIntegrationName: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { AssetFactory } from "../../services/assets/asset-processing-service/index.js";
2
+ import { AssetFactory } from "../../../services/assets/asset-processing-service/index.js";
3
3
  import { walkComponentGraph } from "./component-graph.js";
4
4
  function toGraphRoots(components) {
5
5
  return components.filter(Boolean).map((component) => ({ component }));
@@ -1,4 +1,4 @@
1
- import type { EcoComponent, OwnershipPlanNodeSource } from '../../types/public-types.js';
1
+ import type { EcoComponent, OwnershipPlanNodeSource } from '../../../types/public-types.js';
2
2
  export type DeclaredOwnershipRoot = {
3
3
  component: EcoComponent;
4
4
  source: Extract<OwnershipPlanNodeSource, 'page' | 'layout' | 'html-template'>;
@@ -32,5 +32,3 @@ export declare function mapComponentGraph<T>(input: {
32
32
  currentIntegrationName: string;
33
33
  mapNode: (node: DeclaredOwnershipNodeInput, children: T[]) => T;
34
34
  }): T[];
35
- /** @deprecated Use `mapComponentGraph` from `component-graph.ts`. */
36
- export declare const mapDeclaredOwnershipGraph: typeof mapComponentGraph;
@@ -90,9 +90,7 @@ function mapComponentGraph(input) {
90
90
  ({ component, source }) => mapComponent(component, source, input.currentIntegrationName, /* @__PURE__ */ new Set())
91
91
  );
92
92
  }
93
- const mapDeclaredOwnershipGraph = mapComponentGraph;
94
93
  export {
95
94
  mapComponentGraph,
96
- mapDeclaredOwnershipGraph,
97
95
  walkComponentGraph
98
96
  };
@@ -1,5 +1,5 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { OwnershipPlanNodeSource, OwnershipValidationError, EcoComponent } from '../../types/public-types.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { OwnershipPlanNodeSource, OwnershipValidationError, EcoComponent } from '../../../types/public-types.js';
3
3
  type OwnershipValidationInput = {
4
4
  currentIntegrationName: string;
5
5
  roots: Array<{
@@ -1,6 +1,5 @@
1
- import { assertEcoDeclaredComponent } from "../../eco/eco-declared-component.js";
2
- import { mapDeclaredOwnershipGraph } from "./declared-ownership-graph.js";
3
- import { walkComponentGraph } from "./component-graph.js";
1
+ import { assertEcoDeclaredComponent } from "../../../eco/eco-declared-component.js";
2
+ import { mapComponentGraph, walkComponentGraph } from "./component-graph.js";
4
3
  class OwnershipValidationService {
5
4
  appConfig;
6
5
  /**
@@ -14,7 +13,7 @@ class OwnershipValidationService {
14
13
  */
15
14
  validate(input) {
16
15
  this.assertDeclaredComponentDependencies(input);
17
- return mapDeclaredOwnershipGraph({
16
+ return mapComponentGraph({
18
17
  roots: input.roots,
19
18
  currentIntegrationName: input.currentIntegrationName,
20
19
  mapNode: ({ component, integrationName, componentId, isForeignToParent }, children) => {
@@ -1,6 +1,6 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { ResolvedLazyTrigger } from '../../types/public-types.js';
3
- import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
1
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
2
+ import type { ResolvedLazyTrigger } from '../../../types/public-types.js';
3
+ import { type AssetProcessingService, type ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
4
4
  /**
5
5
  * Builds processed global-injector map and bootstrap assets for resolved lazy triggers.
6
6
  */
@@ -2,8 +2,8 @@ import { createRequire } from "node:module";
2
2
  import path from "node:path";
3
3
  import {
4
4
  AssetFactory
5
- } from "../../services/assets/asset-processing-service/index.js";
6
- import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from "../../eco/global-injector-map.js";
5
+ } from "../../../services/assets/asset-processing-service/index.js";
6
+ import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from "../../../eco/global-injector-map.js";
7
7
  async function buildGlobalInjectorAssets(appConfig, assetProcessingService, triggers, currentIntegrationName) {
8
8
  const appProjectDir = appConfig.rootDir ?? appConfig.absolutePaths?.projectDir ?? process.cwd();
9
9
  const appPackageRequire = createRequire(path.join(appProjectDir, "package.json"));
@@ -1,4 +1,4 @@
1
- import type { EcoPageFile, PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../types/public-types.js';
1
+ import type { EcoPageFile, PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../../types/public-types.js';
2
2
  /**
3
3
  * Loads a page module once and collects declarative browser-graph requirements.
4
4
  *
@@ -0,0 +1,12 @@
1
+ import { type ProcessedAsset } from '../../../services/assets/asset-processing-service/index.js';
2
+ import type { PageBrowserGraphResult, PagePackageResult } from '../../../types/public-types.js';
3
+ /**
4
+ * Merges a page browser graph into an existing page package (or processed dependencies).
5
+ *
6
+ * Deduplicates entry/chunk assets against the existing package and rebuilds the
7
+ * package so downstream HTML finalization sees one canonical graph.
8
+ */
9
+ export declare function mergePageBrowserGraph(currentPagePackage: PagePackageResult | undefined, processedDependencies: ProcessedAsset[], pageBrowserGraph: PageBrowserGraphResult): {
10
+ pagePackage: PagePackageResult;
11
+ mergedGraph: PageBrowserGraphResult;
12
+ };