/** * This module contains the abstract class for the Integration Renderer * Every integration renderer should extend this class * @module */ import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js'; import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoComponentDependencies, EcoPageFile, EcoPagesElement, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js'; import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js'; import { HtmlTransformerService } from '../../services/html/html-transformer.service.js'; import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js'; import { HttpError } from '../../errors/http-error.js'; import { DependencyResolverService } from '../page-loading/dependency-resolver.js'; import { PageModuleLoaderService } from '../page-loading/page-module-loader.js'; import { RouteRenderOrchestrator, type RouteRenderOrchestratorAdapter, type RouteRenderOrchestratorResolvedInputs } from './route-pipeline/route-render-orchestrator.js'; import { type ResolvedPageDependencies } from '../page-loading/resolved-page-dependencies.js'; import { type GroupedGraphBuildPlan } from './page-browser-graph/grouped-graph-build-plan.js'; import type { ForeignChildRuntime } from './foreign-child/component-render-context.js'; import { ForeignSubtreeExecutionService } from './foreign-child/foreign-subtree-execution.service.js'; import { type DocumentShellLayoutInput } from './document-shell/document-shell-render.service.js'; /** * Controls how one route module is loaded outside the normal render path. */ export type RouteModuleLoadOptions = { bypassCache?: boolean; }; /** * Context for renderToResponse method. */ export interface RenderToResponseContext { partial?: boolean; status?: number; headers?: HeadersInit; } export type HtmlDocumentContributionContext = { renderOptions?: IntegrationRendererRenderOptions; partial?: boolean; }; export type { PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../types/public-types.js'; export type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js'; /** * The IntegrationRenderer class is an abstract class that provides a base for rendering integration-specific components in the EcoPages framework. * It handles the import of page files, collection of dependencies, and preparation of render options. * The class is designed to be extended by specific integration renderers. */ export declare abstract class IntegrationRenderer { abstract name: string; protected appConfig: EcoPagesAppConfig; protected assetProcessingService: AssetProcessingService; protected htmlTransformer: HtmlTransformerService; protected hmrManager?: IHmrManager; protected resolvedIntegrationDependencies: ProcessedAsset[]; protected rendererModules?: unknown; protected options: Required; protected runtimeOrigin: string; protected dependencyResolverService: DependencyResolverService; protected pageModuleLoaderService: PageModuleLoaderService; protected routeRenderOrchestrator: RouteRenderOrchestrator; protected readonly foreignSubtreeExecutionService: ForeignSubtreeExecutionService; /** * Serializes route and view renders that mutate `htmlTransformer` state. * * Integration renderers are cached per integration, so concurrent static builds * and overlapping SSR requests must not share one transformer page package. */ private renderExclusiveChain; protected DOC_TYPE: string; private runRenderExclusive; /** * Activates the owning integration runtime on first render or graph use. */ protected ensureIntegrationRuntimeActivated(): Promise; /** * Prebuilds the production Page Browser Graph for one route file and optional params. */ prebuildProductionPageBrowserGraph(routeFile: string, options?: Pick & { groupedBuildPlan?: GroupedGraphBuildPlan; }): Promise; /** * Builds one production grouped graph plan for the supplied static route instances. */ buildGroupedGraphBuildPlan(instances: ReadonlyArray<{ routeFile: string; params?: RouteRendererOptions['params']; query?: RouteRendererOptions['query']; }>): Promise; /** * Loads one route module through the owning renderer's import path. * * Request-time infrastructure may need page metadata such as cache strategy or * middleware before full rendering starts. Exposing this narrow entrypoint lets * those callers reuse integration-specific import setup instead of bypassing it * with raw transpiler access. */ loadPageModule(file: string, options?: RouteModuleLoadOptions): Promise; protected getRendererModuleValue(key: string): unknown; protected getRendererModuleString(key: string): string | undefined; protected getRendererBootstrapDependencies(partial?: boolean): ProcessedAsset[]; setHmrManager(hmrManager: IHmrManager): void; /** * Build response headers with optional custom headers. * @param contentType - The Content-Type header value * @param customHeaders - Optional custom headers to merge * @returns Headers object */ protected buildHeaders(contentType: string, customHeaders?: HeadersInit): Headers; /** * Create an HTML Response. * @param body - Response body (string or ReadableStream) * @param ctx - Render context with status and headers * @returns Response object */ protected createHtmlResponse(body: BodyInit, ctx: RenderToResponseContext): Response; /** * Create an HttpError for unexpected render failures. * @param message - Error message * @param cause - Original error if available * @returns The original HttpError when the cause is already one, otherwise a 500 HttpError * @remarks HttpError from pages (for example NotFound from content lookup) must keep its * status so the request matcher can serve a 404 instead of a 500. */ protected createRenderError(message: string, cause?: unknown): HttpError; /** * Prepares dependencies for renderToResponse by resolving component dependencies * and configuring the HTML transformer. * @param view - The view component being rendered * @param layout - Optional layout component * @returns Resolved processed assets */ protected prepareViewDependencies(view: EcoComponent, layout?: EcoComponent): Promise; protected resolvePageBrowserGraphForFile(filePath: string): Promise; protected resolvePageBrowserGraphForRoute(filePath: string, routeOptions?: Pick, groupedBuildPlan?: GroupedGraphBuildPlan): Promise; /** * Merges component-scoped assets into the active HTML transformer state. * * Explicit page, layout, and document shell composition can produce assets at * each foreign subtree. This helper deduplicates those groups and folds them back into * the transformer so downstream HTML finalization sees one canonical asset set. * * @param assetGroups - Optional groups of processed assets to merge. * @returns The deduplicated asset subset contributed by this merge operation. */ protected appendProcessedDependencies(...assetGroups: Array): ProcessedAsset[]; /** * Resolves metadata for explicit view rendering. * * When a view declares a `metadata()` function, that contract owns the final * metadata for the explicit render. Otherwise the app-level default metadata is * reused so explicit routes and page-module routes share the same fallback. * * @param view - View component being rendered. * @param props - Props passed to the view. * @returns Resolved metadata for the final document shell. */ protected resolveViewMetadata

(view: EcoComponent

, props: P): Promise; /** * Renders one explicit view response in partial mode. * * Same-integration views can optionally stream or render inline via the caller's * `renderInline()` hook. Once a view may cross integration boundaries, this * helper routes the render through `renderComponentWithForeignChildren()` instead so mixed * shells can reuse the execution-scoped renderer cache and resolve nested * foreign ownership before the partial response is returned. * * @param input - View render options for the partial response. * @returns HTML response for the partial render. */ protected renderPartialViewResponse

(input: { view: EcoComponent

; props: P; ctx: RenderToResponseContext; renderInline?: () => Promise; transformHtml?: (html: string) => string; }): Promise; /** * Renders an explicit view through optional layout and document shells. * * This helper is the shared explicit-route path for string-oriented and mixed * integrations. It prepares view dependencies, resolves metadata, and composes * view, layout, and html template boundaries with one execution-scoped renderer * cache so repeated foreign shell delegation can reuse initialized renderers * during the same render flow. * * @param input - View, props, and optional layout metadata for the render. * @returns HTML response for the explicit view render. */ protected renderViewWithDocumentShell

(input: { view: EcoComponent

; props: P; ctx: RenderToResponseContext; layout?: EcoComponent; transformDocumentHtml?: (html: string) => string; }): Promise; /** * Renders a route page through optional layout and document shells. * * Route rendering and explicit view rendering now share the same renderer-owned * shell composition model. This helper composes page, layout, and html template * renders while threading one execution-scoped renderer cache through every * delegated foreign subtree so foreign shell ownership remains stable and renderer * initialization is reused inside the current request. * * @param input - Page, layout, document, and metadata inputs for the route render. * @returns Final serialized document HTML including the doctype prefix. */ protected renderPageWithDocumentShell(input: { page: { component: EcoComponent; props: Record; }; layout?: { component: EcoComponent; props?: Record; }; layouts?: DocumentShellLayoutInput[]; htmlTemplate: EcoComponent; metadata: PageMetadataProps; pageProps: Record; documentProps?: Record; transformDocumentHtml?: (html: string) => string; }): Promise; protected renderStringComponentWithSerializedChildren(input: ComponentRenderInput, component: (props: Record) => Promise | EcoPagesElement): Promise; /** * Renders a string-first component, then resolves any queued foreign * boundaries before returning final component HTML. */ protected renderStringComponentWithQueuedForeignSubtrees(input: ComponentRenderInput, component: (props: Record) => Promise | EcoPagesElement): Promise; constructor({ appConfig, assetProcessingService, resolvedIntegrationDependencies, rendererModules, runtimeOrigin, }: { appConfig: EcoPagesAppConfig; assetProcessingService: AssetProcessingService; resolvedIntegrationDependencies?: ProcessedAsset[]; rendererModules?: unknown; runtimeOrigin: string; }); /** * Returns the HTML template component. * It imports the HTML template from the specified path in the app configuration. * * @returns The HTML template component. */ protected getHtmlTemplate(): Promise>; protected normalizeImportedPageFile(_file: string, pageModule: TPageModule): TPageModule; /** * Imports the page file from the specified path. * * @param file - The file path to import. * @returns The imported module. */ protected importPageFile(file: string, options?: RouteModuleLoadOptions): Promise; /** * Resolves the dependency path based on the component directory. * It combines the component directory with the provided path URL. * * @param componentDir - The component directory path. * @param pathUrl - The path URL to resolve. * @returns The resolved dependency path. */ protected resolveDependencyPath(componentDir: string, pathUrl: string): string; /** * Collects the dependencies for the provided components. * Combines component-specific dependencies with global integration dependencies. * * @param components - The components to collect dependencies from. */ protected resolveDependencies(components: (EcoComponent | Partial)[]): Promise; /** * Processes component-specific dependencies WITHOUT prepending global integration dependencies. * Use this method when you need only the component's own assets. * * @param components - The components to collect dependencies from. */ protected processComponentDependencies(components: (EcoComponent | Partial)[]): Promise; /** * Builds the internal route-render adapter consumed by `RouteRenderOrchestrator`. * * The route orchestrator needs a narrow orchestration contract, but those hooks should * not become public API on the renderer base class. Keeping the adapter object * local to the execution path lets the orchestrator depend on one explicit seam while * subclasses continue to override protected renderer behavior directly. */ protected createRouteRenderOrchestratorAdapter(): RouteRenderOrchestratorAdapter; protected resolveRouteRenderInputs(routeOptions: RouteRendererOptions): Promise; protected buildPageBrowserGraphContributionContext(routeFile: string, routeOptions?: Pick): Promise; protected resolvePageDependencies(context: PageBrowserGraphContributionContext): Promise; protected resolvePageBrowserGraphContributionFromDependencies(dependencies: EcoComponentDependencies, ownerFile: string): Promise; protected resolveRouteDependencies(input: { components: (EcoComponent | Partial)[]; }): Promise<{ resolvedDependencies: ProcessedAsset[]; }>; protected renderRouteBody(renderOptions: IntegrationRendererRenderOptions): Promise; /** * Prepares the render options for the integration renderer. * It imports the page file, collects dependencies, and prepares the render options. * * @param options - The route renderer options. * @returns The prepared render options. */ protected prepareRenderOptions(options: RouteRendererOptions, adapter?: RouteRenderOrchestratorAdapter): Promise>; /** * Executes the integration renderer with the provided options. * * Execution flow: * 1. Build normalized render options (`prepareRenderOptions`). * 2. Render the route body once. * 3. Reject unresolved route-level eco-marker artifacts. * 4. Optionally apply document attributes for integration-owned document boundaries. * 5. Run HTML transformer with final dependency set. * * Stream-safety note: the first render result is normalized to a string once, * then the pipeline continues with that immutable HTML value to avoid disturbed * response-body errors. * * @param options Route renderer options. * @returns Rendered route body plus effective cache strategy. */ execute(options: RouteRendererOptions): Promise; /** * Returns document-level attributes to stamp onto the rendered `` tag. * * Integrations can override this to expose explicit document ownership or * other runtime coordination markers without relying on script sniffing. */ protected getDocumentAttributes(_renderOptions: IntegrationRendererRenderOptions): Record | undefined; protected applyAttributesToFirstBodyElement(html: string, attributes: Record): string; protected applyAttributesToHtmlElement(html: string, attributes: Record): string; /** * Returns declarative HTML fragments that core should inject into the final document. * * @remarks * Integrations may contribute document markup here, but core retains ownership * of the final HTML rewrite pipeline and placement semantics. This is the * supported document-markup extension point for integrations instead of custom * response finalization logic. */ protected getHtmlDocumentContributions(_options: HtmlDocumentContributionContext): HtmlDocumentContribution[] | undefined; /** * Abstract method to render the integration-specific component. * This method should be implemented by the specific integration renderer. * * @param options - The integration renderer render options. * @returns The rendered body. */ abstract render(options: IntegrationRendererRenderOptions): Promise; /** * Renders one component under this integration's foreign-child runtime and resolves * any nested foreign children captured during that render. * * Without this wrapper, a component tree with foreign-owned descendants would * render them with no active foreign-child runtime, which bypasses the owning * renderer's nested foreign-child handoff. */ renderComponentWithForeignChildren(input: ComponentRenderInput): Promise; protected finalizeIslandComponentRender(input: ComponentRenderInput, result: ComponentRenderResult): ComponentRenderResult; private normalizeComponentRenderOutput; protected normalizeUnresolvedMarkerArtifactHtml(html: string): string; /** * Returns whether the component dependency tree crosses into another * integration. * * This keeps foreign-child runtime setup narrow: same-integration trees can render * directly without paying the queue orchestration cost. */ protected hasForeignChildDescendants(component: EcoComponent): boolean; /** * Render a view directly to a Response object. * Used for explicit routing where views are rendered from route handlers. * * @param view - The eco.page component to render * @param props - Props to pass to the view * @param ctx - Render context with partial flag and response options * @returns A Response object with the rendered content */ abstract renderToResponse

>(view: EcoComponent

, props: P, ctx: RenderToResponseContext): Promise; /** * Render a single component and return structured output for orchestration paths. * * Default behavior delegates to `renderToResponse` in partial mode and wraps * the resulting HTML into the `ComponentRenderResult` contract. * * In foreign-subtree resolution, this method is the integration-owned step that turns an * already-resolved deferred foreign subtree into concrete HTML, assets, and optional * root attributes. * * Integrations can override this for richer behavior (asset emission, * root attributes, integration-specific hydration metadata). * * @param input Component render request. * @returns Structured render result used by component/page orchestration. */ renderComponent(input: ComponentRenderInput): Promise; /** * Extracts the first root element tag name from HTML output. * * @param html HTML fragment. * @returns Root tag name when present; otherwise `undefined`. */ protected getRootTagName(html: string): string | undefined; /** * Collects declarative Page Browser Graph contributions for one Page. * * @remarks * Integrations may describe page-scoped browser requirements here, while core * retains ownership of dependency processing and final graph assembly. This is * the supported page-browser extension point for integrations. * * @param context - The route file path and already imported page module. * @returns Declarative dependencies or pre-resolved assets for the Page. */ protected collectPageBrowserGraphContribution(_context: PageBrowserGraphContributionContext): Promise; /** * Creates the per-render foreign-child runtime adopted by the shared component * render context. * * The default runtime queues delegated foreign subtrees inside the owning * renderer so string and markup renderers do not need to re-declare the same * handoff boilerplate. Override only when a renderer needs custom runtime * context or a different foreign-child execution strategy. */ protected createForeignChildRuntime(options: { renderInput: ComponentRenderInput; rendererCache: Map>; }): ForeignChildRuntime; /** * Creates an explicit fail-fast runtime for tests or renderers that do not * support cross-integration foreign-child execution. */ protected createFailFastForeignChildRuntime(): ForeignChildRuntime; }