binja 0.5.2 → 0.6.0

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.
package/dist/index.js CHANGED
@@ -153,6 +153,159 @@ function tokenizeNative(source) {
153
153
  return tokens;
154
154
  }
155
155
 
156
+ // src/errors/index.ts
157
+ var colors = {
158
+ red: "\x1B[31m",
159
+ yellow: "\x1B[33m",
160
+ cyan: "\x1B[36m",
161
+ gray: "\x1B[90m",
162
+ bold: "\x1B[1m",
163
+ dim: "\x1B[2m",
164
+ reset: "\x1B[0m"
165
+ };
166
+ var useColors = process.stdout?.isTTY !== false;
167
+ function c(color, text) {
168
+ return useColors ? `${colors[color]}${text}${colors.reset}` : text;
169
+ }
170
+
171
+ class TemplateError extends Error {
172
+ line;
173
+ column;
174
+ source;
175
+ templateName;
176
+ suggestion;
177
+ availableOptions;
178
+ constructor(message, options) {
179
+ const formatted = formatError("TemplateError", message, options);
180
+ super(formatted);
181
+ this.name = "TemplateError";
182
+ this.line = options.line;
183
+ this.column = options.column;
184
+ this.source = options.source;
185
+ this.templateName = options.templateName;
186
+ this.suggestion = options.suggestion;
187
+ this.availableOptions = options.availableOptions;
188
+ }
189
+ }
190
+
191
+ class TemplateSyntaxError extends Error {
192
+ line;
193
+ column;
194
+ source;
195
+ templateName;
196
+ suggestion;
197
+ constructor(message, options) {
198
+ const formatted = formatError("TemplateSyntaxError", message, options);
199
+ super(formatted);
200
+ this.name = "TemplateSyntaxError";
201
+ this.line = options.line;
202
+ this.column = options.column;
203
+ this.source = options.source;
204
+ this.templateName = options.templateName;
205
+ this.suggestion = options.suggestion;
206
+ }
207
+ }
208
+
209
+ class TemplateRuntimeError extends Error {
210
+ line;
211
+ column;
212
+ source;
213
+ templateName;
214
+ suggestion;
215
+ availableOptions;
216
+ constructor(message, options) {
217
+ const formatted = formatError("TemplateRuntimeError", message, options);
218
+ super(formatted);
219
+ this.name = "TemplateRuntimeError";
220
+ this.line = options.line;
221
+ this.column = options.column;
222
+ this.source = options.source;
223
+ this.templateName = options.templateName;
224
+ this.suggestion = options.suggestion;
225
+ this.availableOptions = options.availableOptions;
226
+ }
227
+ }
228
+ function formatError(type, message, options) {
229
+ const parts = [];
230
+ const location = options.templateName ? `${options.templateName}:${options.line}:${options.column}` : `line ${options.line}, column ${options.column}`;
231
+ parts.push(`${c("red", c("bold", type))}: ${message} at ${c("cyan", location)}`);
232
+ if (options.source) {
233
+ parts.push("");
234
+ parts.push(generateSnippet(options.source, options.line, options.column));
235
+ }
236
+ if (options.suggestion) {
237
+ parts.push("");
238
+ parts.push(`${c("yellow", "Did you mean")}: ${c("cyan", options.suggestion)}?`);
239
+ }
240
+ if (options.availableOptions && options.availableOptions.length > 0) {
241
+ parts.push("");
242
+ const truncated = options.availableOptions.slice(0, 8);
243
+ const more = options.availableOptions.length > 8 ? ` ${c("gray", `... and ${options.availableOptions.length - 8} more`)}` : "";
244
+ parts.push(`${c("gray", "Available")}: ${truncated.join(", ")}${more}`);
245
+ }
246
+ return parts.join(`
247
+ `);
248
+ }
249
+ function generateSnippet(source, errorLine, errorColumn) {
250
+ const lines = source.split(`
251
+ `);
252
+ const parts = [];
253
+ const startLine = Math.max(1, errorLine - 2);
254
+ const endLine = Math.min(lines.length, errorLine + 1);
255
+ const gutterWidth = String(endLine).length;
256
+ for (let i = startLine;i <= endLine; i++) {
257
+ const lineContent = lines[i - 1] || "";
258
+ const lineNum = String(i).padStart(gutterWidth, " ");
259
+ const isErrorLine = i === errorLine;
260
+ if (isErrorLine) {
261
+ parts.push(`${c("red", " \u2192")} ${c("gray", lineNum)} ${c("dim", "\u2502")} ${lineContent}`);
262
+ const caretPadding = " ".repeat(gutterWidth + 4 + Math.max(0, errorColumn - 1));
263
+ const caret = c("red", "^");
264
+ parts.push(`${caretPadding}${caret}`);
265
+ } else {
266
+ parts.push(` ${c("gray", lineNum)} ${c("dim", "\u2502")} ${c("gray", lineContent)}`);
267
+ }
268
+ }
269
+ return parts.join(`
270
+ `);
271
+ }
272
+ function findSimilar(input, candidates, maxDistance = 3) {
273
+ let bestMatch = null;
274
+ let bestDistance = maxDistance + 1;
275
+ const inputLower = input.toLowerCase();
276
+ for (const candidate of candidates) {
277
+ const distance = levenshteinDistance(inputLower, candidate.toLowerCase());
278
+ if (distance < bestDistance) {
279
+ bestDistance = distance;
280
+ bestMatch = candidate;
281
+ }
282
+ }
283
+ return bestDistance <= maxDistance ? bestMatch : null;
284
+ }
285
+ function levenshteinDistance(a, b) {
286
+ if (a.length === 0)
287
+ return b.length;
288
+ if (b.length === 0)
289
+ return a.length;
290
+ const matrix = [];
291
+ for (let i = 0;i <= b.length; i++) {
292
+ matrix[i] = [i];
293
+ }
294
+ for (let j = 0;j <= a.length; j++) {
295
+ matrix[0][j] = j;
296
+ }
297
+ for (let i = 1;i <= b.length; i++) {
298
+ for (let j = 1;j <= a.length; j++) {
299
+ if (b[i - 1] === a[j - 1]) {
300
+ matrix[i][j] = matrix[i - 1][j - 1];
301
+ } else {
302
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
303
+ }
304
+ }
305
+ }
306
+ return matrix[b.length][a.length];
307
+ }
308
+
156
309
  // src/lexer/index.ts
