@emkodev/emroute 1.7.2 → 1.8.0-beta.1

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 (236) 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 +19 -45
  7. package/{src/renderer/ssr → core/renderer}/md.renderer.ts +21 -40
  8. package/{src/renderer/ssr → core/renderer}/ssr.renderer.ts +42 -53
  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 +346 -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 +0 -2
  22. package/{src/route → core/util}/route-tree.util.ts +0 -2
  23. package/{src → core}/util/widget-resolve.util.ts +15 -45
  24. package/{src → core}/widget/widget.parser.ts +0 -21
  25. package/core/widget/widget.registry.ts +24 -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 +12 -33
  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 +14 -31
  43. package/dist/core/renderer/md.renderer.js.map +1 -0
  44. package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.d.ts +11 -17
  45. package/dist/{src/renderer/ssr → core/renderer}/ssr.renderer.js +34 -36
  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 +261 -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 +32 -0
  92. package/dist/{src → core}/util/widget-resolve.util.js +11 -41
  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 +0 -21
  96. package/dist/core/widget/widget.parser.js.map +1 -0
  97. package/dist/core/widget/widget.registry.d.ts +13 -0
  98. package/dist/core/widget/widget.registry.js +19 -0
  99. package/dist/core/widget/widget.registry.js.map +1 -0
  100. package/dist/emroute.js +1137 -1151
  101. package/dist/emroute.js.map +36 -5
  102. package/dist/runtime/abstract.runtime.d.ts +50 -5
  103. package/dist/runtime/abstract.runtime.js +446 -6
  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 +18 -2
  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 +11 -1
  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 +18 -2
  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 +45 -36
  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 -341
  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 -68
  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 +4 -7
  132. package/dist/src/element/component.element.js +23 -22
  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 +4 -3
  136. package/dist/src/element/markdown.element.js.map +1 -1
  137. package/dist/src/index.d.ts +15 -13
  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 +34 -12
  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/route/route.core.js +21 -7
  151. package/dist/src/route/route.core.js.map +1 -1
  152. package/dist/src/util/html.util.d.ts +5 -22
  153. package/dist/src/util/html.util.js +8 -56
  154. package/dist/src/util/html.util.js.map +1 -1
  155. package/dist/src/widget/breadcrumb.widget.d.ts +2 -2
  156. package/dist/src/widget/breadcrumb.widget.js +2 -2
  157. package/dist/src/widget/breadcrumb.widget.js.map +1 -1
  158. package/dist/src/widget/page-title.widget.d.ts +1 -1
  159. package/dist/src/widget/page-title.widget.js +1 -1
  160. package/dist/src/widget/page-title.widget.js.map +1 -1
  161. package/package.json +8 -8
  162. package/runtime/abstract.runtime.ts +483 -8
  163. package/runtime/bun/fs/bun-fs.runtime.ts +17 -1
  164. package/runtime/bun/sqlite/bun-sqlite.runtime.ts +11 -0
  165. package/runtime/fetch.runtime.ts +3 -3
  166. package/runtime/sitemap.generator.ts +2 -2
  167. package/runtime/universal/fs/universal-fs.runtime.ts +17 -1
  168. package/server/build.util.ts +53 -47
  169. package/server/codegen.util.ts +1 -1
  170. package/server/emroute.server.ts +12 -412
  171. package/src/element/component.element.ts +24 -31
  172. package/src/element/markdown.element.ts +5 -4
  173. package/src/index.ts +22 -18
  174. package/src/renderer/spa/{thin-client.ts → emroute.app.ts} +46 -22
  175. package/src/renderer/spa/mod.ts +22 -22
  176. package/src/util/html.util.ts +16 -61
  177. package/src/widget/breadcrumb.widget.ts +3 -3
  178. package/src/widget/page-title.widget.ts +1 -1
  179. package/dist/src/component/abstract.component.d.ts +0 -199
  180. package/dist/src/component/abstract.component.js +0 -84
  181. package/dist/src/component/abstract.component.js.map +0 -1
  182. package/dist/src/component/page.component.d.ts +0 -74
  183. package/dist/src/component/page.component.js +0 -107
  184. package/dist/src/component/page.component.js.map +0 -1
  185. package/dist/src/component/widget.component.d.ts +0 -47
  186. package/dist/src/component/widget.component.js +0 -69
  187. package/dist/src/component/widget.component.js.map +0 -1
  188. package/dist/src/renderer/ssr/html.renderer.js.map +0 -1
  189. package/dist/src/renderer/ssr/md.renderer.js.map +0 -1
  190. package/dist/src/renderer/ssr/ssr.renderer.js.map +0 -1
  191. package/dist/src/route/route-tree.util.js.map +0 -1
  192. package/dist/src/route/route.matcher.d.ts +0 -86
  193. package/dist/src/route/route.matcher.js +0 -214
  194. package/dist/src/route/route.matcher.js.map +0 -1
  195. package/dist/src/route/route.resolver.js.map +0 -1
  196. package/dist/src/route/route.trie.d.ts +0 -38
  197. package/dist/src/route/route.trie.js +0 -206
  198. package/dist/src/route/route.trie.js.map +0 -1
  199. package/dist/src/type/logger.type.d.ts +0 -17
  200. package/dist/src/type/logger.type.js +0 -9
  201. package/dist/src/type/logger.type.js.map +0 -1
  202. package/dist/src/type/markdown.type.d.ts +0 -20
  203. package/dist/src/type/markdown.type.js +0 -2
  204. package/dist/src/type/markdown.type.js.map +0 -1
  205. package/dist/src/type/route-tree.type.js.map +0 -1
  206. package/dist/src/type/route.type.d.ts +0 -94
  207. package/dist/src/type/route.type.js +0 -8
  208. package/dist/src/type/route.type.js.map +0 -1
  209. package/dist/src/type/widget.type.d.ts +0 -55
  210. package/dist/src/type/widget.type.js +0 -10
  211. package/dist/src/type/widget.type.js.map +0 -1
  212. package/dist/src/util/logger.util.d.ts +0 -26
  213. package/dist/src/util/logger.util.js +0 -80
  214. package/dist/src/util/logger.util.js.map +0 -1
  215. package/dist/src/util/md.util.js.map +0 -1
  216. package/dist/src/util/widget-resolve.util.d.ts +0 -52
  217. package/dist/src/util/widget-resolve.util.js.map +0 -1
  218. package/dist/src/widget/widget.parser.js.map +0 -1
  219. package/dist/src/widget/widget.registry.d.ts +0 -23
  220. package/dist/src/widget/widget.registry.js +0 -42
  221. package/dist/src/widget/widget.registry.js.map +0 -1
  222. package/runtime/bun/esbuild-runtime-loader.plugin.ts +0 -112
  223. package/server/esbuild-manifest.plugin.ts +0 -209
  224. package/server/server-api.type.ts +0 -97
  225. package/src/component/abstract.component.ts +0 -231
  226. package/src/component/widget.component.ts +0 -85
  227. package/src/route/route.core.ts +0 -362
  228. package/src/route/route.trie.ts +0 -265
  229. package/src/type/logger.type.ts +0 -24
  230. package/src/type/markdown.type.ts +0 -21
  231. package/src/type/route-tree.type.ts +0 -51
  232. package/src/type/route.type.ts +0 -124
  233. package/src/type/widget.type.ts +0 -65
  234. package/src/util/logger.util.ts +0 -83
  235. package/src/widget/widget.registry.ts +0 -51
  236. /package/dist/{src/route → core/util}/route-tree.util.d.ts +0 -0
