@ecopages/core 0.2.0-alpha.5 → 0.2.0-alpha.7

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 (418) hide show
  1. package/README.md +213 -12
  2. package/package.json +100 -188
  3. package/src/adapters/README.md +39 -0
  4. package/src/adapters/bun/hmr-manager.test.ts +267 -0
  5. package/src/adapters/bun/hmr-manager.ts +180 -47
  6. package/src/adapters/bun/index.ts +1 -2
  7. package/src/adapters/bun/server-adapter.ts +41 -34
  8. package/src/adapters/bun/server-lifecycle.ts +40 -70
  9. package/src/adapters/index.ts +1 -1
  10. package/src/adapters/node/bootstrap-dependency-resolver.test.ts +282 -0
  11. package/src/adapters/node/bootstrap-dependency-resolver.ts +301 -0
  12. package/src/adapters/node/index.ts +7 -0
  13. package/src/adapters/node/node-client-bridge.test.ts +198 -0
  14. package/src/adapters/node/node-hmr-manager.test.ts +322 -0
  15. package/src/adapters/node/node-hmr-manager.ts +207 -97
  16. package/src/adapters/node/runtime-adapter.test.ts +868 -0
  17. package/src/adapters/node/runtime-adapter.ts +439 -0
  18. package/src/adapters/node/server-adapter.ts +31 -104
  19. package/src/adapters/node/static-content-server.test.ts +60 -0
  20. package/src/adapters/node/static-content-server.ts +36 -0
  21. package/src/adapters/node/write-runtime-manifest.ts +38 -0
  22. package/src/adapters/shared/api-response.test.ts +97 -0
  23. package/src/{define-api-handler.ts → adapters/shared/define-api-handler.ts} +1 -1
  24. package/src/adapters/shared/explicit-static-route-matcher.test.ts +381 -0
  25. package/src/adapters/shared/explicit-static-route-matcher.ts +7 -1
  26. package/src/adapters/shared/file-route-middleware-pipeline.test.ts +90 -0
  27. package/src/adapters/shared/file-route-middleware-pipeline.ts +6 -2
  28. package/src/adapters/shared/fs-server-response-factory.test.ts +187 -0
  29. package/src/adapters/shared/fs-server-response-matcher.test.ts +286 -0
  30. package/src/adapters/shared/fs-server-response-matcher.ts +17 -10
  31. package/src/adapters/shared/hmr-entrypoint-registrar.ts +149 -0
  32. package/src/adapters/shared/hmr-html-response.ts +52 -0
  33. package/src/adapters/shared/hmr-manager.contract.test.ts +196 -0
  34. package/src/adapters/shared/hmr-manager.dispatch.test.ts +220 -0
  35. package/src/adapters/shared/render-context.test.ts +146 -0
  36. package/src/adapters/shared/render-context.ts +21 -6
  37. package/src/adapters/shared/runtime-bootstrap.ts +79 -0
  38. package/src/adapters/shared/server-adapter.test.ts +77 -0
  39. package/src/adapters/shared/server-adapter.ts +51 -4
  40. package/src/adapters/shared/server-route-handler.test.ts +110 -0
  41. package/src/adapters/shared/server-route-handler.ts +5 -18
  42. package/src/adapters/shared/server-static-builder.test.ts +316 -0
  43. package/src/adapters/shared/server-static-builder.ts +92 -8
  44. package/src/build/README.md +101 -0
  45. package/src/build/build-adapter-serialization.test.ts +268 -0
  46. package/src/build/build-adapter.test.ts +815 -0
  47. package/src/build/build-adapter.ts +234 -6
  48. package/src/build/build-manifest.ts +54 -0
  49. package/src/build/dev-build-coordinator.ts +221 -0
  50. package/src/build/esbuild-build-adapter.ts +132 -84
  51. package/src/build/runtime-build-executor.ts +34 -0
  52. package/src/build/runtime-specifier-alias-plugin.test.ts +43 -0
  53. package/src/build/runtime-specifier-alias-plugin.ts +58 -0
  54. package/src/config/README.md +33 -0
  55. package/src/config/config-builder.test.ts +410 -0
  56. package/src/config/config-builder.ts +281 -49
  57. package/src/constants.ts +15 -0
  58. package/src/declarations.d.ts +18 -13
  59. package/src/eco/README.md +70 -16
  60. package/src/eco/component-render-context.ts +39 -17
  61. package/src/eco/eco.test.ts +678 -0
  62. package/src/eco/eco.ts +29 -8
  63. package/src/eco/eco.types.ts +20 -1
  64. package/src/eco/eco.utils.test.ts +124 -0
  65. package/src/eco/global-injector-map.test.ts +42 -0
  66. package/src/eco/lazy-injector-map.test.ts +66 -0
  67. package/src/eco/module-dependencies.test.ts +30 -0
  68. package/src/errors/http-error.test.ts +134 -0
  69. package/src/global/utils.test.ts +12 -0
  70. package/src/hmr/README.md +26 -0
  71. 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
  72. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-HMR-Server-Integration-should-load-fixture-app-page-1.png +0 -0
  73. package/src/hmr/client/__screenshots__/hmr-runtime.test.browser.ts/HMR-Runtime-WebSocket-Connection-should-connect-to-correct-HMR-endpoint-1.png +0 -0
  74. package/src/hmr/client/hmr-runtime.ts +38 -7
  75. package/src/hmr/hmr-strategy.test.ts +124 -0
  76. package/src/hmr/hmr.postcss.test.e2e.ts +41 -0
  77. package/src/hmr/hmr.test.e2e.ts +29 -38
  78. package/src/hmr/strategies/js-hmr-strategy.test.ts +335 -0
  79. package/src/hmr/strategies/js-hmr-strategy.ts +71 -78
  80. package/src/index.ts +1 -1
  81. package/src/integrations/ghtml/ghtml-renderer.test.ts +63 -0
  82. package/src/integrations/ghtml/ghtml-renderer.ts +4 -1
  83. package/src/internal-types.ts +39 -19
  84. package/src/plugins/README.md +34 -0
  85. package/src/plugins/alias-resolver-plugin.test.ts +41 -0
  86. package/src/plugins/alias-resolver-plugin.ts +21 -3
  87. package/src/plugins/eco-component-meta-plugin.test.ts +380 -0
  88. package/src/plugins/eco-component-meta-plugin.ts +10 -3
  89. package/src/plugins/integration-plugin.test.ts +111 -0
  90. package/src/plugins/integration-plugin.ts +45 -3
  91. package/src/plugins/processor.test.ts +148 -0
  92. package/src/plugins/processor.ts +22 -2
  93. package/src/plugins/runtime-capability.ts +14 -0
  94. package/src/public-types.ts +73 -11
  95. package/src/route-renderer/GRAPH.md +16 -20
  96. package/src/route-renderer/README.md +8 -21
  97. package/src/route-renderer/component-graph/component-graph-executor.test.ts +41 -0
  98. package/src/route-renderer/component-graph/component-graph.test.ts +63 -0
  99. package/src/route-renderer/component-graph/component-marker.test.ts +73 -0
  100. package/src/route-renderer/component-graph/component-reference.ts +29 -0
  101. package/src/route-renderer/component-graph/marker-graph-resolver.test.ts +135 -0
  102. package/src/route-renderer/{marker-graph-resolver.ts → component-graph/marker-graph-resolver.ts} +11 -9
  103. package/src/route-renderer/orchestration/integration-renderer.test.ts +936 -0
  104. package/src/route-renderer/{integration-renderer.ts → orchestration/integration-renderer.ts} +113 -19
  105. package/src/route-renderer/orchestration/render-execution.service.test.ts +97 -0
  106. package/src/route-renderer/{render-execution.service.ts → orchestration/render-execution.service.ts} +109 -37
  107. package/src/route-renderer/orchestration/render-preparation.service.test.ts +235 -0
  108. package/src/route-renderer/{render-preparation.service.ts → orchestration/render-preparation.service.ts} +127 -9
  109. package/src/route-renderer/page-loading/dependency-resolver.test.ts +345 -0
  110. package/src/route-renderer/{dependency-resolver.ts → page-loading/dependency-resolver.ts} +28 -12
  111. package/src/route-renderer/page-loading/page-module-loader.test.ts +96 -0
  112. package/src/route-renderer/{page-module-loader.ts → page-loading/page-module-loader.ts} +49 -21
  113. package/src/route-renderer/route-renderer.ts +36 -1
  114. package/src/router/README.md +26 -0
  115. package/src/router/client/link-intent.d.ts +53 -0
  116. package/src/router/client/link-intent.test.browser.ts +51 -0
  117. package/src/router/client/link-intent.ts +92 -0
  118. package/src/router/client/navigation-coordinator.test.ts +237 -0
  119. package/src/router/client/navigation-coordinator.ts +433 -0
  120. package/src/router/server/fs-router-scanner.test.ts +83 -0
  121. package/src/router/{fs-router-scanner.ts → server/fs-router-scanner.ts} +12 -10
  122. package/src/router/server/fs-router.test.ts +214 -0
  123. package/src/router/{fs-router.ts → server/fs-router.ts} +2 -2
  124. package/src/services/README.md +29 -0
  125. package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +385 -0
  126. package/src/services/{asset-processing-service → assets/asset-processing-service}/asset-processing.service.ts +101 -6
  127. package/src/services/assets/asset-processing-service/asset.factory.test.ts +63 -0
  128. package/src/services/{asset-processing-service → assets/asset-processing-service}/asset.factory.ts +2 -2
  129. package/src/services/{asset-processing-service → assets/asset-processing-service}/assets.types.ts +2 -1
  130. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.test.ts +72 -0
  131. package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.ts +95 -0
  132. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.test.ts +67 -0
  133. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.ts +78 -0
  134. package/src/services/{asset-processing-service → assets/asset-processing-service}/index.ts +2 -0
  135. package/src/services/{asset-processing-service → assets/asset-processing-service}/processor.interface.ts +1 -1
  136. package/src/services/assets/asset-processing-service/processors/base/base-processor.test.ts +59 -0
  137. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/base/base-processor.ts +11 -5
  138. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/base/base-script-processor.ts +17 -27
  139. package/src/services/assets/asset-processing-service/processors/script/file-script.processor.test.ts +286 -0
  140. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/file-script.processor.ts +3 -3
  141. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.test.ts +227 -0
  142. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/node-module-script.processor.ts +5 -4
  143. package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.test.ts +199 -0
  144. package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/stylesheet/file-stylesheet.processor.ts +4 -1
  145. package/src/services/assets/browser-bundle.service.test.ts +36 -0
  146. package/src/services/assets/browser-bundle.service.ts +53 -0
  147. package/src/services/cache/index.ts +3 -3
  148. package/src/services/cache/memory-cache-store.test.ts +225 -0
  149. package/src/services/cache/memory-cache-store.ts +1 -1
  150. package/src/services/cache/page-cache-service.test.ts +175 -0
  151. package/src/services/cache/page-cache-service.ts +3 -3
  152. package/src/services/cache/page-request-cache-coordinator.service.test.ts +79 -0
  153. package/src/services/{page-request-cache-coordinator.service.ts → cache/page-request-cache-coordinator.service.ts} +9 -6
  154. package/src/services/html/html-rewriter-provider.service.test.ts +183 -0
  155. package/src/services/html/html-rewriter-provider.service.ts +103 -0
  156. package/src/services/html/html-transformer.service.test.ts +378 -0
  157. package/src/services/html/html-transformer.service.ts +279 -0
  158. package/src/services/invalidation/development-invalidation.service.test.ts +77 -0
  159. package/src/services/invalidation/development-invalidation.service.ts +261 -0
  160. package/src/services/module-loading/app-server-module-transpiler.service.ts +52 -0
  161. package/src/services/module-loading/page-module-import.service.test.ts +253 -0
  162. package/src/services/module-loading/page-module-import.service.ts +200 -0
  163. package/src/services/module-loading/server-loader.service.test.ts +161 -0
  164. package/src/services/module-loading/server-loader.service.ts +130 -0
  165. package/src/services/module-loading/server-module-transpiler.service.test.ts +115 -0
  166. package/src/services/module-loading/server-module-transpiler.service.ts +105 -0
  167. package/src/services/runtime-manifest/node-runtime-manifest.service.test.ts +95 -0
  168. package/src/services/runtime-manifest/node-runtime-manifest.service.ts +101 -0
  169. package/src/services/runtime-state/dev-graph.service.ts +217 -0
  170. package/src/services/runtime-state/entrypoint-dependency-graph.service.ts +136 -0
  171. package/src/services/runtime-state/runtime-specifier-registry.service.ts +96 -0
  172. package/src/services/runtime-state/server-invalidation-state.service.ts +68 -0
  173. package/src/services/validation/schema-validation-service.test.ts +223 -0
  174. package/src/services/{schema-validation-service.ts → validation/schema-validation-service.ts} +1 -1
  175. package/src/static-site-generator/README.md +26 -0
  176. package/src/static-site-generator/static-site-generator.test.ts +307 -0
  177. package/src/static-site-generator/static-site-generator.ts +109 -6
  178. package/src/utils/deep-merge.test.ts +114 -0
  179. package/src/utils/invariant.test.ts +22 -0
  180. package/src/utils/path-utils.test.ts +15 -0
  181. package/src/utils/resolve-work-dir.ts +45 -0
  182. package/src/utils/server-utils.test.ts +38 -0
  183. package/src/watchers/project-watcher.integration.test.ts +337 -0
  184. package/src/watchers/project-watcher.test-helpers.ts +1 -0
  185. package/src/watchers/project-watcher.test.ts +678 -0
  186. package/src/watchers/project-watcher.ts +49 -50
  187. package/CHANGELOG.md +0 -94
  188. package/src/adapters/abstract/application-adapter.d.ts +0 -168
  189. package/src/adapters/abstract/application-adapter.js +0 -109
  190. package/src/adapters/abstract/router-adapter.d.ts +0 -26
  191. package/src/adapters/abstract/router-adapter.js +0 -5
  192. package/src/adapters/abstract/server-adapter.d.ts +0 -69
  193. package/src/adapters/abstract/server-adapter.js +0 -15
  194. package/src/adapters/bun/client-bridge.d.ts +0 -34
  195. package/src/adapters/bun/client-bridge.js +0 -48
  196. package/src/adapters/bun/create-app.d.ts +0 -60
  197. package/src/adapters/bun/create-app.js +0 -117
  198. package/src/adapters/bun/define-api-handler.d.ts +0 -61
  199. package/src/adapters/bun/define-api-handler.js +0 -15
  200. package/src/adapters/bun/define-api-handler.ts +0 -114
  201. package/src/adapters/bun/hmr-manager.d.ts +0 -79
  202. package/src/adapters/bun/hmr-manager.js +0 -222
  203. package/src/adapters/bun/index.d.ts +0 -3
  204. package/src/adapters/bun/index.js +0 -8
  205. package/src/adapters/bun/server-adapter.d.ts +0 -155
  206. package/src/adapters/bun/server-adapter.js +0 -368
  207. package/src/adapters/bun/server-lifecycle.d.ts +0 -52
  208. package/src/adapters/bun/server-lifecycle.js +0 -120
  209. package/src/adapters/index.d.ts +0 -6
  210. package/src/adapters/index.js +0 -14
  211. package/src/adapters/node/create-app.d.ts +0 -21
  212. package/src/adapters/node/create-app.js +0 -143
  213. package/src/adapters/node/index.d.ts +0 -4
  214. package/src/adapters/node/index.js +0 -8
  215. package/src/adapters/node/node-client-bridge.d.ts +0 -26
  216. package/src/adapters/node/node-client-bridge.js +0 -66
  217. package/src/adapters/node/node-hmr-manager.d.ts +0 -62
  218. package/src/adapters/node/node-hmr-manager.js +0 -221
  219. package/src/adapters/node/server-adapter.d.ts +0 -190
  220. package/src/adapters/node/server-adapter.js +0 -420
  221. package/src/adapters/node/static-content-server.d.ts +0 -24
  222. package/src/adapters/node/static-content-server.js +0 -166
  223. package/src/adapters/shared/api-response.d.ts +0 -52
  224. package/src/adapters/shared/api-response.js +0 -96
  225. package/src/adapters/shared/application-adapter.d.ts +0 -18
  226. package/src/adapters/shared/application-adapter.js +0 -90
  227. package/src/adapters/shared/explicit-static-route-matcher.d.ts +0 -38
  228. package/src/adapters/shared/explicit-static-route-matcher.js +0 -100
  229. package/src/adapters/shared/file-route-middleware-pipeline.d.ts +0 -65
  230. package/src/adapters/shared/file-route-middleware-pipeline.js +0 -98
  231. package/src/adapters/shared/fs-server-response-factory.d.ts +0 -19
  232. package/src/adapters/shared/fs-server-response-factory.js +0 -97
  233. package/src/adapters/shared/fs-server-response-matcher.d.ts +0 -71
  234. package/src/adapters/shared/fs-server-response-matcher.js +0 -155
  235. package/src/adapters/shared/render-context.d.ts +0 -14
  236. package/src/adapters/shared/render-context.js +0 -69
  237. package/src/adapters/shared/server-adapter.d.ts +0 -87
  238. package/src/adapters/shared/server-adapter.js +0 -353
  239. package/src/adapters/shared/server-route-handler.d.ts +0 -89
  240. package/src/adapters/shared/server-route-handler.js +0 -120
  241. package/src/adapters/shared/server-static-builder.d.ts +0 -38
  242. package/src/adapters/shared/server-static-builder.js +0 -46
  243. package/src/build/build-adapter.d.ts +0 -75
  244. package/src/build/build-adapter.js +0 -54
  245. package/src/build/build-types.d.ts +0 -57
  246. package/src/build/build-types.js +0 -0
  247. package/src/build/esbuild-build-adapter.d.ts +0 -69
  248. package/src/build/esbuild-build-adapter.js +0 -391
  249. package/src/config/config-builder.d.ts +0 -227
  250. package/src/config/config-builder.js +0 -392
  251. package/src/constants.d.ts +0 -32
  252. package/src/constants.js +0 -21
  253. package/src/create-app.d.ts +0 -17
  254. package/src/create-app.js +0 -66
  255. package/src/define-api-handler.d.ts +0 -25
  256. package/src/define-api-handler.js +0 -15
  257. package/src/dev/sc-server.d.ts +0 -30
  258. package/src/dev/sc-server.js +0 -111
  259. package/src/eco/component-render-context.d.ts +0 -105
  260. package/src/eco/component-render-context.js +0 -77
  261. package/src/eco/eco.d.ts +0 -9
  262. package/src/eco/eco.js +0 -110
  263. package/src/eco/eco.types.d.ts +0 -170
  264. package/src/eco/eco.types.js +0 -0
  265. package/src/eco/eco.utils.d.ts +0 -40
  266. package/src/eco/eco.utils.js +0 -40
  267. package/src/eco/global-injector-map.d.ts +0 -16
  268. package/src/eco/global-injector-map.js +0 -80
  269. package/src/eco/lazy-injector-map.d.ts +0 -8
  270. package/src/eco/lazy-injector-map.js +0 -70
  271. package/src/eco/module-dependencies.d.ts +0 -18
  272. package/src/eco/module-dependencies.js +0 -49
  273. package/src/errors/http-error.d.ts +0 -31
  274. package/src/errors/http-error.js +0 -50
  275. package/src/errors/index.d.ts +0 -2
  276. package/src/errors/index.js +0 -4
  277. package/src/errors/locals-access-error.d.ts +0 -4
  278. package/src/errors/locals-access-error.js +0 -9
  279. package/src/global/app-logger.d.ts +0 -2
  280. package/src/global/app-logger.js +0 -6
  281. package/src/hmr/client/hmr-runtime.d.ts +0 -10
  282. package/src/hmr/client/hmr-runtime.js +0 -86
  283. package/src/hmr/hmr-strategy.d.ts +0 -159
  284. package/src/hmr/hmr-strategy.js +0 -29
  285. package/src/hmr/hmr.test.e2e.d.ts +0 -1
  286. package/src/hmr/hmr.test.e2e.js +0 -50
  287. package/src/hmr/strategies/default-hmr-strategy.d.ts +0 -43
  288. package/src/hmr/strategies/default-hmr-strategy.js +0 -34
  289. package/src/hmr/strategies/js-hmr-strategy.d.ts +0 -136
  290. package/src/hmr/strategies/js-hmr-strategy.js +0 -192
  291. package/src/index.browser.d.ts +0 -3
  292. package/src/index.browser.js +0 -4
  293. package/src/index.d.ts +0 -5
  294. package/src/index.js +0 -10
  295. package/src/integrations/ghtml/ghtml-renderer.d.ts +0 -15
  296. package/src/integrations/ghtml/ghtml-renderer.js +0 -60
  297. package/src/integrations/ghtml/ghtml.plugin.d.ts +0 -20
  298. package/src/integrations/ghtml/ghtml.plugin.js +0 -21
  299. package/src/internal-types.d.ts +0 -200
  300. package/src/internal-types.js +0 -0
  301. package/src/plugins/alias-resolver-plugin.d.ts +0 -2
  302. package/src/plugins/alias-resolver-plugin.js +0 -39
  303. package/src/plugins/eco-component-meta-plugin.d.ts +0 -95
  304. package/src/plugins/eco-component-meta-plugin.js +0 -157
  305. package/src/plugins/integration-plugin.d.ts +0 -102
  306. package/src/plugins/integration-plugin.js +0 -100
  307. package/src/plugins/processor.d.ts +0 -82
  308. package/src/plugins/processor.js +0 -122
  309. package/src/public-types.d.ts +0 -1094
  310. package/src/public-types.js +0 -0
  311. package/src/route-renderer/component-graph-executor.d.ts +0 -32
  312. package/src/route-renderer/component-graph-executor.js +0 -31
  313. package/src/route-renderer/component-graph.d.ts +0 -42
  314. package/src/route-renderer/component-graph.js +0 -72
  315. package/src/route-renderer/component-marker.d.ts +0 -52
  316. package/src/route-renderer/component-marker.js +0 -46
  317. package/src/route-renderer/dependency-resolver.d.ts +0 -24
  318. package/src/route-renderer/dependency-resolver.js +0 -428
  319. package/src/route-renderer/html-post-processing.service.d.ts +0 -40
  320. package/src/route-renderer/html-post-processing.service.js +0 -86
  321. package/src/route-renderer/html-post-processing.service.ts +0 -103
  322. package/src/route-renderer/integration-renderer.d.ts +0 -339
  323. package/src/route-renderer/integration-renderer.js +0 -526
  324. package/src/route-renderer/marker-graph-resolver.d.ts +0 -76
  325. package/src/route-renderer/marker-graph-resolver.js +0 -93
  326. package/src/route-renderer/page-module-loader.d.ts +0 -61
  327. package/src/route-renderer/page-module-loader.js +0 -102
  328. package/src/route-renderer/render-execution.service.d.ts +0 -69
  329. package/src/route-renderer/render-execution.service.js +0 -91
  330. package/src/route-renderer/render-preparation.service.d.ts +0 -112
  331. package/src/route-renderer/render-preparation.service.js +0 -243
  332. package/src/route-renderer/route-renderer.d.ts +0 -26
  333. package/src/route-renderer/route-renderer.js +0 -68
  334. package/src/router/fs-router-scanner.d.ts +0 -41
  335. package/src/router/fs-router-scanner.js +0 -155
  336. package/src/router/fs-router.d.ts +0 -26
  337. package/src/router/fs-router.js +0 -100
  338. package/src/services/asset-processing-service/asset-processing.service.d.ts +0 -41
  339. package/src/services/asset-processing-service/asset-processing.service.js +0 -250
  340. package/src/services/asset-processing-service/asset.factory.d.ts +0 -17
  341. package/src/services/asset-processing-service/asset.factory.js +0 -82
  342. package/src/services/asset-processing-service/assets.types.d.ts +0 -88
  343. package/src/services/asset-processing-service/assets.types.js +0 -0
  344. package/src/services/asset-processing-service/index.d.ts +0 -3
  345. package/src/services/asset-processing-service/index.js +0 -3
  346. package/src/services/asset-processing-service/processor.interface.d.ts +0 -22
  347. package/src/services/asset-processing-service/processor.interface.js +0 -6
  348. package/src/services/asset-processing-service/processor.registry.d.ts +0 -8
  349. package/src/services/asset-processing-service/processor.registry.js +0 -15
  350. package/src/services/asset-processing-service/processors/base/base-processor.d.ts +0 -24
  351. package/src/services/asset-processing-service/processors/base/base-processor.js +0 -59
  352. package/src/services/asset-processing-service/processors/base/base-script-processor.d.ts +0 -16
  353. package/src/services/asset-processing-service/processors/base/base-script-processor.js +0 -80
  354. package/src/services/asset-processing-service/processors/index.d.ts +0 -5
  355. package/src/services/asset-processing-service/processors/index.js +0 -5
  356. package/src/services/asset-processing-service/processors/script/content-script.processor.d.ts +0 -5
  357. package/src/services/asset-processing-service/processors/script/content-script.processor.js +0 -57
  358. package/src/services/asset-processing-service/processors/script/file-script.processor.d.ts +0 -8
  359. package/src/services/asset-processing-service/processors/script/file-script.processor.js +0 -76
  360. package/src/services/asset-processing-service/processors/script/node-module-script.processor.d.ts +0 -7
  361. package/src/services/asset-processing-service/processors/script/node-module-script.processor.js +0 -74
  362. package/src/services/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +0 -5
  363. package/src/services/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +0 -25
  364. package/src/services/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +0 -9
  365. package/src/services/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +0 -63
  366. package/src/services/cache/cache.types.d.ts +0 -107
  367. package/src/services/cache/cache.types.js +0 -0
  368. package/src/services/cache/index.d.ts +0 -7
  369. package/src/services/cache/index.js +0 -7
  370. package/src/services/cache/memory-cache-store.d.ts +0 -42
  371. package/src/services/cache/memory-cache-store.js +0 -98
  372. package/src/services/cache/page-cache-service.d.ts +0 -70
  373. package/src/services/cache/page-cache-service.js +0 -152
  374. package/src/services/html-transformer.service.d.ts +0 -50
  375. package/src/services/html-transformer.service.js +0 -163
  376. package/src/services/html-transformer.service.ts +0 -217
  377. package/src/services/page-module-import.service.d.ts +0 -37
  378. package/src/services/page-module-import.service.js +0 -88
  379. package/src/services/page-module-import.service.ts +0 -129
  380. package/src/services/page-request-cache-coordinator.service.d.ts +0 -75
  381. package/src/services/page-request-cache-coordinator.service.js +0 -107
  382. package/src/services/schema-validation-service.d.ts +0 -122
  383. package/src/services/schema-validation-service.js +0 -101
  384. package/src/services/validation/standard-schema.types.d.ts +0 -65
  385. package/src/services/validation/standard-schema.types.js +0 -0
  386. package/src/static-site-generator/static-site-generator.d.ts +0 -57
  387. package/src/static-site-generator/static-site-generator.js +0 -272
  388. package/src/utils/css.d.ts +0 -1
  389. package/src/utils/css.js +0 -7
  390. package/src/utils/deep-merge.d.ts +0 -14
  391. package/src/utils/deep-merge.js +0 -32
  392. package/src/utils/hash.d.ts +0 -1
  393. package/src/utils/hash.js +0 -7
  394. package/src/utils/html.d.ts +0 -1
  395. package/src/utils/html.js +0 -4
  396. package/src/utils/invariant.d.ts +0 -5
  397. package/src/utils/invariant.js +0 -11
  398. package/src/utils/locals-utils.d.ts +0 -15
  399. package/src/utils/locals-utils.js +0 -24
  400. package/src/utils/parse-cli-args.d.ts +0 -24
  401. package/src/utils/parse-cli-args.js +0 -47
  402. package/src/utils/path-utils.module.d.ts +0 -5
  403. package/src/utils/path-utils.module.js +0 -14
  404. package/src/utils/runtime.d.ts +0 -11
  405. package/src/utils/runtime.js +0 -40
  406. package/src/utils/server-utils.module.d.ts +0 -19
  407. package/src/utils/server-utils.module.js +0 -56
  408. package/src/watchers/project-watcher.d.ts +0 -132
  409. package/src/watchers/project-watcher.js +0 -281
  410. package/src/watchers/project-watcher.test-helpers.d.ts +0 -4
  411. package/src/watchers/project-watcher.test-helpers.js +0 -51
  412. /package/src/route-renderer/{component-graph-executor.ts → component-graph/component-graph-executor.ts} +0 -0
  413. /package/src/route-renderer/{component-graph.ts → component-graph/component-graph.ts} +0 -0
  414. /package/src/route-renderer/{component-marker.ts → component-graph/component-marker.ts} +0 -0
  415. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processor.registry.ts +0 -0
  416. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/index.ts +0 -0
  417. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/script/content-script.processor.ts +0 -0
  418. /package/src/services/{asset-processing-service → assets/asset-processing-service}/processors/stylesheet/content-stylesheet.processor.ts +0 -0
