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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +63 -7
  3. package/package.json +8 -94
  4. package/src/adapters/bun/create-app.d.ts +1 -0
  5. package/src/adapters/bun/create-app.js +39 -2
  6. package/src/adapters/bun/hmr-manager.d.ts +1 -13
  7. package/src/adapters/bun/hmr-manager.js +1 -22
  8. package/src/adapters/bun/server-adapter.js +23 -4
  9. package/src/adapters/node/node-hmr-manager.d.ts +2 -14
  10. package/src/adapters/node/node-hmr-manager.js +2 -23
  11. package/src/adapters/shared/explicit-static-render-preparation.d.ts +25 -0
  12. package/src/adapters/shared/explicit-static-render-preparation.js +26 -0
  13. package/src/adapters/shared/explicit-static-route-matcher.d.ts +5 -2
  14. package/src/adapters/shared/explicit-static-route-matcher.js +14 -16
  15. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +7 -10
  16. package/src/adapters/shared/file-route-middleware-pipeline.js +2 -11
  17. package/src/adapters/shared/fs-server-response-factory.d.ts +13 -9
  18. package/src/adapters/shared/fs-server-response-factory.js +10 -26
  19. package/src/adapters/shared/fs-server-response-matcher.d.ts +14 -6
  20. package/src/adapters/shared/fs-server-response-matcher.js +67 -28
  21. package/src/adapters/shared/render-context.d.ts +2 -2
  22. package/src/adapters/shared/server-adapter.d.ts +21 -10
  23. package/src/adapters/shared/server-adapter.js +171 -132
  24. package/src/adapters/shared/server-route-handler.d.ts +2 -2
  25. package/src/adapters/shared/server-route-handler.js +1 -1
  26. package/src/adapters/shared/server-static-builder.d.ts +4 -4
  27. package/src/config/README.md +1 -1
  28. package/src/config/config-builder.d.ts +2 -2
  29. package/src/config/config-builder.js +0 -5
  30. package/src/dev/host-runtime.d.ts +10 -0
  31. package/src/dev/host-runtime.js +24 -0
  32. package/src/eco/eco.js +7 -7
  33. package/src/eco/eco.types.d.ts +3 -3
  34. package/src/errors/index.d.ts +1 -0
  35. package/src/errors/index.js +3 -1
  36. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -5
  37. package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -4
  38. package/src/integrations/ghtml/ghtml-renderer.js +1 -7
  39. package/src/plugins/eco-component-meta-plugin.js +0 -1
  40. package/src/plugins/integration-plugin.d.ts +14 -18
  41. package/src/plugins/integration-plugin.js +14 -21
  42. package/src/plugins/processor.d.ts +2 -0
  43. package/src/plugins/processor.js +6 -1
  44. package/src/route-renderer/GRAPH.md +81 -289
  45. package/src/route-renderer/README.md +67 -105
  46. package/src/route-renderer/orchestration/component-render-context.d.ts +24 -18
  47. package/src/route-renderer/orchestration/component-render-context.js +14 -14
  48. package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +18 -0
  49. package/src/route-renderer/orchestration/declared-ownership-graph.js +34 -0
  50. package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +108 -0
  51. package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +206 -0
  52. package/src/route-renderer/orchestration/integration-renderer.d.ts +96 -136
  53. package/src/route-renderer/orchestration/integration-renderer.js +280 -303
  54. package/src/route-renderer/orchestration/ownership-planning.service.d.ts +24 -0
  55. package/src/route-renderer/orchestration/ownership-planning.service.js +63 -0
  56. package/src/route-renderer/orchestration/ownership-validation.service.d.ts +29 -0
  57. package/src/route-renderer/orchestration/ownership-validation.service.js +53 -0
  58. package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +90 -0
  59. package/src/route-renderer/orchestration/{queued-boundary-runtime.service.js → queued-foreign-subtree-resolution.service.js} +28 -25
  60. package/src/route-renderer/orchestration/render-output.utils.d.ts +3 -3
  61. package/src/route-renderer/orchestration/render-output.utils.js +6 -6
  62. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +120 -0
  63. package/src/route-renderer/orchestration/{render-preparation.service.js → route-render-orchestrator.js} +132 -108
  64. package/src/route-renderer/page-loading/component-dependency-collection.js +8 -1
  65. package/src/route-renderer/page-loading/dependency-resolver.js +5 -7
  66. package/src/route-renderer/page-loading/page-dependency-bundling.d.ts +1 -1
  67. package/src/route-renderer/page-loading/page-dependency-bundling.js +41 -19
  68. package/src/route-renderer/route-renderer.d.ts +28 -26
  69. package/src/route-renderer/route-renderer.js +4 -27
  70. package/src/router/README.md +16 -19
  71. package/src/router/server/route-registry.d.ts +78 -0
  72. package/src/router/server/route-registry.js +262 -0
  73. package/src/services/README.md +1 -2
  74. package/src/services/assets/asset-processing-service/assets.types.d.ts +3 -0
  75. package/src/services/assets/asset-processing-service/index.d.ts +1 -0
  76. package/src/services/assets/asset-processing-service/index.js +1 -0
  77. package/src/services/assets/asset-processing-service/page-package.d.ts +3 -0
  78. package/src/services/assets/asset-processing-service/page-package.js +74 -0
  79. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +4 -4
  80. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +6 -3
  81. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +9 -3
  82. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +4 -2
  83. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -1
  84. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +3 -1
  85. package/src/services/module-loading/node-bootstrap-plugin.js +15 -3
  86. package/src/static-site-generator/static-site-generator.d.ts +20 -21
  87. package/src/static-site-generator/static-site-generator.js +107 -140
  88. package/src/types/internal-types.d.ts +13 -12
  89. package/src/types/public-types.d.ts +46 -36
  90. package/src/watchers/project-watcher.test-helpers.js +5 -5
  91. package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
  92. package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
  93. package/src/route-renderer/orchestration/page-packaging.service.d.ts +0 -16
  94. package/src/route-renderer/orchestration/page-packaging.service.js +0 -66
  95. package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
  96. package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
  97. package/src/route-renderer/orchestration/render-execution.service.js +0 -106
  98. package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
  99. package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
  100. package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
  101. package/src/router/server/fs-router-scanner.d.ts +0 -41
  102. package/src/router/server/fs-router-scanner.js +0 -161
  103. package/src/router/server/fs-router.d.ts +0 -26
  104. package/src/router/server/fs-router.js +0 -100
  105. package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
  106. package/src/services/runtime-state/runtime-specifier-registry.service.js +0 -37