@@ -5,8 +5,9 @@
5
5
  * Calls getData() + renderHTML() on widgets and injects SSR hydration data.
6
6
  */
7
7
 
8
- import type { Component, ContextProvider } from '../component/abstract.component.ts';
9
- import { logger } from '../type/logger.type.ts';
8
+ import type { Component } from '../component/abstract.component.ts';
9
+ import type { ComponentContext, ContextProvider } from '../type/component.type.ts';
10
+ import { type Logger, defaultLogger } from '../type/logger.type.ts';
10
11
  import type { RouteInfo } from '../type/route.type.ts';
11
12
  import { LAZY_ATTR, SSR_ATTR } from './html.util.ts';
12
13
 
@@ -15,17 +16,6 @@ export const MAX_WIDGET_DEPTH = 10;
15
16
 
16
17
  /**
17
18
  * Recursively resolve widgets in content with depth limit.
18
- *
19
- * Generic utility used by both HTML and Markdown widget resolution.
20
- * Each depth level processes all widgets concurrently, then recurses
21
- * into each rendered result to resolve nested widgets.
22
- *
23
- * @param content - Content containing widgets
24
- * @param parse - Find widgets in content
25
- * @param resolve - Resolve a single widget to rendered output
26
- * @param replace - Replace widgets with resolved content
27
- * @param depth - Current recursion depth (internal)
28
- * @returns Content with all widgets recursively resolved
29
19
  */
