@buenojs/bueno 0.8.4 → 0.8.6

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 (234) hide show
  1. package/README.md +264 -17
  2. package/dist/cli/{index.js → bin.js} +413 -332
  3. package/dist/container/index.js +273 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/graphql/index.js +2156 -0
  8. package/dist/health/index.js +364 -0
  9. package/dist/i18n/index.js +345 -0
  10. package/dist/index.js +9694 -5047
  11. package/dist/jobs/index.js +819 -0
  12. package/dist/lock/index.js +367 -0
  13. package/dist/logger/index.js +281 -0
  14. package/dist/metrics/index.js +289 -0
  15. package/dist/middleware/index.js +77 -0
  16. package/dist/migrations/index.js +571 -0
  17. package/dist/modules/index.js +3411 -0
  18. package/dist/notification/index.js +484 -0
  19. package/dist/observability/index.js +331 -0
  20. package/dist/openapi/index.js +795 -0
  21. package/dist/orm/index.js +1356 -0
  22. package/dist/router/index.js +886 -0
  23. package/dist/rpc/index.js +691 -0
  24. package/dist/schema/index.js +400 -0
  25. package/dist/telemetry/index.js +595 -0
  26. package/dist/template/index.js +640 -0
  27. package/dist/templates/index.js +640 -0
  28. package/dist/testing/index.js +1111 -0
  29. package/dist/types/index.js +60 -0
  30. package/llms.txt +231 -0
  31. package/package.json +125 -27
  32. package/src/cache/index.ts +2 -1
  33. package/src/cli/ARCHITECTURE.md +3 -3
  34. package/src/cli/bin.ts +2 -2
  35. package/src/cli/commands/build.ts +183 -165
  36. package/src/cli/commands/dev.ts +96 -89
  37. package/src/cli/commands/generate.ts +142 -111
  38. package/src/cli/commands/help.ts +20 -16
  39. package/src/cli/commands/index.ts +3 -6
  40. package/src/cli/commands/migration.ts +124 -105
  41. package/src/cli/commands/new.ts +294 -232
  42. package/src/cli/commands/start.ts +81 -79
  43. package/src/cli/core/args.ts +68 -50
  44. package/src/cli/core/console.ts +89 -95
  45. package/src/cli/core/index.ts +4 -4
  46. package/src/cli/core/prompt.ts +65 -62
  47. package/src/cli/core/spinner.ts +23 -20
  48. package/src/cli/index.ts +46 -38
  49. package/src/cli/templates/database/index.ts +37 -18
  50. package/src/cli/templates/database/mysql.ts +3 -3
  51. package/src/cli/templates/database/none.ts +2 -2
  52. package/src/cli/templates/database/postgresql.ts +3 -3
  53. package/src/cli/templates/database/sqlite.ts +3 -3
  54. package/src/cli/templates/deploy.ts +29 -26
  55. package/src/cli/templates/docker.ts +41 -30
  56. package/src/cli/templates/frontend/index.ts +33 -15
  57. package/src/cli/templates/frontend/none.ts +2 -2
  58. package/src/cli/templates/frontend/react.ts +18 -18
  59. package/src/cli/templates/frontend/solid.ts +15 -15
  60. package/src/cli/templates/frontend/svelte.ts +17 -17
  61. package/src/cli/templates/frontend/vue.ts +15 -15
  62. package/src/cli/templates/generators/index.ts +29 -29
  63. package/src/cli/templates/generators/types.ts +21 -21
  64. package/src/cli/templates/index.ts +6 -6
  65. package/src/cli/templates/project/api.ts +37 -36
  66. package/src/cli/templates/project/default.ts +25 -25
  67. package/src/cli/templates/project/fullstack.ts +28 -26
  68. package/src/cli/templates/project/index.ts +55 -16
  69. package/src/cli/templates/project/minimal.ts +17 -12
  70. package/src/cli/templates/project/types.ts +10 -5
  71. package/src/cli/templates/project/website.ts +15 -15
  72. package/src/cli/utils/fs.ts +55 -41
  73. package/src/cli/utils/index.ts +3 -3
  74. package/src/cli/utils/strings.ts +47 -33
  75. package/src/cli/utils/version.ts +14 -8
  76. package/src/config/env-validation.ts +100 -0
  77. package/src/config/env.ts +169 -41
  78. package/src/config/index.ts +28 -20
  79. package/src/config/loader.ts +25 -16
  80. package/src/config/merge.ts +21 -10
  81. package/src/config/types.ts +566 -25
  82. package/src/config/validation.ts +215 -7
  83. package/src/container/forward-ref.ts +22 -22
  84. package/src/container/index.ts +34 -12
  85. package/src/context/index.ts +11 -1
  86. package/src/database/index.ts +7 -190
  87. package/src/database/orm/builder.ts +457 -0
  88. package/src/database/orm/casts/index.ts +130 -0
  89. package/src/database/orm/casts/types.ts +25 -0
  90. package/src/database/orm/compiler.ts +304 -0
  91. package/src/database/orm/hooks/index.ts +114 -0
  92. package/src/database/orm/index.ts +61 -0
  93. package/src/database/orm/model-registry.ts +59 -0
  94. package/src/database/orm/model.ts +821 -0
  95. package/src/database/orm/relationships/base.ts +146 -0
  96. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  97. package/src/database/orm/relationships/belongs-to.ts +56 -0
  98. package/src/database/orm/relationships/has-many.ts +45 -0
  99. package/src/database/orm/relationships/has-one.ts +41 -0
  100. package/src/database/orm/relationships/index.ts +11 -0
  101. package/src/database/orm/scopes/index.ts +55 -0
  102. package/src/events/__tests__/event-system.test.ts +235 -0
  103. package/src/events/config.ts +238 -0
  104. package/src/events/example-usage.ts +185 -0
  105. package/src/events/index.ts +278 -0
  106. package/src/events/manager.ts +385 -0
  107. package/src/events/registry.ts +182 -0
  108. package/src/events/types.ts +124 -0
  109. package/src/frontend/api-routes.ts +65 -23
  110. package/src/frontend/bundler.ts +76 -34
  111. package/src/frontend/console-client.ts +2 -2
  112. package/src/frontend/console-stream.ts +94 -38
  113. package/src/frontend/dev-server.ts +94 -46
  114. package/src/frontend/file-router.ts +61 -19
  115. package/src/frontend/frameworks/index.ts +37 -10
  116. package/src/frontend/frameworks/react.ts +10 -8
  117. package/src/frontend/frameworks/solid.ts +11 -9
  118. package/src/frontend/frameworks/svelte.ts +15 -9
  119. package/src/frontend/frameworks/vue.ts +13 -11
  120. package/src/frontend/hmr-client.ts +12 -10
  121. package/src/frontend/hmr.ts +146 -103
  122. package/src/frontend/index.ts +14 -5
  123. package/src/frontend/islands.ts +41 -22
  124. package/src/frontend/isr.ts +59 -37
  125. package/src/frontend/layout.ts +36 -21
  126. package/src/frontend/ssr/react.ts +74 -27
  127. package/src/frontend/ssr/solid.ts +54 -20
  128. package/src/frontend/ssr/svelte.ts +48 -14
  129. package/src/frontend/ssr/vue.ts +50 -18
  130. package/src/frontend/ssr.ts +83 -39
  131. package/src/frontend/types.ts +91 -56
  132. package/src/graphql/built-in-engine.ts +598 -0
  133. package/src/graphql/context-builder.ts +110 -0
  134. package/src/graphql/decorators.ts +358 -0
  135. package/src/graphql/execution-pipeline.ts +227 -0
  136. package/src/graphql/graphql-module.ts +563 -0
  137. package/src/graphql/index.ts +101 -0
  138. package/src/graphql/metadata.ts +237 -0
  139. package/src/graphql/schema-builder.ts +319 -0
  140. package/src/graphql/subscription-handler.ts +283 -0
  141. package/src/graphql/types.ts +324 -0
  142. package/src/health/index.ts +21 -9
  143. package/src/i18n/engine.ts +305 -0
  144. package/src/i18n/index.ts +38 -0
  145. package/src/i18n/loader.ts +218 -0
  146. package/src/i18n/middleware.ts +164 -0
  147. package/src/i18n/negotiator.ts +162 -0
  148. package/src/i18n/types.ts +158 -0
  149. package/src/index.ts +182 -27
  150. package/src/jobs/drivers/memory.ts +315 -0
  151. package/src/jobs/drivers/redis.ts +459 -0
  152. package/src/jobs/index.ts +30 -0
  153. package/src/jobs/queue.ts +281 -0
  154. package/src/jobs/types.ts +295 -0
  155. package/src/jobs/worker.ts +380 -0
  156. package/src/logger/index.ts +1 -3
  157. package/src/logger/transports/index.ts +62 -22
  158. package/src/metrics/index.ts +25 -16
  159. package/src/migrations/index.ts +9 -0
  160. package/src/modules/filters.ts +13 -17
  161. package/src/modules/guards.ts +49 -26
  162. package/src/modules/index.ts +457 -299
  163. package/src/modules/interceptors.ts +58 -20
  164. package/src/modules/lazy.ts +11 -19
  165. package/src/modules/lifecycle.ts +15 -7
  166. package/src/modules/metadata.ts +15 -5
  167. package/src/modules/pipes.ts +94 -72
  168. package/src/notification/channels/base.ts +68 -0
  169. package/src/notification/channels/email.ts +105 -0
  170. package/src/notification/channels/push.ts +104 -0
  171. package/src/notification/channels/sms.ts +105 -0
  172. package/src/notification/channels/whatsapp.ts +104 -0
  173. package/src/notification/index.ts +48 -0
  174. package/src/notification/service.ts +354 -0
  175. package/src/notification/types.ts +344 -0
  176. package/src/observability/__tests__/observability.test.ts +483 -0
  177. package/src/observability/breadcrumbs.ts +114 -0
  178. package/src/observability/index.ts +136 -0
  179. package/src/observability/interceptor.ts +85 -0
  180. package/src/observability/service.ts +303 -0
  181. package/src/observability/trace.ts +37 -0
  182. package/src/observability/types.ts +196 -0
  183. package/src/openapi/__tests__/decorators.test.ts +335 -0
  184. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  185. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  186. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  187. package/src/openapi/decorators.ts +328 -0
  188. package/src/openapi/document-builder.ts +274 -0
  189. package/src/openapi/index.ts +112 -0
  190. package/src/openapi/metadata.ts +112 -0
  191. package/src/openapi/route-scanner.ts +289 -0
  192. package/src/openapi/schema-generator.ts +256 -0
  193. package/src/openapi/swagger-module.ts +166 -0
  194. package/src/openapi/types.ts +398 -0
  195. package/src/orm/index.ts +10 -0
  196. package/src/rpc/index.ts +3 -1
  197. package/src/schema/index.ts +9 -0
  198. package/src/security/index.ts +15 -6
  199. package/src/ssg/index.ts +9 -8
  200. package/src/telemetry/index.ts +76 -22
  201. package/src/template/index.ts +7 -0
  202. package/src/templates/engine.ts +224 -0
  203. package/src/templates/index.ts +9 -0
  204. package/src/templates/loader.ts +331 -0
  205. package/src/templates/renderers/markdown.ts +212 -0
  206. package/src/templates/renderers/simple.ts +269 -0
  207. package/src/templates/types.ts +154 -0
  208. package/src/testing/index.ts +100 -27
  209. package/src/types/optional-deps.d.ts +347 -187
  210. package/src/validation/index.ts +92 -2
  211. package/src/validation/schemas.ts +536 -0
  212. package/tests/integration/cli.test.ts +19 -19
  213. package/tests/integration/fullstack.test.ts +4 -4
  214. package/tests/unit/cli.test.ts +1 -1
  215. package/tests/unit/database.test.ts +2 -72
  216. package/tests/unit/env-validation.test.ts +166 -0
  217. package/tests/unit/events.test.ts +910 -0
  218. package/tests/unit/graphql.test.ts +991 -0
  219. package/tests/unit/i18n.test.ts +455 -0
  220. package/tests/unit/jobs.test.ts +493 -0
  221. package/tests/unit/notification.test.ts +988 -0
  222. package/tests/unit/observability.test.ts +453 -0
  223. package/tests/unit/orm/builder.test.ts +323 -0
  224. package/tests/unit/orm/casts.test.ts +179 -0
  225. package/tests/unit/orm/compiler.test.ts +220 -0
  226. package/tests/unit/orm/eager-loading.test.ts +285 -0
  227. package/tests/unit/orm/hooks.test.ts +191 -0
  228. package/tests/unit/orm/model.test.ts +373 -0
  229. package/tests/unit/orm/relationships.test.ts +303 -0
  230. package/tests/unit/orm/scopes.test.ts +74 -0
  231. package/tests/unit/templates-simple.test.ts +53 -0
  232. package/tests/unit/templates.test.ts +454 -0
  233. package/tests/unit/validation.test.ts +18 -24
  234. package/tsconfig.json +11 -3
