typebars 1.0.23 → 1.0.25

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { AnalyzeOptions } from \"./analyzer.ts\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport {\n\tdispatchAnalyze,\n\tdispatchAnalyzeAndExecute,\n\tdispatchExecute,\n} from \"./dispatch.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport { LogicalHelpers, MapHelpers, MathHelpers } from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateData,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport { isArrayInput, isLiteralInput, isObjectInput } from \"./types.ts\";\nimport { LRUCache } from \"./utils\";\n\n// ─── Typebars ────────────────────────────────────────────────────────────────\n// Public entry point of the template engine. Orchestrates three phases:\n//\n// 1. **Parsing** — transforms the template string into an AST (via Handlebars)\n// 2. **Analysis** — static validation + return type inference\n// 3. **Execution** — renders the template with real data\n//\n// ─── Architecture v2 ─────────────────────────────────────────────────────────\n// - **LRU cache** for parsed ASTs and compiled Handlebars templates\n// - **Isolated Handlebars environment** per instance (custom helpers)\n// - **`compile()` pattern**: parse-once / execute-many\n// - **`validate()` method**: API shortcut without `outputSchema`\n// - **`registerHelper()`**: custom helpers with static typing\n// - **`ExecuteOptions`**: options object for `execute()`\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing variables from specific data\n// sources, identified by an integer N.\n//\n// - `identifierSchemas`: mapping `{ [id]: JSONSchema7 }` for static analysis\n// - `identifierData`: mapping `{ [id]: Record<string, unknown> }` for execution\n//\n// Usage:\n// engine.execute(\"{{meetingId:1}}\", data, { identifierData: { 1: node1Data } });\n// engine.analyze(\"{{meetingId:1}}\", schema, { identifierSchemas: { 1: node1Schema } });\n\n// ─── Direct Execution Helper Names ───────────────────────────────────────────\n// Helpers whose return values are non-primitive (arrays, objects) and must be\n// wrapped when registered with Handlebars so that mixed/block templates get a\n// human-readable string (e.g. `\", \"` joined) instead of JS default toString().\n// In single-expression mode the executor bypasses Handlebars entirely, so the\n// raw value is preserved.\nconst DIRECT_EXECUTION_HELPER_NAMES = new Set<string>([\n\tMapHelpers.MAP_HELPER_NAME,\n]);\n\n/**\n * Converts a value to a string suitable for embedding in a Handlebars template.\n *\n * - Arrays of primitives are joined with `\", \"`\n * - Arrays of objects are JSON-stringified per element, then joined\n * - Primitives use `String(value)`\n * - null/undefined become `\"\"`\n */\nfunction stringifyForTemplate(value: unknown): string {\n\tif (value === null || value === undefined) return \"\";\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map((item) => {\n\t\t\t\tif (item === null || item === undefined) return \"\";\n\t\t\t\tif (typeof item === \"object\") return JSON.stringify(item);\n\t\t\t\treturn String(item);\n\t\t\t})\n\t\t\t.join(\", \");\n\t}\n\treturn String(value);\n}\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n\nexport class Typebars {\n\t/** Isolated Handlebars environment — each engine has its own helpers */\n\tprivate readonly hbs: typeof Handlebars;\n\n\t/** LRU cache of parsed ASTs (avoids re-parsing) */\n\tprivate readonly astCache: LRUCache<string, hbs.AST.Program>;\n\n\t/** LRU cache of compiled Handlebars templates (avoids recompilation) */\n\tprivate readonly compilationCache: LRUCache<\n\t\tstring,\n\t\tHandlebarsTemplateDelegate\n\t>;\n\n\t/** Custom helpers registered on this instance */\n\tprivate readonly helpers = new Map<string, HelperDefinition>();\n\n\tconstructor(options: TemplateEngineOptions = {}) {\n\t\tthis.hbs = Handlebars.create();\n\t\tthis.astCache = new LRUCache(options.astCacheSize ?? 256);\n\t\tthis.compilationCache = new LRUCache(options.compilationCacheSize ?? 256);\n\n\t\t// ── Built-in helpers ─────────────────────────────────────────────\n\t\tnew MathHelpers().register(this);\n\t\tnew LogicalHelpers().register(this);\n\t\tnew MapHelpers().register(this);\n\n\t\t// ── Custom helpers via options ───────────────────────────────────\n\t\tif (options.helpers) {\n\t\t\tfor (const helper of options.helpers) {\n\t\t\t\tconst { name, ...definition } = helper;\n\t\t\t\tthis.registerHelper(name, definition);\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Compilation ───────────────────────────────────────────────────────\n\n\t/**\n\t * Compiles a template and returns a `CompiledTemplate` ready to be\n\t * executed or analyzed without re-parsing.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is compiled recursively.\n\t *\n\t * @param template - The template to compile\n\t * @returns A reusable `CompiledTemplate`\n\t */\n\tcompile(template: TemplateInput): CompiledTemplate {\n\t\tif (isArrayInput(template)) {\n\t\t\tconst children: CompiledTemplate[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tchildren.push(this.compile(element));\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromArray(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\tconst children: Record<string, CompiledTemplate> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tchildren[key] = this.compile(value);\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromObject(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn CompiledTemplate.fromLiteral(template, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst options: CompiledTemplateOptions = {\n\t\t\thelpers: this.helpers,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t};\n\t\treturn CompiledTemplate.fromTemplate(ast, template, options);\n\t}\n\n\t// ─── Static Analysis ─────────────────────────────────────────────────────\n\n\t/**\n\t * Statically analyzes a template against a JSON Schema v7 describing\n\t * the available context.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is analyzed recursively and the\n\t * `outputSchema` reflects the object structure with resolved types.\n\t *\n\t * @param template - The template to analyze\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): AnalysisResult {\n\t\treturn dispatchAnalyze(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse with cache and analyze the AST\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\treturn analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyze() for child elements\n\t\t\t(child, childOptions) => this.analyze(child, inputSchema, childOptions),\n\t\t);\n\t}\n\n\t// ─── Validation ──────────────────────────────────────────────────────────\n\n\t/**\n\t * Validates a template against a schema without returning the output type.\n\t *\n\t * This is an API shortcut for `analyze()` that only returns `valid` and\n\t * `diagnostics`, without `outputSchema`. The full analysis (including type\n\t * inference) is executed internally — this method provides no performance\n\t * gain, only a simplified API.\n\t *\n\t * @param template - The template to validate\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param identifierSchemas - (optional) Schemas by identifier\n\t */\n\tvalidate(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, options);\n\t\treturn {\n\t\t\tvalid: analysis.valid,\n\t\t\tdiagnostics: analysis.diagnostics,\n\t\t};\n\t}\n\n\t// ─── Syntax Validation ───────────────────────────────────────────────────\n\n\t/**\n\t * Checks only that the template syntax is valid (parsing).\n\t * Does not require a schema — useful for quick feedback in an editor.\n\t *\n\t * For objects, recursively checks each property.\n\t *\n\t * @param template - The template to validate\n\t * @returns `true` if the template is syntactically correct\n\t */\n\tisValidSyntax(template: TemplateInput): boolean {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn template.every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn Object.values(template).every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isLiteralInput(template)) return true;\n\t\ttry {\n\t\t\tthis.getCachedAst(template);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ─── Execution ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Executes a template with the provided data.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is executed recursively and an object with\n\t * resolved values is returned.\n\t *\n\t * If a `schema` is provided in options, static analysis is performed\n\t * before execution. A `TemplateAnalysisError` is thrown on errors.\n\t *\n\t * @param template - The template to execute\n\t * @param data - The context data for rendering\n\t * @param options - Execution options (schema, identifierData, identifierSchemas)\n\t * @returns The execution result\n\t */\n\texecute(\n\t\ttemplate: TemplateInput,\n\t\tdata?: TemplateData,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\treturn dispatchExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse, optionally validate, then execute\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\n\t\t\t\t// Pre-execution static validation if a schema is provided\n\t\t\t\tif (options?.schema) {\n\t\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, options.schema, {\n\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\t});\n\t\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter execute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.execute(child, data, { ...options, ...childOptions }),\n\t\t);\n\t}\n\n\t// ─── Combined Shortcuts ──────────────────────────────────────────────────\n\n\t/**\n\t * Analyzes a template and, if valid, executes it with the provided data.\n\t * Returns both the analysis result and the executed value.\n\t *\n\t * For objects, each property is analyzed and executed recursively.\n\t * The entire object is considered invalid if at least one property is.\n\t *\n\t * @param template - The template\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param data - The context data for rendering\n\t * @param options - (optional) Options for template identifiers\n\t * @returns An object `{ analysis, value }` where `value` is `undefined`\n\t * if analysis failed.\n\t */\n\tanalyzeAndExecute(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\tdata: TemplateData,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\treturn dispatchAnalyzeAndExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — analyze then execute if valid\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\n\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\treturn { analysis, value: undefined };\n\t\t\t\t}\n\n\t\t\t\tconst value = executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t\treturn { analysis, value };\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyzeAndExecute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.analyzeAndExecute(child, inputSchema, data, childOptions),\n\t\t);\n\t}\n\n\t// ─── Custom Helper Management ──────────────────────────────────────────\n\n\t/**\n\t * Registers a custom helper on this engine instance.\n\t *\n\t * The helper is available for both execution (via Handlebars) and\n\t * static analysis (via its declared `returnType`).\n\t *\n\t * @param name - Helper name (e.g. `\"uppercase\"`)\n\t * @param definition - Helper definition (implementation + return type)\n\t * @returns `this` to allow chaining\n\t */\n\tregisterHelper(name: string, definition: HelperDefinition): this {\n\t\tthis.helpers.set(name, definition);\n\n\t\t// For helpers that return non-primitive values (arrays, objects),\n\t\t// register a Handlebars-friendly wrapper that converts the result\n\t\t// to a string. In single-expression mode the executor bypasses\n\t\t// Handlebars entirely (via tryDirectHelperExecution) and returns\n\t\t// the raw value, so this wrapper only affects mixed/block templates\n\t\t// where Handlebars renders the result into a larger string.\n\t\tif (DIRECT_EXECUTION_HELPER_NAMES.has(name)) {\n\t\t\tthis.hbs.registerHelper(name, (...args: unknown[]) => {\n\t\t\t\t// Handlebars appends an `options` object as the last argument.\n\t\t\t\t// Strip it before calling the real fn.\n\t\t\t\tconst hbsArgs = args.slice(0, -1);\n\t\t\t\tconst raw = definition.fn(...hbsArgs);\n\t\t\t\treturn stringifyForTemplate(raw);\n\t\t\t});\n\t\t} else {\n\t\t\tthis.hbs.registerHelper(name, definition.fn);\n\t\t}\n\n\t\t// Invalidate the compilation cache because helpers have changed\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a custom helper from this engine instance.\n\t *\n\t * @param name - Name of the helper to remove\n\t * @returns `this` to allow chaining\n\t */\n\tunregisterHelper(name: string): this {\n\t\tthis.helpers.delete(name);\n\t\tthis.hbs.unregisterHelper(name);\n\n\t\t// Invalidate the compilation cache\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Checks whether a helper is registered on this instance.\n\t *\n\t * @param name - Helper name\n\t * @returns `true` if the helper is registered\n\t */\n\thasHelper(name: string): boolean {\n\t\treturn this.helpers.has(name);\n\t}\n\n\t// ─── Cache Management ──────────────────────────────────────────────────\n\n\t/**\n\t * Clears all internal caches (AST + compilation).\n\t *\n\t * Useful after a configuration change or to free memory.\n\t */\n\tclearCaches(): void {\n\t\tthis.astCache.clear();\n\t\tthis.compilationCache.clear();\n\t}\n\n\t// ─── Internals ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Retrieves the AST of a template from the cache, or parses and caches it.\n\t */\n\tprivate getCachedAst(template: string): hbs.AST.Program {\n\t\tlet ast = this.astCache.get(template);\n\t\tif (!ast) {\n\t\t\tast = parse(template);\n\t\t\tthis.astCache.set(template, ast);\n\t\t}\n\t\treturn ast;\n\t}\n}\n"],"names":["Typebars","DIRECT_EXECUTION_HELPER_NAMES","Set","MapHelpers","MAP_HELPER_NAME","stringifyForTemplate","value","undefined","Array","isArray","map","item","JSON","stringify","String","join","compile","template","isArrayInput","children","element","push","CompiledTemplate","fromArray","helpers","hbs","compilationCache","isObjectInput","key","Object","entries","fromObject","isLiteralInput","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","dispatchAnalyze","tpl","coerceSchema","analyzeFromAst","identifierSchemas","child","childOptions","validate","analysis","valid","diagnostics","isValidSyntax","every","v","values","execute","data","dispatchExecute","schema","TemplateAnalysisError","executeFromAst","identifierData","analyzeAndExecute","dispatchAnalyzeAndExecute","registerHelper","name","definition","set","has","args","hbsArgs","slice","raw","fn","clear","unregisterHelper","delete","hasHelper","clearCaches","astCache","get","parse","Map","Handlebars","create","LRUCache","astCacheSize","compilationCacheSize","MathHelpers","register","LogicalHelpers","helper"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { AnalyzeOptions } from \"./analyzer.ts\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport {\n\tdispatchAnalyze,\n\tdispatchAnalyzeAndExecute,\n\tdispatchExecute,\n} from \"./dispatch.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport {\n\tDefaultHelpers,\n\tLogicalHelpers,\n\tMapHelpers,\n\tMathHelpers,\n} from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateData,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport { isArrayInput, isLiteralInput, isObjectInput } from \"./types.ts\";\nimport { LRUCache } from \"./utils\";\n\n// ─── Typebars ────────────────────────────────────────────────────────────────\n// Public entry point of the template engine. Orchestrates three phases:\n//\n// 1. **Parsing** — transforms the template string into an AST (via Handlebars)\n// 2. **Analysis** — static validation + return type inference\n// 3. **Execution** — renders the template with real data\n//\n// ─── Architecture v2 ─────────────────────────────────────────────────────────\n// - **LRU cache** for parsed ASTs and compiled Handlebars templates\n// - **Isolated Handlebars environment** per instance (custom helpers)\n// - **`compile()` pattern**: parse-once / execute-many\n// - **`validate()` method**: API shortcut without `outputSchema`\n// - **`registerHelper()`**: custom helpers with static typing\n// - **`ExecuteOptions`**: options object for `execute()`\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing variables from specific data\n// sources, identified by an integer N.\n//\n// - `identifierSchemas`: mapping `{ [id]: JSONSchema7 }` for static analysis\n// - `identifierData`: mapping `{ [id]: Record<string, unknown> }` for execution\n//\n// Usage:\n// engine.execute(\"{{meetingId:1}}\", data, { identifierData: { 1: node1Data } });\n// engine.analyze(\"{{meetingId:1}}\", schema, { identifierSchemas: { 1: node1Schema } });\n\n// ─── Direct Execution Helper Names ───────────────────────────────────────────\n// Helpers whose return values are non-primitive (arrays, objects) and must be\n// wrapped when registered with Handlebars so that mixed/block templates get a\n// human-readable string (e.g. `\", \"` joined) instead of JS default toString().\n// In single-expression mode the executor bypasses Handlebars entirely, so the\n// raw value is preserved.\nconst DIRECT_EXECUTION_HELPER_NAMES = new Set<string>([\n\tMapHelpers.MAP_HELPER_NAME,\n]);\n\n/**\n * Converts a value to a string suitable for embedding in a Handlebars template.\n *\n * - Arrays of primitives are joined with `\", \"`\n * - Arrays of objects are JSON-stringified per element, then joined\n * - Primitives use `String(value)`\n * - null/undefined become `\"\"`\n */\nfunction stringifyForTemplate(value: unknown): string {\n\tif (value === null || value === undefined) return \"\";\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map((item) => {\n\t\t\t\tif (item === null || item === undefined) return \"\";\n\t\t\t\tif (typeof item === \"object\") return JSON.stringify(item);\n\t\t\t\treturn String(item);\n\t\t\t})\n\t\t\t.join(\", \");\n\t}\n\treturn String(value);\n}\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n\nexport class Typebars {\n\t/** Isolated Handlebars environment — each engine has its own helpers */\n\tprivate readonly hbs: typeof Handlebars;\n\n\t/** LRU cache of parsed ASTs (avoids re-parsing) */\n\tprivate readonly astCache: LRUCache<string, hbs.AST.Program>;\n\n\t/** LRU cache of compiled Handlebars templates (avoids recompilation) */\n\tprivate readonly compilationCache: LRUCache<\n\t\tstring,\n\t\tHandlebarsTemplateDelegate\n\t>;\n\n\t/** Custom helpers registered on this instance */\n\tprivate readonly helpers = new Map<string, HelperDefinition>();\n\n\tconstructor(options: TemplateEngineOptions = {}) {\n\t\tthis.hbs = Handlebars.create();\n\t\tthis.astCache = new LRUCache(options.astCacheSize ?? 256);\n\t\tthis.compilationCache = new LRUCache(options.compilationCacheSize ?? 256);\n\n\t\t// ── Built-in helpers ─────────────────────────────────────────────\n\t\tnew MathHelpers().register(this);\n\t\tnew LogicalHelpers().register(this);\n\t\tnew MapHelpers().register(this);\n\t\tnew DefaultHelpers().register(this);\n\n\t\t// ── Custom helpers via options ───────────────────────────────────\n\t\tif (options.helpers) {\n\t\t\tfor (const helper of options.helpers) {\n\t\t\t\tconst { name, ...definition } = helper;\n\t\t\t\tthis.registerHelper(name, definition);\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Compilation ───────────────────────────────────────────────────────\n\n\t/**\n\t * Compiles a template and returns a `CompiledTemplate` ready to be\n\t * executed or analyzed without re-parsing.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is compiled recursively.\n\t *\n\t * @param template - The template to compile\n\t * @returns A reusable `CompiledTemplate`\n\t */\n\tcompile(template: TemplateInput): CompiledTemplate {\n\t\tif (isArrayInput(template)) {\n\t\t\tconst children: CompiledTemplate[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tchildren.push(this.compile(element));\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromArray(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\tconst children: Record<string, CompiledTemplate> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tchildren[key] = this.compile(value);\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromObject(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn CompiledTemplate.fromLiteral(template, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst options: CompiledTemplateOptions = {\n\t\t\thelpers: this.helpers,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t};\n\t\treturn CompiledTemplate.fromTemplate(ast, template, options);\n\t}\n\n\t// ─── Static Analysis ─────────────────────────────────────────────────────\n\n\t/**\n\t * Statically analyzes a template against a JSON Schema v7 describing\n\t * the available context.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is analyzed recursively and the\n\t * `outputSchema` reflects the object structure with resolved types.\n\t *\n\t * @param template - The template to analyze\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): AnalysisResult {\n\t\treturn dispatchAnalyze(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse with cache and analyze the AST\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\treturn analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyze() for child elements\n\t\t\t(child, childOptions) => this.analyze(child, inputSchema, childOptions),\n\t\t);\n\t}\n\n\t// ─── Validation ──────────────────────────────────────────────────────────\n\n\t/**\n\t * Validates a template against a schema without returning the output type.\n\t *\n\t * This is an API shortcut for `analyze()` that only returns `valid` and\n\t * `diagnostics`, without `outputSchema`. The full analysis (including type\n\t * inference) is executed internally — this method provides no performance\n\t * gain, only a simplified API.\n\t *\n\t * @param template - The template to validate\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param identifierSchemas - (optional) Schemas by identifier\n\t */\n\tvalidate(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, options);\n\t\treturn {\n\t\t\tvalid: analysis.valid,\n\t\t\tdiagnostics: analysis.diagnostics,\n\t\t};\n\t}\n\n\t// ─── Syntax Validation ───────────────────────────────────────────────────\n\n\t/**\n\t * Checks only that the template syntax is valid (parsing).\n\t * Does not require a schema — useful for quick feedback in an editor.\n\t *\n\t * For objects, recursively checks each property.\n\t *\n\t * @param template - The template to validate\n\t * @returns `true` if the template is syntactically correct\n\t */\n\tisValidSyntax(template: TemplateInput): boolean {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn template.every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn Object.values(template).every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isLiteralInput(template)) return true;\n\t\ttry {\n\t\t\tthis.getCachedAst(template);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ─── Execution ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Executes a template with the provided data.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is executed recursively and an object with\n\t * resolved values is returned.\n\t *\n\t * If a `schema` is provided in options, static analysis is performed\n\t * before execution. A `TemplateAnalysisError` is thrown on errors.\n\t *\n\t * @param template - The template to execute\n\t * @param data - The context data for rendering\n\t * @param options - Execution options (schema, identifierData, identifierSchemas)\n\t * @returns The execution result\n\t */\n\texecute(\n\t\ttemplate: TemplateInput,\n\t\tdata?: TemplateData,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\treturn dispatchExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse, optionally validate, then execute\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\n\t\t\t\t// Pre-execution static validation if a schema is provided\n\t\t\t\tif (options?.schema) {\n\t\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, options.schema, {\n\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\t});\n\t\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter execute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.execute(child, data, { ...options, ...childOptions }),\n\t\t);\n\t}\n\n\t// ─── Combined Shortcuts ──────────────────────────────────────────────────\n\n\t/**\n\t * Analyzes a template and, if valid, executes it with the provided data.\n\t * Returns both the analysis result and the executed value.\n\t *\n\t * For objects, each property is analyzed and executed recursively.\n\t * The entire object is considered invalid if at least one property is.\n\t *\n\t * @param template - The template\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param data - The context data for rendering\n\t * @param options - (optional) Options for template identifiers\n\t * @returns An object `{ analysis, value }` where `value` is `undefined`\n\t * if analysis failed.\n\t */\n\tanalyzeAndExecute(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\tdata: TemplateData,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\treturn dispatchAnalyzeAndExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — analyze then execute if valid\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\n\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\treturn { analysis, value: undefined };\n\t\t\t\t}\n\n\t\t\t\tconst value = executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t\treturn { analysis, value };\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyzeAndExecute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.analyzeAndExecute(child, inputSchema, data, childOptions),\n\t\t);\n\t}\n\n\t// ─── Custom Helper Management ──────────────────────────────────────────\n\n\t/**\n\t * Registers a custom helper on this engine instance.\n\t *\n\t * The helper is available for both execution (via Handlebars) and\n\t * static analysis (via its declared `returnType`).\n\t *\n\t * @param name - Helper name (e.g. `\"uppercase\"`)\n\t * @param definition - Helper definition (implementation + return type)\n\t * @returns `this` to allow chaining\n\t */\n\tregisterHelper(name: string, definition: HelperDefinition): this {\n\t\tthis.helpers.set(name, definition);\n\n\t\t// For helpers that return non-primitive values (arrays, objects),\n\t\t// register a Handlebars-friendly wrapper that converts the result\n\t\t// to a string. In single-expression mode the executor bypasses\n\t\t// Handlebars entirely (via tryDirectHelperExecution) and returns\n\t\t// the raw value, so this wrapper only affects mixed/block templates\n\t\t// where Handlebars renders the result into a larger string.\n\t\tif (DIRECT_EXECUTION_HELPER_NAMES.has(name)) {\n\t\t\tthis.hbs.registerHelper(name, (...args: unknown[]) => {\n\t\t\t\t// Handlebars appends an `options` object as the last argument.\n\t\t\t\t// Strip it before calling the real fn.\n\t\t\t\tconst hbsArgs = args.slice(0, -1);\n\t\t\t\tconst raw = definition.fn(...hbsArgs);\n\t\t\t\treturn stringifyForTemplate(raw);\n\t\t\t});\n\t\t} else {\n\t\t\tthis.hbs.registerHelper(name, definition.fn);\n\t\t}\n\n\t\t// Invalidate the compilation cache because helpers have changed\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a custom helper from this engine instance.\n\t *\n\t * @param name - Name of the helper to remove\n\t * @returns `this` to allow chaining\n\t */\n\tunregisterHelper(name: string): this {\n\t\tthis.helpers.delete(name);\n\t\tthis.hbs.unregisterHelper(name);\n\n\t\t// Invalidate the compilation cache\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Checks whether a helper is registered on this instance.\n\t *\n\t * @param name - Helper name\n\t * @returns `true` if the helper is registered\n\t */\n\thasHelper(name: string): boolean {\n\t\treturn this.helpers.has(name);\n\t}\n\n\t// ─── Cache Management ──────────────────────────────────────────────────\n\n\t/**\n\t * Clears all internal caches (AST + compilation).\n\t *\n\t * Useful after a configuration change or to free memory.\n\t */\n\tclearCaches(): void {\n\t\tthis.astCache.clear();\n\t\tthis.compilationCache.clear();\n\t}\n\n\t// ─── Internals ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Retrieves the AST of a template from the cache, or parses and caches it.\n\t */\n\tprivate getCachedAst(template: string): hbs.AST.Program {\n\t\tlet ast = this.astCache.get(template);\n\t\tif (!ast) {\n\t\t\tast = parse(template);\n\t\t\tthis.astCache.set(template, ast);\n\t\t}\n\t\treturn ast;\n\t}\n}\n"],"names":["Typebars","DIRECT_EXECUTION_HELPER_NAMES","Set","MapHelpers","MAP_HELPER_NAME","stringifyForTemplate","value","undefined","Array","isArray","map","item","JSON","stringify","String","join","compile","template","isArrayInput","children","element","push","CompiledTemplate","fromArray","helpers","hbs","compilationCache","isObjectInput","key","Object","entries","fromObject","isLiteralInput","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","dispatchAnalyze","tpl","coerceSchema","analyzeFromAst","identifierSchemas","child","childOptions","validate","analysis","valid","diagnostics","isValidSyntax","every","v","values","execute","data","dispatchExecute","schema","TemplateAnalysisError","executeFromAst","identifierData","analyzeAndExecute","dispatchAnalyzeAndExecute","registerHelper","name","definition","set","has","args","hbsArgs","slice","raw","fn","clear","unregisterHelper","delete","hasHelper","clearCaches","astCache","get","parse","Map","Handlebars","create","LRUCache","astCacheSize","compilationCacheSize","MathHelpers","register","LogicalHelpers","DefaultHelpers","helper"],"mappings":"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"}
@@ -126,7 +126,9 @@ export type DiagnosticCode =
126
126
  /** Unsupported JSON Schema feature (e.g. if/then/else conditional schemas) */
