@emkodev/emroute 1.7.3 → 1.8.0-beta.2

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 (238) hide show
  1. package/README.md +1 -1
  2. package/core/component/abstract.component.ts +74 -0
  3. package/{src → core}/component/page.component.ts +3 -61
  4. package/core/component/widget.component.ts +54 -0
  5. package/core/pipeline/pipeline.ts +224 -0
  6. package/{src/renderer/ssr → core/renderer}/html.renderer.ts +26 -47
  7. package/{src/renderer/ssr → core/renderer}/md.renderer.ts +22 -41
  8. package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +44 -58
  9. package/{src/route → core/router}/route.resolver.ts +1 -10
  10. package/core/router/route.trie.ts +175 -0
  11. package/core/runtime/abstract.runtime.ts +47 -0
  12. package/core/server/emroute.server.ts +324 -0
  13. package/core/type/component.type.ts +39 -0
  14. package/core/type/element.type.ts +10 -0
  15. package/core/type/logger.type.ts +20 -0
  16. package/core/type/markdown.type.ts +8 -0
  17. package/core/type/route-tree.type.ts +28 -0
  18. package/core/type/route.type.ts +75 -0
  19. package/core/type/widget.type.ts +27 -0
  20. package/core/util/html.util.ts +50 -0
  21. package/{src → core}/util/md.util.ts +3 -5
  22. package/{src/route → core/util}/route-tree.util.ts +0 -2
  23. package/{src → core}/util/widget-resolve.util.ts +15 -46
  24. package/{src → core}/widget/widget.parser.ts +2 -23
  25. package/core/widget/widget.registry.ts +36 -0
  26. package/dist/core/component/abstract.component.d.ts +48 -0
  27. package/dist/core/component/abstract.component.js +42 -0
  28. package/dist/core/component/abstract.component.js.map +1 -0
  29. package/dist/core/component/page.component.d.ts +23 -0
  30. package/dist/core/component/page.component.js +49 -0
  31. package/dist/core/component/page.component.js.map +1 -0
  32. package/dist/core/component/widget.component.d.ts +17 -0
  33. package/dist/core/component/widget.component.js +37 -0
  34. package/dist/core/component/widget.component.js.map +1 -0
  35. package/dist/core/pipeline/pipeline.d.ts +61 -0
  36. package/dist/core/pipeline/pipeline.js +189 -0
  37. package/dist/core/pipeline/pipeline.js.map +1 -0
  38. package/dist/{src/renderer/ssr → core/renderer}/html.renderer.d.ts +8 -24
  39. package/dist/{src/renderer/ssr → core/renderer}/html.renderer.js +20 -35
  40. package/dist/core/renderer/html.renderer.js.map +1 -0
  41. package/dist/{src/renderer/ssr → core/renderer}/md.renderer.d.ts +6 -21
  42. package/dist/{src/renderer/ssr → core/renderer}/md.renderer.js +16 -32
  43. package/dist/core/renderer/md.renderer.js.map +1 -0
  44. package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -27
  45. package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +33 -37
  46. package/dist/core/renderer/ssr.renderer.js.map +1 -0
  47. package/dist/{src/route → core/router}/route.resolver.d.ts +1 -8
  48. package/dist/{src/route → core/router}/route.resolver.js +0 -1
  49. package/dist/core/router/route.resolver.js.map +1 -0
  50. package/dist/core/router/route.trie.d.ts +32 -0
  51. package/dist/core/router/route.trie.js +152 -0
  52. package/dist/core/router/route.trie.js.map +1 -0
  53. package/dist/core/runtime/abstract.runtime.d.ts +32 -0
  54. package/dist/core/runtime/abstract.runtime.js +26 -0
  55. package/dist/core/runtime/abstract.runtime.js.map +1 -0
  56. package/dist/core/server/emroute.server.d.ts +48 -0
  57. package/dist/core/server/emroute.server.js +239 -0
  58. package/dist/core/server/emroute.server.js.map +1 -0
  59. package/dist/core/server/server.type.d.ts +45 -0
  60. package/dist/core/server/server.type.js +11 -0
  61. package/dist/core/server/server.type.js.map +1 -0
  62. package/dist/core/type/component.type.d.ts +37 -0
  63. package/dist/core/type/component.type.js +7 -0
  64. package/dist/core/type/component.type.js.map +1 -0
  65. package/dist/core/type/element.type.d.ts +9 -0
  66. package/dist/core/type/element.type.js +5 -0
  67. package/dist/core/type/element.type.js.map +1 -0
  68. package/dist/core/type/logger.type.d.ts +14 -0
  69. package/dist/core/type/logger.type.js +8 -0
  70. package/dist/core/type/logger.type.js.map +1 -0
  71. package/dist/core/type/markdown.type.d.ts +7 -0
  72. package/dist/core/type/markdown.type.js +5 -0
  73. package/dist/core/type/markdown.type.js.map +1 -0
  74. package/dist/{src → core}/type/route-tree.type.d.ts +0 -12
  75. package/dist/{src → core}/type/route-tree.type.js +0 -1
  76. package/dist/core/type/route-tree.type.js.map +1 -0
  77. package/dist/core/type/route.type.d.ts +62 -0
  78. package/dist/core/type/route.type.js +7 -0
  79. package/dist/core/type/route.type.js.map +1 -0
  80. package/dist/core/type/widget.type.d.ts +27 -0
  81. package/dist/core/type/widget.type.js +5 -0
  82. package/dist/core/type/widget.type.js.map +1 -0
  83. package/dist/core/util/html.util.d.ts +14 -0
  84. package/dist/core/util/html.util.js +43 -0
  85. package/dist/core/util/html.util.js.map +1 -0
  86. package/dist/{src → core}/util/md.util.d.ts +0 -1
  87. package/dist/{src → core}/util/md.util.js +0 -2
  88. package/dist/core/util/md.util.js.map +1 -0
  89. package/dist/{src/route → core/util}/route-tree.util.js +0 -2
  90. package/dist/core/util/route-tree.util.js.map +1 -0
  91. package/dist/core/util/widget-resolve.util.d.ts +28 -0
  92. package/dist/{src → core}/util/widget-resolve.util.js +12 -42
  93. package/dist/core/util/widget-resolve.util.js.map +1 -0
  94. package/dist/{src → core}/widget/widget.parser.d.ts +0 -13
  95. package/dist/{src → core}/widget/widget.parser.js +1 -22
  96. package/dist/core/widget/widget.parser.js.map +1 -0
  97. package/dist/core/widget/widget.registry.d.ts +14 -0
  98. package/dist/core/widget/widget.registry.js +26 -0
  99. package/dist/core/widget/widget.registry.js.map +1 -0
  100. package/dist/emroute.js +1092 -1220
  101. package/dist/emroute.js.map +36 -5
  102. package/dist/runtime/abstract.runtime.d.ts +41 -7
  103. package/dist/runtime/abstract.runtime.js +404 -9
  104. package/dist/runtime/abstract.runtime.js.map +1 -1
  105. package/dist/runtime/bun/fs/bun-fs.runtime.d.ts +1 -0
  106. package/dist/runtime/bun/fs/bun-fs.runtime.js +15 -1
  107. package/dist/runtime/bun/fs/bun-fs.runtime.js.map +1 -1
  108. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.d.ts +2 -0
  109. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js +8 -0
  110. package/dist/runtime/bun/sqlite/bun-sqlite.runtime.js.map +1 -1
  111. package/dist/runtime/fetch.runtime.d.ts +3 -3
  112. package/dist/runtime/fetch.runtime.js +3 -3
  113. package/dist/runtime/sitemap.generator.d.ts +1 -1
  114. package/dist/runtime/sitemap.generator.js +1 -1
  115. package/dist/runtime/sitemap.generator.js.map +1 -1
  116. package/dist/runtime/universal/fs/universal-fs.runtime.d.ts +1 -0
  117. package/dist/runtime/universal/fs/universal-fs.runtime.js +15 -1
  118. package/dist/runtime/universal/fs/universal-fs.runtime.js.map +1 -1
  119. package/dist/server/build.util.d.ts +9 -10
  120. package/dist/server/build.util.js +11 -31
  121. package/dist/server/build.util.js.map +1 -1
  122. package/dist/server/codegen.util.d.ts +1 -1
  123. package/dist/server/emroute.server.d.ts +8 -35
  124. package/dist/server/emroute.server.js +7 -351
  125. package/dist/server/emroute.server.js.map +1 -1
  126. package/dist/server/esbuild-manifest.plugin.js +1 -1
  127. package/dist/server/esbuild-manifest.plugin.js.map +1 -1
  128. package/dist/server/server-api.type.d.ts +3 -71
  129. package/dist/server/server-api.type.js +1 -8
  130. package/dist/server/server-api.type.js.map +1 -1
  131. package/dist/src/element/component.element.d.ts +6 -14
  132. package/dist/src/element/component.element.js +13 -40
  133. package/dist/src/element/component.element.js.map +1 -1
  134. package/dist/src/element/markdown.element.d.ts +2 -2
  135. package/dist/src/element/markdown.element.js +3 -2
  136. package/dist/src/element/markdown.element.js.map +1 -1
  137. package/dist/src/index.d.ts +15 -14
  138. package/dist/src/index.js +8 -8
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/renderer/spa/emroute.app.d.ts +50 -0
  141. package/dist/src/renderer/spa/emroute.app.js +246 -0
  142. package/dist/src/renderer/spa/emroute.app.js.map +1 -0
  143. package/dist/src/renderer/spa/mod.d.ts +17 -16
  144. package/dist/src/renderer/spa/mod.js +9 -9
  145. package/dist/src/renderer/spa/mod.js.map +1 -1
  146. package/dist/src/renderer/spa/thin-client.d.ts +3 -3
  147. package/dist/src/renderer/spa/thin-client.js +7 -7
  148. package/dist/src/renderer/spa/thin-client.js.map +1 -1
  149. package/dist/src/route/route.core.d.ts +3 -3
  150. package/dist/src/util/html.util.d.ts +5 -22
  151. package/dist/src/util/html.util.js +8 -56
  152. package/dist/src/util/html.util.js.map +1 -1
  153. package/dist/src/widget/breadcrumb.widget.d.ts +2 -2
  154. package/dist/src/widget/breadcrumb.widget.js +2 -2
  155. package/dist/src/widget/breadcrumb.widget.js.map +1 -1
  156. package/dist/src/widget/page-title.widget.d.ts +1 -1
  157. package/dist/src/widget/page-title.widget.js +1 -1
  158. package/dist/src/widget/page-title.widget.js.map +1 -1
  159. package/package.json +8 -8
  160. package/runtime/abstract.runtime.ts +433 -17
  161. package/runtime/bun/fs/bun-fs.runtime.ts +15 -1
  162. package/runtime/bun/sqlite/bun-sqlite.runtime.ts +9 -0
  163. package/runtime/fetch.runtime.ts +3 -3
  164. package/runtime/sitemap.generator.ts +2 -2
  165. package/runtime/universal/fs/universal-fs.runtime.ts +15 -1
  166. package/server/build.util.ts +17 -43
  167. package/server/codegen.util.ts +1 -1
  168. package/server/emroute.server.ts +12 -426
  169. package/src/element/component.element.ts +14 -54
  170. package/src/element/markdown.element.ts +4 -3
  171. package/src/index.ts +22 -19
  172. package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +19 -20
  173. package/src/renderer/spa/mod.ts +22 -22
  174. package/src/util/html.util.ts +16 -61
  175. package/src/widget/breadcrumb.widget.ts +3 -3
  176. package/src/widget/page-title.widget.ts +1 -1
  177. package/dist/src/component/abstract.component.d.ts +0 -199
  178. package/dist/src/component/abstract.component.js +0 -84
  179. package/dist/src/component/abstract.component.js.map +0 -1
  180. package/dist/src/component/page.component.d.ts +0 -74
  181. package/dist/src/component/page.component.js +0 -107
  182. package/dist/src/component/page.component.js.map +0 -1
  183. package/dist/src/component/widget.component.d.ts +0 -47
  184. package/dist/src/component/widget.component.js +0 -69
  185. package/dist/src/component/widget.component.js.map +0 -1
  186. package/dist/src/renderer/ssr/html.renderer.js.map +0 -1
  187. package/dist/src/renderer/ssr/md.renderer.js.map +0 -1
  188. package/dist/src/renderer/ssr/ssr.renderer.js.map +0 -1
  189. package/dist/src/route/route-tree.util.js.map +0 -1
  190. package/dist/src/route/route.matcher.d.ts +0 -86
  191. package/dist/src/route/route.matcher.js +0 -214
  192. package/dist/src/route/route.matcher.js.map +0 -1
  193. package/dist/src/route/route.resolver.js.map +0 -1
  194. package/dist/src/route/route.trie.d.ts +0 -38
  195. package/dist/src/route/route.trie.js +0 -206
  196. package/dist/src/route/route.trie.js.map +0 -1
  197. package/dist/src/type/element.type.d.ts +0 -19
  198. package/dist/src/type/element.type.js +0 -9
  199. package/dist/src/type/element.type.js.map +0 -1
  200. package/dist/src/type/logger.type.d.ts +0 -17
  201. package/dist/src/type/logger.type.js +0 -9
  202. package/dist/src/type/logger.type.js.map +0 -1
  203. package/dist/src/type/markdown.type.d.ts +0 -20
  204. package/dist/src/type/markdown.type.js +0 -2
  205. package/dist/src/type/markdown.type.js.map +0 -1
  206. package/dist/src/type/route-tree.type.js.map +0 -1
  207. package/dist/src/type/route.type.d.ts +0 -94
  208. package/dist/src/type/route.type.js +0 -8
  209. package/dist/src/type/route.type.js.map +0 -1
  210. package/dist/src/type/widget.type.d.ts +0 -55
  211. package/dist/src/type/widget.type.js +0 -10
  212. package/dist/src/type/widget.type.js.map +0 -1
  213. package/dist/src/util/logger.util.d.ts +0 -26
  214. package/dist/src/util/logger.util.js +0 -80
  215. package/dist/src/util/logger.util.js.map +0 -1
  216. package/dist/src/util/md.util.js.map +0 -1
  217. package/dist/src/util/widget-resolve.util.d.ts +0 -52
  218. package/dist/src/util/widget-resolve.util.js.map +0 -1
  219. package/dist/src/widget/widget.parser.js.map +0 -1
  220. package/dist/src/widget/widget.registry.d.ts +0 -23
  221. package/dist/src/widget/widget.registry.js +0 -42
  222. package/dist/src/widget/widget.registry.js.map +0 -1
  223. package/runtime/bun/esbuild-runtime-loader.plugin.ts +0 -112
  224. package/server/esbuild-manifest.plugin.ts +0 -209
  225. package/server/server-api.type.ts +0 -101
  226. package/src/component/abstract.component.ts +0 -231
  227. package/src/component/widget.component.ts +0 -85
  228. package/src/route/route.core.ts +0 -371
  229. package/src/route/route.trie.ts +0 -265
  230. package/src/type/element.type.ts +0 -22
  231. package/src/type/logger.type.ts +0 -24
  232. package/src/type/markdown.type.ts +0 -21
  233. package/src/type/route-tree.type.ts +0 -51
  234. package/src/type/route.type.ts +0 -124
  235. package/src/type/widget.type.ts +0 -65
  236. package/src/util/logger.util.ts +0 -83
  237. package/src/widget/widget.registry.ts +0 -51
  238. /package/dist/{src/route → core/util}/route-tree.util.d.ts +0 -0
package/dist/emroute.js CHANGED
@@ -1,7 +1,32 @@
1
- // dist/src/util/html.util.js
1
+ // dist/core/util/html.util.js
2
2
  var SSR_ATTR = "ssr";
3
3
  var LAZY_ATTR = "lazy";