@@ -0,0 +1,640 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
13
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
14
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
15
+ r = Reflect.decorate(decorators, target, key, desc);
16
+ else
17
+ for (var i = decorators.length - 1;i >= 0; i--)
18
+ if (d = decorators[i])
19
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
20
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
21
+ };
22
+ var __legacyMetadataTS = (k, v) => {
23
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
24
+ return Reflect.metadata(k, v);
25
+ };
26
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
27
+ var __require = import.meta.require;
28
+ // src/templates/loader.ts
29
+ import { readFileSync, watch } from "fs";
30
+ import { existsSync } from "fs";
31
+ import { extname, resolve } from "path";
32
+ function parseFrontMatter(content) {
33
+ const lines = content.split(`
34
+ `);
35
+ if (lines[0]?.trim() !== "---") {
36
+ return { metadata: {}, body: content };
37
+ }
38
+ let endIdx = -1;
39
+ for (let i = 1;i < lines.length; i++) {
40
+ if (lines[i]?.trim() === "---") {
41
+ endIdx = i;
42
+ break;
43
+ }
44
+ }
45
+ if (endIdx === -1) {
46
+ return { metadata: {}, body: content };
47
+ }
48
+ const frontMatterLines = lines.slice(1, endIdx);
49
+ const metadata = {};
50
+ for (const line of frontMatterLines) {
51
+ if (!line.trim())
52
+ continue;
53
+ const colonIdx = line.indexOf(":");
54
+ if (colonIdx === -1)
55
+ continue;
56
+ const key = line.substring(0, colonIdx).trim();
57
+ let value = line.substring(colonIdx + 1).trim();
58
+ if (value === "true")
59
+ value = true;
60
+ else if (value === "false")
61
+ value = false;
62
+ else if (value === "null")
63
+ value = null;
64
+ else if (!isNaN(Number(value)) && value !== "")
65
+ value = Number(value);
66
+ else if (value.startsWith("[") && value.endsWith("]")) {
67
+ const arrayContent = value.slice(1, -1).trim();
68
+ value = arrayContent.split(",").map((v) => {
69
+ let item = v.trim();
70
+ if (item.startsWith('"') && item.endsWith('"') || item.startsWith("'") && item.endsWith("'")) {
71
+ item = item.slice(1, -1);
72
+ }
73
+ return item;
74
+ });
75
+ }
76
+ metadata[key] = value;
77
+ }
78
+ const body = lines.slice(endIdx + 1).join(`
79
+ `);
80
+ return { metadata, body };
81
+ }
82
+ function parseVariants(content) {
83
+ const sections = content.split(/^## /m);
84
+ const variants = {};
85
+ for (const section of sections) {
86
+ if (!section.trim())
87
+ continue;
88
+ const lines = section.split(`
89
+ `);
90
+ const headerLine = lines[0];
91
+ if (!headerLine)
92
+ continue;
93
+ const variantNames = headerLine.split(/[,/]\s*/).map((s) => s.trim().toLowerCase()).filter((s) => s);
94
+ const contentLines = lines.slice(1);
95
+ let contentStart = 0;
96
+ while (contentStart < contentLines.length && !contentLines[contentStart]?.trim()) {
97
+ contentStart++;
98
+ }
99
+ const sectionContent = contentLines.slice(contentStart).join(`
100
+ `).trim();
101
+ const separatorIdx = sectionContent.indexOf(`
102
+ ---`);
103
+ const finalContent = separatorIdx !== -1 ? sectionContent.substring(0, separatorIdx) : sectionContent;
104
+ for (const variantName of variantNames) {
105
+ variants[variantName] = finalContent;
106
+ }
107
+ }
108
+ return variants;
109
+ }
110
+ function detectFormat(filePath, metadata) {
111
+ if (metadata.format) {
112
+ const fmt = String(metadata.format).toLowerCase();
113
+ if (["markdown", "text", "html"].includes(fmt)) {
114
+ return fmt;
115
+ }
116
+ }
117
+ const ext = extname(filePath).toLowerCase();
118
+ if (ext === ".md" || ext === ".markdown")
119
+ return "markdown";
120
+ if (ext === ".txt" || ext === ".text")
121
+ return "text";
122
+ if (ext === ".html" || ext === ".htm")
123
+ return "html";
124
+ return "markdown";
125
+ }
126
+
127
+ class TemplateLoader {
128
+ options;
129
+ cache = new Map;
130
+ watchers = new Map;
131
+ metrics = {
132
+ loads: 0,
133
+ cacheHits: 0,
134
+ cacheMisses: 0
135
+ };
136
+ constructor(options) {
137
+ this.options = options;
138
+ this.options.cacheEnabled = options.cacheEnabled ?? true;
139
+ this.options.cacheTtl = options.cacheTtl ?? 3600;
140
+ this.options.maxCacheSize = options.maxCacheSize ?? 100;
141
+ this.options.extension = options.extension ?? ".md";
142
+ }
143
+ load(templateId) {
144
+ const now = Date.now();
145
+ if (this.options.cacheEnabled) {
146
+ const cached = this.cache.get(templateId);
147
+ if (cached) {
148
+ const age = (now - cached.timestamp) / 1000;
149
+ if (age < (this.options.cacheTtl ?? 3600)) {
150
+ this.metrics.cacheHits++;
151
+ return cached.template;
152
+ }
153
+ }
154
+ }
155
+ this.metrics.cacheMisses++;
156
+ const template = this._loadFromDisk(templateId);
157
+ if (this.options.cacheEnabled) {
158
+ if (this.cache.size >= (this.options.maxCacheSize ?? 100) && !this.cache.has(templateId)) {
159
+ const oldest = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)[0];
160
+ if (oldest) {
161
+ this.cache.delete(oldest[0]);
162
+ }
163
+ }
164
+ this.cache.set(templateId, { template, timestamp: now });
165
+ if (this.options.watch) {
166
+ this._watchTemplate(templateId, template.id);
167
+ }
168
+ }
169
+ this.metrics.loads++;
170
+ return template;
171
+ }
172
+ _loadFromDisk(templateId) {
173
+ let filePath = resolve(this.options.basePath, templateId + this.options.extension);
174
+ if (!existsSync(filePath)) {
175
+ filePath = resolve(this.options.basePath, templateId + ".md");
176
+ }
177
+ if (!existsSync(filePath)) {
178
+ filePath = resolve(this.options.basePath, templateId + ".txt");
179
+ }
180
+ if (!existsSync(filePath)) {
181
+ throw new Error(`Template not found: ${templateId}`);
182
+ }
183
+ const content = readFileSync(filePath, "utf-8");
184
+ const { metadata, body } = parseFrontMatter(content);
185
+ const variants = parseVariants(body);
186
+ if (Object.keys(variants).length === 0) {
187
+ const defaultVariant = metadata.default || "default";
188
+ variants[defaultVariant] = body;
189
+ }
190
+ const format = detectFormat(filePath, metadata);
191
+ return {
192
+ id: templateId,
193
+ format,
194
+ content: body,
195
+ variants,
196
+ metadata,
197
+ loadedAt: Date.now()
198
+ };
199
+ }
200
+ _watchTemplate(templateId, filePath) {
201
+ if (this.watchers.has(templateId)) {
202
+ return;
203
+ }
204
+ try {
205
+ const fullPath = resolve(this.options.basePath, filePath);
206
+ const watcher = watch(fullPath, () => {
207
+ this.cache.delete(templateId);
208
+ this.watchers.delete(templateId);
209
+ });
210
+ this.watchers.set(templateId, watcher);
211
+ } catch {}
212
+ }
213
+ clear() {
214
+ this.cache.clear();
215
+ for (const watcher of this.watchers.values()) {
216
+ watcher.close();
217
+ }
218
+ this.watchers.clear();
219
+ }
220
+ getMetrics() {
221
+ return { ...this.metrics };
222
+ }
223
+ }
224
+
225
+ // src/templates/renderers/markdown.ts
226
+ class MarkdownRenderer {
227
+ static toHtml(markdown) {
228
+ let html = markdown;
229
+ html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
230
+ html = html.replace(/\*\*([^\*]+)\*\*/g, "<strong>$1</strong>");
231
+ html = html.replace(/__([^_]+)__/g, "<strong>$1</strong>");
232
+ html = html.replace(/(?<!\*)\*([^\*]+)\*(?!\*)/g, "<em>$1</em>");
233
+ html = html.replace(/(?<!_)_([^_]+)_(?!_)/g, "<em>$1</em>");
234
+ html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
235
+ html = html.replace(/ {2}\n/g, `<br>
236
+ `);
237
+ html = html.replace(/^> (.+)$/gm, "<blockquote>$1</blockquote>");
238
+ html = html.replace(/^### ([^\n]+)$/gm, "<h3>$1</h3>");
239
+ html = html.replace(/^## ([^\n]+)$/gm, "<h2>$1</h2>");
240
+ html = html.replace(/^# ([^\n]+)$/gm, "<h1>$1</h1>");
241
+ html = this._processOrderedLists(html);
242
+ html = this._processUnorderedLists(html);
243
+ html = this._processParagraphs(html);
244
+ html = html.replace(/^---$/gm, "<hr>");
245
+ return html.trim();
246
+ }
247
+ static toText(markdown) {
248
+ let text = markdown;
249
+ text = text.replace(/`([^`]+)`/g, "$1");
250
+ text = text.replace(/\*\*([^\*]+)\*\*/g, "$1");
251
+ text = text.replace(/__([^_]+)__/g, "$1");
252
+ text = text.replace(/\*([^\*]+)\*/g, "$1");
253
+ text = text.replace(/_([^_]+)_/g, "$1");
254
+ text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1: $2");
255
+ text = text.replace(/^> (.+)$/gm, "$1");
256
+ text = text.replace(/^#+\s+(.+)$/gm, "$1");
257
+ text = text.replace(/^\d+\.\s+/gm, "");
258
+ text = text.replace(/^[-*+]\s+/gm, "");
259
+ text = text.replace(/\n\n\n+/g, `
260
+
261
+ `);
262
+ return text.trim();
263
+ }
264
+ static _processOrderedLists(html) {
265
+ const lines = html.split(`
266
+ `);
267
+ let inList = false;
268
+ let listHtml = "";
269
+ const result = [];
270
+ for (let i = 0;i < lines.length; i++) {
271
+ const line = lines[i];
272
+ const match = line.match(/^(\d+)\.\s+(.+)$/);
273
+ if (match) {
274
+ if (!inList) {
275
+ inList = true;
276
+ listHtml = `<ol>
277
+ <li>${match[2]}</li>`;
278
+ } else {
279
+ listHtml += `
280
+ <li>${match[2]}</li>`;
281
+ }
282
+ } else {
283
+ if (inList) {
284
+ result.push(listHtml + `
285
+ </ol>`);
286
+ inList = false;
287
+ listHtml = "";
288
+ }
289
+ result.push(line);
290
+ }
291
+ }
292
+ if (inList) {
293
+ result.push(listHtml + `
294
+ </ol>`);
295
+ }
296
+ return result.join(`
297
+ `);
298
+ }
299
+ static _processUnorderedLists(html) {
300
+ const lines = html.split(`
301
+ `);
302
+ let inList = false;
303
+ let listHtml = "";
304
+ const result = [];
305
+ for (let i = 0;i < lines.length; i++) {
306
+ const line = lines[i];
307
+ const match = line.match(/^[-*+]\s+(.+)$/);
308
+ if (match) {
309
+ if (!inList) {
310
+ inList = true;
311
+ listHtml = `<ul>
312
+ <li>${match[1]}</li>`;
313
+ } else {
314
+ listHtml += `
315
+ <li>${match[1]}</li>`;
316
+ }
317
+ } else {
318
+ if (inList) {
319
+ result.push(listHtml + `
320
+ </ul>`);
321
+ inList = false;
322
+ listHtml = "";
323
+ }
324
+ result.push(line);
325
+ }
326
+ }
327
+ if (inList) {
328
+ result.push(listHtml + `
329
+ </ul>`);
330
+ }
331
+ return result.join(`
332
+ `);
333
+ }
334
+ static _processParagraphs(html) {
335
+ const blocks = html.split(/\n\n+/);
336
+ const result = [];
337
+ for (const block of blocks) {
338
+ if (!block.trim())
339
+ continue;
340
+ if (block.match(/^<[a-z]/) || block.match(/^<\/[a-z]/)) {
341
+ result.push(block);
342
+ } else if (block.match(/^<(h[1-6]|ul|ol|blockquote|hr|code)/)) {
343
+ result.push(block);
344
+ } else {
345
+ result.push(`<p>${block}</p>`);
346
+ }
347
+ }
348
+ return result.join(`
349
+ `);
350
+ }
351
+ }
352
+
353
+ // src/templates/renderers/simple.ts
354
+ var builtinFilters = {
355
+ uppercase: (value) => String(value).toUpperCase(),
356
+ lowercase: (value) => String(value).toLowerCase(),
357
+ capitalize: (value) => {
358
+ const str = String(value);
359
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
360
+ },
361
+ trim: (value) => String(value).trim(),
362
+ reverse: (value) => String(value).split("").reverse().join(""),
363
+ length: (value) => {
364
+ if (Array.isArray(value))
365
+ return value.length;
366
+ if (typeof value === "string")
367
+ return value.length;
368
+ return 0;
369
+ },
370
+ join: (value, separator = ",") => {
371
+ if (Array.isArray(value))
372
+ return value.join(String(separator));
373
+ return String(value);
374
+ },
375
+ slice: (value, start = 0, end = undefined) => {
376
+ const str = String(value);
377
+ return str.slice(Number(start), end ? Number(end) : undefined);
378
+ },
379
+ default: (value, defaultVal) => {
380
+ if (value === null || value === undefined || value === "") {
381
+ return defaultVal;
382
+ }
383
+ return value;
384
+ },
385
+ isEmpty: (value) => {
386
+ if (Array.isArray(value))
387
+ return value.length === 0;
388
+ if (value === null || value === undefined || value === "")
389
+ return true;
390
+ return false;
391
+ },
392
+ date: (value, format = "YYYY-MM-DD") => {
393
+ if (!(value instanceof Date)) {
394
+ value = new Date(value);
395
+ }
396
+ if (isNaN(value.getTime()))
397
+ return String(value);
398
+ const year = value.getFullYear();
399
+ const month = String(value.getMonth() + 1).padStart(2, "0");
400
+ const date = String(value.getDate()).padStart(2, "0");
401
+ const hours = String(value.getHours()).padStart(2, "0");
402
+ const minutes = String(value.getMinutes()).padStart(2, "0");
403
+ const seconds = String(value.getSeconds()).padStart(2, "0");
404
+ return format.replace("YYYY", String(year)).replace("MM", month).replace("DD", date).replace("HH", hours).replace("mm", minutes).replace("ss", seconds);
405
+ }
406
+ };
407
+ function getNestedValue(obj, path) {
408
+ const parts = path.split(".");
409
+ let current = obj;
410
+ for (const part of parts) {
411
+ if (current === null || current === undefined) {
412
+ return;
413
+ }
414
+ current = current[part];
415
+ }
416
+ return current;
417
+ }
418
+ function evaluateCondition(condition, data) {
419
+ condition = condition.trim();
420
+ if (condition.includes("||")) {
421
+ return condition.split("||").map((c) => evaluateCondition(c.trim(), data)).some((result) => result);
422
+ }
423
+ if (condition.includes("&&")) {
424
+ return condition.split("&&").map((c) => evaluateCondition(c.trim(), data)).every((result) => result);
425
+ }
426
+ if (condition.startsWith("!")) {
427
+ return !evaluateCondition(condition.slice(1).trim(), data);
428
+ }
429
+ const value = getNestedValue(data, condition);
430
+ return Boolean(value);
431
+ }
432
+
433
+ class SimpleRenderer {
434
+ filters;
435
+ constructor(customFilters) {
436
+ this.filters = { ...builtinFilters, ...customFilters || {} };
437
+ }
438
+ render(template, data) {
439
+ let result = template;
440
+ result = this._processConditionals(result, data);
441
+ result = this._processVariables(result, data);
442
+ return result;
443
+ }
444
+ _processConditionals(template, data) {
445
+ let result = template;
446
+ const ifRegex = /\{\{\s*if\s+([^}]+)\s*\}\}([\s\S]*?)\{\{\s*endif\s*\}\}/;
447
+ while (ifRegex.test(result)) {
448
+ result = result.replace(ifRegex, (match, condition, content) => {
449
+ const elseRegex = /\{\{\s*else\s*\}\}([\s\S]*)$/;
450
+ let thenBlock = content;
451
+ let elseBlock = "";
452
+ const elseMatch = content.match(elseRegex);
453
+ if (elseMatch) {
454
+ thenBlock = content.substring(0, elseMatch.index);
455
+ elseBlock = elseMatch[1];
456
+ }
457
+ if (evaluateCondition(condition, data)) {
458
+ return thenBlock;
459
+ } else {
460
+ return elseBlock;
461
+ }
462
+ });
463
+ }
464
+ return result;
465
+ }
466
+ _processVariables(template, data) {
467
+ const varRegex = /\{\{\s*([^|}\s][^}]*?)\s*(?:\|([^}]*?))?\s*\}\}/g;
468
+ return template.replace(varRegex, (match, varPath, filterChain) => {
469
+ let value = getNestedValue(data, varPath.trim());
470
+ if (value === undefined || value === null) {
471
+ return "";
472
+ }
473
+ if (filterChain) {
474
+ const filters = filterChain.split("|").map((f) => f.trim());
475
+ for (const filterStr of filters) {
476
+ const filterMatch = filterStr.match(/^(\w+)(?:\(([^)]*)\))?$/);
477
+ if (!filterMatch)
478
+ continue;
479
+ const filterName = filterMatch[1];
480
+ const argsStr = filterMatch[2];
481
+ const filterFn = this.filters[filterName];
482
+ if (!filterFn) {
483
+ console.warn(`Unknown filter: ${filterName}`);
484
+ continue;
485
+ }
486
+ const args = [];
487
+ if (argsStr) {
488
+ let current = "";
489
+ let inQuotes = false;
490
+ let quoteChar = "";
491
+ for (const char of argsStr) {
492
+ if ((char === '"' || char === "'") && !inQuotes) {
493
+ inQuotes = true;
494
+ quoteChar = char;
495
+ } else if (char === quoteChar && inQuotes) {
496
+ inQuotes = false;
497
+ quoteChar = "";
498
+ } else if (char === "," && !inQuotes) {
499
+ const trimmed = current.trim();
500
+ const unquoted = trimmed.replace(/^["']|["']$/g, "");
501
+ args.push(unquoted);
502
+ current = "";
503
+ } else {
504
+ current += char;
505
+ }
506
+ }
507
+ if (current) {
508
+ const trimmed = current.trim();
509
+ const unquoted = trimmed.replace(/^["']|["']$/g, "");
510
+ args.push(unquoted);
511
+ }
512
+ }
513
+ value = filterFn(value, ...args);
514
+ }
515
+ }
516
+ return String(value);
517
+ });
518
+ }
519
+ registerFilter(name, fn) {
520
+ this.filters[name] = fn;
521
+ }
522
+ }
523
+
524
+ // src/templates/engine.ts
525
+ var DEFAULT_CHANNEL_VARIANT_MAP = {
526
+ email: "email",
527
+ sms: "sms",
528
+ push: "push",
529
+ whatsapp: "whatsapp",
530
+ web: "web"
531
+ };
532
+
533
+ class TemplateEngine {
534
+ config;
535
+ loader;
536
+ simpleRenderer;
537
+ metrics = {
538
+ loaded: 0,
539
+ cacheHits: 0,
540
+ cacheMisses: 0,
541
+ avgRenderTime: 0,
542
+ totalRenders: 0
543
+ };
544
+ renderTimes = [];
545
+ channelVariantMap;
546
+ constructor(config) {
547
+ this.config = config;
548
+ this.loader = new TemplateLoader({
549
+ basePath: config.basePath,
550
+ cacheEnabled: config.cache?.enabled ?? true,
551
+ cacheTtl: config.cache?.ttl ?? 3600,
552
+ maxCacheSize: config.cache?.maxSize ?? 100,
553
+ watch: config.watch ?? false
554
+ });
555
+ this.simpleRenderer = new SimpleRenderer;
556
+ this.channelVariantMap = config.channelVariantMap || DEFAULT_CHANNEL_VARIANT_MAP;
557
+ }
558
+ async render(templateId, data, options) {
559
+ const startTime = Date.now();
560
+ try {
561
+ const template = this.loader.load(templateId);
562
+ const variant = this._getVariant(template, options?.variant);
563
+ let content = template.variants[variant];
564
+ if (!content) {
565
+ content = Object.values(template.variants)[0] || "";
566
+ }
567
+ const interpolated = this.simpleRenderer.render(content, data);
568
+ const outputFormat = options?.outputFormat || "html";
569
+ let result;
570
+ if (template.format === "markdown") {
571
+ if (outputFormat === "text") {
572
+ result = MarkdownRenderer.toText(interpolated);
573
+ } else {
574
+ result = MarkdownRenderer.toHtml(interpolated);
575
+ }
576
+ } else {
577
+ result = interpolated;
578
+ }
579
+ const duration = Date.now() - startTime;
580
+ this._updateMetrics(duration);
581
+ return result;
582
+ } catch (error) {
583
+ throw new Error(`Failed to render template "${templateId}": ${error instanceof Error ? error.message : String(error)}`);
584
+ }
585
+ }
586
+ async renderToHtml(templateId, data, variant) {
587
+ return this.render(templateId, data, {
588
+ variant,
589
+ outputFormat: "html"
590
+ });
591
+ }
592
+ async renderToText(templateId, data, variant) {
593
+ return this.render(templateId, data, {
594
+ variant,
595
+ outputFormat: "text"
596
+ });
597
+ }
598
+ _getVariant(template, explicitVariant) {
599
+ if (explicitVariant && template.variants[explicitVariant]) {
600
+ return explicitVariant;
601
+ }
602
+ if (template.metadata.default) {
603
+ const defaultVariant = String(template.metadata.default);
604
+ if (template.variants[defaultVariant]) {
605
+ return defaultVariant;
606
+ }
607
+ }
608
+ return Object.keys(template.variants)[0] || "default";
609
+ }
610
+ getVariantForChannel(channel) {
611
+ return this.channelVariantMap[channel.toLowerCase()] || channel;
612
+ }
613
+ registerFilter(name, fn) {
614
+ this.simpleRenderer.registerFilter(name, fn);
615
+ }
616
+ _updateMetrics(duration) {
617
+ this.metrics.totalRenders++;
618
+ this.renderTimes.push(duration);
619
+ if (this.renderTimes.length > 100) {
620
+ this.renderTimes.shift();
621
+ }
622
+ this.metrics.avgRenderTime = this.renderTimes.reduce((a, b) => a + b, 0) / this.renderTimes.length;
623
+ const loaderMetrics = this.loader.getMetrics();
624
+ this.metrics.loaded = loaderMetrics.loads;
625
+ this.metrics.cacheHits = loaderMetrics.cacheHits;
626
+ this.metrics.cacheMisses = loaderMetrics.cacheMisses;
627
+ }
628
+ getMetrics() {
629
+ return { ...this.metrics };
630
+ }
631
+ clear() {
632
+ this.loader.clear();
633
+ }
634
+ }
635
+ export {
636
+ TemplateLoader,
637
+ TemplateEngine,
638
+ SimpleRenderer,
639
+ MarkdownRenderer
640
+ };