157
310
  class Lexer {
158
311
  state;
@@ -240,7 +393,11 @@ class Lexer {
240
393
  if (this.peek() === "-")
241
394
  this.advance();
242
395
  if (!this.match(this.blockEnd)) {
243
- throw new Error(`Expected ${this.blockEnd} after ${tagName} at line ${this.state.line}`);
396
+ throw new TemplateSyntaxError(`Expected '${this.blockEnd}' after '${tagName}'`, {
397
+ line: this.state.line,
398
+ column: this.state.column,
399
+ source: this.state.source
400
+ });
244
401
  }
245
402
  const endTag = `end${tagName}`;
246
403
  const contentStart = this.state.pos;
@@ -270,7 +427,11 @@ class Lexer {
270
427
  if (this.peek() === "-")
271
428
  this.advance();
272
429
  if (!this.match(this.blockEnd)) {
273
- throw new Error(`Expected ${this.blockEnd} after ${endTag} at line ${this.state.line}`);
430
+ throw new TemplateSyntaxError(`Expected '${this.blockEnd}' after '${endTag}'`, {
431
+ line: this.state.line,
432
+ column: this.state.column,
433
+ source: this.state.source
434
+ });
274
435
  }
275
436
  return;
276
437
  }
@@ -285,7 +446,12 @@ class Lexer {
285
446
  }
286
447
  this.advance();
287
448
  }
288
- throw new Error(`Unclosed ${tagName} block starting at line ${startLine}`);
449
+ throw new TemplateSyntaxError(`Unclosed '${tagName}' block`, {
450
+ line: startLine,
451
+ column: startColumn,
452
+ source: this.state.source,
453
+ suggestion: `Add {% end${tagName} %} to close the block`
454
+ });
289
455
  }
290
456
  scanText() {
291
457
  const start = this.state.pos;
@@ -325,22 +491,27 @@ class Lexer {
325
491
  }
326
492
  this.scanExpressionToken();
327
493
  }
328
- throw new Error(`Unclosed template tag at line ${this.state.line}`);
494
+ throw new TemplateSyntaxError(`Unclosed template tag`, {
495
+ line: this.state.line,
496
+ column: this.state.column,
497
+ source: this.state.source,
498
+ suggestion: `Add closing delimiter '${endDelimiter}'`
499
+ });
329
500
  }