package/src/eco/eco.ts CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  import type {
7
7
  EcoComponent,
8
+ EcoHtmlComponent,
9
+ EcoLayoutComponent,
8
10
  EcoPagesElement,
9
11
  EcoPageComponent,
10
12
  GetMetadata,
@@ -18,6 +20,8 @@ import type { CacheStrategy } from '../services/cache/cache.types.ts';
18
20
  import type {
19
21
  ComponentOptions,
20
22
  Eco,
23
+ HtmlOptions,
24
+ LayoutOptions,
21
25
  PageOptions,
22
26
  PageOptionsBase,
23
27
  PagePropsFor,
@@ -25,7 +29,8 @@ import type {
25
29
  PageRequires,
26
30
  } from './eco.types.ts';
27
31
  import { createNodeId, createPropsRef, createSlotRef, getComponentRenderContext } from './component-render-context.ts';
28
- import { createComponentMarker, parseComponentMarkers } from '../route-renderer/component-marker.ts';
32
+ import { createComponentMarker, parseComponentMarkers } from '../route-renderer/component-graph/component-marker.ts';
33
+ import { getComponentReference } from '../route-renderer/component-graph/component-reference.ts';
29
34
  import { addTriggerAttribute, isThenable, wrapWithScriptsInjector } from './eco.utils.ts';
30
35
 
31
36
  /**
@@ -57,13 +62,7 @@ function createComponentFactory<P, E>(options: ComponentOptions<P, E>): EcoCompo
57
62
  if (shouldEmitMarker && renderContext) {
58
63
  const nodeId = createNodeId(renderContext);
59
64
  const propsRef = createPropsRef(renderContext);
60
- const componentRef = comp.config?.__eco?.id ?? comp.config?.__eco?.file;
61
-
62
- if (!componentRef) {
63
- throw new Error(
64
- '[ecopages] Missing component reference metadata for cross-integration marker emission.',
65
- );
66
- }
65
+ const componentRef = getComponentReference(comp);
67
66
 
68
67
  const componentProps = (props ?? {}) as Record<string, unknown>;
69
68
  renderContext.propsByRef[propsRef] = componentProps;
@@ -129,6 +128,26 @@ function component<P = {}, E = EcoPagesElement>(options: ComponentOptions<P, E>)
129
128
  return createComponentFactory(options);
130
129
  }
131
130
 
131
+ /**
132
+ * Creates a document shell component.
133
+ *
134
+ * Phase 1 keeps this as a semantic alias over eco.component() so existing
135
+ * renderer behavior remains unchanged.
136
+ */
137
+ function html<E = EcoPagesElement>(options: HtmlOptions<E>): EcoHtmlComponent<E> {
138
+ return createComponentFactory(options) as EcoHtmlComponent<E>;
139
+ }
140
+
141
+ /**
142
+ * Creates a route layout component.
143
+ *
144
+ * Phase 1 keeps this as a semantic alias over eco.component() so existing
145
+ * renderer behavior remains unchanged.
146
+ */
147
+ function layout<E = EcoPagesElement>(options: LayoutOptions<E>): EcoLayoutComponent<E> {
148
+ return createComponentFactory(options) as EcoLayoutComponent<E>;
149
+ }
150
+
132
151
  /**
133
152
  * Creates a page component with typed props and optional static helpers.
134
153
  */
@@ -214,6 +233,8 @@ function staticProps<P>(fn: GetStaticProps<P>): GetStaticProps<P> {
214
233
  */
215
234
  export const eco: Eco = {
216
235
  component,
236
+ html,
237
+ layout,
217
238
  page,
218
239
  metadata,
219
240
  staticPaths,
@@ -7,11 +7,16 @@ import type {
7
7
  DependencyLazyTrigger,
8
8
  EcoComponent,
9
9
  EcoComponentDependencies,
10
+ EcoHtmlComponent,
10
11
  EcoInjectedMeta,
12
+ EcoLayoutComponent,
13
+ EcoPageLayoutComponent,
11
14
  EcoPagesElement,
12
15
  GetMetadata,
13
16
  GetStaticPaths,
14
17
  GetStaticProps,
18
+ HtmlTemplateProps,
19
+ LayoutProps,
15
20
  Middleware,
16
21
  RequestLocals,
17
22
  RequestPageContext,
@@ -45,6 +50,10 @@ export interface ComponentOptions<P, E = EcoPagesElement> {
45
50
  render: (props: P) => E | Promise<E>;
46
51
  }
47
52
 
53
+ export type HtmlOptions<E = EcoPagesElement> = ComponentOptions<HtmlTemplateProps, E>;
54
+
55
+ export type LayoutOptions<E = EcoPagesElement> = ComponentOptions<LayoutProps<E>, E>;
56
+
48
57
  /**
49
58
  * Base options shared by all page variants
50
59
  */
@@ -53,7 +62,7 @@ export interface PageOptionsBase<T, E = EcoPagesElement> {
53
62
  __eco?: EcoInjectedMeta;
54
63
  integration?: string;
55
64
  dependencies?: EcoComponentDependencies;
56
- layout?: EcoComponent<{ children: E } & Partial<RequestPageContext>>;
65
+ layout?: EcoPageLayoutComponent<E>;
57
66
 
58
67
  /**
59
68
  * Define static paths for dynamic routes (e.g., [slug].tsx).
@@ -169,6 +178,16 @@ export interface Eco {
169
178
  */
170
179
  component: <P = {}, E = EcoPagesElement>(options: ComponentOptions<P, E>) => EcoComponent<P, E>;
171
180
 
181
+ /**
182
+ * Create a document shell component for the HTML wrapper.
183
+ */
184
+ html: <E = EcoPagesElement>(options: HtmlOptions<E>) => EcoHtmlComponent<E>;
185
+
186
+ /**
187
+ * Create a route layout component.
188
+ */
189
+ layout: <E = EcoPagesElement>(options: LayoutOptions<E>) => EcoLayoutComponent<E>;
190
+
172
191
  /**
173
192
  * Create a page component with type-safe props from getStaticProps.
174
193
  * Returns an EcoPageComponent with attached staticPaths, staticProps, and metadata.
@@ -0,0 +1,124 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { addTriggerAttribute, isThenable } from './eco.utils.ts';
3
+
4
+ const TRIGGER_ID = 'eco-trigger-abc123';
5
+
6
+ describe('addTriggerAttribute', () => {
7
+ describe('basic element injection', () => {
8
+ test('injects into a plain div', () => {
9
+ const result = addTriggerAttribute('<div>content</div>', TRIGGER_ID);
10
+ expect(result).toBe(`<div data-eco-trigger="${TRIGGER_ID}">content</div>`);
11
+ });
12
+
13
+ test('injects into a custom element', () => {
14
+ const result = addTriggerAttribute('<my-counter value="0"></my-counter>', TRIGGER_ID);
15
+ expect(result).toBe(`<my-counter data-eco-trigger="${TRIGGER_ID}" value="0"></my-counter>`);
16
+ });
17
+
18
+ test('injects into an element that already has attributes', () => {
19
+ const result = addTriggerAttribute('<div class="foo" id="bar">content</div>', TRIGGER_ID);
20
+ expect(result).toBe(`<div data-eco-trigger="${TRIGGER_ID}" class="foo" id="bar">content</div>`);
21
+ });
22
+
23
+ test('injects into a self-closing void element', () => {
24
+ const result = addTriggerAttribute('<img src="x.png" alt="" />', TRIGGER_ID);
25
+ expect(result).toBe(`<img data-eco-trigger="${TRIGGER_ID}" src="x.png" alt="" />`);
26
+ });
27
+
28
+ test('injects into an element with no attributes and immediate close', () => {
29
+ const result = addTriggerAttribute('<section></section>', TRIGGER_ID);
30
+ expect(result).toBe(`<section data-eco-trigger="${TRIGGER_ID}"></section>`);
31
+ });
32
+ });
33
+
34
+ describe('leading non-element nodes are skipped', () => {
35
+ test('skips leading whitespace and injects into the first element', () => {
36
+ const result = addTriggerAttribute(' \n <div>content</div>', TRIGGER_ID);
37
+ expect(result).toBe(` \n <div data-eco-trigger="${TRIGGER_ID}">content</div>`);
38
+ });
39
+
40
+ test('skips a leading HTML comment', () => {
41
+ const result = addTriggerAttribute('<!-- wrapper --><div>content</div>', TRIGGER_ID);
42
+ expect(result).toBe(`<!-- wrapper --><div data-eco-trigger="${TRIGGER_ID}">content</div>`);
43
+ });
44
+
45
+ test('skips multiple leading HTML comments', () => {
46
+ const result = addTriggerAttribute('<!-- a --><!-- b --><span>text</span>', TRIGGER_ID);
47
+ expect(result).toBe(`<!-- a --><!-- b --><span data-eco-trigger="${TRIGGER_ID}">text</span>`);
48
+ });
49
+
50
+ test('skips a leading doctype declaration', () => {
51
+ const result = addTriggerAttribute('<!DOCTYPE html><html lang="en"></html>', TRIGGER_ID);
52
+ expect(result).toBe(`<!DOCTYPE html><html data-eco-trigger="${TRIGGER_ID}" lang="en"></html>`);
53
+ });
54
+
55
+ test('skips a leading XML processing instruction', () => {
56
+ const result = addTriggerAttribute('<?xml version="1.0"?><root/>', TRIGGER_ID);
57
+ expect(result).toBe(`<?xml version="1.0"?><root data-eco-trigger="${TRIGGER_ID}"/>`);
58
+ });
59
+
60
+ test('skips comment then whitespace then element', () => {
61
+ const result = addTriggerAttribute('<!-- note -->\n<article>body</article>', TRIGGER_ID);
62
+ expect(result).toBe(`<!-- note -->\n<article data-eco-trigger="${TRIGGER_ID}">body</article>`);
63
+ });
64
+ });
65
+
66
+ describe('only the first element is modified', () => {
67
+ test('does not inject into sibling elements', () => {
68
+ const result = addTriggerAttribute('<div>first</div><div>second</div>', TRIGGER_ID);
69
+ expect(result).toBe(`<div data-eco-trigger="${TRIGGER_ID}">first</div><div>second</div>`);
70
+ });
71
+ });
72
+
73
+ describe('fallback when no element is found', () => {
74
+ test('returns the original string unchanged when there is no opening tag', () => {
75
+ const input = 'just some text';
76
+ expect(addTriggerAttribute(input, TRIGGER_ID)).toBe(input);
77
+ });
78
+
79
+ test('returns the original string unchanged when input is empty', () => {
80
+ expect(addTriggerAttribute('', TRIGGER_ID)).toBe('');
81
+ });
82
+ });
83
+
84
+ describe('non-string input coercion', () => {
85
+ test('coerces a number to string before injecting', () => {
86
+ const result = addTriggerAttribute(42, TRIGGER_ID);
87
+ expect(result).toBe('42');
88
+ });
89
+
90
+ test('coerces null to the string "null"', () => {
91
+ expect(addTriggerAttribute(null, TRIGGER_ID)).toBe('null');
92
+ });
93
+ });
94
+ });
95
+
96
+ describe('isThenable', () => {
97
+ test('returns true for a native Promise', () => {
98
+ expect(isThenable(Promise.resolve())).toBe(true);
99
+ });
100
+
101
+ test('returns true for a plain object with a then function', () => {
102
+ expect(isThenable({ then: () => {} })).toBe(true);
103
+ });
104
+
105
+ test('returns false for a plain string', () => {
106
+ expect(isThenable('hello')).toBe(false);
107
+ });
108
+
109
+ test('returns false for a number', () => {
110
+ expect(isThenable(42)).toBe(false);
111
+ });
112
+
113
+ test('returns false for null', () => {
114
+ expect(isThenable(null)).toBe(false);
115
+ });
116
+
117
+ test('returns false for an object without then', () => {
118
+ expect(isThenable({ value: 1 })).toBe(false);
119
+ });
120
+
121
+ test('returns false when then is not a function', () => {
122
+ expect(isThenable({ then: 'not-a-function' })).toBe(false);
123
+ });
124
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { buildGlobalInjectorBootstrapContent, buildGlobalInjectorMapScript } from './global-injector-map.ts';
3
+ import type { ResolvedLazyTrigger } from '../public-types.ts';
4
+
5
+ describe('buildGlobalInjectorMapScript', () => {
6
+ test('builds merged map by trigger id and escapes script closing tags', () => {
7
+ const triggers: ResolvedLazyTrigger[] = [
8
+ {
9
+ triggerId: 'eco-trigger-one',
10
+ rules: [
11
+ { 'on:interaction': { value: 'click', scripts: ['/a.js', '/b</script>.js'] } },
12
+ { 'on:idle': { scripts: ['/idle.js'] } },
13
+ ],
14
+ },
15
+ {
16
+ triggerId: 'eco-trigger-one',
17
+ rules: [{ 'on:interaction': { value: 'click', scripts: ['/a.js', '/c.js'] } }],
18
+ },
19
+ ];
20
+
21
+ const payload = buildGlobalInjectorMapScript(triggers);
22
+ expect(payload).toContain('<\\/script>');
23
+
24
+ const map = JSON.parse(payload) as Record<string, Record<string, { scripts: string[]; value?: string }>>;
25
+ expect(map['eco-trigger-one']?.['on:idle']?.scripts).toEqual(['/idle.js']);
26
+ expect(map['eco-trigger-one']?.['on:interaction']).toEqual({
27
+ value: 'click',
28
+ scripts: ['/a.js', '/b</script>.js', '/c.js'],
29
+ });
30
+ });
31
+ });
32
+
33
+ describe('buildGlobalInjectorBootstrapContent', () => {
34
+ test('binds refresh to after-swap and avoids before-swap cleanup listener', () => {
35
+ const bootstrap = buildGlobalInjectorBootstrapContent('/assets/injector-global.js');
36
+
37
+ expect(bootstrap).toContain("document.addEventListener('eco:after-swap', handleAfterSwap);");
38
+ expect(bootstrap).not.toContain("document.addEventListener('eco:before-swap'");
39
+ expect(bootstrap).not.toContain('const handleBeforeSwap');
40
+ expect(bootstrap).toContain("document.removeEventListener('eco:after-swap', handleAfterSwap);");
41
+ });
42
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { buildInjectorMapScript } from './lazy-injector-map.ts';
3
+ import type { InjectorMapConfig } from '@ecopages/scripts-injector/types';
4
+ import type { ResolvedLazyScriptGroup } from '../public-types.ts';
5
+
6
+ describe('buildInjectorMapScript', () => {
7
+ test('builds injector map for mixed lazy triggers', () => {
8
+ const lazyGroups: ResolvedLazyScriptGroup[] = [
9
+ { lazy: { 'on:idle': true }, scripts: './idle-a.js, /idle-b.js' },
10
+ { lazy: { 'on:interaction': 'click' }, scripts: './int-a.js' },
11
+ { lazy: { 'on:visible': '0.5' }, scripts: '/vis-a.js' },
12
+ ];
13
+
14
+ const result = buildInjectorMapScript(lazyGroups);
15
+ const map = JSON.parse(result) as InjectorMapConfig;
16
+
17
+ expect(map['on:idle']).toEqual({ scripts: ['/idle-a.js', '/idle-b.js'] });
18
+ expect(map['on:interaction']).toEqual({
19
+ value: 'click',
20
+ scripts: ['/int-a.js'],
21
+ });
22
+ expect(map['on:visible']).toEqual({
23
+ value: '0.5',
24
+ scripts: ['/vis-a.js'],
25
+ });
26
+ });
27
+
28
+ test('dedupes scripts and merges interaction events while preserving order', () => {
29
+ const lazyGroups: ResolvedLazyScriptGroup[] = [
30
+ { lazy: { 'on:interaction': 'click,mouseenter' }, scripts: './one.js,./two.js' },
31
+ { lazy: { 'on:interaction': 'mouseenter,focusin' }, scripts: '/two.js,/three.js' },
32
+ ];
33
+
34
+ const result = buildInjectorMapScript(lazyGroups);
35
+ const map = JSON.parse(result) as InjectorMapConfig;
36
+
37
+ expect(map['on:interaction']).toEqual({
38
+ value: 'click,mouseenter,focusin',
39
+ scripts: ['/one.js', '/two.js', '/three.js'],
40
+ });
41
+ });
42
+
43
+ test('keeps existing on:visible threshold when a later boolean trigger is encountered', () => {
44
+ const lazyGroups: ResolvedLazyScriptGroup[] = [
45
+ { lazy: { 'on:visible': '100px' }, scripts: './threshold.js' },
46
+ { lazy: { 'on:visible': true }, scripts: './fallback.js' },
47
+ ];
48
+
49
+ const result = buildInjectorMapScript(lazyGroups);
50
+ const map = JSON.parse(result) as InjectorMapConfig;
51
+
52
+ expect(map['on:visible']).toEqual({
53
+ value: '100px',
54
+ scripts: ['/threshold.js', '/fallback.js'],
55
+ });
56
+ });
57
+
58
+ test('escapes closing script tags in output payload', () => {
59
+ const lazyGroups: ResolvedLazyScriptGroup[] = [
60
+ { lazy: { 'on:idle': true }, scripts: '/safe.js, /x</script>y.js' },
61
+ ];
62
+
63
+ const result = buildInjectorMapScript(lazyGroups);
64
+ expect(result).toContain('<\\/script>');
65
+ });
66
+ });
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { normalizeModuleDeclarations, parseModuleDeclaration } from './module-dependencies.ts';
3
+
4
+ describe('module dependencies parser', () => {
5
+ test('parses simple module declaration', () => {
6
+ expect(parseModuleDeclaration('react-aria-components')).toEqual({
7
+ from: 'react-aria-components',
8
+ });
9
+ });
10
+
11
+ test('parses declaration with named imports', () => {
12
+ expect(parseModuleDeclaration('react-aria-components{Table,Select}')).toEqual({
13
+ from: 'react-aria-components',
14
+ imports: ['Table', 'Select'],
15
+ });
16
+ });
17
+
18
+ test('normalizes and deduplicates module declarations', () => {
19
+ expect(
20
+ normalizeModuleDeclarations([
21
+ 'react-aria-components{Table,Select}',
22
+ 'react-aria-components{Table,Select}',
23
+ 'lodash-es{debounce}',
24
+ ]),
25
+ ).toEqual([
26
+ { from: 'react-aria-components', imports: ['Table', 'Select'] },
27
+ { from: 'lodash-es', imports: ['debounce'] },
28
+ ]);
29
+ });
30
+ });
@@ -0,0 +1,134 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { HttpError } from './http-error.ts';
3
+
4
+ describe('HttpError', () => {
5
+ describe('constructor', () => {
6
+ test('creates error with status and message', () => {
7
+ const error = new HttpError(400, 'Bad request');
8
+
9
+ expect(error).toBeInstanceOf(Error);
10
+ expect(error).toBeInstanceOf(HttpError);
11
+ expect(error.status).toBe(400);
12
+ expect(error.message).toBe('Bad request');
13
+ expect(error.name).toBe('HttpError');
14
+ expect(error.details).toBeUndefined();
15
+ });
16
+
17
+ test('creates error with details', () => {
18
+ const details = { field: 'email', reason: 'invalid format' };
19
+ const error = new HttpError(400, 'Validation failed', details);
20
+
21
+ expect(error.details).toEqual(details);
22
+ });
23
+ });
24
+
25
+ describe('toJSON', () => {
26
+ test('serializes error without details', () => {
27
+ const error = new HttpError(404, 'Not found');
28
+
29
+ expect(error.toJSON()).toEqual({
30
+ error: 'Not found',
31
+ status: 404,
32
+ });
33
+ });
34
+
35
+ test('serializes error with details', () => {
36
+ const details = { body: [{ path: ['title'], message: 'Required' }] };
37
+ const error = new HttpError(400, 'Validation failed', details);
38
+
39
+ expect(error.toJSON()).toEqual({
40
+ error: 'Validation failed',
41
+ status: 400,
42
+ details,
43
+ });
44
+ });
45
+ });
46
+
47
+ describe('toResponse', () => {
48
+ test('creates Response with correct status and body', async () => {
49
+ const error = new HttpError(403, 'Access denied');
50
+ const response = error.toResponse();
51
+
52
+ expect(response).toBeInstanceOf(Response);
53
+ expect(response.status).toBe(403);
54
+ expect(response.headers.get('content-type')).toContain('application/json');
55
+
56
+ const body = await response.json();
57
+ expect(body).toEqual({ error: 'Access denied', status: 403 });
58
+ });
59
+ });
60
+
61
+ describe('factory methods', () => {
62
+ test('BadRequest creates 400 error', () => {
63
+ const error = HttpError.BadRequest();
64
+ expect(error.status).toBe(400);
65
+ expect(error.message).toBe('Bad Request');
66
+ });
67
+
68
+ test('BadRequest accepts custom message and details', () => {
69
+ const details = { field: 'name' };
70
+ const error = HttpError.BadRequest('Invalid input', details);
71
+
72
+ expect(error.message).toBe('Invalid input');
73
+ expect(error.details).toEqual(details);
74
+ });
75
+
76
+ test('Unauthorized creates 401 error', () => {
77
+ const error = HttpError.Unauthorized();
78
+ expect(error.status).toBe(401);
79
+ expect(error.message).toBe('Unauthorized');
80
+ });
81
+
82
+ test('Unauthorized accepts custom message', () => {
83
+ const error = HttpError.Unauthorized('Token expired');
84
+ expect(error.message).toBe('Token expired');
85
+ });
86
+
87
+ test('Forbidden creates 403 error', () => {
88
+ const error = HttpError.Forbidden();
89
+ expect(error.status).toBe(403);
90
+ expect(error.message).toBe('Forbidden');
91
+ });
92
+
93
+ test('Forbidden accepts custom message', () => {
94
+ const error = HttpError.Forbidden('Admin access required');
95
+ expect(error.message).toBe('Admin access required');
96
+ });
97
+
98
+ test('NotFound creates 404 error', () => {
99
+ const error = HttpError.NotFound();
100
+ expect(error.status).toBe(404);
101
+ expect(error.message).toBe('Not Found');
102
+ });
103
+
104
+ test('NotFound accepts custom message', () => {
105
+ const error = HttpError.NotFound('Post not found');
106
+ expect(error.message).toBe('Post not found');
107
+ });
108
+
109
+ test('Conflict creates 409 error', () => {
110
+ const error = HttpError.Conflict();
111
+ expect(error.status).toBe(409);
112
+ expect(error.message).toBe('Conflict');
113
+ });
114
+
115
+ test('Conflict accepts custom message and details', () => {
116
+ const details = { resource: 'user', conflict: 'email already exists' };
117
+ const error = HttpError.Conflict('Resource conflict', details);
118
+
119
+ expect(error.message).toBe('Resource conflict');
120
+ expect(error.details).toEqual(details);
121
+ });
122
+
123
+ test('InternalServerError creates 500 error', () => {
124
+ const error = HttpError.InternalServerError();
125
+ expect(error.status).toBe(500);
126
+ expect(error.message).toBe('Internal Server Error');
127
+ });
128
+
129
+ test('InternalServerError accepts custom message', () => {
130
+ const error = HttpError.InternalServerError('Database connection failed');
131
+ expect(error.message).toBe('Database connection failed');
132
+ });
133
+ });
134
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { invariant } from '../utils/invariant.ts';
3
+
4
+ describe('Utils', () => {
5
+ test('invariant should throw error when condition is falsy', () => {
6
+ expect(() => invariant(false, 'Test error')).toThrowError('[ecopages] Test error');
7
+ });
8
+
9
+ test('invariant should not throw error when condition is truthy', () => {
10
+ expect(() => invariant(true, 'Test error')).not.toThrow();
11
+ });
12
+ });
@@ -0,0 +1,26 @@
1
+ # HMR Layer
2
+
3
+ This directory contains the framework-owned hot-update strategy contracts used by runtime adapters and integrations.
4
+
5
+ ## Purpose
6
+
7
+ The HMR layer separates change classification from update execution.
8
+
9
+ It is responsible for:
10
+
11
+ - defining the shared HMR manager and strategy contracts
12
+ - letting integrations contribute framework-specific update strategies
13
+ - keeping adapter transports independent from update policy
14
+
15
+ ## How It Fits
16
+
17
+ 1. `ProjectWatcher` observes file changes.
18
+ 2. `DevelopmentInvalidationService` classifies the change.
19
+ 3. The active HMR manager selects a strategy.
20
+ 4. The strategy coordinates browser rebuilds, metadata reloads, and client broadcasts.
21
+
22
+ ## Design Rule
23
+
24
+ Generic invalidation policy belongs in core services.
25
+ Framework-specific update behavior belongs in HMR strategies.
26
+ Runtime-specific WebSocket or event-stream transport belongs in adapters.
@@ -3,6 +3,8 @@
3
3
  * Injected into the browser to handle Hot Module Replacement updates.
4
4
  */
5
5
 
6
+ import { getEcoNavigationRuntime } from '../../router/client/navigation-coordinator.ts';
7
+
6
8
  interface HMRPayload {
7
9
  type: 'reload' | 'error' | 'update' | 'css-update' | 'layout-update';
8
10
  path?: string;
@@ -41,14 +43,16 @@ interface HMRPayload {
41
43
  }
42
44
 
43
45
  async function handleMessage(payload: HMRPayload) {
46
+ const navigationRuntime = getEcoNavigationRuntime(window);
47
+
44
48
  switch (payload.type) {
45
49
  case 'reload':
50
+ await waitForNavigationToSettle(navigationRuntime);
46
51
  location.reload();
47
52
  break;
48
53
  case 'layout-update': {
49
- const reloadFn = window.__ecopages_reload_current_page__;
50
- if (typeof reloadFn === 'function') {
51
- await reloadFn({ clearCache: true });
54
+ await waitForNavigationToSettle(navigationRuntime);
55
+ if (await navigationRuntime.reloadCurrentPage({ clearCache: true })) {
52
56
  } else {
53
57
  location.reload();
54
58
  }
@@ -78,7 +82,9 @@ interface HMRPayload {
78
82
  async function applyUpdate(path: string, timestamp?: number) {
79
83
  try {
80
84
  const url = path + '?t=' + (timestamp || Date.now());
81
- const handlers = window.__ecopages_hmr_handlers__;
85
+ const handlers = window.__ECO_PAGES__?.hmrHandlers;
86
+ const navigationRuntime = getEcoNavigationRuntime(window);
87
+ await waitForNavigationToSettle(navigationRuntime);
82
88
 
83
89
  if (handlers?.[path]) {
84
90
  await handlers[path](url);
@@ -89,15 +95,40 @@ interface HMRPayload {
89
95
 
90
96
  // If we're inside the EcoRouter, we need to trigger a router navigation to render the new component.
91
97
  // Passing clearCache: false preserves the persisted layout cache.
92
- const reloadFn = window.__ecopages_reload_current_page__;
93
- if (typeof reloadFn === 'function') {
94
- await reloadFn({ clearCache: false });
98
+ if (await navigationRuntime.reloadCurrentPage({ clearCache: false })) {
95
99
  }
96
100
  } catch (e) {
97
101
  console.error('[ecopages] Failed to apply HMR update:', e);
98
102
  }
99
103
  }
100
104
 
105
+ async function waitForNavigationToSettle(navigationRuntime: ReturnType<typeof getEcoNavigationRuntime>) {
106
+ if (!navigationRuntime.hasPendingNavigationTransaction()) {
107
+ return;
108
+ }
109
+
110
+ await new Promise<void>((resolve) => {
111
+ const startedAt = performance.now();
112
+ const timeoutMs = 2000;
113
+
114
+ const poll = () => {
115
+ if (!navigationRuntime.hasPendingNavigationTransaction()) {
116
+ resolve();
117
+ return;
118
+ }
119
+
120
+ if (performance.now() - startedAt >= timeoutMs) {
121
+ resolve();
122
+ return;
123
+ }
124
+
125
+ requestAnimationFrame(poll);
126
+ };
127
+
128
+ requestAnimationFrame(poll);
129
+ });
130
+ }
131
+
101
132
  /**
102
133
  * Hot-reload CSS by updating stylesheet link href with cache-busting query param.
103
134
  * This causes the browser to re-fetch the stylesheet without a full page reload.