typebars 1.0.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.
Files changed (87) hide show
  1. package/README.md +1582 -0
  2. package/dist/analyzer.d.ts +59 -0
  3. package/dist/analyzer.js +4 -0
  4. package/dist/analyzer.js.map +9 -0
  5. package/dist/chunk-1gm6cf0e.js +5 -0
  6. package/dist/chunk-1gm6cf0e.js.map +10 -0
  7. package/dist/chunk-1qwj7pjc.js +4 -0
  8. package/dist/chunk-1qwj7pjc.js.map +10 -0
  9. package/dist/chunk-4zv02svp.js +7 -0
  10. package/dist/chunk-4zv02svp.js.map +10 -0
  11. package/dist/chunk-60gk3q7z.js +5 -0
  12. package/dist/chunk-60gk3q7z.js.map +10 -0
  13. package/dist/chunk-6955jpr7.js +5 -0
  14. package/dist/chunk-6955jpr7.js.map +10 -0
  15. package/dist/chunk-6c0pw73w.js +7 -0
  16. package/dist/chunk-6c0pw73w.js.map +10 -0
  17. package/dist/chunk-7j6q1e3z.js +5 -0
  18. package/dist/chunk-7j6q1e3z.js.map +14 -0
  19. package/dist/chunk-8g0d6h85.js +5 -0
  20. package/dist/chunk-8g0d6h85.js.map +10 -0
  21. package/dist/chunk-9mg6qfrs.js +5 -0
  22. package/dist/chunk-9mg6qfrs.js.map +10 -0
  23. package/dist/chunk-a37yzqra.js +5 -0
  24. package/dist/chunk-a37yzqra.js.map +11 -0
  25. package/dist/chunk-awgj10qg.js +4 -0
  26. package/dist/chunk-awgj10qg.js.map +10 -0
  27. package/dist/chunk-fhvf5y4x.js +7 -0
  28. package/dist/chunk-fhvf5y4x.js.map +10 -0
  29. package/dist/chunk-ggdfaqhe.js +5 -0
  30. package/dist/chunk-ggdfaqhe.js.map +10 -0
  31. package/dist/chunk-hc1jnqaw.js +5 -0
  32. package/dist/chunk-hc1jnqaw.js.map +10 -0
  33. package/dist/chunk-kznb0bev.js +5 -0
  34. package/dist/chunk-kznb0bev.js.map +10 -0
  35. package/dist/chunk-mx8neh7q.js +5 -0
  36. package/dist/chunk-mx8neh7q.js.map +10 -0
  37. package/dist/chunk-p3xzf1ew.js +5 -0
  38. package/dist/chunk-p3xzf1ew.js.map +10 -0
  39. package/dist/chunk-qh2r1pa1.js +5 -0
  40. package/dist/chunk-qh2r1pa1.js.map +10 -0
  41. package/dist/chunk-qpzzr2rd.js +5 -0
  42. package/dist/chunk-qpzzr2rd.js.map +10 -0
  43. package/dist/chunk-vka4e61h.js +7 -0
  44. package/dist/chunk-vka4e61h.js.map +10 -0
  45. package/dist/chunk-xbvk4ygq.js +5 -0
  46. package/dist/chunk-xbvk4ygq.js.map +11 -0
  47. package/dist/chunk-ybh51hbe.js +7 -0
  48. package/dist/chunk-ybh51hbe.js.map +10 -0
  49. package/dist/chunk-yczpjh73.js +4 -0
  50. package/dist/chunk-yczpjh73.js.map +10 -0
  51. package/dist/chunk-yraqh2tz.js +5 -0
  52. package/dist/chunk-yraqh2tz.js.map +10 -0
  53. package/dist/chunk-z6yh5qvc.js +5 -0
  54. package/dist/chunk-z6yh5qvc.js.map +10 -0
  55. package/dist/compiled-template.d.ts +130 -0
  56. package/dist/compiled-template.js +4 -0
  57. package/dist/compiled-template.js.map +9 -0
  58. package/dist/errors.d.ts +93 -0
  59. package/dist/errors.js +4 -0
  60. package/dist/errors.js.map +9 -0
  61. package/dist/executor.d.ts +55 -0
  62. package/dist/executor.js +4 -0
  63. package/dist/executor.js.map +9 -0
  64. package/dist/helpers/helper-factory.d.ts +56 -0
  65. package/dist/helpers/index.d.ts +4 -0
  66. package/dist/helpers/logical-helpers.d.ts +15 -0
  67. package/dist/helpers/math-helpers.d.ts +13 -0
  68. package/dist/helpers/utils.d.ts +19 -0
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.js +4 -0
  71. package/dist/index.js.map +9 -0
  72. package/dist/parser.d.ts +146 -0
  73. package/dist/parser.js +4 -0
  74. package/dist/parser.js.map +9 -0
  75. package/dist/schema-resolver.d.ts +50 -0
  76. package/dist/schema-resolver.js +4 -0
  77. package/dist/schema-resolver.js.map +9 -0
  78. package/dist/typebars.d.ts +130 -0
  79. package/dist/typebars.js +4 -0
  80. package/dist/typebars.js.map +9 -0
  81. package/dist/types.d.ts +334 -0
  82. package/dist/types.js +4 -0
  83. package/dist/types.js.map +9 -0
  84. package/dist/utils.d.ts +113 -0
  85. package/dist/utils.js +4 -0
  86. package/dist/utils.js.map +9 -0
  87. package/package.json +42 -0