@@ -2,7 +2,7 @@ import type { Readable } from 'node:stream';
2
2
  import type { ApiResponseBuilder } from '../adapters/shared/api-response.js';
3
3
  import type { BuildExecutor } from '../build/build-adapter.js';
4
4
  import type { EcoBuildPlugin } from '../build/build-types.js';
5
- import type { ComponentBoundaryRuntime } from '../route-renderer/orchestration/component-render-context.js';
5
+ import type { ForeignChildRuntime } from '../route-renderer/orchestration/component-render-context.js';
6
6
  import type { EcoPageComponent } from '../eco/eco.types.js';
7
7
  import type { EcoPagesAppConfig } from './internal-types.js';
8
8
  import type { HmrStrategy } from '../hmr/hmr-strategy.js';
@@ -14,7 +14,7 @@ export type { EcoPagesAppConfig } from './internal-types.js';
14
14
  export type { EcoPageComponent } from '../eco/eco.types.js';
15
15
  export type { ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.js';
16
16
  import type { StandardSchema, StandardSchemaResult, StandardSchemaSuccessResult, StandardSchemaFailureResult, StandardSchemaIssue, InferOutput } from '../services/validation/standard-schema.types.js';
17
- export type { StandardSchema, StandardSchemaResult, StandardSchemaSuccessResult, StandardSchemaFailureResult, StandardSchemaIssue, InferOutput, ComponentBoundaryRuntime, };
17
+ export type { StandardSchema, StandardSchemaResult, StandardSchemaSuccessResult, StandardSchemaFailureResult, StandardSchemaIssue, InferOutput, ForeignChildRuntime, };
18
18
  export type InteractionEventsString = ScriptsInjectorInteractionEventsString;
19
19
  export type DependencyLazyTrigger = {
20
20
  'on:idle': true;
@@ -94,10 +94,6 @@ export interface DefaultHmrContext {
94
94
  * Map of registered entrypoints to their output URLs.
95
95
  */
96
96
  getWatchedFiles(): Map<string, string>;
97
- /**
98
- * Map of bare specifiers to runtime URLs for browser import resolution.
99
- */
100
- getSpecifierMap(): Map<string, string>;
101
97
  /**
102
98
  * Directory where HMR bundles are written.
103
99
  */
@@ -188,15 +184,6 @@ export interface IHmrManager {
188
184
  * script bundling path.
189
185
  */
190
186
  registerScriptEntrypoint(entrypointPath: string): Promise<string>;
191
- /**
192
- * Registers mappings from bare specifiers to runtime URLs.
193
- *
194
- * @remarks
195
- * This is the shared registration seam for integration-owned runtime alias
196
- * maps. The registry may later back a broader import-map-style facility, but
197
- * the mappings themselves remain integration-owned.
198
- */
199
- registerSpecifierMap(map: Record<string, string>): void;
200
187
  /**
201
188
  * Registers a custom HMR strategy.
202
189
  */
@@ -225,10 +212,6 @@ export interface IHmrManager {
225
212
  * Gets the map of watched files.
226
213
  */
227
214
  getWatchedFiles(): Map<string, string>;
228
- /**
229
- * Gets the registered bare-specifier map.
230
- */
231
- getSpecifierMap(): Map<string, string>;
232
215
  /**
233
216
  * Gets the HMR dist directory.
234
217
  */
@@ -679,7 +662,7 @@ export type IntegrationRendererRenderOptions<C = EcoPagesElement> = RouteRendere
679
662
  pageProps?: Record<string, unknown>;
680
663
  cacheStrategy?: CacheStrategy;
681
664
  pageLocals?: RequestLocals;
682
- boundaryPlan?: BoundaryPlan;
665
+ ownershipPlan?: OwnershipPlan;
683
666
  };
684
667
  /**
685
668
  * Structured page asset package produced after dependency resolution.
@@ -718,51 +701,65 @@ export interface PagePackageResult {
718
701
  */
719
702
  dynamicChunks: ProcessedAsset[];
720
703
  }
721
- export type BoundaryValidationErrorCode = 'UNKNOWN_INTEGRATION_OWNER' | 'MISSING_COMPONENT_METADATA';
722
- export interface BoundaryValidationError {
723
- code: BoundaryValidationErrorCode;
704
+ /**
705
+ * Page-scoped browser output planned before final HTML packaging.
706
+ *
707
+ * The initial seam keeps the graph payload intentionally small: integrations
708
+ * return the processed assets that belong to the Page browser graph, while the
709
+ * surrounding route pipeline remains free to evolve toward richer entry/lazy/
710
+ * shared chunk structure later.
711
+ */
712
+ export interface PageBrowserGraphResult {
713
+ /**
714
+ * Processed assets owned by the current Page browser graph.
715
+ */
716
+ assets: ProcessedAsset[];
717
+ }
718
+ export type OwnershipValidationErrorCode = 'UNKNOWN_INTEGRATION_OWNER' | 'MISSING_COMPONENT_METADATA';
719
+ export interface OwnershipValidationError {
720
+ code: OwnershipValidationErrorCode;
724
721
  message: string;
725
722
  componentId?: string;
726
723
  componentFile?: string;
727
724
  integrationName?: string;
728
725
  }
729
- export type BoundaryPlanNodeSource = 'route' | 'page' | 'layout' | 'html-template' | 'dependency';
730
- export interface BoundaryOwnership {
726
+ export type OwnershipPlanNodeSource = 'route' | 'page' | 'layout' | 'html-template' | 'dependency';
727
+ export interface IntegrationOwnership {
731
728
  integrationName: string;
732
729
  componentId: string;
733
730
  componentFile?: string;
734
731
  isPageEntry: boolean;
735
732
  isForeignToParent: boolean;
736
733
  }
737
- export interface BoundaryPlanNode {
734
+ export interface OwnershipPlanNode {
738
735
  id: string;
739
- source: BoundaryPlanNodeSource;
740
- ownership: BoundaryOwnership;
741
- children: BoundaryPlanNode[];
736
+ source: OwnershipPlanNodeSource;
737
+ ownership: IntegrationOwnership;
738
+ children: OwnershipPlanNode[];
742
739
  declaredDependenciesValid: boolean;
743
740
  }
744
- export interface BoundaryPlan {
745
- root: BoundaryPlanNode;
741
+ export interface OwnershipPlan {
742
+ root: OwnershipPlanNode;
746
743
  rendererNames: string[];
747
744
  foreignEdgeCount: number;
748
745
  hasValidationErrors: boolean;
749
- validationErrors: BoundaryValidationError[];
746
+ validationErrors: OwnershipValidationError[];
750
747
  }
751
- export type BoundaryAttachmentPolicy = {
748
+ export type ForeignSubtreeAttachmentPolicy = {
752
749
  kind: 'none';
753
750
  } | {
754
751
  kind: 'first-element';
755
752
  };
756
- export interface BoundaryRenderPayload {
753
+ export interface ForeignSubtreeRenderPayload {
757
754
  html: string;
758
755
  assets: ProcessedAsset[];
759
756
  rootTag?: string;
760
757
  rootAttributes?: Record<string, string>;
761
- attachmentPolicy: BoundaryAttachmentPolicy;
758
+ attachmentPolicy: ForeignSubtreeAttachmentPolicy;
762
759
  integrationName: string;
763
760
  }
764
761
  /**
765
- * Shared execution-scoped context threaded through component boundary renders.
762
+ * Shared execution-scoped context threaded through foreign-child renders.
766
763
  *
767
764
  * Integrations can extend this with renderer-local runtime keys, but the cache
768
765
  * and optional component instance identity are shared across all renderers.
@@ -1016,6 +1013,15 @@ export interface ApiHandlerContext<TRequest extends Request = Request, TServer =
1016
1013
  */
1017
1014
  headers?: unknown;
1018
1015
  }
1016
+ /**
1017
+ * Context available to file-route page middleware.
1018
+ *
1019
+ * Page middleware can mutate locals, short-circuit the request, and use the
1020
+ * response helpers, but final document rendering stays owned by the page route
1021
+ * execution path.
1022
+ */
1023
+ export interface FileRouteMiddlewareContext<TRequest extends Request = Request, TServer = any> extends Omit<ApiHandlerContext<TRequest, TServer>, 'render' | 'renderPartial'> {
1024
+ }
1019
1025
  /**
1020
1026
  * Next function for middleware chain.
1021
1027
  * Call to continue to the next middleware or final handler.
@@ -1051,6 +1057,10 @@ export type MiddlewareNext = () => Promise<Response>;
1051
1057
  * ```
1052
1058
  */
1053
1059
  export type Middleware<TRequest extends Request = Request, TServer = any, TContext extends ApiHandlerContext<TRequest, TServer> = ApiHandlerContext<TRequest, TServer>> = (context: TContext, next: MiddlewareNext) => Promise<Response> | Response;
1060
+ /**
1061
+ * Middleware contract for file-based page routes.
1062
+ */
1063
+ export type FileRouteMiddleware<TRequest extends Request = Request, TServer = any, TContext extends FileRouteMiddlewareContext<TRequest, TServer> = FileRouteMiddlewareContext<TRequest, TServer>> = (context: TContext, next: MiddlewareNext) => Promise<Response> | Response;
1054
1064
  /**
1055
1065
  * Helper type for defining middleware with extended context.
1056
1066
  * Automatically infers TRequest and TServer from the provided context type.
@@ -10,23 +10,23 @@ const createMockHmrManager = () => ({
10
10
  }),
11
11
  registerEntrypoint: vi.fn(async () => ""),
12
12
  registerScriptEntrypoint: vi.fn(async () => ""),
13
- registerSpecifierMap: vi.fn(() => {
14
- }),
15
13
  registerStrategy: vi.fn(() => {
16
14
  }),
17
15
  isEnabled: vi.fn(() => true),
18
16
  getOutputUrl: vi.fn(() => void 0),
19
17
  getWatchedFiles: vi.fn(() => /* @__PURE__ */ new Map()),
20
- getSpecifierMap: vi.fn(() => /* @__PURE__ */ new Map()),
21
18
  getDistDir: vi.fn(() => ""),
22
19
  getPlugins: vi.fn(() => []),
23
20
  getDefaultContext: vi.fn(() => ({
24
21
  getWatchedFiles: () => /* @__PURE__ */ new Map(),
25
- getSpecifierMap: () => /* @__PURE__ */ new Map(),
26
22
  getDistDir: () => "",
27
23
  getPlugins: () => [],
28
24
  getSrcDir: () => "",
29
- getLayoutsDir: () => ""
25
+ getLayoutsDir: () => "",
26
+ getPagesDir: () => "",
27
+ getBuildExecutor: () => ({ build: vi.fn(async () => ({ success: true, logs: [], outputs: [] })) }),
28
+ getBrowserBundleService: () => ({ bundle: vi.fn(async () => ({ success: true, logs: [], outputs: [] })) }),
29
+ importServerModule: vi.fn(async () => ({}))
30
30
  }))
31
31
  });
32
32
  const createMockBridge = () => ({
@@ -1,25 +0,0 @@
1
- import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
- import type { BoundaryPlan, EcoComponent } from '../../types/public-types.js';
3
- type BoundaryPlanBuildInput = {
4
- routeFile: string;
5
- currentIntegrationName: string;
6
- HtmlTemplate: EcoComponent;
7
- Layout?: EcoComponent;
8
- Page: EcoComponent;
9
- };
10
- /**
11
- * Builds a declared ownership plan from the component dependency graph.
12
- *
13
- * The plan is intentionally conservative: it reflects declared component
14
- * dependencies available during render preparation and records diagnostics for
15
- * foreign ownership edges that cannot be validated against registered
16
- * integrations or stable component metadata.
17
- */
18
- export declare class BoundaryPlanningService {
19
- private readonly appConfig;
20
- private nextSyntheticId;
21
- constructor(appConfig: EcoPagesAppConfig);
22
- buildPlan(input: BoundaryPlanBuildInput): BoundaryPlan;
23
- private isRegisteredIntegration;
24
- }
25
- export {};
@@ -1,97 +0,0 @@
1
- class BoundaryPlanningService {
2
- appConfig;
3
- nextSyntheticId = 0;
4
- constructor(appConfig) {
5
- this.appConfig = appConfig;
6
- }
7
- buildPlan(input) {
8
- this.nextSyntheticId = 0;
9
- const validationErrors = [];
10
- const rendererNames = /* @__PURE__ */ new Set([input.currentIntegrationName]);
11
- let foreignEdgeCount = 0;
12
- const buildNode = (component, source, parentIntegrationName, lineage) => {
13
- const integrationName = component.config?.integration ?? component.config?.__eco?.integration ?? parentIntegrationName;
14
- const componentMeta = component.config?.__eco;
15
- const isForeignToParent = integrationName !== parentIntegrationName;
16
- const componentId = componentMeta?.id ?? componentMeta?.file ?? `${source}:${this.nextSyntheticId += 1}`;
17
- rendererNames.add(integrationName);
18
- if (isForeignToParent) {
19
- foreignEdgeCount += 1;
20
- if (!componentMeta) {
21
- validationErrors.push({
22
- code: "MISSING_COMPONENT_METADATA",
23
- message: `[ecopages] Foreign boundary "${componentId}" must provide stable __eco metadata so ownership diagnostics stay actionable. Declared dependencies must include all possible foreign children.`,
24
- componentId,
25
- integrationName
26
- });
27
- }
28
- if (!this.isRegisteredIntegration(integrationName, input.currentIntegrationName)) {
29
- validationErrors.push({
30
- code: "UNKNOWN_INTEGRATION_OWNER",
31
- message: `[ecopages] Foreign boundary "${componentId}" references unknown integration owner "${integrationName}". Declared dependencies must include all possible foreign children and those integrations must be registered.`,
32
- componentId,
33
- componentFile: componentMeta?.file,
34
- integrationName
35
- });
36
- }
37
- }
38
- const nextLineage = new Set(lineage);
39
- nextLineage.add(component);
40
- const children = (component.config?.dependencies?.components ?? []).flatMap((child) => {
41
- if (!child || nextLineage.has(child)) {
42
- return [];
43
- }
44
- return [buildNode(child, "dependency", integrationName, nextLineage)];
45
- });
46
- return {
47
- id: componentId,
48
- source,
49
- ownership: {
50
- integrationName,
51
- componentId,
52
- componentFile: componentMeta?.file,
53
- isPageEntry: source === "page",
54
- isForeignToParent
55
- },
56
- children,
57
- declaredDependenciesValid: true
58
- };
59
- };
60
- const roots = [
61
- { component: input.HtmlTemplate, source: "html-template" },
62
- ...input.Layout ? [{ component: input.Layout, source: "layout" }] : [],
63
- { component: input.Page, source: "page" }
64
- ];
65
- const root = {
66
- id: `route:${input.routeFile}`,
67
- source: "route",
68
- ownership: {
69
- integrationName: input.currentIntegrationName,
70
- componentId: `route:${input.routeFile}`,
71
- componentFile: input.routeFile,
72
- isPageEntry: false,
73
- isForeignToParent: false
74
- },
75
- children: roots.map(
76
- ({ component, source }) => buildNode(component, source, input.currentIntegrationName, /* @__PURE__ */ new Set())
77
- ),
78
- declaredDependenciesValid: validationErrors.length === 0
79
- };
80
- return {
81
- root,
82
- rendererNames: Array.from(rendererNames),
83
- foreignEdgeCount,
84
- hasValidationErrors: validationErrors.length > 0,
85
- validationErrors
86
- };
87
- }
88
- isRegisteredIntegration(integrationName, currentIntegrationName) {
89
- if (integrationName === currentIntegrationName) {
90
- return true;
91
- }
92
- return this.appConfig.integrations.some((integration) => integration.name === integrationName);
93
- }
94
- }
95
- export {
96
- BoundaryPlanningService
97
- };
@@ -1,16 +0,0 @@
1
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
2
- import type { PagePackageResult } from '../../types/public-types.js';
3
- /**
4
- * Creates the structured page package consumed by final HTML injection.
5
- *
6
- * This first pass is intentionally behavior-preserving. It establishes the
7
- * packaging seam while forwarding the current flat processed asset list.
8
- */
9
- export declare class PagePackagingService {
10
- /**
11
- * Partitions processed assets into the page-level groups used during final
12
- * HTML injection and post-processing.
13
- */
14
- createPagePackage(assets: ProcessedAsset[]): PagePackageResult;
15
- private shouldIncludeInHtml;
16
- }
@@ -1,66 +0,0 @@
1
- class PagePackagingService {
2
- /**
3
- * Partitions processed assets into the page-level groups used during final
4
- * HTML injection and post-processing.
5
- */
6
- createPagePackage(assets) {
7
- const inlineAssets = [];
8
- const separateAssets = [];
9
- const dynamicChunks = [];
10
- let pageScript;
11
- let pageStylesheet;
12
- for (const asset of assets) {
13
- if (asset.inline) {
14
- inlineAssets.push(asset);
15
- continue;
16
- }
17
- if (asset.packageRole === "dynamic-chunk") {
18
- dynamicChunks.push(asset);
19
- continue;
20
- }
21
- if (!pageScript && asset.packageRole === "page-script") {
22
- pageScript = asset;
23
- continue;
24
- }
25
- if (!pageStylesheet && asset.packageRole === "page-style") {
26
- pageStylesheet = asset;
27
- continue;
28
- }
29
- if (asset.packageRole === "keep-separate" || asset.packageRole === "runtime") {
30
- separateAssets.push(asset);
31
- continue;
32
- }
33
- if (!pageScript && asset.kind === "script" && !asset.excludeFromHtml) {
34
- pageScript = asset;
35
- continue;
36
- }
37
- if (!pageStylesheet && asset.kind === "stylesheet") {
38
- pageStylesheet = asset;
39
- continue;
40
- }
41
- separateAssets.push(asset);
42
- }
43
- const htmlAssets = assets.filter((asset) => this.shouldIncludeInHtml(asset));
44
- return {
45
- assets,
46
- htmlAssets,
47
- pageScript,
48
- pageStylesheet,
49
- inlineAssets,
50
- separateAssets,
51
- dynamicChunks
52
- };
53
- }
54
- shouldIncludeInHtml(asset) {
55
- if (asset.excludeFromHtml) {
56
- return false;
57
- }
58
- if (asset.packageRole === "runtime") {
59
- return false;
60
- }
61
- return true;
62
- }
63
- }
64
- export {
65
- PagePackagingService
66
- };
@@ -1,89 +0,0 @@
1
- import type { ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
2
- import type { BaseIntegrationContext, BoundaryRenderPayload, ComponentRenderInput, EcoComponent } from '../../types/public-types.js';
3
- import type { ComponentBoundaryRuntime } from './component-render-context.js';
4
- export type QueuedBoundaryDecisionInput = {
5
- currentIntegration: string;
6
- targetIntegration?: string;
7
- component: EcoComponent;
8
- props: Record<string, unknown>;
9
- };
10
- export type QueuedBoundaryResolution = {
11
- token: string;
12
- component: EcoComponent;
13
- props: Record<string, unknown>;
14
- componentInstanceId: string;
15
- };
16
- /**
17
- * Shared mutable state for one renderer-owned queued boundary runtime.
18
- *
19
- * Renderers that cannot resolve foreign boundaries inline can enqueue transport
20
- * tokens during their initial render, then resolve those tokens against the
21
- * owning renderer before returning final HTML.
22
- */
23
- export type QueuedBoundaryRuntimeContext = {
24
- rendererCache: Map<string, unknown>;
25
- componentInstanceScope?: string;
26
- nextBoundaryId: number;
27
- queuedResolutions: QueuedBoundaryResolution[];
28
- };
29
- type QueuedBoundaryIntegrationContext = BaseIntegrationContext & Record<string, unknown>;
30
- type QueuedBoundaryChildRenderResult = {
31
- assets: ProcessedAsset[];
32
- html?: string;
33
- };
34
- /**
35
- * Shared queue orchestration for renderer-owned boundary runtimes that emit
36
- * temporary transport tokens during one render pass.
37
- *
38
- * The service keeps three responsibilities in one place:
39
- * - storing per-render queue state on the active integration context
40
- * - creating a `ComponentBoundaryRuntime` that enqueues foreign boundaries
41
- * - resolving queued tokens back through the owning renderer before final HTML
42
- * leaves the current renderer
43
- *
44
- * Renderers still own framework-specific child rendering. This service only
45
- * handles queue bookkeeping, recursion, cycle detection, and asset merging.
46
- */
47
- export declare class QueuedBoundaryRuntimeService {
48
- /**
49
- * Reads the queued boundary runtime state previously attached to one render.
50
- */
51
- getRuntimeContext<TContext extends QueuedBoundaryRuntimeContext>(input: ComponentRenderInput, runtimeContextKey: string): TContext | undefined;
52
- /**
53
- * Creates the runtime hook used by `runWithComponentRenderContext()` for one
54
- * renderer-owned queue.
55
- *
56
- * When the renderer decides a boundary must be handed off, the runtime returns
57
- * a resolved transport token instead of rendering the foreign component inline.
58
- */
59
- createRuntime<TContext extends QueuedBoundaryRuntimeContext>(options: {
60
- boundaryInput: ComponentRenderInput;
61
- rendererCache: Map<string, unknown>;
62
- runtimeContextKey: string;
63
- tokenPrefix: string;
64
- shouldQueueBoundary: (input: QueuedBoundaryDecisionInput) => boolean;
65
- createRuntimeContext?: (integrationContext: QueuedBoundaryIntegrationContext, rendererCache: Map<string, unknown>) => TContext;
66
- }): ComponentBoundaryRuntime;
67
- /**
68
- * Resolves every queued transport token in one renderer-owned HTML fragment.
69
- *
70
- * The caller supplies framework-specific child rendering, while this service
71
- * handles recursive token replacement, cycle detection, root-attribute
72
- * application, and merged asset collection.
73
- */
74
- resolveQueuedHtml<TContext extends QueuedBoundaryRuntimeContext>(options: {
75
- html: string;
76
- runtimeContext?: TContext;
77
- queueLabel: string;
78
- renderQueuedChildren: (children: unknown, runtimeContext: TContext, queuedResolutionsByToken: Map<string, QueuedBoundaryResolution>, resolveToken: (token: string) => Promise<string>) => Promise<QueuedBoundaryChildRenderResult>;
79
- resolveBoundary: (input: ComponentRenderInput, rendererCache: Map<string, unknown>) => Promise<BoundaryRenderPayload | undefined>;
80
- applyAttributesToFirstElement: (html: string, attributes: Record<string, string>) => string;
81
- dedupeProcessedAssets: (assets: ProcessedAsset[]) => ProcessedAsset[];
82
- }): Promise<{
83
- assets: ProcessedAsset[];
84
- html: string;
85
- }>;
86
- private createBoundaryToken;
87
- private ensureRuntimeContext;
88
- }
89
- export {};
@@ -1,43 +0,0 @@
1
- import type { IntegrationRendererRenderOptions, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
2
- export interface CapturedHtmlRenderResult {
3
- body: RouteRendererBody;
4
- html: string;
5
- }
6
- export interface FinalizeHtmlRenderOptions {
7
- html: string;
8
- componentRootAttributes?: Record<string, string>;
9
- documentAttributes?: Record<string, string>;
10
- }
11
- export interface RenderExecutionCallbacks<C> {
12
- prepareRenderOptions(options: RouteRendererOptions): Promise<IntegrationRendererRenderOptions<C>>;
13
- render(renderOptions: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
14
- getDocumentAttributes(renderOptions: IntegrationRendererRenderOptions<C>): Record<string, string> | undefined;
15
- applyAttributesToHtmlElement(html: string, attributes: Record<string, string>): string;
16
- applyAttributesToFirstBodyElement(html: string, attributes: Record<string, string>): string;
17
- transformResponse(response: Response): Promise<RouteRendererBody>;
18
- }
19
- /**
20
- * Executes the main post-preparation rendering flow for integration renderers.
21
- *
22
- * This service owns the orchestration that happens after normalized render
23
- * options have been prepared: one render pass, unresolved boundary-marker
24
- * enforcement, root-attribute application, and final HTML transformation into
25
- * a response body stream.
26
- */
27
- export declare class RenderExecutionService {
28
- captureHtmlRender(render: () => Promise<RouteRendererBody>): Promise<CapturedHtmlRenderResult>;
29
- /**
30
- * Executes one integration render pass and returns the final route render
31
- * result.
32
- *
33
- * @typeParam C Integration render output element type.
34
- * @param options Route-level render options.
35
- * @param currentIntegrationName Active integration name for this render pass.
36
- * @param callbacks Renderer-specific hooks required during execution.
37
- * @returns Final route render output with body and cache strategy.
38
- */
39
- execute<C = unknown>(options: RouteRendererOptions, callbacks: RenderExecutionCallbacks<C>): Promise<RouteRenderResult>;
40
- private captureRenderedBody;
41
- finalizeHtmlRender(options: FinalizeHtmlRenderOptions, callbacks: Pick<RenderExecutionCallbacks<unknown>, 'applyAttributesToHtmlElement' | 'applyAttributesToFirstBodyElement'>): Promise<string>;
42
- private applyFinalHtmlAttributes;
43
- }
@@ -1,106 +0,0 @@
1
- import { inspectBoundaryArtifactHtml } from "./render-output.utils.js";
2
- class RenderExecutionService {
3
- async captureHtmlRender(render) {
4
- const renderedBody = await render();
5
- const capturedRender = await this.captureRenderedBody(renderedBody);
6
- return {
7
- body: capturedRender.body,
8
- html: capturedRender.html
9
- };
10
- }
11
- /**
12
- * Executes one integration render pass and returns the final route render
13
- * result.
14
- *
15
- * @typeParam C Integration render output element type.
16
- * @param options Route-level render options.
17
- * @param currentIntegrationName Active integration name for this render pass.
18
- * @param callbacks Renderer-specific hooks required during execution.
19
- * @returns Final route render output with body and cache strategy.
20
- */
21
- async execute(options, callbacks) {
22
- const renderOptions = await callbacks.prepareRenderOptions(options);
23
- const shouldApplyComponentRootAttributes = renderOptions.componentRender?.canAttachAttributes && renderOptions.componentRender.rootAttributes && Object.keys(renderOptions.componentRender.rootAttributes).length > 0;
24
- const renderExecution = await this.captureHtmlRender(async () => callbacks.render(renderOptions));
25
- const boundaryArtifacts = inspectBoundaryArtifactHtml(renderExecution.html);
26
- const documentAttributes = callbacks.getDocumentAttributes(renderOptions);
27
- const hasBoundaryMarkerHtml = boundaryArtifacts.hasUnresolvedBoundaryArtifacts;
28
- if (hasBoundaryMarkerHtml) {
29
- throw new Error(
30
- "[ecopages] Route render returned unresolved boundary artifact HTML. Full-route unresolved-boundary fallback has been removed; resolve mixed boundaries inside renderComponentBoundary()."
31
- );
32
- }
33
- const canReuseCapturedBody = !hasBoundaryMarkerHtml && !shouldApplyComponentRootAttributes && !(documentAttributes && Object.keys(documentAttributes).length > 0);
34
- if (canReuseCapturedBody) {
35
- const body2 = await callbacks.transformResponse(
36
- new Response(renderExecution.body, {
37
- headers: {
38
- "Content-Type": "text/html"
39
- }
40
- })
41
- );
42
- return {
43
- body: body2,
44
- cacheStrategy: renderOptions.cacheStrategy
45
- };
46
- }
47
- const finalization = await this.finalizeHtmlRender(
48
- {
49
- html: boundaryArtifacts.normalizedHtml,
50
- componentRootAttributes: shouldApplyComponentRootAttributes ? renderOptions.componentRender?.rootAttributes : void 0,
51
- documentAttributes
52
- },
53
- {
54
- applyAttributesToHtmlElement: callbacks.applyAttributesToHtmlElement,
55
- applyAttributesToFirstBodyElement: callbacks.applyAttributesToFirstBodyElement
56
- }
57
- );
58
- const body = await callbacks.transformResponse(
59
- new Response(finalization, {
60
- headers: {
61
- "Content-Type": "text/html"
62
- }
63
- })
64
- );
65
- return {
66
- body,
67
- cacheStrategy: renderOptions.cacheStrategy
68
- };
69
- }
70
- async captureRenderedBody(body) {
71
- const response = new Response(body);
72
- if (typeof body === "string") {
73
- return {
74
- body,
75
- html: await response.text()
76
- };
77
- }
78
- if (!response.body) {
79
- return {
80
- body,
81
- html: await response.text()
82
- };
83
- }
84
- const [capturedBody, replayBody] = response.body.tee();
85
- return {
86
- body: replayBody,
87
- html: await new Response(capturedBody).text()
88
- };
89
- }
90
- async finalizeHtmlRender(options, callbacks) {
91
- return this.applyFinalHtmlAttributes(options.html, options, callbacks);
92
- }
93
- applyFinalHtmlAttributes(html, options, callbacks) {
94
- let renderedHtml = html;
95
- if (options.componentRootAttributes && Object.keys(options.componentRootAttributes).length > 0) {
96
- renderedHtml = callbacks.applyAttributesToFirstBodyElement(renderedHtml, options.componentRootAttributes);
97
- }
98
- if (options.documentAttributes && Object.keys(options.documentAttributes).length > 0) {
99
- renderedHtml = callbacks.applyAttributesToHtmlElement(renderedHtml, options.documentAttributes);
100
- }
101
- return renderedHtml;
102
- }
103
- }
104
- export {
105
- RenderExecutionService
106
- };