30
20
  export async function resolveRecursively<T>(
31
21
  content: string,
@@ -33,6 +23,7 @@ export async function resolveRecursively<T>(
33
23
  resolve: (widget: T) => Promise<string>,
34
24
  replace: (content: string, replacements: Map<T, string>) => string,
35
25
  depth = 0,
26
+ logger: Logger = defaultLogger,
36
27
  ): Promise<string> {
37
28
  if (depth >= MAX_WIDGET_DEPTH) {
38
29
  logger.warn(
@@ -49,10 +40,7 @@ export async function resolveRecursively<T>(
49
40
  await Promise.all(
50
41
  widgets.map(async (widget) => {
51
42
  let rendered = await resolve(widget);
52
-
53
- // Recursively resolve any nested widgets in the rendered output
54
- rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1);
55
-
43
+ rendered = await resolveRecursively(rendered, parse, resolve, replace, depth + 1, logger);
56
44
  replacements.set(widget, rendered);
57
45
  }),
58
46
  );
@@ -61,18 +49,7 @@ export async function resolveRecursively<T>(
61
49
  }
62
50
 
63
51
  /**
64
- * Resolve <widget-*> tags in HTML by calling getData() + renderHTML()
65
- * via the widget registry. Injects rendered content and boolean ssr attribute.
66
- *
67
- * Supports nested widgets: if a widget's renderHTML() returns HTML containing
68
- * other <widget-*> tags, those will be resolved recursively up to MAX_WIDGET_DEPTH.
69
- *
70
- * Before: <widget-crypto-price coin="bitcoin"></widget-crypto-price>
71
- * After: <widget-crypto-price coin="bitcoin" ssr><template shadowrootmode="open"><span>$42,000</span></template></widget-crypto-price>
72
- *
73
- * When a widget has `exposeSsrData = true`, the getData() result is serialized
74
- * as JSON text in the light DOM (alongside the shadow root template):
75
- * After: <widget-crypto-price coin="bitcoin" ssr><template shadowrootmode="open"><span>$42,000</span></template>{"price":42000}</widget-crypto-price>
52
+ * Resolve <widget-*> tags in HTML by calling getData() + renderHTML().
76
53
  */
77
54
  export function resolveWidgetTags(
78
55
  html: string,
@@ -83,17 +60,14 @@ export function resolveWidgetTags(
83
60
  declaredFiles?: { html?: string; md?: string; css?: string },
84
61
  ) => Promise<{ html?: string; md?: string; css?: string }>,
85
62
  contextProvider?: ContextProvider,
63
+ logger: Logger = defaultLogger,
86
64
  ): Promise<string> {
87
65
  const tagPattern =
88
66
  /<widget-(?<name>[a-z][a-z0-9-]*)(?<attrs>\s[^>]*)?>(?<content>.*?)<\/widget-\k<name>>/gis;
89
67
 
90
- // Wrapping info stored per-match so replace() can apply it after recursion
91
68
  const wrappers = new Map<RegExpExecArray, { tagName: string; attrs: string; ssrData: string }>();
92
-
93
- // Matches standalone ssr attribute (boolean or with value), not as substring of another value
94
69
  const ssrAttrPattern = new RegExp(`\\s${SSR_ATTR}(?:\\s|=|$)`);
95
70
 
96
- // Parse: find unprocessed widget tags
97
71
  const parse = (content: string) => {
98
72
  const matches = content.matchAll(tagPattern).toArray();
99
73
  return matches.filter((match) => {
@@ -102,13 +76,12 @@ export function resolveWidgetTags(
102
76
  });
103
77
  };
104
78
 
105
- // Resolve: render a single widget's inner content (no outer tag wrapping — that's in replace)
106
79
  const resolve = async (match: RegExpExecArray): Promise<string> => {
107
80
  const widgetName = match.groups!.name;
108
81
  const attrsString = match.groups!.attrs?.trim() ?? '';
109
82
  const widget = registry.get(widgetName);
110
83
 
111
- if (!widget) return match[0]; // no widget found — leave original tag as-is
84
+ if (!widget) return match[0];
112
85
 
113
86
  const params = parseAttrsToParams(attrsString);
114
87
 
@@ -118,18 +91,17 @@ export function resolveWidgetTags(
118
91
  files = await loadFiles(widgetName, widget.files);
119
92
  }
120
93
 
121
- const baseContext = {
94
+ const baseContext: ComponentContext = {
122
95
  ...routeInfo,
123
96
  pathname: routeInfo.url.pathname,
124
97
  searchParams: routeInfo.url.searchParams,
125
- files,
98
+ ...(files ? { files } : {}),
126
99
  };
127
- const context = contextProvider ? contextProvider(baseContext) : baseContext;
100
+ const context: ComponentContext = contextProvider ? contextProvider(baseContext) : baseContext;
128
101
 
129
102
  const data = await widget.getData({ params, context });
130
103
  const rendered = widget.renderHTML({ data, params, context });
131
104
 
132
- // Store wrapping info — applied in replace() after recursion resolves nested widgets
133
105
  wrappers.set(match, {
134
106
  tagName: `widget-${widgetName}`,
135
107
  attrs: attrsString ? ` ${attrsString}` : '',
@@ -142,11 +114,10 @@ export function resolveWidgetTags(
142
114
  `[SSR HTML] Widget "${widgetName}" render failed`,
143
115
  e instanceof Error ? e : undefined,
144
116
  );
145
- return match[0]; // render failed — leave original tag as-is
117
+ return match[0];
146
118
  }
147
119
  };
148
120
 
149
- // Replace: wrap resolved content in outer tag + DSD template, then substitute by index
150
121
  const replace = (content: string, replacements: Map<RegExpExecArray, string>) => {
151
122
  let result = content;
152
123
  const entries = [...replacements.entries()].sort((a, b) => b[0].index! - a[0].index!);
@@ -157,16 +128,16 @@ export function resolveWidgetTags(
157
128
  const lightDomData = wrap?.ssrData ? wrap.ssrData : '';
158
129
  const replacement = wrap
159
130
  ? `<${wrap.tagName}${wrap.attrs} ${SSR_ATTR}><template shadowrootmode="open">${innerHtml}</template>${lightDomData}</${wrap.tagName}>`
160
- : innerHtml; // no wrapper = unresolved widget, innerHtml is the original tag
131
+ : innerHtml;
161
132
  result = result.slice(0, start) + replacement + result.slice(end);
162
133
  }
163
134
  return result;
164
135
  };
165
136
 
166
- return resolveRecursively(html, parse, resolve, replace);
137
+ return resolveRecursively(html, parse, resolve, replace, 0, logger);
167
138
  }
168
139
 
169
- /** Parse HTML attribute string into params object (kebab→camelCase, JSON.parse with string fallback). */
140
+ /** Parse HTML attribute string into params object. */
170
141
  export function parseAttrsToParams(attrsString: string): Record<string, unknown> {
171
142
  const params: Record<string, unknown> = {};
172
143
  if (!attrsString) return params;
@@ -196,7 +167,6 @@ export function parseAttrsToParams(attrsString: string): Record<string, unknown>
196
167
  return params;
197
168
  }
198
169
 
199
- /** Escape a value for use in a single-quoted HTML attribute. */
200
170
  function escapeAttr(value: string): string {
201
171
  return value.replaceAll('&', '&amp;').replaceAll("'", '&#39;');
202
172
  }
@@ -11,18 +11,8 @@
11
11
 
12
12
  import type { ParsedWidgetBlock } from '../type/widget.type.ts';
13
13
 
14
- /**
15
- * Pattern to match widget fenced code blocks.
16
- * Captures: widget name, params content
17
- */
18
14
  const WIDGET_PATTERN = /```widget:(?<name>[a-z][a-z0-9-]*)\n(?<params>.*?)```/gs;
19
15
 
20
- /**
21
- * Parse all widget blocks from markdown content.
22
- *
23
- * @param markdown - Markdown content to parse
24
- * @returns Array of parsed widget blocks with positions
25
- */
26
16
  export function parseWidgetBlocks(markdown: string): ParsedWidgetBlock[] {
27
17
  const blocks: ParsedWidgetBlock[] = [];
28
18
 
@@ -40,7 +30,6 @@ export function parseWidgetBlocks(markdown: string): ParsedWidgetBlock[] {
40
30
  endIndex: startIndex + fullMatch.length,
41
31
  };
42
32
 
43
- // Parse JSON params if present
44
33
  if (paramsJson) {
45
34
  try {
46
35
  const parsed = JSON.parse(paramsJson);
@@ -53,7 +42,6 @@ export function parseWidgetBlocks(markdown: string): ParsedWidgetBlock[] {
53
42
  block.parseError = `Invalid JSON: ${e instanceof Error ? e.message : String(e)}`;
54
43
  }
55
44
  } else {
56
- // Empty params is valid - use empty object
57
45
  block.params = {};
58
46
  }
59
47
 
@@ -63,19 +51,10 @@ export function parseWidgetBlocks(markdown: string): ParsedWidgetBlock[] {
63
51
  return blocks;
64
52
  }
65
53
 
66
- /**
67
- * Replace widget blocks in markdown with rendered content.
68
- *
69
- * @param markdown - Original markdown content
70
- * @param replacements - Map of parsed blocks to replacement strings
71
- * @returns Markdown with widget blocks replaced
72
- */
73
54
  export function replaceWidgetBlocks(
74
55
  markdown: string,
75
56
  replacements: Map<ParsedWidgetBlock, string>,
76
57
  ): string {
77
- // Sort blocks by position descending to replace from end first
78
- // This preserves indices during replacement
79
58
  const sortedBlocks = [...replacements.entries()].sort(
80
59
  ([a], [b]) => b.startIndex - a.startIndex,
81
60
  );
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Widget Registry
3
+ *
4
+ * Name → Component lookup. Used by all renderers.
5
+ * Pages are NOT in this registry — they live in the routes manifest.
6
+ */
7
+
8
+ import type { WidgetComponent } from '../component/widget.component.ts';
9
+
10
+ export class WidgetRegistry {
11
+ private widgets = new Map<string, WidgetComponent>();
12
+
13
+ add(widget: WidgetComponent): void {
14
+ this.widgets.set(widget.name, widget);
15
+ }
16
+
17
+ get(name: string): WidgetComponent | undefined {
18
+ return this.widgets.get(name);
19
+ }
20
+
21
+ [Symbol.iterator](): IterableIterator<WidgetComponent> {
22
+ return this.widgets.values();
23
+ }
24
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Component Base Class
3
+ *
4
+ * Everything is a Component: pages and widgets.
5
+ * Components render differently based on context:
6
+ * - /md/* → Markdown (LLMs, text clients)
7
+ * - /html/* → Pre-rendered HTML (SSR)
8
+ * - SPA → Hydrated custom elements
9
+ */
10
+ import type { ComponentContext } from '../type/component.type.ts';
11
+ export declare abstract class Component<TParams = unknown, TData = unknown, TContext extends ComponentContext = ComponentContext> {
12
+ readonly DataArgs: {
13
+ params: TParams;
14
+ signal?: AbortSignal;
15
+ context: TContext;
16
+ };
17
+ readonly RenderArgs: {
18
+ data: TData | null;
19
+ params: TParams;
20
+ context: TContext;
21
+ };
22
+ abstract readonly name: string;
23
+ /** Host element reference, set by ComponentElement in the browser. */
24
+ element?: HTMLElement | undefined;
25
+ /** Associated file paths for pre-loaded content (html, md, css). */
26
+ readonly files?: {
27
+ html?: string;
28
+ md?: string;
29
+ css?: string;
30
+ };
31
+ /**
32
+ * When true, SSR serializes the getData() result into the element's
33
+ * light DOM so the client can access it immediately in hydrate()
34
+ * without re-fetching.
35
+ */
36
+ readonly exposeSsrData?: boolean;
37
+ abstract getData(args: this['DataArgs']): Promise<TData | null>;
38
+ abstract renderMarkdown(args: this['RenderArgs']): string;
39
+ renderHTML(args: this['RenderArgs']): string;
40
+ hydrate?(args: this['RenderArgs']): void;
41
+ destroy?(): void;
42
+ validateParams?(params: TParams): string | undefined;
43
+ renderError(args: {
44
+ error: unknown;
45
+ params: TParams;
46
+ }): string;
47
+ renderMarkdownError(error: unknown): string;
48
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Component Base Class
3
+ *
4
+ * Everything is a Component: pages and widgets.
5
+ * Components render differently based on context:
6
+ * - /md/* → Markdown (LLMs, text clients)
7
+ * - /html/* → Pre-rendered HTML (SSR)
8
+ * - SPA → Hydrated custom elements
9
+ */
10
+ import { escapeHtml } from "../util/html.util.js";
11
+ export class Component {
12
+ /** Host element reference, set by ComponentElement in the browser. */
13
+ element;
14
+ /** Associated file paths for pre-loaded content (html, md, css). */
15
+ files;
16
+ /**
17
+ * When true, SSR serializes the getData() result into the element's
18
+ * light DOM so the client can access it immediately in hydrate()
19
+ * without re-fetching.
20
+ */
21
+ exposeSsrData;
22
+ renderHTML(args) {
23
+ if (args.data === null) {
24
+ return `<div data-component="${this.name}">Loading...</div>`;
25
+ }
26
+ const markdown = this.renderMarkdown({
27
+ data: args.data,
28
+ params: args.params,
29
+ context: args.context,
30
+ });
31
+ return `<div data-component="${this.name}" data-markdown>${escapeHtml(markdown)}</div>`;
32
+ }
33
+ renderError(args) {
34
+ const msg = args.error instanceof Error ? args.error.message : String(args.error);
35
+ return `<div data-component="${this.name}">Error: ${escapeHtml(msg)}</div>`;
36
+ }
37
+ renderMarkdownError(error) {
38
+ const msg = error instanceof Error ? error.message : String(error);
39
+ return `> **Error** (\`${this.name}\`): ${msg}`;
40
+ }
41
+ }
42
+ //# sourceMappingURL=abstract.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abstract.component.js","sourceRoot":"","sources":["../../../core/component/abstract.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,MAAM,OAAgB,SAAS;IAmB7B,sEAAsE;IACtE,OAAO,CAA2B;IAElC,oEAAoE;IAC3D,KAAK,CAAgD;IAE9D;;;;OAIG;IACM,aAAa,CAAW;IAKjC,UAAU,CAAC,IAAwB;QACjC,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,wBAAwB,IAAI,CAAC,IAAI,oBAAoB,CAAC;QAC/D,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC;YACnC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,OAAO,EAAE,IAAI,CAAC,OAAO;SACtB,CAAC,CAAC;QACH,OAAO,wBAAwB,IAAI,CAAC,IAAI,mBAAmB,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC1F,CAAC;IAMD,WAAW,CAAC,IAAyC;QACnD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClF,OAAO,wBAAwB,IAAI,CAAC,IAAI,YAAY,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;IAC9E,CAAC;IAED,mBAAmB,CAAC,KAAc;QAChC,MAAM,GAAG,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACnE,OAAO,kBAAkB,IAAI,CAAC,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Page Component
3
+ *
4
+ * Params come from URL, context carries file content.
5
+ *
6
+ * Default implementations follow the fallback table:
7
+ * - renderHTML: html file → md via <mark-down> → <router-slot /> (non-leaf only)
8
+ * - renderMarkdown: md file → ```router-slot\n``` (non-leaf only)
9
+ * - getData: no-op (returns null)
10
+ */
11
+ import { Component } from './abstract.component.ts';
12
+ import type { ComponentContext } from '../type/component.type.ts';
13
+ export declare class PageComponent<TParams extends Record<string, string> = Record<string, string>, TData = unknown, TContext extends ComponentContext = ComponentContext> extends Component<TParams, TData, TContext> {
14
+ readonly name: string;
15
+ readonly pattern?: string;
16
+ getData(_args: this['DataArgs']): Promise<TData | null>;
17
+ renderHTML(args: this['RenderArgs']): string;
18
+ renderMarkdown(args: this['RenderArgs']): string;
19
+ getTitle(_args: this['RenderArgs']): string | undefined;
20
+ }
21
+ /** Shared default instance used by renderers when no custom .page.ts exists. */
22
+ declare const _default: PageComponent<Record<string, string>, unknown, ComponentContext>;
23
+ export default _default;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Page Component
3
+ *
4
+ * Params come from URL, context carries file content.
5
+ *
6
+ * Default implementations follow the fallback table:
7
+ * - renderHTML: html file → md via <mark-down> → <router-slot /> (non-leaf only)
8
+ * - renderMarkdown: md file → ```router-slot\n``` (non-leaf only)
9
+ * - getData: no-op (returns null)
10
+ */
11
+ import { Component } from "./abstract.component.js";
12
+ import { escapeHtml } from "../util/html.util.js";
13
+ export class PageComponent extends Component {
14
+ name = 'page';
15
+ pattern;
16
+ getData(_args) {
17
+ return Promise.resolve(null);
18
+ }
19
+ renderHTML(args) {
20
+ const files = args.context.files;
21
+ const style = files?.css ? `<style>${files.css}</style>\n` : '';
22
+ if (files?.html) {
23
+ let html = style + files.html;
24
+ if (files.md && html.includes('<mark-down></mark-down>')) {
25
+ html = html.replace('<mark-down></mark-down>', `<mark-down>${escapeHtml(files.md)}</mark-down>`);
26
+ }
27
+ return html;
28
+ }
29
+ if (files?.md) {
30
+ const hasSlot = files.md.includes('```router-slot');
31
+ const slot = args.context.isLeaf || hasSlot ? '' : '\n<router-slot></router-slot>';
32
+ return `${style}<mark-down>${escapeHtml(files.md)}</mark-down>${slot}`;
33
+ }
34
+ return args.context.isLeaf ? '' : '<router-slot></router-slot>';
35
+ }
36
+ renderMarkdown(args) {
37
+ const files = args.context.files;
38
+ if (files?.md) {
39
+ return files.md;
40
+ }
41
+ return args.context.isLeaf ? '' : '```router-slot\n```';
42
+ }
43
+ getTitle(_args) {
44
+ return undefined;
45
+ }
46
+ }
47
+ /** Shared default instance used by renderers when no custom .page.ts exists. */
48
+ export default new PageComponent();
49
+ //# sourceMappingURL=page.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.component.js","sourceRoot":"","sources":["../../../core/component/page.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,MAAM,OAAO,aAIX,SAAQ,SAAmC;IACzB,IAAI,GAAW,MAAM,CAAC;IAC/B,OAAO,CAAU;IAEjB,OAAO,CACd,KAAuB;QAEvB,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAEQ,UAAU,CACjB,IAAwB;QAExB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QAEhE,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC;YAChB,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC;YAC9B,IAAI,KAAK,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CAAC;gBACzD,IAAI,GAAG,IAAI,CAAC,OAAO,CACjB,yBAAyB,EACzB,cAAc,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,cAAc,CACjD,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;YACd,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;YACpD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,+BAA+B,CAAC;YACnF,OAAO,GAAG,KAAK,cAAc,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,IAAI,EAAE,CAAC;QACzE,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,6BAA6B,CAAC;IAClE,CAAC;IAEQ,cAAc,CACrB,IAAwB;QAExB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEjC,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;YACd,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAC1D,CAAC;IAED,QAAQ,CACN,KAAyB;QAEzB,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAED,gFAAgF;AAChF,eAAe,IAAI,aAAa,EAAE,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Widget Component
3
+ *
4
+ * Embeddable unit within page content. Everything reusable that is not
5
+ * a page is a Widget. Widgets render across all contexts (HTML, Markdown, SPA)
6
+ * and are resolved by name via WidgetRegistry.
7
+ *
8
+ * Default rendering fallback chains (parallel to PageComponent):
9
+ * - renderHTML: html file → md file in <mark-down> → base Component default
10
+ * - renderMarkdown: md file → ''
11
+ */
12
+ import { Component } from './abstract.component.ts';
13
+ import type { ComponentContext } from '../type/component.type.ts';
14
+ export declare abstract class WidgetComponent<TParams = unknown, TData = unknown, TContext extends ComponentContext = ComponentContext> extends Component<TParams, TData, TContext> {
15
+ renderHTML(args: this['RenderArgs']): string;
16
+ renderMarkdown(args: this['RenderArgs']): string;
17
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Widget Component
3
+ *
4
+ * Embeddable unit within page content. Everything reusable that is not
5
+ * a page is a Widget. Widgets render across all contexts (HTML, Markdown, SPA)
6
+ * and are resolved by name via WidgetRegistry.
7
+ *
8
+ * Default rendering fallback chains (parallel to PageComponent):
9
+ * - renderHTML: html file → md file in <mark-down> → base Component default
10
+ * - renderMarkdown: md file → ''
11
+ */
12
+ import { Component } from "./abstract.component.js";
13
+ import { escapeHtml, scopeWidgetCss } from "../util/html.util.js";
14
+ export class WidgetComponent extends Component {
15
+ renderHTML(args) {
16
+ const files = args.context.files;
17
+ const style = files?.css ? `<style>${scopeWidgetCss(files.css, this.name)}</style>\n` : '';
18
+ if (files?.html) {
19
+ return style + files.html;
20
+ }
21
+ if (files?.md) {
22
+ return `${style}<mark-down>${escapeHtml(files.md)}</mark-down>`;
23
+ }
24
+ if (style) {
25
+ return style + super.renderHTML(args);
26
+ }
27
+ return super.renderHTML(args);
28
+ }
29
+ renderMarkdown(args) {
30
+ const files = args.context.files;
31
+ if (files?.md) {
32
+ return files.md;
33
+ }
34
+ return '';
35
+ }
36
+ }
37
+ //# sourceMappingURL=widget.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.component.js","sourceRoot":"","sources":["../../../core/component/widget.component.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAElE,MAAM,OAAgB,eAIpB,SAAQ,SAAmC;IAClC,UAAU,CACjB,IAAwB;QAExB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QACjC,MAAM,KAAK,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,UAAU,cAAc,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QAE3F,IAAI,KAAK,EAAE,IAAI,EAAE,CAAC;YAChB,OAAO,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC;QAC5B,CAAC;QAED,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;YACd,OAAO,GAAG,KAAK,cAAc,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,cAAc,CAAC;QAClE,CAAC;QAED,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;QAED,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAEQ,cAAc,CACrB,IAAwB;QAExB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC;QAEjC,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;YACd,OAAO,KAAK,CAAC,EAAE,CAAC;QAClB,CAAC;QAED,OAAO,EAAE,CAAC;IACZ,CAAC;CACF"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Pipeline
3
+ *
4
+ * Orchestration layer between Router, Runtime, and Component.
5
+ *
6
+ * Owns:
7
+ * - Route matching (reads manifest from Runtime, walks RouteNode tree)
8
+ * - Module loading (delegates to Runtime)
9
+ * - Companion file reading (delegates to Runtime)
10
+ * - ComponentContext building
11
+ * - Route hierarchy construction
12
+ *
13
+ * Does NOT own:
14
+ * - Rendering (that's Renderer)
15
+ * - HTTP routing / base paths (that's Server)
16
+ * - Storage I/O (that's Runtime)
17
+ * - Navigation events (that's the browser adapter)
18
+ */
19
+ import type { RouteConfig, MatchedRoute, RouteInfo } from '../type/route.type.ts';
20
+ import type { ComponentContext, ContextProvider, FileContents } from '../type/component.type.ts';
21
+ import type { Runtime } from '../runtime/abstract.runtime.ts';
22
+ import { type Logger } from '../type/logger.type.ts';
23
+ /** Default root route — renders a slot for child routes. */
24
+ export declare const DEFAULT_ROOT_ROUTE: RouteConfig;
25
+ /** Pipeline configuration. */
26
+ export interface PipelineOptions {
27
+ runtime: Runtime;
28
+ contextProvider?: ContextProvider;
29
+ /** Pre-bundled module loaders (browser boot passes these). */
30
+ moduleLoaders?: Record<string, () => Promise<unknown>>;
31
+ logger?: Logger;
32
+ }
33
+ export declare class Pipeline {
34
+ private readonly runtime;
35
+ readonly contextProvider: ContextProvider | undefined;
36
+ readonly logger: Logger;
37
+ private readonly moduleLoaders;
38
+ constructor(options: PipelineOptions);
39
+ private getResolver;
40
+ match(url: URL): Promise<MatchedRoute | undefined>;
41
+ findRoute(pattern: string): Promise<RouteConfig | undefined>;
42
+ findErrorBoundary(pathname: string): Promise<{
43
+ pattern: string;
44
+ modulePath: string;
45
+ } | undefined>;
46
+ getStatusPage(status: number): Promise<RouteConfig | undefined>;
47
+ getErrorHandler(): Promise<RouteConfig | undefined>;
48
+ buildRouteHierarchy(pattern: string): string[];
49
+ loadModule<T>(modulePath: string): Promise<T>;
50
+ /**
51
+ * Get inlined `__files` from a loaded module (merged module pattern).
52
+ */
53
+ getModuleFiles(mod: unknown): FileContents | undefined;
54
+ loadFiles(files: {
55
+ html?: string;
56
+ md?: string;
57
+ css?: string;
58
+ }): Promise<FileContents>;
59
+ toRouteInfo(matched: MatchedRoute, url: URL): RouteInfo;
60
+ buildContext(routeInfo: RouteInfo, route: RouteConfig, signal?: AbortSignal, isLeaf?: boolean, loadedModule?: unknown): Promise<ComponentContext>;
61
+ }