127
127
  | "UNSUPPORTED_SCHEMA"
128
128
  /** The map helper implicitly flattens the input array one level before mapping */
129
- | "MAP_IMPLICIT_FLATTEN";
129
+ | "MAP_IMPLICIT_FLATTEN"
130
+ /** The default helper's argument chain does not end with a guaranteed (non-optional) value */
131
+ | "DEFAULT_NO_GUARANTEED_VALUE";
130
132
  export interface DiagnosticDetails {
131
133
  /** Path of the expression that caused the error (e.g. `"user.name.foo"`) */
132
134
  path?: string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport type { FromSchema, JSONSchema } from \"json-schema-to-ts\";\n\n// ─── Identifier Data ─────────────────────────────────────────────────────────\n// Maps template identifier integers to their associated data objects.\n//\n// Each identifier can hold either:\n// - A **single object** — the standard case (one data source per identifier)\n// - An **array of objects** — the aggregated case (multiple versions of a\n// node merged into a collection, e.g. from a multi-versioned workflow node)\n//\n// When the value is an array, expressions like `{{key:N}}` automatically\n// extract the property from each element, producing a result array.\n// The full array is accessible via `{{$root:N}}` and can be used with\n// the `map` helper: `{{ map ($root:N) \"property\" }}`.\n\n/**\n * Data associated with a single template identifier.\n *\n * Can be a single record (scalar case) or an array of records\n * (aggregated multi-version case).\n */\nexport type IdentifierDataEntry =\n\t| Record<string, unknown>\n\t| Record<string, unknown>[];\n\n/**\n * Mapping from template identifier integers to their data.\n *\n * @example\n * ```ts\n * // Scalar identifiers (standard)\n * { 1: { meetingId: \"abc\" }, 2: { name: \"Alice\" } }\n *\n * // Aggregated identifier (multi-version node)\n * { 4: [{ accountId: \"A\" }, { accountId: \"B\" }, { accountId: \"C\" }] }\n * ```\n */\nexport type IdentifierData = Record<number, IdentifierDataEntry>;\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 * Array where each element is a `TemplateInput` (recursive).\n *\n * Allows passing an array as a template:\n * ```\n * engine.analyze([\"{{name}}\", \"{{age}}\"], inputSchema);\n * engine.execute([\"{{name}}\", 42], data);\n * ```\n */\nexport type TemplateInputArray = TemplateInput[];\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 * - `TemplateInputArray` → array where each element is a `TemplateInput`\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| TemplateInputArray\n\t| TemplateInputObject;\n\n// ─── Template Data ───────────────────────────────────────────────────────────\n// The data parameter accepted by `execute()` and `analyzeAndExecute()`.\n// In most cases this is a `Record<string, unknown>` (object context), but\n// primitives are also allowed — for example when using `{{$root}}` to\n// reference the entire data value directly.\n\n/**\n * Data type accepted by the template engine's execution methods.\n *\n * - `Record<string, unknown>` → standard object context (most common)\n * - `string` → primitive value (e.g. for `{{$root}}`)\n * - `number` → primitive value\n * - `boolean` → primitive value\n * - `null` → null value\n * - `unknown[]` → array value\n */\nexport type TemplateData =\n\t| Record<string, unknown>\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| unknown[];\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`) and arrays (`TemplateInputArray`) 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 array (`TemplateInputArray`).\n * Template arrays are processed recursively by the engine:\n * each element is analyzed/executed individually and the result is an array.\n */\nexport function isArrayInput(\n\tinput: TemplateInput,\n): input is TemplateInputArray {\n\treturn Array.isArray(input);\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 *\n * Note: arrays are excluded — use `isArrayInput()` first.\n */\nexport function isObjectInput(\n\tinput: TemplateInput,\n): input is TemplateInputObject {\n\treturn input !== null && typeof input === \"object\" && !Array.isArray(input);\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\t/** The $root token is used with path traversal (e.g. $root.name) */\n\t| \"ROOT_PATH_TRAVERSAL\"\n\t/** Unsupported JSON Schema feature (e.g. if/then/else conditional schemas) */\n\t| \"UNSUPPORTED_SCHEMA\"\n\t/** The map helper implicitly flattens the input array one level before mapping */\n\t| \"MAP_IMPLICIT_FLATTEN\";\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\nexport interface CommonTypebarsOptions {\n\t/**\n\t * Explicit coercion schema for the output value.\n\t * When provided with a primitive type, the execution result will be\n\t * coerced to match the declared type instead of using auto-detection.\n\t */\n\tcoerceSchema?: JSONSchema7;\n\t/**\n\t * When `true`, properties whose values contain Handlebars expressions\n\t * (i.e. any `{{…}}` syntax) are excluded from the execution result.\n\t *\n\t * - **Object**: properties with template expressions are omitted from\n\t * the resulting object.\n\t * - **Array**: elements with template expressions are omitted from\n\t * the resulting array.\n\t * - **Root string** with expressions: returns `null` (there is no\n\t * parent to exclude from).\n\t * - **Literals** (number, boolean, null): unaffected.\n\t *\n\t * This mirrors the analysis-side `excludeTemplateExpression` option\n\t * but applied at runtime.\n\t *\n\t * @default false\n\t */\n\texcludeTemplateExpression?: boolean;\n}\n\n// ─── Execution Options ───────────────────────────────────────────────────────\n// Optional options object for `execute()`, replacing multiple positional\n// parameters for better ergonomics.\n\nexport interface ExecuteOptions extends CommonTypebarsOptions {\n\t/** JSON Schema for pre-execution static validation */\n\tschema?: JSONSchema7;\n\t/**\n\t * Data by identifier `{ [id]: { key: value } }`.\n\t *\n\t * Each identifier can map to a single object (standard) or an array\n\t * of objects (aggregated multi-version data). When the value is an\n\t * array, `{{key:N}}` extracts the property from each element.\n\t */\n\tidentifierData?: IdentifierData;\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 extends CommonTypebarsOptions {\n\t/** Schemas by identifier `{ [id]: JSONSchema7 }` for static analysis */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/**\n\t * Data by identifier `{ [id]: { key: value } }` for execution.\n\t *\n\t * Each identifier can map to a single object (standard) or an array\n\t * of objects (aggregated multi-version data). When the value is an\n\t * array, `{{key:N}}` extracts the property from each element.\n\t */\n\tidentifierData?: IdentifierData;\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"],"names":["defineHelper","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","input","Array","isArray","value","type","Number","isInteger","config"],"mappings":"mPA2iBgBA,sBAAAA,kBAtYAC,8BAAAA,0BAhCAC,sBAAAA,kBAbAC,wBAAAA,oBA0BAC,uBAAAA,iBA1BT,SAASD,eACfE,KAAoB,EAEpB,OACCA,QAAU,MAAS,OAAOA,QAAU,UAAY,OAAOA,QAAU,QAEnE,CAOO,SAASH,aACfG,KAAoB,EAEpB,OAAOC,MAAMC,OAAO,CAACF,MACtB,CASO,SAASD,cACfC,KAAoB,EAEpB,OAAOA,QAAU,MAAQ,OAAOA,QAAU,UAAY,CAACC,MAAMC,OAAO,CAACF,MACtE,CAeO,SAASJ,qBACfO,KAA8B,EAE9B,GAAIA,QAAU,KAAM,MAAO,CAAEC,KAAM,MAAO,EAC1C,GAAI,OAAOD,QAAU,UAAW,MAAO,CAAEC,KAAM,SAAU,EACzD,GAAI,OAAOD,QAAU,SAAU,CAC9B,OAAOE,OAAOC,SAAS,CAACH,OAAS,CAAEC,KAAM,SAAU,EAAI,CAAEA,KAAM,QAAS,CACzE,CAGAD,MACA,MAAO,CAAEC,KAAM,MAAO,CACvB,CA0XO,SAAST,aACfY,MAA4B,EAE5B,OAAOA,MACR"}