@@ -0,0 +1,59 @@
1
+ import type { JSONSchema7 } from "json-schema";
2
+ import type { AnalysisResult, HelperDefinition, TemplateDiagnostic, TemplateInput } from "./types.ts";
3
+ /** Context passed recursively during AST traversal */
4
+ interface AnalysisContext {
5
+ /** Root schema (for resolving $refs) */
6
+ root: JSONSchema7;
7
+ /** Current context schema (changes with #each, #with) — mutated via save/restore */
8
+ current: JSONSchema7;
9
+ /** Diagnostics accumulator */
10
+ diagnostics: TemplateDiagnostic[];
11
+ /** Full template source (for extracting error snippets) */
12
+ template: string;
13
+ /** Schemas by template identifier (for the {{key:N}} syntax) */
14
+ identifierSchemas?: Record<number, JSONSchema7>;
15
+ /** Registered custom helpers (for static analysis) */
16
+ helpers?: Map<string, HelperDefinition>;
17
+ }
18
+ /**
19
+ * Statically analyzes a template against a JSON Schema v7 describing the
20
+ * available context.
21
+ *
22
+ * Backward-compatible version — parses the template internally.
23
+ *
24
+ * @param template - The template string (e.g. `"Hello {{user.name}}"`)
25
+ * @param inputSchema - JSON Schema v7 describing the available variables
26
+ * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`
27
+ * @returns An `AnalysisResult` containing validity, diagnostics, and the
28
+ * inferred output schema.
29
+ */
30
+ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
31
+ /**
32
+ * Statically analyzes a template from an already-parsed AST.
33
+ *
34
+ * This is the internal function used by `Typebars.compile()` and
35
+ * `CompiledTemplate.analyze()` to avoid costly re-parsing.
36
+ *
37
+ * @param ast - The already-parsed Handlebars AST
38
+ * @param template - The template source (for error snippets)
39
+ * @param inputSchema - JSON Schema v7 describing the available variables
40
+ * @param options - Additional options
41
+ * @returns An `AnalysisResult`
42
+ */
43
+ export declare function analyzeFromAst(ast: hbs.AST.Program, template: string, inputSchema: JSONSchema7, options?: {
44
+ identifierSchemas?: Record<number, JSONSchema7>;
45
+ helpers?: Map<string, HelperDefinition>;
46
+ }): AnalysisResult;
47
+ /**
48
+ * Infers the output type of a BlockStatement and validates its content.
49
+ *
50
+ * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom
51
+ * helpers registered via `Typebars.registerHelper()`.
52
+ *
53
+ * Uses the **save/restore** pattern for context: instead of creating a new
54
+ * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,
55
+ * mutate it, process the body, then restore. This reduces GC pressure for
56
+ * deeply nested templates.
57
+ */
58
+ declare function inferBlockType(stmt: hbs.AST.BlockStatement, ctx: AnalysisContext): JSONSchema7;
59
+ export { inferBlockType };
@@ -0,0 +1,4 @@
1
+ import{c as a,d as b,e as c}from"./chunk-1qwj7pjc.js";import"./chunk-6955jpr7.js";import"./chunk-1gm6cf0e.js";import"./chunk-ybh51hbe.js";import"./chunk-8g0d6h85.js";import"./chunk-4zv02svp.js";export{c as inferBlockType,b as analyzeFromAst,a as analyze};
2
+
3
+ //# debugId=618AD80F85BF812A64756E2164756E21
4
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [
5
+ ],
6
+ "mappings": "",
7
+ "debugId": "618AD80F85BF812A64756E2164756E21",
8
+ "names": []
9
+ }
@@ -0,0 +1,5 @@
1
+ import{A as J}from"./chunk-ybh51hbe.js";import Q from"handlebars";var V=/^(.+):(\d+)$/,W=/^-?\d+(\.\d+)?$/;function j(k){try{return Q.parse(k)}catch(q){let z=q instanceof Error?q.message:String(q),G=z.match(/line\s+(\d+).*?column\s+(\d+)/i),O=G?{line:parseInt(G[1]??"0",10),column:parseInt(G[2]??"0",10)}:void 0;throw new J(z,O)}}function C(k){let{body:q}=k;return q.length===1&&q[0]?.type==="MustacheStatement"}function D(k){if(k.type==="PathExpression")return k.parts;return[]}function F(k){if(k.type!=="PathExpression")return!1;let q=k;return q.original==="this"||q.original==="."}function K(k){return k.body.filter((q)=>!(q.type==="ContentStatement"&&q.value.trim()===""))}function P(k){let q=K(k);if(q.length===1&&q[0]?.type==="BlockStatement")return q[0];return null}function U(k){let q=K(k);if(q.length===1&&q[0]?.type==="MustacheStatement")return q[0];return null}function A(k){return k.body.every((q)=>q.type==="ContentStatement"||q.type==="MustacheStatement"&&q.params.length===0&&!q.hash)}function X(k){if(W.test(k))return"number";if(k==="true"||k==="false")return"boolean";if(k==="null")return"null";return null}function B(k){let q=k.trim(),z=X(q);if(z==="number")return Number(q);if(z==="boolean")return q==="true";if(z==="null")return null;return k}function Y(k){let q=k.match(V);if(q)return{key:q[1]??k,identifier:parseInt(q[2]??"0",10)};return{key:k,identifier:null}}function H(k){if(k.length===0)return{cleanSegments:[],identifier:null};let q=k[k.length-1],z=Y(q);if(z.identifier!==null)return{cleanSegments:[...k.slice(0,-1),z.key],identifier:z.identifier};return{cleanSegments:k,identifier:null}}
2
+ export{j as n,C as o,D as p,F as q,K as r,P as s,U as t,A as u,X as v,B as w,Y as x,H as y};
3
+
4
+ //# debugId=FFAC2924EA9D94B364756E2164756E21
5
+ //# sourceMappingURL=chunk-1gm6cf0e.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/parser.ts"],
4
+ "sourcesContent": [
5
+ "import Handlebars from \"handlebars\";\nimport { TemplateParseError } from \"./errors.ts\";\n\n// ─── Regex for detecting a template identifier (e.g. \"meetingId:1\") ──────────\n// The identifier is always a positive integer or zero, separated from the\n// variable name by a `:`. The `:` and number are on the **last** segment\n// of the path (Handlebars splits on `.`).\nconst IDENTIFIER_RE = /^(.+):(\\d+)$/;\n\n// ─── Template Parser ─────────────────────────────────────────────────────────\n// Thin wrapper around the Handlebars parser. Centralizing the parser call\n// here allows us to:\n// 1. Wrap errors into our own hierarchy (`TemplateParseError`)\n// 2. Expose AST introspection helpers (e.g. `isSingleExpression`)\n// 3. Isolate the direct Handlebars dependency from the rest of the codebase\n//\n// AST caching is handled at the `Typebars` instance level (via its own\n// configurable LRU cache), not here. This module only parses and wraps errors.\n\n// ─── Regex for detecting a numeric literal (integer or decimal, signed) ──────\n// Intentionally conservative: no scientific notation (1e5), no hex (0xFF),\n// no separators (1_000). We only want to recognize what a human would write\n// as a numeric value in a template.\nconst NUMERIC_LITERAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Parses a template string and returns the Handlebars AST.\n *\n * This function does not cache results — caching is managed at the\n * `Typebars` instance level via its own configurable LRU cache.\n *\n * @param template - The template string to parse (e.g. `\"Hello {{name}}\"`)\n * @returns The root AST node (`hbs.AST.Program`)\n * @throws {TemplateParseError} if the template syntax is invalid\n */\nexport function parse(template: string): hbs.AST.Program {\n\ttry {\n\t\treturn Handlebars.parse(template);\n\t} catch (error: unknown) {\n\t\t// Handlebars throws a plain Error with a descriptive message.\n\t\t// We transform it into a TemplateParseError for uniform handling.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t// Handlebars sometimes includes the position in the message —\n\t\t// attempt to extract it to enrich our error.\n\t\tconst locMatch = message.match(/line\\s+(\\d+).*?column\\s+(\\d+)/i);\n\t\tconst loc = locMatch\n\t\t\t? {\n\t\t\t\t\tline: parseInt(locMatch[1] ?? \"0\", 10),\n\t\t\t\t\tcolumn: parseInt(locMatch[2] ?? \"0\", 10),\n\t\t\t\t}\n\t\t\t: undefined;\n\n\t\tthrow new TemplateParseError(message, loc);\n\t}\n}\n\n/**\n * Determines whether the AST represents a template consisting of a single\n * expression `{{expression}}` with no text content around it.\n *\n * This matters for return type inference:\n * - Template `{{value}}` → returns the raw type of `value` (number, object…)\n * - Template `Hello {{name}}` → always returns `string` (concatenation)\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template is a single expression\n */\nexport function isSingleExpression(ast: hbs.AST.Program): boolean {\n\tconst { body } = ast;\n\n\t// Exactly one node, and it's a MustacheStatement (not a block, not text)\n\treturn body.length === 1 && body[0]?.type === \"MustacheStatement\";\n}\n\n/**\n * Extracts the path segments from a Handlebars `PathExpression`.\n *\n * Handlebars decomposes `user.address.city` into `{ parts: [\"user\", \"address\", \"city\"] }`.\n * This function safely extracts those segments.\n *\n * @param expr - The expression to extract the path from\n * @returns The path segments, or an empty array if the expression is not\n * a `PathExpression`\n */\nexport function extractPathSegments(expr: hbs.AST.Expression): string[] {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).parts;\n\t}\n\treturn [];\n}\n\n/**\n * Checks whether an AST expression is a `PathExpression` pointing to `this`\n * (used inside `{{#each}}` blocks).\n */\nexport function isThisExpression(expr: hbs.AST.Expression): boolean {\n\tif (expr.type !== \"PathExpression\") return false;\n\tconst path = expr as hbs.AST.PathExpression;\n\treturn path.original === \"this\" || path.original === \".\";\n}\n\n// ─── Filtering Semantically Significant Nodes ───────────────────────────────\n// In a Handlebars AST, formatting (newlines, indentation) produces\n// `ContentStatement` nodes whose value is purely whitespace. These nodes\n// have no semantic impact and must be ignored during type inference to\n// correctly detect \"effectively a single block\" or \"effectively a single\n// expression\" cases.\n\n/**\n * Returns the semantically significant statements of a Program by\n * filtering out `ContentStatement` nodes that contain only whitespace.\n */\nexport function getEffectiveBody(\n\tprogram: hbs.AST.Program,\n): hbs.AST.Statement[] {\n\treturn program.body.filter(\n\t\t(s) =>\n\t\t\t!(\n\t\t\t\ts.type === \"ContentStatement\" &&\n\t\t\t\t(s as hbs.AST.ContentStatement).value.trim() === \"\"\n\t\t\t),\n\t);\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `BlockStatement` (ignoring surrounding whitespace).\n *\n * Recognized examples:\n * ```\n * {{#if x}}...{{/if}}\n *\n * {{#each items}}...{{/each}}\n * ```\n *\n * @returns The single `BlockStatement`, or `null` if the program contains\n * other significant nodes.\n */\nexport function getEffectivelySingleBlock(\n\tprogram: hbs.AST.Program,\n): hbs.AST.BlockStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"BlockStatement\") {\n\t\treturn effective[0] as hbs.AST.BlockStatement;\n\t}\n\treturn null;\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `MustacheStatement` (ignoring surrounding whitespace).\n *\n * Example: ` {{age}} ` → true\n */\nexport function getEffectivelySingleExpression(\n\tprogram: hbs.AST.Program,\n): hbs.AST.MustacheStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"MustacheStatement\") {\n\t\treturn effective[0] as hbs.AST.MustacheStatement;\n\t}\n\treturn null;\n}\n\n// ─── Fast-Path Detection ─────────────────────────────────────────────────────\n// For templates consisting only of text and simple expressions (no blocks,\n// no helpers with parameters), we can bypass Handlebars entirely and perform\n// a simple variable replacement via string concatenation.\n\n/**\n * Determines whether an AST can be executed via the fast-path (direct\n * concatenation without going through `Handlebars.compile()`).\n *\n * The fast-path is possible when the template only contains:\n * - `ContentStatement` nodes (static text)\n * - Simple `MustacheStatement` nodes (no params, no hash)\n *\n * This excludes:\n * - Block helpers (`{{#if}}`, `{{#each}}`, etc.)\n * - Inline helpers (`{{uppercase name}}`)\n * - Sub-expressions\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template can use the fast-path\n */\nexport function canUseFastPath(ast: hbs.AST.Program): boolean {\n\treturn ast.body.every(\n\t\t(s) =>\n\t\t\ts.type === \"ContentStatement\" ||\n\t\t\t(s.type === \"MustacheStatement\" &&\n\t\t\t\t(s as hbs.AST.MustacheStatement).params.length === 0 &&\n\t\t\t\t!(s as hbs.AST.MustacheStatement).hash),\n\t);\n}\n\n// ─── Literal Detection in Text Content ───────────────────────────────────────\n// When a program contains only ContentStatements (no expressions), we try\n// to detect whether the concatenated and trimmed text is a typed literal\n// (number, boolean, null). This enables correct type inference for branches\n// like `{{#if x}} 42 {{/if}}`.\n\n/**\n * Attempts to detect the type of a raw text literal.\n *\n * @param text - The trimmed text from a ContentStatement or group of ContentStatements\n * @returns The detected JSON Schema type, or `null` if it's free-form text (string).\n */\nexport function detectLiteralType(\n\ttext: string,\n): \"number\" | \"boolean\" | \"null\" | null {\n\tif (NUMERIC_LITERAL_RE.test(text)) return \"number\";\n\tif (text === \"true\" || text === \"false\") return \"boolean\";\n\tif (text === \"null\") return \"null\";\n\treturn null;\n}\n\n/**\n * Coerces a raw string from Handlebars rendering to its actual type\n * if it represents a literal (number, boolean, null).\n * Returns the raw (untrimmed) string otherwise.\n */\nexport function coerceLiteral(raw: string): unknown {\n\tconst trimmed = raw.trim();\n\tconst type = detectLiteralType(trimmed);\n\tif (type === \"number\") return Number(trimmed);\n\tif (type === \"boolean\") return trimmed === \"true\";\n\tif (type === \"null\") return null;\n\t// Not a typed literal — return the raw string without trimming,\n\t// as whitespace may be significant (e.g. output of an #each block).\n\treturn raw;\n}\n\n// ─── Template Identifier Parsing ─────────────────────────────────────────────\n// Syntax `{{key:N}}` where N is a positive integer or zero.\n// The identifier allows resolving a variable from a specific data source\n// (e.g. a workflow node identified by its number).\n\n/** Result of parsing a path segment with a potential identifier */\nexport interface ParsedIdentifier {\n\t/** The variable name, without the `:N` suffix */\n\tkey: string;\n\t/** The numeric identifier, or `null` if absent */\n\tidentifier: number | null;\n}\n\n/**\n * Parses an individual path segment to extract the key and optional identifier.\n *\n * @param segment - A raw path segment (e.g. `\"meetingId:1\"` or `\"meetingId\"`)\n * @returns An object `{ key, identifier }`\n *\n * @example\n * ```\n * parseIdentifier(\"meetingId:1\") // → { key: \"meetingId\", identifier: 1 }\n * parseIdentifier(\"meetingId\") // → { key: \"meetingId\", identifier: null }\n * parseIdentifier(\"meetingId:0\") // → { key: \"meetingId\", identifier: 0 }\n * ```\n */\nexport function parseIdentifier(segment: string): ParsedIdentifier {\n\tconst match = segment.match(IDENTIFIER_RE);\n\tif (match) {\n\t\treturn {\n\t\t\tkey: match[1] ?? segment,\n\t\t\tidentifier: parseInt(match[2] ?? \"0\", 10),\n\t\t};\n\t}\n\treturn { key: segment, identifier: null };\n}\n\n/** Result of extracting the identifier from a complete expression */\nexport interface ExpressionIdentifier {\n\t/** Cleaned path segments (without the `:N` suffix on the last one) */\n\tcleanSegments: string[];\n\t/** The numeric identifier extracted from the last segment, or `null` */\n\tidentifier: number | null;\n}\n\n/**\n * Extracts the identifier from a complete expression (array of segments).\n *\n * The identifier is always on the **last** segment of the path, because\n * Handlebars splits on `.` before the `:`.\n *\n * @param segments - The raw path segments (e.g. `[\"user\", \"name:1\"]`)\n * @returns An object `{ cleanSegments, identifier }`\n *\n * @example\n * ```\n * extractExpressionIdentifier([\"meetingId:1\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"user\", \"name:1\"])\n * // → { cleanSegments: [\"user\", \"name\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"meetingId\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: null }\n * ```\n */\nexport function extractExpressionIdentifier(\n\tsegments: string[],\n): ExpressionIdentifier {\n\tif (segments.length === 0) {\n\t\treturn { cleanSegments: [], identifier: null };\n\t}\n\n\tconst lastSegment = segments[segments.length - 1] as string;\n\tconst parsed = parseIdentifier(lastSegment);\n\n\tif (parsed.identifier !== null) {\n\t\tconst cleanSegments = [...segments.slice(0, -1), parsed.key];\n\t\treturn { cleanSegments, identifier: parsed.identifier };\n\t}\n\n\treturn { cleanSegments: segments, identifier: null };\n}\n"
6
+ ],
7
+ "mappings": "wCAAA,0BAOA,IAAM,EAAgB,eAgBhB,EAAqB,kBAYpB,SAAS,CAAK,CAAC,EAAmC,CACxD,GAAI,CACH,OAAO,EAAW,MAAM,CAAQ,EAC/B,MAAO,EAAgB,CAGxB,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAI/D,EAAW,EAAQ,MAAM,gCAAgC,EACzD,EAAM,EACT,CACA,KAAM,SAAS,EAAS,IAAM,IAAK,EAAE,EACrC,OAAQ,SAAS,EAAS,IAAM,IAAK,EAAE,CACxC,EACC,OAEH,MAAM,IAAI,EAAmB,EAAS,CAAG,GAepC,SAAS,CAAkB,CAAC,EAA+B,CACjE,IAAQ,QAAS,EAGjB,OAAO,EAAK,SAAW,GAAK,EAAK,IAAI,OAAS,oBAaxC,SAAS,CAAmB,CAAC,EAAoC,CACvE,GAAI,EAAK,OAAS,iBACjB,OAAQ,EAAgC,MAEzC,MAAO,CAAC,EAOF,SAAS,CAAgB,CAAC,EAAmC,CACnE,GAAI,EAAK,OAAS,iBAAkB,MAAO,GAC3C,IAAM,EAAO,EACb,OAAO,EAAK,WAAa,QAAU,EAAK,WAAa,IAc/C,SAAS,CAAgB,CAC/B,EACsB,CACtB,OAAO,EAAQ,KAAK,OACnB,CAAC,IACA,EACC,EAAE,OAAS,oBACV,EAA+B,MAAM,KAAK,IAAM,GAEpD,EAiBM,SAAS,CAAyB,CACxC,EACgC,CAChC,IAAM,EAAY,EAAiB,CAAO,EAC1C,GAAI,EAAU,SAAW,GAAK,EAAU,IAAI,OAAS,iBACpD,OAAO,EAAU,GAElB,OAAO,KASD,SAAS,CAA8B,CAC7C,EACmC,CACnC,IAAM,EAAY,EAAiB,CAAO,EAC1C,GAAI,EAAU,SAAW,GAAK,EAAU,IAAI,OAAS,oBACpD,OAAO,EAAU,GAElB,OAAO,KAwBD,SAAS,CAAc,CAAC,EAA+B,CAC7D,OAAO,EAAI,KAAK,MACf,CAAC,IACA,EAAE,OAAS,oBACV,EAAE,OAAS,qBACV,EAAgC,OAAO,SAAW,GACnD,CAAE,EAAgC,IACrC,EAeM,SAAS,CAAiB,CAChC,EACuC,CACvC,GAAI,EAAmB,KAAK,CAAI,EAAG,MAAO,SAC1C,GAAI,IAAS,QAAU,IAAS,QAAS,MAAO,UAChD,GAAI,IAAS,OAAQ,MAAO,OAC5B,OAAO,KAQD,SAAS,CAAa,CAAC,EAAsB,CACnD,IAAM,EAAU,EAAI,KAAK,EACnB,EAAO,EAAkB,CAAO,EACtC,GAAI,IAAS,SAAU,OAAO,OAAO,CAAO,EAC5C,GAAI,IAAS,UAAW,OAAO,IAAY,OAC3C,GAAI,IAAS,OAAQ,OAAO,KAG5B,OAAO,EA6BD,SAAS,CAAe,CAAC,EAAmC,CAClE,IAAM,EAAQ,EAAQ,MAAM,CAAa,EACzC,GAAI,EACH,MAAO,CACN,IAAK,EAAM,IAAM,EACjB,WAAY,SAAS,EAAM,IAAM,IAAK,EAAE,CACzC,EAED,MAAO,CAAE,IAAK,EAAS,WAAY,IAAK,EAgClC,SAAS,CAA2B,CAC1C,EACuB,CACvB,GAAI,EAAS,SAAW,EACvB,MAAO,CAAE,cAAe,CAAC,EAAG,WAAY,IAAK,EAG9C,IAAM,EAAc,EAAS,EAAS,OAAS,GACzC,EAAS,EAAgB,CAAW,EAE1C,GAAI,EAAO,aAAe,KAEzB,MAAO,CAAE,cADa,CAAC,GAAG,EAAS,MAAM,EAAG,EAAE,EAAG,EAAO,GAAG,EACnC,WAAY,EAAO,UAAW,EAGvD,MAAO,CAAE,cAAe,EAAU,WAAY,IAAK",
8
+ "debugId": "FFAC2924EA9D94B364756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,4 @@
1
+ import{j as g,k as v,l as f}from"./chunk-6955jpr7.js";import{n as k,p as q,q as C,r as D,s as W,t as E,v as T,y as P}from"./chunk-1gm6cf0e.js";import{D as $,E as B,F as U,G as M,H as b}from"./chunk-ybh51hbe.js";import{J as _,K as S,L as V}from"./chunk-8g0d6h85.js";import{M as y,O as u,P as Z,Q as N}from"./chunk-4zv02svp.js";function l(j,w,A){if(v(j))return i(j,w,A);if(g(j))return{valid:!0,diagnostics:[],outputSchema:f(j)};let F=k(j);return p(F,j,w,{identifierSchemas:A})}function i(j,w,A){return N(Object.keys(j),(F)=>l(j[F],w,A))}function p(j,w,A,F){let z={root:A,current:A,diagnostics:[],template:w,identifierSchemas:F?.identifierSchemas,helpers:F?.helpers},H=J(j,z);return{valid:!z.diagnostics.some((G)=>G.severity==="error"),diagnostics:z.diagnostics,outputSchema:V(H)}}function a(j,w){switch(j.type){case"ContentStatement":case"CommentStatement":return;case"MustacheStatement":return h(j,w);case"BlockStatement":return Y(j,w);default:K(w,"UNANALYZABLE","warning",`Unsupported AST node type: "${j.type}"`,j);return}}function h(j,w){if(j.path.type==="SubExpression")return K(w,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",j),{};if(j.params.length>0||j.hash){let A=d(j.path),F=w.helpers?.get(A);if(F){let z=F.params;if(z){let H=z.filter((I)=>!I.optional).length;if(j.params.length<H)K(w,"MISSING_ARGUMENT","error",`Helper "${A}" expects at least ${H} argument(s), but got ${j.params.length}`,j,{helperName:A,expected:`${H} argument(s)`,actual:`${j.params.length} argument(s)`})}for(let H=0;H<j.params.length;H++){let I=R(j.params[H],w,j),G=z?.[H];if(I&&G?.type){let L=G.type;if(!o(I,L)){let O=G.name;K(w,"TYPE_MISMATCH","error",`Helper "${A}" parameter "${O}" expects ${Q(L)}, but got ${Q(I)}`,j,{helperName:A,expected:Q(L),actual:Q(I)})}}}return F.returnType??{type:"string"}}return K(w,"UNKNOWN_HELPER","warning",`Unknown inline helper "${A}" — cannot analyze statically`,j,{helperName:A}),{type:"string"}}return R(j.path,w,j)??{}}function o(j,w){if(!w.type||!j.type)return!0;let A=Array.isArray(w.type)?w.type:[w.type];return(Array.isArray(j.type)?j.type:[j.type]).some((z)=>A.some((H)=>z===H||H==="number"&&z==="integer"||H==="integer"&&z==="number"))}function J(j,w){let A=D(j);if(A.length===0)return{type:"string"};let F=E(j);if(F)return h(F,w);let z=W(j);if(z)return Y(z,w);if(A.every((G)=>G.type==="ContentStatement")){let G=A.map((O)=>O.value).join("").trim();if(G==="")return{type:"string"};let L=T(G);if(L)return{type:L}}if(A.every((G)=>G.type==="BlockStatement")){let G=[];for(let L of A){let O=Y(L,w);if(O)G.push(O)}if(G.length===1)return G[0];if(G.length>1)return V({oneOf:G});return{type:"string"}}for(let G of j.body)a(G,w);return{type:"string"}}function Y(j,w){let A=m(j);switch(A){case"if":case"unless":{let F=X(j);if(F)R(F,w,j);else K(w,"MISSING_ARGUMENT","error",U(A),j,{helperName:A});let z=J(j.program,w);if(j.inverse){let H=J(j.inverse,w);if(y(z,H))return z;return V({oneOf:[z,H]})}return z}case"each":{let F=X(j);if(!F){K(w,"MISSING_ARGUMENT","error",U("each"),j,{helperName:"each"});let G=w.current;if(w.current={},J(j.program,w),w.current=G,j.inverse)J(j.inverse,w);return{type:"string"}}let z=R(F,w,j);if(!z){let G=w.current;if(w.current={},J(j.program,w),w.current=G,j.inverse)J(j.inverse,w);return{type:"string"}}let H=S(z,w.root);if(!H){K(w,"TYPE_MISMATCH","error",B("each","an array",Q(z)),j,{helperName:"each",expected:"array",actual:Q(z)});let G=w.current;if(w.current={},J(j.program,w),w.current=G,j.inverse)J(j.inverse,w);return{type:"string"}}let I=w.current;if(w.current=H,J(j.program,w),w.current=I,j.inverse)J(j.inverse,w);return{type:"string"}}case"with":{let F=X(j);if(!F){K(w,"MISSING_ARGUMENT","error",U("with"),j,{helperName:"with"});let G=w.current;w.current={};let L=J(j.program,w);if(w.current=G,j.inverse)J(j.inverse,w);return L}let z=R(F,w,j),H=w.current;w.current=z??{};let I=J(j.program,w);if(w.current=H,j.inverse)J(j.inverse,w);return I}default:{let F=w.helpers?.get(A);if(F){for(let z of j.params)R(z,w,j);if(J(j.program,w),j.inverse)J(j.inverse,w);return F.returnType??{type:"string"}}if(K(w,"UNKNOWN_HELPER","warning",M(A),j,{helperName:A}),J(j.program,w),j.inverse)J(j.inverse,w);return{type:"string"}}}}function R(j,w,A){if(C(j))return w.current;if(j.type==="SubExpression")return r(j,w,A);let F=q(j);if(F.length===0){if(j.type==="StringLiteral")return{type:"string"};if(j.type==="NumberLiteral")return{type:"number"};if(j.type==="BooleanLiteral")return{type:"boolean"};if(j.type==="NullLiteral")return{type:"null"};if(j.type==="UndefinedLiteral")return{};K(w,"UNANALYZABLE","warning",b(j.type),A??j);return}let{cleanSegments:z,identifier:H}=P(F);if(H!==null)return x(z,H,w,A??j);let I=_(w.current,z);if(I===void 0){let G=z.join("."),L=Z(w.current);K(w,"UNKNOWN_PROPERTY","error",$(G,L),A??j,{path:G,availableProperties:L});return}return I}function x(j,w,A,F){let z=j.join(".");if(!A.identifierSchemas){K(A,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${z}:${w}" uses an identifier but no identifier schemas were provided`,F,{path:`${z}:${w}`,identifier:w});return}let H=A.identifierSchemas[w];if(!H){K(A,"UNKNOWN_IDENTIFIER","error",`Property "${z}:${w}" references identifier ${w} but no schema exists for this identifier`,F,{path:`${z}:${w}`,identifier:w});return}let I=_(H,j);if(I===void 0){let G=Z(H);K(A,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${z}" does not exist in the schema for identifier ${w}`,F,{path:z,identifier:w,availableProperties:G});return}return I}function r(j,w,A){let F=d(j.path),z=w.helpers?.get(F);if(!z)return K(w,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${F}" — cannot analyze statically`,A??j,{helperName:F}),{type:"string"};let H=z.params;if(H){let I=H.filter((G)=>!G.optional).length;if(j.params.length<I)K(w,"MISSING_ARGUMENT","error",`Helper "${F}" expects at least ${I} argument(s), but got ${j.params.length}`,A??j,{helperName:F,expected:`${I} argument(s)`,actual:`${j.params.length} argument(s)`})}for(let I=0;I<j.params.length;I++){let G=R(j.params[I],w,A??j),L=H?.[I];if(G&&L?.type){let O=L.type;if(!o(G,O)){let n=L.name;K(w,"TYPE_MISMATCH","error",`Helper "${F}" parameter "${n}" expects ${Q(O)}, but got ${Q(G)}`,A??j,{helperName:F,expected:Q(O),actual:Q(G)})}}}return z.returnType??{type:"string"}}function X(j){return j.params[0]}function m(j){if(j.path.type==="PathExpression")return j.path.original;return""}function d(j){if(j.type==="PathExpression")return j.original;return""}function K(j,w,A,F,z,H){let I={severity:A,code:w,message:F};if(z&&"loc"in z&&z.loc)I.loc={start:{line:z.loc.start.line,column:z.loc.start.column},end:{line:z.loc.end.line,column:z.loc.end.column}},I.source=u(j.template,I.loc);if(H)I.details=H;j.diagnostics.push(I)}function Q(j){if(j.type)return Array.isArray(j.type)?j.type.join(" | "):j.type;if(j.oneOf)return"oneOf(...)";if(j.anyOf)return"anyOf(...)";if(j.allOf)return"allOf(...)";if(j.enum)return"enum";return"unknown"}export{l as c,p as d,Y as e};
2
+
3
+ //# debugId=812E930FBACC0FA364756E2164756E21
4
+ //# sourceMappingURL=chunk-1qwj7pjc.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/analyzer.ts"],
4
+ "sourcesContent": [
5
+ "import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors.ts\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser.ts\";\nimport {\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver.ts\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils.ts\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\treturn analyzeFromAst(ast, template, inputSchema, { identifierSchemas });\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) =>\n\t\tanalyze(template[key] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t},\n): AnalysisResult {\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"
6
+ ],
7
+ "mappings": "8VAqGO,GAAS,LAAO,LACtB,JACA,EACA,EACiB,CACjB,GAAI,EAAc,CAAQ,EACzB,OAAO,EAAsB,EAAU,EAAa,CAAiB,EAEtE,GAAI,EAAe,CAAQ,EAC1B,MAAO,CACN,MAAO,GACP,YAAa,CAAC,EACd,aAAc,EAAqB,CAAQ,CAC5C,EAED,IAAM,EAAM,EAAM,CAAQ,EAC1B,OAAO,EAAe,EAAK,EAAU,EAAa,CAAE,mBAAkB,CAAC,EAQxE,SAAS,CAAqB,CAC7B,EACA,EACA,EACiB,CACjB,OAAO,EAAwB,OAAO,KAAK,CAAQ,EAAG,CAAC,IACtD,EAAQ,EAAS,GAAuB,EAAa,CAAiB,CACvE,EAeM,SAAS,CAAc,CAC7B,EACA,EACA,EACA,EAIiB,CACjB,IAAM,EAAuB,CAC5B,KAAM,EACN,QAAS,EACT,YAAa,CAAC,EACd,WACA,kBAAmB,GAAS,kBAC5B,QAAS,GAAS,OACnB,EAGM,EAAe,EAAiB,EAAK,CAAG,EAI9C,MAAO,CACN,MAAO,CAHU,EAAI,YAAY,KAAK,CAAC,IAAM,EAAE,WAAa,OAAO,EAInE,YAAa,EAAI,YACjB,aAAc,EAAe,CAAY,CAC1C,EAuBD,SAAS,CAAgB,CACxB,EACA,EAC0B,CAC1B,OAAQ,EAAK,UACP,uBACA,mBAEJ,WAEI,oBACJ,OAAO,EAAgB,EAAmC,CAAG,MAEzD,iBACJ,OAAO,EAAe,EAAgC,CAAG,UAKzD,EACC,EACA,eACA,UACA,+BAA+B,EAAK,QACpC,CACD,EACA,QAaH,SAAS,CAAe,CACvB,EACA,EACc,CAGd,GAAI,EAAK,KAAK,OAAS,gBAQtB,OAPA,EACC,EACA,eACA,UACA,gDACA,CACD,EACO,CAAC,EAMT,GAAI,EAAK,OAAO,OAAS,GAAK,EAAK,KAAM,CACxC,IAAM,EAAa,EAAkB,EAAK,IAAI,EAGxC,EAAS,EAAI,SAAS,IAAI,CAAU,EAC1C,GAAI,EAAQ,CACX,IAAM,EAAe,EAAO,OAG5B,GAAI,EAAc,CACjB,IAAM,EAAgB,EAAa,OAAO,CAAC,IAAM,CAAC,EAAE,QAAQ,EAAE,OAC9D,GAAI,EAAK,OAAO,OAAS,EACxB,EACC,EACA,mBACA,QACA,WAAW,uBAAgC,0BAAsC,EAAK,OAAO,SAC7F,EACA,CACC,aACA,SAAU,GAAG,gBACb,OAAQ,GAAG,EAAK,OAAO,oBACxB,CACD,EAKF,QAAS,EAAI,EAAG,EAAI,EAAK,OAAO,OAAQ,IAAK,CAC5C,IAAM,EAAiB,EACtB,EAAK,OAAO,GACZ,EACA,CACD,EAIM,EAAc,IAAe,GACnC,GAAI,GAAkB,GAAa,KAAM,CACxC,IAAM,EAAe,EAAY,KACjC,GAAI,CAAC,EAAsB,EAAgB,CAAY,EAAG,CACzD,IAAM,EAAY,EAAY,KAC9B,EACC,EACA,gBACA,QACA,WAAW,iBAA0B,cAAsB,EAAgB,CAAY,cAAc,EAAgB,CAAc,IACnI,EACA,CACC,aACA,SAAU,EAAgB,CAAY,EACtC,OAAQ,EAAgB,CAAc,CACvC,CACD,IAKH,OAAO,EAAO,YAAc,CAAE,KAAM,QAAS,EAY9C,OARA,EACC,EACA,iBACA,UACA,0BAA0B,iCAC1B,EACA,CAAE,YAAW,CACd,EACO,CAAE,KAAM,QAAS,EAIzB,OAAO,EAAiC,EAAK,KAAM,EAAK,CAAI,GAAK,CAAC,EAanE,SAAS,CAAqB,CAC7B,EACA,EACU,CAEV,GAAI,CAAC,EAAS,MAAQ,CAAC,EAAS,KAAM,MAAO,GAE7C,IAAM,EAAgB,MAAM,QAAQ,EAAS,IAAI,EAC9C,EAAS,KACT,CAAC,EAAS,IAAI,EAMjB,OALsB,MAAM,QAAQ,EAAS,IAAI,EAC9C,EAAS,KACT,CAAC,EAAS,IAAI,GAGI,KAAK,CAAC,IAC1B,EAAc,KACb,CAAC,IACA,IAAO,GAEN,IAAO,UAAY,IAAO,WAC1B,IAAO,WAAa,IAAO,QAC9B,CACD,EAgBD,SAAS,CAAgB,CACxB,EACA,EACc,CACd,IAAM,EAAY,EAAiB,CAAO,EAG1C,GAAI,EAAU,SAAW,EACxB,MAAO,CAAE,KAAM,QAAS,EAIzB,IAAM,EAAa,EAA+B,CAAO,EACzD,GAAI,EACH,OAAO,EAAgB,EAAY,CAAG,EAIvC,IAAM,EAAc,EAA0B,CAAO,EACrD,GAAI,EACH,OAAO,EAAe,EAAa,CAAG,EAOvC,GADmB,EAAU,MAAM,CAAC,IAAM,EAAE,OAAS,kBAAkB,EACvD,CACf,IAAM,EAAO,EACX,IAAI,CAAC,IAAO,EAA+B,KAAK,EAChD,KAAK,EAAE,EACP,KAAK,EAEP,GAAI,IAAS,GAAI,MAAO,CAAE,KAAM,QAAS,EAEzC,IAAM,EAAc,EAAkB,CAAI,EAC1C,GAAI,EAAa,MAAO,CAAE,KAAM,CAAY,EAY7C,GADkB,EAAU,MAAM,CAAC,IAAM,EAAE,OAAS,gBAAgB,EACrD,CACd,IAAM,EAAuB,CAAC,EAC9B,QAAW,KAAQ,EAAW,CAC7B,IAAM,EAAI,EAAe,EAAgC,CAAG,EAC5D,GAAI,EAAG,EAAM,KAAK,CAAC,EAEpB,GAAI,EAAM,SAAW,EAAG,OAAO,EAAM,GACrC,GAAI,EAAM,OAAS,EAAG,OAAO,EAAe,CAAE,MAAO,CAAM,CAAC,EAC5D,MAAO,CAAE,KAAM,QAAS,EAMzB,QAAW,KAAQ,EAAQ,KAC1B,EAAiB,EAAM,CAAG,EAE3B,MAAO,CAAE,KAAM,QAAS,EAczB,SAAS,CAAc,CACtB,EACA,EACc,CACd,IAAM,EAAa,EAAmB,CAAI,EAE1C,OAAQ,OAGF,SACA,SAAU,CACd,IAAM,EAAM,EAAiB,CAAI,EACjC,GAAI,EACH,EAAiC,EAAK,EAAK,CAAI,EAE/C,OACC,EACA,mBACA,QACA,EAA6B,CAAU,EACvC,EACA,CAAE,YAAW,CACd,EAID,IAAM,EAAW,EAAiB,EAAK,QAAS,CAAG,EAEnD,GAAI,EAAK,QAAS,CACjB,IAAM,EAAW,EAAiB,EAAK,QAAS,CAAG,EAEnD,GAAI,EAAU,EAAU,CAAQ,EAAG,OAAO,EAE1C,OAAO,EAAe,CAAE,MAAO,CAAC,EAAU,CAAQ,CAAE,CAAC,EAKtD,OAAO,CACR,KAKK,OAAQ,CACZ,IAAM,EAAM,EAAiB,CAAI,EACjC,GAAI,CAAC,EAAK,CACT,EACC,EACA,mBACA,QACA,EAA6B,MAAM,EACnC,EACA,CAAE,WAAY,MAAO,CACtB,EAEA,IAAM,EAAQ,EAAI,QAIlB,GAHA,EAAI,QAAU,CAAC,EACf,EAAiB,EAAK,QAAS,CAAG,EAClC,EAAI,QAAU,EACV,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EACpD,MAAO,CAAE,KAAM,QAAS,EAGzB,IAAM,EAAmB,EAAiC,EAAK,EAAK,CAAI,EACxE,GAAI,CAAC,EAAkB,CAEtB,IAAM,EAAQ,EAAI,QAIlB,GAHA,EAAI,QAAU,CAAC,EACf,EAAiB,EAAK,QAAS,CAAG,EAClC,EAAI,QAAU,EACV,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EACpD,MAAO,CAAE,KAAM,QAAS,EAIzB,IAAM,EAAa,EAAkB,EAAkB,EAAI,IAAI,EAC/D,GAAI,CAAC,EAAY,CAChB,EACC,EACA,gBACA,QACA,EACC,OACA,WACA,EAAgB,CAAgB,CACjC,EACA,EACA,CACC,WAAY,OACZ,SAAU,QACV,OAAQ,EAAgB,CAAgB,CACzC,CACD,EAEA,IAAM,EAAQ,EAAI,QAIlB,GAHA,EAAI,QAAU,CAAC,EACf,EAAiB,EAAK,QAAS,CAAG,EAClC,EAAI,QAAU,EACV,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EACpD,MAAO,CAAE,KAAM,QAAS,EAIzB,IAAM,EAAQ,EAAI,QAMlB,GALA,EAAI,QAAU,EACd,EAAiB,EAAK,QAAS,CAAG,EAClC,EAAI,QAAU,EAGV,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EAGpD,MAAO,CAAE,KAAM,QAAS,CACzB,KAKK,OAAQ,CACZ,IAAM,EAAM,EAAiB,CAAI,EACjC,GAAI,CAAC,EAAK,CACT,EACC,EACA,mBACA,QACA,EAA6B,MAAM,EACnC,EACA,CAAE,WAAY,MAAO,CACtB,EAEA,IAAM,EAAQ,EAAI,QAClB,EAAI,QAAU,CAAC,EACf,IAAM,EAAS,EAAiB,EAAK,QAAS,CAAG,EAEjD,GADA,EAAI,QAAU,EACV,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EACpD,OAAO,EAGR,IAAM,EAAc,EAAiC,EAAK,EAAK,CAAI,EAE7D,EAAQ,EAAI,QAClB,EAAI,QAAU,GAAe,CAAC,EAC9B,IAAM,EAAS,EAAiB,EAAK,QAAS,CAAG,EAIjD,GAHA,EAAI,QAAU,EAGV,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EAEpD,OAAO,CACR,SAGS,CACR,IAAM,EAAS,EAAI,SAAS,IAAI,CAAU,EAC1C,GAAI,EAAQ,CAEX,QAAW,KAAS,EAAK,OACxB,EACC,EACA,EACA,CACD,EAID,GADA,EAAiB,EAAK,QAAS,CAAG,EAC9B,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EACpD,OAAO,EAAO,YAAc,CAAE,KAAM,QAAS,EAc9C,GAVA,EACC,EACA,iBACA,UACA,EAA2B,CAAU,EACrC,EACA,CAAE,YAAW,CACd,EAEA,EAAiB,EAAK,QAAS,CAAG,EAC9B,EAAK,QAAS,EAAiB,EAAK,QAAS,CAAG,EACpD,MAAO,CAAE,KAAM,QAAS,CACzB,GAiBF,SAAS,CAAgC,CACxC,EACA,EAEA,EAC0B,CAE1B,GAAI,EAAiB,CAAI,EACxB,OAAO,EAAI,QAIZ,GAAI,EAAK,OAAS,gBACjB,OAAO,EAAqB,EAA+B,EAAK,CAAU,EAG3E,IAAM,EAAW,EAAoB,CAAI,EACzC,GAAI,EAAS,SAAW,EAAG,CAE1B,GAAI,EAAK,OAAS,gBAAiB,MAAO,CAAE,KAAM,QAAS,EAC3D,GAAI,EAAK,OAAS,gBAAiB,MAAO,CAAE,KAAM,QAAS,EAC3D,GAAI,EAAK,OAAS,iBAAkB,MAAO,CAAE,KAAM,SAAU,EAC7D,GAAI,EAAK,OAAS,cAAe,MAAO,CAAE,KAAM,MAAO,EACvD,GAAI,EAAK,OAAS,mBAAoB,MAAO,CAAC,EAE9C,EACC,EACA,eACA,UACA,EAA0B,EAAK,IAAI,EACnC,GAAc,CACf,EACA,OAID,IAAQ,gBAAe,cAAe,EAA4B,CAAQ,EAE1E,GAAI,IAAe,KAGlB,OAAO,EACN,EACA,EACA,EACA,GAAc,CACf,EAID,IAAM,EAAW,EAAkB,EAAI,QAAS,CAAa,EAC7D,GAAI,IAAa,OAAW,CAC3B,IAAM,EAAW,EAAc,KAAK,GAAG,EACjC,EAAsB,EAAuB,EAAI,OAAO,EAC9D,EACC,EACA,mBACA,QACA,EAA8B,EAAU,CAAmB,EAC3D,GAAc,EACd,CAAE,KAAM,EAAU,qBAAoB,CACvC,EACA,OAGD,OAAO,EAYR,SAAS,CAAqB,CAC7B,EACA,EACA,EACA,EAC0B,CAC1B,IAAM,EAAW,EAAc,KAAK,GAAG,EAGvC,GAAI,CAAC,EAAI,kBAAmB,CAC3B,EACC,EACA,6BACA,QACA,aAAa,KAAY,gEACzB,EACA,CAAE,KAAM,GAAG,KAAY,IAAc,YAAW,CACjD,EACA,OAID,IAAM,EAAW,EAAI,kBAAkB,GACvC,GAAI,CAAC,EAAU,CACd,EACC,EACA,qBACA,QACA,aAAa,KAAY,4BAAqC,6CAC9D,EACA,CAAE,KAAM,GAAG,KAAY,IAAc,YAAW,CACjD,EACA,OAID,IAAM,EAAW,EAAkB,EAAU,CAAa,EAC1D,GAAI,IAAa,OAAW,CAC3B,IAAM,EAAsB,EAAuB,CAAQ,EAC3D,EACC,EACA,gCACA,QACA,aAAa,kDAAyD,IACtE,EACA,CACC,KAAM,EACN,aACA,qBACD,CACD,EACA,OAGD,OAAO,EA4BR,SAAS,CAAoB,CAC5B,EACA,EACA,EAC0B,CAC1B,IAAM,EAAa,EAAkB,EAAK,IAAI,EAExC,EAAS,EAAI,SAAS,IAAI,CAAU,EAC1C,GAAI,CAAC,EASJ,OARA,EACC,EACA,iBACA,UACA,kCAAkC,iCAClC,GAAc,EACd,CAAE,YAAW,CACd,EACO,CAAE,KAAM,QAAS,EAGzB,IAAM,EAAe,EAAO,OAG5B,GAAI,EAAc,CACjB,IAAM,EAAgB,EAAa,OAAO,CAAC,IAAM,CAAC,EAAE,QAAQ,EAAE,OAC9D,GAAI,EAAK,OAAO,OAAS,EACxB,EACC,EACA,mBACA,QACA,WAAW,uBAAgC,0BAAsC,EAAK,OAAO,SAC7F,GAAc,EACd,CACC,aACA,SAAU,GAAG,gBACb,OAAQ,GAAG,EAAK,OAAO,oBACxB,CACD,EAKF,QAAS,EAAI,EAAG,EAAI,EAAK,OAAO,OAAQ,IAAK,CAC5C,IAAM,EAAiB,EACtB,EAAK,OAAO,GACZ,EACA,GAAc,CACf,EAEM,EAAc,IAAe,GACnC,GAAI,GAAkB,GAAa,KAAM,CACxC,IAAM,EAAe,EAAY,KACjC,GAAI,CAAC,EAAsB,EAAgB,CAAY,EAAG,CACzD,IAAM,EAAY,EAAY,KAC9B,EACC,EACA,gBACA,QACA,WAAW,iBAA0B,cAAsB,EAAgB,CAAY,cAAc,EAAgB,CAAc,IACnI,GAAc,EACd,CACC,aACA,SAAU,EAAgB,CAAY,EACtC,OAAQ,EAAgB,CAAc,CACvC,CACD,IAKH,OAAO,EAAO,YAAc,CAAE,KAAM,QAAS,EAG9C,SAAS,CAAgB,CACxB,EACiC,CACjC,OAAO,EAAK,OAAO,GAMpB,SAAS,CAAkB,CAAC,EAAsC,CACjE,GAAI,EAAK,KAAK,OAAS,iBACtB,OAAQ,EAAK,KAAgC,SAE9C,MAAO,GAOR,SAAS,CAAiB,CAAC,EAAkC,CAC5D,GAAI,EAAK,OAAS,iBACjB,OAAQ,EAAgC,SAEzC,MAAO,GAYR,SAAS,CAAa,CACrB,EACA,EACA,EACA,EACA,EACA,EACO,CACP,IAAM,EAAiC,CAAE,WAAU,OAAM,SAAQ,EAGjE,GAAI,GAAQ,QAAS,GAAQ,EAAK,IACjC,EAAW,IAAM,CAChB,MAAO,CAAE,KAAM,EAAK,IAAI,MAAM,KAAM,OAAQ,EAAK,IAAI,MAAM,MAAO,EAClE,IAAK,CAAE,KAAM,EAAK,IAAI,IAAI,KAAM,OAAQ,EAAK,IAAI,IAAI,MAAO,CAC7D,EAEA,EAAW,OAAS,EAAqB,EAAI,SAAU,EAAW,GAAG,EAGtE,GAAI,EACH,EAAW,QAAU,EAGtB,EAAI,YAAY,KAAK,CAAU,EAMhC,SAAS,CAAe,CAAC,EAA6B,CACrD,GAAI,EAAO,KACV,OAAO,MAAM,QAAQ,EAAO,IAAI,EAAI,EAAO,KAAK,KAAK,KAAK,EAAI,EAAO,KAEtE,GAAI,EAAO,MAAO,MAAO,aACzB,GAAI,EAAO,MAAO,MAAO,aACzB,GAAI,EAAO,MAAO,MAAO,aACzB,GAAI,EAAO,KAAM,MAAO,OACxB,MAAO",
8
+ "debugId": "812E930FBACC0FA364756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,7 @@
1
+ function P(w,G){if(w===G)return!0;if(w===null||G===null)return!1;if(typeof w!==typeof G)return!1;if(Array.isArray(w)){if(!Array.isArray(G))return!1;if(w.length!==G.length)return!1;for(let F=0;F<w.length;F++)if(!P(w[F],G[F]))return!1;return!0}if(typeof w==="object"){let F=w,H=G,I=Object.keys(F),J=Object.keys(H);if(I.length!==J.length)return!1;for(let M of I)if(!(M in H)||!P(F[M],H[M]))return!1;return!0}return!1}class Q{capacity;cache=new Map;constructor(w){this.capacity=w;if(w<1)throw Error("LRUCache capacity must be at least 1")}get(w){if(!this.cache.has(w))return;let G=this.cache.get(w);return this.cache.delete(w),this.cache.set(w,G),G}set(w,G){if(this.cache.has(w))this.cache.delete(w);else if(this.cache.size>=this.capacity){let F=this.cache.keys().next().value;if(F!==void 0)this.cache.delete(F)}this.cache.set(w,G)}has(w){return this.cache.has(w)}delete(w){return this.cache.delete(w)}clear(){this.cache.clear()}get size(){return this.cache.size}}function S(w,G){let F=w.split(`
2
+ `),H=G.start.line-1,I=G.end.line-1;if(H<0||H>=F.length)return"";if(H===I)return(F[H]??"").trim();let J=Math.min(I,F.length-1);return F.slice(H,J+1).map((M)=>M.trimEnd()).join(`
3
+ `).trim()}function U(w){let G=new Set;if(w.properties)for(let F of Object.keys(w.properties))G.add(F);for(let F of["allOf","anyOf","oneOf"]){let H=w[F];if(H)for(let I of H){if(typeof I==="boolean")continue;if(I.properties)for(let J of Object.keys(I.properties))G.add(J)}}return Array.from(G).sort()}function W(w,G){let F=[],H={},I=!0;for(let J of w){let M=G(J);if(!M.valid)I=!1;F.push(...M.diagnostics),H[J]=M.outputSchema}return{valid:I,diagnostics:F,outputSchema:{type:"object",properties:H,required:w}}}function X(w,G){let F=[],H={},I={},J=!0;for(let O of w){let N=G(O);if(!N.analysis.valid)J=!1;F.push(...N.analysis.diagnostics),H[O]=N.analysis.outputSchema,I[O]=N.value}return{analysis:{valid:J,diagnostics:F,outputSchema:{type:"object",properties:H,required:w}},value:J?I:void 0}}
4
+ export{P as M,Q as N,S as O,U as P,W as Q,X as R};
5
+
6
+ //# debugId=2FCDD9900EFA4FE664756E2164756E21
7
+ //# sourceMappingURL=chunk-4zv02svp.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/utils.ts"],
4
+ "sourcesContent": [
5
+ "import type { JSONSchema7 } from \"json-schema\";\nimport type { AnalysisResult, TemplateDiagnostic } from \"./types.ts\";\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n// Shared utility functions and classes used across the different modules\n// of the template engine.\n\n// ─── Deep Equality ───────────────────────────────────────────────────────────\n// Deep structural comparison for JSON-compatible values.\n// More robust than `JSON.stringify` because it is independent of key order\n// and does not allocate intermediate strings.\n\n/**\n * Recursively compares two JSON-compatible values.\n *\n * @param a - First value\n * @param b - Second value\n * @returns `true` if the two values are structurally identical\n *\n * @example\n * ```\n * deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 }) // → true\n * deepEqual([1, 2], [1, 2]) // → true\n * deepEqual({ a: 1 }, { a: 2 }) // → false\n * ```\n */\nexport function deepEqual(a: unknown, b: unknown): boolean {\n\t// Strict identity (covers primitives, same ref; NaN !== NaN is intentional)\n\tif (a === b) return true;\n\n\t// null is typeof \"object\" in JS — handle it separately\n\tif (a === null || b === null) return false;\n\tif (typeof a !== typeof b) return false;\n\n\t// ── Arrays ───────────────────────────────────────────────────────────────\n\tif (Array.isArray(a)) {\n\t\tif (!Array.isArray(b)) return false;\n\t\tif (a.length !== b.length) return false;\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!deepEqual(a[i], b[i])) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// ── Objects ──────────────────────────────────────────────────────────────\n\tif (typeof a === \"object\") {\n\t\tconst objA = a as Record<string, unknown>;\n\t\tconst objB = b as Record<string, unknown>;\n\t\tconst keysA = Object.keys(objA);\n\t\tconst keysB = Object.keys(objB);\n\n\t\tif (keysA.length !== keysB.length) return false;\n\n\t\tfor (const key of keysA) {\n\t\t\tif (!(key in objB) || !deepEqual(objA[key], objB[key])) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t// Different primitives (already covered by a === b at the top)\n\treturn false;\n}\n\n// ─── LRU Cache ───────────────────────────────────────────────────────────────\n// Fixed-capacity cache with Least Recently Used (LRU) eviction.\n// Leverages `Map` insertion order to track access: the oldest entry\n// is always in the first position.\n\n/**\n * Simple fixed-capacity LRU cache.\n *\n * @example\n * ```\n * const cache = new LRUCache<string, number>(2);\n * cache.set(\"a\", 1);\n * cache.set(\"b\", 2);\n * cache.get(\"a\"); // → 1 (marks \"a\" as recently used)\n * cache.set(\"c\", 3); // evicts \"b\" (least recently used)\n * cache.get(\"b\"); // → undefined\n * ```\n */\nexport class LRUCache<K, V> {\n\tprivate readonly cache = new Map<K, V>();\n\n\tconstructor(private readonly capacity: number) {\n\t\tif (capacity < 1) {\n\t\t\tthrow new Error(\"LRUCache capacity must be at least 1\");\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves a value from the cache. Returns `undefined` if absent.\n\t * Marks the entry as recently used.\n\t */\n\tget(key: K): V | undefined {\n\t\tif (!this.cache.has(key)) return undefined;\n\n\t\t// Move to the end of the Map (= most recently used)\n\t\tconst value = this.cache.get(key) as V;\n\t\tthis.cache.delete(key);\n\t\tthis.cache.set(key, value);\n\t\treturn value;\n\t}\n\n\t/**\n\t * Inserts or updates a value in the cache.\n\t * If the cache is full, evicts the least recently used entry.\n\t */\n\tset(key: K, value: V): void {\n\t\tif (this.cache.has(key)) {\n\t\t\tthis.cache.delete(key);\n\t\t} else if (this.cache.size >= this.capacity) {\n\t\t\t// Evict the first entry (the oldest one)\n\t\t\tconst oldestKey = this.cache.keys().next().value;\n\t\t\tif (oldestKey !== undefined) {\n\t\t\t\tthis.cache.delete(oldestKey);\n\t\t\t}\n\t\t}\n\t\tthis.cache.set(key, value);\n\t}\n\n\t/**\n\t * Checks whether a key exists in the cache (without affecting LRU order).\n\t */\n\thas(key: K): boolean {\n\t\treturn this.cache.has(key);\n\t}\n\n\t/**\n\t * Removes an entry from the cache.\n\t * @returns `true` if the entry existed and was removed\n\t */\n\tdelete(key: K): boolean {\n\t\treturn this.cache.delete(key);\n\t}\n\n\t/** Clears the entire cache. */\n\tclear(): void {\n\t\tthis.cache.clear();\n\t}\n\n\t/** Number of entries currently in the cache. */\n\tget size(): number {\n\t\treturn this.cache.size;\n\t}\n}\n\n// ─── Source Snippet Extraction ────────────────────────────────────────────────\n// Used to enrich diagnostics with the template fragment that caused the error.\n\n/**\n * Extracts a template fragment around a given position.\n *\n * @param template - The full template source\n * @param loc - The position (line/column, 1-based) of the error\n * @returns The corresponding code fragment (trimmed)\n */\nexport function extractSourceSnippet(\n\ttemplate: string,\n\tloc: {\n\t\tstart: { line: number; column: number };\n\t\tend: { line: number; column: number };\n\t},\n): string {\n\tconst lines = template.split(\"\\n\");\n\tconst startLine = loc.start.line - 1; // 0-based\n\tconst endLine = loc.end.line - 1;\n\n\tif (startLine < 0 || startLine >= lines.length) return \"\";\n\n\tif (startLine === endLine) {\n\t\t// Same line — extract the portion between start.column and end.column\n\t\tconst line = lines[startLine] ?? \"\";\n\t\treturn line.trim();\n\t}\n\n\t// Multi-line — return the affected lines\n\tconst clampedEnd = Math.min(endLine, lines.length - 1);\n\treturn lines\n\t\t.slice(startLine, clampedEnd + 1)\n\t\t.map((l) => l.trimEnd())\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\n// ─── Schema Properties ──────────────────────────────────────────────────────\n// Utility for listing available properties in a schema, used to enrich\n// error messages with suggestions.\n\n/**\n * Lists the declared property names in a JSON Schema.\n * Returns an empty array if the schema has no `properties`.\n */\nexport function getSchemaPropertyNames(schema: JSONSchema7): string[] {\n\tconst names = new Set<string>();\n\n\t// Direct properties\n\tif (schema.properties) {\n\t\tfor (const key of Object.keys(schema.properties)) {\n\t\t\tnames.add(key);\n\t\t}\n\t}\n\n\t// Properties within combinators\n\tfor (const combinator of [\"allOf\", \"anyOf\", \"oneOf\"] as const) {\n\t\tconst branches = schema[combinator];\n\t\tif (branches) {\n\t\t\tfor (const branch of branches) {\n\t\t\t\tif (typeof branch === \"boolean\") continue;\n\t\t\t\tif (branch.properties) {\n\t\t\t\t\tfor (const key of Object.keys(branch.properties)) {\n\t\t\t\t\t\tnames.add(key);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn Array.from(names).sort();\n}\n\n// ─── Object Analysis Aggregation ─────────────────────────────────────────────\n// Factorizes the common recursion pattern over template objects:\n// iterate the keys, analyze each entry via a callback, accumulate\n// diagnostics, and build the output object schema.\n//\n// Used by:\n// - `analyzer.ts` (analyzeObjectTemplate)\n// - `Typebars.analyzeObject()` (typebars.ts)\n// - `CompiledTemplate.analyze()` in object mode (compiled-template.ts)\n\n/**\n * Aggregates analysis results from a set of named entries into a single\n * `AnalysisResult` with an object-typed `outputSchema`.\n *\n * @param keys - The keys of the object to analyze\n * @param analyzeEntry - Callback that analyzes an entry by its key\n * @returns An aggregated `AnalysisResult`\n *\n * @example\n * ```\n * aggregateObjectAnalysis(\n * Object.keys(template),\n * (key) => analyze(template[key], inputSchema),\n * );\n * ```\n */\nexport function aggregateObjectAnalysis(\n\tkeys: string[],\n\tanalyzeEntry: (key: string) => AnalysisResult,\n): AnalysisResult {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst properties: Record<string, JSONSchema7> = {};\n\tlet allValid = true;\n\n\tfor (const key of keys) {\n\t\tconst child = analyzeEntry(key);\n\t\tif (!child.valid) allValid = false;\n\t\tallDiagnostics.push(...child.diagnostics);\n\t\tproperties[key] = child.outputSchema;\n\t}\n\n\treturn {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired: keys,\n\t\t},\n\t};\n}\n\n/**\n * Aggregates both analysis **and** execution results from a set of named\n * entries. Returns the aggregated `AnalysisResult` and the object of\n * executed values (or `undefined` if at least one entry is invalid).\n *\n * @param keys - The keys of the object\n * @param processEntry - Callback that analyzes and executes an entry by its key\n * @returns Aggregated `{ analysis, value }`\n */\nexport function aggregateObjectAnalysisAndExecution(\n\tkeys: string[],\n\tprocessEntry: (key: string) => { analysis: AnalysisResult; value: unknown },\n): { analysis: AnalysisResult; value: unknown } {\n\tconst allDiagnostics: TemplateDiagnostic[] = [];\n\tconst properties: Record<string, JSONSchema7> = {};\n\tconst resultValues: Record<string, unknown> = {};\n\tlet allValid = true;\n\n\tfor (const key of keys) {\n\t\tconst child = processEntry(key);\n\t\tif (!child.analysis.valid) allValid = false;\n\t\tallDiagnostics.push(...child.analysis.diagnostics);\n\t\tproperties[key] = child.analysis.outputSchema;\n\t\tresultValues[key] = child.value;\n\t}\n\n\tconst analysis: AnalysisResult = {\n\t\tvalid: allValid,\n\t\tdiagnostics: allDiagnostics,\n\t\toutputSchema: {\n\t\t\ttype: \"object\",\n\t\t\tproperties,\n\t\t\trequired: keys,\n\t\t},\n\t};\n\n\treturn {\n\t\tanalysis,\n\t\tvalue: allValid ? resultValues : undefined,\n\t};\n}\n"
6
+ ],
7
+ "mappings": "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",
8
+ "debugId": "2FCDD9900EFA4FE664756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ import{e as V,f as X,g as Y,h as Z,j as _,k as $,m as w,o as B}from"./chunk-9mg6qfrs.js";import{s as K}from"./chunk-6c0pw73w.js";import M from"handlebars";function H(q,z,j){let A=V(q);if(X(A)){let J=A.body[0];return N(J.path,z,j)}let C=$(A);if(C)return N(C.path,z,j);if(_(A)){let J=Q(z,j),L=U(q,J);return w(L)}let G=Q(z,j);return U(q,G)}function N(q,z,j){if(Z(q))return z;if(q.type==="StringLiteral")return q.value;if(q.type==="NumberLiteral")return q.value;if(q.type==="BooleanLiteral")return q.value;if(q.type==="NullLiteral")return null;if(q.type==="UndefinedLiteral")return;let A=Y(q);if(A.length===0)throw new K(`Cannot resolve expression of type "${q.type}"`);let{cleanSegments:C,identifier:F}=B(A);if(F!==null&&j){let G=j[F];if(G)return O(G,C);return}if(F!==null&&!j)return;return O(z,C)}function O(q,z){let j=q;for(let A of z){if(j===null||j===void 0)return;if(typeof j!=="object")return;j=j[A]}return j}function Q(q,z){if(!z)return q;let j={...q};for(let[A,C]of Object.entries(z))for(let[F,G]of Object.entries(C))j[`${F}:${A}`]=G;return j}function U(q,z){try{return M.compile(q,{noEscape:!0,strict:!1})(z)}catch(j){let A=j instanceof Error?j.message:String(j);throw new K(A)}}
2
+ export{H as c,O as d};
3
+
4
+ //# debugId=4F7A13979718154664756E2164756E21
5
+ //# sourceMappingURL=chunk-60gk3q7z.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/executor.ts"],
4
+ "sourcesContent": [
5
+ "import Handlebars from \"handlebars\";\nimport { TemplateRuntimeError } from \"./errors.ts\";\nimport {\n\tcoerceLiteral,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisSingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser.ts\";\n\n// ─── Template Executor ───────────────────────────────────────────────────────\n// Exécute un template Handlebars avec des données réelles.\n//\n// Trois modes d'exécution :\n//\n// 1. **Expression unique** (`{{value}}` ou ` {{value}} `) → retourne la\n// valeur brute sans conversion en string. Cela permet de préserver le type\n// original (number, boolean, object, array, null).\n//\n// 2. **Bloc unique** (`{{#if x}}10{{else}}20{{/if}}` éventuellement entouré\n// de whitespace) → rendu via Handlebars puis coercion intelligente du\n// résultat (détection de littéraux number, boolean, null).\n//\n// 3. **Template mixte** (`Hello {{name}}`, texte + blocs multiples, …) →\n// délègue à Handlebars qui produit toujours une string.\n//\n// Cette distinction est la raison pour laquelle on ne peut pas simplement\n// appeler `Handlebars.compile()` dans tous les cas.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// La syntaxe `{{key:N}}` permet de résoudre une variable depuis une source\n// de données spécifique, identifiée par un entier N. Le paramètre optionnel\n// `identifierData` fournit un mapping `{ [id]: { key: value, ... } }`.\n//\n// - `{{meetingId}}` → résolu dans `data` (comportement standard)\n// - `{{meetingId:1}}` → résolu dans `identifierData[1]`\n//\n// Pour le rendu Handlebars (templates mixtes / blocs), les données\n// identifiées sont aplaties dans l'objet de données sous la forme\n// `\"key:N\": value`, ce qui correspond à la façon dont Handlebars parse\n// naturellement `{{key:N}}` (un seul segment `\"key:N\"`).\n\n// ─── API publique ────────────────────────────────────────────────────────────\n\n/**\n * Exécute un template avec les données fournies et retourne le résultat.\n *\n * Le type de retour dépend de la structure du template :\n * - Expression unique `{{expr}}` → valeur brute (any)\n * - Bloc unique → valeur coercée (number, boolean, null ou string)\n * - Template mixte → `string`\n *\n * @param template - La chaîne de template\n * @param data - Les données de contexte principal\n * @param identifierData - (optionnel) Données par identifiant `{ [id]: { key: value } }`\n */\nexport function execute(\n\ttemplate: string,\n\tdata: Record<string, unknown>,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): unknown {\n\tconst ast = parse(template);\n\n\t// ── Cas 1 : expression unique stricte `{{expr}}` ─────────────────────\n\tif (isSingleExpression(ast)) {\n\t\tconst stmt = ast.body[0] as hbs.AST.MustacheStatement;\n\t\treturn resolveExpression(stmt.path, data, identifierData);\n\t}\n\n\t// ── Cas 1b : expression unique avec whitespace autour ` {{expr}} ` ──\n\tconst singleExpr = getEffectivelySingleExpression(ast);\n\tif (singleExpr) {\n\t\treturn resolveExpression(singleExpr.path, data, identifierData);\n\t}\n\n\t// ── Cas 2 : bloc unique (éventuellement entouré de whitespace) ────────\n\t// On rend via Handlebars puis on tente de coercer le résultat vers le\n\t// type littéral détecté (number, boolean, null).\n\tconst singleBlock = getEffectivelySingleBlock(ast);\n\tif (singleBlock) {\n\t\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\t\tconst raw = renderWithHandlebars(template, merged);\n\t\treturn coerceLiteral(raw);\n\t}\n\n\t// ── Cas 3 : template mixte → string ───────────────────────────────────\n\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\treturn renderWithHandlebars(template, merged);\n}\n\n// ─── Résolution directe d'expression ─────────────────────────────────────────\n// Utilisé uniquement pour les templates à expression unique, afin de\n// retourner la valeur brute sans passer par le moteur Handlebars.\n\n/**\n * Résout une expression AST en suivant le chemin dans les données.\n *\n * Si l'expression contient un identifiant (ex: `meetingId:1`), la résolution\n * se fait dans `identifierData[1]` au lieu de `data`.\n *\n * @param expr - L'expression AST à résoudre\n * @param data - Le contexte de données principal\n * @param identifierData - Données par identifiant (optionnel)\n * @returns La valeur brute pointée par l'expression\n */\nfunction resolveExpression(\n\texpr: hbs.AST.Expression,\n\tdata: Record<string, unknown>,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): unknown {\n\t// this / . → retourne le contexte entier\n\tif (isThisExpression(expr)) {\n\t\treturn data;\n\t}\n\n\t// Literals\n\tif (expr.type === \"StringLiteral\")\n\t\treturn (expr as hbs.AST.StringLiteral).value;\n\tif (expr.type === \"NumberLiteral\")\n\t\treturn (expr as hbs.AST.NumberLiteral).value;\n\tif (expr.type === \"BooleanLiteral\")\n\t\treturn (expr as hbs.AST.BooleanLiteral).value;\n\tif (expr.type === \"NullLiteral\") return null;\n\tif (expr.type === \"UndefinedLiteral\") return undefined;\n\n\t// PathExpression — navigation par segments dans l'objet data\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\tthrow new TemplateRuntimeError(\n\t\t\t`Cannot resolve expression of type \"${expr.type}\"`,\n\t\t);\n\t}\n\n\t// Extraire l'identifiant éventuel du dernier segment\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null && identifierData) {\n\t\tconst source = identifierData[identifier];\n\t\tif (source) {\n\t\t\treturn resolveDataPath(source, cleanSegments);\n\t\t}\n\t\t// La source n'existe pas → undefined (comme une clé manquante)\n\t\treturn undefined;\n\t}\n\n\tif (identifier !== null && !identifierData) {\n\t\t// Template utilise un identifiant mais aucune identifierData fournie\n\t\treturn undefined;\n\t}\n\n\treturn resolveDataPath(data, cleanSegments);\n}\n\n/**\n * Navigue dans un objet de données en suivant un chemin de segments.\n *\n * @param data - L'objet de données\n * @param segments - Les segments du chemin (ex: `[\"user\", \"address\", \"city\"]`)\n * @returns La valeur au bout du chemin, ou `undefined` si un segment\n * intermédiaire est null/undefined\n */\nexport function resolveDataPath(data: unknown, segments: string[]): unknown {\n\tlet current: unknown = data;\n\n\tfor (const segment of segments) {\n\t\tif (current === null || current === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tcurrent = (current as Record<string, unknown>)[segment];\n\t}\n\n\treturn current;\n}\n\n// ─── Fusion des données ──────────────────────────────────────────────────────\n// Pour le rendu Handlebars (templates mixtes, blocs), on ne peut pas\n// intercepter la résolution expression par expression. On fusionne donc\n// les données identifiées dans l'objet principal sous la forme `\"key:N\"`.\n//\n// Handlebars parse `{{meetingId:1}}` comme un PathExpression avec un seul\n// segment `\"meetingId:1\"`, donc il cherchera la clé `\"meetingId:1\"` dans\n// l'objet de données — ce qui correspond exactement à notre format aplati.\n\n/**\n * Fusionne les données principales avec les données identifiées.\n *\n * @param data - Données principales\n * @param identifierData - Données par identifiant\n * @returns Un objet fusionné où les données identifiées sont sous la forme `\"key:N\"`\n *\n * @example\n * ```\n * mergeDataWithIdentifiers(\n * { name: \"Alice\" },\n * { 1: { meetingId: \"val1\" }, 2: { meetingId: \"val2\" } }\n * )\n * // → { name: \"Alice\", \"meetingId:1\": \"val1\", \"meetingId:2\": \"val2\" }\n * ```\n */\nfunction mergeDataWithIdentifiers(\n\tdata: Record<string, unknown>,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): Record<string, unknown> {\n\tif (!identifierData) return data;\n\n\tconst merged: Record<string, unknown> = { ...data };\n\n\tfor (const [id, idData] of Object.entries(identifierData)) {\n\t\tfor (const [key, value] of Object.entries(idData)) {\n\t\t\tmerged[`${key}:${id}`] = value;\n\t\t}\n\t}\n\n\treturn merged;\n}\n\n// ─── Rendu Handlebars ────────────────────────────────────────────────────────\n// Pour les templates mixtes (texte + expressions, blocs), on délègue à\n// Handlebars qui gère nativement tous les helpers intégrés (#if, #each,\n// #with, #unless) et produit une string.\n\n/**\n * Compile et exécute un template via Handlebars.\n * Retourne toujours une string.\n *\n * @param template - La chaîne de template\n * @param data - Les données de contexte\n */\nfunction renderWithHandlebars(\n\ttemplate: string,\n\tdata: Record<string, unknown>,\n): string {\n\ttry {\n\t\tconst compiled = Handlebars.compile(template, {\n\t\t\t// Désactive le HTML-escaping par défaut — ce moteur n'est pas\n\t\t\t// spécifique au HTML, on veut les valeurs brutes.\n\t\t\tnoEscape: true,\n\t\t\t// Mode strict : lève une erreur si un chemin n'existe pas dans les données.\n\t\t\tstrict: false,\n\t\t});\n\n\t\treturn compiled(data);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new TemplateRuntimeError(message);\n\t}\n}\n"
6
+ ],
7
+ "mappings": "uIAAA,oBA2DO,SAAS,CAAO,CACtB,EACA,EACA,EACU,CACV,IAAM,EAAM,EAAM,CAAQ,EAG1B,GAAI,EAAmB,CAAG,EAAG,CAC5B,IAAM,EAAO,EAAI,KAAK,GACtB,OAAO,EAAkB,EAAK,KAAM,EAAM,CAAc,EAIzD,IAAM,EAAa,EAA+B,CAAG,EACrD,GAAI,EACH,OAAO,EAAkB,EAAW,KAAM,EAAM,CAAc,EAO/D,GADoB,EAA0B,CAAG,EAChC,CAChB,IAAM,EAAS,EAAyB,EAAM,CAAc,EACtD,EAAM,EAAqB,EAAU,CAAM,EACjD,OAAO,EAAc,CAAG,EAIzB,IAAM,EAAS,EAAyB,EAAM,CAAc,EAC5D,OAAO,EAAqB,EAAU,CAAM,EAkB7C,SAAS,CAAiB,CACzB,EACA,EACA,EACU,CAEV,GAAI,EAAiB,CAAI,EACxB,OAAO,EAIR,GAAI,EAAK,OAAS,gBACjB,OAAQ,EAA+B,MACxC,GAAI,EAAK,OAAS,gBACjB,OAAQ,EAA+B,MACxC,GAAI,EAAK,OAAS,iBACjB,OAAQ,EAAgC,MACzC,GAAI,EAAK,OAAS,cAAe,OAAO,KACxC,GAAI,EAAK,OAAS,mBAAoB,OAGtC,IAAM,EAAW,EAAoB,CAAI,EACzC,GAAI,EAAS,SAAW,EACvB,MAAM,IAAI,EACT,sCAAsC,EAAK,OAC5C,EAID,IAAQ,gBAAe,cAAe,EAA4B,CAAQ,EAE1E,GAAI,IAAe,MAAQ,EAAgB,CAC1C,IAAM,EAAS,EAAe,GAC9B,GAAI,EACH,OAAO,EAAgB,EAAQ,CAAa,EAG7C,OAGD,GAAI,IAAe,MAAQ,CAAC,EAE3B,OAGD,OAAO,EAAgB,EAAM,CAAa,EAWpC,SAAS,CAAe,CAAC,EAAe,EAA6B,CAC3E,IAAI,EAAmB,EAEvB,QAAW,KAAW,EAAU,CAC/B,GAAI,IAAY,MAAQ,IAAY,OACnC,OAGD,GAAI,OAAO,IAAY,SACtB,OAGD,EAAW,EAAoC,GAGhD,OAAO,EA4BR,SAAS,CAAwB,CAChC,EACA,EAC0B,CAC1B,GAAI,CAAC,EAAgB,OAAO,EAE5B,IAAM,EAAkC,IAAK,CAAK,EAElD,QAAY,EAAI,KAAW,OAAO,QAAQ,CAAc,EACvD,QAAY,EAAK,KAAU,OAAO,QAAQ,CAAM,EAC/C,EAAO,GAAG,KAAO,KAAQ,EAI3B,OAAO,EAeR,SAAS,CAAoB,CAC5B,EACA,EACS,CACT,GAAI,CASH,OARiB,EAAW,QAAQ,EAAU,CAG7C,SAAU,GAEV,OAAQ,EACT,CAAC,EAEe,CAAI,EACnB,MAAO,EAAgB,CACxB,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACrE,MAAM,IAAI,EAAqB,CAAO",
8
+ "debugId": "4F7A13979718154664756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ function k(j){return j===null||typeof j!=="string"&&typeof j!=="object"}function z(j){return j!==null&&typeof j==="object"}function A(j){if(j===null)return{type:"null"};if(typeof j==="boolean")return{type:"boolean"};if(typeof j==="number")return Number.isInteger(j)?{type:"integer"}:{type:"number"};return{type:"null"}}function B(j){return j}
2
+ export{k as j,z as k,A as l,B as m};
3
+
4
+ //# debugId=ABFED878C9A1EDA264756E2164756E21
5
+ //# sourceMappingURL=chunk-6955jpr7.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/types.ts"],
4
+ "sourcesContent": [
5
+ "import type { JSONSchema7 } from \"json-schema\";\nimport type { FromSchema, JSONSchema } from \"json-schema-to-ts\";\n\n// ─── Template Input ──────────────────────────────────────────────────────────\n// The engine accepts primitive values in addition to template strings.\n// When a non-string value is passed, it is treated as a literal passthrough:\n// analysis returns the inferred type, and execution returns the value as-is.\n\n/**\n * Object where each property is a `TemplateInput` (recursive).\n *\n * Allows passing an entire structure as a template:\n * ```\n * engine.analyze({\n * userName: \"{{name}}\",\n * userAge: \"{{age}}\",\n * nested: { x: \"{{foo}}\" },\n * }, inputSchema);\n * ```\n */\nexport interface TemplateInputObject {\n\t[key: string]: TemplateInput;\n}\n\n/**\n * Input type accepted by the template engine.\n *\n * - `string` → standard Handlebars template (parsed and executed)\n * - `number` → numeric literal (passthrough)\n * - `boolean` → boolean literal (passthrough)\n * - `null` → null literal (passthrough)\n * - `TemplateInputObject` → object where each property is a `TemplateInput`\n */\nexport type TemplateInput =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| TemplateInputObject;\n\n/**\n * Checks whether a value is a non-string primitive literal (number, boolean, null).\n * These values are treated as passthrough by the engine.\n *\n * Note: objects (`TemplateInputObject`) are NOT literals.\n */\nexport function isLiteralInput(\n\tinput: TemplateInput,\n): input is number | boolean | null {\n\treturn (\n\t\tinput === null || (typeof input !== \"string\" && typeof input !== \"object\")\n\t);\n}\n\n/**\n * Checks whether a value is a template object (`TemplateInputObject`).\n * Template objects are processed recursively by the engine:\n * each property is analyzed/executed individually.\n */\nexport function isObjectInput(\n\tinput: TemplateInput,\n): input is TemplateInputObject {\n\treturn input !== null && typeof input === \"object\";\n}\n\n/**\n * Infers the JSON Schema of a non-string primitive value.\n *\n * @param value - The primitive value (number, boolean, null)\n * @returns The corresponding JSON Schema\n *\n * @example\n * ```\n * inferPrimitiveSchema(42) // → { type: \"number\" }\n * inferPrimitiveSchema(true) // → { type: \"boolean\" }\n * inferPrimitiveSchema(null) // → { type: \"null\" }\n * ```\n */\nexport function inferPrimitiveSchema(\n\tvalue: number | boolean | null,\n): JSONSchema7 {\n\tif (value === null) return { type: \"null\" };\n\tif (typeof value === \"boolean\") return { type: \"boolean\" };\n\tif (typeof value === \"number\") {\n\t\treturn Number.isInteger(value) ? { type: \"integer\" } : { type: \"number\" };\n\t}\n\t// Exhaustiveness check — all branches are covered above.\n\t// If the type of `value` changes, TypeScript will raise an error here.\n\tvalue satisfies never;\n\treturn { type: \"null\" };\n}\n\n// ─── Diagnostic Codes ────────────────────────────────────────────────────────\n// Machine-readable codes for each error/warning type, enabling the frontend\n// to react programmatically without parsing the human-readable message.\n\nexport type DiagnosticCode =\n\t/** The referenced property does not exist in the context schema */\n\t| \"UNKNOWN_PROPERTY\"\n\t/** Type mismatch (e.g. #each on a non-array) */\n\t| \"TYPE_MISMATCH\"\n\t/** A block helper is used without a required argument */\n\t| \"MISSING_ARGUMENT\"\n\t/** Unknown block helper (neither built-in nor registered) */\n\t| \"UNKNOWN_HELPER\"\n\t/** The expression cannot be statically analyzed */\n\t| \"UNANALYZABLE\"\n\t/** The {{key:N}} syntax is used but no identifierSchemas were provided */\n\t| \"MISSING_IDENTIFIER_SCHEMAS\"\n\t/** The identifier N does not exist in the provided identifierSchemas */\n\t| \"UNKNOWN_IDENTIFIER\"\n\t/** The property does not exist in the identifier's schema */\n\t| \"IDENTIFIER_PROPERTY_NOT_FOUND\"\n\t/** Syntax error in the template */\n\t| \"PARSE_ERROR\";\n\n// ─── Diagnostic Details ──────────────────────────────────────────────────────\n// Supplementary information to understand the exact cause of the error.\n// Designed to be easily JSON-serializable and consumable by a frontend.\n\nexport interface DiagnosticDetails {\n\t/** Path of the expression that caused the error (e.g. `\"user.name.foo\"`) */\n\tpath?: string;\n\t/** Name of the helper involved (for helper-related errors) */\n\thelperName?: string;\n\t/** What was expected (e.g. `\"array\"`, `\"property to exist\"`) */\n\texpected?: string;\n\t/** What was found (e.g. `\"string\"`, `\"undefined\"`) */\n\tactual?: string;\n\t/** Available properties in the current schema (for suggestions) */\n\tavailableProperties?: string[];\n\t/** Template identifier number (for `{{key:N}}` errors) */\n\tidentifier?: number;\n}\n\n// ─── Static Analysis Result ──────────────────────────────────────────────────\n\n/** Diagnostic produced by the static analyzer */\nexport interface TemplateDiagnostic {\n\t/** \"error\" blocks execution, \"warning\" is informational */\n\tseverity: \"error\" | \"warning\";\n\n\t/** Machine-readable code identifying the error type */\n\tcode: DiagnosticCode;\n\n\t/** Human-readable message describing the problem */\n\tmessage: string;\n\n\t/** Position in the template source (if available from the AST) */\n\tloc?: {\n\t\tstart: { line: number; column: number };\n\t\tend: { line: number; column: number };\n\t};\n\n\t/** Fragment of the template source around the error */\n\tsource?: string;\n\n\t/** Structured information for debugging and frontend display */\n\tdetails?: DiagnosticDetails;\n}\n\n/** Complete result of the static analysis */\nexport interface AnalysisResult {\n\t/** true if no errors (warnings are tolerated) */\n\tvalid: boolean;\n\t/** List of diagnostics (errors + warnings) */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** JSON Schema describing the template's return type */\n\toutputSchema: JSONSchema7;\n}\n\n/** Lightweight validation result (without output type inference) */\nexport interface ValidationResult {\n\t/** true if no errors (warnings are tolerated) */\n\tvalid: boolean;\n\t/** List of diagnostics (errors + warnings) */\n\tdiagnostics: TemplateDiagnostic[];\n}\n\n// ─── Public Engine Options ───────────────────────────────────────────────────\n\nexport interface TemplateEngineOptions {\n\t/**\n\t * Capacity of the parsed AST cache. Each parsed template is cached\n\t * to avoid costly re-parsing on repeated calls.\n\t * @default 256\n\t */\n\tastCacheSize?: number;\n\n\t/**\n\t * Capacity of the compiled Handlebars template cache.\n\t * @default 256\n\t */\n\tcompilationCacheSize?: number;\n\n\t/**\n\t * Custom helpers to register during engine construction.\n\t *\n\t * Each entry describes a helper with its name, implementation,\n\t * expected parameters, and return type.\n\t *\n\t * @example\n\t * ```\n\t * const engine = new Typebars({\n\t * helpers: [\n\t * {\n\t * name: \"uppercase\",\n\t * description: \"Converts a string to uppercase\",\n\t * fn: (value: string) => String(value).toUpperCase(),\n\t * params: [\n\t * { name: \"value\", type: { type: \"string\" }, description: \"The string to convert\" },\n\t * ],\n\t * returnType: { type: \"string\" },\n\t * },\n\t * ],\n\t * });\n\t * ```\n\t */\n\thelpers?: HelperConfig[];\n}\n\n// ─── Execution Options ───────────────────────────────────────────────────────\n// Optional options object for `execute()`, replacing multiple positional\n// parameters for better ergonomics.\n\nexport interface ExecuteOptions {\n\t/** JSON Schema for pre-execution static validation */\n\tschema?: JSONSchema7;\n\t/** Data by identifier `{ [id]: { key: value } }` */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n\t/** Schemas by identifier (for static validation with identifiers) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n}\n\n// ─── Combined Analyze-and-Execute Options ────────────────────────────────────\n// Optional options object for `analyzeAndExecute()`, grouping parameters\n// related to template identifiers.\n\nexport interface AnalyzeAndExecuteOptions {\n\t/** Schemas by identifier `{ [id]: JSONSchema7 }` for static analysis */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Data by identifier `{ [id]: { key: value } }` for execution */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n}\n\n// ─── Custom Helpers ──────────────────────────────────────────────────────────\n// Allows registering custom helpers with their type signature for static\n// analysis support.\n\n/** Describes a parameter expected by a helper */\nexport interface HelperParam {\n\t/** Parameter name (for documentation / introspection) */\n\tname: string;\n\n\t/**\n\t * JSON Schema describing the expected type for this parameter.\n\t * Used for documentation and static validation.\n\t */\n\ttype?: JSONSchema7;\n\n\t/** Human-readable description of the parameter */\n\tdescription?: string;\n\n\t/**\n\t * Whether the parameter is optional.\n\t * @default false\n\t */\n\toptional?: boolean;\n}\n\n/**\n * Definition of a helper registerable via `registerHelper()`.\n *\n * Contains the runtime implementation and typing metadata\n * for static analysis.\n */\nexport interface HelperDefinition {\n\t/**\n\t * Runtime implementation of the helper — will be registered with Handlebars.\n\t *\n\t * For an inline helper `{{uppercase name}}`:\n\t * `(value: string) => string`\n\t *\n\t * For a block helper `{{#repeat count}}...{{/repeat}}`:\n\t * `function(this: any, count: number, options: Handlebars.HelperOptions) { ... }`\n\t */\n\t// biome-ignore lint/suspicious/noExplicitAny: Handlebars helper signatures are inherently dynamic\n\tfn: (...args: any[]) => unknown;\n\n\t/**\n\t * Parameters expected by the helper (for documentation and analysis).\n\t *\n\t * @example\n\t * ```\n\t * params: [\n\t * { name: \"value\", type: { type: \"number\" }, description: \"The value to round\" },\n\t * { name: \"precision\", type: { type: \"number\" }, description: \"Decimal places\", optional: true },\n\t * ]\n\t * ```\n\t */\n\tparams?: HelperParam[];\n\n\t/**\n\t * JSON Schema describing the helper's return type for static analysis.\n\t * @default { type: \"string\" }\n\t */\n\treturnType?: JSONSchema7;\n\n\t/** Human-readable description of the helper */\n\tdescription?: string;\n}\n\n/**\n * Full helper configuration for registration via the `Typebars({ helpers: [...] })`\n * constructor options.\n *\n * Extends `HelperDefinition` with a required `name`.\n *\n * @example\n * ```\n * const config: HelperConfig = {\n * name: \"round\",\n * description: \"Rounds a number to a given precision\",\n * fn: (value: number, precision?: number) => { ... },\n * params: [\n * { name: \"value\", type: { type: \"number\" } },\n * { name: \"precision\", type: { type: \"number\" }, optional: true },\n * ],\n * returnType: { type: \"number\" },\n * };\n * ```\n */\nexport interface HelperConfig extends HelperDefinition {\n\t/** Name of the helper as used in templates (e.g. `\"uppercase\"`) */\n\tname: string;\n}\n\n// ─── Automatic Type Inference via json-schema-to-ts ──────────────────────────\n// Allows `defineHelper()` to infer TypeScript types for `fn` arguments\n// from the JSON Schemas declared in `params`.\n\n/**\n * Param definition used for type inference.\n * Accepts `JSONSchema` from `json-schema-to-ts` to allow `FromSchema`\n * to resolve literal types.\n */\ntype TypedHelperParam = {\n\treadonly name: string;\n\treadonly type?: JSONSchema;\n\treadonly description?: string;\n\treadonly optional?: boolean;\n};\n\n/**\n * Infers the TypeScript type of a single parameter from its JSON Schema.\n * - If `optional: true`, the resolved type is unioned with `undefined`.\n * - If `type` is not provided, the type is `unknown`.\n */\ntype InferParamType<P> = P extends {\n\treadonly type: infer S extends JSONSchema;\n\treadonly optional: true;\n}\n\t? FromSchema<S> | undefined\n\t: P extends { readonly type: infer S extends JSONSchema }\n\t\t? FromSchema<S>\n\t\t: unknown;\n\n/**\n * Maps a tuple of `TypedHelperParam` to a tuple of inferred TypeScript types,\n * usable as the `fn` signature.\n *\n * @example\n * ```\n * type Args = InferArgs<readonly [\n * { name: \"a\"; type: { type: \"string\" } },\n * { name: \"b\"; type: { type: \"number\" }; optional: true },\n * ]>;\n * // => [string, number | undefined]\n * ```\n */\ntype InferArgs<P extends readonly TypedHelperParam[]> = {\n\t[K in keyof P]: InferParamType<P[K]>;\n};\n\n/**\n * Helper configuration with generic parameter inference.\n * Used exclusively by `defineHelper()`.\n */\ninterface TypedHelperConfig<P extends readonly TypedHelperParam[]> {\n\tname: string;\n\tdescription?: string;\n\tparams: P;\n\tfn: (...args: InferArgs<P>) => unknown;\n\treturnType?: JSONSchema;\n}\n\n/**\n * Creates a `HelperConfig` with automatic type inference for `fn` arguments\n * based on the JSON Schemas declared in `params`.\n *\n * The generic parameter `const P` preserves schema literal types\n * (equivalent of `as const`), enabling `FromSchema` to resolve the\n * corresponding TypeScript types.\n *\n * @example\n * ```\n * const helper = defineHelper({\n * name: \"concat\",\n * description: \"Concatenates two strings\",\n * params: [\n * { name: \"a\", type: { type: \"string\" }, description: \"First string\" },\n * { name: \"b\", type: { type: \"string\" }, description: \"Second string\" },\n * { name: \"sep\", type: { type: \"string\" }, description: \"Separator\", optional: true },\n * ],\n * fn: (a, b, sep) => {\n * // a: string, b: string, sep: string | undefined\n * const separator = sep ?? \"\";\n * return `${a}${separator}${b}`;\n * },\n * returnType: { type: \"string\" },\n * });\n * ```\n */\nexport function defineHelper<const P extends readonly TypedHelperParam[]>(\n\tconfig: TypedHelperConfig<P>,\n): HelperConfig {\n\treturn config as unknown as HelperConfig;\n}\n"
6
+ ],
7
+ "mappings": "AA8CO,SAAS,CAAc,CAC7B,EACmC,CACnC,OACC,IAAU,MAAS,OAAO,IAAU,UAAY,OAAO,IAAU,SAS5D,SAAS,CAAa,CAC5B,EAC+B,CAC/B,OAAO,IAAU,MAAQ,OAAO,IAAU,SAgBpC,SAAS,CAAoB,CACnC,EACc,CACd,GAAI,IAAU,KAAM,MAAO,CAAE,KAAM,MAAO,EAC1C,GAAI,OAAO,IAAU,UAAW,MAAO,CAAE,KAAM,SAAU,EACzD,GAAI,OAAO,IAAU,SACpB,OAAO,OAAO,UAAU,CAAK,EAAI,CAAE,KAAM,SAAU,EAAI,CAAE,KAAM,QAAS,EAKzE,MAAO,CAAE,KAAM,MAAO,EA8UhB,SAAS,CAAyD,CACxE,EACe,CACf,OAAO",
8
+ "debugId": "ABFED878C9A1EDA264756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,7 @@
1
+ class f extends Error{constructor(b){super(b);this.name="TemplateError"}}class q extends f{loc;constructor(b,h){super(`Parse error: ${b}`);this.loc=h;this.name="TemplateParseError"}}class v extends f{diagnostics;constructor(b){let h=b.filter((j)=>j.severity==="error"),k=h.map((j)=>` • ${j.message}`).join(`
2
+ `);super(`Static analysis failed with ${h.length} error(s):
3
+ ${k}`);this.diagnostics=b;this.name="TemplateAnalysisError"}}class w extends f{constructor(b){super(`Runtime error: ${b}`);this.name="TemplateRuntimeError"}}
4
+ export{f as p,q,v as r,w as s};
5
+
6
+ //# debugId=7F986ADBFE9FCFEF64756E2164756E21
7
+ //# sourceMappingURL=chunk-6c0pw73w.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts"],
4
+ "sourcesContent": [
5
+ "import type { TemplateDiagnostic } from \"./types.ts\";\n\n// ─── Classe de base ──────────────────────────────────────────────────────────\n// Toutes les erreurs du moteur de template héritent de cette classe pour\n// permettre un `catch` ciblé : `catch (e) { if (e instanceof TemplateError) … }`\n\nexport class TemplateError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"TemplateError\";\n\t}\n}\n\n// ─── Erreur de parsing ───────────────────────────────────────────────────────\n// Levée quand Handlebars ne parvient pas à parser le template (syntaxe invalide).\n\nexport class TemplateParseError extends TemplateError {\n\tconstructor(\n\t\tmessage: string,\n\t\t/** Position approximative de l'erreur dans le source */\n\t\tpublic readonly loc?: { line: number; column: number },\n\t) {\n\t\tsuper(`Parse error: ${message}`);\n\t\tthis.name = \"TemplateParseError\";\n\t}\n}\n\n// ─── Erreur d'analyse statique ───────────────────────────────────────────────\n// Levée en mode strict quand l'analyse produit au moins une erreur.\n\nexport class TemplateAnalysisError extends TemplateError {\n\tconstructor(\n\t\t/** Liste complète des diagnostics (erreurs + warnings) */\n\t\tpublic readonly diagnostics: TemplateDiagnostic[],\n\t) {\n\t\tconst errors = diagnostics.filter((d) => d.severity === \"error\");\n\t\tconst summary = errors.map((d) => ` • ${d.message}`).join(\"\\n\");\n\t\tsuper(`Static analysis failed with ${errors.length} error(s):\\n${summary}`);\n\t\tthis.name = \"TemplateAnalysisError\";\n\t}\n}\n\n// ─── Erreur d'exécution ──────────────────────────────────────────────────────\n// Levée quand l'exécution du template échoue (accès à une propriété\n// inexistante en mode strict, type inattendu, etc.).\n\nexport class TemplateRuntimeError extends TemplateError {\n\tconstructor(message: string) {\n\t\tsuper(`Runtime error: ${message}`);\n\t\tthis.name = \"TemplateRuntimeError\";\n\t}\n}\n"
6
+ ],
7
+ "mappings": "AAMO,MAAM,UAAsB,KAAM,CACxC,WAAW,CAAC,EAAiB,CAC5B,MAAM,CAAO,EACb,KAAK,KAAO,gBAEd,CAKO,MAAM,UAA2B,CAAc,CAIpC,IAHjB,WAAW,CACV,EAEgB,EACf,CACD,MAAM,gBAAgB,GAAS,EAFf,WAGhB,KAAK,KAAO,qBAEd,CAKO,MAAM,UAA8B,CAAc,CAGvC,YAFjB,WAAW,CAEM,EACf,CACD,IAAM,EAAS,EAAY,OAAO,CAAC,IAAM,EAAE,WAAa,OAAO,EACzD,EAAU,EAAO,IAAI,CAAC,IAAM,OAAM,EAAE,SAAS,EAAE,KAAK;AAAA,CAAI,EAC9D,MAAM,+BAA+B,EAAO;AAAA,EAAqB,GAAS,EAJ1D,mBAKhB,KAAK,KAAO,wBAEd,CAMO,MAAM,UAA6B,CAAc,CACvD,WAAW,CAAC,EAAiB,CAC5B,MAAM,kBAAkB,GAAS,EACjC,KAAK,KAAO,uBAEd",
8
+ "debugId": "7F986ADBFE9FCFEF64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ import{b as C}from"./chunk-kznb0bev.js";import{d as z}from"./chunk-1qwj7pjc.js";import{g as L}from"./chunk-qpzzr2rd.js";import{j as Q,k as W,l as _}from"./chunk-6955jpr7.js";import{n as k}from"./chunk-1gm6cf0e.js";import{B as U}from"./chunk-ybh51hbe.js";import{N,Q as E,R as I}from"./chunk-4zv02svp.js";import P from"handlebars";class Y{_definitions=null;_helperNames=null;_helperNamesSet=null;getDefinitions(){if(!this._definitions)this._definitions=new Map,this.buildDefinitions(this._definitions);return this._definitions}getHelperNames(){if(!this._helperNames)this._helperNames=[...this.getDefinitions().keys()];return this._helperNames}isHelper(w){if(!this._helperNamesSet)this._helperNamesSet=new Set(this.getHelperNames());return this._helperNamesSet.has(w)}register(w){for(let[M,H]of this.getDefinitions())w.registerHelper(M,H)}unregister(w){for(let M of this.getHelperNames())w.unregisterHelper(M)}}function B(w,M=NaN){if(typeof w==="number")return w;if(typeof w==="string"){let H=Number(w);return Number.isNaN(H)?M:H}if(typeof w==="boolean")return w?1:0;return M}var j=new Set(["==","===","!=","!==","<","<=",">",">="]);function T(w,M,H){switch(M){case"==":return w==H;case"===":return w===H;case"!=":return w!=H;case"!==":return w!==H;case"<":return B(w)<B(H);case"<=":return B(w)<=B(H);case">":return B(w)>B(H);case">=":return B(w)>=B(H)}}function A(w){return w!==null&&typeof w==="object"&&"hash"in w&&"name"in w}class Z extends Y{buildDefinitions(w){this.registerEquality(w),this.registerComparison(w),this.registerLogicalOperators(w),this.registerCollectionHelpers(w),this.registerGenericCompare(w)}registerEquality(w){w.set("eq",{fn:(H,V)=>H===V,params:[{name:"a",description:"Left value"},{name:"b",description:"Right value"}],returnType:{type:"boolean"},description:"Returns true if a is strictly equal to b: {{#if (eq a b)}}"});let M={fn:(H,V)=>H!==V,params:[{name:"a",description:"Left value"},{name:"b",description:"Right value"}],returnType:{type:"boolean"},description:"Returns true if a is not strictly equal to b: {{#if (ne a b)}}"};w.set("ne",M),w.set("neq",M)}registerComparison(w){w.set("lt",{fn:(V,q)=>B(V)<B(q),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a < b: {{#if (lt a b)}}"});let M={fn:(V,q)=>B(V)<=B(q),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a <= b: {{#if (lte a b)}}"};w.set("lte",M),w.set("le",M),w.set("gt",{fn:(V,q)=>B(V)>B(q),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a > b: {{#if (gt a b)}}"});let H={fn:(V,q)=>B(V)>=B(q),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a >= b: {{#if (gte a b)}}"};w.set("gte",H),w.set("ge",H)}registerLogicalOperators(w){w.set("not",{fn:(M)=>!M,params:[{name:"value",description:"Value to negate"}],returnType:{type:"boolean"},description:"Returns true if the value is falsy: {{#if (not active)}}"}),w.set("and",{fn:(M,H)=>!!M&&!!H,params:[{name:"a",description:"First condition"},{name:"b",description:"Second condition"}],returnType:{type:"boolean"},description:"Returns true if both values are truthy: {{#if (and a b)}}"}),w.set("or",{fn:(M,H)=>!!M||!!H,params:[{name:"a",description:"First condition"},{name:"b",description:"Second condition"}],returnType:{type:"boolean"},description:"Returns true if at least one value is truthy: {{#if (or a b)}}"})}registerCollectionHelpers(w){w.set("contains",{fn:(M,H)=>{if(typeof M==="string")return M.includes(String(H));if(Array.isArray(M))return M.includes(H);return!1},params:[{name:"haystack",description:"String or array to search in"},{name:"needle",description:"Value to search for"}],returnType:{type:"boolean"},description:'Checks if a string contains a substring or an array contains an element: {{#if (contains name "ali")}}'}),w.set("in",{fn:(...M)=>{if(M.length<2)return!1;let H=M[0];return M.slice(1).filter((q)=>!A(q)).some((q)=>q===H)},params:[{name:"value",description:"Value to look for"},{name:"candidates",description:'One or more candidate values (variadic): {{#if (in status "active" "pending")}}'}],returnType:{type:"boolean"},description:'Checks if a value is one of the provided options: {{#if (in status "active" "pending" "draft")}}'})}registerGenericCompare(w){w.set("compare",{fn:(M,H,V)=>{let q=String(H);if(!j.has(q))throw Error(`[compare helper] Unknown operator "${q}". Supported: ${[...j].join(", ")} `);return T(M,q,V)},params:[{name:"a",description:"Left operand"},{name:"operator",type:{type:"string",enum:["==","===","!=","!==","<","<=",">",">="]},description:'Comparison operator: "==", "===", "!=", "!==", "<", "<=", ">", ">="'},{name:"b",description:"Right operand"}],returnType:{type:"boolean"},description:'Generic comparison helper with operator as parameter: {{#if (compare a "<" b)}}. Supported operators: ==, ===, !=, !==, <, <=, >, >='})}}var F=new Set(["+","-","*","/","%","**"]),x=(w)=>B(w,0);function g(w,M,H){switch(M){case"+":return w+H;case"-":return w-H;case"*":return w*H;case"/":return H===0?1/0:w/H;case"%":return H===0?NaN:w%H;case"**":return w**H}}class $ extends Y{buildDefinitions(w){this.registerBinaryOperators(w),this.registerUnaryFunctions(w),this.registerMinMax(w),this.registerGenericMath(w)}registerBinaryOperators(w){let M={fn:(G,K)=>x(G)+x(K),params:[{name:"a",type:{type:"number"},description:"First operand"},{name:"b",type:{type:"number"},description:"Second operand"}],returnType:{type:"number"},description:"Adds two numbers: {{ add a b }}"};w.set("add",M);let H={fn:(G,K)=>x(G)-x(K),params:[{name:"a",type:{type:"number"},description:"Value to subtract from"},{name:"b",type:{type:"number"},description:"Value to subtract"}],returnType:{type:"number"},description:"Subtracts b from a: {{ subtract a b }}"};w.set("subtract",H),w.set("sub",H);let V={fn:(G,K)=>x(G)*x(K),params:[{name:"a",type:{type:"number"},description:"First factor"},{name:"b",type:{type:"number"},description:"Second factor"}],returnType:{type:"number"},description:"Multiplies two numbers: {{ multiply a b }}"};w.set("multiply",V),w.set("mul",V);let q={fn:(G,K)=>{let X=x(K);return X===0?1/0:x(G)/X},params:[{name:"a",type:{type:"number"},description:"Dividend"},{name:"b",type:{type:"number"},description:"Divisor"}],returnType:{type:"number"},description:"Divides a by b: {{ divide a b }}. Returns Infinity if b is 0."};w.set("divide",q),w.set("div",q);let J={fn:(G,K)=>{let X=x(K);return X===0?NaN:x(G)%X},params:[{name:"a",type:{type:"number"},description:"Dividend"},{name:"b",type:{type:"number"},description:"Divisor"}],returnType:{type:"number"},description:"Returns the remainder of a divided by b: {{ modulo a b }}"};w.set("modulo",J),w.set("mod",J),w.set("pow",{fn:(G,K)=>x(G)**x(K),params:[{name:"base",type:{type:"number"},description:"The base"},{name:"exponent",type:{type:"number"},description:"The exponent"}],returnType:{type:"number"},description:"Raises base to the power of exponent: {{ pow base exponent }}"})}registerUnaryFunctions(w){w.set("abs",{fn:(M)=>Math.abs(x(M)),params:[{name:"value",type:{type:"number"},description:"The number"}],returnType:{type:"number"},description:"Returns the absolute value: {{ abs value }}"}),w.set("ceil",{fn:(M)=>Math.ceil(x(M)),params:[{name:"value",type:{type:"number"},description:"The number to round up"}],returnType:{type:"number"},description:"Rounds up to the nearest integer: {{ ceil value }}"}),w.set("floor",{fn:(M)=>Math.floor(x(M)),params:[{name:"value",type:{type:"number"},description:"The number to round down"}],returnType:{type:"number"},description:"Rounds down to the nearest integer: {{ floor value }}"}),w.set("round",{fn:(M,H)=>{let V=x(M);if(H===void 0||H===null||typeof H==="object")return Math.round(V);let J=10**x(H);return Math.round(V*J)/J},params:[{name:"value",type:{type:"number"},description:"The number to round"},{name:"precision",type:{type:"number"},description:"Number of decimal places (default: 0)",optional:!0}],returnType:{type:"number"},description:"Rounds to the nearest integer or to a given precision: {{ round value }} or {{ round value 2 }}"}),w.set("sqrt",{fn:(M)=>Math.sqrt(x(M)),params:[{name:"value",type:{type:"number"},description:"The number"}],returnType:{type:"number"},description:"Returns the square root: {{ sqrt value }}"})}registerMinMax(w){w.set("min",{fn:(M,H)=>Math.min(x(M),x(H)),params:[{name:"a",type:{type:"number"},description:"First number"},{name:"b",type:{type:"number"},description:"Second number"}],returnType:{type:"number"},description:"Returns the smaller of two numbers: {{ min a b }}"}),w.set("max",{fn:(M,H)=>Math.max(x(M),x(H)),params:[{name:"a",type:{type:"number"},description:"First number"},{name:"b",type:{type:"number"},description:"Second number"}],returnType:{type:"number"},description:"Returns the larger of two numbers: {{ max a b }}"})}registerGenericMath(w){w.set("math",{fn:(M,H,V)=>{let q=String(H);if(!F.has(q))throw Error(`[math helper] Unknown operator "${q}". Supported: ${[...F].join(", ")} `);return g(x(M),q,x(V))},params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"operator",type:{type:"string",enum:["+","-","*","/","%","**"]},description:'Arithmetic operator: "+", "-", "*", "/", "%", "**"'},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"number"},description:'Generic math helper with operator as parameter: {{ math a "+" b }}, {{ math a "/" b }}. Supported operators: +, -, *, /, %, **'})}}class R{hbs;astCache;compilationCache;helpers=new Map;constructor(w={}){if(this.hbs=P.create(),this.astCache=new N(w.astCacheSize??256),this.compilationCache=new N(w.compilationCacheSize??256),new $().register(this),new Z().register(this),w.helpers)for(let M of w.helpers){let{name:H,...V}=M;this.registerHelper(H,V)}}compile(w){if(W(w)){let V={};for(let[q,J]of Object.entries(w))V[q]=this.compile(J);return C.fromObject(V,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if(Q(w))return C.fromLiteral(w,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache});let M=this.getCachedAst(w),H={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return C.fromTemplate(M,w,H)}analyze(w,M,H){if(W(w))return E(Object.keys(w),(q)=>this.analyze(w[q],M,H));if(Q(w))return{valid:!0,diagnostics:[],outputSchema:_(w)};let V=this.getCachedAst(w);return z(V,w,M,{identifierSchemas:H,helpers:this.helpers})}validate(w,M,H){let V=this.analyze(w,M,H);return{valid:V.valid,diagnostics:V.diagnostics}}isValidSyntax(w){if(W(w))return Object.values(w).every((M)=>this.isValidSyntax(M));if(Q(w))return!0;try{return this.getCachedAst(w),!0}catch{return!1}}execute(w,M,H){if(W(w)){let q={};for(let[J,G]of Object.entries(w))q[J]=this.execute(G,M,H);return q}if(Q(w))return w;let V=this.getCachedAst(w);if(H?.schema){let q=z(V,w,H.schema,{identifierSchemas:H.identifierSchemas,helpers:this.helpers});if(!q.valid)throw new U(q.diagnostics)}return L(V,w,M,{identifierData:H?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache})}analyzeAndExecute(w,M,H,V){if(W(w))return I(Object.keys(w),(K)=>this.analyzeAndExecute(w[K],M,H,V));if(Q(w))return{analysis:{valid:!0,diagnostics:[],outputSchema:_(w)},value:w};let q=this.getCachedAst(w),J=z(q,w,M,{identifierSchemas:V?.identifierSchemas,helpers:this.helpers});if(!J.valid)return{analysis:J,value:void 0};let G=L(q,w,H,{identifierData:V?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache});return{analysis:J,value:G}}registerHelper(w,M){return this.helpers.set(w,M),this.hbs.registerHelper(w,M.fn),this.compilationCache.clear(),this}unregisterHelper(w){return this.helpers.delete(w),this.hbs.unregisterHelper(w),this.compilationCache.clear(),this}hasHelper(w){return this.helpers.has(w)}clearCaches(){this.astCache.clear(),this.compilationCache.clear()}getCachedAst(w){let M=this.astCache.get(w);if(!M)M=k(w),this.astCache.set(w,M);return M}}
2
+ export{R as a};
3
+
4
+ //# debugId=D5BDDDBE699F089564756E2164756E21
5
+ //# sourceMappingURL=chunk-7j6q1e3z.js.map