@ecopages/core 0.2.0-alpha.22 → 0.2.0-alpha.24

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 (515) hide show
  1. package/package.json +93 -226
  2. package/src/adapters/abstract/application-adapter.test.ts +172 -0
  3. package/src/adapters/abstract/application-adapter.ts +379 -0
  4. package/src/adapters/abstract/router-adapter.ts +30 -0
  5. package/src/adapters/abstract/server-adapter.ts +79 -0
  6. package/src/adapters/bun/client-bridge.ts +62 -0
  7. package/src/adapters/bun/create-app.ts +180 -0
  8. package/src/adapters/bun/hmr-manager.test.ts +267 -0
  9. package/src/adapters/bun/hmr-manager.ts +406 -0
  10. package/src/adapters/bun/index.ts +2 -0
  11. package/src/adapters/bun/server-adapter.ts +500 -0
  12. package/src/adapters/bun/server-lifecycle.ts +124 -0
  13. package/src/adapters/create-app.test.ts +10 -0
  14. package/src/adapters/create-app.ts +91 -0
  15. package/src/adapters/index.ts +2 -0
  16. package/src/adapters/node/create-app.test.ts +53 -0
  17. package/src/adapters/node/create-app.ts +183 -0
  18. package/src/adapters/node/node-client-bridge.test.ts +198 -0
  19. package/src/adapters/node/node-client-bridge.ts +79 -0
  20. package/src/adapters/node/node-hmr-manager.test.ts +322 -0
  21. package/src/adapters/node/node-hmr-manager.ts +378 -0
  22. package/src/adapters/node/server-adapter.ts +502 -0
  23. package/src/adapters/node/static-content-server.test.ts +60 -0
  24. package/src/adapters/node/static-content-server.ts +239 -0
  25. package/src/adapters/shared/api-response.test.ts +97 -0
  26. package/src/adapters/shared/api-response.ts +104 -0
  27. package/src/adapters/shared/application-adapter.ts +199 -0
  28. package/src/adapters/shared/define-api-handler.ts +66 -0
  29. package/src/adapters/shared/explicit-static-route-matcher.test.ts +381 -0
  30. package/src/adapters/shared/explicit-static-route-matcher.ts +140 -0
  31. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +90 -0
  32. package/src/adapters/shared/file-route-middleware-pipeline.ts +127 -0
  33. package/src/adapters/shared/fs-server-response-factory.test.ts +187 -0
  34. package/src/adapters/shared/fs-server-response-factory.ts +118 -0
  35. package/src/adapters/shared/fs-server-response-matcher.test.ts +285 -0
  36. package/src/adapters/shared/fs-server-response-matcher.ts +189 -0
  37. package/src/adapters/shared/hmr-entrypoint-registrar.ts +149 -0
  38. package/src/adapters/shared/hmr-html-response.ts +52 -0
  39. package/src/adapters/shared/hmr-manager.contract.test.ts +232 -0
  40. package/src/adapters/shared/hmr-manager.dispatch.test.ts +220 -0
  41. package/src/adapters/shared/render-context.test.ts +150 -0
  42. package/src/adapters/shared/render-context.ts +123 -0
  43. package/src/adapters/shared/runtime-bootstrap.ts +79 -0
  44. package/src/adapters/shared/server-adapter.test.ts +77 -0
  45. package/src/adapters/shared/server-adapter.ts +493 -0
  46. package/src/adapters/shared/server-route-handler.test.ts +110 -0
  47. package/src/adapters/shared/server-route-handler.ts +153 -0
  48. package/src/adapters/shared/server-static-builder.test.ts +338 -0
  49. package/src/adapters/shared/server-static-builder.ts +170 -0
  50. package/src/build/build-adapter-serialization.test.ts +281 -0
  51. package/src/build/build-adapter.test.ts +1240 -0
  52. package/src/build/build-adapter.ts +1012 -0
  53. package/src/build/build-manifest.ts +54 -0
  54. package/src/build/build-types.ts +83 -0
  55. package/src/build/dev-build-coordinator.ts +220 -0
  56. package/src/build/esbuild-build-adapter.ts +660 -0
  57. package/src/build/runtime-build-executor.test.ts +81 -0
  58. package/src/build/runtime-build-executor.ts +40 -0
  59. package/src/build/runtime-specifier-alias-plugin.test.ts +67 -0
  60. package/src/build/runtime-specifier-alias-plugin.ts +62 -0
  61. package/src/build/runtime-specifier-aliases.ts +135 -0
  62. package/src/config/config-builder.test.ts +443 -0
  63. package/src/config/config-builder.ts +742 -0
  64. package/src/config/config-builder.typecheck.test.ts +96 -0
  65. package/src/config/{constants.d.ts → constants.ts} +22 -13
  66. package/src/dev/sc-server.ts +143 -0
  67. package/src/eco/eco.browser.test.ts +43 -0
  68. package/src/eco/eco.browser.ts +118 -0
  69. package/src/eco/eco.test.ts +654 -0
  70. package/src/eco/eco.ts +205 -0
  71. package/src/eco/eco.types.ts +221 -0
  72. package/src/eco/eco.utils.test.ts +219 -0
  73. package/src/eco/eco.utils.ts +5 -0
  74. package/src/eco/global-injector-map.test.ts +42 -0
  75. package/src/eco/global-injector-map.ts +112 -0
  76. package/src/eco/lazy-injector-map.test.ts +66 -0
  77. package/src/eco/lazy-injector-map.ts +120 -0
  78. package/src/eco/module-dependencies.test.ts +30 -0
  79. package/src/eco/module-dependencies.ts +75 -0
  80. package/src/errors/http-error.test.ts +134 -0
  81. package/src/errors/http-error.ts +72 -0
  82. package/src/errors/{index.d.ts → index.ts} +2 -2
  83. package/src/errors/locals-access-error.ts +7 -0
  84. package/src/global/app-logger.ts +4 -0
  85. package/src/global/utils.test.ts +12 -0
  86. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-have-HMR-script-injected-in-page-1.png +0 -0
  87. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-load-fixture-app-page-1.png +0 -0
  88. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-WebSocket-Connection-should-connect-to-correct-HMR-endpoint-1.png +0 -0
  89. package/src/hmr/client/hmr-runtime.ts +160 -0
  90. package/src/hmr/hmr-strategy.test.ts +124 -0
  91. package/src/hmr/hmr-strategy.ts +177 -0
  92. package/src/hmr/hmr.postcss.test.e2e.ts +41 -0
  93. package/src/hmr/hmr.test.e2e.ts +66 -0
  94. package/src/hmr/strategies/default-hmr-strategy.ts +60 -0
  95. package/src/hmr/strategies/js-hmr-strategy.test.ts +335 -0
  96. package/src/hmr/strategies/js-hmr-strategy.ts +320 -0
  97. package/src/index.browser.ts +3 -0
  98. package/src/index.ts +15 -0
  99. package/src/integrations/ghtml/ghtml-renderer.test.ts +253 -0
  100. package/src/integrations/ghtml/ghtml-renderer.ts +97 -0
  101. package/src/integrations/ghtml/ghtml.constants.ts +1 -0
  102. package/src/integrations/ghtml/ghtml.plugin.ts +28 -0
  103. package/src/plugins/alias-resolver-plugin.test.ts +41 -0
  104. package/src/plugins/alias-resolver-plugin.ts +63 -0
  105. package/src/plugins/eco-component-meta-plugin.test.ts +406 -0
  106. package/src/plugins/eco-component-meta-plugin.ts +495 -0
  107. package/src/plugins/foreign-jsx-override-plugin.test.ts +65 -0
  108. package/src/plugins/foreign-jsx-override-plugin.ts +67 -0
  109. package/src/plugins/integration-plugin.test.ts +156 -0
  110. package/src/plugins/integration-plugin.ts +311 -0
  111. package/src/plugins/processor.test.ts +148 -0
  112. package/src/plugins/processor.ts +240 -0
  113. package/src/plugins/{runtime-capability.d.ts → runtime-capability.ts} +8 -3
  114. package/src/plugins/source-transform.test.ts +82 -0
  115. package/src/plugins/source-transform.ts +123 -0
  116. package/src/route-renderer/orchestration/boundary-planning.service.ts +146 -0
  117. package/src/route-renderer/orchestration/component-render-context.ts +318 -0
  118. package/src/route-renderer/orchestration/integration-renderer.test.ts +2088 -0
  119. package/src/route-renderer/orchestration/integration-renderer.ts +1285 -0
  120. package/src/route-renderer/orchestration/page-packaging.service.test.ts +76 -0
  121. package/src/route-renderer/orchestration/page-packaging.service.ts +85 -0
  122. package/src/route-renderer/orchestration/processed-asset-dedupe.ts +25 -0
  123. package/src/route-renderer/orchestration/queued-boundary-runtime.service.test.ts +319 -0
  124. package/src/route-renderer/orchestration/queued-boundary-runtime.service.ts +289 -0
  125. package/src/route-renderer/orchestration/render-execution.service.test.ts +196 -0
  126. package/src/route-renderer/orchestration/render-execution.service.ts +182 -0
  127. package/src/route-renderer/orchestration/render-output.utils.ts +302 -0
  128. package/src/route-renderer/orchestration/render-preparation.service.test.ts +569 -0
  129. package/src/route-renderer/orchestration/render-preparation.service.ts +508 -0
  130. package/src/route-renderer/orchestration/route-shell-composer.service.ts +162 -0
  131. package/src/route-renderer/orchestration/template-serialization.test.ts +110 -0
  132. package/src/route-renderer/orchestration/template-serialization.ts +117 -0
  133. package/src/route-renderer/page-loading/component-dependency-collection.ts +196 -0
  134. package/src/route-renderer/page-loading/declared-asset-collection.ts +156 -0
  135. package/src/route-renderer/page-loading/dependency-resolver.test.ts +665 -0
  136. package/src/route-renderer/page-loading/dependency-resolver.ts +150 -0
  137. package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +75 -0
  138. package/src/route-renderer/page-loading/lazy-entry-collection.ts +167 -0
  139. package/src/route-renderer/page-loading/lazy-trigger-planning.ts +74 -0
  140. package/src/route-renderer/page-loading/module-declaration-aggregation.ts +60 -0
  141. package/src/route-renderer/page-loading/module-declaration-scripts.ts +16 -0
  142. package/src/route-renderer/page-loading/page-dependency-bundling.ts +205 -0
  143. package/src/route-renderer/page-loading/page-module-loader.test.ts +183 -0
  144. package/src/route-renderer/page-loading/page-module-loader.ts +184 -0
  145. package/src/route-renderer/route-renderer.ts +136 -0
  146. package/src/router/client/link-intent.test.browser.ts +51 -0
  147. package/src/router/client/link-intent.ts +92 -0
  148. package/src/router/client/navigation-coordinator.test.ts +237 -0
  149. package/src/router/client/navigation-coordinator.ts +453 -0
  150. package/src/router/server/fs-router-scanner.test.ts +83 -0
  151. package/src/router/server/fs-router-scanner.ts +224 -0
  152. package/src/router/server/fs-router.test.ts +214 -0
  153. package/src/router/server/fs-router.ts +122 -0
  154. package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +66 -0
  155. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +476 -0
  156. package/src/services/assets/asset-processing-service/asset-processing.service.ts +345 -0
  157. package/src/services/assets/asset-processing-service/asset.factory.test.ts +63 -0
  158. package/src/services/assets/asset-processing-service/asset.factory.ts +105 -0
  159. package/src/services/assets/asset-processing-service/assets.types.ts +125 -0
  160. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.test.ts +74 -0
  161. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.ts +96 -0
  162. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.test.ts +67 -0
  163. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.ts +78 -0
  164. package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +104 -0
  165. package/src/services/assets/asset-processing-service/index.ts +5 -0
  166. package/src/services/assets/asset-processing-service/{processor.interface.d.ts → processor.interface.ts} +10 -5
  167. package/src/services/assets/asset-processing-service/processor.registry.ts +18 -0
  168. package/src/services/assets/asset-processing-service/processors/base/base-processor.test.ts +59 -0
  169. package/src/services/assets/asset-processing-service/processors/base/base-processor.ts +83 -0
  170. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +174 -0
  171. package/src/services/assets/asset-processing-service/processors/index.ts +5 -0
  172. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +192 -0
  173. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +134 -0
  174. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.test.ts +326 -0
  175. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +110 -0
  176. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.test.ts +227 -0
  177. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +87 -0
  178. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.test.ts +261 -0
  179. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +71 -0
  180. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +81 -0
  181. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +65 -0
  182. package/src/services/assets/browser-bundle.service.test.ts +66 -0
  183. package/src/services/assets/browser-bundle.service.ts +109 -0
  184. package/src/services/cache/cache.types.ts +126 -0
  185. package/src/services/cache/index.ts +18 -0
  186. package/src/services/cache/memory-cache-store.test.ts +225 -0
  187. package/src/services/cache/memory-cache-store.ts +130 -0
  188. package/src/services/cache/page-cache-service.test.ts +175 -0
  189. package/src/services/cache/page-cache-service.ts +202 -0
  190. package/src/services/cache/page-request-cache-coordinator.service.test.ts +79 -0
  191. package/src/services/cache/page-request-cache-coordinator.service.ts +131 -0
  192. package/src/services/html/html-rewriter-provider.service.test.ts +183 -0
  193. package/src/services/html/html-rewriter-provider.service.ts +104 -0
  194. package/src/services/html/html-transformer.service.test.ts +479 -0
  195. package/src/services/html/html-transformer.service.ts +275 -0
  196. package/src/services/invalidation/development-invalidation.service.test.ts +87 -0
  197. package/src/services/invalidation/development-invalidation.service.ts +262 -0
  198. package/src/services/module-loading/app-module-loader.service.ts +9 -0
  199. package/src/services/module-loading/app-server-module-transpiler.service.test.ts +130 -0
  200. package/src/services/module-loading/app-server-module-transpiler.service.ts +143 -0
  201. package/src/services/module-loading/host-module-loader-registry.ts +15 -0
  202. package/src/services/module-loading/{module-loading-types.d.ts → module-loading-types.ts} +1 -0
  203. package/src/services/module-loading/node-bootstrap-plugin.test.ts +335 -0
  204. package/src/services/module-loading/node-bootstrap-plugin.ts +297 -0
  205. package/src/services/module-loading/page-module-import.service.test.ts +504 -0
  206. package/src/services/module-loading/page-module-import.service.ts +252 -0
  207. package/src/services/module-loading/server-module-transpiler.service.test.ts +243 -0
  208. package/src/services/module-loading/server-module-transpiler.service.ts +104 -0
  209. package/src/services/module-loading/source-module-support.ts +19 -0
  210. package/src/services/runtime-state/dev-graph.service.ts +217 -0
  211. package/src/services/runtime-state/entrypoint-dependency-graph.service.ts +136 -0
  212. package/src/services/runtime-state/runtime-specifier-registry.service.ts +96 -0
  213. package/src/services/runtime-state/server-invalidation-state.service.ts +68 -0
  214. package/src/services/validation/schema-validation-service.test.ts +223 -0
  215. package/src/services/validation/schema-validation-service.ts +204 -0
  216. package/src/services/validation/{standard-schema.types.d.ts → standard-schema.types.ts} +20 -17
  217. package/src/static-site-generator/static-site-generator.test.ts +316 -0
  218. package/src/static-site-generator/static-site-generator.ts +462 -0
  219. package/src/types/internal-types.ts +242 -0
  220. package/src/types/public-types.ts +1443 -0
  221. package/src/utils/deep-merge.test.ts +114 -0
  222. package/src/utils/deep-merge.ts +47 -0
  223. package/src/utils/hash.ts +5 -0
  224. package/src/utils/html-escaping.ts +9 -0
  225. package/src/utils/invariant.test.ts +22 -0
  226. package/src/utils/invariant.ts +15 -0
  227. package/src/utils/locals-utils.ts +37 -0
  228. package/src/utils/parse-cli-args.test.ts +69 -0
  229. package/src/utils/parse-cli-args.ts +105 -0
  230. package/src/utils/path-utils.module.ts +14 -0
  231. package/src/utils/path-utils.test.ts +15 -0
  232. package/src/utils/resolve-work-dir.ts +45 -0
  233. package/src/utils/runtime.ts +44 -0
  234. package/src/utils/server-utils.module.ts +67 -0
  235. package/src/utils/server-utils.test.ts +38 -0
  236. package/src/watchers/project-watcher.integration.test.ts +337 -0
  237. package/src/watchers/project-watcher.test-helpers.ts +41 -0
  238. package/src/watchers/project-watcher.test.ts +768 -0
  239. package/src/watchers/project-watcher.ts +357 -0
  240. package/CHANGELOG.md +0 -51
  241. package/src/adapters/abstract/application-adapter.d.ts +0 -194
  242. package/src/adapters/abstract/application-adapter.js +0 -121
  243. package/src/adapters/abstract/router-adapter.d.ts +0 -26
  244. package/src/adapters/abstract/router-adapter.js +0 -5
  245. package/src/adapters/abstract/server-adapter.d.ts +0 -69
  246. package/src/adapters/abstract/server-adapter.js +0 -15
  247. package/src/adapters/bun/client-bridge.d.ts +0 -34
  248. package/src/adapters/bun/client-bridge.js +0 -48
  249. package/src/adapters/bun/create-app.d.ts +0 -52
  250. package/src/adapters/bun/create-app.js +0 -116
  251. package/src/adapters/bun/hmr-manager.d.ts +0 -143
  252. package/src/adapters/bun/hmr-manager.js +0 -333
  253. package/src/adapters/bun/index.d.ts +0 -2
  254. package/src/adapters/bun/index.js +0 -8
  255. package/src/adapters/bun/server-adapter.d.ts +0 -155
  256. package/src/adapters/bun/server-adapter.js +0 -374
  257. package/src/adapters/bun/server-lifecycle.d.ts +0 -63
  258. package/src/adapters/bun/server-lifecycle.js +0 -92
  259. package/src/adapters/create-app.d.ts +0 -20
  260. package/src/adapters/create-app.js +0 -66
  261. package/src/adapters/index.d.ts +0 -2
  262. package/src/adapters/index.js +0 -8
  263. package/src/adapters/node/create-app.d.ts +0 -18
  264. package/src/adapters/node/create-app.js +0 -149
  265. package/src/adapters/node/node-client-bridge.d.ts +0 -26
  266. package/src/adapters/node/node-client-bridge.js +0 -66
  267. package/src/adapters/node/node-hmr-manager.d.ts +0 -133
  268. package/src/adapters/node/node-hmr-manager.js +0 -311
  269. package/src/adapters/node/server-adapter.d.ts +0 -161
  270. package/src/adapters/node/server-adapter.js +0 -359
  271. package/src/adapters/node/static-content-server.d.ts +0 -60
  272. package/src/adapters/node/static-content-server.js +0 -194
  273. package/src/adapters/shared/api-response.d.ts +0 -52
  274. package/src/adapters/shared/api-response.js +0 -96
  275. package/src/adapters/shared/application-adapter.d.ts +0 -18
  276. package/src/adapters/shared/application-adapter.js +0 -90
  277. package/src/adapters/shared/define-api-handler.d.ts +0 -25
  278. package/src/adapters/shared/define-api-handler.js +0 -15
  279. package/src/adapters/shared/explicit-static-route-matcher.d.ts +0 -38
  280. package/src/adapters/shared/explicit-static-route-matcher.js +0 -103
  281. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +0 -65
  282. package/src/adapters/shared/file-route-middleware-pipeline.js +0 -99
  283. package/src/adapters/shared/fs-server-response-factory.d.ts +0 -19
  284. package/src/adapters/shared/fs-server-response-factory.js +0 -97
  285. package/src/adapters/shared/fs-server-response-matcher.d.ts +0 -67
  286. package/src/adapters/shared/fs-server-response-matcher.js +0 -147
  287. package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +0 -55
  288. package/src/adapters/shared/hmr-entrypoint-registrar.js +0 -87
  289. package/src/adapters/shared/hmr-html-response.d.ts +0 -22
  290. package/src/adapters/shared/hmr-html-response.js +0 -32
  291. package/src/adapters/shared/render-context.d.ts +0 -15
  292. package/src/adapters/shared/render-context.js +0 -72
  293. package/src/adapters/shared/runtime-bootstrap.d.ts +0 -38
  294. package/src/adapters/shared/runtime-bootstrap.js +0 -43
  295. package/src/adapters/shared/server-adapter.d.ts +0 -97
  296. package/src/adapters/shared/server-adapter.js +0 -390
  297. package/src/adapters/shared/server-route-handler.d.ts +0 -89
  298. package/src/adapters/shared/server-route-handler.js +0 -111
  299. package/src/adapters/shared/server-static-builder.d.ts +0 -70
  300. package/src/adapters/shared/server-static-builder.js +0 -100
  301. package/src/build/build-adapter.d.ts +0 -239
  302. package/src/build/build-adapter.js +0 -642
  303. package/src/build/build-manifest.d.ts +0 -27
  304. package/src/build/build-manifest.js +0 -30
  305. package/src/build/build-types.d.ts +0 -57
  306. package/src/build/build-types.js +0 -0
  307. package/src/build/dev-build-coordinator.d.ts +0 -72
  308. package/src/build/dev-build-coordinator.js +0 -154
  309. package/src/build/esbuild-build-adapter.d.ts +0 -78
  310. package/src/build/esbuild-build-adapter.js +0 -505
  311. package/src/build/runtime-build-executor.d.ts +0 -14
  312. package/src/build/runtime-build-executor.js +0 -22
  313. package/src/build/runtime-specifier-alias-plugin.d.ts +0 -15
  314. package/src/build/runtime-specifier-alias-plugin.js +0 -35
  315. package/src/build/runtime-specifier-aliases.d.ts +0 -5
  316. package/src/build/runtime-specifier-aliases.js +0 -95
  317. package/src/config/config-builder.d.ts +0 -252
  318. package/src/config/config-builder.js +0 -603
  319. package/src/config/constants.js +0 -25
  320. package/src/dev/sc-server.d.ts +0 -30
  321. package/src/dev/sc-server.js +0 -111
  322. package/src/eco/eco.browser.d.ts +0 -2
  323. package/src/eco/eco.browser.js +0 -83
  324. package/src/eco/eco.d.ts +0 -9
  325. package/src/eco/eco.js +0 -85
  326. package/src/eco/eco.types.d.ts +0 -178
  327. package/src/eco/eco.types.js +0 -0
  328. package/src/eco/eco.utils.d.ts +0 -1
  329. package/src/eco/eco.utils.js +0 -10
  330. package/src/eco/global-injector-map.d.ts +0 -16
  331. package/src/eco/global-injector-map.js +0 -80
  332. package/src/eco/lazy-injector-map.d.ts +0 -8
  333. package/src/eco/lazy-injector-map.js +0 -70
  334. package/src/eco/module-dependencies.d.ts +0 -18
  335. package/src/eco/module-dependencies.js +0 -49
  336. package/src/errors/http-error.d.ts +0 -31
  337. package/src/errors/http-error.js +0 -50
  338. package/src/errors/index.js +0 -4
  339. package/src/errors/locals-access-error.d.ts +0 -4
  340. package/src/errors/locals-access-error.js +0 -9
  341. package/src/global/app-logger.d.ts +0 -2
  342. package/src/global/app-logger.js +0 -6
  343. package/src/hmr/client/hmr-runtime.d.ts +0 -5
  344. package/src/hmr/client/hmr-runtime.js +0 -109
  345. package/src/hmr/hmr-strategy.d.ts +0 -162
  346. package/src/hmr/hmr-strategy.js +0 -44
  347. package/src/hmr/hmr.postcss.test.e2e.d.ts +0 -1
  348. package/src/hmr/hmr.postcss.test.e2e.js +0 -31
  349. package/src/hmr/hmr.test.e2e.d.ts +0 -1
  350. package/src/hmr/hmr.test.e2e.js +0 -43
  351. package/src/hmr/strategies/default-hmr-strategy.d.ts +0 -43
  352. package/src/hmr/strategies/default-hmr-strategy.js +0 -34
  353. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -139
  354. package/src/hmr/strategies/js-hmr-strategy.js +0 -178
  355. package/src/index.browser.d.ts +0 -3
  356. package/src/index.browser.js +0 -4
  357. package/src/index.d.ts +0 -6
  358. package/src/index.js +0 -21
  359. package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -20
  360. package/src/integrations/ghtml/ghtml-renderer.js +0 -63
  361. package/src/integrations/ghtml/ghtml.constants.d.ts +0 -1
  362. package/src/integrations/ghtml/ghtml.constants.js +0 -4
  363. package/src/integrations/ghtml/ghtml.plugin.d.ts +0 -16
  364. package/src/integrations/ghtml/ghtml.plugin.js +0 -20
  365. package/src/plugins/alias-resolver-plugin.d.ts +0 -2
  366. package/src/plugins/alias-resolver-plugin.js +0 -53
  367. package/src/plugins/eco-component-meta-plugin.d.ts +0 -108
  368. package/src/plugins/eco-component-meta-plugin.js +0 -163
  369. package/src/plugins/foreign-jsx-override-plugin.d.ts +0 -31
  370. package/src/plugins/foreign-jsx-override-plugin.js +0 -35
  371. package/src/plugins/integration-plugin.d.ts +0 -219
  372. package/src/plugins/integration-plugin.js +0 -196
  373. package/src/plugins/processor.d.ts +0 -95
  374. package/src/plugins/processor.js +0 -136
  375. package/src/plugins/runtime-capability.js +0 -0
  376. package/src/plugins/source-transform.d.ts +0 -46
  377. package/src/plugins/source-transform.js +0 -71
  378. package/src/route-renderer/orchestration/boundary-planning.service.d.ts +0 -25
  379. package/src/route-renderer/orchestration/boundary-planning.service.js +0 -97
  380. package/src/route-renderer/orchestration/component-render-context.d.ts +0 -83
  381. package/src/route-renderer/orchestration/component-render-context.js +0 -147
  382. package/src/route-renderer/orchestration/integration-renderer.d.ts +0 -554
  383. package/src/route-renderer/orchestration/integration-renderer.js +0 -957
  384. package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +0 -89
  385. package/src/route-renderer/orchestration/queued-boundary-runtime.service.js +0 -155
  386. package/src/route-renderer/orchestration/render-execution.service.d.ts +0 -43
  387. package/src/route-renderer/orchestration/render-execution.service.js +0 -106
  388. package/src/route-renderer/orchestration/render-output.utils.d.ts +0 -46
  389. package/src/route-renderer/orchestration/render-output.utils.js +0 -65
  390. package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -120
  391. package/src/route-renderer/orchestration/render-preparation.service.js +0 -341
  392. package/src/route-renderer/orchestration/route-shell-composer.service.d.ts +0 -50
  393. package/src/route-renderer/orchestration/route-shell-composer.service.js +0 -81
  394. package/src/route-renderer/orchestration/template-serialization.d.ts +0 -38
  395. package/src/route-renderer/orchestration/template-serialization.js +0 -45
  396. package/src/route-renderer/page-loading/dependency-resolver.d.ts +0 -35
  397. package/src/route-renderer/page-loading/dependency-resolver.js +0 -444
  398. package/src/route-renderer/page-loading/page-module-loader.d.ts +0 -90
  399. package/src/route-renderer/page-loading/page-module-loader.js +0 -127
  400. package/src/route-renderer/route-renderer.d.ts +0 -67
  401. package/src/route-renderer/route-renderer.js +0 -103
  402. package/src/router/client/link-intent.js +0 -34
  403. package/src/router/client/link-intent.test.browser.d.ts +0 -1
  404. package/src/router/client/link-intent.test.browser.js +0 -43
  405. package/src/router/client/navigation-coordinator.d.ts +0 -149
  406. package/src/router/client/navigation-coordinator.js +0 -215
  407. package/src/router/server/fs-router-scanner.d.ts +0 -41
  408. package/src/router/server/fs-router-scanner.js +0 -161
  409. package/src/router/server/fs-router.d.ts +0 -26
  410. package/src/router/server/fs-router.js +0 -100
  411. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +0 -120
  412. package/src/services/assets/asset-processing-service/asset-processing.service.js +0 -331
  413. package/src/services/assets/asset-processing-service/asset.factory.d.ts +0 -17
  414. package/src/services/assets/asset-processing-service/asset.factory.js +0 -82
  415. package/src/services/assets/asset-processing-service/assets.types.d.ts +0 -89
  416. package/src/services/assets/asset-processing-service/assets.types.js +0 -0
  417. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +0 -55
  418. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +0 -48
  419. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +0 -20
  420. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +0 -41
  421. package/src/services/assets/asset-processing-service/index.d.ts +0 -5
  422. package/src/services/assets/asset-processing-service/index.js +0 -5
  423. package/src/services/assets/asset-processing-service/processor.interface.js +0 -6
  424. package/src/services/assets/asset-processing-service/processor.registry.d.ts +0 -8
  425. package/src/services/assets/asset-processing-service/processor.registry.js +0 -15
  426. package/src/services/assets/asset-processing-service/processors/base/base-processor.d.ts +0 -24
  427. package/src/services/assets/asset-processing-service/processors/base/base-processor.js +0 -64
  428. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.d.ts +0 -17
  429. package/src/services/assets/asset-processing-service/processors/base/base-script-processor.js +0 -72
  430. package/src/services/assets/asset-processing-service/processors/index.d.ts +0 -5
  431. package/src/services/assets/asset-processing-service/processors/index.js +0 -5
  432. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +0 -5
  433. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +0 -57
  434. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts +0 -9
  435. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +0 -88
  436. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.d.ts +0 -7
  437. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +0 -75
  438. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +0 -5
  439. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +0 -25
  440. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +0 -9
  441. package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +0 -66
  442. package/src/services/assets/browser-bundle.service.d.ts +0 -32
  443. package/src/services/assets/browser-bundle.service.js +0 -33
  444. package/src/services/cache/cache.types.d.ts +0 -107
  445. package/src/services/cache/cache.types.js +0 -0
  446. package/src/services/cache/index.d.ts +0 -7
  447. package/src/services/cache/index.js +0 -7
  448. package/src/services/cache/memory-cache-store.d.ts +0 -42
  449. package/src/services/cache/memory-cache-store.js +0 -98
  450. package/src/services/cache/page-cache-service.d.ts +0 -70
  451. package/src/services/cache/page-cache-service.js +0 -152
  452. package/src/services/cache/page-request-cache-coordinator.service.d.ts +0 -75
  453. package/src/services/cache/page-request-cache-coordinator.service.js +0 -109
  454. package/src/services/html/html-rewriter-provider.service.d.ts +0 -37
  455. package/src/services/html/html-rewriter-provider.service.js +0 -68
  456. package/src/services/html/html-transformer.service.d.ts +0 -77
  457. package/src/services/html/html-transformer.service.js +0 -215
  458. package/src/services/invalidation/development-invalidation.service.d.ts +0 -74
  459. package/src/services/invalidation/development-invalidation.service.js +0 -190
  460. package/src/services/module-loading/app-module-loader.service.d.ts +0 -28
  461. package/src/services/module-loading/app-module-loader.service.js +0 -35
  462. package/src/services/module-loading/app-server-module-transpiler.service.d.ts +0 -24
  463. package/src/services/module-loading/app-server-module-transpiler.service.js +0 -109
  464. package/src/services/module-loading/host-module-loader-registry.d.ts +0 -4
  465. package/src/services/module-loading/host-module-loader-registry.js +0 -15
  466. package/src/services/module-loading/module-loading-types.js +0 -0
  467. package/src/services/module-loading/node-bootstrap-plugin.d.ts +0 -42
  468. package/src/services/module-loading/node-bootstrap-plugin.js +0 -204
  469. package/src/services/module-loading/page-module-import.service.d.ts +0 -76
  470. package/src/services/module-loading/page-module-import.service.js +0 -173
  471. package/src/services/module-loading/server-module-transpiler.service.d.ts +0 -72
  472. package/src/services/module-loading/server-module-transpiler.service.js +0 -64
  473. package/src/services/runtime-state/dev-graph.service.d.ts +0 -118
  474. package/src/services/runtime-state/dev-graph.service.js +0 -162
  475. package/src/services/runtime-state/entrypoint-dependency-graph.service.d.ts +0 -41
  476. package/src/services/runtime-state/entrypoint-dependency-graph.service.js +0 -85
  477. package/src/services/runtime-state/runtime-specifier-registry.service.d.ts +0 -69
  478. package/src/services/runtime-state/runtime-specifier-registry.service.js +0 -37
  479. package/src/services/runtime-state/server-invalidation-state.service.d.ts +0 -26
  480. package/src/services/runtime-state/server-invalidation-state.service.js +0 -35
  481. package/src/services/validation/schema-validation-service.d.ts +0 -122
  482. package/src/services/validation/schema-validation-service.js +0 -101
  483. package/src/services/validation/standard-schema.types.js +0 -0
  484. package/src/static-site-generator/static-site-generator.d.ts +0 -104
  485. package/src/static-site-generator/static-site-generator.js +0 -338
  486. package/src/types/internal-types.d.ts +0 -231
  487. package/src/types/internal-types.js +0 -0
  488. package/src/types/public-types.d.ts +0 -1219
  489. package/src/types/public-types.js +0 -0
  490. package/src/utils/deep-merge.d.ts +0 -14
  491. package/src/utils/deep-merge.js +0 -32
  492. package/src/utils/hash.d.ts +0 -1
  493. package/src/utils/hash.js +0 -7
  494. package/src/utils/html-escaping.d.ts +0 -7
  495. package/src/utils/html-escaping.js +0 -6
  496. package/src/utils/html.js +0 -4
  497. package/src/utils/invariant.d.ts +0 -5
  498. package/src/utils/invariant.js +0 -11
  499. package/src/utils/locals-utils.d.ts +0 -15
  500. package/src/utils/locals-utils.js +0 -24
  501. package/src/utils/parse-cli-args.d.ts +0 -27
  502. package/src/utils/parse-cli-args.js +0 -62
  503. package/src/utils/path-utils.module.d.ts +0 -5
  504. package/src/utils/path-utils.module.js +0 -14
  505. package/src/utils/resolve-work-dir.d.ts +0 -11
  506. package/src/utils/resolve-work-dir.js +0 -31
  507. package/src/utils/runtime.d.ts +0 -11
  508. package/src/utils/runtime.js +0 -40
  509. package/src/utils/server-utils.module.d.ts +0 -19
  510. package/src/utils/server-utils.module.js +0 -56
  511. package/src/watchers/project-watcher.d.ts +0 -136
  512. package/src/watchers/project-watcher.js +0 -275
  513. package/src/watchers/project-watcher.test-helpers.d.ts +0 -4
  514. package/src/watchers/project-watcher.test-helpers.js +0 -52
  515. /package/src/utils/{html.d.ts → html.ts} +0 -0