1
+ {"version":3,"sources":["../../src/types.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport type { FromSchema, JSONSchema } from \"json-schema-to-ts\";\n\n// ─── Identifier Data ─────────────────────────────────────────────────────────\n// Maps template identifier integers to their associated data objects.\n//\n// Each identifier can hold either:\n// - A **single object** — the standard case (one data source per identifier)\n// - An **array of objects** — the aggregated case (multiple versions of a\n// node merged into a collection, e.g. from a multi-versioned workflow node)\n//\n// When the value is an array, expressions like `{{key:N}}` automatically\n// extract the property from each element, producing a result array.\n// The full array is accessible via `{{$root:N}}` and can be used with\n// the `map` helper: `{{ map ($root:N) \"property\" }}`.\n\n/**\n * Data associated with a single template identifier.\n *\n * Can be a single record (scalar case) or an array of records\n * (aggregated multi-version case).\n */\nexport type IdentifierDataEntry =\n\t| Record<string, unknown>\n\t| Record<string, unknown>[];\n\n/**\n * Mapping from template identifier integers to their data.\n *\n * @example\n * ```ts\n * // Scalar identifiers (standard)\n * { 1: { meetingId: \"abc\" }, 2: { name: \"Alice\" } }\n *\n * // Aggregated identifier (multi-version node)\n * { 4: [{ accountId: \"A\" }, { accountId: \"B\" }, { accountId: \"C\" }] }\n * ```\n */\nexport type IdentifierData = Record<number, IdentifierDataEntry>;\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 * Array where each element is a `TemplateInput` (recursive).\n *\n * Allows passing an array as a template:\n * ```\n * engine.analyze([\"{{name}}\", \"{{age}}\"], inputSchema);\n * engine.execute([\"{{name}}\", 42], data);\n * ```\n */\nexport type TemplateInputArray = TemplateInput[];\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 * - `TemplateInputArray` → array where each element is a `TemplateInput`\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| TemplateInputArray\n\t| TemplateInputObject;\n\n// ─── Template Data ───────────────────────────────────────────────────────────\n// The data parameter accepted by `execute()` and `analyzeAndExecute()`.\n// In most cases this is a `Record<string, unknown>` (object context), but\n// primitives are also allowed — for example when using `{{$root}}` to\n// reference the entire data value directly.\n\n/**\n * Data type accepted by the template engine's execution methods.\n *\n * - `Record<string, unknown>` → standard object context (most common)\n * - `string` → primitive value (e.g. for `{{$root}}`)\n * - `number` → primitive value\n * - `boolean` → primitive value\n * - `null` → null value\n * - `unknown[]` → array value\n */\nexport type TemplateData =\n\t| Record<string, unknown>\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| unknown[];\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`) and arrays (`TemplateInputArray`) 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 array (`TemplateInputArray`).\n * Template arrays are processed recursively by the engine:\n * each element is analyzed/executed individually and the result is an array.\n */\nexport function isArrayInput(\n\tinput: TemplateInput,\n): input is TemplateInputArray {\n\treturn Array.isArray(input);\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 *\n * Note: arrays are excluded — use `isArrayInput()` first.\n */\nexport function isObjectInput(\n\tinput: TemplateInput,\n): input is TemplateInputObject {\n\treturn input !== null && typeof input === \"object\" && !Array.isArray(input);\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\t/** The $root token is used with path traversal (e.g. $root.name) */\n\t| \"ROOT_PATH_TRAVERSAL\"\n\t/** Unsupported JSON Schema feature (e.g. if/then/else conditional schemas) */\n\t| \"UNSUPPORTED_SCHEMA\"\n\t/** The map helper implicitly flattens the input array one level before mapping */\n\t| \"MAP_IMPLICIT_FLATTEN\"\n\t/** The default helper's argument chain does not end with a guaranteed (non-optional) value */\n\t| \"DEFAULT_NO_GUARANTEED_VALUE\";\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\nexport interface CommonTypebarsOptions {\n\t/**\n\t * Explicit coercion schema for the output value.\n\t * When provided with a primitive type, the execution result will be\n\t * coerced to match the declared type instead of using auto-detection.\n\t */\n\tcoerceSchema?: JSONSchema7;\n\t/**\n\t * When `true`, properties whose values contain Handlebars expressions\n\t * (i.e. any `{{…}}` syntax) are excluded from the execution result.\n\t *\n\t * - **Object**: properties with template expressions are omitted from\n\t * the resulting object.\n\t * - **Array**: elements with template expressions are omitted from\n\t * the resulting array.\n\t * - **Root string** with expressions: returns `null` (there is no\n\t * parent to exclude from).\n\t * - **Literals** (number, boolean, null): unaffected.\n\t *\n\t * This mirrors the analysis-side `excludeTemplateExpression` option\n\t * but applied at runtime.\n\t *\n\t * @default false\n\t */\n\texcludeTemplateExpression?: boolean;\n}\n\n// ─── Execution Options ───────────────────────────────────────────────────────\n// Optional options object for `execute()`, replacing multiple positional\n// parameters for better ergonomics.\n\nexport interface ExecuteOptions extends CommonTypebarsOptions {\n\t/** JSON Schema for pre-execution static validation */\n\tschema?: JSONSchema7;\n\t/**\n\t * Data by identifier `{ [id]: { key: value } }`.\n\t *\n\t * Each identifier can map to a single object (standard) or an array\n\t * of objects (aggregated multi-version data). When the value is an\n\t * array, `{{key:N}}` extracts the property from each element.\n\t */\n\tidentifierData?: IdentifierData;\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 extends CommonTypebarsOptions {\n\t/** Schemas by identifier `{ [id]: JSONSchema7 }` for static analysis */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/**\n\t * Data by identifier `{ [id]: { key: value } }` for execution.\n\t *\n\t * Each identifier can map to a single object (standard) or an array\n\t * of objects (aggregated multi-version data). When the value is an\n\t * array, `{{key:N}}` extracts the property from each element.\n\t */\n\tidentifierData?: IdentifierData;\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"],"names":["defineHelper","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","input","Array","isArray","value","type","Number","isInteger","config"],"mappings":"mPA6iBgBA,sBAAAA,kBAxYAC,8BAAAA,0BAhCAC,sBAAAA,kBAbAC,wBAAAA,oBA0BAC,uBAAAA,iBA1BT,SAASD,eACfE,KAAoB,EAEpB,OACCA,QAAU,MAAS,OAAOA,QAAU,UAAY,OAAOA,QAAU,QAEnE,CAOO,SAASH,aACfG,KAAoB,EAEpB,OAAOC,MAAMC,OAAO,CAACF,MACtB,CASO,SAASD,cACfC,KAAoB,EAEpB,OAAOA,QAAU,MAAQ,OAAOA,QAAU,UAAY,CAACC,MAAMC,OAAO,CAACF,MACtE,CAeO,SAASJ,qBACfO,KAA8B,EAE9B,GAAIA,QAAU,KAAM,MAAO,CAAEC,KAAM,MAAO,EAC1C,GAAI,OAAOD,QAAU,UAAW,MAAO,CAAEC,KAAM,SAAU,EACzD,GAAI,OAAOD,QAAU,SAAU,CAC9B,OAAOE,OAAOC,SAAS,CAACH,OAAS,CAAEC,KAAM,SAAU,EAAI,CAAEA,KAAM,QAAS,CACzE,CAGAD,MACA,MAAO,CAAEC,KAAM,MAAO,CACvB,CA4XO,SAAST,aACfY,MAA4B,EAE5B,OAAOA,MACR"}
@@ -1,2 +1,2 @@
1
- import{dispatchAnalyze}from"./dispatch.js";import{createMissingArgumentMessage,createPropertyNotFoundMessage,createRootPathTraversalMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{MapHelpers}from"./helpers/map-helpers.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isRootPathTraversal,isRootSegments,isThisExpression,parse}from"./parser.js";import{findConditionalSchemaLocations,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";function coerceTextValue(text,targetType){switch(targetType){case"number":case"integer":{if(text==="")return undefined;const num=Number(text);if(Number.isNaN(num))return undefined;if(targetType==="integer"&&!Number.isInteger(num))return undefined;return num}case"boolean":{const lower=text.toLowerCase();if(lower==="true")return true;if(lower==="false")return false;return undefined}case"null":return null;default:return text}}export function analyze(template,inputSchema={},options){return dispatchAnalyze(template,options,(tpl,coerceSchema)=>{const ast=parse(tpl);return analyzeFromAst(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema})},(child,childOptions)=>analyze(child,inputSchema,childOptions))}export function analyzeFromAst(ast,template,inputSchema={},options){const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,coerceSchema:options?.coerceSchema};const conditionalLocations=findConditionalSchemaLocations(inputSchema);for(const loc of conditionalLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){const idLocations=findConditionalSchemaLocations(idSchema,`/identifierSchemas/${id}`);for(const loc of idLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}}}if(ctx.diagnostics.length>0){return{valid:false,diagnostics:ctx.diagnostics,outputSchema:{}}}const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:simplifySchema(outputSchema)}}function processStatement(stmt,ctx){switch(stmt.type){case"ContentStatement":case"CommentStatement":return undefined;case"MustacheStatement":return processMustache(stmt,ctx);case"BlockStatement":return inferBlockType(stmt,ctx);default:addDiagnostic(ctx,"UNANALYZABLE","warning",`Unsupported AST node type: "${stmt.type}"`,stmt);return undefined}}function processMustache(stmt,ctx){if(stmt.path.type==="SubExpression"){addDiagnostic(ctx,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",stmt);return{}}if(stmt.params.length>0||stmt.hash){const helperName=getExpressionName(stmt.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapHelper(stmt,ctx)}const helper=ctx.helpers?.get(helperName);if(helper){const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(stmt.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:`${requiredCount} argument(s)`,actual:`${stmt.params.length} argument(s)`})}}for(let i=0;i<stmt.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(stmt.params[i],ctx,stmt);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,stmt,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown inline helper "${helperName}" — cannot analyze statically`,stmt,{helperName});return{type:"string"}}return resolveExpressionWithDiagnostics(stmt.path,ctx,stmt)??{}}function processMapHelper(stmt,ctx){const helperName=MapHelpers.MAP_HELPER_NAME;if(stmt.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:"2 argument(s)",actual:`${stmt.params.length} argument(s)`});return{type:"array"}}const collectionExpr=stmt.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,stmt);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,stmt,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems;addDiagnostic(ctx,"MAP_IMPLICIT_FLATTEN","warning",`The "${helperName}" helper will automatically flatten the input array one level before mapping. `+`The item type "${Array.isArray(itemType)?itemType.join(" | "):itemType}" was unwrapped to its inner items schema.`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)})}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=stmt.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use {{ ${helperName} … "${bare}" }} instead of {{ ${helperName} … ${bare} }}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),stmt,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function isParamTypeCompatible(resolved,expected){if(!expected.type||!resolved.type)return true;const expectedTypes=Array.isArray(expected.type)?expected.type:[expected.type];const resolvedTypes=Array.isArray(resolved.type)?resolved.type:[resolved.type];return resolvedTypes.some(rt=>expectedTypes.some(et=>rt===et||et==="number"&&rt==="integer"||et==="integer"&&rt==="number"))}function inferProgramType(program,ctx){const effective=getEffectiveBody(program);if(effective.length===0){return{type:"string"}}const singleExpr=getEffectivelySingleExpression(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=getEffectivelySingleBlock(program);if(singleBlock){return inferBlockType(singleBlock,ctx)}const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){const text=effective.map(s=>s.value).join("").trim();if(text==="")return{type:"string"};const coercedType=ctx.coerceSchema?.type;if(typeof coercedType==="string"&&(coercedType==="string"||coercedType==="number"||coercedType==="integer"||coercedType==="boolean"||coercedType==="null")){const coercedValue=coerceTextValue(text,coercedType);return{type:coercedType,const:coercedValue}}const literalType=detectLiteralType(text);if(literalType)return{type:literalType}}const allBlocks=effective.every(s=>s.type==="BlockStatement");if(allBlocks){const types=[];for(const stmt of effective){const t=inferBlockType(stmt,ctx);if(t)types.push(t)}if(types.length===1)return types[0];if(types.length>1)return simplifySchema({oneOf:types});return{type:"string"}}for(const stmt of program.body){processStatement(stmt,ctx)}return{type:"string"}}function inferBlockType(stmt,ctx){const helperName=getBlockHelperName(stmt);switch(helperName){case"if":case"unless":{const arg=getBlockArgument(stmt);if(arg){resolveExpressionWithDiagnostics(arg,ctx,stmt)}else{addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if(deepEqual(thenType,elseType))return thenType;return simplifySchema({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("each"),stmt,{helperName:"each"});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const collectionSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);if(!collectionSchema){const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",createTypeMismatchMessage("each","an array",schemaTypeLabel(collectionSchema)),stmt,{helperName:"each",expected:"array",actual:schemaTypeLabel(collectionSchema)});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const saved=ctx.current;ctx.current=itemSchema;inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}case"with":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("with"),stmt,{helperName:"with"});const saved=ctx.current;ctx.current={};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}const innerSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);const saved=ctx.current;ctx.current=innerSchema??{};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}default:{const helper=ctx.helpers?.get(helperName);if(helper){for(const param of stmt.params){resolveExpressionWithDiagnostics(param,ctx,stmt)}inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",createUnknownHelperMessage(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if(isThisExpression(expr)){return ctx.current}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=extractPathSegments(expr);if(segments.length===0){if(expr.type==="StringLiteral")return{type:"string"};if(expr.type==="NumberLiteral")return{type:"number"};if(expr.type==="BooleanLiteral")return{type:"boolean"};if(expr.type==="NullLiteral")return{type:"null"};if(expr.type==="UndefinedLiteral")return{};addDiagnostic(ctx,"UNANALYZABLE","warning",createUnanalyzableMessage(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(isRootPathTraversal(cleanSegments)){const fullPath=cleanSegments.join(".");addDiagnostic(ctx,"ROOT_PATH_TRAVERSAL","error",createRootPathTraversalMessage(fullPath),parentNode??expr,{path:fullPath});return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null){return resolveRootWithIdentifier(identifier,ctx,parentNode??expr)}return ctx.current}if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=resolveSchemaPath(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=getSchemaPropertyNames(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}function resolveRootWithIdentifier(identifier,ctx,node){if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "$root:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`$root:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "$root:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`$root:${identifier}`,identifier});return undefined}return idSchema}function resolveWithIdentifier(cleanSegments,identifier,ctx,node){const fullPath=cleanSegments.join(".");if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${fullPath}:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "${fullPath}:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const itemSchema=resolveArrayItems(idSchema,ctx.root);if(itemSchema!==undefined){const resolved=resolveSchemaPath(itemSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(itemSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the items schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return{type:"array",items:resolved}}const resolved=resolveSchemaPath(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(idSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return resolved}function resolveSubExpression(expr,ctx,parentNode){const helperName=getExpressionName(expr.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapSubExpression(expr,ctx,parentNode)}const helper=ctx.helpers?.get(helperName);if(!helper){addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${helperName}" — cannot analyze statically`,parentNode??expr,{helperName});return{type:"string"}}const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(expr.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,parentNode??expr,{helperName,expected:`${requiredCount} argument(s)`,actual:`${expr.params.length} argument(s)`})}}for(let i=0;i<expr.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(expr.params[i],ctx,parentNode??expr);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,parentNode??expr,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}function processMapSubExpression(expr,ctx,parentNode){const helperName=MapHelpers.MAP_HELPER_NAME;const node=parentNode??expr;if(expr.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${expr.params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${expr.params.length} argument(s)`});return{type:"array"}}const collectionExpr=expr.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,node);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,node,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,node,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=expr.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use (${helperName} … "${bare}") instead of (${helperName} … ${bare})`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),node,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function getBlockArgument(stmt){return stmt.params[0]}function getBlockHelperName(stmt){if(stmt.path.type==="PathExpression"){return stmt.path.original}return""}function getExpressionName(expr){if(expr.type==="PathExpression"){return expr.original}return""}function addDiagnostic(ctx,code,severity,message,node,details){const diagnostic={severity,code,message};if(node&&"loc"in node&&node.loc){diagnostic.loc={start:{line:node.loc.start.line,column:node.loc.start.column},end:{line:node.loc.end.line,column:node.loc.end.column}};diagnostic.source=extractSourceSnippet(ctx.template,diagnostic.loc)}if(details){diagnostic.details=details}ctx.diagnostics.push(diagnostic)}function schemaTypeLabel(schema){if(schema.type){return Array.isArray(schema.type)?schema.type.join(" | "):schema.type}if(schema.oneOf)return"oneOf(...)";if(schema.anyOf)return"anyOf(...)";if(schema.allOf)return"allOf(...)";if(schema.enum)return"enum";return"unknown"}export{inferBlockType};
1
+ import{dispatchAnalyze}from"./dispatch.js";import{createMissingArgumentMessage,createPropertyNotFoundMessage,createRootPathTraversalMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{DefaultHelpers}from"./helpers/default-helpers.js";import{MapHelpers}from"./helpers/map-helpers.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isRootPathTraversal,isRootSegments,isThisExpression,parse}from"./parser.js";import{findConditionalSchemaLocations,isPropertyRequired,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";function coerceTextValue(text,targetType){switch(targetType){case"number":case"integer":{if(text==="")return undefined;const num=Number(text);if(Number.isNaN(num))return undefined;if(targetType==="integer"&&!Number.isInteger(num))return undefined;return num}case"boolean":{const lower=text.toLowerCase();if(lower==="true")return true;if(lower==="false")return false;return undefined}case"null":return null;default:return text}}export function analyze(template,inputSchema={},options){return dispatchAnalyze(template,options,(tpl,coerceSchema)=>{const ast=parse(tpl);return analyzeFromAst(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema})},(child,childOptions)=>analyze(child,inputSchema,childOptions))}export function analyzeFromAst(ast,template,inputSchema={},options){const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,coerceSchema:options?.coerceSchema};const conditionalLocations=findConditionalSchemaLocations(inputSchema);for(const loc of conditionalLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){const idLocations=findConditionalSchemaLocations(idSchema,`/identifierSchemas/${id}`);for(const loc of idLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}}}if(ctx.diagnostics.length>0){return{valid:false,diagnostics:ctx.diagnostics,outputSchema:{}}}const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:simplifySchema(outputSchema)}}function processStatement(stmt,ctx){switch(stmt.type){case"ContentStatement":case"CommentStatement":return undefined;case"MustacheStatement":return processMustache(stmt,ctx);case"BlockStatement":return inferBlockType(stmt,ctx);default:addDiagnostic(ctx,"UNANALYZABLE","warning",`Unsupported AST node type: "${stmt.type}"`,stmt);return undefined}}function processMustache(stmt,ctx){if(stmt.path.type==="SubExpression"){addDiagnostic(ctx,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",stmt);return{}}if(stmt.params.length>0||stmt.hash){const helperName=getExpressionName(stmt.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapHelper(stmt,ctx)}if(helperName===DefaultHelpers.DEFAULT_HELPER_NAME){return processDefaultHelper(stmt,ctx)}const helper=ctx.helpers?.get(helperName);if(helper){const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(stmt.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:`${requiredCount} argument(s)`,actual:`${stmt.params.length} argument(s)`})}}for(let i=0;i<stmt.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(stmt.params[i],ctx,stmt);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,stmt,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown inline helper "${helperName}" — cannot analyze statically`,stmt,{helperName});return{type:"string"}}return resolveExpressionWithDiagnostics(stmt.path,ctx,stmt)??{}}function processMapHelper(stmt,ctx){const helperName=MapHelpers.MAP_HELPER_NAME;if(stmt.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:"2 argument(s)",actual:`${stmt.params.length} argument(s)`});return{type:"array"}}const collectionExpr=stmt.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,stmt);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,stmt,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems;addDiagnostic(ctx,"MAP_IMPLICIT_FLATTEN","warning",`The "${helperName}" helper will automatically flatten the input array one level before mapping. `+`The item type "${Array.isArray(itemType)?itemType.join(" | "):itemType}" was unwrapped to its inner items schema.`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)})}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=stmt.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use {{ ${helperName} … "${bare}" }} instead of {{ ${helperName} … ${bare} }}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),stmt,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function processDefaultHelper(stmt,ctx){return analyzeDefaultArgs(stmt.params,ctx,stmt)}function processDefaultSubExpression(expr,ctx,parentNode){return analyzeDefaultArgs(expr.params,ctx,parentNode??expr)}function analyzeDefaultArgs(params,ctx,node){const helperName=DefaultHelpers.DEFAULT_HELPER_NAME;if(params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${params.length} argument(s)`});return{}}const resolvedSchemas=[];let hasGuaranteedValue=false;for(let i=0;i<params.length;i++){const param=params[i];const resolvedSchema=resolveExpressionWithDiagnostics(param,ctx,node);if(resolvedSchema){resolvedSchemas.push(resolvedSchema)}if(isGuaranteedExpression(param,ctx)){hasGuaranteedValue=true}}if(resolvedSchemas.length>=2){const firstWithType=resolvedSchemas.find(s=>s.type);if(firstWithType){for(let i=0;i<resolvedSchemas.length;i++){const schema=resolvedSchemas[i];if(schema.type&&!isParamTypeCompatible(schema,firstWithType)){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" argument ${i+1} has type ${schemaTypeLabel(schema)}, incompatible with ${schemaTypeLabel(firstWithType)}`,node,{helperName,expected:schemaTypeLabel(firstWithType),actual:schemaTypeLabel(schema)})}}}}if(!hasGuaranteedValue){addDiagnostic(ctx,"DEFAULT_NO_GUARANTEED_VALUE","error",`Helper "${helperName}" argument chain has no guaranteed fallback — `+"the last argument must be a literal or a non-optional property",node,{helperName})}if(resolvedSchemas.length===0)return{};if(resolvedSchemas.length===1)return resolvedSchemas[0];return simplifySchema({oneOf:resolvedSchemas})}function isGuaranteedExpression(expr,ctx){if(expr.type==="StringLiteral"||expr.type==="NumberLiteral"||expr.type==="BooleanLiteral"){return true}if(expr.type==="SubExpression"){return true}if(expr.type==="PathExpression"){const segments=extractPathSegments(expr);if(segments.length===0)return false;const{cleanSegments}=extractExpressionIdentifier(segments);return isPropertyRequired(ctx.current,cleanSegments)}return false}function isParamTypeCompatible(resolved,expected){if(!expected.type||!resolved.type)return true;const expectedTypes=Array.isArray(expected.type)?expected.type:[expected.type];const resolvedTypes=Array.isArray(resolved.type)?resolved.type:[resolved.type];return resolvedTypes.some(rt=>expectedTypes.some(et=>rt===et||et==="number"&&rt==="integer"||et==="integer"&&rt==="number"))}function inferProgramType(program,ctx){const effective=getEffectiveBody(program);if(effective.length===0){return{type:"string"}}const singleExpr=getEffectivelySingleExpression(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=getEffectivelySingleBlock(program);if(singleBlock){return inferBlockType(singleBlock,ctx)}const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){const text=effective.map(s=>s.value).join("").trim();if(text==="")return{type:"string"};const coercedType=ctx.coerceSchema?.type;if(typeof coercedType==="string"&&(coercedType==="string"||coercedType==="number"||coercedType==="integer"||coercedType==="boolean"||coercedType==="null")){const coercedValue=coerceTextValue(text,coercedType);return{type:coercedType,const:coercedValue}}const literalType=detectLiteralType(text);if(literalType)return{type:literalType}}const allBlocks=effective.every(s=>s.type==="BlockStatement");if(allBlocks){const types=[];for(const stmt of effective){const t=inferBlockType(stmt,ctx);if(t)types.push(t)}if(types.length===1)return types[0];if(types.length>1)return simplifySchema({oneOf:types});return{type:"string"}}for(const stmt of program.body){processStatement(stmt,ctx)}return{type:"string"}}function inferBlockType(stmt,ctx){const helperName=getBlockHelperName(stmt);switch(helperName){case"if":case"unless":{const arg=getBlockArgument(stmt);if(arg){resolveExpressionWithDiagnostics(arg,ctx,stmt)}else{addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if(deepEqual(thenType,elseType))return thenType;return simplifySchema({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("each"),stmt,{helperName:"each"});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const collectionSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);if(!collectionSchema){const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",createTypeMismatchMessage("each","an array",schemaTypeLabel(collectionSchema)),stmt,{helperName:"each",expected:"array",actual:schemaTypeLabel(collectionSchema)});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const saved=ctx.current;ctx.current=itemSchema;inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}case"with":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("with"),stmt,{helperName:"with"});const saved=ctx.current;ctx.current={};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}const innerSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);const saved=ctx.current;ctx.current=innerSchema??{};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}default:{const helper=ctx.helpers?.get(helperName);if(helper){for(const param of stmt.params){resolveExpressionWithDiagnostics(param,ctx,stmt)}inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",createUnknownHelperMessage(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if(isThisExpression(expr)){return ctx.current}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=extractPathSegments(expr);if(segments.length===0){if(expr.type==="StringLiteral")return{type:"string"};if(expr.type==="NumberLiteral")return{type:"number"};if(expr.type==="BooleanLiteral")return{type:"boolean"};if(expr.type==="NullLiteral")return{type:"null"};if(expr.type==="UndefinedLiteral")return{};addDiagnostic(ctx,"UNANALYZABLE","warning",createUnanalyzableMessage(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(isRootPathTraversal(cleanSegments)){const fullPath=cleanSegments.join(".");addDiagnostic(ctx,"ROOT_PATH_TRAVERSAL","error",createRootPathTraversalMessage(fullPath),parentNode??expr,{path:fullPath});return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null){return resolveRootWithIdentifier(identifier,ctx,parentNode??expr)}return ctx.current}if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=resolveSchemaPath(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=getSchemaPropertyNames(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}function resolveRootWithIdentifier(identifier,ctx,node){if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "$root:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`$root:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "$root:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`$root:${identifier}`,identifier});return undefined}return idSchema}function resolveWithIdentifier(cleanSegments,identifier,ctx,node){const fullPath=cleanSegments.join(".");if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${fullPath}:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "${fullPath}:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const itemSchema=resolveArrayItems(idSchema,ctx.root);if(itemSchema!==undefined){const resolved=resolveSchemaPath(itemSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(itemSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the items schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return{type:"array",items:resolved}}const resolved=resolveSchemaPath(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(idSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return resolved}function resolveSubExpression(expr,ctx,parentNode){const helperName=getExpressionName(expr.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapSubExpression(expr,ctx,parentNode)}if(helperName===DefaultHelpers.DEFAULT_HELPER_NAME){return processDefaultSubExpression(expr,ctx,parentNode)}const helper=ctx.helpers?.get(helperName);if(!helper){addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${helperName}" — cannot analyze statically`,parentNode??expr,{helperName});return{type:"string"}}const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(expr.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,parentNode??expr,{helperName,expected:`${requiredCount} argument(s)`,actual:`${expr.params.length} argument(s)`})}}for(let i=0;i<expr.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(expr.params[i],ctx,parentNode??expr);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,parentNode??expr,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}function processMapSubExpression(expr,ctx,parentNode){const helperName=MapHelpers.MAP_HELPER_NAME;const node=parentNode??expr;if(expr.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${expr.params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${expr.params.length} argument(s)`});return{type:"array"}}const collectionExpr=expr.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,node);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,node,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,node,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=expr.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use (${helperName} … "${bare}") instead of (${helperName} … ${bare})`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),node,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function getBlockArgument(stmt){return stmt.params[0]}function getBlockHelperName(stmt){if(stmt.path.type==="PathExpression"){return stmt.path.original}return""}function getExpressionName(expr){if(expr.type==="PathExpression"){return expr.original}return""}function addDiagnostic(ctx,code,severity,message,node,details){const diagnostic={severity,code,message};if(node&&"loc"in node&&node.loc){diagnostic.loc={start:{line:node.loc.start.line,column:node.loc.start.column},end:{line:node.loc.end.line,column:node.loc.end.column}};diagnostic.source=extractSourceSnippet(ctx.template,diagnostic.loc)}if(details){diagnostic.details=details}ctx.diagnostics.push(diagnostic)}function schemaTypeLabel(schema){if(schema.type){return Array.isArray(schema.type)?schema.type.join(" | "):schema.type}if(schema.oneOf)return"oneOf(...)";if(schema.anyOf)return"anyOf(...)";if(schema.allOf)return"allOf(...)";if(schema.enum)return"enum";return"unknown"}export{inferBlockType};
2
2
  //# sourceMappingURL=analyzer.js.map