@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27

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 (111) hide show
  1. package/README.md +63 -7
  2. package/package.json +4 -47
  3. package/src/adapters/bun/create-app.ts +54 -2
  4. package/src/adapters/bun/hmr-manager.test.ts +0 -2
  5. package/src/adapters/bun/hmr-manager.ts +1 -24
  6. package/src/adapters/bun/server-adapter.ts +30 -4
  7. package/src/adapters/node/node-hmr-manager.test.ts +0 -2
  8. package/src/adapters/node/node-hmr-manager.ts +2 -25
  9. package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
  10. package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
  11. package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
  12. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
  13. package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
  14. package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
  15. package/src/adapters/shared/fs-server-response-factory.ts +15 -37
  16. package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
  17. package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
  18. package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
  19. package/src/adapters/shared/render-context.ts +3 -3
  20. package/src/adapters/shared/server-adapter.test.ts +53 -0
  21. package/src/adapters/shared/server-adapter.ts +228 -159
  22. package/src/adapters/shared/server-route-handler.test.ts +6 -5
  23. package/src/adapters/shared/server-route-handler.ts +4 -4
  24. package/src/adapters/shared/server-static-builder.test.ts +4 -4
  25. package/src/adapters/shared/server-static-builder.ts +4 -4
  26. package/src/config/README.md +1 -1
  27. package/src/config/config-builder.test.ts +0 -1
  28. package/src/config/config-builder.ts +2 -7
  29. package/src/dev/host-runtime.ts +34 -0
  30. package/src/eco/eco.browser.test.ts +2 -2
  31. package/src/eco/eco.browser.ts +2 -2
  32. package/src/eco/eco.test.ts +6 -6
  33. package/src/eco/eco.ts +12 -12
  34. package/src/eco/eco.types.ts +3 -3
  35. package/src/errors/index.ts +1 -0
  36. package/src/hmr/client/hmr-runtime.ts +4 -2
  37. package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
  38. package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
  39. package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
  40. package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
  41. package/src/plugins/eco-component-meta-plugin.ts +0 -1
  42. package/src/plugins/integration-plugin.test.ts +9 -14
  43. package/src/plugins/integration-plugin.ts +34 -22
  44. package/src/plugins/processor.ts +17 -0
  45. package/src/route-renderer/GRAPH.md +81 -289
  46. package/src/route-renderer/README.md +67 -105
  47. package/src/route-renderer/orchestration/component-render-context.ts +45 -38
  48. package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
  49. package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
  50. package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
  51. package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
  52. package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
  53. package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
  54. package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
  55. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
  56. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
  57. package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
  58. package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
  59. package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
  60. package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
  61. package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
  62. package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
  63. package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
  64. package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
  65. package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
  66. package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
  67. package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
  68. package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
  69. package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
  70. package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
  71. package/src/route-renderer/route-renderer.ts +28 -31
  72. package/src/router/README.md +16 -19
  73. package/src/router/server/route-registry.test.ts +176 -0
  74. package/src/router/server/route-registry.ts +382 -0
  75. package/src/services/README.md +1 -2
  76. package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
  77. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
  78. package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
  79. package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
  80. package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
  81. package/src/services/assets/asset-processing-service/index.ts +1 -0
  82. package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
  83. package/src/services/assets/asset-processing-service/page-package.ts +93 -0
  84. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
  85. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
  86. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
  87. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
  88. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
  89. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
  90. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
  91. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
  92. package/src/services/html/html-transformer.service.test.ts +1 -4
  93. package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
  94. package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
  95. package/src/services/module-loading/page-module-import.service.ts +0 -1
  96. package/src/services/module-loading/source-module-support.ts +1 -1
  97. package/src/static-site-generator/static-site-generator.test.ts +124 -32
  98. package/src/static-site-generator/static-site-generator.ts +168 -185
  99. package/src/types/internal-types.ts +13 -12
  100. package/src/types/public-types.ts +55 -39
  101. package/src/watchers/project-watcher.test-helpers.ts +4 -3
  102. package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
  103. package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
  104. package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
  105. package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
  106. package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
  107. package/src/router/server/fs-router-scanner.test.ts +0 -83
  108. package/src/router/server/fs-router-scanner.ts +0 -224
  109. package/src/router/server/fs-router.test.ts +0 -214
  110. package/src/router/server/fs-router.ts +0 -122
  111. package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
@@ -3,12 +3,12 @@ import { eco } from '../../eco/eco.ts';
3
3
  import { IntegrationRenderer, type RenderToResponseContext } from './integration-renderer.ts';
4
4
  import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
