kire 0.1.3 → 0.1.4

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/cjs/index.js CHANGED
@@ -230,20 +230,184 @@ function parseParamDefinition(def) {
230
230
  };
231
231
  }
232
232
 
233
+ // core/src/utils/source-map.ts
234
+ var BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
235
+ function encodeVLQ(value) {
236
+ let res = "";
237
+ let vlq = value < 0 ? -value << 1 | 1 : value << 1;
238
+ do {
239
+ let digit = vlq & 31;
240
+ vlq >>>= 5;
241
+ if (vlq > 0)
242
+ digit |= 32;
243
+ res += BASE64_CHARS[digit];
244
+ } while (vlq > 0);
245
+ return res;
246
+ }
247
+ function decodeVLQ(str, index) {
248
+ let result = 0;
249
+ let shift = 0;
250
+ let continuation = true;
251
+ let i = index;
252
+ while (continuation) {
253
+ const char = str[i++];
254
+ if (!char)
255
+ throw new Error("Invalid VLQ");
256
+ const digit = BASE64_CHARS.indexOf(char);
257
+ if (digit === -1)
258
+ throw new Error(`Invalid Base64 char: ${char}`);
259
+ continuation = (digit & 32) !== 0;
260
+ result += (digit & 31) << shift;
261
+ shift += 5;
262
+ }
263
+ const value = result & 1 ? -(result >>> 1) : result >>> 1;
264
+ return [value, i];
265
+ }
266
+
267
+ class SourceMapGenerator {
268
+ file;
269
+ mappings = [];
270
+ sources = [];
271
+ constructor(file) {
272
+ this.file = file;
273
+ }
274
+ addSource(source) {
275
+ const index = this.sources.indexOf(source);
276
+ if (index === -1) {
277
+ this.sources.push(source);
278
+ return this.sources.length - 1;
279
+ }
280
+ return index;
281
+ }
282
+ addMapping(mapping) {
283
+ this.mappings.push(mapping);
284
+ }
285
+ toString() {
286
+ let lastGenLine = 1;
287
+ let lastGenCol = 0;
288
+ let lastSourceIndex = 0;
289
+ let lastSourceLine = 0;
290
+ let lastSourceCol = 0;
291
+ const encodedMappings = [];
292
+ let lineMappings = [];
293
+ this.mappings.sort((a, b) => {
294
+ if (a.genLine !== b.genLine)
295
+ return a.genLine - b.genLine;
296
+ return a.genCol - b.genCol;
297
+ });
298
+ for (const m of this.mappings) {
299
+ while (m.genLine > lastGenLine) {
300
+ encodedMappings.push(lineMappings.join(","));
301
+ lineMappings = [];
302
+ lastGenLine++;
303
+ lastGenCol = 0;
304
+ }
305
+ let segment = "";
306
+ segment += encodeVLQ(m.genCol - lastGenCol);
307
+ lastGenCol = m.genCol;
308
+ if (m.sourceLine !== undefined) {
309
+ const sourceIndex = m.sourceIndex ?? 0;
310
+ segment += encodeVLQ(sourceIndex - lastSourceIndex);
311
+ lastSourceIndex = sourceIndex;
312
+ segment += encodeVLQ(m.sourceLine - 1 - lastSourceLine);
313
+ lastSourceLine = m.sourceLine - 1;
314
+ segment += encodeVLQ(m.sourceCol - 1 - lastSourceCol);
315
+ lastSourceCol = m.sourceCol - 1;
316
+ }
317
+ lineMappings.push(segment);
318
+ }
319
+ encodedMappings.push(lineMappings.join(","));
320
+ const map = {
321
+ version: 3,
322
+ file: this.file,
323
+ sources: this.sources,
324
+ names: [],
325
+ mappings: encodedMappings.join(";")
326
+ };
327
+ return JSON.stringify(map);
328
+ }
329
+ toDataUri() {
330
+ const base64 = Buffer.from(this.toString()).toString("base64");
331
+ return `data:application/json;charset=utf-8;base64,${base64}`;
332
+ }
333
+ }
334
+ function resolveSourceLocation(map, genLine, genCol) {
335
+ if (!map || !map.mappings)
336
+ return null;
337
+ const lines = map.mappings.split(";");
338
+ if (genLine > lines.length || genLine < 1)
339
+ return null;
340
+ let stateGenCol = 0;
341
+ let stateSourceIndex = 0;
342
+ let stateSourceLine = 0;
343
+ let stateSourceCol = 0;
344
+ let bestMatch = null;
345
+ for (let l = 0;l < genLine; l++) {
346
+ const line = lines[l];
347
+ stateGenCol = 0;
348
+ if (!line)
349
+ continue;
350
+ let i = 0;
351
+ while (i < line.length) {
352
+ const [dCol, nextI1] = decodeVLQ(line, i);
353
+ i = nextI1;
354
+ stateGenCol += dCol;
355
+ if (i >= line.length || line[i] === ",") {
356
+ if (l === genLine - 1 && stateGenCol <= genCol) {}
357
+ } else {
358
+ const [dSrcIdx, nextI2] = decodeVLQ(line, i);
359
+ i = nextI2;
360
+ stateSourceIndex += dSrcIdx;
361
+ const [dSrcLine, nextI3] = decodeVLQ(line, i);
362
+ i = nextI3;
363
+ stateSourceLine += dSrcLine;
364
+ const [dSrcCol, nextI4] = decodeVLQ(line, i);
365
+ i = nextI4;
366
+ stateSourceCol += dSrcCol;
367
+ if (i < line.length && line[i] !== ",") {
368
+ const [_, nextI5] = decodeVLQ(line, i);
369
+ i = nextI5;
370
+ }
371
+ if (l === genLine - 1) {
372
+ if (stateGenCol <= genCol) {
373
+ bestMatch = {
374
+ line: stateSourceLine + 1,
375
+ column: stateSourceCol + 1,
376
+ source: map.sources[stateSourceIndex] || ""
377
+ };
378
+ } else {
379
+ break;
380
+ }
381
+ }
382
+ }
383
+ if (i < line.length && line[i] === ",")
384
+ i++;
385
+ }
386
+ }
387
+ return bestMatch;
388
+ }
389
+
233
390
  // core/src/compiler.ts