@@ -0,0 +1,1285 @@
1
+ /**
2
+ * This module contains the abstract class for the Integration Renderer
3
+ * Every integration renderer should extend this class
4
+ * @module
5
+ */
6
+
7
+ import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.ts';
8
+ import type {
9
+ ComponentRenderInput,
10
+ ComponentRenderResult,
11
+ BoundaryRenderPayload,
12
+ EcoComponent,
13
+ EcoComponentDependencies,
14
+ EcoFunctionComponent,
15
+ EcoPageComponent,
16
+ EcoPageFile,
17
+ EcoPagesElement,
18
+ GetMetadata,
19
+ GetMetadataContext,
20
+ GetStaticProps,
21
+ BaseIntegrationContext,
22
+ HtmlTemplateProps,
23
+ IntegrationRendererRenderOptions,
24
+ PageMetadataProps,
25
+ RouteRendererBody,
26
+ RouteRendererOptions,
27
+ RouteRenderResult,
28
+ } from '../../types/public-types.ts';
29
+ import {
30
+ type AssetProcessingService,
31
+ type ProcessedAsset,
32
+ } from '../../services/assets/asset-processing-service/index.ts';
33
+ import { HtmlTransformerService } from '../../services/html/html-transformer.service.ts';
34
+ import { invariant } from '../../utils/invariant.ts';
35
+ import { HttpError } from '../../errors/http-error.ts';
36
+ import { DependencyResolverService } from '../page-loading/dependency-resolver.ts';
37
+ import { PageModuleLoaderService } from '../page-loading/page-module-loader.ts';
38
+ import { PagePackagingService } from './page-packaging.service.ts';
39
+ import { RenderExecutionService } from './render-execution.service.ts';
40
+ import { RenderPreparationService } from './render-preparation.service.ts';
41
+ import { RouteShellComposer } from './route-shell-composer.service.ts';
42
+ import type { ComponentBoundaryRuntime } from './component-render-context.ts';
43
+ import { normalizeBoundaryArtifactHtml } from './render-output.utils.ts';
44
+ import { getComponentRenderContext, runWithComponentRenderContext } from './component-render-context.ts';
45
+ import {
46
+ QueuedBoundaryRuntimeService,
47
+ type QueuedBoundaryResolution,
48
+ type QueuedBoundaryRuntimeContext,
49
+ } from './queued-boundary-runtime.service.ts';
50
+
51
+ type BoundaryRenderDecisionInput = {
52
+ currentIntegration: string;
53
+ targetIntegration?: string;
54
+ };
55
+
56
+ /**
57
+ * Controls how one route module is loaded outside the normal render path.
58
+ *
59
+ * Request-time metadata inspection and static-generation probes use these
60
+ * options to isolate their module identity from the main render cache while
61
+ * still going through the owning integration's import setup.
62
+ */
63
+ export type RouteModuleLoadOptions = {
64
+ bypassCache?: boolean;
65
+ cacheScope?: string;
66
+ };
67
+
68
+ /**
69
+ * Context for renderToResponse method.
70
+ */
71
+ export interface RenderToResponseContext {
72
+ partial?: boolean;
73
+ status?: number;
74
+ headers?: HeadersInit;
75
+ }
76
+
77
+ /**
78
+ * The IntegrationRenderer class is an abstract class that provides a base for rendering integration-specific components in the EcoPages framework.
79
+ * It handles the import of page files, collection of dependencies, and preparation of render options.
80
+ * The class is designed to be extended by specific integration renderers.
81
+ */
82
+ export abstract class IntegrationRenderer<C = EcoPagesElement> {
83
+ abstract name: string;
84
+ protected appConfig: EcoPagesAppConfig;
85
+ protected assetProcessingService: AssetProcessingService;
86
+ protected htmlTransformer: HtmlTransformerService;
87
+ protected hmrManager?: IHmrManager;
88
+ protected resolvedIntegrationDependencies: ProcessedAsset[] = [];
89
+ protected rendererModules?: unknown;
90
+ declare protected options: Required<IntegrationRendererRenderOptions>;
91
+ protected runtimeOrigin: string;
92
+ protected dependencyResolverService: DependencyResolverService;
93
+ protected pageModuleLoaderService: PageModuleLoaderService;
94
+ protected renderPreparationService: RenderPreparationService;
95
+ protected renderExecutionService: RenderExecutionService;
96
+ protected pagePackagingService: PagePackagingService;
97
+ protected readonly routeShellComposer = new RouteShellComposer();
98
+ protected readonly queuedBoundaryRuntimeService = new QueuedBoundaryRuntimeService();
99
+
100
+ protected DOC_TYPE = '<!DOCTYPE html>';
101
+
102
+ /**
103
+ * Loads one route module through the owning renderer's import path.
104
+ *
105
+ * Request-time infrastructure may need page metadata such as cache strategy or
106
+ * middleware before full rendering starts. Exposing this narrow entrypoint lets
107
+ * those callers reuse integration-specific import setup instead of bypassing it
108
+ * with raw transpiler access.
109
+ */
110
+ public async loadPageModule(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile> {
111
+ return this.importPageFile(file, options);
112
+ }
113
+
114
+ /**
115
+ * Reads the execution-scoped foreign renderer cache from one boundary input.
116
+ *
117
+ * Shared page/layout/document shell helpers pass one cache through
118
+ * `integrationContext` so repeated delegation to the same foreign integration
119
+ * can reuse a single initialized renderer instance during one render flow.
120
+ * The cache is deliberately scoped to the current render execution rather than
121
+ * stored on the renderer, which avoids leaking mutable integration state across
122
+ * requests while still preventing redundant renderer initialization.
123
+ *
124
+ * @param integrationContext - Optional boundary context carried with one render input.
125
+ * @returns The current execution cache when present.
126
+ */
127
+ private getBoundaryRendererCache(
128
+ integrationContext?: BaseIntegrationContext,
129
+ ): Map<string, IntegrationRenderer<any>> | undefined {
130
+ if (integrationContext?.rendererCache instanceof Map) {
131
+ return integrationContext.rendererCache as Map<string, IntegrationRenderer<any>>;
132
+ }
133
+
134
+ return undefined;
135
+ }
136
+
137
+ private getRegisteredBoundaryOwner(component: EcoComponent): string | undefined {
138
+ const integrationName = component.config?.integration ?? component.config?.__eco?.integration;
139
+ if (!integrationName || integrationName === this.name) {
140
+ return undefined;
141
+ }
142
+
143
+ return this.appConfig.integrations.some((integration) => integration.name === integrationName)
144
+ ? integrationName
145
+ : undefined;
146
+ }
147
+
148
+ /**
149
+ * Attaches an execution-scoped foreign renderer cache to one boundary input.
150
+ *
151
+ * Foreign-owned page, layout, or document shells may delegate several times in
152
+ * the same render flow. Threading the cache through `integrationContext`
153
+ * preserves renderer reuse without changing the public boundary input contract.
154
+ * Existing integration-specific context is preserved and augmented.
155
+ *
156
+ * @param input - Original boundary render input.
157
+ * @param rendererCache - Execution-scoped renderer cache to propagate.
158
+ * @returns Boundary input augmented with the shared renderer cache.
159
+ */
160
+ private withBoundaryRendererCache(
161
+ input: ComponentRenderInput,
162
+ rendererCache: Map<string, IntegrationRenderer<any>>,
163
+ ): ComponentRenderInput {
164
+ const integrationContext = input.integrationContext;
165
+ const sharedRendererCache = rendererCache as BaseIntegrationContext['rendererCache'];
166
+
167
+ return {
168
+ ...input,
169
+ integrationContext: integrationContext
170
+ ? { ...integrationContext, rendererCache: sharedRendererCache }
171
+ : { rendererCache: sharedRendererCache },
172
+ };
173
+ }
174
+
175
+ protected getRendererModuleValue(key: string): unknown {
176
+ if (!this.rendererModules || typeof this.rendererModules !== 'object') {
177
+ return undefined;
178
+ }
179
+
180
+ return (this.rendererModules as Record<string, unknown>)[key];
181
+ }
182
+
183
+ protected getRendererModuleString(key: string): string | undefined {
184
+ const value = this.getRendererModuleValue(key);
185
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
186
+ }
187
+
188
+ protected getRendererBootstrapDependencies(partial = false): ProcessedAsset[] {
189
+ if (partial) {
190
+ return [];
191
+ }
192
+
193
+ const islandClientModuleId = this.getRendererModuleString('islandClientModuleId');
194
+ if (!islandClientModuleId) {
195
+ return [];
196
+ }
197
+
198
+ return [
199
+ {
200
+ attributes: {
201
+ crossorigin: 'anonymous',
202
+ 'data-ecopages-runtime': 'islands',
203
+ type: 'module',
204
+ },
205
+ content: `import ${JSON.stringify(islandClientModuleId)};`,
206
+ inline: true,
207
+ kind: 'script',
208
+ packageRole: 'keep-separate',
209
+ position: 'body',
210
+ },
211
+ ];
212
+ }
213
+
214
+ public setHmrManager(hmrManager: IHmrManager) {
215
+ this.hmrManager = hmrManager;
216
+ if (this.assetProcessingService) {
217
+ this.assetProcessingService.setHmrManager(hmrManager);
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Build response headers with optional custom headers.
223
+ * @param contentType - The Content-Type header value
224
+ * @param customHeaders - Optional custom headers to merge
225
+ * @returns Headers object
226
+ */
227
+ protected buildHeaders(contentType: string, customHeaders?: HeadersInit): Headers {
228
+ const headers = new Headers({ 'Content-Type': contentType });
229
+ if (customHeaders) {
230
+ const incoming = new Headers(customHeaders);
231
+ incoming.forEach((value, key) => headers.set(key, value));
232
+ }
233
+ return headers;
234
+ }
235
+
236
+ /**
237
+ * Create an HTML Response.
238
+ * @param body - Response body (string or ReadableStream)
239
+ * @param ctx - Render context with status and headers
240
+ * @returns Response object
241
+ */
242
+ protected createHtmlResponse(body: BodyInit, ctx: RenderToResponseContext): Response {
243
+ return new Response(body, {
244
+ status: ctx.status ?? 200,
245
+ headers: this.buildHeaders('text/html; charset=utf-8', ctx.headers),
246
+ });
247
+ }
248
+
249
+ /**
250
+ * Create an HttpError for render failures.
251
+ * @param message - Error message
252
+ * @param cause - Original error if available
253
+ * @returns HttpError with 500 status
254
+ */
255
+ protected createRenderError(message: string, cause?: unknown): HttpError {
256
+ const errorMessage = cause instanceof Error ? `${message}: ${cause.message}` : message;
257
+ return HttpError.InternalServerError(errorMessage);
258
+ }
259
+
260
+ /**
261
+ * Prepares dependencies for renderToResponse by resolving component dependencies
262
+ * and configuring the HTML transformer.
263
+ * @param view - The view component being rendered
264
+ * @param layout - Optional layout component
265
+ * @returns Resolved processed assets
266
+ */
267
+ protected async prepareViewDependencies(view: EcoComponent, layout?: EcoComponent): Promise<ProcessedAsset[]> {
268
+ const HtmlTemplate = await this.getHtmlTemplate();
269
+ const componentsToResolve = layout ? [HtmlTemplate, layout, view] : [HtmlTemplate, view];
270
+ const resolvedDependencies = this.htmlTransformer.dedupeProcessedAssets(
271
+ await this.resolveDependencies(componentsToResolve),
272
+ );
273
+ this.htmlTransformer.setPagePackage(this.pagePackagingService.createPagePackage(resolvedDependencies));
274
+ return resolvedDependencies;
275
+ }
276
+
277
+ /**
278
+ * Merges component-scoped assets into the active HTML transformer state.
279
+ *
280
+ * Explicit page, layout, and document shell composition can produce assets at
281
+ * each boundary. This helper deduplicates those groups and folds them back into
282
+ * the transformer so downstream HTML finalization sees one canonical asset set.
283
+ *
284
+ * @param assetGroups - Optional groups of processed assets to merge.
285
+ * @returns The deduplicated asset subset contributed by this merge operation.
286
+ */
287
+ protected appendProcessedDependencies(
288
+ ...assetGroups: Array<readonly ProcessedAsset[] | undefined>
289
+ ): ProcessedAsset[] {
290
+ const nextDependencies = this.htmlTransformer.dedupeProcessedAssets(
291
+ assetGroups.flatMap((assets) => assets ?? []),
292
+ );
293
+
294
+ if (nextDependencies.length === 0) {
295
+ return nextDependencies;
296
+ }
297
+
298
+ const mergedDependencies = this.htmlTransformer.dedupeProcessedAssets([
299
+ ...this.htmlTransformer.getProcessedDependencies(),
300
+ ...nextDependencies,
301
+ ]);
302
+
303
+ this.htmlTransformer.setPagePackage(this.pagePackagingService.createPagePackage(mergedDependencies));
304
+
305
+ return nextDependencies;
306
+ }
307
+
308
+ /**
309
+ * Resolves metadata for explicit view rendering.
310
+ *
311
+ * When a view declares a `metadata()` function, that contract owns the final
312
+ * metadata for the explicit render. Otherwise the app-level default metadata is
313
+ * reused so explicit routes and page-module routes share the same fallback.
314
+ *
315
+ * @param view - View component being rendered.
316
+ * @param props - Props passed to the view.
317
+ * @returns Resolved metadata for the final document shell.
318
+ */
319
+ protected async resolveViewMetadata<P>(view: EcoComponent<P>, props: P): Promise<PageMetadataProps> {
320
+ return view.metadata
321
+ ? await view.metadata({
322
+ params: {},
323
+ query: {},
324
+ props,
325
+ appConfig: this.appConfig,
326
+ })
327
+ : this.appConfig.defaultMetadata;
328
+ }
329
+
330
+ /**
331
+ * Renders one explicit view response in partial mode.
332
+ *
333
+ * Same-integration views can optionally stream or render inline via the caller's
334
+ * `renderInline()` hook. Once a view may cross integration boundaries, this
335
+ * helper routes the render through `renderComponentBoundary()` instead so mixed
336
+ * shells can reuse the execution-scoped renderer cache and resolve nested
337
+ * foreign ownership before the partial response is returned.
338
+ *
339
+ * @param input - View render options for the partial response.
340
+ * @returns HTML response for the partial render.
341
+ */
342
+ protected async renderPartialViewResponse<P>(input: {
343
+ view: EcoComponent<P>;
344
+ props: P;
345
+ ctx: RenderToResponseContext;
346
+ renderInline?: () => Promise<BodyInit>;
347
+ transformHtml?: (html: string) => string;
348
+ }): Promise<Response> {
349
+ return this.routeShellComposer.renderPartialViewResponse(input, {
350
+ hasForeignBoundaryDescendants: (component) => this.hasForeignBoundaryDescendants(component),
351
+ createHtmlResponse: (body, ctx) => this.createHtmlResponse(body, ctx),
352
+ renderComponentBoundary: (boundaryInput) => this.renderComponentBoundary(boundaryInput),
353
+ prepareViewDependencies: (view, layout) => this.prepareViewDependencies(view, layout),
354
+ getHtmlTemplate: () => this.getHtmlTemplate(),
355
+ resolveViewMetadata: (view, props) => this.resolveViewMetadata(view, props),
356
+ appendProcessedDependencies: (...assetGroups) => this.appendProcessedDependencies(...assetGroups),
357
+ finalizeResolvedHtml: (options) => this.finalizeResolvedHtml(options),
358
+ docType: this.DOC_TYPE,
359
+ });
360
+ }
361
+
362
+ /**
363
+ * Renders an explicit view through optional layout and document shells.
364
+ *
365
+ * This helper is the shared explicit-route path for string-oriented and mixed
366
+ * integrations. It prepares view dependencies, resolves metadata, and composes
367
+ * view, layout, and html template boundaries with one execution-scoped renderer
368
+ * cache so repeated foreign shell delegation can reuse initialized renderers
369
+ * during the same render flow.
370
+ *
371
+ * @param input - View, props, and optional layout metadata for the render.
372
+ * @returns HTML response for the explicit view render.
373
+ */
374
+ protected async renderViewWithDocumentShell<P>(input: {
375
+ view: EcoComponent<P>;
376
+ props: P;
377
+ ctx: RenderToResponseContext;
378
+ layout?: EcoComponent;
379
+ }): Promise<Response> {
380
+ return this.routeShellComposer.renderViewWithDocumentShell(input, {
381
+ hasForeignBoundaryDescendants: (component) => this.hasForeignBoundaryDescendants(component),
382
+ createHtmlResponse: (body, ctx) => this.createHtmlResponse(body, ctx),
383
+ renderComponentBoundary: (boundaryInput) => this.renderComponentBoundary(boundaryInput),
384
+ prepareViewDependencies: (view, layout) => this.prepareViewDependencies(view, layout),
385
+ getHtmlTemplate: () => this.getHtmlTemplate(),
386
+ resolveViewMetadata: (view, props) => this.resolveViewMetadata(view, props),
387
+ appendProcessedDependencies: (...assetGroups) => this.appendProcessedDependencies(...assetGroups),
388
+ finalizeResolvedHtml: (options) => this.finalizeResolvedHtml(options),
389
+ docType: this.DOC_TYPE,
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Renders a route page through optional layout and document shells.
395
+ *
396
+ * Route rendering and explicit view rendering now share the same boundary-owned
397
+ * shell composition model. This helper composes page, layout, and html template
398
+ * boundaries while threading one execution-scoped renderer cache through every
399
+ * delegated boundary so foreign shell ownership remains stable and renderer
400
+ * initialization is reused inside the current request.
401
+ *
402
+ * @param input - Page, layout, document, and metadata inputs for the route render.
403
+ * @returns Final serialized document HTML including the doctype prefix.
404
+ */
405
+ protected async renderPageWithDocumentShell(input: {
406
+ page: {
407
+ component: EcoComponent;
408
+ props: Record<string, unknown>;
409
+ };
410
+ layout?: {
411
+ component: EcoComponent;
412
+ props?: Record<string, unknown>;
413
+ };
414
+ htmlTemplate: EcoComponent;
415
+ metadata: PageMetadataProps;
416
+ pageProps: Record<string, unknown>;
417
+ documentProps?: Record<string, unknown>;
418
+ transformDocumentHtml?: (html: string) => string;
419
+ }): Promise<string> {
420
+ return this.routeShellComposer.renderPageWithDocumentShell(input, {
421
+ hasForeignBoundaryDescendants: (component) => this.hasForeignBoundaryDescendants(component),
422
+ createHtmlResponse: (body, ctx) => this.createHtmlResponse(body, ctx),
423
+ renderComponentBoundary: (boundaryInput) => this.renderComponentBoundary(boundaryInput),
424
+ prepareViewDependencies: (view, layout) => this.prepareViewDependencies(view, layout),
425
+ getHtmlTemplate: () => this.getHtmlTemplate(),
426
+ resolveViewMetadata: (view, props) => this.resolveViewMetadata(view, props),
427
+ appendProcessedDependencies: (...assetGroups) => this.appendProcessedDependencies(...assetGroups),
428
+ finalizeResolvedHtml: (options) => this.finalizeResolvedHtml(options),
429
+ docType: this.DOC_TYPE,
430
+ });
431
+ }
432
+
433
+ /**
434
+ * Renders one string-first component boundary and collects its assets.
435
+ *
436
+ * String-oriented integrations frequently share the same boundary contract:
437
+ * pass serialized children through props, coerce the render result to HTML, and
438
+ * attach any component-scoped dependencies. This helper centralizes that flow
439
+ * so integrations can opt into shared orchestration without repeating the same
440
+ * boundary boilerplate.
441
+ *
442
+ * @param input - Boundary render input.
443
+ * @param component - String-oriented component implementation to execute.
444
+ * @returns Structured component render result for orchestration paths.
445
+ */
446
+ protected async renderStringComponentBoundary(
447
+ input: ComponentRenderInput,
448
+ component: (props: Record<string, unknown>) => Promise<EcoPagesElement> | EcoPagesElement,
449
+ ): Promise<ComponentRenderResult> {
450
+ const props = input.children === undefined ? input.props : { ...input.props, children: input.children };
451
+ const content = await component(props);
452
+ const html = String(content);
453
+ const assets =
454
+ input.component.config?.dependencies &&
455
+ typeof this.assetProcessingService?.processDependencies === 'function'
456
+ ? await this.processComponentDependencies([input.component])
457
+ : undefined;
458
+
459
+ return {
460
+ html,
461
+ canAttachAttributes: true,
462
+ rootTag: this.getRootTagName(html),
463
+ integrationName: this.name,
464
+ assets,
465
+ };
466
+ }
467
+
468
+ protected getBoundaryTokenPrefix(): string {
469
+ return `__${this.name}_boundary__`;
470
+ }
471
+
472
+ protected getBoundaryRuntimeContextKey(): string {
473
+ return `__${this.name}_boundary_runtime__`;
474
+ }
475
+
476
+ protected getQueuedBoundaryRuntime<TContext extends QueuedBoundaryRuntimeContext>(
477
+ input: ComponentRenderInput,
478
+ runtimeContextKey = this.getBoundaryRuntimeContextKey(),
479
+ ): TContext | undefined {
480
+ return this.queuedBoundaryRuntimeService.getRuntimeContext<TContext>(input, runtimeContextKey);
481
+ }
482
+
483
+ protected async resolveQueuedBoundaryTokens(
484
+ html: string,
485
+ queuedResolutionsByToken: Map<string, QueuedBoundaryResolution>,
486
+ resolveToken: (token: string) => Promise<string>,
487
+ ): Promise<string> {
488
+ let resolvedHtml = html;
489
+
490
+ for (const token of queuedResolutionsByToken.keys()) {
491
+ if (!resolvedHtml.includes(token)) {
492
+ continue;
493
+ }
494
+
495
+ resolvedHtml = resolvedHtml.split(token).join(await resolveToken(token));
496
+ }
497
+
498
+ return resolvedHtml;
499
+ }
500
+
501
+ protected createQueuedBoundaryRuntime<TContext extends QueuedBoundaryRuntimeContext>(options: {
502
+ boundaryInput: ComponentRenderInput;
503
+ rendererCache: Map<string, IntegrationRenderer<any>>;
504
+ runtimeContextKey?: string;
505
+ tokenPrefix?: string;
506
+ createRuntimeContext?: (
507
+ integrationContext: BaseIntegrationContext & Record<string, unknown>,
508
+ rendererCache: Map<string, unknown>,
509
+ ) => TContext;
510
+ }): ComponentBoundaryRuntime {
511
+ return this.queuedBoundaryRuntimeService.createRuntime<TContext>({
512
+ boundaryInput: options.boundaryInput,
513
+ rendererCache: options.rendererCache as Map<string, unknown>,
514
+ runtimeContextKey: options.runtimeContextKey ?? this.getBoundaryRuntimeContextKey(),
515
+ tokenPrefix: options.tokenPrefix ?? this.getBoundaryTokenPrefix(),
516
+ shouldQueueBoundary: (input) => this.shouldResolveBoundaryInOwningRenderer(input),
517
+ createRuntimeContext: options.createRuntimeContext,
518
+ });
519
+ }
520
+
521
+ protected async resolveRendererOwnedQueuedBoundaryHtml<TContext extends QueuedBoundaryRuntimeContext>(options: {
522
+ html: string;
523
+ runtimeContext?: TContext;
524
+ queueLabel: string;
525
+ renderQueuedChildren: (
526
+ children: unknown,
527
+ runtimeContext: TContext,
528
+ queuedResolutionsByToken: Map<string, QueuedBoundaryResolution>,
529
+ resolveToken: (token: string) => Promise<string>,
530
+ ) => Promise<{ assets: ProcessedAsset[]; html?: string }>;
531
+ }): Promise<{ assets: ProcessedAsset[]; html: string }> {
532
+ return this.queuedBoundaryRuntimeService.resolveQueuedHtml({
533
+ html: options.html,
534
+ runtimeContext: options.runtimeContext,
535
+ queueLabel: options.queueLabel,
536
+ renderQueuedChildren: options.renderQueuedChildren,
537
+ resolveBoundary: (input, rendererCache) =>
538
+ this.resolveBoundaryPayloadInOwningRenderer(
539
+ input,
540
+ rendererCache as Map<string, IntegrationRenderer<any>>,
541
+ ),
542
+ applyAttributesToFirstElement: (html, attributes) =>
543
+ this.htmlTransformer.applyAttributesToFirstElement(html, attributes),
544
+ dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
545
+ });
546
+ }
547
+
548
+ /**
549
+ * Renders a string-first component, then resolves any queued foreign
550
+ * boundaries before returning final component HTML.
551
+ */
552
+ protected async renderStringComponentBoundaryWithQueuedForeignBoundaries(
553
+ input: ComponentRenderInput,
554
+ component: (props: Record<string, unknown>) => Promise<EcoPagesElement> | EcoPagesElement,
555
+ ): Promise<ComponentRenderResult> {
556
+ const componentRender = await this.renderStringComponentBoundary(input, component);
557
+ const queuedBoundaryResolution = await this.resolveRendererOwnedQueuedBoundaryHtml({
558
+ html: componentRender.html,
559
+ runtimeContext: this.getQueuedBoundaryRuntime<QueuedBoundaryRuntimeContext>(input),
560
+ queueLabel: 'String',
561
+ renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => {
562
+ if (children === undefined) {
563
+ return { assets: [], html: undefined };
564
+ }
565
+
566
+ const html = await this.resolveQueuedBoundaryTokens(
567
+ typeof children === 'string' ? children : String(children ?? ''),
568
+ queuedResolutionsByToken,
569
+ resolveToken,
570
+ );
571
+
572
+ return { assets: [], html };
573
+ },
574
+ });
575
+ const mergedAssets = this.htmlTransformer.dedupeProcessedAssets([
576
+ ...(componentRender.assets ?? []),
577
+ ...queuedBoundaryResolution.assets,
578
+ ]);
579
+
580
+ return {
581
+ ...componentRender,
582
+ html: queuedBoundaryResolution.html,
583
+ rootTag: this.getRootTagName(queuedBoundaryResolution.html),
584
+ assets: mergedAssets.length > 0 ? mergedAssets : undefined,
585
+ };
586
+ }
587
+
588
+ constructor({
589
+ appConfig,
590
+ assetProcessingService,
591
+ resolvedIntegrationDependencies,
592
+ rendererModules,
593
+ runtimeOrigin,
594
+ }: {
595
+ appConfig: EcoPagesAppConfig;
596
+ assetProcessingService: AssetProcessingService;
597
+ resolvedIntegrationDependencies?: ProcessedAsset[];
598
+ rendererModules?: unknown;
599
+ runtimeOrigin: string;
600
+ }) {
601
+ this.appConfig = appConfig;
602
+ this.assetProcessingService = assetProcessingService;
603
+ this.htmlTransformer = new HtmlTransformerService();
604
+ this.pagePackagingService = new PagePackagingService();
605
+ this.resolvedIntegrationDependencies = resolvedIntegrationDependencies || [];
606
+ this.rendererModules = rendererModules ?? appConfig.runtime?.rendererModuleContext;
607
+ this.runtimeOrigin = runtimeOrigin;
608
+ this.dependencyResolverService = new DependencyResolverService(appConfig, assetProcessingService);
609
+ this.pageModuleLoaderService = new PageModuleLoaderService(appConfig, runtimeOrigin);
610
+ this.renderPreparationService = new RenderPreparationService(appConfig, assetProcessingService, {
611
+ pagePackagingService: this.pagePackagingService,
612
+ });
613
+ this.renderExecutionService = new RenderExecutionService();
614
+ }
615
+
616
+ /**
617
+ * Returns the HTML path from the provided file path.
618
+ * It extracts the path relative to the pages directory and removes the 'index' part if present.
619
+ *
620
+ * @param file - The file path to extract the HTML path from.
621
+ * @returns The extracted HTML path.
622
+ */
623
+ protected getHtmlPath({ file }: { file: string }): string {
624
+ const pagesDir = this.appConfig.absolutePaths.pagesDir;
625
+ const pagesIndex = file.indexOf(pagesDir);
626
+ if (pagesIndex === -1) return file;
627
+ const startIndex = file.indexOf(pagesDir) + pagesDir.length;
628
+ const endIndex = file.lastIndexOf('/');
629
+ const path = file.substring(startIndex, endIndex);
630
+ if (path === '/index') return '';
631
+ return path;
632
+ }
633
+
634
+ /**
635
+ * Returns the HTML template component.
636
+ * It imports the HTML template from the specified path in the app configuration.
637
+ *
638
+ * @returns The HTML template component.
639
+ */
640
+ protected async getHtmlTemplate(): Promise<EcoComponent<HtmlTemplateProps>> {
641
+ const htmlTemplatePath =
642
+ this.getRendererModuleString('htmlTemplateModulePath') ?? this.appConfig.absolutePaths.htmlTemplatePath;
643
+ try {
644
+ const { default: HtmlTemplate } = await this.importPageFile(htmlTemplatePath);
645
+ return HtmlTemplate as EcoComponent<HtmlTemplateProps>;
646
+ } catch (error) {
647
+ invariant(false, `Error importing HtmlTemplate: ${error}`);
648
+ }
649
+ }
650
+
651
+ /**
652
+ * Returns the static props for the page.
653
+ * It calls the provided getStaticProps function with the given options.
654
+ *
655
+ * @param getStaticProps - The function to get static props.
656
+ * @param options - The options to pass to the getStaticProps function.
657
+ * @returns The static props and metadata.
658
+ */
659
+ protected async getStaticProps(
660
+ getStaticProps?: GetStaticProps<Record<string, unknown>>,
661
+ options?: Pick<RouteRendererOptions, 'params'>,
662
+ ): Promise<{
663
+ props: Record<string, unknown>;
664
+ metadata?: PageMetadataProps;
665
+ }> {
666
+ return this.pageModuleLoaderService.getStaticPropsForPage({
667
+ getStaticProps,
668
+ params: options?.params,
669
+ });
670
+ }
671
+
672
+ /**
673
+ * Returns the metadata properties for the page.
674
+ * It calls the provided getMetadata function with the given context.
675
+ *
676
+ * @param getMetadata - The function to get metadata.
677
+ * @param context - The context to pass to the getMetadata function.
678
+ * @returns The metadata properties.
679
+ */
680
+ protected async getMetadataProps(
681
+ getMetadata: GetMetadata | undefined,
682
+ { props, params, query }: GetMetadataContext,
683
+ ): Promise<PageMetadataProps> {
684
+ return this.pageModuleLoaderService.getMetadataPropsForPage({
685
+ getMetadata,
686
+ context: { props, params, query } as GetMetadataContext,
687
+ });
688
+ }
689
+
690
+ protected usesIntegrationPageImporter(_file: string): boolean {
691
+ return false;
692
+ }
693
+
694
+ protected async importIntegrationPageFile(_file: string, _options?: RouteModuleLoadOptions): Promise<EcoPageFile> {
695
+ invariant(false, 'Integration page importer must be implemented when enabled');
696
+ }
697
+
698
+ protected normalizeImportedPageFile<TPageModule extends EcoPageFile>(
699
+ _file: string,
700
+ pageModule: TPageModule,
701
+ ): TPageModule {
702
+ return pageModule;
703
+ }
704
+
705
+ /**
706
+ * Imports the page file from the specified path.
707
+ * It uses dynamic import to load the file and returns the imported module.
708
+ *
709
+ * @param file - The file path to import.
710
+ * @returns The imported module.
711
+ */
712
+ protected async importPageFile(file: string, options?: RouteModuleLoadOptions): Promise<EcoPageFile> {
713
+ const bypassCache =
714
+ options?.bypassCache ?? (typeof Bun !== 'undefined' && process.env.NODE_ENV === 'development');
715
+ const pageModule = this.usesIntegrationPageImporter(file)
716
+ ? await this.importIntegrationPageFile(file, {
717
+ bypassCache,
718
+ cacheScope: options?.cacheScope,
719
+ })
720
+ : await this.pageModuleLoaderService.importPageFile(file, {
721
+ bypassCache,
722
+ cacheScope: options?.cacheScope,
723
+ });
724
+
725
+ return this.normalizeImportedPageFile(file, pageModule);
726
+ }
727
+
728
+ /**
729
+ * Resolves the dependency path based on the component directory.
730
+ * It combines the component directory with the provided path URL.
731
+ *
732
+ * @param componentDir - The component directory path.
733
+ * @param pathUrl - The path URL to resolve.
734
+ * @returns The resolved dependency path.
735
+ */
736
+ protected resolveDependencyPath(componentDir: string, pathUrl: string): string {
737
+ return this.dependencyResolverService.resolveDependencyPath(componentDir, pathUrl);
738
+ }
739
+
740
+ /**
741
+ * Extracts the dependencies from the provided component configuration.
742
+ * It resolves the paths for scripts and stylesheets based on the component directory.
743
+ *
744
+ * @param componentDir - The component directory path.
745
+ * @param scripts - The scripts to extract.
746
+ * @param stylesheets - The stylesheets to extract.
747
+ * @returns The extracted dependencies.
748
+ */
749
+ protected extractDependencies({
750
+ componentDir,
751
+ scripts,
752
+ stylesheets,
753
+ }: {
754
+ componentDir: string;
755
+ } & EcoComponentDependencies): EcoComponentDependencies {
756
+ const scriptsPaths = [
757
+ ...new Set(
758
+ (scripts ?? [])
759
+ .filter((script) => (typeof script === 'string' ? true : !script.lazy))
760
+ .map((script) => (typeof script === 'string' ? script : script.src))
761
+ .filter((script): script is string => Boolean(script))
762
+ .map((script) => this.resolveDependencyPath(componentDir, script)),
763
+ ),
764
+ ];
765
+
766
+ const stylesheetsPaths = [
767
+ ...new Set(
768
+ (stylesheets ?? [])
769
+ .map((style) => (typeof style === 'string' ? style : style.src))
770
+ .filter((style): style is string => Boolean(style))
771
+ .map((style) => this.resolveDependencyPath(componentDir, style)),
772
+ ),
773
+ ];
774
+
775
+ return {
776
+ scripts: scriptsPaths,
777
+ stylesheets: stylesheetsPaths,
778
+ };
779
+ }
780
+
781
+ /**
782
+ * Resolves lazy script paths to public asset URLs.
783
+ * Converts source paths to their final bundled output paths.
784
+ *
785
+ * @param componentDir - The component directory path.
786
+ * @param scripts - The lazy script paths to resolve.
787
+ * @returns Comma-separated string of resolved public script paths.
788
+ */
789
+ protected resolveLazyScripts(componentDir: string, scripts: string[]): string {
790
+ return this.dependencyResolverService.resolveLazyScripts(componentDir, scripts);
791
+ }
792
+
793
+ /**
794
+ * Collects the dependencies for the provided components.
795
+ * Combines component-specific dependencies with global integration dependencies.
796
+ *
797
+ * @param components - The components to collect dependencies from.
798
+ */
799
+ protected async resolveDependencies(
800
+ components: (EcoComponent | Partial<EcoComponent>)[],
801
+ ): Promise<ProcessedAsset[]> {
802
+ const componentDeps = await this.processComponentDependencies(components);
803
+ return this.resolvedIntegrationDependencies.concat(componentDeps);
804
+ }
805
+
806
+ /**
807
+ * Processes component-specific dependencies WITHOUT prepending global integration dependencies.
808
+ * Use this method when you need only the component's own assets.
809
+ *
810
+ * @param components - The components to collect dependencies from.
811
+ */
812
+ protected async processComponentDependencies(
813
+ components: (EcoComponent | Partial<EcoComponent>)[],
814
+ ): Promise<ProcessedAsset[]> {
815
+ return this.dependencyResolverService.processComponentDependencies(components, this.name);
816
+ }
817
+
818
+ /**
819
+ * Prepares the render options for the integration renderer.
820
+ * It imports the page file, collects dependencies, and prepares the render options.
821
+ *
822
+ * @param options - The route renderer options.
823
+ * @returns The prepared render options.
824
+ */
825
+ protected async prepareRenderOptions(options: RouteRendererOptions): Promise<IntegrationRendererRenderOptions> {
826
+ const preparedOptions = await this.renderPreparationService.prepare(options, this.name, {
827
+ resolvePageModule: (file) => this.resolvePageModule(file),
828
+ getHtmlTemplate: () => this.getHtmlTemplate(),
829
+ resolvePageData: (pageModule, routeOptions) => this.resolvePageData(pageModule, routeOptions),
830
+ resolveDependencies: (components) => this.resolveDependencies(components),
831
+ buildRouteRenderAssets: (file) => this.buildRouteRenderAssets(file),
832
+ shouldRenderPageComponent: (input) => this.shouldRenderPageComponent(input),
833
+ renderPageComponent: ({ component, props }) =>
834
+ this.renderComponentBoundary({
835
+ component,
836
+ props,
837
+ integrationContext: {
838
+ componentInstanceId: 'eco-page-root',
839
+ },
840
+ }),
841
+ });
842
+
843
+ invariant(preparedOptions.pagePackage !== undefined, 'Expected render preparation to produce a page package');
844
+ this.htmlTransformer.setPagePackage(preparedOptions.pagePackage);
845
+ return preparedOptions;
846
+ }
847
+
848
+ /**
849
+ * Controls whether the page root should be rendered through `renderComponent()`
850
+ * during route option preparation in component-capable modes.
851
+ *
852
+ * Integrations that already own page-level hydration (for example router-driven
853
+ * React rendering) can override this and return `false` to avoid duplicate root
854
+ * mount assets and competing hydration entrypoints.
855
+ */
856
+ protected shouldRenderPageComponent(_input: {
857
+ Page: EcoComponent;
858
+ Layout?: EcoComponent;
859
+ options: RouteRendererOptions;
860
+ }): boolean {
861
+ return true;
862
+ }
863
+
864
+ /**
865
+ * Resolves the page module and normalizes exports.
866
+ */
867
+ protected async resolvePageModule(file: string): Promise<{
868
+ Page: EcoPageFile['default'] | EcoPageComponent<any>;
869
+ getStaticProps?: GetStaticProps<Record<string, unknown>>;
870
+ getMetadata?: GetMetadata;
871
+ integrationSpecificProps: Record<string, unknown>;
872
+ }> {
873
+ return this.pageModuleLoaderService.resolvePageModule({
874
+ file,
875
+ importPageFileFn: (targetFile) => this.importPageFile(targetFile),
876
+ });
877
+ }
878
+
879
+ /**
880
+ * Resolves static props and metadata for the page.
881
+ */
882
+ protected async resolvePageData(
883
+ pageModule: {
884
+ getStaticProps?: GetStaticProps<Record<string, unknown>>;
885
+ getMetadata?: GetMetadata;
886
+ },
887
+ options: RouteRendererOptions,
888
+ ): Promise<{
889
+ props: Record<string, unknown>;
890
+ metadata: PageMetadataProps;
891
+ }> {
892
+ return this.pageModuleLoaderService.resolvePageData({
893
+ pageModule,
894
+ routeOptions: options,
895
+ });
896
+ }
897
+
898
+ /**
899
+ * Executes the integration renderer with the provided options.
900
+ *
901
+ * Execution flow:
902
+ * 1. Build normalized render options (`prepareRenderOptions`).
903
+ * 2. Render the route body once.
904
+ * 3. Reject unresolved route-level boundary artifacts.
905
+ * 4. Optionally apply root attributes for page/component root boundaries.
906
+ * 5. Run HTML transformer with final dependency set.
907
+ *
908
+ * Stream-safety note: the first render result is normalized to a string once,
909
+ * then the pipeline continues with that immutable HTML value to avoid disturbed
910
+ * response-body errors.
911
+ *
912
+ * @param options Route renderer options.
913
+ * @returns Rendered route body plus effective cache strategy.
914
+ */
915
+ public async execute(options: RouteRendererOptions): Promise<RouteRenderResult> {
916
+ return this.renderExecutionService.execute(options, {
917
+ prepareRenderOptions: (routeOptions) =>
918
+ this.prepareRenderOptions(routeOptions) as Promise<IntegrationRendererRenderOptions<C>>,
919
+ render: (renderOptions) => this.render(renderOptions),
920
+ getDocumentAttributes: (renderOptions) => this.getDocumentAttributes(renderOptions),
921
+ applyAttributesToHtmlElement: (html, attributes) =>
922
+ this.htmlTransformer.applyAttributesToHtmlElement(html, attributes),
923
+ applyAttributesToFirstBodyElement: (html, attributes) =>
924
+ this.htmlTransformer.applyAttributesToFirstBodyElement(html, attributes),
925
+ transformResponse: async (response) => {
926
+ const transformedResponse = await this.htmlTransformer.transform(response);
927
+ return (transformedResponse.body ?? (await transformedResponse.text())) as RouteRendererBody;
928
+ },
929
+ });
930
+ }
931
+
932
+ /**
933
+ * Finalizes already-resolved HTML for explicit renderer-owned paths.
934
+ *
935
+ * This keeps document and root-attribute stamping plus HTML transformation
936
+ * available after a renderer has completed nested boundary resolution without
937
+ * routing back through shared route execution.
938
+ */
939
+ protected async finalizeResolvedHtml(options: {
940
+ html: string;
941
+ partial?: boolean;
942
+ componentRootAttributes?: Record<string, string>;
943
+ documentAttributes?: Record<string, string>;
944
+ transformHtml?: boolean;
945
+ }): Promise<string> {
946
+ const rendererBootstrapDependencies = this.getRendererBootstrapDependencies(options.partial);
947
+ this.appendProcessedDependencies(rendererBootstrapDependencies);
948
+
949
+ let html = options.html;
950
+
951
+ if (options.componentRootAttributes && Object.keys(options.componentRootAttributes).length > 0) {
952
+ html = this.htmlTransformer.applyAttributesToFirstBodyElement(html, options.componentRootAttributes);
953
+ }
954
+
955
+ if (options.documentAttributes && Object.keys(options.documentAttributes).length > 0) {
956
+ html = this.htmlTransformer.applyAttributesToHtmlElement(html, options.documentAttributes);
957
+ }
958
+
959
+ const shouldTransform = options.transformHtml ?? !options.partial;
960
+ if (!shouldTransform) {
961
+ return html;
962
+ }
963
+
964
+ const transformedResponse = await this.htmlTransformer.transform(
965
+ new Response(html, {
966
+ headers: { 'Content-Type': 'text/html' },
967
+ }),
968
+ );
969
+
970
+ return await transformedResponse.text();
971
+ }
972
+
973
+ /**
974
+ * Returns document-level attributes to stamp onto the rendered `<html>` tag.
975
+ *
976
+ * Integrations can override this to expose explicit document ownership or
977
+ * other runtime coordination markers without relying on script sniffing.
978
+ */
979
+ protected getDocumentAttributes(
980
+ _renderOptions: IntegrationRendererRenderOptions<C>,
981
+ ): Record<string, string> | undefined {
982
+ return undefined;
983
+ }
984
+
985
+ /**
986
+ * Returns a renderer instance for a given integration name.
987
+ *
988
+ * Uses a per-execution cache to avoid repeated renderer initialization.
989
+ *
990
+ * @param integrationName Target integration name.
991
+ * @param cache Render-pass renderer cache.
992
+ * @returns Renderer for the requested integration.
993
+ * @throws Error when no integration plugin matches `integrationName`.
994
+ */
995
+ private getIntegrationRendererForName(
996
+ integrationName: string,
997
+ cache: Map<string, IntegrationRenderer<any>>,
998
+ ): IntegrationRenderer<any> {
999
+ if (cache.has(integrationName)) {
1000
+ return cache.get(integrationName) as IntegrationRenderer<any>;
1001
+ }
1002
+
1003
+ if (integrationName === this.name) {
1004
+ cache.set(integrationName, this);
1005
+ return this;
1006
+ }
1007
+
1008
+ const integrationPlugin = this.appConfig.integrations.find(
1009
+ (integration) => integration.name === integrationName,
1010
+ );
1011
+ invariant(!!integrationPlugin, `[ecopages] Integration not found for boundary owner: ${integrationName}`);
1012
+ const renderer = integrationPlugin.initializeRenderer({
1013
+ rendererModules: this.appConfig.runtime?.rendererModuleContext,
1014
+ });
1015
+ cache.set(integrationName, renderer);
1016
+ return renderer;
1017
+ }
1018
+
1019
+ /**
1020
+ * Abstract method to render the integration-specific component.
1021
+ * This method should be implemented by the specific integration renderer.
1022
+ *
1023
+ * @param options - The integration renderer render options.
1024
+ * @returns The rendered body.
1025
+ */
1026
+ abstract render(options: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
1027
+
1028
+ protected async resolveBoundaryInOwningRenderer(
1029
+ input: ComponentRenderInput,
1030
+ rendererCache: Map<string, IntegrationRenderer<any>>,
1031
+ ): Promise<ComponentRenderResult | undefined> {
1032
+ const boundaryOwner = this.getRegisteredBoundaryOwner(input.component);
1033
+ if (!boundaryOwner) {
1034
+ return undefined;
1035
+ }
1036
+
1037
+ const owningRenderer = this.getIntegrationRendererForName(boundaryOwner, rendererCache);
1038
+ if (owningRenderer === this || owningRenderer.name === this.name) {
1039
+ return undefined;
1040
+ }
1041
+ return await owningRenderer.renderComponentBoundary(this.withBoundaryRendererCache(input, rendererCache));
1042
+ }
1043
+
1044
+ protected async resolveBoundaryPayloadInOwningRenderer(
1045
+ input: ComponentRenderInput,
1046
+ rendererCache: Map<string, IntegrationRenderer<any>>,
1047
+ ): Promise<BoundaryRenderPayload | undefined> {
1048
+ const boundaryOwner = this.getRegisteredBoundaryOwner(input.component);
1049
+ if (!boundaryOwner) {
1050
+ return undefined;
1051
+ }
1052
+
1053
+ const owningRenderer = this.getIntegrationRendererForName(boundaryOwner, rendererCache);
1054
+ if (owningRenderer === this || owningRenderer.name === this.name) {
1055
+ return undefined;
1056
+ }
1057
+
1058
+ return await owningRenderer.renderBoundary(this.withBoundaryRendererCache(input, rendererCache));
1059
+ }
1060
+
1061
+ /**
1062
+ * Renders one component under this integration's boundary runtime and resolves
1063
+ * any nested foreign boundaries captured during that render.
1064
+ *
1065
+ * Without this wrapper, a component tree with foreign-owned descendants would
1066
+ * render them with no active boundary runtime, which bypasses the owning
1067
+ * renderer's nested-boundary handoff.
1068
+ */
1069
+ async renderComponentBoundary(input: ComponentRenderInput): Promise<ComponentRenderResult> {
1070
+ const rendererCache =
1071
+ this.getBoundaryRendererCache(input.integrationContext) ?? new Map<string, IntegrationRenderer<any>>();
1072
+ const delegatedBoundaryRender = await this.resolveBoundaryInOwningRenderer(input, rendererCache);
1073
+
1074
+ if (delegatedBoundaryRender) {
1075
+ return delegatedBoundaryRender;
1076
+ }
1077
+
1078
+ const hasForeignBoundaries = this.hasForeignBoundaryDescendants(input.component);
1079
+ const activeRenderContext = getComponentRenderContext();
1080
+
1081
+ if (!hasForeignBoundaries) {
1082
+ if (!activeRenderContext || activeRenderContext.currentIntegration === this.name) {
1083
+ return this.normalizeComponentBoundaryRender(await this.renderComponent(input));
1084
+ }
1085
+
1086
+ const sameIntegrationExecution = await runWithComponentRenderContext(
1087
+ {
1088
+ currentIntegration: this.name,
1089
+ },
1090
+ async () => this.renderComponent(input),
1091
+ );
1092
+
1093
+ return this.normalizeComponentBoundaryRender(sameIntegrationExecution.value);
1094
+ }
1095
+
1096
+ const execution = await runWithComponentRenderContext(
1097
+ {
1098
+ currentIntegration: this.name,
1099
+ boundaryRuntime: this.createComponentBoundaryRuntime({
1100
+ boundaryInput: input,
1101
+ rendererCache,
1102
+ }),
1103
+ },
1104
+ async () => this.renderComponent(input),
1105
+ );
1106
+
1107
+ return this.normalizeComponentBoundaryRender(execution.value);
1108
+ }
1109
+
1110
+ /**
1111
+ * Compatibility boundary contract that exposes a narrower payload shape for
1112
+ * future route-composition work while preserving the current
1113
+ * `renderComponentBoundary()` runtime semantics.
1114
+ */
1115
+ async renderBoundary(input: ComponentRenderInput): Promise<BoundaryRenderPayload> {
1116
+ const result = await this.renderComponentBoundary(input);
1117
+
1118
+ return {
1119
+ html: result.html,
1120
+ assets: result.assets ?? [],
1121
+ rootTag: result.rootTag,
1122
+ rootAttributes: result.rootAttributes,
1123
+ attachmentPolicy: result.canAttachAttributes ? { kind: 'first-element' } : { kind: 'none' },
1124
+ integrationName: result.integrationName,
1125
+ };
1126
+ }
1127
+
1128
+ private normalizeComponentBoundaryRender(result: ComponentRenderResult): ComponentRenderResult {
1129
+ const normalizedHtml = this.normalizeBoundaryArtifactHtml(result.html);
1130
+
1131
+ return normalizedHtml === result.html
1132
+ ? result
1133
+ : {
1134
+ ...result,
1135
+ html: normalizedHtml,
1136
+ };
1137
+ }
1138
+
1139
+ protected normalizeBoundaryArtifactHtml(html: string): string {
1140
+ return normalizeBoundaryArtifactHtml(html);
1141
+ }
1142
+
1143
+ /**
1144
+ * Returns whether the component dependency tree crosses into another
1145
+ * integration.
1146
+ *
1147
+ * This keeps boundary-runtime setup narrow: same-integration trees can render
1148
+ * directly without paying the queue orchestration cost.
1149
+ */
1150
+ protected hasForeignBoundaryDescendants(component: EcoComponent): boolean {
1151
+ const stack = [component];
1152
+ const seen = new Set<EcoComponent>();
1153
+
1154
+ while (stack.length > 0) {
1155
+ const current = stack.pop();
1156
+ if (!current || seen.has(current)) {
1157
+ continue;
1158
+ }
1159
+
1160
+ seen.add(current);
1161
+ const integrationName = current.config?.integration ?? current.config?.__eco?.integration;
1162
+ if (integrationName && integrationName !== this.name) {
1163
+ return true;
1164
+ }
1165
+
1166
+ stack.push(...(current.config?.dependencies?.components ?? []));
1167
+ }
1168
+
1169
+ return false;
1170
+ }
1171
+
1172
+ /**
1173
+ * Render a view directly to a Response object.
1174
+ * Used for explicit routing where views are rendered from route handlers.
1175
+ *
1176
+ * @param view - The eco.page component to render
1177
+ * @param props - Props to pass to the view
1178
+ * @param ctx - Render context with partial flag and response options
1179
+ * @returns A Response object with the rendered content
1180
+ */
1181
+ abstract renderToResponse<P = Record<string, unknown>>(
1182
+ view: EcoComponent<P>,
1183
+ props: P,
1184
+ ctx: RenderToResponseContext,
1185
+ ): Promise<Response>;
1186
+
1187
+ /**
1188
+ * Render a single component and return structured output for orchestration paths.
1189
+ *
1190
+ * Default behavior delegates to `renderToResponse` in partial mode and wraps
1191
+ * the resulting HTML into the `ComponentRenderResult` contract.
1192
+ *
1193
+ * In boundary resolution, this method is the integration-owned step that turns an
1194
+ * already-resolved deferred boundary into concrete HTML, assets, and optional
1195
+ * root attributes.
1196
+ *
1197
+ * Integrations can override this for richer behavior (asset emission,
1198
+ * root attributes, integration-specific hydration metadata).
1199
+ *
1200
+ * @param input Component render request.
1201
+ * @returns Structured render result used by component/page orchestration.
1202
+ */
1203
+ async renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult> {
1204
+ const response = await this.renderToResponse(
1205
+ input.component as EcoFunctionComponent<Record<string, unknown>, EcoPagesElement>,
1206
+ input.props,
1207
+ { partial: true },
1208
+ );
1209
+ const html = await response.text();
1210
+
1211
+ return {
1212
+ html,
1213
+ canAttachAttributes: true,
1214
+ rootTag: this.getRootTagName(html),
1215
+ integrationName: this.name,
1216
+ };
1217
+ }
1218
+
1219
+ /**
1220
+ * Extracts the first root element tag name from HTML output.
1221
+ *
1222
+ * @param html HTML fragment.
1223
+ * @returns Root tag name when present; otherwise `undefined`.
1224
+ */
1225
+ protected getRootTagName(html: string): string | undefined {
1226
+ const rootTag = html.match(/^(?:\s|<!--[\s\S]*?-->)*<([a-zA-Z][a-zA-Z0-9:-]*)\b/);
1227
+ return rootTag?.[1];
1228
+ }
1229
+
1230
+ /**
1231
+ * Method to build route render assets.
1232
+ * This method can be optionally overridden by the specific integration renderer.
1233
+ *
1234
+ * @param file - The file path to build assets for.
1235
+ * @returns The processed assets or undefined.
1236
+ */
1237
+ protected buildRouteRenderAssets(_file: string): Promise<ProcessedAsset[]> | undefined {
1238
+ return undefined;
1239
+ }
1240
+
1241
+ /**
1242
+ * Creates the per-render boundary runtime adopted by the shared component
1243
+ * render context.
1244
+ *
1245
+ * Real mixed-integration renderers should override this and keep foreign
1246
+ * boundary resolution inside their own renderer-owned queue. The base runtime
1247
+ * fails fast when a renderer crosses into a foreign owner without providing its
1248
+ * own handoff mechanism.
1249
+ */
1250
+ protected createComponentBoundaryRuntime(_options: {
1251
+ boundaryInput: ComponentRenderInput;
1252
+ rendererCache: Map<string, IntegrationRenderer<any>>;
1253
+ }): ComponentBoundaryRuntime {
1254
+ const decideBoundaryInterception = (input: BoundaryRenderDecisionInput) => {
1255
+ if (!this.shouldResolveBoundaryInOwningRenderer(input)) {
1256
+ return { kind: 'inline' as const };
1257
+ }
1258
+
1259
+ throw new Error(
1260
+ `[ecopages] ${this.name} renderer crossed into ${input.targetIntegration} without a renderer-owned boundary runtime. Override createComponentBoundaryRuntime() to resolve foreign boundaries inside the owning renderer.`,
1261
+ );
1262
+ };
1263
+
1264
+ const runtime: ComponentBoundaryRuntime = {
1265
+ interceptBoundary: decideBoundaryInterception,
1266
+ interceptBoundarySync: decideBoundaryInterception,
1267
+ };
1268
+
1269
+ return runtime;
1270
+ }
1271
+
1272
+ /**
1273
+ * Resolves whether a boundary should leave the current render pass and be
1274
+ * resolved by its owning renderer.
1275
+ *
1276
+ * Boundaries owned by the current integration always render inline. Foreign-
1277
+ * owned boundaries must be handed off by a renderer-owned runtime.
1278
+ *
1279
+ * @param input Boundary metadata for the active render pass.
1280
+ * @returns `true` when the boundary should leave the current pass; otherwise `false`.
1281
+ */
1282
+ protected shouldResolveBoundaryInOwningRenderer(input: BoundaryRenderDecisionInput): boolean {
1283
+ return !!input.targetIntegration && input.targetIntegration !== input.currentIntegration;
1284
+ }
1285
+ }