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