234
391
  class Compiler {
235
392
  kire;
393
+ filename;
236
394
  preBuffer = [];
237
395
  resBuffer = [];
238
396
  posBuffer = [];
239
397
  usedDirectives = new Set;
240
- constructor(kire) {
398
+ generator;
399
+ resMappings = [];
400
+ constructor(kire, filename = "template.kire") {
241
401
  this.kire = kire;
402
+ this.filename = filename;
403
+ this.generator = new SourceMapGenerator(filename);
404
+ this.generator.addSource(filename);
242
405
  }
243
406
  async compile(nodes) {
244
407
  this.preBuffer = [];
245
408
  this.resBuffer = [];
246
409
  this.posBuffer = [];
410
+ this.resMappings = [];
247
411
  this.usedDirectives.clear();
248
412
  const varLocals = this.kire.$var_locals || "it";
249
413
  await this.compileNodes(nodes);
@@ -253,22 +417,53 @@ class Compiler {
253
417
  `);
254
418
  const pos = this.posBuffer.join(`
255
419
  `);
256
- const code = `
257
- with ($ctx.$globals) {
258
- let ${varLocals} = $ctx.$props;
420
+ const sanitizedKeys = Array.from(this.kire.$globals.keys()).filter((key) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key));
421
+ const destructuring = sanitizedKeys.length > 0 ? `var { ${sanitizedKeys.join(", ")} } = $ctx.$globals;` : "";
422
+ const startCode = `
423
+ ${destructuring}
424
+ let ${varLocals} = $ctx.$props;
425
+ ${pre}
426
+ `;
427
+ const startCodeLines = startCode.split(`
428
+ `);
429
+ let currentGenLine = startCodeLines.length;
430
+ if (!this.kire.production) {
431
+ const bufferLineOffsets = new Map;
432
+ for (let i = 0;i < this.resBuffer.length; i++) {
433
+ bufferLineOffsets.set(i, currentGenLine);
434
+ const entry = this.resBuffer[i];
435
+ currentGenLine += (entry.match(/\n/g) || []).length + 1;
436
+ }
437
+ for (const mapping of this.resMappings) {
438
+ const genLine = bufferLineOffsets.get(mapping.index);
439
+ if (genLine !== undefined) {
440
+ this.generator.addMapping({
441
+ genLine,
442
+ genCol: mapping.col,
443
+ sourceLine: mapping.node.loc.start.line,
444
+ sourceCol: mapping.node.loc.start.column
445
+ });
446
+ }
447
+ }
448
+ }
449
+ let code = `
450
+ ${destructuring}
451
+ let ${varLocals} = $ctx.$props;
259
452
  ${pre}
260
453
  ${res}
261
454
  ${pos}
262
- }
263
455
  return $ctx;
264
- //# sourceURL=kire-generated.js`;
456
+ //# sourceURL=${this.filename}`;
457
+ if (!this.kire.production) {
458
+ code += `
459
+ //# sourceMappingURL=${this.generator.toDataUri()}`;
460
+ }
265
461
  return code;
266
462
  }
267
463
  async compileNodes(nodes) {
268
464
  let i = 0;
269
465
  while (i < nodes.length) {
270
466
  const node = nodes[i];
271
- this.addSourceLine(node);
272
467
  switch (node.type) {
273
468
  case "text":
274
469
  this.compileText(node);
@@ -286,18 +481,31 @@ return $ctx;
286
481
  i++;
287
482
  }
288
483
  }
289
- addSourceLine(node) {
290
- if (node.loc) {
291
- this.resBuffer.push(`// kire-line: ${node.loc.start.line}`);
292
- }
293
- }
294
484
  compileText(node) {
295
485
  if (node.content) {
486
+ if (node.loc) {
487
+ const currentLine = node.loc.start.line;
488
+ this.resMappings.push({
489
+ index: this.resBuffer.length + 1,
490
+ node,
491
+ col: this.resBuffer[this.resBuffer.length - 1]?.length || 0
492
+ });
493
+ this.resBuffer.push(`// kire-line: ${currentLine}`);
494
+ }
296
495
  this.resBuffer.push(`$ctx.$add(${JSON.stringify(node.content)});`);
297
496
  }
298
497
  }
299
498
  compileVariable(node) {
300
499
  if (node.content) {
500
+ if (node.loc) {
501
+ const currentLine = node.loc.start.line;
502
+ this.resMappings.push({
503
+ index: this.resBuffer.length + 1,
504
+ node,
505
+ col: this.resBuffer[this.resBuffer.length - 1]?.length || 0
506
+ });
507
+ this.resBuffer.push(`// kire-line: ${currentLine}`);
508
+ }
301
509
  if (node.raw) {
302
510
  this.resBuffer.push(`$ctx.$add(${node.content});`);
303
511
  } else {
@@ -307,7 +515,30 @@ return $ctx;
307
515
  }
308
516
  compileJavascript(node) {
309
517
  if (node.content) {
310
- this.resBuffer.push(node.content);
518
+ const lines = node.content.split(`
519
+ `);
520
+ for (let i = 0;i < lines.length; i++) {
521
+ const lineContent = lines[i];
522
+ if (node.loc) {
523
+ const currentLine = node.loc.start.line + i;
524
+ this.resMappings.push({
525
+ index: this.resBuffer.length + 1,
526
+ node: {
527
+ ...node,
528
+ loc: {
529
+ start: {
530
+ line: currentLine,
531
+ column: i === 0 ? node.loc.start.column + 4 : 1
532
+ },
533
+ end: node.loc.end
534
+ }
535
+ },
536
+ col: 0
537
+ });
538
+ this.resBuffer.push(`// kire-line: ${currentLine}`);
539
+ }
540
+ this.resBuffer.push(lineContent);
541
+ }
311
542
  }
312
543
  }
313
544
  async processDirective(node) {
@@ -319,6 +550,8 @@ return $ctx;
319
550
  console.warn(`Directive @${name} not found.`);
320
551
  return;
321
552
  }
553
+ if (node.loc)
554
+ this.resMappings.push({ index: this.resBuffer.length, node, col: this.resBuffer[this.resBuffer.length - 1]?.length || 0 });
322
555
  const compiler = this.createCompilerContext(node, directive);
323
556
  await directive.onCall(compiler);
324
557
  }
@@ -1008,6 +1241,75 @@ var natives_default = (kire) => {
1008
1241
  compiler.raw(`}`);
1009
1242
  }
1010
1243
  });
1244
+ kire.directive({
1245
+ name: "csrf",
1246
+ type: "html",
1247
+ description: "Renders a CSRF token input field.",
1248
+ onCall(compiler) {
1249
+ compiler.raw(`
1250
+ if (typeof $ctx.$globals.csrf === 'undefined') {
1251
+ throw new Error("CSRF token not defined. Please define it using kire.$global('csrf', 'token')");
1252
+ }
1253
+ $ctx.$add(\`<input type="hidden" name="_token" value="\${$ctx.$globals.csrf}">\`);
1254
+ `);
1255
+ }
1256
+ });
1257
+ kire.directive({
1258
+ name: "method",
1259
+ params: ["method:string"],
1260
+ type: "html",
1261
+ description: "Spoofs an HTTP method using a hidden input.",
1262
+ onCall(compiler) {
1263
+ const method = compiler.param("method");
1264
+ compiler.res(`<input type="hidden" name="_method" value="${method}">`);
1265
+ }
1266
+ });
1267
+ kire.directive({
1268
+ name: "defer",
1269
+ children: true,
1270
+ type: "html",
1271
+ description: "Defers rendering of a block until the main content is loaded (Out-of-Order Streaming). Falls back to immediate rendering if streaming is disabled.",
1272
+ async onCall(compiler) {
1273
+ compiler.raw(`{`);
1274
+ compiler.raw(`const deferredRender = async ($ctx) => {`);
1275
+ if (compiler.children)
1276
+ await compiler.set(compiler.children);
1277
+ compiler.raw(`};`);
1278
+ compiler.raw(`if ($ctx.$kire.stream) {`);
1279
+ compiler.raw(` const deferId = 'defer-' + Math.random().toString(36).substr(2, 9);`);
1280
+ compiler.raw(` $ctx.$add(\`<div id="\${deferId}"></div>\`);`);
1281
+ compiler.raw(` if (!$ctx.$deferred) $ctx.$deferred = [];`);
1282
+ compiler.raw(` $ctx.$deferred.push(async () => {`);
1283
+ compiler.raw(` const $parentCtx = $ctx;`);
1284
+ compiler.raw(` {`);
1285
+ compiler.raw(` const $ctx = $parentCtx.$fork();`);
1286
+ compiler.raw(` let swapScript = '';`);
1287
+ compiler.raw(` await $ctx.$merge(deferredRender);`);
1288
+ compiler.raw(` const content = $ctx.$response;`);
1289
+ compiler.raw(` $ctx.$response = '';`);
1290
+ compiler.raw(` const templateId = 'tpl-' + deferId;`);
1291
+ compiler.raw(` swapScript = \`
1292
+ <template id="\${templateId}">\${content}</template>
1293
+ <script>
1294
+ (function() {
1295
+ var src = document.getElementById('\${templateId}');
1296
+ var dest = document.getElementById('\${deferId}');
1297
+ if (src && dest) {
1298
+ dest.replaceWith(src.content);
1299
+ src.remove();
1300
+ }
1301
+ })();
1302
+ </script>
1303
+ \`;`);
1304
+ compiler.raw(` $ctx.$add(swapScript);`);
1305
+ compiler.raw(` }`);
1306
+ compiler.raw(` });`);
1307
+ compiler.raw(`} else {`);
1308
+ compiler.raw(` await deferredRender($ctx);`);
1309
+ compiler.raw(`}`);
1310
+ compiler.raw(`}`);
1311
+ }
1312
+ });
1011
1313
  };
1012
1314
 
1013
1315
  // core/src/directives/index.ts
@@ -1022,6 +1324,7 @@ var KireDirectives = {
1022
1324
  const isProd = kire.production;
1023
1325
  const cachedHash = cached.get(`md5:${resolvedPath}`);
1024
1326
  let compiledFn = cached.get(`js:${resolvedPath}`);
1327
+ let sourceMap = cached.get(`map:${resolvedPath}`);
1025
1328
  let content = "";
1026
1329
  if (!cachedHash || !compiledFn || !isProd) {
1027
1330
  try {
@@ -1037,13 +1340,20 @@ var KireDirectives = {
1037
1340
  }
1038
1341
  const newHash = md5(content);
1039
1342
  if (cachedHash === newHash && compiledFn) {} else {
1040
- compiledFn = await kire.compileFn(content);
1343
+ compiledFn = await kire.compileFn(content, resolvedPath);
1344
+ sourceMap = compiledFn._map;
1041
1345
  cached.set(`md5:${resolvedPath}`, newHash);
1042
1346
  cached.set(`js:${resolvedPath}`, compiledFn);
1347
+ if (sourceMap) {
1348
+ cached.set(`map:${resolvedPath}`, sourceMap);
1349
+ }
1043
1350
  }
1044
1351
  }
1045
1352
  if (!compiledFn)
1046
1353
  return null;
1354
+ if (sourceMap && !compiledFn._map) {
1355
+ compiledFn._map = sourceMap;
1356
+ }
1047
1357
  return kire.run(compiledFn, locals, true, controller);
1048
1358
  });
1049
1359
  layout_default(kire);
@@ -1114,7 +1424,7 @@ class Parser {
1114
1424
  const inner = this.template.slice(this.cursor + 4, end);
1115
1425
  this.addNode({
1116
1426
  type: "javascript",
1117
- content: inner.trim(),
1427
+ content: inner,
1118
1428
  start: this.cursor,
1119
1429
  end: end + 2,
1120
1430
  loc: this.getLoc(content)
@@ -1561,6 +1871,78 @@ class Parser {
1561
1871
  }
1562
1872
  }
1563
1873
 
1874
+ // core/src/utils/error.ts
1875
+ class KireError extends Error {
1876
+ originalError;
1877
+ fileMeta;
1878
+ constructor(message, fileMeta) {
1879
+ const originalError = message instanceof Error ? message : new Error(message);
1880
+ super(originalError.message);
1881
+ this.name = "KireError";
1882
+ this.originalError = originalError;
1883
+ this.fileMeta = fileMeta;
1884
+ const originalStack = originalError.stack || "";
1885
+ this.stack = this.formatStack(originalStack);
1886
+ }
1887
+ formatStack(stack) {
1888
+ const lines = stack.split(`
1889
+ `);
1890
+ const messageLine = lines[0] || `${this.name}: ${this.message}`;
1891
+ const mappedLines = [];
1892
+ for (let i = 1;i < lines.length; i++) {
1893
+ const line = lines[i];
1894
+ const mappedLine = this.mapStackLine(line);
1895
+ mappedLines.push(mappedLine);
1896
+ }
1897
+ let finalMessage = messageLine;
1898
+ if (finalMessage.startsWith("Error:")) {
1899
+ finalMessage = `KireError:${finalMessage.slice(6)}`;
1900
+ } else if (!finalMessage.includes("KireError")) {
1901
+ finalMessage = `KireError: ${finalMessage}`;
1902
+ }
1903
+ return `${finalMessage}
1904
+ ${mappedLines.join(`
1905
+ `)}`;
1906
+ }
1907
+ mapStackLine(line) {
1908
+ const match = line.match(/^\s*at\s+(?:(.*?)\s+\()?(.+?):(\d+):(\d+)\)?$/);
1909
+ if (match) {
1910
+ const functionName = match[1];
1911
+ const filename = match[2];
1912
+ const genLine = parseInt(match[3]);
1913
+ const genCol = parseInt(match[4]);
1914
+ if (this.fileMeta) {
1915
+ const isTemplateFile = filename.includes(this.fileMeta.path) || filename.includes("template.kire") || filename.includes("<anonymous>") || filename.includes("eval") || filename.includes("AsyncFunction");
1916
+ if (isTemplateFile) {
1917
+ if (this.fileMeta.code) {
1918
+ const generatedLines = this.fileMeta.code.split(`
1919
+ `);
1920
+ const codeGenLine = genLine - 2;
1921
+ for (let offset = 0;offset >= -10; offset--) {
1922
+ const idx = codeGenLine + offset - 1;
1923
+ if (idx >= 0 && idx < generatedLines.length) {
1924
+ const gl = generatedLines[idx];
1925
+ if (gl && gl.trim().startsWith("// kire-line:")) {
1926
+ const sourceLine = parseInt(gl.split(":")[1].trim());
1927
+ const finalLine = sourceLine;
1928
+ return ` at ${functionName ? functionName + " " : ""}(${this.fileMeta.path}:${finalLine}:${genCol})`;
1929
+ }
1930
+ }
1931
+ }
1932
+ }
1933
+ if (this.fileMeta.map) {
1934
+ const resolved = resolveSourceLocation(this.fileMeta.map, genLine, genCol);
1935
+ if (resolved) {
1936
+ return ` at ${functionName ? functionName + " " : ""}(${resolved.source}:${resolved.line}:${resolved.column})`;
1937
+ }
1938
+ }
1939
+ }
1940
+ }
1941
+ }
1942
+ return line;
1943
+ }
1944
+ }
1945
+
1564
1946
  // core/src/utils/elements-runner.ts
1565
1947
  var processElements = async ($ctx) => {
1566
1948
  let resultHtml = $ctx.$response;
@@ -1635,7 +2017,7 @@ function escapeHtml(unsafe) {
1635
2017
  if (unsafe === null || unsafe === undefined)
1636
2018
  return "";
1637
2019
  const str = String(unsafe);
1638
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
2020
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;").replace(/`/g, "&#96;");
1639
2021
  }
1640
2022
 
1641
2023
  // core/src/runtime.ts
@@ -1656,6 +2038,7 @@ function runtime_default($kire, locals, meta) {
1656
2038
  $ctx.$hooks.set("before", []);
1657
2039
  $ctx.$hooks.set("after", []);
1658
2040
  $ctx.$hooks.set("end", []);
2041
+ $ctx.$deferred = [];
1659
2042
  $ctx.$on = (ev, cb) => {
1660
2043
  const hooks = $ctx.$hooks.get(ev);
1661
2044
  if (hooks) {
@@ -1692,6 +2075,24 @@ function runtime_default($kire, locals, meta) {
1692
2075
  await fn($ctx);
1693
2076
  $ctx.$response = $pres + $ctx.$response;
1694
2077
  };
2078
+ $ctx.$fork = () => {
2079
+ const newCtx = { ...$ctx };
2080
+ newCtx.$merge = async (fn) => {
2081
+ const originalAdd = newCtx.$add;
2082
+ let buffer = "";
2083
+ newCtx.$add = (str) => {
2084
+ buffer += str;
2085
+ newCtx.$response = (newCtx.$response || "") + str;
2086
+ };
2087
+ const prevRes = newCtx.$response;
2088
+ newCtx.$response = "";
2089
+ await fn(newCtx);
2090
+ newCtx.$add = originalAdd;
2091
+ newCtx.$response = prevRes + buffer;
2092
+ };
2093
+ newCtx.$fork = $ctx.$fork;
2094
+ return newCtx;
2095
+ };
1695
2096
  const execute = async () => {
1696
2097
  if ($ctx.$kire.$directives.size > 0) {
1697
2098
  for (const directive of $ctx.$kire.$directives.values()) {
@@ -1700,13 +2101,14 @@ function runtime_default($kire, locals, meta) {
1700
2101
  }
1701
2102
  }
1702
2103
  }
2104
+ await $ctx.$emit("before");
1703
2105
  try {
1704
2106
  await $ctx.$file.execute.call($ctx.$props, $ctx);
1705
2107
  } catch (e) {
1706
- console.error(e);
1707
- return $kire.renderError(e, $ctx);
2108
+ const kireError = new KireError(e, $ctx.$file);
2109
+ console.error(kireError.stack);
2110
+ return $kire.renderError(kireError, $ctx);
1708
2111
  }
1709
- await $ctx.$emit("before");
1710
2112
  await $ctx.$emit("after");
1711
2113
  await $ctx.$emit("end");
1712
2114
  let resultHtml = $ctx.$response;
@@ -1740,6 +2142,9 @@ function runtime_default($kire, locals, meta) {
1740
2142
  if (typeof result === "string" && result.includes("Kire Runtime Error")) {
1741
2143
  controller.enqueue(encoder.encode(result));
1742
2144
  }
2145
+ if ($ctx.$deferred && $ctx.$deferred.length > 0) {
2146
+ await Promise.all($ctx.$deferred.map((t) => t()));
2147
+ }
1743
2148
  controller.close();
1744
2149
  } catch (e) {
1745
2150
  controller.error(e);
@@ -1854,14 +2259,19 @@ function resolvePath(filepath, namespaces, locals = {}, extension = "kire") {
1854
2259
  let normalized = filepath.replace(/\\/g, "/");
1855
2260
  const sortedNamespaces = Array.from(namespaces.keys()).sort((a, b) => b.length - a.length);
1856
2261
  for (const ns of sortedNamespaces) {
1857
- if (normalized.startsWith(ns)) {
2262
+ if (normalized.startsWith(ns) || ns === "@") {
1858
2263
  let template = namespaces.get(ns);
1859
2264
  const data = { ...locals };
1860
2265
  template = template.replace(/\{(\w+)\}/g, (_, key) => {
1861
2266
  return data[key] !== undefined ? String(data[key]) : `{${key}}`;
1862
2267
  });
1863
2268
  template = template.replace(/\\/g, "/");
1864
- let suffix = normalized.slice(ns.length);
2269
+ let suffix = "";
2270
+ if (ns === "@") {
2271
+ suffix = normalized;
2272
+ } else {
2273
+ suffix = normalized.slice(ns.length);
2274
+ }
1865
2275
  if (suffix.startsWith(".") || suffix.startsWith("/")) {
1866
2276
  suffix = suffix.slice(1);
1867
2277
  }
@@ -1905,6 +2315,7 @@ return (${callbackSource}).apply(this, arguments);`);
1905
2315
  // core/src/kire.ts
1906
2316
  class Kire {
1907
2317
  $scoped = scoped;
2318
+ $error = KireError;
1908
2319
  $directives = new Map;
1909
2320
  $elements = new Set;
1910
2321
  $globals = new LayeredMap;
@@ -1989,6 +2400,7 @@ class Kire {
1989
2400
  for (const item of pluginsToLoad) {
1990
2401
  this.plugin(item.p, item.o);
1991
2402
  }
2403
+ this.$error.html = (e, ctx) => this.renderError(e, ctx);
1992
2404
  }
1993
2405
  namespace(name, path) {
1994
2406
  const unixPath = path.replace(/\\/g, "/");
@@ -1996,6 +2408,9 @@ class Kire {
1996
2408
  return this;
1997
2409
  }
1998
2410
  $prop(keyOrObj, value) {
2411
+ if (!this.$parent) {
2412
+ console.warn("Kire Warning: You are setting props on the global instance. This data will be shared across all requests. Use kire.fork() for per-request isolation.");
2413
+ }
1999
2414
  if (typeof keyOrObj === "string") {
2000
2415
  this.$props.set(keyOrObj, value);
2001
2416
  } else if (typeof keyOrObj === "object") {
@@ -2115,45 +2530,71 @@ class Kire {
2115
2530
  const parser = new this.$parser(template, this);
2116
2531
  return parser.parse();
2117
2532
  }
2118
- async compile(template) {
2533
+ async compile(template, filename) {
2119
2534
  const parser = new this.$parser(template, this);
2120
2535
  const nodes = parser.parse();
2121
- const compiler = new this.$compiler(this);
2536
+ const compiler = new this.$compiler(this, filename);
2122
2537
  return compiler.compile(nodes);
2123
2538
  }
2124
- async compileFn(content) {
2125
- const code = `${await this.compile(content)}`;
2539
+ async compileFn(content, filename = "template.kire") {
2540
+ const code = `${await this.compile(content, filename)}`;
2126
2541
  try {
2127
2542
  const mainFn = this.$executor(code, ["$ctx"]);
2128
2543
  mainFn._code = code;
2129
2544
  mainFn._source = content;
2545
+ mainFn._path = filename;
2546
+ const mapMatch = code.match(/\/\/# sourceMappingURL=data:application\/json;charset=utf-8;base64,(.*)/);
2547
+ if (mapMatch) {
2548
+ try {
2549
+ const json = Buffer.from(mapMatch[1], "base64").toString("utf8");
2550
+ mainFn._map = JSON.parse(json);
2551
+ } catch (e) {}
2552
+ }
2130
2553
  return mainFn;
2131
2554
  } catch (e) {
2132
2555
  console.error("Error creating function from code:", code);
2133
2556
  throw e;
2134
2557
  }
2135
2558
  }
2136
- async render(template, locals = {}, controller) {
2137
- const fn = await this.compileFn(template);
2559
+ async render(template, locals = {}, controller, filename = "template.kire") {
2560
+ if (!this.$parent) {
2561
+ console.warn("Kire Warning: You are rendering on the global instance. Use kire.fork() for per-request isolation.");
2562
+ }
2563
+ const fn = await this.compileFn(template, filename);
2138
2564
  return this.run(fn, locals, false, controller);
2139
2565
  }
2140
2566
  renderError(e, ctx) {
2141
2567
  let snippet = "";
2142
2568
  let location = "";
2143
2569
  if (ctx && ctx.$file && ctx.$file.code && e.stack) {
2144
- const match = e.stack.match(/kire-generated\.js:(\d+):(\d+)/) || e.stack.match(/eval:(\d+):(\d+)/) || e.stack.match(/<anonymous>:(\d+):(\d+)/);
2570
+ const safePath = ctx.$file.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2571
+ const match = e.stack.match(new RegExp(`${safePath}:(\\d+):(\\d+)`)) || e.stack.match(/kire-generated\.js:(\d+):(\d+)/) || e.stack.match(/eval:(\d+):(\d+)/) || e.stack.match(/<anonymous>:(\d+):(\d+)/);
2145
2572
  if (match) {
2146
2573
  const genLine = parseInt(match[1]) - 1;
2574
+ const genCol = parseInt(match[2]);
2147
2575
  const lines = ctx.$file.code.split(`
2148
2576
  `);
2149
2577
  let sourceLine = -1;
2150
- for (let i = genLine;i >= 0; i--) {
2151
- const line = lines[i];
2152
- if (line && line.trim().startsWith("// kire-line:")) {
2153
- sourceLine = parseInt(line.split(":")[1].trim()) - 1;
2154
- break;
2578
+ if (ctx.$file.map) {
2579
+ console.log("Resolving map for", genLine + 1, genCol);
2580
+ const resolved = resolveSourceLocation(ctx.$file.map, genLine + 1, genCol);
2581
+ console.log("Resolved:", resolved);
2582
+ if (resolved) {
2583
+ sourceLine = resolved.line - 1;
2584
+ }
2585
+ }
2586
+ if (sourceLine === -1) {
2587
+ for (let i = genLine;i >= 0; i--) {
2588
+ const line = lines[i];
2589
+ if (line && line.trim().startsWith("// kire-line:")) {
2590
+ sourceLine = parseInt(line.split(":")[1].trim()) - 1;
2591
+ break;
2592
+ }
2155
2593
  }
2156
2594
  }
2595
+ if (sourceLine === -1 && match[0].includes(ctx.$file.path)) {
2596
+ sourceLine = genLine;
2597
+ }
2157
2598
  if (sourceLine !== -1 && ctx.$file.source) {
2158
2599
  location = ` at ${ctx.$file.path}:${sourceLine + 1}`;
2159
2600
  const sourceLines = ctx.$file.source.split(`
@@ -2202,6 +2643,9 @@ class Kire {
2202
2643
  </html>`;
2203
2644
  }
2204
2645
  async view(path, locals = {}, controller) {
2646
+ if (!this.$parent) {
2647
+ console.warn("Kire Warning: You are rendering a view on the global instance. Use kire.fork() for per-request isolation.");
2648
+ }
2205
2649
  let ext = this.extension;
2206
2650
  if (path.endsWith(".md") || path.endsWith(".markdown")) {
2207
2651
  ext = null;
@@ -2215,7 +2659,7 @@ class Kire {
2215
2659
  compiled = this.$files.get(resolvedPath);
2216
2660
  } else {
2217
2661
  const content = await this.$resolver(resolvedPath);
2218
- compiled = await this.compileFn(content);
2662
+ compiled = await this.compileFn(content, resolvedPath);
2219
2663
  if (this.production) {
2220
2664
  this.$files.set(resolvedPath, compiled);
2221
2665
  }
@@ -2225,7 +2669,7 @@ class Kire {
2225
2669
  return this.run(compiled, locals, false, controller);
2226
2670
  }
2227
2671
  resolvePath(filepath, locals = {}, extension = this.extension) {
2228
- return resolvePath(filepath, this.$namespaces, { ...this.$props.toObject(), ...locals }, extension === null ? "" : extension);
2672
+ return resolvePath(filepath, this.$namespaces, { ...this.$globals.toObject(), ...this.$props.toObject(), ...locals }, extension === null ? "" : extension);
2229
2673
  }
2230
2674
  async run(mainFn, locals, children = false, controller) {
2231
2675
  return runtime_default(this, locals, {
@@ -2235,6 +2679,7 @@ class Kire {
2235
2679
  name: mainFn.name,
2236
2680
  source: mainFn._source,
2237
2681
  path: mainFn._path,
2682
+ map: mainFn._map,
2238
2683
  controller
2239
2684
  });
2240
2685
  }