4
- var SsrShadowRoot = class {
4
+ var BLOCKED_PROTOCOLS = /^(javascript|data|vbscript):/i;
5
+ function assertSafeRedirect(url) {
6
+ if (BLOCKED_PROTOCOLS.test(url.trim())) {
7
+ throw new Error(`Unsafe redirect URL blocked: ${url}`);
8
+ }
9
+ }
10
+ function escapeHtml(text) {
11
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("`", "&#96;");
12
+ }
13
+ function unescapeHtml(text) {
14
+ return text.replaceAll("&#96;", "`").replaceAll("&#39;", "'").replaceAll("&quot;", '"').replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
15
+ }
16
+ function scopeWidgetCss(css, widgetName) {
17
+ return `@scope (widget-${widgetName}) {
18
+ ${css}
19
+ }`;
20
+ }
21
+ var STATUS_MESSAGES = {
22
+ 401: "Unauthorized",
23
+ 403: "Forbidden",
24
+ 404: "Not Found",
25
+ 500: "Internal Server Error"
26
+ };
27
+
28
+ // dist/src/util/html.util.js
29
+ class SsrShadowRoot {
5
30
  host;
6
31
  _innerHTML = "";
7
32
  constructor(host) {
@@ -16,8 +41,7 @@ var SsrShadowRoot = class {
16
41
  setHTMLUnsafe(html, _options) {
17
42
  this._innerHTML = html;
18
43
  }
19
- append(..._nodes) {
20
- }
44
+ append(..._nodes) {}
21
45
  querySelector(_selector) {
22
46
  return null;
23
47
  }
@@ -30,12 +54,12 @@ var SsrShadowRoot = class {
30
54
  get firstChild() {
31
55
  return null;
32
56
  }
33
- };
34
- var SsrHTMLElement = class {
57
+ }
58
+
59
+ class SsrHTMLElement {
35
60
  _innerHTML = "";
36
61
  _shadowRoot = null;
37
- _attributes = /* @__PURE__ */ new Map();
38
- // Accept any CSS property assignment without error
62
+ _attributes = new Map;
39
63
  style = new Proxy({}, {
40
64
  set(_target, _prop, _value) {
41
65
  return true;
@@ -43,7 +67,7 @@ var SsrHTMLElement = class {
43
67
  get(_target, prop) {
44
68
  if (typeof prop === "string")
45
69
  return "";
46
- return void 0;
70
+ return;
47
71
  }
48
72
  });
49
73
  get innerHTML() {
@@ -90,73 +114,41 @@ var SsrHTMLElement = class {
90
114
  querySelectorAll(_selector) {
91
115
  return [];
92
116
  }
93
- append(..._nodes) {
94
- }
117
+ append(..._nodes) {}
95
118
  appendChild(node) {
96
119
  return node;
97
120
  }
98
- };
99
- var HTMLElementBase = globalThis.HTMLElement ?? SsrHTMLElement;
100
- function escapeHtml(text) {
101
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;").replaceAll("`", "&#96;");
102
- }
103
- function unescapeHtml(text) {
104
- return text.replaceAll("&#96;", "`").replaceAll("&#39;", "'").replaceAll("&quot;", '"').replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
105
- }
106
- function scopeWidgetCss(css, widgetName) {
107
- return `@scope (widget-${widgetName}) {
108
- ${css}
109
- }`;
110
121
  }
111
- var STATUS_MESSAGES = {
112
- 401: "Unauthorized",
113
- 403: "Forbidden",
114
- 404: "Not Found",
115
- 500: "Internal Server Error"
116
- };
122
+ var HTMLElementBase = globalThis.HTMLElement ?? SsrHTMLElement;
117
123
 
118
124
  // dist/src/element/slot.element.js
119
- var RouterSlot = class extends HTMLElementBase {
120
- };
125
+ class RouterSlot extends HTMLElementBase {
126
+ }
121
127
 
122
128
  // dist/src/element/markdown.element.js
123
- var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
129
+ class MarkdownElement extends HTMLElementBase {
124
130
  static renderer = null;
125
131
  static rendererInitPromise = null;
126
132
  abortController = null;
127
- /**
128
- * Set the markdown renderer.
129
- * Must be called before any <mark-down> elements are connected.
130
- *
131
- * @example
132
- * ```ts
133
- * import { createEmkoRenderer } from './emko.renderer.ts';
134
- * MarkdownElement.setRenderer(await createEmkoRenderer());
135
- * ```
136
- */
137
133
  static setRenderer(renderer) {
138
- _MarkdownElement.renderer = renderer;
139
- _MarkdownElement.rendererInitPromise = renderer.init ? renderer.init() : null;
134
+ MarkdownElement.renderer = renderer;
135
+ MarkdownElement.rendererInitPromise = renderer.init ? renderer.init() : null;
140
136
  }
141
- /** Get the current renderer (if set). Used by bootEmrouteApp to pass through to createEmrouteServer. */
142
137
  static getConfiguredRenderer() {
143
- return _MarkdownElement.renderer;
138
+ return MarkdownElement.renderer;
144
139
  }
145
- /**
146
- * Get the current renderer, waiting for init if needed.
147
- */
148
140
  static async getRenderer() {
149
- const renderer = _MarkdownElement.renderer;
141
+ const renderer = MarkdownElement.renderer;
150
142
  if (!renderer) {
151
143
  throw new Error("No markdown renderer configured. Call MarkdownElement.setRenderer() before using <mark-down> elements.");
152
144
  }
153
- if (_MarkdownElement.rendererInitPromise) {
154
- await _MarkdownElement.rendererInitPromise;
145
+ if (MarkdownElement.rendererInitPromise) {
146
+ await MarkdownElement.rendererInitPromise;
155
147
  }
156
148
  return renderer;
157
149
  }
158
150
  async connectedCallback() {
159
- this.abortController = new AbortController();
151
+ this.abortController = new AbortController;
160
152
  await this.loadContent();
161
153
  }
162
154
  disconnectedCallback() {
@@ -192,7 +184,7 @@ var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
192
184
  }
193
185
  async renderContent(markdown) {
194
186
  try {
195
- const renderer = await _MarkdownElement.getRenderer();
187
+ const renderer = await MarkdownElement.getRenderer();
196
188
  this.innerHTML = renderer.render(markdown);
197
189
  } catch (error) {
198
190
  this.showError(error);
@@ -202,32 +194,26 @@ var MarkdownElement = class _MarkdownElement extends HTMLElementBase {
202
194
  const message = error instanceof Error ? error.message : String(error);
203
195
  this.innerHTML = `<div>Markdown Error: ${escapeHtml(message)}</div>`;
204
196
  }
205
- };
197
+ }
206
198
 
207
199
  // dist/src/element/component.element.js
208
200
  function filterUndefined(obj) {
209
201
  const result = {};
210
202
  let hasValue = false;
211
203
  for (const [k, v] of Object.entries(obj)) {
212
- if (v !== void 0) {
204
+ if (v !== undefined) {
213
205
  result[k] = v;
214
206
  hasValue = true;
215
207
  }
216
208
  }
217
- return hasValue ? result : void 0;
209
+ return hasValue ? result : undefined;
218
210
  }
219
- var ComponentElement = class _ComponentElement extends HTMLElementBase {
220
- /** Shared file content cache — deduplicates fetches across all widget instances. */
221
- static fileCache = /* @__PURE__ */ new Map();
222
- /** Lazy module loaders keyed by tag name — set by registerLazy(). */
223
- static lazyLoaders = /* @__PURE__ */ new Map();
224
- /** Cached module promises for lazy-loaded widgets — avoids re-fetching. */
225
- static lazyModules = /* @__PURE__ */ new Map();
226
- /** App-level context provider set once during router initialization. */
211
+
212
+ class ComponentElement extends HTMLElementBase {
213
+ static lazyLoaders = new Map;
227
214
  static extendContext;
228
- /** Register (or clear) the context provider that enriches every widget's ComponentContext. */
229
215
  static setContextProvider(provider) {
230
- _ComponentElement.extendContext = provider;
216
+ ComponentElement.extendContext = provider;
231
217
  }
232
218
  component;
233
219
  effectiveFiles;
@@ -239,7 +225,6 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
239
225
  deferred = null;
240
226
  abortController = null;
241
227
  intersectionObserver = null;
242
- /** Promise that resolves with fetched data (available after loadData starts) */
243
228
  dataPromise = null;
244
229
  constructor(component, files) {
245
230
  super();
@@ -249,52 +234,36 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
249
234
  this.attachShadow({ mode: "open" });
250
235
  }
251
236
  }
252
- /**
253
- * Register a widget as a custom element: `widget-{name}`.
254
- * Creates a fresh widget instance per DOM element (per-element state).
255
- * Optional `files` parameter provides discovered file paths without mutating
256
- * the component instance.
257
- */
258
237
  static register(component, files) {
259
238
  const tagName = `widget-${component.name}`;
260
239
  if (!globalThis.customElements || customElements.get(tagName)) {
261
240
  return;
262
241
  }
263
242
  const WidgetClass = component.constructor;
264
- const BoundElement = class extends _ComponentElement {
243
+ const BoundElement = class extends ComponentElement {
265
244
  constructor() {
266
- super(new WidgetClass(), files);
245
+ super(new WidgetClass, files);
267
246
  }
268
247
  };
269
248
  customElements.define(tagName, BoundElement);
270
249
  }
271
- /**
272
- * Register a widget class (not instance) as a custom element: `widget-{name}`.
273
- * Used for manifest-based registration where classes are loaded dynamically.
274
- */
275
250
  static registerClass(WidgetClass, name, files) {
276
251
  const tagName = `widget-${name}`;
277
252
  if (!globalThis.customElements || customElements.get(tagName)) {
278
253
  return;
279
254
  }
280
- const BoundElement = class extends _ComponentElement {
255
+ const BoundElement = class extends ComponentElement {
281
256
  constructor() {
282
- super(new WidgetClass(), files);
257
+ super(new WidgetClass, files);
283
258
  }
284
259
  };
285
260
  customElements.define(tagName, BoundElement);
286
261
  }
287
- /**
288
- * Register a widget lazily: define the custom element immediately (so SSR
289
- * content via Declarative Shadow DOM is adopted), but defer loading the
290
- * module until connectedCallback fires. Once loaded, the real component
291
- * replaces the placeholder and hydration proceeds normally.
292
- */
293
- static registerLazy(name, files, loader) {
262
+ static registerLazy(name, loader) {
294
263
  const tagName = `widget-${name}`;
295
264
  if (!globalThis.customElements || customElements.get(tagName))
296
265
  return;
297
- _ComponentElement.lazyLoaders.set(tagName, loader);
266
+ ComponentElement.lazyLoaders.set(tagName, loader);
298
267
  const placeholder = {
299
268
  name,
300
269
  getData: () => Promise.resolve(null),
@@ -303,17 +272,13 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
303
272
  renderError: () => "",
304
273
  renderMarkdownError: () => ""
305
274
  };
306
- const BoundElement = class extends _ComponentElement {
275
+ const BoundElement = class extends ComponentElement {
307
276
  constructor() {
308
- super(placeholder, files);
277
+ super(placeholder);
309
278
  }
310
279
  };
311
280
  customElements.define(tagName, BoundElement);
312
281
  }
313
- /**
314
- * Promise that resolves when component is ready (data loaded and rendered).
315
- * Used by router to wait for async components.
316
- */
317
282
  get ready() {
318
283
  if (this.state === "ready") {
319
284
  return Promise.resolve();
@@ -323,23 +288,21 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
323
288
  }
324
289
  async connectedCallback() {
325
290
  const tagName = this.tagName.toLowerCase();
326
- const lazyLoader = _ComponentElement.lazyLoaders.get(tagName);
291
+ const lazyLoader = ComponentElement.lazyLoaders.get(tagName);
327
292
  if (lazyLoader) {
328
293
  try {
329
- let modulePromise = _ComponentElement.lazyModules.get(tagName);
330
- if (!modulePromise) {
331
- modulePromise = lazyLoader();
332
- _ComponentElement.lazyModules.set(tagName, modulePromise);
294
+ const mod = await lazyLoader();
295
+ if (mod.__files && typeof mod.__files === "object") {
296
+ this.effectiveFiles = mod.__files;
333
297
  }
334
- const mod = await modulePromise;
335
298
  for (const exp of Object.values(mod)) {
336
299
  if (exp && typeof exp === "object" && "getData" in exp) {
337
300
  const WidgetClass = exp.constructor;
338
- this.component = new WidgetClass();
301
+ this.component = new WidgetClass;
339
302
  break;
340
303
  }
341
304
  if (typeof exp === "function" && exp.prototype?.getData) {
342
- this.component = new exp();
305
+ this.component = new exp;
343
306
  break;
344
307
  }
345
308
  }
@@ -353,7 +316,7 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
353
316
  }
354
317
  this.component.element = this;
355
318
  this.style.contentVisibility = "auto";
356
- this.abortController = new AbortController();
319
+ this.abortController = new AbortController;
357
320
  const signal = this.abortController.signal;
358
321
  const params = {};
359
322
  for (const attr of this.attributes) {
@@ -386,15 +349,14 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
386
349
  params: this.params ?? {},
387
350
  ...filteredFiles ? { files: filteredFiles } : {}
388
351
  };
389
- this.context = _ComponentElement.extendContext ? _ComponentElement.extendContext(base) : base;
352
+ this.context = ComponentElement.extendContext ? ComponentElement.extendContext(base) : base;
390
353
  if (this.hasAttribute(SSR_ATTR)) {
391
354
  this.removeAttribute(SSR_ATTR);
392
355
  const lightText = this.textContent?.trim();
393
356
  if (lightText) {
394
357
  try {
395
358
  this.data = JSON.parse(lightText);
396
- } catch {
397
- }
359
+ } catch {}
398
360
  }
399
361
  this.textContent = "";
400
362
  this.state = "ready";
@@ -408,7 +370,8 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
408
370
  return;
409
371
  }
410
372
  if (this.hasAttribute(LAZY_ATTR)) {
411
- this.intersectionObserver = new IntersectionObserver(([entry]) => {
373
+ this.intersectionObserver = new IntersectionObserver((entries) => {
374
+ const entry = entries[0];
412
375
  if (entry.isIntersecting) {
413
376
  this.intersectionObserver?.disconnect();
414
377
  this.intersectionObserver = null;
@@ -422,56 +385,28 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
422
385
  }
423
386
  disconnectedCallback() {
424
387
  this.component.destroy?.();
425
- this.component.element = void 0;
388
+ this.component.element = undefined;
426
389
  this.intersectionObserver?.disconnect();
427
390
  this.intersectionObserver = null;
428
391
  this.abortController?.abort();
429
392
  this.abortController = null;
430
393
  this.state = "idle";
431
394
  this.data = null;
432
- this.context = void 0;
395
+ this.context = undefined;
433
396
  this.dataPromise = null;
434
397
  this.errorMessage = "";
435
398
  this.signalReady();
436
399
  this.deferred = null;
437
400
  }
438
- /**
439
- * Reload component data. Aborts any in-flight request first.
440
- */
441
401
  async reload() {
442
402
  if (this.params === null)
443
403
  return;
444
404
  this.abortController?.abort();
445
- this.abortController = new AbortController();
405
+ this.abortController = new AbortController;
446
406
  await this.loadData();
447
407
  }
448
- /**
449
- * Fetch a single file by path, with caching.
450
- * Absolute URLs (http/https) pass through; relative paths get '/' prefix.
451
- */
452
- static loadFile(path) {
453
- const cached = _ComponentElement.fileCache.get(path);
454
- if (cached)
455
- return cached;
456
- const url = path.startsWith("http://") || path.startsWith("https://") ? path : path.startsWith("/") ? path : "/" + path;
457
- const promise = fetch(url).then((res) => res.ok ? res.text() : void 0, () => void 0);
458
- _ComponentElement.fileCache.set(path, promise);
459
- return promise;
460
- }
461
- /**
462
- * Load all companion files for this widget instance.
463
- * Uses effectiveFiles (from registration) falling back to component.files.
464
- */
465
408
  async loadFiles() {
466
- const filePaths = this.effectiveFiles ?? this.component.files;
467
- if (!filePaths)
468
- return {};
469
- const [html, md, css] = await Promise.all([
470
- filePaths.html ? _ComponentElement.loadFile(filePaths.html) : void 0,
471
- filePaths.md ? _ComponentElement.loadFile(filePaths.md) : void 0,
472
- filePaths.css ? _ComponentElement.loadFile(filePaths.css) : void 0
473
- ]);
474
- return filterUndefined({ html, md, css }) ?? {};
409
+ return this.effectiveFiles ?? {};
475
410
  }
476
411
  async loadData() {
477
412
  if (this.params === null)
@@ -535,189 +470,242 @@ var ComponentElement = class _ComponentElement extends HTMLElementBase {
535
470
  });
536
471
  }
537
472
  }
538
- };
473
+ }
539
474
 
540
- // dist/src/widget/widget.registry.js
541
- var WidgetRegistry = class {
542
- widgets = /* @__PURE__ */ new Map();
543
- /** Register a widget by its name. */
544
- add(widget) {
545
- this.widgets.set(widget.name, widget);
546
- }
547
- /** Look up a widget by name. */
475
+ // dist/core/widget/widget.registry.js
476
+ class WidgetRegistry {
477
+ entries = new Map;
478
+ add(widget, modulePath) {
479
+ this.entries.set(widget.name, { widget, modulePath });
480
+ }
548
481
  get(name) {
549
- return this.widgets.get(name);
482
+ return this.entries.get(name)?.widget;
483
+ }
484
+ getModulePath(name) {
485
+ return this.entries.get(name)?.modulePath;
550
486
  }
551
- /** Iterate all registered widgets. */
552
487
  [Symbol.iterator]() {
553
- return this.widgets.values();
488
+ const entries = this.entries.values();
489
+ return function* () {
490
+ for (const entry of entries)
491
+ yield entry.widget;
492
+ }();
554
493
  }
555
- /** Emit a WidgetsManifest from registered widgets. */
556
- toManifest() {
557
- const widgets = [];
558
- const moduleLoaders = {};
559
- for (const [name, widget] of this.widgets) {
560
- const entry = {
561
- name,
562
- modulePath: name,
563
- tagName: `widget-${name}`,
564
- ...widget.files ? { files: widget.files } : {}
565
- };
566
- widgets.push(entry);
567
- moduleLoaders[name] = () => Promise.resolve({ default: widget.constructor });
494
+ }
495
+
496
+ // dist/core/router/route.trie.js
497
+ class RouteTrie {
498
+ tree;
499
+ constructor(tree) {
500
+ this.tree = tree;
501
+ }
502
+ match(pathname) {
503
+ pathname = this.normalizePath(pathname);
504
+ if (pathname === "/") {
505
+ if (this.tree.files || this.tree.redirect) {
506
+ return { node: this.tree, pattern: "/", params: {} };
507
+ }
508
+ return;
568
509
  }
569
- return { widgets, moduleLoaders };
510
+ return this.walk(this.tree, this.splitSegments(pathname), 0, {}, "/");
570
511
  }
571
- };
572
-
573
- // dist/src/route/route.core.js
574
- var __rewriteRelativeImportExtension = function(path, preserveJsx) {
575
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
576
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
577
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
578
- });
512
+ findErrorBoundary(pathname) {
513
+ pathname = this.normalizePath(pathname);
514
+ if (pathname === "/")
515
+ return this.tree.errorBoundary;
516
+ return this.walkForBoundary(this.tree, this.splitSegments(pathname), 0, this.tree.errorBoundary);
579
517
  }
580
- return path;
581
- };
582
- var DEFAULT_BASE_PATH = { html: "/html", md: "/md", app: "/app" };
583
- var BLOCKED_PROTOCOLS = /^(javascript|data|vbscript):/i;
584
- function assertSafeRedirect(url) {
585
- if (BLOCKED_PROTOCOLS.test(url.trim())) {
586
- throw new Error(`Unsafe redirect URL blocked: ${url}`);
518
+ findRoute(pattern) {
519
+ if (pattern === "/") {
520
+ return this.tree.files || this.tree.redirect ? this.tree : undefined;
521
+ }
522
+ const segments = this.splitSegments(pattern);
523
+ let node = this.tree;
524
+ for (const segment of segments) {
525
+ let child;
526
+ if (segment.startsWith(":") && segment.endsWith("*")) {
527
+ child = node.wildcard?.child;
528
+ } else if (segment.startsWith(":")) {
529
+ child = node.dynamic?.child;
530
+ } else {
531
+ child = node.children?.[segment];
532
+ }
533
+ if (!child)
534
+ return;
535
+ node = child;
536
+ }
537
+ return node.files || node.redirect ? node : undefined;
538
+ }
539
+ safeDecode(segment) {
540
+ try {
541
+ return decodeURIComponent(segment);
542
+ } catch {
543
+ return segment;
544
+ }
545
+ }
546
+ splitSegments(pathname) {
547
+ return pathname.substring(1).split("/");
548
+ }
549
+ normalizePath(pathname) {
550
+ if (pathname.length > 1 && pathname.endsWith("/")) {
551
+ pathname = pathname.slice(0, -1);
552
+ }
553
+ if (!pathname.startsWith("/")) {
554
+ pathname = "/" + pathname;
555
+ }
556
+ return pathname;
557
+ }
558
+ walk(node, segments, index, params, pattern) {
559
+ if (index === segments.length) {
560
+ if (node.files || node.redirect) {
561
+ return { node, pattern, params: { ...params } };
562
+ }
563
+ if (node.wildcard && (node.wildcard.child.files || node.wildcard.child.redirect)) {
564
+ const wp = pattern === "/" ? `/:${node.wildcard.param}*` : `${pattern}/:${node.wildcard.param}*`;
565
+ return {
566
+ node: node.wildcard.child,
567
+ pattern: wp,
568
+ params: { ...params, [node.wildcard.param]: "" }
569
+ };
570
+ }
571
+ return;
572
+ }
573
+ const segment = segments[index];
574
+ const staticChild = node.children?.[segment];
575
+ if (staticChild) {
576
+ const childPattern = pattern === "/" ? `/${segment}` : `${pattern}/${segment}`;
577
+ const result = this.walk(staticChild, segments, index + 1, params, childPattern);
578
+ if (result)
579
+ return result;
580
+ }
581
+ if (node.dynamic) {
582
+ const { param, child } = node.dynamic;
583
+ params[param] = this.safeDecode(segment);
584
+ const childPattern = pattern === "/" ? `/:${param}` : `${pattern}/:${param}`;
585
+ const result = this.walk(child, segments, index + 1, params, childPattern);
586
+ if (result)
587
+ return result;
588
+ delete params[param];
589
+ }
590
+ if (node.wildcard && (node.wildcard.child.files || node.wildcard.child.redirect)) {
591
+ const { param, child } = node.wildcard;
592
+ let rest = this.safeDecode(segment);
593
+ for (let i = index + 1;i < segments.length; i++) {
594
+ rest += "/" + this.safeDecode(segments[i]);
595
+ }
596
+ const wp = pattern === "/" ? `/:${param}*` : `${pattern}/:${param}*`;
597
+ return {
598
+ node: child,
599
+ pattern: wp,
600
+ params: { ...params, [param]: rest }
601
+ };
602
+ }
603
+ return;
604
+ }
605
+ walkForBoundary(node, segments, index, deepest) {
606
+ if (index === segments.length) {
607
+ return node.errorBoundary ?? deepest;
608
+ }
609
+ const segment = segments[index];
610
+ const staticChild = node.children?.[segment];
611
+ if (staticChild) {
612
+ return this.walkForBoundary(staticChild, segments, index + 1, staticChild.errorBoundary ?? deepest);
613
+ }
614
+ if (node.dynamic) {
615
+ return this.walkForBoundary(node.dynamic.child, segments, index + 1, node.dynamic.child.errorBoundary ?? deepest);
616
+ }
617
+ if (node.wildcard) {
618
+ return node.wildcard.child.errorBoundary ?? deepest;
619
+ }
620
+ return deepest;
587
621
  }
588
622
  }
623
+
624
+ // dist/core/runtime/abstract.runtime.js
625
+ var ROUTES_MANIFEST_PATH = "/routes.manifest.json";
626
+ var WIDGETS_MANIFEST_PATH = "/widgets.manifest.json";
627
+ var ELEMENTS_MANIFEST_PATH = "/elements.manifest.json";
628
+
629
+ // dist/core/type/logger.type.js
630
+ var noop = () => {};
631
+ var defaultLogger = { error: noop, warn: noop };
632
+ function setLogger(_logger) {
633
+ console.warn("[emroute] setLogger() is deprecated. Pass `logger` in Emroute.create() config instead.");
634
+ }
635
+
636
+ // dist/core/pipeline/pipeline.js
589
637
  var DEFAULT_ROOT_ROUTE = {
590
638
  pattern: "/",
591
639
  type: "page",
592
640
  modulePath: "__default_root__"
593
641
  };
594
- function toRouteConfig(resolved) {
595
- const node = resolved.node;
642
+ function toRouteConfig(node, pattern) {
596
643
  return {
597
- pattern: resolved.pattern,
644
+ pattern,
598
645
  type: node.redirect ? "redirect" : "page",
599
646
  modulePath: node.redirect ?? node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
600
647
  ...node.files ? { files: node.files } : {}
601
648
  };
602
649
  }
603
- var RouteCore = class {
604
- resolver;
605
- /** Registered context provider (if any). Exposed so renderers can apply it to inline contexts. */
650
+
651
+ class Pipeline {
652
+ runtime;
606
653
  contextProvider;
607
- listeners = /* @__PURE__ */ new Set();
608
- moduleCache = /* @__PURE__ */ new Map();
609
- widgetFileCache = /* @__PURE__ */ new Map();
654
+ logger;
610
655
  moduleLoaders;
611
- currentRoute = null;
612
- readFile;
613
- constructor(resolver, options = {}) {
614
- this.resolver = resolver;
615
- this.readFile = options.fileReader ?? ((path) => fetch(path, { headers: { Accept: "text/plain" } }).then((r) => r.text()));
616
- this.contextProvider = options.extendContext;
656
+ constructor(options) {
657
+ this.runtime = options.runtime;
658
+ this.contextProvider = options.contextProvider;
659
+ this.logger = options.logger ?? defaultLogger;
617
660
  this.moduleLoaders = options.moduleLoaders ?? {};
618
661
  }
619
- /**
620
- * Get current route parameters.
621
- */
622
- getParams() {
623
- return this.currentRoute?.params ?? {};
624
- }
625
- /**
626
- * Add event listener for router events.
627
- */
628
- addEventListener(listener) {
629
- this.listeners.add(listener);
630
- return () => this.listeners.delete(listener);
631
- }
632
- /**
633
- * Emit router event to listeners.
634
- */
635
- emit(event) {
636
- for (const listener of this.listeners) {
637
- try {
638
- listener(event);
639
- } catch (e) {
640
- console.error("[Router] Event listener error:", e);
641
- }
642
- }
662
+ async getResolver() {
663
+ const response = await this.runtime.query(ROUTES_MANIFEST_PATH);
664
+ const tree = response.status === 404 ? {} : await response.json();
665
+ return new RouteTrie(tree);
643
666
  }
644
- /**
645
- * Match a URL to a route.
646
- * Falls back to the default root route for '/'.
647
- */
648
- match(url) {
649
- const pathname = url.pathname;
650
- const resolved = this.resolver.match(pathname);
667
+ async match(url) {
668
+ const resolver = await this.getResolver();
669
+ const resolved = resolver.match(url.pathname);
651
670
  if (resolved) {
652
- return {
653
- route: toRouteConfig(resolved),
654
- params: resolved.params
655
- };
671
+ return { route: toRouteConfig(resolved.node, resolved.pattern), params: resolved.params };
656
672
  }
657
- if (pathname === "/" || pathname === "") {
658
- return {
659
- route: DEFAULT_ROOT_ROUTE,
660
- params: {}
661
- };
673
+ if (url.pathname === "/" || url.pathname === "") {
674
+ return { route: DEFAULT_ROOT_ROUTE, params: {} };
662
675
  }
663
- return void 0;
676
+ return;
664
677
  }
665
- /** Get status-specific page (404, 401, 403). */
666
- getStatusPage(status) {
667
- const node = this.resolver.findRoute(`/${status}`);
678
+ async findRoute(pattern) {
679
+ const resolver = await this.getResolver();
680
+ const node = resolver.findRoute(pattern);
668
681
  if (!node)
669
- return void 0;
670
- return {
671
- pattern: `/${status}`,
672
- type: "page",
673
- modulePath: node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
674
- ...node.files ? { files: node.files } : {}
675
- };
676
- }
677
- /** Get global error handler (root errorBoundary). */
678
- getErrorHandler() {
679
- const modulePath = this.resolver.findErrorBoundary("/");
680
- if (!modulePath)
681
- return void 0;
682
- return { pattern: "/", type: "error", modulePath };
682
+ return;
683
+ return toRouteConfig(node, pattern);
683
684
  }
684
- /**
685
- * Find error boundary for a given pathname.
686
- * Note: pattern is the input pathname, not the boundary's own pattern.
687
- * Callers should only rely on modulePath.
688
- */
689
- findErrorBoundary(pathname) {
690
- const modulePath = this.resolver.findErrorBoundary(pathname);
685
+ async findErrorBoundary(pathname) {
686
+ const resolver = await this.getResolver();
687
+ const modulePath = resolver.findErrorBoundary(pathname);
691
688
  if (!modulePath)
692
- return void 0;
689
+ return;
693
690
  return { pattern: pathname, modulePath };
694
691
  }
695
- /**
696
- * Find a route by its exact pattern.
697
- * Used for building route hierarchy.
698
- */
699
- findRoute(pattern) {
700
- const node = this.resolver.findRoute(pattern);
692
+ async getStatusPage(status) {
693
+ const resolver = await this.getResolver();
694
+ const node = resolver.findRoute(`/${status}`);
701
695
  if (!node)
702
- return void 0;
703
- return {
704
- pattern,
705
- type: node.redirect ? "redirect" : "page",
706
- modulePath: node.redirect ?? node.files?.ts ?? node.files?.js ?? node.files?.html ?? node.files?.md ?? "",
707
- ...node.files ? { files: node.files } : {}
708
- };
696
+ return;
697
+ return toRouteConfig(node, `/${status}`);
698
+ }
699
+ async getErrorHandler() {
700
+ const resolver = await this.getResolver();
701
+ const modulePath = resolver.findErrorBoundary("/");
702
+ if (!modulePath)
703
+ return;
704
+ return { pattern: "/", type: "error", modulePath };
709
705
  }
710
- /**
711
- * Build route hierarchy from a pattern.
712
- * Patterns are always unprefixed (no basePath).
713
- *
714
- * e.g., '/projects/:id/tasks'
715
- * → ['/', '/projects', '/projects/:id', '/projects/:id/tasks']
716
- */
717
706
  buildRouteHierarchy(pattern) {
718
- if (pattern === "/") {
707
+ if (pattern === "/")
719
708
  return ["/"];
720
- }
721
709
  const segments = pattern.split("/").filter(Boolean);
722
710
  const hierarchy = ["/"];
723
711
  let current = "";
@@ -727,351 +715,119 @@ var RouteCore = class {
727
715
  }
728
716
  return hierarchy;
729
717
  }
730
- /**
731
- * Normalize URL by removing trailing slashes (except bare '/').
732
- */
733
- normalizeUrl(url) {
734
- if (url.length > 1 && url.endsWith("/")) {
735
- return url.slice(0, -1);
736
- }
737
- return url;
738
- }
739
- /**
740
- * Convert relative path to absolute path.
741
- */
742
- toAbsolutePath(path) {
743
- return path.startsWith("/") ? path : "/" + path;
744
- }
745
- /**
746
- * Load a module with caching.
747
- * Uses pre-bundled loaders when available, falls back to dynamic import.
748
- */
749
718
  async loadModule(modulePath) {
750
- if (this.moduleCache.has(modulePath)) {
751
- return this.moduleCache.get(modulePath);
752
- }
753
- let module;
754
719
  const loader = this.moduleLoaders[modulePath];
755
720
  if (loader) {
756
- module = await loader();
757
- } else {
758
- const absolutePath = this.toAbsolutePath(modulePath);
759
- module = await import(__rewriteRelativeImportExtension(absolutePath));
721
+ return await loader();
760
722
  }
761
- this.moduleCache.set(modulePath, module);
762
- return module;
723
+ const abs = modulePath.startsWith("/") ? modulePath : "/" + modulePath;
724
+ return await this.runtime.loadModule(abs);
725
+ }
726
+ getModuleFiles(mod) {
727
+ if (!mod || typeof mod !== "object")
728
+ return;
729
+ const files = mod.__files;
730
+ if (!files || typeof files !== "object")
731
+ return;
732
+ return files;
763
733
  }
764
- /**
765
- * Load widget file contents with caching.
766
- */
767
- async loadWidgetFiles(widgetFiles) {
734
+ async loadFiles(files) {
768
735
  const load = async (path) => {
769
- const absPath = this.toAbsolutePath(path);
770
- const cached = this.widgetFileCache.get(absPath);
771
- if (cached !== void 0)
772
- return cached;
736
+ const abs = path.startsWith("/") ? path : "/" + path;
773
737
  try {
774
- const content = await this.readFile(absPath);
775
- this.widgetFileCache.set(absPath, content);
776
- return content;
738
+ return await this.runtime.query(abs, { as: "text" });
777
739
  } catch (e) {
778
- console.warn(`[RouteCore] Failed to load widget file ${path}:`, e instanceof Error ? e.message : e);
779
- return void 0;
740
+ console.warn(`[Pipeline] Failed to load file ${path}:`, e instanceof Error ? e.message : e);
741
+ return;
780
742
  }
781
743
  };
782
744
  const [html, md, css] = await Promise.all([
783
- widgetFiles.html ? load(widgetFiles.html) : void 0,
784
- widgetFiles.md ? load(widgetFiles.md) : void 0,
785
- widgetFiles.css ? load(widgetFiles.css) : void 0
745
+ files.html ? load(files.html) : undefined,
746
+ files.md ? load(files.md) : undefined,
747
+ files.css ? load(files.css) : undefined
786
748
  ]);
787
749
  const result = {};
788
- if (html != null)
750
+ if (html !== undefined)
789
751
  result.html = html;
790
- if (md != null)
752
+ if (md !== undefined)
791
753
  result.md = md;
792
- if (css != null)
754
+ if (css !== undefined)
793
755
  result.css = css;
794
756
  return result;
795
757
  }
796
- /**
797
- * Build a RouteInfo from a matched route and the resolved URL pathname.
798
- * Called once per navigation; the result is reused across the route hierarchy.
799
- */
800
758
  toRouteInfo(matched, url) {
801
- return {
802
- url,
803
- params: matched.params
804
- };
759
+ return { url, params: matched.params };
805
760
  }
806
- /**
807
- * Get inlined `__files` from a cached module (merged module pattern).
808
- * Returns undefined if the module isn't cached or has no __files.
809
- */
810
- getModuleFiles(modulePath) {
811
- const cached = this.moduleCache.get(modulePath);
812
- if (!cached || typeof cached !== "object")
813
- return void 0;
814
- const files = cached.__files;
815
- if (!files || typeof files !== "object")
816
- return void 0;
817
- return files;
818
- }
819
- /**
820
- * Build a ComponentContext by extending RouteInfo with loaded file contents.
821
- *
822
- * When the route module is a merged module (contains `__files`), uses
823
- * inlined content directly. Otherwise falls back to reading companion files.
824
- */
825
- async buildComponentContext(routeInfo, route, signal, isLeaf) {
761
+ async buildContext(routeInfo, route, signal, isLeaf, loadedModule) {
826
762
  const rf = route.files;
827
- const modulePath = rf?.ts ?? rf?.js;
828
- const inlined = modulePath ? this.getModuleFiles(modulePath) : void 0;
829
- let html;
830
- let md;
831
- let css;
763
+ const inlined = loadedModule ? this.getModuleFiles(loadedModule) : undefined;
764
+ let files;
832
765
  if (inlined) {
833
- html = inlined.html;
834
- md = inlined.md;
835
- css = inlined.css;
766
+ files = inlined;
767
+ } else if (rf) {
768
+ const filePaths = {};
769
+ if (rf.html)
770
+ filePaths.html = rf.html;
771
+ if (rf.md)
772
+ filePaths.md = rf.md;
773
+ if (rf.css)
774
+ filePaths.css = rf.css;
775
+ files = await this.loadFiles(filePaths);
836
776
  } else {
837
- const fetchFile = (filePath) => this.readFile(this.toAbsolutePath(filePath));
838
- [html, md, css] = await Promise.all([
839
- rf?.html ? fetchFile(rf.html) : void 0,
840
- rf?.md ? fetchFile(rf.md) : void 0,
841
- rf?.css ? fetchFile(rf.css) : void 0
842
- ]);
777
+ files = {};
843
778
  }
844
- const files = {};
845
- if (html != null)
846
- files.html = html;
847
- if (md != null)
848
- files.md = md;
849
- if (css != null)
850
- files.css = css;
851
779
  const base = {
852
780
  ...routeInfo,
853
781
  pathname: routeInfo.url.pathname,
854
782
  searchParams: routeInfo.url.searchParams,
855
783
  files,
856
784
  ...signal ? { signal } : {},
857
- ...isLeaf != null ? { isLeaf } : {}
785
+ ...isLeaf !== undefined ? { isLeaf } : {}
858
786
  };
859
787
  return this.contextProvider ? this.contextProvider(base) : base;
860
788
  }
861
- };
862
-
863
- // dist/src/route/route.trie.js
864
- function createNode() {
865
- return { static: /* @__PURE__ */ new Map() };
866
- }
867
- function safeDecode(segment) {
868
- try {
869
- return decodeURIComponent(segment);
870
- } catch {
871
- return segment;
872
- }
873
789
  }
874
- function splitSegments(pathname) {
875
- return pathname.substring(1).split("/");
876
- }
877
- function convertNode(source, pattern) {
878
- const node = createNode();
879
- if (source.files || source.redirect) {
880
- node.route = source;
881
- node.pattern = pattern;
882
- }
883
- if (source.errorBoundary) {
884
- node.errorBoundary = source.errorBoundary;
885
- }
886
- if (source.children) {
887
- for (const [segment, child] of Object.entries(source.children)) {
888
- const childPattern = pattern === "/" ? `/${segment}` : `${pattern}/${segment}`;
889
- node.static.set(segment, convertNode(child, childPattern));
890
- }
891
- }
892
- if (source.dynamic) {
893
- const { param, child } = source.dynamic;
894
- const childPattern = pattern === "/" ? `/:${param}` : `${pattern}/:${param}`;
895
- node.dynamic = { param, node: convertNode(child, childPattern) };
896
- }
897
- if (source.wildcard) {
898
- const { param, child } = source.wildcard;
899
- const childPattern = pattern === "/" ? `/:${param}*` : `${pattern}/:${param}*`;
900
- node.wildcard = { param, node: convertNode(child, childPattern) };
790
+
791
+ // dist/core/util/widget-resolve.util.js
792
+ var MAX_WIDGET_DEPTH = 10;
793
+ async function resolveRecursively(content, parse, resolve, replace, depth = 0, logger = defaultLogger) {
794
+ if (depth >= MAX_WIDGET_DEPTH) {
795
+ logger.warn(`Widget nesting depth limit reached (${MAX_WIDGET_DEPTH}). ` + "Possible circular dependency or excessive nesting.");
796
+ return content;
901
797
  }
902
- return node;
798
+ const widgets = parse(content);
799
+ if (widgets.length === 0)
800
+ return content;
801
+ const replacements = new Map;
802
+ await Promise.all(widgets.map(async (widget) => {
803
+ let rendered = await resolve(widget);
804
+ rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1, logger);
805
+ replacements.set(widget, rendered);
806
+ }));
807
+ return replace(content, replacements);
903
808
  }
904
- var RouteTrie = class {
905
- root;
906
- constructor(tree) {
907
- this.root = convertNode(tree, "/");
908
- }
909
- match(pathname) {
910
- if (pathname.length > 1 && pathname.endsWith("/")) {
911
- pathname = pathname.slice(0, -1);
912
- }
913
- if (!pathname.startsWith("/")) {
914
- pathname = "/" + pathname;
915
- }
916
- if (pathname === "/") {
917
- if (this.root.route) {
918
- return { node: this.root.route, pattern: "/", params: {} };
919
- }
920
- return void 0;
921
- }
922
- const segments = splitSegments(pathname);
923
- return this.walk(this.root, segments, 0, {});
924
- }
925
- findErrorBoundary(pathname) {
926
- if (pathname.length > 1 && pathname.endsWith("/")) {
927
- pathname = pathname.slice(0, -1);
928
- }
929
- if (!pathname.startsWith("/")) {
930
- pathname = "/" + pathname;
931
- }
932
- if (pathname === "/")
933
- return this.root.errorBoundary;
934
- const segments = splitSegments(pathname);
935
- return this.walkForBoundary(this.root, segments, 0, this.root.errorBoundary);
936
- }
937
- findRoute(pattern) {
938
- if (pattern === "/") {
939
- return this.root.route;
940
- }
941
- const segments = splitSegments(pattern);
942
- let node = this.root;
943
- for (const segment of segments) {
944
- let child;
945
- if (segment.startsWith(":") && segment.endsWith("*")) {
946
- child = node.wildcard?.node;
947
- } else if (segment.startsWith(":")) {
948
- child = node.dynamic?.node;
949
- } else {
950
- child = node.static.get(segment);
951
- }
952
- if (!child)
953
- return void 0;
954
- node = child;
955
- }
956
- return node.route;
957
- }
958
- // ── Private matching ──────────────────────────────────────────────────
959
- walk(node, segments, index, params) {
960
- if (index === segments.length) {
961
- if (node.route) {
962
- return { node: node.route, pattern: node.pattern, params: { ...params } };
963
- }
964
- if (node.wildcard?.node.route) {
965
- return {
966
- node: node.wildcard.node.route,
967
- pattern: node.wildcard.node.pattern,
968
- params: { ...params, [node.wildcard.param]: "" }
969
- };
970
- }
971
- return void 0;
972
- }
973
- const segment = segments[index];
974
- const staticChild = node.static.get(segment);
975
- if (staticChild) {
976
- const result = this.walk(staticChild, segments, index + 1, params);
977
- if (result)
978
- return result;
979
- }
980
- if (node.dynamic) {
981
- const { param, node: dynamicNode } = node.dynamic;
982
- params[param] = safeDecode(segment);
983
- const result = this.walk(dynamicNode, segments, index + 1, params);
984
- if (result)
985
- return result;
986
- delete params[param];
987
- }
988
- if (node.wildcard?.node.route) {
989
- const { param, node: wildcardNode } = node.wildcard;
990
- let rest = safeDecode(segments[index]);
991
- for (let i = index + 1; i < segments.length; i++) {
992
- rest += "/" + safeDecode(segments[i]);
993
- }
994
- return {
995
- node: wildcardNode.route,
996
- pattern: wildcardNode.pattern,
997
- params: { ...params, [param]: rest }
998
- };
999
- }
1000
- return void 0;
1001
- }
1002
- /**
1003
- * Walk for error boundary. Follows the same priority as match
1004
- * (static → dynamic → wildcard) without backtracking across branches.
1005
- * Returns the deepest error boundary module path found along the path.
1006
- */
1007
- walkForBoundary(node, segments, index, deepest) {
1008
- if (index === segments.length) {
1009
- return node.errorBoundary ?? deepest;
1010
- }
1011
- const segment = segments[index];
1012
- const staticChild = node.static.get(segment);
1013
- if (staticChild) {
1014
- return this.walkForBoundary(staticChild, segments, index + 1, staticChild.errorBoundary ?? deepest);
1015
- }
1016
- if (node.dynamic) {
1017
- return this.walkForBoundary(node.dynamic.node, segments, index + 1, node.dynamic.node.errorBoundary ?? deepest);
1018
- }
1019
- if (node.wildcard) {
1020
- return node.wildcard.node.errorBoundary ?? deepest;
1021
- }
1022
- return deepest;
1023
- }
1024
- };
1025
-
1026
- // dist/src/type/logger.type.js
1027
- var noop = () => {
1028
- };
1029
- var logger = { error: noop, warn: noop };
1030
- function setLogger(impl) {
1031
- logger.error = impl.error.bind(impl);
1032
- logger.warn = impl.warn.bind(impl);
1033
- }
1034
-
1035
- // dist/src/util/widget-resolve.util.js
1036
- var MAX_WIDGET_DEPTH = 10;
1037
- async function resolveRecursively(content, parse, resolve, replace, depth = 0) {
1038
- if (depth >= MAX_WIDGET_DEPTH) {
1039
- logger.warn(`Widget nesting depth limit reached (${MAX_WIDGET_DEPTH}). Possible circular dependency or excessive nesting.`);
1040
- return content;
1041
- }
1042
- const widgets = parse(content);
1043
- if (widgets.length === 0)
1044
- return content;
1045
- const replacements = /* @__PURE__ */ new Map();
1046
- await Promise.all(widgets.map(async (widget) => {
1047
- let rendered = await resolve(widget);
1048
- rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1);
1049
- replacements.set(widget, rendered);
1050
- }));
1051
- return replace(content, replacements);
1052
- }
1053
- function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider) {
1054
- const tagPattern = /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
1055
- const wrappers = /* @__PURE__ */ new Map();
1056
- const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
1057
- const parse = (content) => {
1058
- const matches = content.matchAll(tagPattern).toArray();
1059
- return matches.filter((match) => {
1060
- const attrsString = match.groups.attrs || "";
1061
- return !ssrAttrPattern.test(attrsString);
1062
- });
1063
- };
1064
- const resolve = async (match) => {
1065
- const widgetName = match.groups.name;
1066
- const attrsString = match.groups.attrs?.trim() ?? "";
1067
- const widget = registry.get(widgetName);
1068
- if (!widget)
1069
- return match[0];
1070
- const params = parseAttrsToParams(attrsString);
1071
- try {
1072
- let files;
1073
- if (loadFiles) {
1074
- files = await loadFiles(widgetName, widget.files);
809
+ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider, logger = defaultLogger) {
810
+ const tagPattern = /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
811
+ const wrappers = new Map;
812
+ const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
813
+ const parse = (content) => {
814
+ const matches = content.matchAll(tagPattern).toArray();
815
+ return matches.filter((match) => {
816
+ const attrsString = match.groups.attrs || "";
817
+ return !ssrAttrPattern.test(attrsString);
818
+ });
819
+ };
820
+ const resolve = async (match) => {
821
+ const widgetName = match.groups.name;
822
+ const attrsString = match.groups.attrs?.trim() ?? "";
823
+ const widget = registry.get(widgetName);
824
+ if (!widget)
825
+ return match[0];
826
+ const params = parseAttrsToParams(attrsString);
827
+ try {
828
+ let files;
829
+ if (loadFiles) {
830
+ files = await loadFiles(widgetName);
1075
831
  }
1076
832
  const baseContext = {
1077
833
  ...routeInfo,
@@ -1089,7 +845,7 @@ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider
1089
845
  });
1090
846
  return rendered;
1091
847
  } catch (e) {
1092
- logger.error(`[SSR HTML] Widget "${widgetName}" render failed`, e instanceof Error ? e : void 0);
848
+ logger.error(`[SSR HTML] Widget "${widgetName}" render failed`, e instanceof Error ? e : undefined);
1093
849
  return match[0];
1094
850
  }
1095
851
  };
@@ -1106,7 +862,7 @@ function resolveWidgetTags(html, registry, routeInfo, loadFiles, contextProvider
1106
862
  }
1107
863
  return result;
1108
864
  };
1109
- return resolveRecursively(html, parse, resolve, replace);
865
+ return resolveRecursively(html, parse, resolve, replace, 0, logger);
1110
866
  }
1111
867
  function parseAttrsToParams(attrsString) {
1112
868
  const params = {};
@@ -1115,11 +871,11 @@ function parseAttrsToParams(attrsString) {
1115
871
  const attrPattern = /(?<attr>[a-z][a-z0-9-]*)(?:="(?<dq>[^"]*)"|='(?<sq>[^']*)'|=(?<uq>[^\s>]+))?/gi;
1116
872
  for (const match of attrsString.matchAll(attrPattern)) {
1117
873
  const { attr: attrName, dq, sq, uq } = match.groups;
1118
- if (attrName === SSR_ATTR || attrName === LAZY_ATTR)
874
+ if (!attrName || attrName === SSR_ATTR || attrName === LAZY_ATTR)
1119
875
  continue;
1120
876
  const key = attrName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
1121
877
  const rawValue = dq ?? sq ?? uq;
1122
- if (rawValue === void 0) {
878
+ if (rawValue === undefined) {
1123
879
  params[key] = "";
1124
880
  continue;
1125
881
  }
@@ -1136,32 +892,11 @@ function escapeAttr(value) {
1136
892
  return value.replaceAll("&", "&amp;").replaceAll("'", "&#39;");
1137
893
  }
1138
894
 
1139
- // dist/src/component/abstract.component.js
1140
- var Component = class {
1141
- /** Host element reference, set by ComponentElement in the browser. */
895
+ // dist/core/component/abstract.component.js
896
+ class Component {
1142
897
  element;
1143
- /** Associated file paths for pre-loaded content (html, md, css). */
1144
898
  files;
1145
- /**
1146
- * When true, SSR serializes the getData() result into the element's
1147
- * light DOM so the client can access it immediately in hydrate()
1148
- * without re-fetching.
1149
- *
1150
- * Default is false — hydrate() receives `data: null`. Most widgets
1151
- * don't need this because the rendered Shadow DOM already contains
1152
- * the visual representation of the data.
1153
- *
1154
- * If you find yourself parsing the shadow DOM in hydrate() trying to
1155
- * reconstruct the original data object, set this to true instead.
1156
- * The server-fetched data will be available as `args.data` in hydrate().
1157
- */
1158
899
  exposeSsrData;
1159
- /**
1160
- * Render as HTML for browser context.
1161
- *
1162
- * Default implementation converts renderMarkdown() output to HTML.
1163
- * Override for custom HTML rendering with rich styling/interactivity.
1164
- */
1165
900
  renderHTML(args) {
1166
901
  if (args.data === null) {
1167
902
  return `<div data-component="${this.name}">Loading...</div>`;
@@ -1173,56 +908,23 @@ var Component = class {
1173
908
  });
1174
909
  return `<div data-component="${this.name}" data-markdown>${escapeHtml(markdown)}</div>`;
1175
910
  }
1176
- /**
1177
- * Render error state.
1178
- */
1179
911
  renderError(args) {
1180
912
  const msg = args.error instanceof Error ? args.error.message : String(args.error);
1181
913
  return `<div data-component="${this.name}">Error: ${escapeHtml(msg)}</div>`;
1182
914
  }
1183
- /**
1184
- * Render error as markdown.
1185
- */
1186
915
  renderMarkdownError(error) {
1187
916
  const msg = error instanceof Error ? error.message : String(error);
1188
917
  return `> **Error** (\`${this.name}\`): ${msg}`;
1189
918
  }
1190
- };
919
+ }
1191
920
 
1192
- // dist/src/component/page.component.js
1193
- var PageComponent = class extends Component {
921
+ // dist/core/component/page.component.js
922
+ class PageComponent extends Component {
1194
923
  name = "page";
1195
- /** Route pattern this page handles (optional — set by subclasses) */
1196
924
  pattern;
1197
- /**
1198
- * Fetch or compute page data. Override in subclasses.
1199
- * Default: returns null (no data needed).
1200
- *
1201
- * @example
1202
- * ```ts
1203
- * override getData({ params, context }: this['DataArgs']) {
1204
- * return fetch(`/api/${params.id}`, { signal: context?.signal });
1205
- * }
1206
- * ```
1207
- */
1208
925
  getData(_args) {
1209
926
  return Promise.resolve(null);
1210
927
  }
1211
- /**
1212
- * Render page as HTML.
1213
- *
1214
- * Fallback chain:
1215
- * 1. html file content from context
1216
- * 2. md file content wrapped in `<mark-down>`
1217
- * 3. `<router-slot />` (bare slot for child routes)
1218
- *
1219
- * @example
1220
- * ```ts
1221
- * override renderHTML({ data, params, context }: this['RenderArgs']) {
1222
- * return `<h1>${params.id}</h1><p>${context?.files?.html ?? ''}</p>`;
1223
- * }
1224
- * ```
1225
- */
1226
928
  renderHTML(args) {
1227
929
  const files = args.context.files;
1228
930
  const style = files?.css ? `<style>${files.css}</style>
@@ -1236,25 +938,12 @@ var PageComponent = class extends Component {
1236
938
  }
1237
939
  if (files?.md) {
1238
940
  const hasSlot = files.md.includes("```router-slot");
1239
- const slot = args.context.isLeaf || hasSlot ? "" : "\n<router-slot></router-slot>";
941
+ const slot = args.context.isLeaf || hasSlot ? "" : `
942
+ <router-slot></router-slot>`;
1240
943
  return `${style}<mark-down>${escapeHtml(files.md)}</mark-down>${slot}`;
1241
944
  }
1242
945
  return args.context.isLeaf ? "" : "<router-slot></router-slot>";
1243
946
  }
1244
- /**
1245
- * Render page as Markdown.
1246
- *
1247
- * Fallback chain:
1248
- * 1. md file content from context
1249
- * 2. `` ```router-slot\n``` `` (slot placeholder in markdown — newline required)
1250
- *
1251
- * @example
1252
- * ```ts
1253
- * override renderMarkdown({ data, params, context }: this['RenderArgs']) {
1254
- * return `# ${params.id}\n\n${context?.files?.md ?? ''}`;
1255
- * }
1256
- * ```
1257
- */
1258
947
  renderMarkdown(args) {
1259
948
  const files = args.context.files;
1260
949
  if (files?.md) {
@@ -1262,53 +951,39 @@ var PageComponent = class extends Component {
1262
951
  }
1263
952
  return args.context.isLeaf ? "" : "```router-slot\n```";
1264
953
  }
1265
- /**
1266
- * Page title. Override in subclasses.
1267
- * Default: undefined (no title).
1268
- *
1269
- * @example
1270
- * ```ts
1271
- * override getTitle({ data, params }: this['RenderArgs']) {
1272
- * return `Project ${params.id}`;
1273
- * }
1274
- * ```
1275
- */
1276
954
  getTitle(_args) {
1277
- return void 0;
955
+ return;
1278
956
  }
1279
- };
1280
- var page_component_default = new PageComponent();
957
+ }
958
+ var page_component_default = new PageComponent;
1281
959
 
1282
- // dist/src/renderer/ssr/ssr.renderer.js
1283
- var SsrRenderer = class _SsrRenderer {
1284
- core;
960
+ // dist/core/renderer/ssr.renderer.js
961
+ class SsrRenderer {
962
+ pipeline;
1285
963
  widgets;
1286
- widgetFiles;
1287
- constructor(resolver, options = {}) {
1288
- this.core = new RouteCore(resolver, options);
964
+ logger;
965
+ constructor(pipeline, options = {}) {
966
+ this.pipeline = pipeline;
967
+ this.logger = pipeline.logger;
1289
968
  this.widgets = options.widgets ?? null;
1290
- this.widgetFiles = options.widgetFiles ?? {};
1291
969
  }
1292
- /**
1293
- * Render a URL to a content string.
1294
- */
1295
970
  async render(url, signal) {
1296
- const matched = this.core.match(url);
971
+ const matched = await this.pipeline.match(url);
1297
972
  if (!matched) {
1298
- const statusPage = this.core.getStatusPage(404);
973
+ const statusPage = await this.pipeline.getStatusPage(404);
1299
974
  if (statusPage) {
1300
975
  try {
1301
976
  const ri = { url, params: {} };
1302
- const result = await this.renderRouteContent(ri, statusPage, void 0, signal);
1303
- return { content: this.stripSlots(result.content), status: 404, ...result.title != null ? { title: result.title } : {} };
977
+ const result = await this.renderRouteContent(ri, statusPage, undefined, signal);
978
+ return { content: this.stripSlots(result.content), status: 404, ...result.title !== undefined ? { title: result.title } : {} };
1304
979
  } catch (e) {
1305
- logger.error(`[${this.label}] Failed to render 404 status page for ${url.pathname}`, e instanceof Error ? e : void 0);
980
+ this.logger.error(`[${this.label}] Failed to render 404 status page for ${url.pathname}`, e instanceof Error ? e : undefined);
1306
981
  }
1307
982
  }
1308
983
  return { content: this.renderStatusPage(404, url), status: 404 };
1309
984
  }
1310
985
  if (matched.route.type === "redirect") {
1311
- const module = await this.core.loadModule(matched.route.modulePath);
986
+ const module = await this.pipeline.loadModule(matched.route.modulePath);
1312
987
  const redirectConfig = module.default;
1313
988
  assertSafeRedirect(redirectConfig.to);
1314
989
  return {
@@ -1317,36 +992,36 @@ var SsrRenderer = class _SsrRenderer {
1317
992
  redirect: redirectConfig.to
1318
993
  };
1319
994
  }
1320
- const routeInfo = this.core.toRouteInfo(matched, url);
995
+ const routeInfo = this.pipeline.toRouteInfo(matched, url);
1321
996
  try {
1322
997
  const { content, title } = await this.renderPage(routeInfo, matched, signal);
1323
- return { content, status: 200, ...title != null ? { title } : {} };
998
+ return { content, status: 200, ...title !== undefined ? { title } : {} };
1324
999
  } catch (error) {
1325
1000
  if (error instanceof Response) {
1326
- const statusPage = this.core.getStatusPage(error.status);
1001
+ const statusPage = await this.pipeline.getStatusPage(error.status);
1327
1002
  if (statusPage) {
1328
1003
  try {
1329
1004
  const ri = { url, params: {} };
1330
- const result = await this.renderRouteContent(ri, statusPage, void 0, signal);
1005
+ const result = await this.renderRouteContent(ri, statusPage, undefined, signal);
1331
1006
  return {
1332
1007
  content: this.stripSlots(result.content),
1333
1008
  status: error.status,
1334
- ...result.title != null ? { title: result.title } : {}
1009
+ ...result.title !== undefined ? { title: result.title } : {}
1335
1010
  };
1336
1011
  } catch (e) {
1337
- logger.error(`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`, e instanceof Error ? e : void 0);
1012
+ this.logger.error(`[${this.label}] Failed to render ${error.status} status page for ${url.pathname}`, e instanceof Error ? e : undefined);
1338
1013
  }
1339
1014
  }
1340
1015
  return { content: this.renderStatusPage(error.status, url), status: error.status };
1341
1016
  }
1342
- logger.error(`[${this.label}] Error rendering ${url.pathname}:`, error instanceof Error ? error : void 0);
1343
- const boundary = this.core.findErrorBoundary(url.pathname);
1017
+ this.logger.error(`[${this.label}] Error rendering ${url.pathname}:`, error instanceof Error ? error : undefined);
1018
+ const boundary = await this.pipeline.findErrorBoundary(url.pathname);
1344
1019
  if (boundary) {
1345
1020
  const result = await this.tryRenderErrorModule(boundary.modulePath, url, "boundary");
1346
1021
  if (result)
1347
1022
  return result;
1348
1023
  }
1349
- const errorHandler = this.core.getErrorHandler();
1024
+ const errorHandler = await this.pipeline.getErrorHandler();
1350
1025
  if (errorHandler) {
1351
1026
  const result = await this.tryRenderErrorModule(errorHandler.modulePath, url, "handler");
1352
1027
  if (result)
@@ -1355,15 +1030,12 @@ var SsrRenderer = class _SsrRenderer {
1355
1030
  return { content: this.renderErrorPage(error, url), status: 500 };
1356
1031
  }
1357
1032
  }
1358
- /**
1359
- * Render a matched page by composing the route hierarchy.
1360
- */
1361
1033
  async renderPage(routeInfo, matched, signal) {
1362
- const hierarchy = this.core.buildRouteHierarchy(matched.route.pattern);
1034
+ const hierarchy = this.pipeline.buildRouteHierarchy(matched.route.pattern);
1363
1035
  const segments = [];
1364
- for (let i = 0; i < hierarchy.length; i++) {
1036
+ for (let i = 0;i < hierarchy.length; i++) {
1365
1037
  const routePattern = hierarchy[i];
1366
- let route = this.core.findRoute(routePattern);
1038
+ let route = await this.pipeline.findRoute(routePattern);
1367
1039
  if (!route && routePattern === "/") {
1368
1040
  route = DEFAULT_ROOT_ROUTE;
1369
1041
  }
@@ -1377,7 +1049,7 @@ var SsrRenderer = class _SsrRenderer {
1377
1049
  let result = "";
1378
1050
  let pageTitle;
1379
1051
  let lastRenderedPattern = "";
1380
- for (let i = 0; i < segments.length; i++) {
1052
+ for (let i = 0;i < segments.length; i++) {
1381
1053
  const { content, title } = results[i];
1382
1054
  if (title) {
1383
1055
  pageTitle = title;
@@ -1387,59 +1059,57 @@ var SsrRenderer = class _SsrRenderer {
1387
1059
  } else {
1388
1060
  const injected = this.injectSlot(result, content, lastRenderedPattern);
1389
1061
  if (injected === result) {
1390
- logger.warn(`[${this.label}] Route "${lastRenderedPattern}" has no <router-slot> for child route "${hierarchy[i]}" to render into. Add <router-slot></router-slot> to the parent template.`);
1062
+ this.logger.warn(`[${this.label}] Route "${lastRenderedPattern}" has no <router-slot> ` + `for child route "${hierarchy[i]}" to render into. ` + `Add <router-slot></router-slot> to the parent template.`);
1391
1063
  }
1392
1064
  result = injected;
1393
1065
  }
1394
1066
  lastRenderedPattern = segments[i].route.pattern;
1395
1067
  }
1396
1068
  result = this.stripSlots(result);
1397
- return { content: result, ...pageTitle != null ? { title: pageTitle } : {} };
1069
+ return { content: result, ...pageTitle !== undefined ? { title: pageTitle } : {} };
1398
1070
  }
1399
- /** Load component, build context, get data, render content, get title. */
1400
1071
  async loadRouteContent(routeInfo, route, isLeaf, signal) {
1401
1072
  const files = route.files ?? {};
1402
1073
  const tsModule = files.ts ?? files.js;
1403
- const component = tsModule ? (await this.core.loadModule(tsModule)).default : page_component_default;
1404
- const context = await this.core.buildComponentContext(routeInfo, route, signal, isLeaf);
1074
+ const loadedModule = tsModule ? await this.pipeline.loadModule(tsModule) : undefined;
1075
+ const component = loadedModule?.default ?? page_component_default;
1076
+ const context = await this.pipeline.buildContext(routeInfo, route, signal, isLeaf, loadedModule);
1405
1077
  const data = await component.getData({ params: routeInfo.params, ...signal ? { signal } : {}, context });
1406
1078
  const content = this.renderContent(component, { data, params: routeInfo.params, context });
1407
1079
  const title = component.getTitle({ data, params: routeInfo.params, context });
1408
- return { content, ...title != null ? { title } : {} };
1080
+ return { content, ...title !== undefined ? { title } : {} };
1409
1081
  }
1410
- /** Render a component for error boundary/handler with minimal context. */
1411
1082
  renderComponent(component, data, context) {
1412
1083
  return this.renderContent(component, { data, params: {}, context });
1413
1084
  }
1414
1085
  static EMPTY_URL = new URL("http://error");
1415
- /** Try to load and render an error boundary or handler module. Returns null on failure. */
1416
1086
  async tryRenderErrorModule(modulePath, url, kind) {
1417
1087
  try {
1418
- const module = await this.core.loadModule(modulePath);
1088
+ const module = await this.pipeline.loadModule(modulePath);
1419
1089
  const component = module.default;
1420
1090
  const minCtx = {
1421
- url: _SsrRenderer.EMPTY_URL,
1091
+ url: SsrRenderer.EMPTY_URL,
1422
1092
  params: {},
1423
1093
  pathname: "",
1424
- searchParams: new URLSearchParams()
1094
+ searchParams: new URLSearchParams
1425
1095
  };
1426
1096
  const data = await component.getData({ params: {}, context: minCtx });
1427
1097
  const content = this.renderComponent(component, data, minCtx);
1428
1098
  return { content, status: 500 };
1429
1099
  } catch (e) {
1430
- logger.error(`[${this.label}] Error ${kind} failed for ${url.pathname}`, e instanceof Error ? e : void 0);
1100
+ this.logger.error(`[${this.label}] Error ${kind} failed for ${url.pathname}`, e instanceof Error ? e : undefined);
1431
1101
  return null;
1432
1102
  }
1433
1103
  }
1434
- };
1104
+ }
1435
1105
 
1436
- // dist/src/renderer/ssr/html.renderer.js
1437
- var SsrHtmlRouter = class extends SsrRenderer {
1106
+ // dist/core/renderer/html.renderer.js
1107
+ class SsrHtmlRenderer extends SsrRenderer {
1438
1108
  label = "SSR HTML";
1439
1109
  markdownRenderer;
1440
1110
  markdownReady = null;
1441
- constructor(resolver, options = {}) {
1442
- super(resolver, options);
1111
+ constructor(pipeline, options = {}) {
1112
+ super(pipeline, options);
1443
1113
  this.markdownRenderer = options.markdownRenderer ?? null;
1444
1114
  if (this.markdownRenderer?.init) {
1445
1115
  this.markdownReady = this.markdownRenderer.init();
@@ -1452,9 +1122,6 @@ var SsrHtmlRouter = class extends SsrRenderer {
1452
1122
  stripSlots(result) {
1453
1123
  return result.replace(/<router-slot[^>]*><\/router-slot>/g, "");
1454
1124
  }
1455
- /**
1456
- * Render a single route's content.
1457
- */
1458
1125
  async renderRouteContent(routeInfo, route, isLeaf, signal) {
1459
1126
  if (route.modulePath === DEFAULT_ROOT_ROUTE.modulePath) {
1460
1127
  return { content: `<router-slot pattern="${route.pattern}"></router-slot>` };
@@ -1464,12 +1131,18 @@ var SsrHtmlRouter = class extends SsrRenderer {
1464
1131
  content = await this.expandMarkdown(content);
1465
1132
  content = this.attributeSlots(content, route.pattern);
1466
1133
  if (this.widgets) {
1467
- content = await resolveWidgetTags(content, this.widgets, routeInfo, (name, declared) => {
1468
- const files = this.widgetFiles[name] ?? declared;
1469
- return files ? this.core.loadWidgetFiles(files) : Promise.resolve({});
1470
- }, this.core.contextProvider);
1134
+ content = await resolveWidgetTags(content, this.widgets, routeInfo, async (name) => {
1135
+ const modulePath = this.widgets.getModulePath(name);
1136
+ if (modulePath) {
1137
+ const mod = await this.pipeline.loadModule(modulePath);
1138
+ const inlined = this.pipeline.getModuleFiles(mod);
1139
+ if (inlined)
1140
+ return inlined;
1141
+ }
1142
+ return {};
1143
+ }, this.pipeline.contextProvider, this.logger);
1471
1144
  }
1472
- return { content, ...title != null ? { title } : {} };
1145
+ return { content, ...title !== undefined ? { title } : {} };
1473
1146
  }
1474
1147
  renderContent(component, args) {
1475
1148
  return component.renderHTML(args);
@@ -1491,14 +1164,9 @@ var SsrHtmlRouter = class extends SsrRenderer {
1491
1164
  <p>${escapeHtml(message)}</p>
1492
1165
  `;
1493
1166
  }
1494
- /** Add pattern attribute to bare <router-slot> tags. */
1495
1167
  attributeSlots(content, routePattern) {
1496
1168
  return content.replace(/<router-slot(?![^>]*\bpattern=)([^>]*)><\/router-slot>/g, `<router-slot pattern="${routePattern}"$1></router-slot>`);
1497
1169
  }
1498
- /**
1499
- * Expand <mark-down> tags by rendering markdown to HTML server-side.
1500
- * Leaves content unchanged if no markdown renderer is configured.
1501
- */
1502
1170
  async expandMarkdown(content) {
1503
1171
  if (!this.markdownRenderer)
1504
1172
  return content;
@@ -1511,13 +1179,12 @@ var SsrHtmlRouter = class extends SsrRenderer {
1511
1179
  const pattern = /<mark-down>([\s\S]*?)<\/mark-down>/g;
1512
1180
  return content.replace(pattern, (_match, escaped) => {
1513
1181
  const markdown = unescapeHtml(escaped);
1514
- const rendered = renderer.render(markdown);
1515
- return rendered;
1182
+ return renderer.render(markdown);
1516
1183
  });
1517
1184
  }
1518
- };
1185
+ }
1519
1186
 
1520
- // dist/src/widget/widget.parser.js
1187
+ // dist/core/widget/widget.parser.js
1521
1188
  var WIDGET_PATTERN = /```widget:(?<name>[a-z][a-z0-9-]*)\n(?<params>.*?)```/gs;
1522
1189
  function parseWidgetBlocks(markdown) {
1523
1190
  const blocks = [];
@@ -1560,17 +1227,18 @@ function replaceWidgetBlocks(markdown, replacements) {
1560
1227
  return result;
1561
1228
  }
1562
1229
 
1563
- // dist/src/renderer/ssr/md.renderer.js
1230
+ // dist/core/renderer/md.renderer.js
1564
1231
  var BARE_SLOT_BLOCK = "```router-slot\n```";
1565
1232
  function routerSlotBlock(pattern) {
1566
1233
  return `\`\`\`router-slot
1567
1234
  {"pattern":"${pattern}"}
1568
1235
  \`\`\``;
1569
1236
  }
1570
- var SsrMdRouter = class extends SsrRenderer {
1237
+
1238
+ class SsrMdRenderer extends SsrRenderer {
1571
1239
  label = "SSR MD";
1572
- constructor(resolver, options = {}) {
1573
- super(resolver, options);
1240
+ constructor(pipeline, options = {}) {
1241
+ super(pipeline, options);
1574
1242
  }
1575
1243
  injectSlot(parent, child, parentPattern) {
1576
1244
  return parent.replace(routerSlotBlock(parentPattern), child);
@@ -1578,9 +1246,6 @@ var SsrMdRouter = class extends SsrRenderer {
1578
1246
  stripSlots(result) {
1579
1247
  return result.replace(/```router-slot\n(?:\{[^}]*\}\n)?```/g, "").trim();
1580
1248
  }
1581
- /**
1582
- * Render a single route's content to Markdown.
1583
- */
1584
1249
  async renderRouteContent(routeInfo, route, isLeaf, signal) {
1585
1250
  if (route.modulePath === DEFAULT_ROOT_ROUTE.modulePath) {
1586
1251
  return { content: routerSlotBlock(route.pattern) };
@@ -1591,7 +1256,7 @@ var SsrMdRouter = class extends SsrRenderer {
1591
1256
  if (this.widgets) {
1592
1257
  content = await this.resolveWidgets(content, routeInfo);
1593
1258
  }
1594
- return { content, ...title != null ? { title } : {} };
1259
+ return { content, ...title !== undefined ? { title } : {} };
1595
1260
  }
1596
1261
  renderContent(component, args) {
1597
1262
  return component.renderMarkdown(args);
@@ -1609,10 +1274,6 @@ Path: \`${url.pathname}\``;
1609
1274
 
1610
1275
  Path: \`${url.pathname}\``;
1611
1276
  }
1612
- /**
1613
- * Resolve fenced widget blocks in markdown content.
1614
- * Replaces ```widget:name blocks with rendered markdown output.
1615
- */
1616
1277
  resolveWidgets(content, routeInfo) {
1617
1278
  return resolveRecursively(content, parseWidgetBlocks, async (block) => {
1618
1279
  if (block.parseError || !block.params) {
@@ -1624,9 +1285,10 @@ Path: \`${url.pathname}\``;
1624
1285
  }
1625
1286
  try {
1626
1287
  let files;
1627
- const filePaths = this.widgetFiles[block.widgetName] ?? widget.files;
1628
- if (filePaths) {
1629
- files = await this.core.loadWidgetFiles(filePaths);
1288
+ const modulePath = this.widgets.getModulePath(block.widgetName);
1289
+ if (modulePath) {
1290
+ const mod = await this.pipeline.loadModule(modulePath);
1291
+ files = this.pipeline.getModuleFiles(mod);
1630
1292
  }
1631
1293
  const baseContext = {
1632
1294
  ...routeInfo,
@@ -1634,25 +1296,26 @@ Path: \`${url.pathname}\``;
1634
1296
  searchParams: routeInfo.url.searchParams,
1635
1297
  ...files ? { files } : {}
1636
1298
  };
1637
- const context = this.core.contextProvider ? this.core.contextProvider(baseContext) : baseContext;
1299
+ const context = this.pipeline.contextProvider ? this.pipeline.contextProvider(baseContext) : baseContext;
1638
1300
  const data = await widget.getData({ params: block.params, context });
1639
1301
  return widget.renderMarkdown({ data, params: block.params, context });
1640
1302
  } catch (e) {
1641
1303
  return widget.renderMarkdownError(e);
1642
1304
  }
1643
- }, replaceWidgetBlocks);
1305
+ }, replaceWidgetBlocks, 0, this.logger);
1644
1306
  }
1645
- };
1307
+ }
1646
1308
 
1647
- // dist/src/util/md.util.js
1309
+ // dist/core/util/md.util.js
1648
1310
  function rewriteMdLinks(markdown, base, skipPrefixes) {
1649
1311
  const prefix = base + "/";
1650
1312
  const skip = skipPrefixes.map((p) => p.slice(1) + "/").join("|");
1651
1313
  const inlineRe = new RegExp(`\\]\\(\\/(?!${skip})`, "g");
1652
1314
  const refRe = new RegExp(`^(\\[[^\\]]+\\]:\\s+)\\/(?!${skip})`, "g");
1653
- const lines = markdown.split("\n");
1315
+ const lines = markdown.split(`
1316
+ `);
1654
1317
  let inCodeBlock = false;
1655
- for (let i = 0; i < lines.length; i++) {
1318
+ for (let i = 0;i < lines.length; i++) {
1656
1319
  if (lines[i].startsWith("```")) {
1657
1320
  inCodeBlock = !inCodeBlock;
1658
1321
  continue;
@@ -1662,10 +1325,219 @@ function rewriteMdLinks(markdown, base, skipPrefixes) {
1662
1325
  lines[i] = lines[i].replaceAll(inlineRe, `](${prefix}`);
1663
1326
  lines[i] = lines[i].replaceAll(refRe, `$1${prefix}`);
1664
1327
  }
1665
- return lines.join("\n");
1328
+ return lines.join(`
1329
+ `);
1330
+ }
1331
+
1332
+ // dist/core/server/emroute.server.js
1333
+ var DEFAULT_BASE_PATH = { html: "/html", md: "/md", app: "/app" };
1334
+
1335
+ class Emroute {
1336
+ runtime;
1337
+ htmlBase;
1338
+ mdBase;
1339
+ appBase;
1340
+ spa;
1341
+ title;
1342
+ htmlRenderer;
1343
+ mdRenderer;
1344
+ shell;
1345
+ constructor(htmlRenderer, mdRenderer, shell, runtime, htmlBase, mdBase, appBase, spa, title) {
1346
+ this.runtime = runtime;
1347
+ this.htmlBase = htmlBase;
1348
+ this.mdBase = mdBase;
1349
+ this.appBase = appBase;
1350
+ this.spa = spa;
1351
+ this.title = title;
1352
+ this.htmlRenderer = htmlRenderer;
1353
+ this.mdRenderer = mdRenderer;
1354
+ this.shell = shell;
1355
+ }
1356
+ static async create(config, runtime) {
1357
+ const { spa = "root" } = config;
1358
+ const { html: htmlBase, md: mdBase, app: appBase } = config.basePath ?? DEFAULT_BASE_PATH;
1359
+ const manifestResponse = await runtime.query(ROUTES_MANIFEST_PATH);
1360
+ if (manifestResponse.status === 404 && !config.routeTree) {
1361
+ throw new Error(`[emroute] ${ROUTES_MANIFEST_PATH} not found in runtime. ` + "Provide routeTree in config or ensure the runtime produces it.");
1362
+ }
1363
+ if (config.routeTree && manifestResponse.status === 404) {
1364
+ await runtime.command(ROUTES_MANIFEST_PATH, {
1365
+ body: JSON.stringify(config.routeTree)
1366
+ });
1367
+ }
1368
+ const pipeline = new Pipeline({
1369
+ runtime,
1370
+ ...config.extendContext ? { contextProvider: config.extendContext } : {},
1371
+ ...config.moduleLoaders ? { moduleLoaders: config.moduleLoaders } : {}
1372
+ });
1373
+ let widgets = config.widgets;
1374
+ const widgetsResponse = await runtime.query(WIDGETS_MANIFEST_PATH);
1375
+ if (widgetsResponse.status !== 404) {
1376
+ const entries = await widgetsResponse.json();
1377
+ if (!config.widgets) {
1378
+ widgets = await Emroute.importWidgets(entries, runtime);
1379
+ }
1380
+ }
1381
+ let ssrHtmlRenderer = null;
1382
+ let ssrMdRenderer = null;
1383
+ if (spa !== "only") {
1384
+ ssrHtmlRenderer = new SsrHtmlRenderer(pipeline, {
1385
+ ...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {},
1386
+ ...widgets ? { widgets } : {}
1387
+ });
1388
+ ssrMdRenderer = new SsrMdRenderer(pipeline, {
1389
+ ...widgets ? { widgets } : {}
1390
+ });
1391
+ }
1392
+ const title = config.title ?? "emroute";
1393
+ const shellBase = spa === "root" || spa === "only" ? appBase : htmlBase;
1394
+ let shell = await Emroute.resolveShell(runtime, title, shellBase);
1395
+ if ((await runtime.query("/main.css")).status !== 404) {
1396
+ shell = shell.replace("</head>", ` <link rel="stylesheet" href="/main.css">
1397
+ </head>`);
1398
+ }
1399
+ return new Emroute(ssrHtmlRenderer, ssrMdRenderer, shell, runtime, htmlBase, mdBase, appBase, spa, title);
1400
+ }
1401
+ async handleRequest(req) {
1402
+ const url = new URL(req.url);
1403
+ const pathname = url.pathname;
1404
+ const mdPrefix = this.mdBase + "/";
1405
+ const htmlPrefix = this.htmlBase + "/";
1406
+ const appPrefix = this.appBase + "/";
1407
+ if (this.mdRenderer && (pathname.startsWith(mdPrefix) || pathname === this.mdBase)) {
1408
+ const routePath = pathname === this.mdBase ? "/" : pathname.slice(this.mdBase.length);
1409
+ if (routePath.length > 1 && routePath.endsWith("/")) {
1410
+ const canonical = this.mdBase + routePath.slice(0, -1) + (url.search || "");
1411
+ return Response.redirect(new URL(canonical, url.origin), 301);
1412
+ }
1413
+ try {
1414
+ const routeUrl = new URL(routePath + url.search, url.origin);
1415
+ const { content, status, redirect } = await this.mdRenderer.render(routeUrl, req.signal);
1416
+ if (redirect) {
1417
+ const target = redirect.startsWith("/") ? this.mdBase + redirect : redirect;
1418
+ return Response.redirect(new URL(target, url.origin), status);
1419
+ }
1420
+ return new Response(rewriteMdLinks(content, this.mdBase, [this.mdBase, this.htmlBase]), {
1421
+ status,
1422
+ headers: { "Content-Type": "text/markdown; charset=utf-8; variant=CommonMark" }
1423
+ });
1424
+ } catch (e) {
1425
+ console.error(`[emroute] Error rendering ${pathname}:`, e);
1426
+ return new Response("Internal Server Error", { status: 500 });
1427
+ }
1428
+ }
1429
+ if (this.htmlRenderer && (pathname.startsWith(htmlPrefix) || pathname === this.htmlBase)) {
1430
+ const routePath = pathname === this.htmlBase ? "/" : pathname.slice(this.htmlBase.length);
1431
+ if (routePath.length > 1 && routePath.endsWith("/")) {
1432
+ const canonical = this.htmlBase + routePath.slice(0, -1) + (url.search || "");
1433
+ return Response.redirect(new URL(canonical, url.origin), 301);
1434
+ }
1435
+ try {
1436
+ const routeUrl = new URL(routePath + url.search, url.origin);
1437
+ const result = await this.htmlRenderer.render(routeUrl, req.signal);
1438
+ if (result.redirect) {
1439
+ const target = result.redirect.startsWith("/") ? this.htmlBase + result.redirect : result.redirect;
1440
+ return Response.redirect(new URL(target, url.origin), result.status);
1441
+ }
1442
+ const ssrTitle = result.title ?? this.title;
1443
+ const html = Emroute.injectSsrContent(this.shell, result.content, ssrTitle, pathname);
1444
+ return new Response(html, {
1445
+ status: result.status,
1446
+ headers: { "Content-Type": "text/html; charset=utf-8" }
1447
+ });
1448
+ } catch (e) {
1449
+ console.error(`[emroute] Error rendering ${pathname}:`, e);
1450
+ return new Response("Internal Server Error", { status: 500 });
1451
+ }
1452
+ }
1453
+ if (pathname.startsWith(appPrefix) || pathname === this.appBase) {
1454
+ return new Response(this.shell, {
1455
+ status: 200,
1456
+ headers: { "Content-Type": "text/html; charset=utf-8" }
1457
+ });
1458
+ }
1459
+ if (pathname.startsWith(htmlPrefix) || pathname === this.htmlBase || pathname.startsWith(mdPrefix) || pathname === this.mdBase) {
1460
+ return new Response(this.shell, {
1461
+ status: 200,
1462
+ headers: { "Content-Type": "text/html; charset=utf-8" }
1463
+ });
1464
+ }
1465
+ const lastSegment = pathname.split("/").pop() ?? "";
1466
+ if (lastSegment.includes(".")) {
1467
+ const fileResponse = await this.runtime.handle(pathname);
1468
+ if (fileResponse.status === 200)
1469
+ return fileResponse;
1470
+ return null;
1471
+ }
1472
+ const base = this.spa === "root" || this.spa === "only" ? this.appBase : this.htmlBase;
1473
+ const bare = pathname === "/" ? "" : pathname.slice(1).replace(/\/$/, "");
1474
+ return Response.redirect(new URL(`${base}/${bare}`, url.origin), 302);
1475
+ }
1476
+ static extractWidgetExport(mod) {
1477
+ for (const value of Object.values(mod)) {
1478
+ if (!value)
1479
+ continue;
1480
+ if (typeof value === "object" && "getData" in value) {
1481
+ return value;
1482
+ }
1483
+ if (typeof value === "function" && value.prototype?.getData) {
1484
+ return new value;
1485
+ }
1486
+ }
1487
+ return null;
1488
+ }
1489
+ static async importWidgets(entries, runtime) {
1490
+ const registry = new WidgetRegistry;
1491
+ for (const entry of entries) {
1492
+ try {
1493
+ const runtimePath = entry.modulePath.startsWith("/") ? entry.modulePath : `/${entry.modulePath}`;
1494
+ const mod = await runtime.loadModule(runtimePath);
1495
+ const instance = Emroute.extractWidgetExport(mod);
1496
+ if (!instance)
1497
+ continue;
1498
+ registry.add(instance, runtimePath);
1499
+ } catch (e) {
1500
+ console.error(`[emroute] Failed to load widget ${entry.modulePath}:`, e);
1501
+ }
1502
+ }
1503
+ return registry;
1504
+ }
1505
+ static buildHtmlShell(title, htmlBase) {
1506
+ const baseTag = htmlBase ? `
1507
+ <base href="${escapeHtml(htmlBase)}/">` : "";
1508
+ return `<!DOCTYPE html>
1509
+ <html>
1510
+ <head>${baseTag}
1511
+ <meta charset="utf-8">
1512
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1513
+ <title>${escapeHtml(title)}</title>
1514
+ <style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>
1515
+ </head>
1516
+ <body>
1517
+ <router-slot></router-slot>
1518
+ </body>
1519
+ </html>`;
1520
+ }
1521
+ static injectSsrContent(html, content, title, ssrRoute) {
1522
+ const slotPattern = /<router-slot\b[^>]*>.*?<\/router-slot>/s;
1523
+ if (!slotPattern.test(html))
1524
+ return html;
1525
+ const ssrAttr = ssrRoute ? ` data-ssr-route="${ssrRoute}"` : "";
1526
+ html = html.replace(slotPattern, `<router-slot${ssrAttr}>${content}</router-slot>`);
1527
+ if (title) {
1528
+ html = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(title)}</title>`);
1529
+ }
1530
+ return html;
1531
+ }
1532
+ static async resolveShell(runtime, title, htmlBase) {
1533
+ const response = await runtime.query("/index.html");
1534
+ if (response.status !== 404)
1535
+ return await response.text();
1536
+ return Emroute.buildHtmlShell(title, htmlBase);
1537
+ }
1666
1538
  }
1667
1539
 
1668
- // dist/src/route/route-tree.util.js
1540
+ // dist/core/util/route-tree.util.js
1669
1541
  function resolveTargetNode(node, name, isRoot) {
1670
1542
  if (name === "index") {
1671
1543
  if (isRoot)
@@ -1684,104 +1556,425 @@ function resolveTargetNode(node, name, isRoot) {
1684
1556
  }
1685
1557
 
1686
1558
  // dist/runtime/abstract.runtime.js
1559
+ var CONTENT_TYPES = new Map([
1560
+ [".html", "text/html; charset=utf-8"],
1561
+ [".css", "text/css; charset=utf-8"],
1562
+ [".js", "application/javascript; charset=utf-8"],
1563
+ [".mjs", "application/javascript; charset=utf-8"],
1564
+ [".ts", "text/typescript; charset=utf-8"],
1565
+ [".json", "application/json; charset=utf-8"],
1566
+ [".md", "text/plain; charset=utf-8"],
1567
+ [".txt", "text/plain; charset=utf-8"],
1568
+ [".wasm", "application/wasm"],
1569
+ [".map", "application/json; charset=utf-8"],
1570
+ [".png", "image/png"],
1571
+ [".jpg", "image/jpeg"],
1572
+ [".jpeg", "image/jpeg"],
1573
+ [".gif", "image/gif"],
1574
+ [".svg", "image/svg+xml"],
1575
+ [".ico", "image/x-icon"],
1576
+ [".webp", "image/webp"],
1577
+ [".avif", "image/avif"],
1578
+ [".woff", "font/woff"],
1579
+ [".woff2", "font/woff2"],
1580
+ [".ttf", "font/ttf"]
1581
+ ]);
1687
1582
  var DEFAULT_ROUTES_DIR = "/routes";
1688
1583
  var DEFAULT_WIDGETS_DIR = "/widgets";
1689
1584
  var DEFAULT_ELEMENTS_DIR = "/elements";
1690
- var ROUTES_MANIFEST_PATH = "/routes.manifest.json";
1691
- var WIDGETS_MANIFEST_PATH = "/widgets.manifest.json";
1692
- var ELEMENTS_MANIFEST_PATH = "/elements.manifest.json";
1693
- var Runtime = class {
1585
+ class Runtime {
1694
1586
  config;
1695
1587
  constructor(config = {}) {
1696
1588
  this.config = config;
1697
1589
  this.config = config;
1698
1590
  }
1699
- /** Write. Defaults to PUT; pass `{ method: "DELETE" }` etc. to override. */
1700
1591
  command(resource, options) {
1701
1592
  const path = typeof resource === "string" ? resource : new URL(resource instanceof Request ? resource.url : resource.toString()).pathname;
1702
- const result = this.handle(resource, { method: "PUT", ...options });
1593
+ const method = options?.method ?? "PUT";
1594
+ const isDelete = method === "DELETE";
1595
+ const result = this.handle(resource, { method, ...options });
1703
1596
  const routesDir = this.config.routesDir ?? DEFAULT_ROUTES_DIR;
1597
+ const widgetsDir = this.config.widgetsDir ?? DEFAULT_WIDGETS_DIR;
1598
+ const elementsDir = this.config.elementsDir ?? DEFAULT_ELEMENTS_DIR;
1704
1599
  if (path.startsWith(routesDir + "/")) {
1705
1600
  return result.then(async (res) => {
1706
- await this.mergeRouteIntoManifest(path, routesDir);
1601
+ if (isDelete) {
1602
+ await this.pruneRouteFromManifest(path, routesDir);
1603
+ } else {
1604
+ await this.mergeRouteIntoManifest(path, routesDir);
1605
+ await this.retranspileIfNeeded(path, routesDir, "route");
1606
+ }
1707
1607
  return res;
1708
1608
  });
1709
1609
  }
1710
- return result;
1610
+ if (path.startsWith(widgetsDir + "/")) {
1611
+ return result.then(async (res) => {
1612
+ if (isDelete) {
1613
+ await this.pruneWidgetFromManifest(path, widgetsDir);
1614
+ } else {
1615
+ await this.mergeWidgetIntoManifest(path, widgetsDir);
1616
+ await this.retranspileIfNeeded(path, widgetsDir, "widget");
1617
+ }
1618
+ return res;
1619
+ });
1620
+ }
1621
+ if (path.startsWith(elementsDir + "/")) {
1622
+ return result.then(async (res) => {
1623
+ if (isDelete) {
1624
+ await this.pruneElementFromManifest(path, elementsDir);
1625
+ } else {
1626
+ await this.mergeElementIntoManifest(path, elementsDir);
1627
+ await this.retranspileIfNeeded(path, elementsDir, "element");
1628
+ }
1629
+ return res;
1630
+ });
1631
+ }
1632
+ return result;
1633
+ }
1634
+ async mergeRouteIntoManifest(filePath, routesDir) {
1635
+ const relativePath = filePath.slice(routesDir.length + 1);
1636
+ const parts = relativePath.split("/");
1637
+ const filename = parts[parts.length - 1];
1638
+ const dirSegments = parts.slice(0, -1);
1639
+ const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
1640
+ if (!match)
1641
+ return;
1642
+ const name = match[1];
1643
+ const kind = match[2];
1644
+ const ext = match[3];
1645
+ const response = await this.handle(ROUTES_MANIFEST_PATH);
1646
+ const tree = response.status === 404 ? {} : await response.json();
1647
+ let node = tree;
1648
+ for (const dir of dirSegments) {
1649
+ if (dir.startsWith("[") && dir.endsWith("]")) {
1650
+ const param = dir.slice(1, -1);
1651
+ node.dynamic ??= { param, child: {} };
1652
+ node = node.dynamic.child;
1653
+ } else {
1654
+ node.children ??= {};
1655
+ node.children[dir] ??= {};
1656
+ node = node.children[dir];
1657
+ }
1658
+ }
1659
+ if (kind === "error") {
1660
+ node.errorBoundary = filePath;
1661
+ } else {
1662
+ const target = resolveTargetNode(node, name, dirSegments.length === 0);
1663
+ if (kind === "redirect") {
1664
+ target.redirect = filePath;
1665
+ } else {
1666
+ target.files ??= {};
1667
+ target.files[ext] = filePath;
1668
+ }
1669
+ }
1670
+ this.routesManifestCache = null;
1671
+ await this.handle(ROUTES_MANIFEST_PATH, {
1672
+ method: "PUT",
1673
+ body: JSON.stringify(tree)
1674
+ });
1675
+ }
1676
+ async pruneRouteFromManifest(filePath, routesDir) {
1677
+ const relativePath = filePath.slice(routesDir.length + 1);
1678
+ const parts = relativePath.split("/");
1679
+ const filename = parts[parts.length - 1];
1680
+ const dirSegments = parts.slice(0, -1);
1681
+ const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
1682
+ if (!match)
1683
+ return;
1684
+ const name = match[1];
1685
+ const kind = match[2];
1686
+ const ext = match[3];
1687
+ const response = await this.handle(ROUTES_MANIFEST_PATH);
1688
+ if (response.status === 404)
1689
+ return;
1690
+ const tree = await response.json();
1691
+ const ancestors = [];
1692
+ let node = tree;
1693
+ for (const dir of dirSegments) {
1694
+ if (dir.startsWith("[") && dir.endsWith("]")) {
1695
+ if (!node.dynamic)
1696
+ return;
1697
+ ancestors.push({ node, key: dir, via: "dynamic" });
1698
+ node = node.dynamic.child;
1699
+ } else {
1700
+ if (!node.children?.[dir])
1701
+ return;
1702
+ ancestors.push({ node, key: dir, via: "children" });
1703
+ node = node.children[dir];
1704
+ }
1705
+ }
1706
+ if (kind === "error") {
1707
+ if (node.errorBoundary === filePath)
1708
+ delete node.errorBoundary;
1709
+ } else {
1710
+ const isRoot = dirSegments.length === 0;
1711
+ const target = this.findTargetNode(node, name, isRoot);
1712
+ if (!target)
1713
+ return;
1714
+ if (kind === "redirect") {
1715
+ if (target.redirect === filePath)
1716
+ delete target.redirect;
1717
+ } else {
1718
+ if (target.files?.[ext] === filePath) {
1719
+ delete target.files[ext];
1720
+ if (Object.keys(target.files).length === 0)
1721
+ delete target.files;
1722
+ }
1723
+ }
1724
+ if (target !== node && this.isEmptyNode(target)) {
1725
+ if (name === "index" && !isRoot) {
1726
+ delete node.wildcard;
1727
+ } else if (name.startsWith("[") && name.endsWith("]")) {
1728
+ delete node.dynamic;
1729
+ } else if (node.children) {
1730
+ delete node.children[name];
1731
+ if (Object.keys(node.children).length === 0)
1732
+ delete node.children;
1733
+ }
1734
+ }
1735
+ }
1736
+ for (let i = ancestors.length - 1;i >= 0; i--) {
1737
+ const { node: parent, key, via } = ancestors[i];
1738
+ const child = via === "dynamic" ? parent.dynamic?.child : parent.children?.[key];
1739
+ if (child && this.isEmptyNode(child)) {
1740
+ if (via === "dynamic") {
1741
+ delete parent.dynamic;
1742
+ } else if (parent.children) {
1743
+ delete parent.children[key];
1744
+ if (Object.keys(parent.children).length === 0)
1745
+ delete parent.children;
1746
+ }
1747
+ }
1748
+ }
1749
+ this.routesManifestCache = null;
1750
+ await this.handle(ROUTES_MANIFEST_PATH, {
1751
+ method: "PUT",
1752
+ body: JSON.stringify(tree)
1753
+ });
1754
+ }
1755
+ findTargetNode(node, name, isRoot) {
1756
+ if (name === "index") {
1757
+ return isRoot ? node : node.wildcard?.child ?? null;
1758
+ }
1759
+ if (name.startsWith("[") && name.endsWith("]")) {
1760
+ return node.dynamic?.child ?? null;
1761
+ }
1762
+ return node.children?.[name] ?? null;
1763
+ }
1764
+ isEmptyNode(node) {
1765
+ return !node.files && !node.errorBoundary && !node.redirect && !node.children && !node.dynamic && !node.wildcard;
1766
+ }
1767
+ async pruneWidgetFromManifest(filePath, widgetsDir) {
1768
+ const relativePath = filePath.slice(widgetsDir.length + 1);
1769
+ const parts = relativePath.split("/");
1770
+ if (parts.length !== 2)
1771
+ return;
1772
+ const [dirName, filename] = parts;
1773
+ const match = filename.match(/^(.+?)\.widget\.(ts|js|html|md|css)$/);
1774
+ if (!match)
1775
+ return;
1776
+ const name = match[1];
1777
+ const ext = match[2];
1778
+ if (name !== dirName)
1779
+ return;
1780
+ const response = await this.handle(WIDGETS_MANIFEST_PATH);
1781
+ if (response.status === 404)
1782
+ return;
1783
+ const entries = await response.json();
1784
+ if (ext === "ts" || ext === "js") {
1785
+ const idx = entries.findIndex((e) => e.name === name);
1786
+ if (idx === -1)
1787
+ return;
1788
+ entries.splice(idx, 1);
1789
+ } else {
1790
+ const entry = entries.find((e) => e.name === name);
1791
+ if (!entry?.files)
1792
+ return;
1793
+ delete entry.files[ext];
1794
+ if (Object.keys(entry.files).length === 0)
1795
+ delete entry.files;
1796
+ }
1797
+ this.widgetsManifestCache = null;
1798
+ await this.handle(WIDGETS_MANIFEST_PATH, {
1799
+ method: "PUT",
1800
+ body: JSON.stringify(entries)
1801
+ });
1802
+ }
1803
+ async pruneElementFromManifest(filePath, elementsDir) {
1804
+ const relativePath = filePath.slice(elementsDir.length + 1);
1805
+ const parts = relativePath.split("/");
1806
+ if (parts.length !== 2)
1807
+ return;
1808
+ const [dirName, filename] = parts;
1809
+ const match = filename.match(/^(.+?)\.element\.(ts|js)$/);
1810
+ if (!match)
1811
+ return;
1812
+ const name = match[1];
1813
+ if (name !== dirName)
1814
+ return;
1815
+ const response = await this.handle(ELEMENTS_MANIFEST_PATH);
1816
+ if (response.status === 404)
1817
+ return;
1818
+ const entries = await response.json();
1819
+ const idx = entries.findIndex((e) => e.name === name);
1820
+ if (idx === -1)
1821
+ return;
1822
+ entries.splice(idx, 1);
1823
+ this.elementsManifestCache = null;
1824
+ await this.handle(ELEMENTS_MANIFEST_PATH, {
1825
+ method: "PUT",
1826
+ body: JSON.stringify(entries)
1827
+ });
1828
+ }
1829
+ async retranspileIfNeeded(filePath, dir, kind) {
1830
+ if (filePath.endsWith(".js"))
1831
+ return;
1832
+ const relativePath = filePath.slice(dir.length + 1);
1833
+ const parts = relativePath.split("/");
1834
+ const filename = parts[parts.length - 1];
1835
+ let jsPath;
1836
+ if (kind === "route") {
1837
+ const match = filename.match(/^(.+?)\.(page)\.(ts|html|md|css)$/);
1838
+ if (!match)
1839
+ return;
1840
+ const name = match[1];
1841
+ jsPath = `${dir}/${parts.slice(0, -1).join("/")}${parts.length > 1 ? "/" : ""}${name}.page.js`;
1842
+ } else if (kind === "widget") {
1843
+ const match = filename.match(/^(.+?)\.(widget)\.(ts|html|md|css)$/);
1844
+ if (!match)
1845
+ return;
1846
+ const name = match[1];
1847
+ jsPath = `${dir}/${name}/${name}.widget.js`;
1848
+ } else {
1849
+ const match = filename.match(/^(.+?)\.(element)\.ts$/);
1850
+ if (!match)
1851
+ return;
1852
+ const name = match[1];
1853
+ jsPath = `${dir}/${name}/${name}.element.js`;
1854
+ }
1855
+ const jsResponse = await this.handle(jsPath);
1856
+ if (jsResponse.status === 404)
1857
+ return;
1858
+ const tsPath = jsPath.replace(/\.js$/, ".ts");
1859
+ let tsSource;
1860
+ try {
1861
+ tsSource = await this.query(tsPath, { as: "text" });
1862
+ } catch {
1863
+ return;
1864
+ }
1865
+ const companionExts = kind === "element" ? [] : ["html", "md", "css"];
1866
+ const files = {};
1867
+ for (const ext of companionExts) {
1868
+ const companionPath = tsPath.replace(/\.ts$/, `.${ext}`);
1869
+ try {
1870
+ files[ext] = await this.query(companionPath, { as: "text" });
1871
+ } catch {}
1872
+ }
1873
+ let jsCode;
1874
+ try {
1875
+ jsCode = await this.transpile(tsSource);
1876
+ } catch {
1877
+ return;
1878
+ }
1879
+ if (Object.keys(files).length > 0) {
1880
+ const entries = Object.entries(files).map(([k, v]) => `${k}: \`${v.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$")}\``).join(", ");
1881
+ jsCode += `
1882
+ export const __files = { ${entries} };
1883
+ `;
1884
+ }
1885
+ await this.handle(jsPath, { method: "PUT", body: jsCode });
1711
1886
  }
1712
- /**
1713
- * Parse a single route file path and merge it into the stored manifest.
1714
- * Avoids a full directory rescan — just reads the current manifest,
1715
- * inserts the new entry, and writes it back.
1716
- */
1717
- async mergeRouteIntoManifest(filePath, routesDir) {
1718
- const relativePath = filePath.slice(routesDir.length + 1);
1887
+ loadModule(_path) {
1888
+ throw new Error(`loadModule not implemented for ${this.constructor.name}`);
1889
+ }
1890
+ transpile(_source) {
1891
+ throw new Error(`transpile not implemented for ${this.constructor.name}`);
1892
+ }
1893
+ async mergeWidgetIntoManifest(filePath, widgetsDir) {
1894
+ const relativePath = filePath.slice(widgetsDir.length + 1);
1719
1895
  const parts = relativePath.split("/");
1720
- const filename = parts[parts.length - 1];
1721
- const dirSegments = parts.slice(0, -1);
1722
- const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
1896
+ if (parts.length !== 2)
1897
+ return;
1898
+ const [dirName, filename] = parts;
1899
+ const match = filename.match(/^(.+?)\.widget\.(ts|js|html|md|css)$/);
1723
1900
  if (!match)
1724
1901
  return;
1725
- const [, name, kind, ext] = match;
1726
- const response = await this.handle(ROUTES_MANIFEST_PATH);
1727
- const tree = response.status === 404 ? {} : await response.json();
1728
- let node = tree;
1729
- for (const dir of dirSegments) {
1730
- if (dir.startsWith("[") && dir.endsWith("]")) {
1731
- const param = dir.slice(1, -1);
1732
- node.dynamic ??= { param, child: {} };
1733
- node = node.dynamic.child;
1902
+ const name = match[1];
1903
+ const ext = match[2];
1904
+ if (name !== dirName)
1905
+ return;
1906
+ const response = await this.handle(WIDGETS_MANIFEST_PATH);
1907
+ const entries = response.status === 404 ? [] : await response.json();
1908
+ const prefix = widgetsDir.replace(/^\//, "");
1909
+ if (ext === "ts" || ext === "js") {
1910
+ let entry = entries.find((e) => e.name === name);
1911
+ if (!entry) {
1912
+ entry = {
1913
+ name,
1914
+ modulePath: `${prefix}/${name}/${filename}`,
1915
+ tagName: `widget-${name}`
1916
+ };
1917
+ entries.push(entry);
1918
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1734
1919
  } else {
1735
- node.children ??= {};
1736
- node.children[dir] ??= {};
1737
- node = node.children[dir];
1920
+ entry.modulePath = `${prefix}/${name}/${filename}`;
1738
1921
  }
1739
- }
1740
- if (kind === "error") {
1741
- node.errorBoundary = filePath;
1742
1922
  } else {
1743
- const target = resolveTargetNode(node, name, dirSegments.length === 0);
1744
- if (kind === "redirect") {
1745
- target.redirect = filePath;
1746
- } else {
1747
- target.files ??= {};
1748
- target.files[ext] = filePath;
1749
- }
1923
+ const entry = entries.find((e) => e.name === name);
1924
+ if (!entry)
1925
+ return;
1926
+ entry.files ??= {};
1927
+ entry.files[ext] = `${prefix}/${name}/${filename}`;
1750
1928
  }
1751
- this.routesManifestCache = null;
1752
- await this.handle(ROUTES_MANIFEST_PATH, {
1929
+ this.widgetsManifestCache = null;
1930
+ await this.handle(WIDGETS_MANIFEST_PATH, {
1753
1931
  method: "PUT",
1754
- body: JSON.stringify(tree)
1932
+ body: JSON.stringify(entries)
1755
1933
  });
1756
1934
  }
1757
- /**
1758
- * Dynamically import a module from this runtime's storage.
1759
- * Used by the server for SSR imports of `.page.ts` and `.widget.ts` files.
1760
- */
1761
- loadModule(_path) {
1762
- throw new Error(`loadModule not implemented for ${this.constructor.name}`);
1763
- }
1764
- /**
1765
- * Transpile TypeScript source to JavaScript.
1766
- * Used by the build step to produce browser-loadable .js modules.
1767
- */
1768
- transpile(_source) {
1769
- throw new Error(`transpile not implemented for ${this.constructor.name}`);
1935
+ async mergeElementIntoManifest(filePath, elementsDir) {
1936
+ const relativePath = filePath.slice(elementsDir.length + 1);
1937
+ const parts = relativePath.split("/");
1938
+ if (parts.length !== 2)
1939
+ return;
1940
+ const [dirName, filename] = parts;
1941
+ const match = filename.match(/^(.+?)\.element\.(ts|js)$/);
1942
+ if (!match)
1943
+ return;
1944
+ const name = match[1];
1945
+ if (name !== dirName)
1946
+ return;
1947
+ if (!name.includes("-"))
1948
+ return;
1949
+ const response = await this.handle(ELEMENTS_MANIFEST_PATH);
1950
+ const entries = response.status === 404 ? [] : await response.json();
1951
+ const prefix = elementsDir.replace(/^\//, "");
1952
+ let entry = entries.find((e) => e.name === name);
1953
+ if (!entry) {
1954
+ entry = {
1955
+ name,
1956
+ modulePath: `${prefix}/${name}/${filename}`,
1957
+ tagName: name
1958
+ };
1959
+ entries.push(entry);
1960
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1961
+ } else {
1962
+ entry.modulePath = `${prefix}/${name}/${filename}`;
1963
+ }
1964
+ this.elementsManifestCache = null;
1965
+ await this.handle(ELEMENTS_MANIFEST_PATH, {
1966
+ method: "PUT",
1967
+ body: JSON.stringify(entries)
1968
+ });
1770
1969
  }
1771
- // ── Manifest resolution ─────────────────────────────────────────────
1772
1970
  routesManifestCache = null;
1773
1971
  widgetsManifestCache = null;
1774
1972
  elementsManifestCache = null;
1775
- /** Clear cached manifests so the next query triggers a fresh scan. */
1776
1973
  invalidateManifests() {
1777
1974
  this.routesManifestCache = null;
1778
1975
  this.widgetsManifestCache = null;
1779
1976
  this.elementsManifestCache = null;
1780
1977
  }
1781
- /**
1782
- * Resolve the routes manifest. Called when the concrete runtime returns
1783
- * 404 for ROUTES_MANIFEST_PATH. Scans `config.routesDir` (or default).
1784
- */
1785
1978
  async resolveRoutesManifest() {
1786
1979
  if (this.routesManifestCache)
1787
1980
  return this.routesManifestCache.clone();
@@ -1794,10 +1987,6 @@ var Runtime = class {
1794
1987
  this.routesManifestCache = Response.json(tree);
1795
1988
  return this.routesManifestCache.clone();
1796
1989
  }
1797
- /**
1798
- * Resolve the widgets manifest. Called when the concrete runtime returns
1799
- * 404 for WIDGETS_MANIFEST_PATH. Scans `config.widgetsDir` (or default).
1800
- */
1801
1990
  async resolveWidgetsManifest() {
1802
1991
  if (this.widgetsManifestCache)
1803
1992
  return this.widgetsManifestCache.clone();
@@ -1810,10 +1999,6 @@ var Runtime = class {
1810
1999
  this.widgetsManifestCache = Response.json(entries);
1811
2000
  return this.widgetsManifestCache.clone();
1812
2001
  }
1813
- /**
1814
- * Resolve the elements manifest. Called when the concrete runtime returns
1815
- * 404 for ELEMENTS_MANIFEST_PATH. Scans `config.elementsDir` (or default).
1816
- */
1817
2002
  async resolveElementsManifest() {
1818
2003
  if (this.elementsManifestCache)
1819
2004
  return this.elementsManifestCache.clone();
@@ -1826,8 +2011,7 @@ var Runtime = class {
1826
2011
  this.elementsManifestCache = Response.json(entries);
1827
2012
  return this.elementsManifestCache.clone();
1828
2013
  }
1829
- // ── Scanning ──────────────────────────────────────────────────────────
1830
- async *walkDirectory(dir) {
2014
+ async* walkDirectory(dir) {
1831
2015
  const trailingDir = dir.endsWith("/") ? dir : dir + "/";
1832
2016
  const response = await this.query(trailingDir);
1833
2017
  const entries = await response.json();
@@ -1840,10 +2024,6 @@ var Runtime = class {
1840
2024
  }
1841
2025
  }
1842
2026
  }
1843
- /**
1844
- * Scan a routes directory and build a RouteNode tree.
1845
- * The filesystem structure maps directly to the tree — no intermediate array.
1846
- */
1847
2027
  async scanRoutes(routesDir) {
1848
2028
  const root = {};
1849
2029
  const allFiles = [];
@@ -1858,7 +2038,9 @@ var Runtime = class {
1858
2038
  const match = filename.match(/^(.+?)\.(page|error|redirect)\.(ts|js|html|md|css)$/);
1859
2039
  if (!match)
1860
2040
  continue;
1861
- const [, name, kind, ext] = match;
2041
+ const name = match[1];
2042
+ const kind = match[2];
2043
+ const ext = match[3];
1862
2044
  let node = root;
1863
2045
  for (const dir of dirSegments) {
1864
2046
  if (dir.startsWith("[") && dir.endsWith("]")) {
@@ -1957,280 +2139,10 @@ var Runtime = class {
1957
2139
  entries.sort((a, b) => a.name.localeCompare(b.name));
1958
2140
  return entries;
1959
2141
  }
1960
- };
1961
-
1962
- // dist/server/emroute.server.js
1963
- function createModuleLoaders(tree, runtime) {
1964
- const paths = /* @__PURE__ */ new Set();
1965
- function walk(node) {
1966
- const modulePath = node.files?.ts ?? node.files?.js;
1967
- if (modulePath)
1968
- paths.add(modulePath);
1969
- if (node.redirect)
1970
- paths.add(node.redirect);
1971
- if (node.errorBoundary)
1972
- paths.add(node.errorBoundary);
1973
- if (node.children) {
1974
- for (const child of Object.values(node.children))
1975
- walk(child);
1976
- }
1977
- if (node.dynamic)
1978
- walk(node.dynamic.child);
1979
- if (node.wildcard)
1980
- walk(node.wildcard.child);
1981
- }
1982
- walk(tree);
1983
- const loaders = {};
1984
- for (const path of paths) {
1985
- loaders[path] = () => runtime.loadModule(path);
1986
- }
1987
- return loaders;
1988
- }
1989
- function extractWidgetExport(mod) {
1990
- for (const value of Object.values(mod)) {
1991
- if (!value)
1992
- continue;
1993
- if (typeof value === "object" && "getData" in value) {
1994
- return value;
1995
- }
1996
- if (typeof value === "function" && value.prototype?.getData) {
1997
- return new value();
1998
- }
1999
- }
2000
- return null;
2001
- }
2002
- async function importWidgets(entries, runtime, manual) {
2003
- const registry = new WidgetRegistry();
2004
- const widgetFiles = {};
2005
- for (const entry of entries) {
2006
- try {
2007
- const runtimePath = entry.modulePath.startsWith("/") ? entry.modulePath : `/${entry.modulePath}`;
2008
- const mod = await runtime.loadModule(runtimePath);
2009
- const instance = extractWidgetExport(mod);
2010
- if (!instance)
2011
- continue;
2012
- registry.add(instance);
2013
- const inlined = mod.__files;
2014
- if (inlined && typeof inlined === "object") {
2015
- widgetFiles[entry.name] = inlined;
2016
- } else if (entry.files) {
2017
- widgetFiles[entry.name] = entry.files;
2018
- }
2019
- } catch (e) {
2020
- console.error(`[emroute] Failed to load widget ${entry.modulePath}:`, e);
2021
- if (entry.files)
2022
- widgetFiles[entry.name] = entry.files;
2023
- }
2024
- }
2025
- if (manual) {
2026
- for (const widget of manual) {
2027
- registry.add(widget);
2028
- }
2029
- }
2030
- return { registry, widgetFiles };
2031
- }
2032
- function buildHtmlShell(title, htmlBase) {
2033
- const baseTag = htmlBase ? `
2034
- <base href="${escapeHtml(htmlBase)}/">` : "";
2035
- return `<!DOCTYPE html>
2036
- <html>
2037
- <head>${baseTag}
2038
- <meta charset="utf-8">
2039
- <meta name="viewport" content="width=device-width, initial-scale=1">
2040
- <title>${escapeHtml(title)}</title>
2041
- <style>@view-transition { navigation: auto; } router-slot { display: contents; }</style>
2042
- </head>
2043
- <body>
2044
- <router-slot></router-slot>
2045
- </body>
2046
- </html>`;
2047
- }
2048
- function injectSsrContent(html, content, title, ssrRoute) {
2049
- const slotPattern = /<router-slot\b[^>]*>.*?<\/router-slot>/s;
2050
- if (!slotPattern.test(html))
2051
- return html;
2052
- const ssrAttr = ssrRoute ? ` data-ssr-route="${ssrRoute}"` : "";
2053
- html = html.replace(slotPattern, `<router-slot${ssrAttr}>${content}</router-slot>`);
2054
- if (title) {
2055
- html = html.replace(/<title>[^<]*<\/title>/, `<title>${escapeHtml(title)}</title>`);
2056
- }
2057
- return html;
2058
- }
2059
- async function resolveShell(runtime, title, htmlBase) {
2060
- const response = await runtime.query("/index.html");
2061
- if (response.status !== 404)
2062
- return await response.text();
2063
- return buildHtmlShell(title, htmlBase);
2064
- }
2065
- async function createEmrouteServer(config, runtime) {
2066
- const { spa = "root" } = config;
2067
- const { html: htmlBase, md: mdBase, app: appBase } = config.basePath ?? DEFAULT_BASE_PATH;
2068
- let routeTree;
2069
- if (config.routeTree) {
2070
- routeTree = config.routeTree;
2071
- } else {
2072
- const manifestResponse = await runtime.query(ROUTES_MANIFEST_PATH);
2073
- if (manifestResponse.status === 404) {
2074
- throw new Error(`[emroute] ${ROUTES_MANIFEST_PATH} not found in runtime. Provide routeTree in config or ensure the runtime produces it.`);
2075
- }
2076
- routeTree = await manifestResponse.json();
2077
- }
2078
- const moduleLoaders = config.moduleLoaders ?? createModuleLoaders(routeTree, runtime);
2079
- const resolver = new RouteTrie(routeTree);
2080
- let widgets = config.widgets;
2081
- let widgetFiles = {};
2082
- let discoveredWidgetEntries = [];
2083
- const widgetsResponse = await runtime.query(WIDGETS_MANIFEST_PATH);
2084
- if (widgetsResponse.status !== 404) {
2085
- discoveredWidgetEntries = await widgetsResponse.json();
2086
- if (config.widgets) {
2087
- widgets = config.widgets;
2088
- for (const entry of discoveredWidgetEntries) {
2089
- if (entry.files)
2090
- widgetFiles[entry.name] = entry.files;
2091
- }
2092
- } else {
2093
- const imported = await importWidgets(discoveredWidgetEntries, runtime);
2094
- widgets = imported.registry;
2095
- widgetFiles = imported.widgetFiles;
2096
- }
2097
- }
2098
- let discoveredElementEntries = [];
2099
- const elementsResponse = await runtime.query(ELEMENTS_MANIFEST_PATH);
2100
- if (elementsResponse.status !== 404) {
2101
- discoveredElementEntries = await elementsResponse.json();
2102
- }
2103
- let ssrHtmlRouter = null;
2104
- let ssrMdRouter = null;
2105
- function buildSsrRouters() {
2106
- if (spa === "only") {
2107
- ssrHtmlRouter = null;
2108
- ssrMdRouter = null;
2109
- return;
2110
- }
2111
- ssrHtmlRouter = new SsrHtmlRouter(resolver, {
2112
- fileReader: (path) => runtime.query(path, { as: "text" }),
2113
- moduleLoaders,
2114
- ...config.markdownRenderer ? { markdownRenderer: config.markdownRenderer } : {},
2115
- ...config.extendContext ? { extendContext: config.extendContext } : {},
2116
- ...widgets ? { widgets } : {},
2117
- widgetFiles
2118
- });
2119
- ssrMdRouter = new SsrMdRouter(resolver, {
2120
- fileReader: (path) => runtime.query(path, { as: "text" }),
2121
- moduleLoaders,
2122
- ...config.extendContext ? { extendContext: config.extendContext } : {},
2123
- ...widgets ? { widgets } : {},
2124
- widgetFiles
2125
- });
2126
- }
2127
- buildSsrRouters();
2128
- const title = config.title ?? "emroute";
2129
- const shellBase = spa === "root" || spa === "only" ? appBase : htmlBase;
2130
- let shell = await resolveShell(runtime, title, shellBase);
2131
- if ((await runtime.query("/main.css")).status !== 404) {
2132
- shell = shell.replace("</head>", ' <link rel="stylesheet" href="/main.css">\n</head>');
2133
- }
2134
- async function handleRequest(req) {
2135
- const url = new URL(req.url);
2136
- const pathname = url.pathname;
2137
- const mdPrefix = mdBase + "/";
2138
- const htmlPrefix = htmlBase + "/";
2139
- const appPrefix = appBase + "/";
2140
- if (ssrMdRouter && (pathname.startsWith(mdPrefix) || pathname === mdBase)) {
2141
- const routePath = pathname === mdBase ? "/" : pathname.slice(mdBase.length);
2142
- if (routePath.length > 1 && routePath.endsWith("/")) {
2143
- const canonical = mdBase + routePath.slice(0, -1) + (url.search || "");
2144
- return Response.redirect(new URL(canonical, url.origin), 301);
2145
- }
2146
- try {
2147
- const routeUrl = new URL(routePath + url.search, url.origin);
2148
- const { content, status, redirect } = await ssrMdRouter.render(routeUrl, req.signal);
2149
- if (redirect) {
2150
- const target = redirect.startsWith("/") ? mdBase + redirect : redirect;
2151
- return Response.redirect(new URL(target, url.origin), status);
2152
- }
2153
- return new Response(rewriteMdLinks(content, mdBase, [mdBase, htmlBase]), {
2154
- status,
2155
- headers: { "Content-Type": "text/markdown; charset=utf-8; variant=CommonMark" }
2156
- });
2157
- } catch (e) {
2158
- console.error(`[emroute] Error rendering ${pathname}:`, e);
2159
- return new Response("Internal Server Error", { status: 500 });
2160
- }
2161
- }
2162
- if (ssrHtmlRouter && (pathname.startsWith(htmlPrefix) || pathname === htmlBase)) {
2163
- const routePath = pathname === htmlBase ? "/" : pathname.slice(htmlBase.length);
2164
- if (routePath.length > 1 && routePath.endsWith("/")) {
2165
- const canonical = htmlBase + routePath.slice(0, -1) + (url.search || "");
2166
- return Response.redirect(new URL(canonical, url.origin), 301);
2167
- }
2168
- try {
2169
- const routeUrl = new URL(routePath + url.search, url.origin);
2170
- const result = await ssrHtmlRouter.render(routeUrl, req.signal);
2171
- if (result.redirect) {
2172
- const target = result.redirect.startsWith("/") ? htmlBase + result.redirect : result.redirect;
2173
- return Response.redirect(new URL(target, url.origin), result.status);
2174
- }
2175
- const ssrTitle = result.title ?? title;
2176
- const html = injectSsrContent(shell, result.content, ssrTitle, pathname);
2177
- return new Response(html, {
2178
- status: result.status,
2179
- headers: { "Content-Type": "text/html; charset=utf-8" }
2180
- });
2181
- } catch (e) {
2182
- console.error(`[emroute] Error rendering ${pathname}:`, e);
2183
- return new Response("Internal Server Error", { status: 500 });
2184
- }
2185
- }
2186
- if (pathname.startsWith(appPrefix) || pathname === appBase) {
2187
- return new Response(shell, {
2188
- status: 200,
2189
- headers: { "Content-Type": "text/html; charset=utf-8" }
2190
- });
2191
- }
2192
- if (pathname.startsWith(htmlPrefix) || pathname === htmlBase || pathname.startsWith(mdPrefix) || pathname === mdBase) {
2193
- return new Response(shell, {
2194
- status: 200,
2195
- headers: { "Content-Type": "text/html; charset=utf-8" }
2196
- });
2197
- }
2198
- const lastSegment = pathname.split("/").pop() ?? "";
2199
- if (lastSegment.includes(".")) {
2200
- const fileResponse = await runtime.handle(pathname);
2201
- if (fileResponse.status === 200)
2202
- return fileResponse;
2203
- return null;
2204
- }
2205
- const base = spa === "root" || spa === "only" ? appBase : htmlBase;
2206
- const bare = pathname === "/" ? "" : pathname.slice(1).replace(/\/$/, "");
2207
- return Response.redirect(new URL(`${base}/${bare}`, url.origin), 302);
2208
- }
2209
- return {
2210
- handleRequest,
2211
- get htmlRouter() {
2212
- return ssrHtmlRouter;
2213
- },
2214
- get mdRouter() {
2215
- return ssrMdRouter;
2216
- },
2217
- get routeTree() {
2218
- return routeTree;
2219
- },
2220
- get widgetEntries() {
2221
- return discoveredWidgetEntries;
2222
- },
2223
- get elementEntries() {
2224
- return discoveredElementEntries;
2225
- },
2226
- get shell() {
2227
- return shell;
2228
- }
2229
- };
2230
2142
  }
2231
2143
 
2232
2144
  // dist/runtime/fetch.runtime.js
2233
- var __rewriteRelativeImportExtension2 = function(path, preserveJsx) {
2145
+ var __rewriteRelativeImportExtension = function(path, preserveJsx) {
2234
2146
  if (typeof path === "string" && /^\.\.?\//.test(path)) {
2235
2147
  return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function(m, tsx, d, ext, cm) {
2236
2148
  return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : d + ext + "." + cm.toLowerCase() + "js";
@@ -2238,11 +2150,9 @@ var __rewriteRelativeImportExtension2 = function(path, preserveJsx) {
2238
2150
  }
2239
2151
  return path;
2240
2152
  };
2241
- var FetchRuntime = class extends Runtime {
2153
+
2154
+ class FetchRuntime extends Runtime {
2242
2155
  origin;
2243
- /**
2244
- * @param origin — Server origin, e.g. `'http://localhost:4100'` or `location.origin`.
2245
- */
2246
2156
  constructor(origin, config = {}) {
2247
2157
  super(config);
2248
2158
  this.origin = origin.endsWith("/") ? origin.slice(0, -1) : origin;
@@ -2262,7 +2172,7 @@ var FetchRuntime = class extends Runtime {
2262
2172
  const response = await fetch(url);
2263
2173
  const js = await response.text();
2264
2174
  const blob = new Blob([js], { type: "application/javascript" });
2265
- return import(__rewriteRelativeImportExtension2(URL.createObjectURL(blob)));
2175
+ return import(__rewriteRelativeImportExtension(URL.createObjectURL(blob)));
2266
2176
  }
2267
2177
  toUrl(resource) {
2268
2178
  if (typeof resource === "string")
@@ -2271,10 +2181,10 @@ var FetchRuntime = class extends Runtime {
2271
2181
  return `${this.origin}${resource.pathname}${resource.search}`;
2272
2182
  return `${this.origin}${new URL(resource.url).pathname}`;
2273
2183
  }
2274
- };
2184
+ }
2275
2185
 
2276
- // dist/src/renderer/spa/thin-client.js
2277
- var EmrouteApp = class {
2186
+ // dist/src/renderer/spa/emroute.app.js
2187
+ class EmrouteApp {
2278
2188
  server;
2279
2189
  appBase;
2280
2190
  slot = null;
@@ -2294,7 +2204,7 @@ var EmrouteApp = class {
2294
2204
  console.warn("[EmrouteApp] Navigation API not available");
2295
2205
  return;
2296
2206
  }
2297
- this.abortController = new AbortController();
2207
+ this.abortController = new AbortController;
2298
2208
  const { signal } = this.abortController;
2299
2209
  navigation.addEventListener("navigate", (event) => {
2300
2210
  if (!event.canIntercept)
@@ -2350,12 +2260,12 @@ var EmrouteApp = class {
2350
2260
  return pathname;
2351
2261
  }
2352
2262
  async handleNavigation(url, signal) {
2353
- if (!this.slot || !this.server.htmlRouter)
2263
+ if (!this.slot || !this.server.htmlRenderer)
2354
2264
  return;
2355
2265
  const routePath = this.stripAppBase(url.pathname);
2356
2266
  const routeUrl = new URL(routePath + url.search, url.origin);
2357
2267
  try {
2358
- const { content, title, redirect } = await this.server.htmlRouter.render(routeUrl, signal);
2268
+ const { content, title, redirect } = await this.server.htmlRenderer.render(routeUrl, signal);
2359
2269
  if (signal.aborted)
2360
2270
  return;
2361
2271
  if (redirect) {
@@ -2385,7 +2295,7 @@ var EmrouteApp = class {
2385
2295
  }
2386
2296
  }
2387
2297
  }
2388
- };
2298
+ }
2389
2299
  async function createEmrouteApp(server, options) {
2390
2300
  const g = globalThis;
2391
2301
  if (g.__emroute_app) {
@@ -2410,9 +2320,9 @@ async function bootEmrouteApp(options) {
2410
2320
  const elementsResponse = await runtime.handle(ELEMENTS_MANIFEST_PATH);
2411
2321
  const elementEntries = elementsResponse.ok ? await elementsResponse.json() : [];
2412
2322
  const moduleLoaders = buildLazyLoaders(routeTree, widgetEntries, elementEntries, runtime);
2413
- const widgets = new WidgetRegistry();
2323
+ const widgets = new WidgetRegistry;
2414
2324
  for (const entry of widgetEntries) {
2415
- ComponentElement.registerLazy(entry.name, entry.files, moduleLoaders[entry.modulePath]);
2325
+ ComponentElement.registerLazy(entry.name, moduleLoaders[entry.modulePath]);
2416
2326
  }
2417
2327
  for (const entry of elementEntries) {
2418
2328
  const loader = moduleLoaders[entry.modulePath];
@@ -2428,7 +2338,7 @@ async function bootEmrouteApp(options) {
2428
2338
  }
2429
2339
  }
2430
2340
  const mdRenderer = MarkdownElement.getConfiguredRenderer();
2431
- const server = await createEmrouteServer({
2341
+ const server = await Emroute.create({
2432
2342
  routeTree,
2433
2343
  widgets,
2434
2344
  moduleLoaders,
@@ -2437,7 +2347,7 @@ async function bootEmrouteApp(options) {
2437
2347
  return createEmrouteApp(server, options);
2438
2348
  }
2439
2349
  function buildLazyLoaders(tree, widgetEntries, elementEntries, runtime) {
2440
- const paths = /* @__PURE__ */ new Set();
2350
+ const paths = new Set;
2441
2351
  function walk(node) {
2442
2352
  const modulePath = node.files?.ts ?? node.files?.js;
2443
2353
  if (modulePath)
@@ -2467,24 +2377,8 @@ function buildLazyLoaders(tree, widgetEntries, elementEntries, runtime) {
2467
2377
  }
2468
2378
  return loaders;
2469
2379
  }
2470
-
2471
- // dist/src/component/widget.component.js
2472
- var WidgetComponent = class extends Component {
2473
- /**
2474
- * Render widget as HTML.
2475
- *
2476
- * Fallback chain:
2477
- * 1. html file content from context
2478
- * 2. md file content wrapped in `<mark-down>`
2479
- * 3. base Component default (markdown→HTML conversion)
2480
- *
2481
- * @example
2482
- * ```ts
2483
- * override renderHTML({ data, params }: this['RenderArgs']) {
2484
- * return `<span>${params.coin}: $${data?.price}</span>`;
2485
- * }
2486
- * ```
2487
- */
2380
+ // dist/core/component/widget.component.js
2381
+ class WidgetComponent extends Component {
2488
2382
  renderHTML(args) {
2489
2383
  const files = args.context.files;
2490
2384
  const style = files?.css ? `<style>${scopeWidgetCss(files.css, this.name)}</style>
@@ -2500,20 +2394,6 @@ var WidgetComponent = class extends Component {
2500
2394
  }
2501
2395
  return super.renderHTML(args);
2502
2396
  }
2503
- /**
2504
- * Render widget as Markdown.
2505
- *
2506
- * Fallback chain:
2507
- * 1. md file content from context
2508
- * 2. empty string
2509
- *
2510
- * @example
2511
- * ```ts
2512
- * override renderMarkdown({ data, params }: this['RenderArgs']) {
2513
- * return `**${params.coin}**: $${data?.price}`;
2514
- * }
2515
- * ```
2516
- */
2517
2397
  renderMarkdown(args) {
2518
2398
  const files = args.context.files;
2519
2399
  if (files?.md) {
@@ -2521,12 +2401,9 @@ var WidgetComponent = class extends Component {
2521
2401
  }
2522
2402
  return "";
2523
2403
  }
2524
- };
2525
-
2404
+ }
2526
2405
  // dist/src/overlay/overlay.css.js
2527
- var overlayCSS = (
2528
- /* css */
2529
- `
2406
+ var overlayCSS = `
2530
2407
  :root {
2531
2408
  --overlay-backdrop: oklch(0% 0 0 / 0.5);
2532
2409
  --overlay-surface: oklch(100% 0 0);
@@ -2687,8 +2564,7 @@ dialog[data-overlay-modal][data-dismissing]::backdrop {
2687
2564
  opacity: 0;
2688
2565
  scale: 0.95;
2689
2566
  }
2690
- `
2691
- );
2567
+ `;
2692
2568
 
2693
2569
  // dist/src/overlay/overlay.service.js
2694
2570
  var ANIMATION_SAFETY_TIMEOUT = 300;
@@ -2730,7 +2606,7 @@ function createOverlayService() {
2730
2606
  document.body.appendChild(dialog);
2731
2607
  dialog.addEventListener("click", (e) => {
2732
2608
  if (e.target === dialog) {
2733
- closeModal(void 0);
2609
+ closeModal(undefined);
2734
2610
  }
2735
2611
  });
2736
2612
  return dialog;
@@ -2761,12 +2637,12 @@ function createOverlayService() {
2761
2637
  if (d.open) {
2762
2638
  d.close();
2763
2639
  if (modalResolve) {
2764
- modalResolve(void 0);
2640
+ modalResolve(undefined);
2765
2641
  modalResolve = null;
2766
2642
  }
2767
2643
  if (modalOnClose) {
2768
2644
  modalOnClose();
2769
- modalOnClose = void 0;
2645
+ modalOnClose = undefined;
2770
2646
  }
2771
2647
  }
2772
2648
  d.innerHTML = "";
@@ -2784,7 +2660,7 @@ function createOverlayService() {
2784
2660
  const onClose = modalOnClose;
2785
2661
  const dialogRef = dialog;
2786
2662
  modalResolve = null;
2787
- modalOnClose = void 0;
2663
+ modalOnClose = undefined;
2788
2664
  animateDismiss(dialogRef, () => {
2789
2665
  if (dialogRef && dialogRef.open) {
2790
2666
  dialogRef.close();
@@ -2830,8 +2706,7 @@ function createOverlayService() {
2830
2706
  cleanupPopoverAnchorObserver();
2831
2707
  try {
2832
2708
  el.hidePopover();
2833
- } catch {
2834
- }
2709
+ } catch {}
2835
2710
  el.removeAttribute("data-dismissing");
2836
2711
  el.innerHTML = "";
2837
2712
  options.render(el);
@@ -2870,8 +2745,7 @@ function createOverlayService() {
2870
2745
  return;
2871
2746
  try {
2872
2747
  popoverEl.hidePopover();
2873
- } catch {
2874
- }
2748
+ } catch {}
2875
2749
  popoverEl.removeAttribute("data-dismissing");
2876
2750
  }
2877
2751
  function cleanupPopoverAnchorObserver() {
@@ -2895,8 +2769,7 @@ function createOverlayService() {
2895
2769
  animateDismiss(popoverEl, () => {
2896
2770
  try {
2897
2771
  popoverEl.hidePopover();
2898
- } catch {
2899
- }
2772
+ } catch {}
2900
2773
  });
2901
2774
  }
2902
2775
  function dismissAll() {
@@ -2904,11 +2777,11 @@ function createOverlayService() {
2904
2777
  const resolve = modalResolve;
2905
2778
  const onClose = modalOnClose;
2906
2779
  modalResolve = null;
2907
- modalOnClose = void 0;
2780
+ modalOnClose = undefined;
2908
2781
  dialog.removeAttribute("data-dismissing");
2909
2782
  dialog.close();
2910
2783
  if (resolve)
2911
- resolve(void 0);
2784
+ resolve(undefined);
2912
2785
  if (onClose)
2913
2786
  onClose();
2914
2787
  }
@@ -2922,8 +2795,7 @@ function createOverlayService() {
2922
2795
  for (const el of document.querySelectorAll(":popover-open")) {
2923
2796
  el.hidePopover();
2924
2797
  }
2925
- } catch {
2926
- }
2798
+ } catch {}
2927
2799
  for (const el of document.querySelectorAll("dialog[open]")) {
2928
2800
  if (el !== dialog)
2929
2801
  el.close();
@@ -2938,9 +2810,8 @@ function createOverlayService() {
2938
2810
  dismissAll
2939
2811
  };
2940
2812
  }
2941
-
2942
2813
  // dist/src/widget/page-title.widget.js
2943
- var PageTitleWidget = class extends WidgetComponent {
2814
+ class PageTitleWidget extends WidgetComponent {
2944
2815
  name = "page-title";
2945
2816
  getData(args) {
2946
2817
  return Promise.resolve({ title: args.params.title });
@@ -2959,14 +2830,14 @@ var PageTitleWidget = class extends WidgetComponent {
2959
2830
  if (!params.title || typeof params.title !== "string") {
2960
2831
  return 'page-title widget requires a "title" string param';
2961
2832
  }
2962
- return void 0;
2833
+ return;
2963
2834
  }
2964
- };
2965
-
2835
+ }
2966
2836
  // dist/src/widget/breadcrumb.widget.js
2967
- var DEFAULT_HTML_SEPARATOR = " \u203A ";
2837
+ var DEFAULT_HTML_SEPARATOR = " ";
2968
2838
  var DEFAULT_MD_SEPARATOR = " > ";
2969
- var BreadcrumbWidget = class extends WidgetComponent {
2839
+
2840
+ class BreadcrumbWidget extends WidgetComponent {
2970
2841
  name = "breadcrumb";
2971
2842
  getData(args) {
2972
2843
  const pathname = args.context.pathname || "/";
@@ -3004,8 +2875,7 @@ var BreadcrumbWidget = class extends WidgetComponent {
3004
2875
  const sep = args.params.separator ?? DEFAULT_MD_SEPARATOR;
3005
2876
  return args.data.segments.map((seg, i, arr) => i === arr.length - 1 ? `**${seg.label}**` : `[${seg.label}](${seg.href})`).join(sep);
3006
2877
  }
3007
- };
3008
-
2878
+ }
3009
2879
  // dist/src/renderer/spa/mod.js
3010
2880
  if (globalThis.customElements) {
3011
2881
  if (!customElements.get("router-slot"))
@@ -3014,25 +2884,27 @@ if (globalThis.customElements) {
3014
2884
  customElements.define("mark-down", MarkdownElement);
3015
2885
  }
3016
2886
  export {
3017
- BreadcrumbWidget,
3018
- Component,
3019
- ComponentElement,
3020
- DEFAULT_BASE_PATH,
3021
- EmrouteApp,
3022
- FetchRuntime,
3023
- MarkdownElement,
3024
- PageComponent,
3025
- PageTitleWidget,
3026
- RouteTrie,
3027
- RouterSlot,
3028
- WidgetComponent,
3029
- WidgetRegistry,
3030
- bootEmrouteApp,
3031
- createEmrouteApp,
3032
- createEmrouteServer,
3033
- createOverlayService,
3034
- escapeHtml,
2887
+ setLogger,
3035
2888
  scopeWidgetCss,
3036
- setLogger
2889
+ escapeHtml,
2890
+ createOverlayService,
2891
+ createEmrouteApp,
2892
+ bootEmrouteApp,
2893
+ WidgetRegistry,
2894
+ WidgetComponent,
2895
+ RouterSlot,
2896
+ RouteTrie,
2897
+ PageTitleWidget,
2898
+ PageComponent,
2899
+ MarkdownElement,
2900
+ FetchRuntime,
2901
+ EmrouteApp,
2902
+ Emroute,
2903
+ DEFAULT_BASE_PATH,
2904
+ ComponentElement,
2905
+ Component,
2906
+ BreadcrumbWidget
3037
2907
  };
2908
+
2909
+ //# debugId=48A0CD62710AC7C064756E2164756E21
3038
2910
  //# sourceMappingURL=emroute.js.map