330
501
  scanExpressionToken() {
331
502
  this.skipWhitespace();
332
503
  if (this.isAtEnd())
333
504
  return;
334
- const c = this.peek();
335
- if (c === '"' || c === "'") {
336
- this.scanString(c);
505
+ const c2 = this.peek();
506
+ if (c2 === '"' || c2 === "'") {
507
+ this.scanString(c2);
337
508
  return;
338
509
  }
339
- if (this.isDigit(c)) {
510
+ if (this.isDigit(c2)) {
340
511
  this.scanNumber();
341
512
  return;
342
513
  }
343
- if (this.isAlpha(c) || c === "_") {
514
+ if (this.isAlpha(c2) || c2 === "_") {
344
515
  this.scanIdentifier();
345
516
  return;
346
517
  }
@@ -361,7 +532,12 @@ class Lexer {
361
532
  this.advance();
362
533
  }
363
534
  if (this.isAtEnd()) {
364
- throw new Error(`Unterminated string at line ${this.state.line}`);
535
+ throw new TemplateSyntaxError(`Unterminated string literal`, {
536
+ line: this.state.line,
537
+ column: this.state.column,
538
+ source: this.state.source,
539
+ suggestion: `Add closing quote '${quote}'`
540
+ });
365
541
  }
366
542
  const value = this.state.source.slice(start, this.state.pos);
367
543
  this.advance();
@@ -391,55 +567,55 @@ class Lexer {
391
567
  this.addToken(type, value);
392
568
  }
393
569
  scanOperator() {
394
- const c = this.advance();
395
- switch (c) {
570
+ const c2 = this.advance();
571
+ switch (c2) {
396
572
  case ".":
397
- this.addToken("DOT" /* DOT */, c);
573
+ this.addToken("DOT" /* DOT */, c2);
398
574
  break;
399
575
  case ",":
400
- this.addToken("COMMA" /* COMMA */, c);
576
+ this.addToken("COMMA" /* COMMA */, c2);
401
577
  break;
402
578
  case ":":
403
- this.addToken("COLON" /* COLON */, c);
579
+ this.addToken("COLON" /* COLON */, c2);
404
580
  break;
405
581
  case "|":
406
- this.addToken("PIPE" /* PIPE */, c);
582
+ this.addToken("PIPE" /* PIPE */, c2);
407
583
  break;
408
584
  case "(":
409
- this.addToken("LPAREN" /* LPAREN */, c);
585
+ this.addToken("LPAREN" /* LPAREN */, c2);
410
586
  break;
411
587
  case ")":
412
- this.addToken("RPAREN" /* RPAREN */, c);
588
+ this.addToken("RPAREN" /* RPAREN */, c2);
413
589
  break;
414
590
  case "[":
415
- this.addToken("LBRACKET" /* LBRACKET */, c);
591
+ this.addToken("LBRACKET" /* LBRACKET */, c2);
416
592
  break;
417
593
  case "]":
418
- this.addToken("RBRACKET" /* RBRACKET */, c);
594
+ this.addToken("RBRACKET" /* RBRACKET */, c2);
419
595
  break;
420
596
  case "{":
421
- this.addToken("LBRACE" /* LBRACE */, c);
597
+ this.addToken("LBRACE" /* LBRACE */, c2);
422
598
  break;
423
599
  case "}":
424
- this.addToken("RBRACE" /* RBRACE */, c);
600
+ this.addToken("RBRACE" /* RBRACE */, c2);
425
601
  break;
426
602
  case "+":
427
- this.addToken("ADD" /* ADD */, c);
603
+ this.addToken("ADD" /* ADD */, c2);
428
604
  break;
429
605
  case "-":
430
- this.addToken("SUB" /* SUB */, c);
606
+ this.addToken("SUB" /* SUB */, c2);
431
607
  break;
432
608
  case "*":
433
- this.addToken("MUL" /* MUL */, c);
609
+ this.addToken("MUL" /* MUL */, c2);
434
610
  break;
435
611
  case "/":
436
- this.addToken("DIV" /* DIV */, c);
612
+ this.addToken("DIV" /* DIV */, c2);
437
613
  break;
438
614
  case "%":
439
- this.addToken("MOD" /* MOD */, c);
615
+ this.addToken("MOD" /* MOD */, c2);
440
616
  break;
441
617
  case "~":
442
- this.addToken("TILDE" /* TILDE */, c);
618
+ this.addToken("TILDE" /* TILDE */, c2);
443
619
  break;
444
620
  case "=":
445
621
  if (this.match("=")) {
@@ -452,7 +628,12 @@ class Lexer {
452
628
  if (this.match("=")) {
453
629
  this.addToken("NE" /* NE */, "!=");
454
630
  } else {
455
- throw new Error(`Unexpected character '!' at line ${this.state.line}`);
631
+ throw new TemplateSyntaxError(`Unexpected character '!'`, {
632
+ line: this.state.line,
633
+ column: this.state.column - 1,
634
+ source: this.state.source,
635
+ suggestion: `Use '!=' for not-equal comparison or 'not' for negation`
636
+ });
456
637
  }
457
638
  break;
458
639
  case "<":
@@ -470,8 +651,12 @@ class Lexer {
470
651
  }
471
652
  break;
472
653
  default:
473
- if (!this.isWhitespace(c)) {
474
- throw new Error(`Unexpected character '${c}' at line ${this.state.line}`);
654
+ if (!this.isWhitespace(c2)) {
655
+ throw new TemplateSyntaxError(`Unexpected character '${c2}'`, {
656
+ line: this.state.line,
657
+ column: this.state.column - 1,
658
+ source: this.state.source
659
+ });
475
660
  }
476
661
  }
477
662
  }
@@ -502,10 +687,10 @@ class Lexer {
502
687
  return this.state.source[this.state.pos + 1];
503
688
  }
504
689
  advance() {
505
- const c = this.state.source[this.state.pos];
690
+ const c2 = this.state.source[this.state.pos];
506
691
  this.state.pos++;
507
692
  this.state.column++;
508
- return c;
693
+ return c2;
509
694
  }
510
695
  match(expected, offset = 0) {
511
696
  const source = this.state.source;
@@ -545,20 +730,20 @@ class Lexer {
545
730
  this.advance();
546
731
  }
547
732
  }
548
- isWhitespace(c) {
549
- return c === " " || c === "\t" || c === `
550
- ` || c === "\r";
733
+ isWhitespace(c2) {
734
+ return c2 === " " || c2 === "\t" || c2 === `
735
+ ` || c2 === "\r";
551
736
  }
552
- isDigit(c) {
553
- const code = c.charCodeAt(0);
737
+ isDigit(c2) {
738
+ const code = c2.charCodeAt(0);
554
739
  return code >= 48 && code <= 57;
555
740
  }
556
- isAlpha(c) {
557
- const code = c.charCodeAt(0);
741
+ isAlpha(c2) {
742
+ const code = c2.charCodeAt(0);
558
743
  return code >= 97 && code <= 122 || code >= 65 && code <= 90;
559
744
  }
560
- isAlphaNumeric(c) {
561
- const code = c.charCodeAt(0);
745
+ isAlphaNumeric(c2) {
746
+ const code = c2.charCodeAt(0);
562
747
  return code >= 48 && code <= 57 || code >= 97 && code <= 122 || code >= 65 && code <= 90;
563
748
  }
564
749
  addToken(type, value) {
@@ -575,8 +760,10 @@ class Lexer {
575
760
  class Parser {
576
761
  tokens;
577
762
  current = 0;
578
- constructor(tokens) {
763
+ source;
764
+ constructor(tokens, source) {
579
765
  this.tokens = tokens;
766
+ this.source = source;
580
767
  }
581
768
  parse() {
582
769
  const body = [];
@@ -1668,7 +1855,11 @@ class Parser {
1668
1855
  }
1669
1856
  error(message) {
1670
1857
  const token = this.peek();
1671
- return new Error(`Parse error at line ${token.line}, column ${token.column}: ${message}`);
1858
+ return new TemplateSyntaxError(message, {
1859
+ line: token.line,
1860
+ column: token.column,
1861
+ source: this.source
1862
+ });
1672
1863
  }
1673
1864
  }
1674
1865
 
@@ -1830,7 +2021,7 @@ var capfirst = (value) => {
1830
2021
  const str = String(value);
1831
2022
  return str.charAt(0).toUpperCase() + str.slice(1);
1832
2023
  };
1833
- var title = (value) => String(value).replace(TITLE_REGEX, (c) => c.toUpperCase());
2024
+ var title = (value) => String(value).replace(TITLE_REGEX, (c2) => c2.toUpperCase());
1834
2025
  var trim = (value) => String(value).trim();
1835
2026
  var striptags = (value) => String(value).replace(STRIPTAGS_REGEX, "");
1836
2027
  var escape = (value) => {
@@ -2943,6 +3134,7 @@ class Runtime {
2943
3134
  tests;
2944
3135
  blocks = new Map;
2945
3136
  parentTemplate = null;
3137
+ source;
2946
3138
  constructor(options = {}) {
2947
3139
  this.options = {
2948
3140
  autoescape: options.autoescape ?? true,
@@ -3679,8 +3871,17 @@ class Runtime {
3679
3871
  }
3680
3872
  const kwargs = this.evalObjectSync(node.kwargs, ctx);
3681
3873
  const filter = this.filters[node.filter];
3682
- if (!filter)
3683
- throw new Error(`Unknown filter: ${node.filter}`);
3874
+ if (!filter) {
3875
+ const available = Object.keys(this.filters);
3876
+ const suggestion = findSimilar(node.filter, available);
3877
+ throw new TemplateRuntimeError(`Unknown filter '${node.filter}'`, {
3878
+ line: node.line,
3879
+ column: node.column,
3880
+ source: this.source,
3881
+ suggestion: suggestion || undefined,
3882
+ availableOptions: available.slice(0, 15)
3883
+ });
3884
+ }
3684
3885
  return filter(value, ...args, ...Object.values(kwargs));
3685
3886
  }
3686
3887
  evalBinaryOp(node, ctx) {
@@ -4117,6 +4318,9 @@ class Runtime {
4117
4318
  addGlobal(name, value) {
4118
4319
  this.options.globals[name] = value;
4119
4320
  }
4321
+ setSource(source) {
4322
+ this.source = source;
4323
+ }
4120
4324
  }
4121
4325
 
4122
4326
  // src/compiler/index.ts
@@ -4877,6 +5081,14 @@ class DebugCollector {
4877
5081
  testsUsed: new Map,
4878
5082
  cacheHits: 0,
4879
5083
  cacheMisses: 0,
5084
+ queries: [],
5085
+ queryStats: {
5086
+ count: 0,
5087
+ totalDuration: 0,
5088
+ slowCount: 0,
5089
+ n1Count: 0,
5090
+ queryCounts: new Map
5091
+ },
4880
5092
  warnings: []
4881
5093
  };
4882
5094
  }
@@ -5017,6 +5229,33 @@ class DebugCollector {
5017
5229
  addWarning(message) {
5018
5230
  this.data.warnings.push(message);
5019
5231
  }
5232
+ recordQuery(query) {
5233
+ const normalizedSql = this.normalizeQuery(query.sql);
5234
+ const currentCount = this.data.queryStats.queryCounts.get(normalizedSql) || 0;
5235
+ this.data.queryStats.queryCounts.set(normalizedSql, currentCount + 1);
5236
+ const isN1 = currentCount >= 2;
5237
+ const queryInfo = {
5238
+ ...query,
5239
+ timestamp: performance.now(),
5240
+ isN1
5241
+ };
5242
+ this.data.queries.push(queryInfo);
5243
+ this.data.queryStats.count++;
5244
+ this.data.queryStats.totalDuration += query.duration;
5245
+ if (query.duration > 100) {
5246
+ this.data.queryStats.slowCount++;
5247
+ }
5248
+ if (isN1 && currentCount === 2) {
5249
+ this.data.queryStats.n1Count++;
5250
+ this.addWarning(`N+1 query detected: ${normalizedSql.slice(0, 50)}...`);
5251
+ }
5252
+ }
5253
+ normalizeQuery(sql) {
5254
+ return sql.replace(/\s+/g, " ").replace(/= \?/g, "= ?").replace(/= \$\d+/g, "= ?").replace(/= '\w+'/g, "= '?'").replace(/= \d+/g, "= ?").replace(/IN \([^)]+\)/gi, "IN (?)").trim();
5255
+ }
5256
+ getQueryStats() {
5257
+ return this.data.queryStats;
5258
+ }
5020
5259
  getData() {
5021
5260
  return this.data;
5022
5261
  }
@@ -5053,13 +5292,13 @@ var DEFAULT_OPTIONS = {
5053
5292
  function generateDebugPanel(data, options = {}) {
5054
5293
  const opts = { ...DEFAULT_OPTIONS, ...options };
5055
5294
  const id = `binja-debug-${Date.now()}`;
5056
- const colors = opts.dark ? darkTheme : lightTheme;
5295
+ const colors2 = opts.dark ? darkTheme : lightTheme;
5057
5296
  return `
5058
5297
  <!-- Binja Debug Panel -->
5059
5298
  <div id="${id}" class="binja-debugger" data-theme="${opts.dark ? "dark" : "light"}">
5060
- <style>${generateStyles(id, colors, opts)}</style>
5061
- ${generateToggle(id, data, colors)}
5062
- ${generatePanel(id, data, colors, opts)}
5299
+ <style>${generateStyles(id, colors2, opts)}</style>
5300
+ ${generateToggle(id, data, colors2)}
5301
+ ${generatePanel(id, data, colors2, opts)}
5063
5302
  <script>${generateScript(id)}</script>
5064
5303
  </div>
5065
5304
  <!-- /Binja Debug Panel -->
@@ -5097,84 +5336,103 @@ var lightTheme = {
5097
5336
  error: "#dc2626",
5098
5337
  info: "#0891b2"
5099
5338
  };
5100
- function generateStyles(id, c, opts) {
5339
+ function generateStyles(id, c2, opts) {
5101
5340
  const pos = getPosition(opts.position);
5102
5341
  return `
5103
5342
  #${id} { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; font-size: 13px; line-height: 1.5; position: fixed; ${pos} z-index: 2147483647; }
5104
5343
  #${id} * { box-sizing: border-box; margin: 0; padding: 0; }
5105
- #${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${c.bg}; border: 1px solid ${c.border}; border-radius: 8px; color: ${c.text}; cursor: pointer; font-size: 12px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.2s ease; }
5106
- #${id} .dbg-toggle:hover { border-color: ${c.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
5344
+ #${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 8px; color: ${c2.text}; cursor: pointer; font-size: 12px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.2s ease; }
5345
+ #${id} .dbg-toggle:hover { border-color: ${c2.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
5107
5346
  #${id} .dbg-toggle svg { width: 16px; height: 16px; }
5108
- #${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${c.bgTertiary}; border-radius: 4px; color: ${c.success}; }
5109
- #${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${c.bg}; border: 1px solid ${c.border}; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.24); overflow: hidden; margin-top: 8px; }
5347
+ #${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${c2.bgTertiary}; border-radius: 4px; color: ${c2.success}; }
5348
+ #${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.24); overflow: hidden; margin-top: 8px; }
5110
5349
  #${id} .dbg-panel.open { display: block; }
5111
- #${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c.bgSecondary}; border-bottom: 1px solid ${c.border}; }
5112
- #${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c.text}; }
5113
- #${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c.accent}; }
5350
+ #${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c2.bgSecondary}; border-bottom: 1px solid ${c2.border}; }
5351
+ #${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c2.text}; }
5352
+ #${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c2.accent}; }
5114
5353
  #${id} .dbg-meta { display: flex; align-items: center; gap: 12px; }
5115
5354
  #${id} .dbg-badge { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; padding: 3px 10px; border-radius: 4px; font-weight: 500; }
5116
- #${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c.success}; }
5117
- #${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c.accent}; }
5118
- #${id} .dbg-close { background: none; border: none; color: ${c.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
5119
- #${id} .dbg-close:hover { background: ${c.bgTertiary}; color: ${c.text}; }
5355
+ #${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c2.success}; }
5356
+ #${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c2.accent}; }
5357
+ #${id} .dbg-close { background: none; border: none; color: ${c2.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
5358
+ #${id} .dbg-close:hover { background: ${c2.bgTertiary}; color: ${c2.text}; }
5120
5359
  #${id} .dbg-close svg { width: 18px; height: 18px; }
5121
5360
  #${id} .dbg-body { max-height: calc(85vh - 52px); overflow-y: auto; }
5122
- #${id} .dbg-section { border-bottom: 1px solid ${c.border}; }
5361
+ #${id} .dbg-section { border-bottom: 1px solid ${c2.border}; }
5123
5362
  #${id} .dbg-section:last-child { border-bottom: none; }
5124
5363
  #${id} .dbg-section-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; cursor: pointer; user-select: none; transition: background 0.15s; }
5125
- #${id} .dbg-section-header:hover { background: ${c.bgSecondary}; }
5126
- #${id} .dbg-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${c.textSecondary}; }
5364
+ #${id} .dbg-section-header:hover { background: ${c2.bgSecondary}; }
5365
+ #${id} .dbg-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${c2.textSecondary}; }
5127
5366
  #${id} .dbg-section-title svg { width: 14px; height: 14px; opacity: 0.7; }
5128
- #${id} .dbg-section-meta { font-size: 11px; color: ${c.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
5129
- #${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c.bgSecondary}; }
5367
+ #${id} .dbg-section-meta { font-size: 11px; color: ${c2.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
5368
+ #${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c2.bgSecondary}; }
5130
5369
  #${id} .dbg-section.open .dbg-section-content { display: block; }
5131
- #${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c.textMuted}; }
5370
+ #${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c2.textMuted}; }
5132
5371
  #${id} .dbg-section.open .dbg-chevron { transform: rotate(90deg); }
5133
- #${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c.border}; }
5372
+ #${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c2.border}; }
5134
5373
  #${id} .dbg-row:last-child { border-bottom: none; }
5135
- #${id} .dbg-label { color: ${c.textSecondary}; font-size: 12px; }
5136
- #${id} .dbg-value { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5137
- #${id} .dbg-bar { height: 3px; background: ${c.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
5374
+ #${id} .dbg-label { color: ${c2.textSecondary}; font-size: 12px; }
5375
+ #${id} .dbg-value { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5376
+ #${id} .dbg-bar { height: 3px; background: ${c2.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
5138
5377
  #${id} .dbg-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s ease; }
5139
- #${id} .dbg-bar-fill.lexer { background: ${c.info}; }
5140
- #${id} .dbg-bar-fill.parser { background: ${c.warning}; }
5141
- #${id} .dbg-bar-fill.render { background: ${c.success}; }
5378
+ #${id} .dbg-bar-fill.lexer { background: ${c2.info}; }
5379
+ #${id} .dbg-bar-fill.parser { background: ${c2.warning}; }
5380
+ #${id} .dbg-bar-fill.render { background: ${c2.success}; }
5142
5381
  #${id} .dbg-templates { display: flex; flex-direction: column; gap: 6px; }
5143
- #${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c.bg}; border-radius: 6px; font-size: 12px; }
5144
- #${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c.textMuted}; flex-shrink: 0; }
5145
- #${id} .dbg-template-name { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; }
5382
+ #${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c2.bg}; border-radius: 6px; font-size: 12px; }
5383
+ #${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c2.textMuted}; flex-shrink: 0; }
5384
+ #${id} .dbg-template-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; }
5146
5385
  #${id} .dbg-template-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; text-transform: uppercase; }
5147
- #${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c.accent}; }
5386
+ #${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c2.accent}; }
5148
5387
  #${id} .dbg-template-tag.extends { background: rgba(168,85,247,0.15); color: #a855f7; }
5149
- #${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c.success}; }
5388
+ #${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c2.success}; }
5150
5389
  #${id} .dbg-ctx-grid { display: flex; flex-direction: column; gap: 4px; }
5151
- #${id} .dbg-ctx-item { background: ${c.bg}; border-radius: 6px; overflow: hidden; }
5390
+ #${id} .dbg-ctx-item { background: ${c2.bg}; border-radius: 6px; overflow: hidden; }
5152
5391
  #${id} .dbg-ctx-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; cursor: default; }
5153
5392
  #${id} .dbg-ctx-row.expandable { cursor: pointer; }
5154
- #${id} .dbg-ctx-row.expandable:hover { background: ${c.bgTertiary}; }
5393
+ #${id} .dbg-ctx-row.expandable:hover { background: ${c2.bgTertiary}; }
5155
5394
  #${id} .dbg-ctx-key { display: flex; align-items: center; gap: 6px; }
5156
- #${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
5395
+ #${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c2.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
5157
5396
  #${id} .dbg-ctx-item.open > .dbg-ctx-row .dbg-ctx-arrow { transform: rotate(90deg); }
5158
- #${id} .dbg-ctx-name { color: ${c.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
5159
- #${id} .dbg-ctx-type { font-size: 10px; color: ${c.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
5160
- #${id} .dbg-ctx-preview { color: ${c.textSecondary}; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5161
- #${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c.border}; margin-left: 10px; }
5397
+ #${id} .dbg-ctx-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
5398
+ #${id} .dbg-ctx-type { font-size: 10px; color: ${c2.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
5399
+ #${id} .dbg-ctx-preview { color: ${c2.textSecondary}; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
5400
+ #${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c2.border}; margin-left: 10px; }
5162
5401
  #${id} .dbg-ctx-item.open > .dbg-ctx-children { display: block; }
5163
5402
  #${id} .dbg-ctx-children .dbg-ctx-item { background: transparent; }
5164
5403
  #${id} .dbg-ctx-children .dbg-ctx-row { padding: 4px 8px; }
5165
5404
  #${id} .dbg-filters { display: flex; flex-wrap: wrap; gap: 6px; }
5166
- #${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${c.bg}; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', Monaco, monospace; color: ${c.text}; }
5167
- #${id} .dbg-filter-count { font-size: 10px; color: ${c.accent}; font-weight: 600; }
5405
+ #${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${c2.bg}; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', Monaco, monospace; color: ${c2.text}; }
5406
+ #${id} .dbg-filter-count { font-size: 10px; color: ${c2.accent}; font-weight: 600; }
5168
5407
  #${id} .dbg-cache { display: flex; gap: 16px; }
5169
- #${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c.bg}; border-radius: 6px; text-align: center; }
5408
+ #${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c2.bg}; border-radius: 6px; text-align: center; }
5170
5409
  #${id} .dbg-cache-num { font-size: 24px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
5171
- #${id} .dbg-cache-num.hit { color: ${c.success}; }
5172
- #${id} .dbg-cache-num.miss { color: ${c.error}; }
5173
- #${id} .dbg-cache-label { font-size: 11px; color: ${c.textMuted}; margin-top: 4px; }
5410
+ #${id} .dbg-cache-num.hit { color: ${c2.success}; }
5411
+ #${id} .dbg-cache-num.miss { color: ${c2.error}; }
5412
+ #${id} .dbg-cache-label { font-size: 11px; color: ${c2.textMuted}; margin-top: 4px; }
5174
5413
  #${id} .dbg-warnings { display: flex; flex-direction: column; gap: 6px; }
5175
- #${id} .dbg-warning { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(234,179,8,0.1); border-radius: 6px; border-left: 3px solid ${c.warning}; }
5176
- #${id} .dbg-warning-icon { color: ${c.warning}; flex-shrink: 0; margin-top: 1px; }
5177
- #${id} .dbg-warning-text { color: ${c.text}; font-size: 12px; }
5414
+ #${id} .dbg-warning { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(234,179,8,0.1); border-radius: 6px; border-left: 3px solid ${c2.warning}; }
5415
+ #${id} .dbg-warning-icon { color: ${c2.warning}; flex-shrink: 0; margin-top: 1px; }
5416
+ #${id} .dbg-warning-text { color: ${c2.text}; font-size: 12px; }
5417
+ #${id} .dbg-queries { display: flex; flex-direction: column; gap: 8px; }
5418
+ #${id} .dbg-query { background: ${c2.bg}; border-radius: 6px; overflow: hidden; }
5419
+ #${id} .dbg-query.n1 { border-left: 3px solid ${c2.error}; }
5420
+ #${id} .dbg-query.slow { border-left: 3px solid ${c2.warning}; }
5421
+ #${id} .dbg-query-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; gap: 8px; }
5422
+ #${id} .dbg-query-sql { flex: 1; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; color: ${c2.text}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
5423
+ #${id} .dbg-query-meta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
5424
+ #${id} .dbg-query-time { font-family: 'SF Mono', Monaco, monospace; font-size: 10px; padding: 2px 6px; border-radius: 3px; background: ${c2.bgTertiary}; }
5425
+ #${id} .dbg-query-time.slow { background: rgba(234,179,8,0.15); color: ${c2.warning}; }
5426
+ #${id} .dbg-query-rows { font-size: 10px; color: ${c2.textMuted}; }
5427
+ #${id} .dbg-query-source { font-size: 9px; padding: 2px 5px; border-radius: 3px; background: rgba(59,130,246,0.1); color: ${c2.accent}; text-transform: uppercase; }
5428
+ #${id} .dbg-query-badge { font-size: 9px; padding: 2px 5px; border-radius: 3px; font-weight: 600; }
5429
+ #${id} .dbg-query-badge.n1 { background: rgba(239,68,68,0.15); color: ${c2.error}; }
5430
+ #${id} .dbg-query-stats { display: flex; gap: 12px; margin-bottom: 12px; padding: 10px; background: ${c2.bg}; border-radius: 6px; }
5431
+ #${id} .dbg-query-stat { flex: 1; text-align: center; }
5432
+ #${id} .dbg-query-stat-num { font-size: 18px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; color: ${c2.text}; }
5433
+ #${id} .dbg-query-stat-num.warning { color: ${c2.warning}; }
5434
+ #${id} .dbg-query-stat-num.error { color: ${c2.error}; }
5435
+ #${id} .dbg-query-stat-label { font-size: 10px; color: ${c2.textMuted}; margin-top: 2px; }
5178
5436
  `;
5179
5437
  }
5180
5438
  function getPosition(pos) {
@@ -5199,9 +5457,10 @@ var icons = {
5199
5457
  filter: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>`,
5200
5458
  cache: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 1010 10H12V2z"/><path d="M12 2a10 10 0 00-8.66 15"/></svg>`,
5201
5459
  warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
5202
- file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg>`
5460
+ file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg>`,
5461
+ database: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>`
5203
5462
  };
5204
- function generateToggle(id, data, c) {
5463
+ function generateToggle(id, data, c2) {
5205
5464
  const time2 = (data.totalTime || 0).toFixed(1);
5206
5465
  return `
5207
5466
  <button class="dbg-toggle" onclick="document.querySelector('#${id} .dbg-panel').classList.add('open');this.style.display='none'">
@@ -5210,7 +5469,7 @@ function generateToggle(id, data, c) {
5210
5469
  <span class="dbg-time">${time2}ms</span>
5211
5470
  </button>`;
5212
5471
  }
5213
- function generatePanel(id, data, c, opts) {
5472
+ function generatePanel(id, data, c2, opts) {
5214
5473
  const time2 = (data.totalTime || 0).toFixed(2);
5215
5474
  const mode = data.mode === "aot" ? "AOT" : "Runtime";
5216
5475
  return `
@@ -5228,6 +5487,7 @@ function generatePanel(id, data, c, opts) {
5228
5487
  ${generateTemplatesSection(data)}
5229
5488
  ${generateContextSection(data)}
5230
5489
  ${generateFiltersSection(data)}
5490
+ ${generateQueriesSection(data)}
5231
5491
  ${generateCacheSection(data)}
5232
5492
  ${generateWarningsSection(data)}
5233
5493
  </div>
@@ -5391,6 +5651,70 @@ function generateWarningsSection(data) {
5391
5651
  </div>
5392
5652
  </div>`;
5393
5653
  }
5654
+ function generateQueriesSection(data) {
5655
+ if (data.queries.length === 0)
5656
+ return "";
5657
+ const stats = data.queryStats;
5658
+ const hasIssues = stats.slowCount > 0 || stats.n1Count > 0;
5659
+ const statsHtml = `
5660
+ <div class="dbg-query-stats">
5661
+ <div class="dbg-query-stat">
5662
+ <div class="dbg-query-stat-num">${stats.count}</div>
5663
+ <div class="dbg-query-stat-label">Queries</div>
5664
+ </div>
5665
+ <div class="dbg-query-stat">
5666
+ <div class="dbg-query-stat-num">${stats.totalDuration.toFixed(1)}ms</div>
5667
+ <div class="dbg-query-stat-label">Total Time</div>
5668
+ </div>
5669
+ <div class="dbg-query-stat">
5670
+ <div class="dbg-query-stat-num ${stats.slowCount > 0 ? "warning" : ""}">${stats.slowCount}</div>
5671
+ <div class="dbg-query-stat-label">Slow (&gt;100ms)</div>
5672
+ </div>
5673
+ <div class="dbg-query-stat">
5674
+ <div class="dbg-query-stat-num ${stats.n1Count > 0 ? "error" : ""}">${stats.n1Count}</div>
5675
+ <div class="dbg-query-stat-label">N+1</div>
5676
+ </div>
5677
+ </div>`;
5678
+ const queries = data.queries.map((q) => {
5679
+ const isSlow = q.duration > 100;
5680
+ const classes = [
5681
+ "dbg-query",
5682
+ q.isN1 ? "n1" : "",
5683
+ isSlow ? "slow" : ""
5684
+ ].filter(Boolean).join(" ");
5685
+ const badges = [];
5686
+ if (q.isN1) {
5687
+ badges.push('<span class="dbg-query-badge n1">N+1</span>');
5688
+ }
5689
+ const rowsText = q.rows !== undefined ? `<span class="dbg-query-rows">${q.rows} rows</span>` : "";
5690
+ const sourceText = q.source ? `<span class="dbg-query-source">${escapeHtml(q.source)}</span>` : "";
5691
+ return `
5692
+ <div class="${classes}">
5693
+ <div class="dbg-query-header">
5694
+ <span class="dbg-query-sql" title="${escapeHtml(q.sql)}">${escapeHtml(q.sql)}</span>
5695
+ <div class="dbg-query-meta">
5696
+ ${badges.join("")}
5697
+ ${sourceText}
5698
+ ${rowsText}
5699
+ <span class="dbg-query-time ${isSlow ? "slow" : ""}">${q.duration.toFixed(1)}ms</span>
5700
+ </div>
5701
+ </div>
5702
+ </div>`;
5703
+ }).join("");
5704
+ const metaColor = hasIssues ? 'style="color:#ef4444"' : "";
5705
+ return `
5706
+ <div class="dbg-section ${hasIssues ? "open" : ""}">
5707
+ <div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
5708
+ <span class="dbg-section-title">${icons.database} Queries</span>
5709
+ <span class="dbg-section-meta" ${metaColor}>${stats.count} (${stats.totalDuration.toFixed(1)}ms)</span>
5710
+ <span class="dbg-chevron">${icons.chevron}</span>
5711
+ </div>
5712
+ <div class="dbg-section-content">
5713
+ ${statsHtml}
5714
+ <div class="dbg-queries">${queries}</div>
5715
+ </div>
5716
+ </div>`;
5717
+ }
5394
5718
  function generateScript(id) {
5395
5719
  return `
5396
5720
  (function(){
@@ -5484,12 +5808,12 @@ function createDebugRenderer(env, options = {}) {
5484
5808
  function debugMiddleware(env, options = {}) {
5485
5809
  return {
5486
5810
  hono() {
5487
- return async (c, next) => {
5811
+ return async (c2, next) => {
5488
5812
  await next();
5489
- const contentType = c.res.headers.get("content-type") || "";
5813
+ const contentType = c2.res.headers.get("content-type") || "";
5490
5814
  if (!contentType.includes("text/html"))
5491
5815
  return;
5492
- const body = await c.res.text();
5816
+ const body = await c2.res.text();
5493
5817
  const collector = startDebugCollection();
5494
5818
  collector.captureContext({});
5495
5819
  collector.setMode("runtime");
@@ -5497,9 +5821,9 @@ function debugMiddleware(env, options = {}) {
5497
5821
  const data = endDebugCollection();
5498
5822
  const panel = generateDebugPanel(data, options.panel);
5499
5823
  const newBody = body.includes("</body>") ? body.replace("</body>", `${panel}</body>`) : body + panel;
5500
- c.res = new Response(newBody, {
5501
- status: c.res.status,
5502
- headers: c.res.headers
5824
+ c2.res = new Response(newBody, {
5825
+ status: c2.res.status,
5826
+ headers: c2.res.headers
5503
5827
  });
5504
5828
  };
5505
5829
  },
@@ -5620,7 +5944,7 @@ class Environment {
5620
5944
  compile(source) {
5621
5945
  const lexer = new Lexer(source);
5622
5946
  const tokens = lexer.tokenize();
5623
- const parser = new Parser(tokens);
5947
+ const parser = new Parser(tokens, source);
5624
5948
  return parser.parse();
5625
5949
  }
5626
5950
  async loadTemplate(templateName) {
@@ -5741,7 +6065,7 @@ function Template(source, options = {}) {
5741
6065
  function compile(source, options = {}) {
5742
6066
  const lexer = new Lexer(source);
5743
6067
  const tokens = lexer.tokenize();
5744
- const parser = new Parser(tokens);
6068
+ const parser = new Parser(tokens, source);
5745
6069
  const ast = parser.parse();
5746
6070
  return compileToFunction(ast, options);
5747
6071
  }
@@ -5764,7 +6088,7 @@ async function compileWithInheritance(templateName, options) {
5764
6088
  parse(source2) {
5765
6089
  const lexer = new Lexer(source2);
5766
6090
  const tokens = lexer.tokenize();
5767
- const parser = new Parser(tokens);
6091
+ const parser = new Parser(tokens, source2);
5768
6092
  return parser.parse();
5769
6093
  }
5770
6094
  };
@@ -5796,7 +6120,7 @@ async function compileWithInheritanceToCode(templateName, options) {
5796
6120
  parse(source2) {
5797
6121
  const lexer = new Lexer(source2);
5798
6122
  const tokens = lexer.tokenize();
5799
- const parser = new Parser(tokens);
6123
+ const parser = new Parser(tokens, source2);
5800
6124
  return parser.parse();
5801
6125
  }
5802
6126
  };
@@ -5812,7 +6136,7 @@ async function compileWithInheritanceToCode(templateName, options) {
5812
6136
  function compileToCode(source, options = {}) {
5813
6137
  const lexer = new Lexer(source);
5814
6138
  const tokens = lexer.tokenize();
5815
- const parser = new Parser(tokens);
6139
+ const parser = new Parser(tokens, source);
5816
6140
  const ast = parser.parse();
5817
6141
  return compileToString(ast, options);
5818
6142
  }
@@ -5832,6 +6156,9 @@ export {
5832
6156
  builtinTests,
5833
6157
  builtinFilters,
5834
6158
  TokenType,
6159
+ TemplateSyntaxError,
6160
+ TemplateRuntimeError,
6161
+ TemplateError,
5835
6162
  Template,
5836
6163
  Runtime,
5837
6164
  Parser,