5
5
  import type { AssetProcessingService, ProcessedAsset } from '../../services/assets/asset-processing-service/index.ts';
6
- import { getComponentRenderContext, type ComponentBoundaryRuntime } from './component-render-context.ts';
6
+ import { getComponentRenderContext, type ForeignChildRuntime } from './component-render-context.ts';
7
7
  import type {
8
8
  BaseIntegrationContext,
9
9
  ComponentRenderInput,
10
10
  ComponentRenderResult,
11
- BoundaryRenderPayload,
11
+ ForeignSubtreeRenderPayload,
12
12
  RouteRendererBody,
13
13
  EcoPagesElement,
14
14
  IntegrationRendererRenderOptions,
@@ -20,7 +20,7 @@ import type {
20
20
  import type { EcoPageComponent } from '../../eco/eco.types.ts';
21
21
  import { runWithComponentRenderContext } from './component-render-context.ts';
22
22
 
23
- function createBoundaryMarker(nodeId: string, componentRef: string, propsRef: string): string {
23
+ function createUnresolvedMarkerArtifact(nodeId: string, componentRef: string, propsRef: string): string {
24
24
  return `<eco-marker data-eco-node-id="${nodeId}" data-eco-component-ref="${componentRef}" data-eco-props-ref="${propsRef}"></eco-marker>`;
25
25
  }
26
26
 
@@ -29,8 +29,9 @@ function createBoundaryMarker(nodeId: string, componentRef: string, propsRef: st
29
29
  */
30
30
  class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
31
31
  name = 'test-renderer';
32
- BoundaryRuntimeCreationCount = 0;
33
- BoundaryRenderCount = 0;
32
+ ForeignChildRuntimeCreationCount = 0;
33
+ ForeignChildRenderCount = 0;
34
+ UseFailFastForeignChildRuntime = false;
34
35
 
35
36
  /** Mock data container for page module */
36
37
  PageModule: EcoPageFile | null = null;
@@ -59,9 +60,9 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
59
60
  return super.renderComponent(_input);
60
61
  }
61
62
 
62
- override async renderComponentBoundary(input: ComponentRenderInput): Promise<ComponentRenderResult> {
63
- this.BoundaryRenderCount += 1;
64
- return await super.renderComponentBoundary(input);
63
+ override async renderComponentWithForeignChildren(input: ComponentRenderInput): Promise<ComponentRenderResult> {
64
+ this.ForeignChildRenderCount += 1;
65
+ return await super.renderComponentWithForeignChildren(input);
65
66
  }
66
67
 
67
68
  async renderToResponse<P>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response> {
@@ -113,8 +114,8 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
113
114
  return [];
114
115
  }
115
116
 
116
- protected override async buildRouteRenderAssets(_file: string): Promise<ProcessedAsset[]> {
117
- return [];
117
+ protected override async buildPageBrowserGraph(_file: string) {
118
+ return { assets: [] as ProcessedAsset[] };
118
119
  }
119
120
 
120
121
  /**
@@ -128,22 +129,12 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
128
129
  return this.processComponentDependencies(components);
129
130
  }
130
131
 
131
- public testShouldResolveBoundaryInOwningRenderer(input: {
132
- currentIntegration: string;
133
- targetIntegration?: string;
134
- }) {
135
- return this.shouldResolveBoundaryInOwningRenderer(input);
136
- }
137
-
138
- public testHasForeignBoundaryDescendants(component: EcoComponent) {
139
- return this.hasForeignBoundaryDescendants(component);
132
+ public testShouldDelegateForeignChild(input: { currentIntegration: string; targetIntegration?: string }) {
133
+ return this.foreignSubtreeExecutionService.shouldDelegateForeignChild(input);
140
134
  }
141
135
 
142
- public async testResolveBoundaryInOwningRenderer(
143
- input: ComponentRenderInput,
144
- rendererCache = new Map<string, IntegrationRenderer<any>>(),
145
- ) {
146
- return this.resolveBoundaryInOwningRenderer(input, rendererCache);
136
+ public testHasForeignChildDescendants(component: EcoComponent) {
137
+ return this.hasForeignChildDescendants(component);
147
138
  }
148
139
 
149
140
  public async testGetHtmlTemplate() {
@@ -195,16 +186,21 @@ class TestIntegrationRenderer extends IntegrationRenderer<EcoPagesElement> {
195
186
  });
196
187
  }
197
188
 
198
- public async testRenderBoundary(input: ComponentRenderInput) {
199
- return this.renderBoundary(input);
189
+ public async testRenderForeignSubtree(input: ComponentRenderInput) {
190
+ return this.renderForeignSubtree(input);
200
191
  }
201
192
 
202
- protected override createComponentBoundaryRuntime(options: {
203
- boundaryInput: ComponentRenderInput;
193
+ protected override createForeignChildRuntime(options: {
194
+ renderInput: ComponentRenderInput;
204
195
  rendererCache: Map<string, IntegrationRenderer<any>>;
205
- }): ComponentBoundaryRuntime {
206
- this.BoundaryRuntimeCreationCount += 1;
207
- return super.createComponentBoundaryRuntime(options);
196
+ }): ForeignChildRuntime {
197
+ this.ForeignChildRuntimeCreationCount += 1;
198
+
199
+ if (this.UseFailFastForeignChildRuntime) {
200
+ return this.createFailFastForeignChildRuntime();
201
+ }
202
+
203
+ return super.createForeignChildRuntime(options);
208
204
  }
209
205
  }
210
206
 
@@ -439,7 +435,7 @@ describe('IntegrationRenderer', () => {
439
435
  expect(result.pageLocals).toBe(incomingLocals);
440
436
  });
441
437
 
442
- it('should include a boundary plan for page, layout, and html template roots', async () => {
438
+ it('should include an ownership plan for page, layout, and html template roots', async () => {
443
439
  const renderer = new TestIntegrationRenderer({
444
440
  appConfig: {
445
441
  ...AppConfig,
@@ -505,7 +501,7 @@ describe('IntegrationRenderer', () => {
505
501
  query: {},
506
502
  });
507
503
 
508
- expect(result.boundaryPlan).toEqual(
504
+ expect(result.ownershipPlan).toEqual(
509
505
  expect.objectContaining({
510
506
  foreignEdgeCount: 1,
511
507
  hasValidationErrors: false,
@@ -571,8 +567,8 @@ describe('IntegrationRenderer', () => {
571
567
  query: {},
572
568
  });
573
569
 
574
- expect(result.boundaryPlan?.hasValidationErrors).toBe(true);
575
- expect(result.boundaryPlan?.validationErrors).toEqual(
570
+ expect(result.ownershipPlan?.hasValidationErrors).toBe(true);
571
+ expect(result.ownershipPlan?.validationErrors).toEqual(
576
572
  expect.arrayContaining([
577
573
  expect.objectContaining({
578
574
  code: 'UNKNOWN_INTEGRATION_OWNER',
@@ -583,7 +579,7 @@ describe('IntegrationRenderer', () => {
583
579
  );
584
580
  });
585
581
 
586
- it('should expose a compatibility boundary payload contract', async () => {
582
+ it('should expose a compatibility foreign-subtree payload contract', async () => {
587
583
  const renderer = new TestIntegrationRenderer({
588
584
  appConfig: AppConfig,
589
585
  assetProcessingService: AssetService,
@@ -600,24 +596,24 @@ describe('IntegrationRenderer', () => {
600
596
  {
601
597
  kind: 'script',
602
598
  inline: true,
603
- content: 'console.log("boundary")',
599
+ content: 'console.log("foreign-subtree")',
604
600
  position: 'body',
605
601
  },
606
602
  ],
607
603
  };
608
604
 
609
- const payload = await renderer.testRenderBoundary({
605
+ const payload = await renderer.testRenderForeignSubtree({
610
606
  component: (() => '<main>Hello</main>') as EcoComponent<Record<string, unknown>>,
611
607
  props: {},
612
608
  });
613
609
 
614
- expect(payload).toEqual<BoundaryRenderPayload>({
610
+ expect(payload).toEqual<ForeignSubtreeRenderPayload>({
615
611
  html: '<main data-root="true">Hello</main>',
616
612
  assets: [
617
613
  {
618
614
  kind: 'script',
619
615
  inline: true,
620
- content: 'console.log("boundary")',
616
+ content: 'console.log("foreign-subtree")',
621
617
  position: 'body',
622
618
  },
623
619
  ],
@@ -635,7 +631,7 @@ describe('IntegrationRenderer', () => {
635
631
  runtimeOrigin: 'http://localhost:3000',
636
632
  });
637
633
 
638
- const result = renderer.testShouldResolveBoundaryInOwningRenderer({
634
+ const result = renderer.testShouldDelegateForeignChild({
639
635
  currentIntegration: 'ghtml',
640
636
  targetIntegration: 'react',
641
637
  });
@@ -650,7 +646,7 @@ describe('IntegrationRenderer', () => {
650
646
  runtimeOrigin: 'http://localhost:3000',
651
647
  });
652
648
 
653
- const result = renderer.testShouldResolveBoundaryInOwningRenderer({
649
+ const result = renderer.testShouldDelegateForeignChild({
654
650
  currentIntegration: 'react',
655
651
  targetIntegration: 'react',
656
652
  });
@@ -658,9 +654,9 @@ describe('IntegrationRenderer', () => {
658
654
  expect(result).toBe(false);
659
655
  });
660
656
 
661
- it('should delegate foreign component boundaries through the shared ownership helper', async () => {
657
+ it('should delegate foreign component boundaries through the shared execution seam', async () => {
662
658
  const foreignRenderer = {
663
- renderComponentBoundary: vi.fn(async () => ({
659
+ renderComponentWithForeignChildren: vi.fn(async () => ({
664
660
  html: '<aside>Owned by foreign renderer</aside>',
665
661
  canAttachAttributes: true,
666
662
  rootTag: 'aside',
@@ -694,14 +690,11 @@ describe('IntegrationRenderer', () => {
694
690
  };
695
691
 
696
692
  const rendererCache = new Map<string, IntegrationRenderer<any>>();
697
- const result = await renderer.testResolveBoundaryInOwningRenderer(
698
- {
699
- component: ForeignComponent,
700
- props: { label: 'foreign' },
701
- integrationContext: { rendererCache },
702
- },
703
- rendererCache,
704
- );
693
+ const result = await renderer.renderComponentWithForeignChildren({
694
+ component: ForeignComponent,
695
+ props: { label: 'foreign' },
696
+ integrationContext: { rendererCache },
697
+ });
705
698
 
706
699
  expect(result).toEqual(
707
700
  expect.objectContaining({
@@ -710,12 +703,12 @@ describe('IntegrationRenderer', () => {
710
703
  }),
711
704
  );
712
705
  expect(initializeRenderer).toHaveBeenCalledTimes(1);
713
- expect(foreignRenderer.renderComponentBoundary).toHaveBeenCalledTimes(1);
706
+ expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledTimes(1);
714
707
  });
715
708
 
716
709
  it('should preserve shared integration context fields when delegating to the owning renderer', async () => {
717
710
  const foreignRenderer = {
718
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) => ({
711
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) => ({
719
712
  html: `<aside>${String(
720
713
  (input.integrationContext as BaseIntegrationContext | undefined)?.componentInstanceId ?? 'missing',
721
714
  )}</aside>`,
@@ -750,29 +743,25 @@ describe('IntegrationRenderer', () => {
750
743
  },
751
744
  };
752
745
 
753
- const rendererCache = new Map<string, IntegrationRenderer<any>>();
754
- await renderer.testResolveBoundaryInOwningRenderer(
755
- {
756
- component: ForeignComponent,
757
- props: { label: 'foreign' },
758
- integrationContext: {
759
- componentInstanceId: 'host-1',
760
- } satisfies BaseIntegrationContext,
761
- },
762
- rendererCache,
763
- );
746
+ await renderer.renderComponentWithForeignChildren({
747
+ component: ForeignComponent,
748
+ props: { label: 'foreign' },
749
+ integrationContext: {
750
+ componentInstanceId: 'host-1',
751
+ } satisfies BaseIntegrationContext,
752
+ });
764
753
 
765
- expect(foreignRenderer.renderComponentBoundary).toHaveBeenCalledWith(
754
+ expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledWith(
766
755
  expect.objectContaining({
767
756
  integrationContext: expect.objectContaining({
768
757
  componentInstanceId: 'host-1',
769
- rendererCache,
758
+ rendererCache: expect.any(Map),
770
759
  }),
771
760
  }),
772
761
  );
773
762
  });
774
763
 
775
- it('should stop boundary delegation when the resolved owner renderer is the current renderer', async () => {
764
+ it('should fall back to local rendering when the resolved owner renderer is the current renderer', async () => {
776
765
  const renderer = new TestIntegrationRenderer({
777
766
  appConfig: {
778
767
  ...AppConfig,
@@ -786,6 +775,12 @@ describe('IntegrationRenderer', () => {
786
775
  assetProcessingService: AssetService,
787
776
  runtimeOrigin: 'http://localhost:3000',
788
777
  });
778
+ renderer.MockComponentRenderResult = {
779
+ html: '<aside>Local renderer</aside>',
780
+ canAttachAttributes: true,
781
+ rootTag: 'aside',
782
+ integrationName: 'test-renderer',
783
+ };
789
784
 
790
785
  const ForeignComponent = (() => '<aside>Foreign</aside>') as EcoComponent<Record<string, unknown>>;
791
786
  ForeignComponent.config = {
@@ -798,16 +793,13 @@ describe('IntegrationRenderer', () => {
798
793
  };
799
794
 
800
795
  const rendererCache = new Map<string, IntegrationRenderer<any>>();
801
- const result = await renderer.testResolveBoundaryInOwningRenderer(
802
- {
803
- component: ForeignComponent,
804
- props: { label: 'foreign' },
805
- integrationContext: { rendererCache },
806
- },
807
- rendererCache,
808
- );
796
+ const result = await renderer.renderComponentWithForeignChildren({
797
+ component: ForeignComponent,
798
+ props: { label: 'foreign' },
799
+ integrationContext: { rendererCache },
800
+ });
809
801
 
810
- expect(result).toBeUndefined();
802
+ expect(result.html).toBe('<aside>Local renderer</aside>');
811
803
  });
812
804
 
813
805
  it('should prefer processed lazy script srcUrl for _resolvedLazyTriggers', async () => {
@@ -1129,7 +1121,7 @@ describe('IntegrationRenderer', () => {
1129
1121
  rootTag: 'aside',
1130
1122
  integrationName: 'explicit-renderer',
1131
1123
  })),
1132
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1124
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1133
1125
  explicitRenderer.renderComponent(input),
1134
1126
  ),
1135
1127
  } as unknown as IntegrationRenderer;
@@ -1266,7 +1258,7 @@ describe('IntegrationRenderer', () => {
1266
1258
  ).toHaveLength(1);
1267
1259
  });
1268
1260
 
1269
- it('should fail route execution when unresolved boundary artifact HTML is returned', async () => {
1261
+ it('should fail route execution when unresolved eco-marker artifact HTML is returned', async () => {
1270
1262
  const explicitRenderer = {
1271
1263
  renderComponent: vi.fn(async () => ({
1272
1264
  html: '<aside>Nested Render</aside>',
@@ -1282,7 +1274,7 @@ describe('IntegrationRenderer', () => {
1282
1274
  } as ProcessedAsset,
1283
1275
  ],
1284
1276
  })),
1285
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1277
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1286
1278
  explicitRenderer.renderComponent(input),
1287
1279
  ),
1288
1280
  } as unknown as IntegrationRenderer;
@@ -1342,11 +1334,11 @@ describe('IntegrationRenderer', () => {
1342
1334
  params: {},
1343
1335
  query: {},
1344
1336
  }),
1345
- ).rejects.toThrow('Full-route unresolved-boundary fallback has been removed');
1337
+ ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1346
1338
  expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
1347
1339
  });
1348
1340
 
1349
- it('should fail route execution when unresolved boundary artifact HTML remains inside surrounding shell html', async () => {
1341
+ it('should fail route execution when unresolved eco-marker artifact HTML remains inside surrounding shell html', async () => {
1350
1342
  const explicitRenderer = {
1351
1343
  renderComponent: vi.fn(async () => ({
1352
1344
  html: '<aside data-explicit-shell="nested"><span>Nested Render</span></aside>',
@@ -1362,7 +1354,7 @@ describe('IntegrationRenderer', () => {
1362
1354
  } as ProcessedAsset,
1363
1355
  ],
1364
1356
  })),
1365
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1357
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1366
1358
  explicitRenderer.renderComponent(input),
1367
1359
  ),
1368
1360
  } as unknown as IntegrationRenderer;
@@ -1422,11 +1414,11 @@ describe('IntegrationRenderer', () => {
1422
1414
  params: {},
1423
1415
  query: {},
1424
1416
  }),
1425
- ).rejects.toThrow('Full-route unresolved-boundary fallback has been removed');
1417
+ ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1426
1418
  expect((explicitRenderer.renderComponent as any).mock.calls).toHaveLength(0);
1427
1419
  });
1428
1420
 
1429
- it('should fail route execution for deep multi-level unresolved boundary artifacts', async () => {
1421
+ it('should fail route execution for deep multi-level unresolved eco-marker artifacts', async () => {
1430
1422
  const renderOrder: string[] = [];
1431
1423
  const explicitRenderer = {
1432
1424
  renderComponent: vi.fn(async (input: ComponentRenderInput) => {
@@ -1459,7 +1451,7 @@ describe('IntegrationRenderer', () => {
1459
1451
  rootAttributes: { 'data-eco-component-id': 'root-node' },
1460
1452
  };
1461
1453
  }),
1462
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1454
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1463
1455
  explicitRenderer.renderComponent(input),
1464
1456
  ),
1465
1457
  } as unknown as IntegrationRenderer;
@@ -1480,10 +1472,10 @@ describe('IntegrationRenderer', () => {
1480
1472
  runtimeOrigin: 'http://localhost:3000',
1481
1473
  });
1482
1474
 
1483
- const _parentMarker = createBoundaryMarker('n_2', 'parent-component', 'props-parent');
1484
- const _leafMarker = createBoundaryMarker('n_3', 'leaf-component', 'props-leaf');
1475
+ const _parentMarker = createUnresolvedMarkerArtifact('n_2', 'parent-component', 'props-parent');
1476
+ const _leafMarker = createUnresolvedMarkerArtifact('n_3', 'leaf-component', 'props-leaf');
1485
1477
 
1486
- renderer.RenderedBody = `<html><body><main>${createBoundaryMarker('n_1', 'root-component', 'props-root')}</main></body></html>`;
1478
+ renderer.RenderedBody = `<html><body><main>${createUnresolvedMarkerArtifact('n_1', 'root-component', 'props-root')}</main></body></html>`;
1487
1479
  renderer.MockComponentRenderResult = {
1488
1480
  html: '<main>Test Page</main>',
1489
1481
  canAttachAttributes: true,
@@ -1541,7 +1533,7 @@ describe('IntegrationRenderer', () => {
1541
1533
  params: {},
1542
1534
  query: {},
1543
1535
  }),
1544
- ).rejects.toThrow('Full-route unresolved-boundary fallback has been removed');
1536
+ ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1545
1537
  expect(renderOrder).toEqual([]);
1546
1538
  });
1547
1539
 
@@ -1553,7 +1545,7 @@ describe('IntegrationRenderer', () => {
1553
1545
  rootTag: 'aside',
1554
1546
  integrationName: 'explicit-renderer',
1555
1547
  })),
1556
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1548
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1557
1549
  explicitRenderer.renderComponent(input),
1558
1550
  ),
1559
1551
  } as unknown as IntegrationRenderer;
@@ -1612,10 +1604,10 @@ describe('IntegrationRenderer', () => {
1612
1604
  params: {},
1613
1605
  query: {},
1614
1606
  }),
1615
- ).rejects.toThrow('Full-route unresolved-boundary fallback has been removed');
1607
+ ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1616
1608
  });
1617
1609
 
1618
- it('should not recursively resolve boundary artifacts that were only passed through resolved child html', async () => {
1610
+ it('should not recursively resolve unresolved eco-marker artifacts that were only passed through resolved child html', async () => {
1619
1611
  const renderer = new TestIntegrationRenderer({
1620
1612
  appConfig: AppConfig,
1621
1613
  assetProcessingService: AssetService,
@@ -1640,7 +1632,7 @@ describe('IntegrationRenderer', () => {
1640
1632
  };
1641
1633
 
1642
1634
  await expect(
1643
- renderer.renderComponentBoundary({
1635
+ renderer.renderComponentWithForeignChildren({
1644
1636
  component: Component,
1645
1637
  props: {},
1646
1638
  children: '<aside>already resolved child html</aside>',
@@ -1652,7 +1644,7 @@ describe('IntegrationRenderer', () => {
1652
1644
  );
1653
1645
  });
1654
1646
 
1655
- it('fails fast when a renderer without a boundary runtime crosses into a foreign owner', async () => {
1647
+ it('fails fast when a renderer without a foreign-child runtime crosses into a foreign owner', async () => {
1656
1648
  const foreignRenderer = {
1657
1649
  renderComponent: vi.fn(async () => ({
1658
1650
  html: '<span>resolved nested marker</span>',
@@ -1660,7 +1652,7 @@ describe('IntegrationRenderer', () => {
1660
1652
  rootTag: 'span',
1661
1653
  integrationName: 'foreign-renderer',
1662
1654
  })),
1663
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1655
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1664
1656
  foreignRenderer.renderComponent(input),
1665
1657
  ),
1666
1658
  } as unknown as IntegrationRenderer;
@@ -1680,6 +1672,7 @@ describe('IntegrationRenderer', () => {
1680
1672
  assetProcessingService: AssetService,
1681
1673
  runtimeOrigin: 'http://localhost:3000',
1682
1674
  });
1675
+ renderer.UseFailFastForeignChildRuntime = true;
1683
1676
 
1684
1677
  const ForeignComponent = eco.component<{}, string>({
1685
1678
  integration: 'foreign-renderer',
@@ -1694,19 +1687,23 @@ describe('IntegrationRenderer', () => {
1694
1687
  render: ({ children }) => `<section>${children ?? ''}${ForeignComponent({})}</section>`,
1695
1688
  });
1696
1689
 
1697
- const passedThroughMarker = createBoundaryMarker('n_passed', 'passed-through-component', 'p_passed');
1690
+ const passedThroughMarker = createUnresolvedMarkerArtifact(
1691
+ 'n_passed',
1692
+ 'passed-through-component',
1693
+ 'p_passed',
1694
+ );
1698
1695
 
1699
1696
  await expect(
1700
- renderer.renderComponentBoundary({
1697
+ renderer.renderComponentWithForeignChildren({
1701
1698
  component: ShellComponent,
1702
1699
  props: { children: passedThroughMarker },
1703
1700
  children: passedThroughMarker,
1704
1701
  }),
1705
- ).rejects.toThrow('without a renderer-owned boundary runtime');
1702
+ ).rejects.toThrow('without a renderer-owned foreign-child runtime');
1706
1703
  expect(foreignRenderer.renderComponent).toHaveBeenCalledTimes(0);
1707
1704
  });
1708
1705
 
1709
- it('fails route execution when deep mixed-integration boundary artifacts are returned at the route level', async () => {
1706
+ it('fails route execution when deep mixed-integration eco-marker artifacts are returned at the route level', async () => {
1710
1707
  const renderOrder: string[] = [];
1711
1708
  const explicitRenderer = {
1712
1709
  renderComponent: vi.fn(async (input: ComponentRenderInput) => {
@@ -1740,7 +1737,7 @@ describe('IntegrationRenderer', () => {
1740
1737
  integrationName: 'explicit-renderer',
1741
1738
  };
1742
1739
  }),
1743
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) =>
1740
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) =>
1744
1741
  explicitRenderer.renderComponent(input),
1745
1742
  ),
1746
1743
  } as unknown as IntegrationRenderer;
@@ -1761,10 +1758,10 @@ describe('IntegrationRenderer', () => {
1761
1758
  runtimeOrigin: 'http://localhost:3000',
1762
1759
  });
1763
1760
 
1764
- const _parentMarker = createBoundaryMarker('n_2', 'parent-component', 'props-parent');
1765
- const _leafMarker = createBoundaryMarker('n_3', 'leaf-component', 'props-leaf');
1761
+ const _parentMarker = createUnresolvedMarkerArtifact('n_2', 'parent-component', 'props-parent');
1762
+ const _leafMarker = createUnresolvedMarkerArtifact('n_3', 'leaf-component', 'props-leaf');
1766
1763
 
1767
- renderer.RenderedBody = `<html><body><main><div data-shell="deep">${createBoundaryMarker('n_1', 'root-component', 'props-root')}</div></main></body></html>`;
1764
+ renderer.RenderedBody = `<html><body><main><div data-shell="deep">${createUnresolvedMarkerArtifact('n_1', 'root-component', 'props-root')}</div></main></body></html>`;
1768
1765
  renderer.MockComponentRenderResult = {
1769
1766
  html: '<main>Test Page</main>',
1770
1767
  canAttachAttributes: true,
@@ -1822,7 +1819,7 @@ describe('IntegrationRenderer', () => {
1822
1819
  params: {},
1823
1820
  query: {},
1824
1821
  }),
1825
- ).rejects.toThrow('Full-route unresolved-boundary fallback has been removed');
1822
+ ).rejects.toThrow('Full-route unresolved-marker fallback has been removed');
1826
1823
  expect(renderOrder).toEqual([]);
1827
1824
  });
1828
1825
 
@@ -1843,7 +1840,7 @@ describe('IntegrationRenderer', () => {
1843
1840
  currentIntegration: 'foreign-renderer',
1844
1841
  },
1845
1842
  async () =>
1846
- renderer.renderComponentBoundary({
1843
+ renderer.renderComponentWithForeignChildren({
1847
1844
  component: LeafComponent,
1848
1845
  props: {},
1849
1846
  }),
@@ -1851,10 +1848,10 @@ describe('IntegrationRenderer', () => {
1851
1848
 
1852
1849
  expect(result.value.html).toBe('<section>Leaf Render</section>');
1853
1850
  expect(result.value.html).not.toContain('<eco-marker');
1854
- expect(renderer.BoundaryRuntimeCreationCount).toBe(0);
1851
+ expect(renderer.ForeignChildRuntimeCreationCount).toBe(0);
1855
1852
  });
1856
1853
 
1857
- it('uses inline partial rendering when no foreign boundaries are present', async () => {
1854
+ it('uses inline partial rendering when no foreign children are present', async () => {
1858
1855
  const renderer = new TestIntegrationRenderer({
1859
1856
  appConfig: AppConfig,
1860
1857
  assetProcessingService: AssetService,
@@ -1882,10 +1879,10 @@ describe('IntegrationRenderer', () => {
1882
1879
 
1883
1880
  expect(await response.text()).toBe('<section>Inline</section>');
1884
1881
  expect(inlineRenderCount).toBe(1);
1885
- expect(renderer.BoundaryRenderCount).toBe(0);
1882
+ expect(renderer.ForeignChildRenderCount).toBe(0);
1886
1883
  });
1887
1884
 
1888
- it('falls back to boundary partial rendering when compatibility is needed', async () => {
1885
+ it('falls back to foreign-subtree partial rendering when compatibility is needed', async () => {
1889
1886
  const renderer = new TestIntegrationRenderer({
1890
1887
  appConfig: AppConfig,
1891
1888
  assetProcessingService: AssetService,
@@ -1901,18 +1898,18 @@ describe('IntegrationRenderer', () => {
1901
1898
  },
1902
1899
  };
1903
1900
 
1904
- const View = (() => '<section>Boundary</section>') as EcoComponent<Record<string, unknown>>;
1901
+ const View = (() => '<section>Foreign Subtree</section>') as EcoComponent<Record<string, unknown>>;
1905
1902
  View.config = {
1906
1903
  integration: 'test-renderer',
1907
1904
  __eco: {
1908
- id: 'boundary-view',
1909
- file: '/app/components/boundary-view.ts',
1905
+ id: 'foreign-subtree-view',
1906
+ file: '/app/components/foreign-subtree-view.ts',
1910
1907
  integration: 'test-renderer',
1911
1908
  },
1912
1909
  dependencies: { components: [ForeignChild] },
1913
1910
  };
1914
1911
  renderer.MockComponentRenderResult = {
1915
- html: '<section>Boundary</section>',
1912
+ html: '<section>Foreign Subtree</section>',
1916
1913
  canAttachAttributes: true,
1917
1914
  rootTag: 'section',
1918
1915
  integrationName: 'test-renderer',
@@ -1928,14 +1925,14 @@ describe('IntegrationRenderer', () => {
1928
1925
  },
1929
1926
  });
1930
1927
 
1931
- expect(await response.text()).toBe('<section>Boundary</section>');
1928
+ expect(await response.text()).toBe('<section>Foreign Subtree</section>');
1932
1929
  expect(inlineRenderCount).toBe(0);
1933
- expect(renderer.BoundaryRenderCount).toBe(1);
1930
+ expect(renderer.ForeignChildRenderCount).toBe(1);
1934
1931
  });
1935
1932
 
1936
1933
  it('reuses one foreign renderer instance across shared view shell composition', async () => {
1937
1934
  const foreignRenderer = {
1938
- renderComponentBoundary: vi.fn(async (input: ComponentRenderInput) => {
1935
+ renderComponentWithForeignChildren: vi.fn(async (input: ComponentRenderInput) => {
1939
1936
  const componentId = input.component.config?.__eco?.id;
1940
1937
 
1941
1938
  if (componentId === 'foreign-html-template') {
@@ -2020,10 +2017,10 @@ describe('IntegrationRenderer', () => {
2020
2017
 
2021
2018
  expect(await response.text()).toContain('<main><section>Foreign View</section></main>');
2022
2019
  expect(initializeRenderer).toHaveBeenCalledTimes(1);
2023
- expect(foreignRenderer.renderComponentBoundary).toHaveBeenCalledTimes(3);
2020
+ expect(foreignRenderer.renderComponentWithForeignChildren).toHaveBeenCalledTimes(3);
2024
2021
  });
2025
2022
 
2026
- it('should skip foreign-boundary wrapping for pure same-integration component trees', () => {
2023
+ it('should skip foreign-child wrapping for pure same-integration component trees', () => {
2027
2024
  const renderer = new TestIntegrationRenderer({
2028
2025
  appConfig: AppConfig,
2029
2026
  assetProcessingService: AssetService,
@@ -2051,7 +2048,7 @@ describe('IntegrationRenderer', () => {
2051
2048
  dependencies: { components: [Child] },
2052
2049
  };
2053
2050
 
2054
- expect(renderer.testHasForeignBoundaryDescendants(Root)).toBe(false);
2051
+ expect(renderer.testHasForeignChildDescendants(Root)).toBe(false);
2055
2052
  });
2056
2053
 
2057
2054
  it('should detect nested cross-integration component trees', () => {
@@ -2082,7 +2079,7 @@ describe('IntegrationRenderer', () => {
2082
2079
  dependencies: { components: [ForeignChild] },
2083
2080
  };
2084
2081
 
2085
- expect(renderer.testHasForeignBoundaryDescendants(Root)).toBe(true);
2082
+ expect(renderer.testHasForeignChildDescendants(Root)).toBe(true);
2086
2083
  });
2087
2084
  });
2088
2085
  });