typebars 1.0.9 → 1.0.10

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.
@@ -14,6 +14,14 @@ interface AnalysisContext {
14
14
  identifierSchemas?: Record<number, JSONSchema7>;
15
15
  /** Registered custom helpers (for static analysis) */
16
16
  helpers?: Map<string, HelperDefinition>;
17
+ /**
18
+ * Expected output type from the inputSchema.
19
+ * When the inputSchema declares a specific type (e.g. `{ type: "string" }`),
20
+ * static literal values like `"123"` should respect that type instead of
21
+ * being auto-detected as `number`. This allows the schema contract to
22
+ * override the default `detectLiteralType` inference.
23
+ */
24
+ expectedOutputType?: JSONSchema7;
17
25
  }
18
26
  /**
19
27
  * Statically analyzes a template against a JSON Schema v7 describing the
@@ -27,7 +35,7 @@ interface AnalysisContext {
27
35
  * @returns An `AnalysisResult` containing validity, diagnostics, and the
28
36
  * inferred output schema.
29
37
  */
30
- export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
38
+ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>, expectedOutputType?: JSONSchema7): AnalysisResult;
31
39
  /**
32
40
  * Statically analyzes a template from an already-parsed AST.
33
41
  *
@@ -43,6 +51,12 @@ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema
43
51
  export declare function analyzeFromAst(ast: hbs.AST.Program, template: string, inputSchema: JSONSchema7, options?: {
44
52
  identifierSchemas?: Record<number, JSONSchema7>;
45
53
  helpers?: Map<string, HelperDefinition>;
54
+ /**
55
+ * When set, provides the expected output type from the parent context
56
+ * (e.g. the inputSchema's property sub-schema for an object template key).
57
+ * Static literal values will respect this type instead of auto-detecting.
58
+ */
59
+ expectedOutputType?: JSONSchema7;
46
60
  }): AnalysisResult;
47
61
  /**
48
62
  * Infers the output type of a BlockStatement and validates its content.
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:Object.getOwnPropertyDescriptor(all,name).get})}_export(exports,{get analyze(){return analyze},get analyzeFromAst(){return analyzeFromAst},get inferBlockType(){return inferBlockType}});const _errors=require("./errors.js");const _parser=require("./parser.js");const _schemaresolver=require("./schema-resolver.js");const _typests=require("./types.js");const _utils=require("./utils.js");function analyze(template,inputSchema,identifierSchemas){if((0,_typests.isArrayInput)(template)){return analyzeArrayTemplate(template,inputSchema,identifierSchemas)}if((0,_typests.isObjectInput)(template)){return analyzeObjectTemplate(template,inputSchema,identifierSchemas)}if((0,_typests.isLiteralInput)(template)){return{valid:true,diagnostics:[],outputSchema:(0,_typests.inferPrimitiveSchema)(template)}}const ast=(0,_parser.parse)(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas})}function analyzeArrayTemplate(template,inputSchema,identifierSchemas){return(0,_utils.aggregateArrayAnalysis)(template.length,index=>analyze(template[index],inputSchema,identifierSchemas))}function analyzeObjectTemplate(template,inputSchema,identifierSchemas){return(0,_utils.aggregateObjectAnalysis)(Object.keys(template),key=>analyze(template[key],inputSchema,identifierSchemas))}function analyzeFromAst(ast,template,inputSchema,options){(0,_schemaresolver.assertNoConditionalSchema)(inputSchema);if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){(0,_schemaresolver.assertNoConditionalSchema)(idSchema,`/identifierSchemas/${id}`)}}const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers};const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:(0,_schemaresolver.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);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 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=(0,_parser.getEffectiveBody)(program);if(effective.length===0){return{type:"string"}}const singleExpr=(0,_parser.getEffectivelySingleExpression)(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=(0,_parser.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 literalType=(0,_parser.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(0,_schemaresolver.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",(0,_errors.createMissingArgumentMessage)(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if((0,_utils.deepEqual)(thenType,elseType))return thenType;return(0,_schemaresolver.simplifySchema)({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",(0,_errors.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=(0,_schemaresolver.resolveArrayItems)(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",(0,_errors.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",(0,_errors.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",(0,_errors.createUnknownHelperMessage)(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if((0,_parser.isThisExpression)(expr)){return ctx.current}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=(0,_parser.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",(0,_errors.createUnanalyzableMessage)(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=(0,_parser.extractExpressionIdentifier)(segments);if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=(0,_schemaresolver.resolveSchemaPath)(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=(0,_utils.getSchemaPropertyNames)(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",(0,_errors.createPropertyNotFoundMessage)(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}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 resolved=(0,_schemaresolver.resolveSchemaPath)(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=(0,_utils.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);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 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=(0,_utils.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"}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:Object.getOwnPropertyDescriptor(all,name).get})}_export(exports,{get analyze(){return analyze},get analyzeFromAst(){return analyzeFromAst},get inferBlockType(){return inferBlockType}});const _errors=require("./errors.js");const _parser=require("./parser.js");const _schemaresolver=require("./schema-resolver.js");const _typests=require("./types.js");const _utils=require("./utils.js");function analyze(template,inputSchema,identifierSchemas,expectedOutputType){if((0,_typests.isArrayInput)(template)){return analyzeArrayTemplate(template,inputSchema,identifierSchemas)}if((0,_typests.isObjectInput)(template)){return analyzeObjectTemplate(template,inputSchema,identifierSchemas,expectedOutputType)}if((0,_typests.isLiteralInput)(template)){return{valid:true,diagnostics:[],outputSchema:(0,_typests.inferPrimitiveSchema)(template)}}const ast=(0,_parser.parse)(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas,expectedOutputType:expectedOutputType??inputSchema})}function analyzeArrayTemplate(template,inputSchema,identifierSchemas){return(0,_utils.aggregateArrayAnalysis)(template.length,index=>analyze(template[index],inputSchema,identifierSchemas))}function analyzeObjectTemplate(template,inputSchema,identifierSchemas,expectedOutputType){const schemaForProperties=expectedOutputType??inputSchema;return(0,_utils.aggregateObjectAnalysis)(Object.keys(template),key=>{const propertySchema=(0,_schemaresolver.resolveSchemaPath)(schemaForProperties,[key]);return analyze(template[key],inputSchema,identifierSchemas,propertySchema)})}function analyzeFromAst(ast,template,inputSchema,options){(0,_schemaresolver.assertNoConditionalSchema)(inputSchema);if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){(0,_schemaresolver.assertNoConditionalSchema)(idSchema,`/identifierSchemas/${id}`)}}const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,expectedOutputType:options?.expectedOutputType};const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:(0,_schemaresolver.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);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 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=(0,_parser.getEffectiveBody)(program);if(effective.length===0){return{type:"string"}}const singleExpr=(0,_parser.getEffectivelySingleExpression)(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=(0,_parser.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 expectedType=ctx.expectedOutputType?.type;if(typeof expectedType==="string"&&(expectedType==="string"||expectedType==="number"||expectedType==="integer"||expectedType==="boolean"||expectedType==="null")){return{type:expectedType}}const literalType=(0,_parser.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(0,_schemaresolver.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",(0,_errors.createMissingArgumentMessage)(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if((0,_utils.deepEqual)(thenType,elseType))return thenType;return(0,_schemaresolver.simplifySchema)({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",(0,_errors.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=(0,_schemaresolver.resolveArrayItems)(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",(0,_errors.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",(0,_errors.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",(0,_errors.createUnknownHelperMessage)(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if((0,_parser.isThisExpression)(expr)){return ctx.current}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=(0,_parser.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",(0,_errors.createUnanalyzableMessage)(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=(0,_parser.extractExpressionIdentifier)(segments);if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=(0,_schemaresolver.resolveSchemaPath)(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=(0,_utils.getSchemaPropertyNames)(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",(0,_errors.createPropertyNotFoundMessage)(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}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 resolved=(0,_schemaresolver.resolveSchemaPath)(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=(0,_utils.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);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 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=(0,_utils.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"}
2
2
  //# sourceMappingURL=analyzer.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser\";\nimport {\n\tassertNoConditionalSchema,\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputArray,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\tif (isArrayInput(template)) {\n\t\treturn analyzeArrayTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\treturn analyzeFromAst(ast, template, inputSchema, { identifierSchemas });\n}\n\n/**\n * Analyzes an array template recursively (standalone version).\n * Each element is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the array structure with a proper `items`.\n */\nfunction analyzeArrayTemplate(\n\ttemplate: TemplateInputArray,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\tanalyze(template[index] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) =>\n\t\tanalyze(template[key] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t},\n): AnalysisResult {\n\t// ── Reject unsupported schema features before analysis ────────────\n\t// Conditional schemas (if/then/else) are non-resolvable without runtime\n\t// data. Fail fast with a clear error rather than producing silently\n\t// incorrect results.\n\tassertNoConditionalSchema(inputSchema);\n\n\tif (options?.identifierSchemas) {\n\t\tfor (const [id, idSchema] of Object.entries(options.identifierSchemas)) {\n\t\t\tassertNoConditionalSchema(idSchema, `/identifierSchemas/${id}`);\n\t\t}\n\t}\n\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"],"names":["analyze","analyzeFromAst","inferBlockType","template","inputSchema","identifierSchemas","isArrayInput","analyzeArrayTemplate","isObjectInput","analyzeObjectTemplate","isLiteralInput","valid","diagnostics","outputSchema","inferPrimitiveSchema","ast","parse","aggregateArrayAnalysis","length","index","aggregateObjectAnalysis","Object","keys","key","options","assertNoConditionalSchema","id","idSchema","entries","ctx","root","current","helpers","inferProgramType","hasErrors","some","d","severity","simplifySchema","processStatement","stmt","type","undefined","processMustache","addDiagnostic","path","params","hash","helperName","getExpressionName","helper","get","helperParams","requiredCount","filter","p","optional","expected","actual","i","resolvedSchema","resolveExpressionWithDiagnostics","helperParam","expectedType","isParamTypeCompatible","paramName","name","schemaTypeLabel","returnType","resolved","expectedTypes","Array","isArray","resolvedTypes","rt","et","program","effective","getEffectiveBody","singleExpr","getEffectivelySingleExpression","singleBlock","getEffectivelySingleBlock","allContent","every","s","text","map","value","join","trim","literalType","detectLiteralType","allBlocks","types","t","push","oneOf","body","getBlockHelperName","arg","getBlockArgument","createMissingArgumentMessage","thenType","inverse","elseType","deepEqual","saved","collectionSchema","itemSchema","resolveArrayItems","createTypeMismatchMessage","result","innerSchema","param","createUnknownHelperMessage","expr","parentNode","isThisExpression","resolveSubExpression","segments","extractPathSegments","createUnanalyzableMessage","cleanSegments","identifier","extractExpressionIdentifier","resolveWithIdentifier","resolveSchemaPath","fullPath","availableProperties","getSchemaPropertyNames","createPropertyNotFoundMessage","node","original","code","message","details","diagnostic","loc","start","line","column","end","source","extractSourceSnippet","schema","anyOf","allOf","enum"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser\";\nimport {\n\tassertNoConditionalSchema,\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputArray,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n\t/**\n\t * Expected output type from the inputSchema.\n\t * When the inputSchema declares a specific type (e.g. `{ type: \"string\" }`),\n\t * static literal values like `\"123\"` should respect that type instead of\n\t * being auto-detected as `number`. This allows the schema contract to\n\t * override the default `detectLiteralType` inference.\n\t */\n\texpectedOutputType?: JSONSchema7;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n\texpectedOutputType?: JSONSchema7,\n): AnalysisResult {\n\tif (isArrayInput(template)) {\n\t\treturn analyzeArrayTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(\n\t\t\ttemplate,\n\t\t\tinputSchema,\n\t\t\tidentifierSchemas,\n\t\t\texpectedOutputType,\n\t\t);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\t// When an explicit expectedOutputType is provided (e.g. from an object\n\t// template property), use it. Otherwise, fall back to the inputSchema\n\t// itself — when the inputSchema has a primitive type (e.g. { type: \"string\" }),\n\t// it constrains the output type of static literal values.\n\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\tidentifierSchemas,\n\t\texpectedOutputType: expectedOutputType ?? inputSchema,\n\t});\n}\n\n/**\n * Analyzes an array template recursively (standalone version).\n * Each element is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the array structure with a proper `items`.\n */\nfunction analyzeArrayTemplate(\n\ttemplate: TemplateInputArray,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\tanalyze(template[index] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n\texpectedOutputType?: JSONSchema7,\n): AnalysisResult {\n\t// Use the expectedOutputType (if it describes an object) to resolve\n\t// child property schemas. This is critical for deeply nested objects:\n\t// when the template is `{ a: { b: { c: \"123\" } } }`, each level must\n\t// resolve its children from the *corresponding* sub-schema, not from\n\t// the root inputSchema.\n\tconst schemaForProperties = expectedOutputType ?? inputSchema;\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) => {\n\t\tconst propertySchema = resolveSchemaPath(schemaForProperties, [key]);\n\t\treturn analyze(\n\t\t\ttemplate[key] as TemplateInput,\n\t\t\tinputSchema,\n\t\t\tidentifierSchemas,\n\t\t\tpropertySchema,\n\t\t);\n\t});\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t\t/**\n\t\t * When set, provides the expected output type from the parent context\n\t\t * (e.g. the inputSchema's property sub-schema for an object template key).\n\t\t * Static literal values will respect this type instead of auto-detecting.\n\t\t */\n\t\texpectedOutputType?: JSONSchema7;\n\t},\n): AnalysisResult {\n\t// ── Reject unsupported schema features before analysis ────────────\n\t// Conditional schemas (if/then/else) are non-resolvable without runtime\n\t// data. Fail fast with a clear error rather than producing silently\n\t// incorrect results.\n\tassertNoConditionalSchema(inputSchema);\n\n\tif (options?.identifierSchemas) {\n\t\tfor (const [id, idSchema] of Object.entries(options.identifierSchemas)) {\n\t\t\tassertNoConditionalSchema(idSchema, `/identifierSchemas/${id}`);\n\t\t}\n\t}\n\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t\texpectedOutputType: options?.expectedOutputType,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\t// If the inputSchema declares a specific primitive type for this value,\n\t\t// respect the schema contract instead of auto-detecting. For example,\n\t\t// \"123\" with inputSchema `{ type: \"string\" }` should stay \"string\".\n\t\tconst expectedType = ctx.expectedOutputType?.type;\n\t\tif (\n\t\t\ttypeof expectedType === \"string\" &&\n\t\t\t(expectedType === \"string\" ||\n\t\t\t\texpectedType === \"number\" ||\n\t\t\t\texpectedType === \"integer\" ||\n\t\t\t\texpectedType === \"boolean\" ||\n\t\t\t\texpectedType === \"null\")\n\t\t) {\n\t\t\treturn { type: expectedType };\n\t\t}\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"],"names":["analyze","analyzeFromAst","inferBlockType","template","inputSchema","identifierSchemas","expectedOutputType","isArrayInput","analyzeArrayTemplate","isObjectInput","analyzeObjectTemplate","isLiteralInput","valid","diagnostics","outputSchema","inferPrimitiveSchema","ast","parse","aggregateArrayAnalysis","length","index","schemaForProperties","aggregateObjectAnalysis","Object","keys","key","propertySchema","resolveSchemaPath","options","assertNoConditionalSchema","id","idSchema","entries","ctx","root","current","helpers","inferProgramType","hasErrors","some","d","severity","simplifySchema","processStatement","stmt","type","undefined","processMustache","addDiagnostic","path","params","hash","helperName","getExpressionName","helper","get","helperParams","requiredCount","filter","p","optional","expected","actual","i","resolvedSchema","resolveExpressionWithDiagnostics","helperParam","expectedType","isParamTypeCompatible","paramName","name","schemaTypeLabel","returnType","resolved","expectedTypes","Array","isArray","resolvedTypes","rt","et","program","effective","getEffectiveBody","singleExpr","getEffectivelySingleExpression","singleBlock","getEffectivelySingleBlock","allContent","every","s","text","map","value","join","trim","literalType","detectLiteralType","allBlocks","types","t","push","oneOf","body","getBlockHelperName","arg","getBlockArgument","createMissingArgumentMessage","thenType","inverse","elseType","deepEqual","saved","collectionSchema","itemSchema","resolveArrayItems","createTypeMismatchMessage","result","innerSchema","param","createUnknownHelperMessage","expr","parentNode","isThisExpression","resolveSubExpression","segments","extractPathSegments","createUnanalyzableMessage","cleanSegments","identifier","extractExpressionIdentifier","resolveWithIdentifier","fullPath","availableProperties","getSchemaPropertyNames","createPropertyNotFoundMessage","node","original","code","message","details","diagnostic","loc","start","line","column","end","source","extractSourceSnippet","schema","anyOf","allOf","enum"],"mappings":"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"}
@@ -34,7 +34,7 @@ export declare class Typebars {
34
34
  * @param inputSchema - JSON Schema v7 describing the available variables
35
35
  * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`
36
36
  */
37
- analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
37
+ analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>, expectedOutputType?: JSONSchema7): AnalysisResult;
38
38
  /**
39
39
  * Validates a template against a schema without returning the output type.
40
40
  *
@@ -88,7 +88,7 @@ export declare class Typebars {
88
88
  * @returns An object `{ analysis, value }` where `value` is `undefined`
89
89
  * if analysis failed.
90
90
  */
91
- analyzeAndExecute(template: TemplateInput, inputSchema: JSONSchema7, data: Record<string, unknown>, options?: AnalyzeAndExecuteOptions): {
91
+ analyzeAndExecute(template: TemplateInput, inputSchema: JSONSchema7, data: Record<string, unknown>, options?: AnalyzeAndExecuteOptions, expectedOutputType?: JSONSchema7): {
92
92
  analysis: AnalysisResult;
93
93
  value: unknown;
94
94
  };
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"Typebars",{enumerable:true,get:function(){return Typebars}});const _handlebars=/*#__PURE__*/_interop_require_default(require("handlebars"));const _analyzerts=require("./analyzer.js");const _compiledtemplatets=require("./compiled-template.js");const _errorsts=require("./errors.js");const _executorts=require("./executor.js");const _indexts=require("./helpers/index.js");const _parserts=require("./parser.js");const _typests=require("./types.js");const _utils=require("./utils.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function _interop_require_default(obj){return obj&&obj.__esModule?obj:{default:obj}}class Typebars{compile(template){if((0,_typests.isArrayInput)(template)){const children=[];for(const element of template){children.push(this.compile(element))}return _compiledtemplatets.CompiledTemplate.fromArray(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isObjectInput)(template)){const children={};for(const[key,value]of Object.entries(template)){children[key]=this.compile(value)}return _compiledtemplatets.CompiledTemplate.fromObject(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isLiteralInput)(template)){return _compiledtemplatets.CompiledTemplate.fromLiteral(template,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}const ast=this.getCachedAst(template);const options={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return _compiledtemplatets.CompiledTemplate.fromTemplate(ast,template,options)}analyze(template,inputSchema,identifierSchemas){if((0,_typests.isArrayInput)(template)){return(0,_utils.aggregateArrayAnalysis)(template.length,index=>this.analyze(template[index],inputSchema,identifierSchemas))}if((0,_typests.isObjectInput)(template)){return(0,_utils.aggregateObjectAnalysis)(Object.keys(template),key=>this.analyze(template[key],inputSchema,identifierSchemas))}if((0,_typests.isLiteralInput)(template)){return{valid:true,diagnostics:[],outputSchema:(0,_typests.inferPrimitiveSchema)(template)}}const ast=this.getCachedAst(template);return(0,_analyzerts.analyzeFromAst)(ast,template,inputSchema,{identifierSchemas,helpers:this.helpers})}validate(template,inputSchema,identifierSchemas){const analysis=this.analyze(template,inputSchema,identifierSchemas);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}isValidSyntax(template){if((0,_typests.isArrayInput)(template)){return template.every(v=>this.isValidSyntax(v))}if((0,_typests.isObjectInput)(template)){return Object.values(template).every(v=>this.isValidSyntax(v))}if((0,_typests.isLiteralInput)(template))return true;try{this.getCachedAst(template);return true}catch{return false}}execute(template,data,options){if((0,_typests.isArrayInput)(template)){const result=[];for(const element of template){result.push(this.execute(element,data,options))}return result}if((0,_typests.isObjectInput)(template)){const result={};for(const[key,value]of Object.entries(template)){result[key]=this.execute(value,data,options)}return result}if((0,_typests.isLiteralInput)(template))return template;const ast=this.getCachedAst(template);if(options?.schema){const analysis=(0,_analyzerts.analyzeFromAst)(ast,template,options.schema,{identifierSchemas:options.identifierSchemas,helpers:this.helpers});if(!analysis.valid){throw new _errorsts.TemplateAnalysisError(analysis.diagnostics)}}return(0,_executorts.executeFromAst)(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache})}analyzeAndExecute(template,inputSchema,data,options){if((0,_typests.isArrayInput)(template)){return(0,_utils.aggregateArrayAnalysisAndExecution)(template.length,index=>this.analyzeAndExecute(template[index],inputSchema,data,options))}if((0,_typests.isObjectInput)(template)){return(0,_utils.aggregateObjectAnalysisAndExecution)(Object.keys(template),key=>this.analyzeAndExecute(template[key],inputSchema,data,options))}if((0,_typests.isLiteralInput)(template)){return{analysis:{valid:true,diagnostics:[],outputSchema:(0,_typests.inferPrimitiveSchema)(template)},value:template}}const ast=this.getCachedAst(template);const analysis=(0,_analyzerts.analyzeFromAst)(ast,template,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers});if(!analysis.valid){return{analysis,value:undefined}}const value=(0,_executorts.executeFromAst)(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache});return{analysis,value}}registerHelper(name,definition){this.helpers.set(name,definition);this.hbs.registerHelper(name,definition.fn);this.compilationCache.clear();return this}unregisterHelper(name){this.helpers.delete(name);this.hbs.unregisterHelper(name);this.compilationCache.clear();return this}hasHelper(name){return this.helpers.has(name)}clearCaches(){this.astCache.clear();this.compilationCache.clear()}getCachedAst(template){let ast=this.astCache.get(template);if(!ast){ast=(0,_parserts.parse)(template);this.astCache.set(template,ast)}return ast}constructor(options={}){_define_property(this,"hbs",void 0);_define_property(this,"astCache",void 0);_define_property(this,"compilationCache",void 0);_define_property(this,"helpers",new Map);this.hbs=_handlebars.default.create();this.astCache=new _utils.LRUCache(options.astCacheSize??256);this.compilationCache=new _utils.LRUCache(options.compilationCacheSize??256);new _indexts.MathHelpers().register(this);new _indexts.LogicalHelpers().register(this);if(options.helpers){for(const helper of options.helpers){const{name,...definition}=helper;this.registerHelper(name,definition)}}}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"Typebars",{enumerable:true,get:function(){return Typebars}});const _handlebars=/*#__PURE__*/_interop_require_default(require("handlebars"));const _analyzerts=require("./analyzer.js");const _compiledtemplatets=require("./compiled-template.js");const _errorsts=require("./errors.js");const _executorts=require("./executor.js");const _indexts=require("./helpers/index.js");const _parserts=require("./parser.js");const _schemaresolverts=require("./schema-resolver.js");const _typests=require("./types.js");const _utils=require("./utils.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function _interop_require_default(obj){return obj&&obj.__esModule?obj:{default:obj}}class Typebars{compile(template){if((0,_typests.isArrayInput)(template)){const children=[];for(const element of template){children.push(this.compile(element))}return _compiledtemplatets.CompiledTemplate.fromArray(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isObjectInput)(template)){const children={};for(const[key,value]of Object.entries(template)){children[key]=this.compile(value)}return _compiledtemplatets.CompiledTemplate.fromObject(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isLiteralInput)(template)){return _compiledtemplatets.CompiledTemplate.fromLiteral(template,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}const ast=this.getCachedAst(template);const options={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return _compiledtemplatets.CompiledTemplate.fromTemplate(ast,template,options)}analyze(template,inputSchema,identifierSchemas,expectedOutputType){if((0,_typests.isArrayInput)(template)){return(0,_utils.aggregateArrayAnalysis)(template.length,index=>this.analyze(template[index],inputSchema,identifierSchemas))}if((0,_typests.isObjectInput)(template)){const schemaForProperties=expectedOutputType??inputSchema;return(0,_utils.aggregateObjectAnalysis)(Object.keys(template),key=>{const propertySchema=(0,_schemaresolverts.resolveSchemaPath)(schemaForProperties,[key]);return this.analyze(template[key],inputSchema,identifierSchemas,propertySchema)})}if((0,_typests.isLiteralInput)(template)){return{valid:true,diagnostics:[],outputSchema:(0,_typests.inferPrimitiveSchema)(template)}}const ast=this.getCachedAst(template);return(0,_analyzerts.analyzeFromAst)(ast,template,inputSchema,{identifierSchemas,helpers:this.helpers,expectedOutputType:expectedOutputType??inputSchema})}validate(template,inputSchema,identifierSchemas){const analysis=this.analyze(template,inputSchema,identifierSchemas);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}isValidSyntax(template){if((0,_typests.isArrayInput)(template)){return template.every(v=>this.isValidSyntax(v))}if((0,_typests.isObjectInput)(template)){return Object.values(template).every(v=>this.isValidSyntax(v))}if((0,_typests.isLiteralInput)(template))return true;try{this.getCachedAst(template);return true}catch{return false}}execute(template,data,options){if((0,_typests.isArrayInput)(template)){const result=[];for(const element of template){result.push(this.execute(element,data,options))}return result}if((0,_typests.isObjectInput)(template)){const result={};for(const[key,value]of Object.entries(template)){result[key]=this.execute(value,data,options)}return result}if((0,_typests.isLiteralInput)(template))return template;const ast=this.getCachedAst(template);if(options?.schema){const analysis=(0,_analyzerts.analyzeFromAst)(ast,template,options.schema,{identifierSchemas:options.identifierSchemas,helpers:this.helpers});if(!analysis.valid){throw new _errorsts.TemplateAnalysisError(analysis.diagnostics)}}return(0,_executorts.executeFromAst)(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache})}analyzeAndExecute(template,inputSchema,data,options,expectedOutputType){if((0,_typests.isArrayInput)(template)){return(0,_utils.aggregateArrayAnalysisAndExecution)(template.length,index=>this.analyzeAndExecute(template[index],inputSchema,data,options))}if((0,_typests.isObjectInput)(template)){const schemaForProperties=expectedOutputType??inputSchema;return(0,_utils.aggregateObjectAnalysisAndExecution)(Object.keys(template),key=>{const propertySchema=(0,_schemaresolverts.resolveSchemaPath)(schemaForProperties,[key]);return this.analyzeAndExecute(template[key],inputSchema,data,options,propertySchema)})}if((0,_typests.isLiteralInput)(template)){return{analysis:{valid:true,diagnostics:[],outputSchema:(0,_typests.inferPrimitiveSchema)(template)},value:template}}const ast=this.getCachedAst(template);const analysis=(0,_analyzerts.analyzeFromAst)(ast,template,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers,expectedOutputType:expectedOutputType??inputSchema});if(!analysis.valid){return{analysis,value:undefined}}const value=(0,_executorts.executeFromAst)(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache});return{analysis,value}}registerHelper(name,definition){this.helpers.set(name,definition);this.hbs.registerHelper(name,definition.fn);this.compilationCache.clear();return this}unregisterHelper(name){this.helpers.delete(name);this.hbs.unregisterHelper(name);this.compilationCache.clear();return this}hasHelper(name){return this.helpers.has(name)}clearCaches(){this.astCache.clear();this.compilationCache.clear()}getCachedAst(template){let ast=this.astCache.get(template);if(!ast){ast=(0,_parserts.parse)(template);this.astCache.set(template,ast)}return ast}constructor(options={}){_define_property(this,"hbs",void 0);_define_property(this,"astCache",void 0);_define_property(this,"compilationCache",void 0);_define_property(this,"helpers",new Map);this.hbs=_handlebars.default.create();this.astCache=new _utils.LRUCache(options.astCacheSize??256);this.compilationCache=new _utils.LRUCache(options.compilationCacheSize??256);new _indexts.MathHelpers().register(this);new _indexts.LogicalHelpers().register(this);if(options.helpers){for(const helper of options.helpers){const{name,...definition}=helper;this.registerHelper(name,definition)}}}}
2
2
  //# sourceMappingURL=typebars.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport { LogicalHelpers, MathHelpers } from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateArrayAnalysisAndExecution,\n\taggregateObjectAnalysis,\n\taggregateObjectAnalysisAndExecution,\n\tLRUCache,\n} 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, { 1: node1Schema });\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\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 identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7,\n\t\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t): AnalysisResult {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\t\t\tthis.analyze(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn aggregateObjectAnalysis(Object.keys(template), (key) =>\n\t\t\t\tthis.analyze(\n\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tvalid: true,\n\t\t\t\tdiagnostics: [],\n\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t};\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas,\n\t\t\thelpers: this.helpers,\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\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, identifierSchemas);\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: Record<string, unknown>,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\t// ── Array template → recursive execution ─────────────────────────────\n\t\tif (isArrayInput(template)) {\n\t\t\tconst result: unknown[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tresult.push(this.execute(element, data, options));\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Object template → recursive execution ────────────────────────────\n\t\tif (isObjectInput(template)) {\n\t\t\tconst result: Record<string, unknown> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tresult[key] = this.execute(value, data, options);\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Passthrough for literal values ────────────────────────────────────\n\t\tif (isLiteralInput(template)) return template;\n\n\t\t// ── Parse once ───────────────────────────────────────────────────────\n\t\tconst ast = this.getCachedAst(template);\n\n\t\t// ── Pre-execution static validation ──────────────────────────────────\n\t\tif (options?.schema) {\n\t\t\tconst analysis = analyzeFromAst(ast, template, options.schema, {\n\t\t\t\tidentifierSchemas: options.identifierSchemas,\n\t\t\t\thelpers: this.helpers,\n\t\t\t});\n\t\t\tif (!analysis.valid) {\n\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t}\n\t\t}\n\n\t\t// ── Execution ────────────────────────────────────────────────────────\n\t\treturn executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\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: Record<string, unknown>,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysisAndExecution(template.length, (index) =>\n\t\t\t\tthis.analyzeAndExecute(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tdata,\n\t\t\t\t\toptions,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn aggregateObjectAnalysisAndExecution(Object.keys(template), (key) =>\n\t\t\t\tthis.analyzeAndExecute(\n\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tdata,\n\t\t\t\t\toptions,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tanalysis: {\n\t\t\t\t\tvalid: true,\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t\t},\n\t\t\t\tvalue: template,\n\t\t\t};\n\t\t}\n\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst analysis = analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\thelpers: this.helpers,\n\t\t});\n\n\t\tif (!analysis.valid) {\n\t\t\treturn { analysis, value: undefined };\n\t\t}\n\n\t\tconst value = executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t});\n\t\treturn { analysis, value };\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\t\tthis.hbs.registerHelper(name, definition.fn);\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","compile","template","isArrayInput","children","element","push","CompiledTemplate","fromArray","helpers","hbs","compilationCache","isObjectInput","key","value","Object","entries","fromObject","isLiteralInput","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","identifierSchemas","aggregateArrayAnalysis","length","index","aggregateObjectAnalysis","keys","valid","diagnostics","outputSchema","inferPrimitiveSchema","analyzeFromAst","validate","analysis","isValidSyntax","every","v","values","execute","data","result","schema","TemplateAnalysisError","executeFromAst","identifierData","analyzeAndExecute","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysisAndExecution","undefined","registerHelper","name","definition","set","fn","clear","unregisterHelper","delete","hasHelper","has","clearCaches","astCache","get","parse","Map","Handlebars","create","LRUCache","astCacheSize","compilationCacheSize","MathHelpers","register","LogicalHelpers","helper"],"mappings":"oGA8DaA,kDAAAA,4EA9DU,yCAEQ,mDAIxB,kDAC+B,yCACP,wCACa,8CACtB,sCAef,mCAOA,mRA8BA,MAAMA,SA8CZC,QAAQC,QAAuB,CAAoB,CAClD,GAAIC,GAAAA,qBAAY,EAACD,UAAW,CAC3B,MAAME,SAA+B,EAAE,CACvC,IAAK,MAAMC,WAAWH,SAAU,CAC/BE,SAASE,IAAI,CAAC,IAAI,CAACL,OAAO,CAACI,SAC5B,CACA,OAAOE,oCAAgB,CAACC,SAAS,CAACJ,SAAU,CAC3CK,QAAS,IAAI,CAACA,OAAO,CACrBC,IAAK,IAAI,CAACA,GAAG,CACbC,iBAAkB,IAAI,CAACA,gBAAgB,AACxC,EACD,CACA,GAAIC,GAAAA,sBAAa,EAACV,UAAW,CAC5B,MAAME,SAA6C,CAAC,EACpD,IAAK,KAAM,CAACS,IAAKC,MAAM,GAAIC,OAAOC,OAAO,CAACd,UAAW,CACpDE,QAAQ,CAACS,IAAI,CAAG,IAAI,CAACZ,OAAO,CAACa,MAC9B,CACA,OAAOP,oCAAgB,CAACU,UAAU,CAACb,SAAU,CAC5CK,QAAS,IAAI,CAACA,OAAO,CACrBC,IAAK,IAAI,CAACA,GAAG,CACbC,iBAAkB,IAAI,CAACA,gBAAgB,AACxC,EACD,CACA,GAAIO,GAAAA,uBAAc,EAAChB,UAAW,CAC7B,OAAOK,oCAAgB,CAACY,WAAW,CAACjB,SAAU,CAC7CO,QAAS,IAAI,CAACA,OAAO,CACrBC,IAAK,IAAI,CAACA,GAAG,CACbC,iBAAkB,IAAI,CAACA,gBAAgB,AACxC,EACD,CACA,MAAMS,IAAM,IAAI,CAACC,YAAY,CAACnB,UAC9B,MAAMoB,QAAmC,CACxCb,QAAS,IAAI,CAACA,OAAO,CACrBC,IAAK,IAAI,CAACA,GAAG,CACbC,iBAAkB,IAAI,CAACA,gBAAgB,AACxC,EACA,OAAOJ,oCAAgB,CAACgB,YAAY,CAACH,IAAKlB,SAAUoB,QACrD,CAgBAE,QACCtB,QAAuB,CACvBuB,WAAwB,CACxBC,iBAA+C,CAC9B,CACjB,GAAIvB,GAAAA,qBAAY,EAACD,UAAW,CAC3B,MAAOyB,GAAAA,6BAAsB,EAACzB,SAAS0B,MAAM,CAAE,AAACC,OAC/C,IAAI,CAACL,OAAO,CACXtB,QAAQ,CAAC2B,MAAM,CACfJ,YACAC,mBAGH,CACA,GAAId,GAAAA,sBAAa,EAACV,UAAW,CAC5B,MAAO4B,GAAAA,8BAAuB,EAACf,OAAOgB,IAAI,CAAC7B,UAAW,AAACW,KACtD,IAAI,CAACW,OAAO,CACXtB,QAAQ,CAACW,IAAI,CACbY,YACAC,mBAGH,CACA,GAAIR,GAAAA,uBAAc,EAAChB,UAAW,CAC7B,MAAO,CACN8B,MAAO,KACPC,YAAa,EAAE,CACfC,aAAcC,GAAAA,6BAAoB,EAACjC,SACpC,CACD,CACA,MAAMkB,IAAM,IAAI,CAACC,YAAY,CAACnB,UAC9B,MAAOkC,GAAAA,0BAAc,EAAChB,IAAKlB,SAAUuB,YAAa,CACjDC,kBACAjB,QAAS,IAAI,CAACA,OAAO,AACtB,EACD,CAgBA4B,SACCnC,QAAuB,CACvBuB,WAAwB,CACxBC,iBAA+C,CAC5B,CACnB,MAAMY,SAAW,IAAI,CAACd,OAAO,CAACtB,SAAUuB,YAAaC,mBACrD,MAAO,CACNM,MAAOM,SAASN,KAAK,CACrBC,YAAaK,SAASL,WAAW,AAClC,CACD,CAaAM,cAAcrC,QAAuB,CAAW,CAC/C,GAAIC,GAAAA,qBAAY,EAACD,UAAW,CAC3B,OAAOA,SAASsC,KAAK,CAAC,AAACC,GAAM,IAAI,CAACF,aAAa,CAACE,GACjD,CACA,GAAI7B,GAAAA,sBAAa,EAACV,UAAW,CAC5B,OAAOa,OAAO2B,MAAM,CAACxC,UAAUsC,KAAK,CAAC,AAACC,GAAM,IAAI,CAACF,aAAa,CAACE,GAChE,CACA,GAAIvB,GAAAA,uBAAc,EAAChB,UAAW,OAAO,KACrC,GAAI,CACH,IAAI,CAACmB,YAAY,CAACnB,UAClB,OAAO,IACR,CAAE,KAAM,CACP,OAAO,KACR,CACD,CAmBAyC,QACCzC,QAAuB,CACvB0C,IAA6B,CAC7BtB,OAAwB,CACd,CAEV,GAAInB,GAAAA,qBAAY,EAACD,UAAW,CAC3B,MAAM2C,OAAoB,EAAE,CAC5B,IAAK,MAAMxC,WAAWH,SAAU,CAC/B2C,OAAOvC,IAAI,CAAC,IAAI,CAACqC,OAAO,CAACtC,QAASuC,KAAMtB,SACzC,CACA,OAAOuB,MACR,CAGA,GAAIjC,GAAAA,sBAAa,EAACV,UAAW,CAC5B,MAAM2C,OAAkC,CAAC,EACzC,IAAK,KAAM,CAAChC,IAAKC,MAAM,GAAIC,OAAOC,OAAO,CAACd,UAAW,CACpD2C,MAAM,CAAChC,IAAI,CAAG,IAAI,CAAC8B,OAAO,CAAC7B,MAAO8B,KAAMtB,QACzC,CACA,OAAOuB,MACR,CAGA,GAAI3B,GAAAA,uBAAc,EAAChB,UAAW,OAAOA,SAGrC,MAAMkB,IAAM,IAAI,CAACC,YAAY,CAACnB,UAG9B,GAAIoB,SAASwB,OAAQ,CACpB,MAAMR,SAAWF,GAAAA,0BAAc,EAAChB,IAAKlB,SAAUoB,QAAQwB,MAAM,CAAE,CAC9DpB,kBAAmBJ,QAAQI,iBAAiB,CAC5CjB,QAAS,IAAI,CAACA,OAAO,AACtB,GACA,GAAI,CAAC6B,SAASN,KAAK,CAAE,CACpB,MAAM,IAAIe,+BAAqB,CAACT,SAASL,WAAW,CACrD,CACD,CAGA,MAAOe,GAAAA,0BAAc,EAAC5B,IAAKlB,SAAU0C,KAAM,CAC1CK,eAAgB3B,SAAS2B,eACzBvC,IAAK,IAAI,CAACA,GAAG,CACbC,iBAAkB,IAAI,CAACA,gBAAgB,AACxC,EACD,CAkBAuC,kBACChD,QAAuB,CACvBuB,WAAwB,CACxBmB,IAA6B,CAC7BtB,OAAkC,CACa,CAC/C,GAAInB,GAAAA,qBAAY,EAACD,UAAW,CAC3B,MAAOiD,GAAAA,yCAAkC,EAACjD,SAAS0B,MAAM,CAAE,AAACC,OAC3D,IAAI,CAACqB,iBAAiB,CACrBhD,QAAQ,CAAC2B,MAAM,CACfJ,YACAmB,KACAtB,SAGH,CACA,GAAIV,GAAAA,sBAAa,EAACV,UAAW,CAC5B,MAAOkD,GAAAA,0CAAmC,EAACrC,OAAOgB,IAAI,CAAC7B,UAAW,AAACW,KAClE,IAAI,CAACqC,iBAAiB,CACrBhD,QAAQ,CAACW,IAAI,CACbY,YACAmB,KACAtB,SAGH,CAEA,GAAIJ,GAAAA,uBAAc,EAAChB,UAAW,CAC7B,MAAO,CACNoC,SAAU,CACTN,MAAO,KACPC,YAAa,EAAE,CACfC,aAAcC,GAAAA,6BAAoB,EAACjC,SACpC,EACAY,MAAOZ,QACR,CACD,CAEA,MAAMkB,IAAM,IAAI,CAACC,YAAY,CAACnB,UAC9B,MAAMoC,SAAWF,GAAAA,0BAAc,EAAChB,IAAKlB,SAAUuB,YAAa,CAC3DC,kBAAmBJ,SAASI,kBAC5BjB,QAAS,IAAI,CAACA,OAAO,AACtB,GAEA,GAAI,CAAC6B,SAASN,KAAK,CAAE,CACpB,MAAO,CAAEM,SAAUxB,MAAOuC,SAAU,CACrC,CAEA,MAAMvC,MAAQkC,GAAAA,0BAAc,EAAC5B,IAAKlB,SAAU0C,KAAM,CACjDK,eAAgB3B,SAAS2B,eACzBvC,IAAK,IAAI,CAACA,GAAG,CACbC,iBAAkB,IAAI,CAACA,gBAAgB,AACxC,GACA,MAAO,CAAE2B,SAAUxB,KAAM,CAC1B,CAcAwC,eAAeC,IAAY,CAAEC,UAA4B,CAAQ,CAChE,IAAI,CAAC/C,OAAO,CAACgD,GAAG,CAACF,KAAMC,YACvB,IAAI,CAAC9C,GAAG,CAAC4C,cAAc,CAACC,KAAMC,WAAWE,EAAE,EAG3C,IAAI,CAAC/C,gBAAgB,CAACgD,KAAK,GAE3B,OAAO,IAAI,AACZ,CAQAC,iBAAiBL,IAAY,CAAQ,CACpC,IAAI,CAAC9C,OAAO,CAACoD,MAAM,CAACN,MACpB,IAAI,CAAC7C,GAAG,CAACkD,gBAAgB,CAACL,MAG1B,IAAI,CAAC5C,gBAAgB,CAACgD,KAAK,GAE3B,OAAO,IAAI,AACZ,CAQAG,UAAUP,IAAY,CAAW,CAChC,OAAO,IAAI,CAAC9C,OAAO,CAACsD,GAAG,CAACR,KACzB,CASAS,aAAoB,CACnB,IAAI,CAACC,QAAQ,CAACN,KAAK,GACnB,IAAI,CAAChD,gBAAgB,CAACgD,KAAK,EAC5B,CAOA,AAAQtC,aAAanB,QAAgB,CAAmB,CACvD,IAAIkB,IAAM,IAAI,CAAC6C,QAAQ,CAACC,GAAG,CAAChE,UAC5B,GAAI,CAACkB,IAAK,CACTA,IAAM+C,GAAAA,eAAK,EAACjE,UACZ,IAAI,CAAC+D,QAAQ,CAACR,GAAG,CAACvD,SAAUkB,IAC7B,CACA,OAAOA,GACR,CA9XA,YAAYE,QAAiC,CAAC,CAAC,CAAE,CAdjD,sBAAiBZ,MAAjB,KAAA,GAGA,sBAAiBuD,WAAjB,KAAA,GAGA,sBAAiBtD,mBAAjB,KAAA,GAMA,sBAAiBF,UAAU,IAAI2D,IAG9B,CAAA,IAAI,CAAC1D,GAAG,CAAG2D,mBAAU,CAACC,MAAM,EAC5B,CAAA,IAAI,CAACL,QAAQ,CAAG,IAAIM,eAAQ,CAACjD,QAAQkD,YAAY,EAAI,IACrD,CAAA,IAAI,CAAC7D,gBAAgB,CAAG,IAAI4D,eAAQ,CAACjD,QAAQmD,oBAAoB,EAAI,KAGrE,IAAIC,oBAAW,GAAGC,QAAQ,CAAC,IAAI,EAC/B,IAAIC,uBAAc,GAAGD,QAAQ,CAAC,IAAI,EAGlC,GAAIrD,QAAQb,OAAO,CAAE,CACpB,IAAK,MAAMoE,UAAUvD,QAAQb,OAAO,CAAE,CACrC,KAAM,CAAE8C,IAAI,CAAE,GAAGC,WAAY,CAAGqB,OAChC,IAAI,CAACvB,cAAc,CAACC,KAAMC,WAC3B,CACD,CACD,CA+WD"}
1
+ {"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport { LogicalHelpers, MathHelpers } from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport { resolveSchemaPath } from \"./schema-resolver.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateArrayAnalysisAndExecution,\n\taggregateObjectAnalysis,\n\taggregateObjectAnalysisAndExecution,\n\tLRUCache,\n} 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, { 1: node1Schema });\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\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 identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7,\n\t\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t\texpectedOutputType?: JSONSchema7,\n\t): AnalysisResult {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\t\t\tthis.analyze(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\t// Use the expectedOutputType (if it describes an object) to resolve\n\t\t\t// child property schemas. This is critical for deeply nested objects:\n\t\t\t// when the template is `{ a: { b: { c: \"123\" } } }`, each level must\n\t\t\t// resolve its children from the *corresponding* sub-schema, not from\n\t\t\t// the root inputSchema.\n\t\t\tconst schemaForProperties = expectedOutputType ?? inputSchema;\n\t\t\treturn aggregateObjectAnalysis(Object.keys(template), (key) => {\n\t\t\t\tconst propertySchema = resolveSchemaPath(schemaForProperties, [key]);\n\t\t\t\treturn this.analyze(\n\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t\tpropertySchema,\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tvalid: true,\n\t\t\t\tdiagnostics: [],\n\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t};\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas,\n\t\t\thelpers: this.helpers,\n\t\t\texpectedOutputType: expectedOutputType ?? inputSchema,\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\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, identifierSchemas);\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: Record<string, unknown>,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\t// ── Array template → recursive execution ─────────────────────────────\n\t\tif (isArrayInput(template)) {\n\t\t\tconst result: unknown[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tresult.push(this.execute(element, data, options));\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Object template → recursive execution ────────────────────────────\n\t\tif (isObjectInput(template)) {\n\t\t\tconst result: Record<string, unknown> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tresult[key] = this.execute(value, data, options);\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Passthrough for literal values ────────────────────────────────────\n\t\tif (isLiteralInput(template)) return template;\n\n\t\t// ── Parse once ───────────────────────────────────────────────────────\n\t\tconst ast = this.getCachedAst(template);\n\n\t\t// ── Pre-execution static validation ──────────────────────────────────\n\t\tif (options?.schema) {\n\t\t\tconst analysis = analyzeFromAst(ast, template, options.schema, {\n\t\t\t\tidentifierSchemas: options.identifierSchemas,\n\t\t\t\thelpers: this.helpers,\n\t\t\t});\n\t\t\tif (!analysis.valid) {\n\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t}\n\t\t}\n\n\t\t// ── Execution ────────────────────────────────────────────────────────\n\t\treturn executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\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: Record<string, unknown>,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t\texpectedOutputType?: JSONSchema7,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysisAndExecution(template.length, (index) =>\n\t\t\t\tthis.analyzeAndExecute(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tdata,\n\t\t\t\t\toptions,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\t// Use the expectedOutputType (if it describes an object) to resolve\n\t\t\t// child property schemas. This is critical for deeply nested objects.\n\t\t\tconst schemaForProperties = expectedOutputType ?? inputSchema;\n\t\t\treturn aggregateObjectAnalysisAndExecution(\n\t\t\t\tObject.keys(template),\n\t\t\t\t(key) => {\n\t\t\t\t\tconst propertySchema = resolveSchemaPath(schemaForProperties, [key]);\n\t\t\t\t\treturn this.analyzeAndExecute(\n\t\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\t\tinputSchema,\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\toptions,\n\t\t\t\t\t\tpropertySchema,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tanalysis: {\n\t\t\t\t\tvalid: true,\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t\t},\n\t\t\t\tvalue: template,\n\t\t\t};\n\t\t}\n\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst analysis = analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\thelpers: this.helpers,\n\t\t\texpectedOutputType: expectedOutputType ?? inputSchema,\n\t\t});\n\n\t\tif (!analysis.valid) {\n\t\t\treturn { analysis, value: undefined };\n\t\t}\n\n\t\tconst value = executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t});\n\t\treturn { analysis, value };\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\t\tthis.hbs.registerHelper(name, definition.fn);\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","compile","template","isArrayInput","children","element","push","CompiledTemplate","fromArray","helpers","hbs","compilationCache","isObjectInput","key","value","Object","entries","fromObject","isLiteralInput","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","identifierSchemas","expectedOutputType","aggregateArrayAnalysis","length","index","schemaForProperties","aggregateObjectAnalysis","keys","propertySchema","resolveSchemaPath","valid","diagnostics","outputSchema","inferPrimitiveSchema","analyzeFromAst","validate","analysis","isValidSyntax","every","v","values","execute","data","result","schema","TemplateAnalysisError","executeFromAst","identifierData","analyzeAndExecute","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysisAndExecution","undefined","registerHelper","name","definition","set","fn","clear","unregisterHelper","delete","hasHelper","has","clearCaches","astCache","get","parse","Map","Handlebars","create","LRUCache","astCacheSize","compilationCacheSize","MathHelpers","register","LogicalHelpers","helper"],"mappings":"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"}
@@ -14,6 +14,14 @@ interface AnalysisContext {
14
14
  identifierSchemas?: Record<number, JSONSchema7>;
15
15
  /** Registered custom helpers (for static analysis) */
16
16
  helpers?: Map<string, HelperDefinition>;
17
+ /**
18
+ * Expected output type from the inputSchema.
19
+ * When the inputSchema declares a specific type (e.g. `{ type: "string" }`),
20
+ * static literal values like `"123"` should respect that type instead of
21
+ * being auto-detected as `number`. This allows the schema contract to
22
+ * override the default `detectLiteralType` inference.
23
+ */
24
+ expectedOutputType?: JSONSchema7;
17
25
  }
18
26
  /**
19
27
  * Statically analyzes a template against a JSON Schema v7 describing the
@@ -27,7 +35,7 @@ interface AnalysisContext {
27
35
  * @returns An `AnalysisResult` containing validity, diagnostics, and the
28
36
  * inferred output schema.
29
37
  */
30
- export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
38
+ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>, expectedOutputType?: JSONSchema7): AnalysisResult;
31
39
  /**
32
40
  * Statically analyzes a template from an already-parsed AST.
33
41
  *
@@ -43,6 +51,12 @@ export declare function analyze(template: TemplateInput, inputSchema: JSONSchema
43
51
  export declare function analyzeFromAst(ast: hbs.AST.Program, template: string, inputSchema: JSONSchema7, options?: {
44
52
  identifierSchemas?: Record<number, JSONSchema7>;
45
53
  helpers?: Map<string, HelperDefinition>;
54
+ /**
55
+ * When set, provides the expected output type from the parent context
56
+ * (e.g. the inputSchema's property sub-schema for an object template key).
57
+ * Static literal values will respect this type instead of auto-detecting.
58
+ */
59
+ expectedOutputType?: JSONSchema7;
46
60
  }): AnalysisResult;
47
61
  /**
48
62
  * Infers the output type of a BlockStatement and validates its content.
@@ -1,2 +1,2 @@
1
- import{createMissingArgumentMessage,createPropertyNotFoundMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isThisExpression,parse}from"./parser.js";import{assertNoConditionalSchema,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{inferPrimitiveSchema,isArrayInput,isLiteralInput,isObjectInput}from"./types.js";import{aggregateArrayAnalysis,aggregateObjectAnalysis,deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";export function analyze(template,inputSchema,identifierSchemas){if(isArrayInput(template)){return analyzeArrayTemplate(template,inputSchema,identifierSchemas)}if(isObjectInput(template)){return analyzeObjectTemplate(template,inputSchema,identifierSchemas)}if(isLiteralInput(template)){return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)}}const ast=parse(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas})}function analyzeArrayTemplate(template,inputSchema,identifierSchemas){return aggregateArrayAnalysis(template.length,index=>analyze(template[index],inputSchema,identifierSchemas))}function analyzeObjectTemplate(template,inputSchema,identifierSchemas){return aggregateObjectAnalysis(Object.keys(template),key=>analyze(template[key],inputSchema,identifierSchemas))}export function analyzeFromAst(ast,template,inputSchema,options){assertNoConditionalSchema(inputSchema);if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){assertNoConditionalSchema(idSchema,`/identifierSchemas/${id}`)}}const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers};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);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 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 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(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 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 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);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 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{createMissingArgumentMessage,createPropertyNotFoundMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isThisExpression,parse}from"./parser.js";import{assertNoConditionalSchema,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{inferPrimitiveSchema,isArrayInput,isLiteralInput,isObjectInput}from"./types.js";import{aggregateArrayAnalysis,aggregateObjectAnalysis,deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";export function analyze(template,inputSchema,identifierSchemas,expectedOutputType){if(isArrayInput(template)){return analyzeArrayTemplate(template,inputSchema,identifierSchemas)}if(isObjectInput(template)){return analyzeObjectTemplate(template,inputSchema,identifierSchemas,expectedOutputType)}if(isLiteralInput(template)){return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)}}const ast=parse(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas,expectedOutputType:expectedOutputType??inputSchema})}function analyzeArrayTemplate(template,inputSchema,identifierSchemas){return aggregateArrayAnalysis(template.length,index=>analyze(template[index],inputSchema,identifierSchemas))}function analyzeObjectTemplate(template,inputSchema,identifierSchemas,expectedOutputType){const schemaForProperties=expectedOutputType??inputSchema;return aggregateObjectAnalysis(Object.keys(template),key=>{const propertySchema=resolveSchemaPath(schemaForProperties,[key]);return analyze(template[key],inputSchema,identifierSchemas,propertySchema)})}export function analyzeFromAst(ast,template,inputSchema,options){assertNoConditionalSchema(inputSchema);if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){assertNoConditionalSchema(idSchema,`/identifierSchemas/${id}`)}}const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,expectedOutputType:options?.expectedOutputType};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);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 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 expectedType=ctx.expectedOutputType?.type;if(typeof expectedType==="string"&&(expectedType==="string"||expectedType==="number"||expectedType==="integer"||expectedType==="boolean"||expectedType==="null")){return{type:expectedType}}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(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 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 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);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 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
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser\";\nimport {\n\tassertNoConditionalSchema,\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputArray,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\tif (isArrayInput(template)) {\n\t\treturn analyzeArrayTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\treturn analyzeFromAst(ast, template, inputSchema, { identifierSchemas });\n}\n\n/**\n * Analyzes an array template recursively (standalone version).\n * Each element is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the array structure with a proper `items`.\n */\nfunction analyzeArrayTemplate(\n\ttemplate: TemplateInputArray,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\tanalyze(template[index] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) =>\n\t\tanalyze(template[key] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t},\n): AnalysisResult {\n\t// ── Reject unsupported schema features before analysis ────────────\n\t// Conditional schemas (if/then/else) are non-resolvable without runtime\n\t// data. Fail fast with a clear error rather than producing silently\n\t// incorrect results.\n\tassertNoConditionalSchema(inputSchema);\n\n\tif (options?.identifierSchemas) {\n\t\tfor (const [id, idSchema] of Object.entries(options.identifierSchemas)) {\n\t\t\tassertNoConditionalSchema(idSchema, `/identifierSchemas/${id}`);\n\t\t}\n\t}\n\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"],"names":["createMissingArgumentMessage","createPropertyNotFoundMessage","createTypeMismatchMessage","createUnanalyzableMessage","createUnknownHelperMessage","detectLiteralType","extractExpressionIdentifier","extractPathSegments","getEffectiveBody","getEffectivelySingleBlock","getEffectivelySingleExpression","isThisExpression","parse","assertNoConditionalSchema","resolveArrayItems","resolveSchemaPath","simplifySchema","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","aggregateArrayAnalysis","aggregateObjectAnalysis","deepEqual","extractSourceSnippet","getSchemaPropertyNames","analyze","template","inputSchema","identifierSchemas","analyzeArrayTemplate","analyzeObjectTemplate","valid","diagnostics","outputSchema","ast","analyzeFromAst","length","index","Object","keys","key","options","id","idSchema","entries","ctx","root","current","helpers","inferProgramType","hasErrors","some","d","severity","processStatement","stmt","type","undefined","processMustache","inferBlockType","addDiagnostic","path","params","hash","helperName","getExpressionName","helper","get","helperParams","requiredCount","filter","p","optional","expected","actual","i","resolvedSchema","resolveExpressionWithDiagnostics","helperParam","expectedType","isParamTypeCompatible","paramName","name","schemaTypeLabel","returnType","resolved","expectedTypes","Array","isArray","resolvedTypes","rt","et","program","effective","singleExpr","singleBlock","allContent","every","s","text","map","value","join","trim","literalType","allBlocks","types","t","push","oneOf","body","getBlockHelperName","arg","getBlockArgument","thenType","inverse","elseType","saved","collectionSchema","itemSchema","result","innerSchema","param","expr","parentNode","resolveSubExpression","segments","cleanSegments","identifier","resolveWithIdentifier","fullPath","availableProperties","node","original","code","message","details","diagnostic","loc","start","line","column","end","source","schema","anyOf","allOf","enum"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/analyzer.ts"],"sourcesContent":["import type { JSONSchema7 } from \"json-schema\";\nimport {\n\tcreateMissingArgumentMessage,\n\tcreatePropertyNotFoundMessage,\n\tcreateTypeMismatchMessage,\n\tcreateUnanalyzableMessage,\n\tcreateUnknownHelperMessage,\n} from \"./errors\";\nimport {\n\tdetectLiteralType,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisThisExpression,\n\tparse,\n} from \"./parser\";\nimport {\n\tassertNoConditionalSchema,\n\tresolveArrayItems,\n\tresolveSchemaPath,\n\tsimplifySchema,\n} from \"./schema-resolver\";\nimport type {\n\tAnalysisResult,\n\tDiagnosticCode,\n\tDiagnosticDetails,\n\tHelperDefinition,\n\tTemplateDiagnostic,\n\tTemplateInput,\n\tTemplateInputArray,\n\tTemplateInputObject,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateObjectAnalysis,\n\tdeepEqual,\n\textractSourceSnippet,\n\tgetSchemaPropertyNames,\n} from \"./utils\";\n\n// ─── Static Analyzer ─────────────────────────────────────────────────────────\n// Static analysis of a Handlebars template against a JSON Schema v7\n// describing the available context.\n//\n// Merged architecture (v2):\n// A single AST traversal performs both **validation** and **return type\n// inference** simultaneously. This eliminates duplication between the former\n// `validate*` and `infer*` functions and improves performance by avoiding\n// a double traversal.\n//\n// Context:\n// The analysis context uses a **save/restore** pattern instead of creating\n// new objects on each recursion (`{ ...ctx, current: X }`). This reduces\n// GC pressure for deeply nested templates.\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing a variable from a specific\n// schema, identified by an integer N. The optional `identifierSchemas`\n// parameter provides a mapping `{ [id]: JSONSchema7 }`.\n//\n// Resolution rules:\n// - `{{meetingId}}` → validated against `inputSchema` (standard behavior)\n// - `{{meetingId:1}}` → validated against `identifierSchemas[1]`\n// - `{{meetingId:1}}` without `identifierSchemas[1]` → error\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Context passed recursively during AST traversal */\ninterface AnalysisContext {\n\t/** Root schema (for resolving $refs) */\n\troot: JSONSchema7;\n\t/** Current context schema (changes with #each, #with) — mutated via save/restore */\n\tcurrent: JSONSchema7;\n\t/** Diagnostics accumulator */\n\tdiagnostics: TemplateDiagnostic[];\n\t/** Full template source (for extracting error snippets) */\n\ttemplate: string;\n\t/** Schemas by template identifier (for the {{key:N}} syntax) */\n\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t/** Registered custom helpers (for static analysis) */\n\thelpers?: Map<string, HelperDefinition>;\n\t/**\n\t * Expected output type from the inputSchema.\n\t * When the inputSchema declares a specific type (e.g. `{ type: \"string\" }`),\n\t * static literal values like `\"123\"` should respect that type instead of\n\t * being auto-detected as `number`. This allows the schema contract to\n\t * override the default `detectLiteralType` inference.\n\t */\n\texpectedOutputType?: JSONSchema7;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Statically analyzes a template against a JSON Schema v7 describing the\n * available context.\n *\n * Backward-compatible version — parses the template internally.\n *\n * @param template - The template string (e.g. `\"Hello {{user.name}}\"`)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n * @returns An `AnalysisResult` containing validity, diagnostics, and the\n * inferred output schema.\n */\nexport function analyze(\n\ttemplate: TemplateInput,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n\texpectedOutputType?: JSONSchema7,\n): AnalysisResult {\n\tif (isArrayInput(template)) {\n\t\treturn analyzeArrayTemplate(template, inputSchema, identifierSchemas);\n\t}\n\tif (isObjectInput(template)) {\n\t\treturn analyzeObjectTemplate(\n\t\t\ttemplate,\n\t\t\tinputSchema,\n\t\t\tidentifierSchemas,\n\t\t\texpectedOutputType,\n\t\t);\n\t}\n\tif (isLiteralInput(template)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tdiagnostics: [],\n\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t};\n\t}\n\tconst ast = parse(template);\n\t// When an explicit expectedOutputType is provided (e.g. from an object\n\t// template property), use it. Otherwise, fall back to the inputSchema\n\t// itself — when the inputSchema has a primitive type (e.g. { type: \"string\" }),\n\t// it constrains the output type of static literal values.\n\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\tidentifierSchemas,\n\t\texpectedOutputType: expectedOutputType ?? inputSchema,\n\t});\n}\n\n/**\n * Analyzes an array template recursively (standalone version).\n * Each element is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the array structure with a proper `items`.\n */\nfunction analyzeArrayTemplate(\n\ttemplate: TemplateInputArray,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n): AnalysisResult {\n\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\tanalyze(template[index] as TemplateInput, inputSchema, identifierSchemas),\n\t);\n}\n\n/**\n * Analyzes an object template recursively (standalone version).\n * Each property is analyzed individually, diagnostics are merged,\n * and the `outputSchema` reflects the object structure.\n */\nfunction analyzeObjectTemplate(\n\ttemplate: TemplateInputObject,\n\tinputSchema: JSONSchema7,\n\tidentifierSchemas?: Record<number, JSONSchema7>,\n\texpectedOutputType?: JSONSchema7,\n): AnalysisResult {\n\t// Use the expectedOutputType (if it describes an object) to resolve\n\t// child property schemas. This is critical for deeply nested objects:\n\t// when the template is `{ a: { b: { c: \"123\" } } }`, each level must\n\t// resolve its children from the *corresponding* sub-schema, not from\n\t// the root inputSchema.\n\tconst schemaForProperties = expectedOutputType ?? inputSchema;\n\treturn aggregateObjectAnalysis(Object.keys(template), (key) => {\n\t\tconst propertySchema = resolveSchemaPath(schemaForProperties, [key]);\n\t\treturn analyze(\n\t\t\ttemplate[key] as TemplateInput,\n\t\t\tinputSchema,\n\t\t\tidentifierSchemas,\n\t\t\tpropertySchema,\n\t\t);\n\t});\n}\n\n/**\n * Statically analyzes a template from an already-parsed AST.\n *\n * This is the internal function used by `Typebars.compile()` and\n * `CompiledTemplate.analyze()` to avoid costly re-parsing.\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for error snippets)\n * @param inputSchema - JSON Schema v7 describing the available variables\n * @param options - Additional options\n * @returns An `AnalysisResult`\n */\nexport function analyzeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tinputSchema: JSONSchema7,\n\toptions?: {\n\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\thelpers?: Map<string, HelperDefinition>;\n\t\t/**\n\t\t * When set, provides the expected output type from the parent context\n\t\t * (e.g. the inputSchema's property sub-schema for an object template key).\n\t\t * Static literal values will respect this type instead of auto-detecting.\n\t\t */\n\t\texpectedOutputType?: JSONSchema7;\n\t},\n): AnalysisResult {\n\t// ── Reject unsupported schema features before analysis ────────────\n\t// Conditional schemas (if/then/else) are non-resolvable without runtime\n\t// data. Fail fast with a clear error rather than producing silently\n\t// incorrect results.\n\tassertNoConditionalSchema(inputSchema);\n\n\tif (options?.identifierSchemas) {\n\t\tfor (const [id, idSchema] of Object.entries(options.identifierSchemas)) {\n\t\t\tassertNoConditionalSchema(idSchema, `/identifierSchemas/${id}`);\n\t\t}\n\t}\n\n\tconst ctx: AnalysisContext = {\n\t\troot: inputSchema,\n\t\tcurrent: inputSchema,\n\t\tdiagnostics: [],\n\t\ttemplate,\n\t\tidentifierSchemas: options?.identifierSchemas,\n\t\thelpers: options?.helpers,\n\t\texpectedOutputType: options?.expectedOutputType,\n\t};\n\n\t// Single pass: type inference + validation in one traversal.\n\tconst outputSchema = inferProgramType(ast, ctx);\n\n\tconst hasErrors = ctx.diagnostics.some((d) => d.severity === \"error\");\n\n\treturn {\n\t\tvalid: !hasErrors,\n\t\tdiagnostics: ctx.diagnostics,\n\t\toutputSchema: simplifySchema(outputSchema),\n\t};\n}\n\n// ─── Unified AST Traversal ───────────────────────────────────────────────────\n// A single set of functions handles both validation (emitting diagnostics)\n// and type inference (returning a JSONSchema7).\n//\n// Main functions:\n// - `inferProgramType` — entry point for a Program (template body or block)\n// - `processStatement` — dispatches a statement (validation side-effects)\n// - `processMustache` — handles a MustacheStatement (expression or inline helper)\n// - `inferBlockType` — handles a BlockStatement (if, each, with, custom…)\n\n/**\n * Dispatches the processing of an individual statement.\n *\n * Called by `inferProgramType` in the \"mixed template\" case to validate\n * each statement while ignoring the returned type (the result is always\n * `string` for a mixed template).\n *\n * @returns The inferred schema for this statement, or `undefined` for\n * statements with no semantics (ContentStatement, CommentStatement).\n */\nfunction processStatement(\n\tstmt: hbs.AST.Statement,\n\tctx: AnalysisContext,\n): JSONSchema7 | undefined {\n\tswitch (stmt.type) {\n\t\tcase \"ContentStatement\":\n\t\tcase \"CommentStatement\":\n\t\t\t// Static text or comment — nothing to validate, no type to infer\n\t\t\treturn undefined;\n\n\t\tcase \"MustacheStatement\":\n\t\t\treturn processMustache(stmt as hbs.AST.MustacheStatement, ctx);\n\n\t\tcase \"BlockStatement\":\n\t\t\treturn inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\n\t\tdefault:\n\t\t\t// Unrecognized AST node — emit a warning rather than an error\n\t\t\t// to avoid blocking on future Handlebars extensions.\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNANALYZABLE\",\n\t\t\t\t\"warning\",\n\t\t\t\t`Unsupported AST node type: \"${stmt.type}\"`,\n\t\t\t\tstmt,\n\t\t\t);\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * Processes a MustacheStatement `{{expression}}` or `{{helper arg}}`.\n *\n * Distinguishes two cases:\n * 1. **Simple expression** (`{{name}}`, `{{user.age}}`) — resolution in the schema\n * 2. **Inline helper** (`{{uppercase name}}`) — params > 0 or hash present\n *\n * @returns The inferred schema for this expression\n */\nfunction processMustache(\n\tstmt: hbs.AST.MustacheStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\t// Sub-expressions (nested helpers) are not supported for static\n\t// analysis — emit a warning.\n\tif (stmt.path.type === \"SubExpression\") {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\t\"Sub-expressions are not statically analyzable\",\n\t\t\tstmt,\n\t\t);\n\t\treturn {};\n\t}\n\n\t// ── Inline helper detection ──────────────────────────────────────────────\n\t// If the MustacheStatement has parameters or a hash, it's a helper call\n\t// (e.g. `{{uppercase name}}`), not a simple expression.\n\tif (stmt.params.length > 0 || stmt.hash) {\n\t\tconst helperName = getExpressionName(stmt.path);\n\n\t\t// Check if the helper is registered\n\t\tconst helper = ctx.helpers?.get(helperName);\n\t\tif (helper) {\n\t\t\tconst helperParams = helper.params;\n\n\t\t\t// ── Check the number of required parameters ──────────────\n\t\t\tif (helperParams) {\n\t\t\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\t\t\tif (stmt.params.length < requiredCount) {\n\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\t\t\tactual: `${stmt.params.length} argument(s)`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// ── Validate each parameter (existence + type) ───────────────\n\t\t\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\t\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\t\t\tstmt.params[i] as hbs.AST.Expression,\n\t\t\t\t\tctx,\n\t\t\t\t\tstmt,\n\t\t\t\t);\n\n\t\t\t\t// Check type compatibility if the helper declares the\n\t\t\t\t// expected type for this parameter\n\t\t\t\tconst helperParam = helperParams?.[i];\n\t\t\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\t\t\tconst expectedType = helperParam.type;\n\t\t\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\t\t\taddDiagnostic(\n\t\t\t\t\t\t\tctx,\n\t\t\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\t\t\tstmt,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\thelperName,\n\t\t\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t}\n\n\t\t// Unknown inline helper — warning\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown inline helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tstmt,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Simple expression ────────────────────────────────────────────────────\n\treturn resolveExpressionWithDiagnostics(stmt.path, ctx, stmt) ?? {};\n}\n\n/**\n * Checks whether a resolved type is compatible with the type expected\n * by a helper parameter.\n *\n * Compatibility rules:\n * - If either schema has no `type`, validation is not possible → compatible\n * - `integer` is compatible with `number` (integer ⊂ number)\n * - For multiple types (e.g. `[\"string\", \"number\"]`), at least one resolved\n * type must match one expected type\n */\nfunction isParamTypeCompatible(\n\tresolved: JSONSchema7,\n\texpected: JSONSchema7,\n): boolean {\n\t// If either has no type info, we cannot validate\n\tif (!expected.type || !resolved.type) return true;\n\n\tconst expectedTypes = Array.isArray(expected.type)\n\t\t? expected.type\n\t\t: [expected.type];\n\tconst resolvedTypes = Array.isArray(resolved.type)\n\t\t? resolved.type\n\t\t: [resolved.type];\n\n\t// At least one resolved type must be compatible with one expected type\n\treturn resolvedTypes.some((rt) =>\n\t\texpectedTypes.some(\n\t\t\t(et) =>\n\t\t\t\trt === et ||\n\t\t\t\t// integer is a subtype of number\n\t\t\t\t(et === \"number\" && rt === \"integer\") ||\n\t\t\t\t(et === \"integer\" && rt === \"number\"),\n\t\t),\n\t);\n}\n\n/**\n * Infers the output type of a `Program` (template body or block body).\n *\n * Handles 4 cases, from most specific to most general:\n *\n * 1. **Single expression** `{{expr}}` → type of the expression\n * 2. **Single block** `{{#if}}…{{/if}}` → type of the block\n * 3. **Pure text content** → literal detection (number, boolean, null)\n * 4. **Mixed template** → always `string` (concatenation)\n *\n * Validation is performed alongside inference: each expression and block\n * is validated during processing.\n */\nfunction inferProgramType(\n\tprogram: hbs.AST.Program,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst effective = getEffectiveBody(program);\n\n\t// No significant statements → empty string\n\tif (effective.length === 0) {\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 1: single expression {{expr}} ─────────────────────────────────\n\tconst singleExpr = getEffectivelySingleExpression(program);\n\tif (singleExpr) {\n\t\treturn processMustache(singleExpr, ctx);\n\t}\n\n\t// ── Case 2: single block {{#if}}, {{#each}}, {{#with}}, … ──────────────\n\tconst singleBlock = getEffectivelySingleBlock(program);\n\tif (singleBlock) {\n\t\treturn inferBlockType(singleBlock, ctx);\n\t}\n\n\t// ── Case 3: only ContentStatements (no expressions) ────────────────────\n\t// If the concatenated (trimmed) text is a typed literal (number, boolean,\n\t// null), we infer the corresponding type.\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\tconst text = effective\n\t\t\t.map((s) => (s as hbs.AST.ContentStatement).value)\n\t\t\t.join(\"\")\n\t\t\t.trim();\n\n\t\tif (text === \"\") return { type: \"string\" };\n\n\t\t// If the inputSchema declares a specific primitive type for this value,\n\t\t// respect the schema contract instead of auto-detecting. For example,\n\t\t// \"123\" with inputSchema `{ type: \"string\" }` should stay \"string\".\n\t\tconst expectedType = ctx.expectedOutputType?.type;\n\t\tif (\n\t\t\ttypeof expectedType === \"string\" &&\n\t\t\t(expectedType === \"string\" ||\n\t\t\t\texpectedType === \"number\" ||\n\t\t\t\texpectedType === \"integer\" ||\n\t\t\t\texpectedType === \"boolean\" ||\n\t\t\t\texpectedType === \"null\")\n\t\t) {\n\t\t\treturn { type: expectedType };\n\t\t}\n\n\t\tconst literalType = detectLiteralType(text);\n\t\tif (literalType) return { type: literalType };\n\t}\n\n\t// ── Case 4: multiple blocks only (no significant text between them) ────\n\t// When the effective body consists entirely of BlockStatements, collect\n\t// each block's inferred type and combine them via oneOf. This handles\n\t// templates like:\n\t// {{#if showName}}{{name}}{{/if}}\n\t// {{#if showAge}}{{age}}{{/if}}\n\t// where the output could be string OR number depending on which branch\n\t// is active.\n\tconst allBlocks = effective.every((s) => s.type === \"BlockStatement\");\n\tif (allBlocks) {\n\t\tconst types: JSONSchema7[] = [];\n\t\tfor (const stmt of effective) {\n\t\t\tconst t = inferBlockType(stmt as hbs.AST.BlockStatement, ctx);\n\t\t\tif (t) types.push(t);\n\t\t}\n\t\tif (types.length === 1) return types[0] as JSONSchema7;\n\t\tif (types.length > 1) return simplifySchema({ oneOf: types });\n\t\treturn { type: \"string\" };\n\t}\n\n\t// ── Case 5: mixed template (text + expressions, blocks…) ───────────────\n\t// Traverse all statements for validation (side-effects: diagnostics).\n\t// The result is always string (concatenation).\n\tfor (const stmt of program.body) {\n\t\tprocessStatement(stmt, ctx);\n\t}\n\treturn { type: \"string\" };\n}\n\n/**\n * Infers the output type of a BlockStatement and validates its content.\n *\n * Supports built-in helpers (`if`, `unless`, `each`, `with`) and custom\n * helpers registered via `Typebars.registerHelper()`.\n *\n * Uses the **save/restore** pattern for context: instead of creating a new\n * object `{ ...ctx, current: X }` on each recursion, we save `ctx.current`,\n * mutate it, process the body, then restore. This reduces GC pressure for\n * deeply nested templates.\n */\nfunction inferBlockType(\n\tstmt: hbs.AST.BlockStatement,\n\tctx: AnalysisContext,\n): JSONSchema7 {\n\tconst helperName = getBlockHelperName(stmt);\n\n\tswitch (helperName) {\n\t\t// ── if / unless ──────────────────────────────────────────────────────\n\t\t// Validate the condition argument, then infer types from both branches.\n\t\tcase \"if\":\n\t\tcase \"unless\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (arg) {\n\t\t\t\tresolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\t} else {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(helperName),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Infer the type of the \"then\" branch\n\t\t\tconst thenType = inferProgramType(stmt.program, ctx);\n\n\t\t\tif (stmt.inverse) {\n\t\t\t\tconst elseType = inferProgramType(stmt.inverse, ctx);\n\t\t\t\t// If both branches have the same type → single type\n\t\t\t\tif (deepEqual(thenType, elseType)) return thenType;\n\t\t\t\t// Otherwise → union of both types\n\t\t\t\treturn simplifySchema({ oneOf: [thenType, elseType] });\n\t\t\t}\n\n\t\t\t// No else branch → the result is the type of the then branch\n\t\t\t// (conceptually optional, but Handlebars returns \"\" for falsy)\n\t\t\treturn thenType;\n\t\t}\n\n\t\t// ── each ─────────────────────────────────────────────────────────────\n\t\t// Resolve the collection schema, then validate the body with the item\n\t\t// schema as the new context.\n\t\tcase \"each\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"each\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"each\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\tconst collectionSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\t\t\tif (!collectionSchema) {\n\t\t\t\t// The path could not be resolved — diagnostic already emitted.\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Resolve the schema of the array elements\n\t\t\tconst itemSchema = resolveArrayItems(collectionSchema, ctx.root);\n\t\t\tif (!itemSchema) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateTypeMismatchMessage(\n\t\t\t\t\t\t\"each\",\n\t\t\t\t\t\t\"an array\",\n\t\t\t\t\t\tschemaTypeLabel(collectionSchema),\n\t\t\t\t\t),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName: \"each\",\n\t\t\t\t\t\texpected: \"array\",\n\t\t\t\t\t\tactual: schemaTypeLabel(collectionSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context (best-effort)\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Validate the body with the item schema as the new context\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = itemSchema;\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch ({{else}}) keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\t// An each concatenates renders → always string\n\t\t\treturn { type: \"string\" };\n\t\t}\n\n\t\t// ── with ─────────────────────────────────────────────────────────────\n\t\t// Resolve the inner schema, then validate the body with it as the\n\t\t// new context.\n\t\tcase \"with\": {\n\t\t\tconst arg = getBlockArgument(stmt);\n\t\t\tif (!arg) {\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\tcreateMissingArgumentMessage(\"with\"),\n\t\t\t\t\tstmt,\n\t\t\t\t\t{ helperName: \"with\" },\n\t\t\t\t);\n\t\t\t\t// Validate the body with an empty context\n\t\t\t\tconst saved = ctx.current;\n\t\t\t\tctx.current = {};\n\t\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\t\tctx.current = saved;\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tconst innerSchema = resolveExpressionWithDiagnostics(arg, ctx, stmt);\n\n\t\t\tconst saved = ctx.current;\n\t\t\tctx.current = innerSchema ?? {};\n\t\t\tconst result = inferProgramType(stmt.program, ctx);\n\t\t\tctx.current = saved;\n\n\t\t\t// The inverse branch keeps the parent context\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Custom or unknown helper ─────────────────────────────────────────\n\t\tdefault: {\n\t\t\tconst helper = ctx.helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\t// Registered custom helper — validate parameters\n\t\t\t\tfor (const param of stmt.params) {\n\t\t\t\t\tresolveExpressionWithDiagnostics(\n\t\t\t\t\t\tparam as hbs.AST.Expression,\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tstmt,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\t// Validate the body with the current context\n\t\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\t\treturn helper.returnType ?? { type: \"string\" };\n\t\t\t}\n\n\t\t\t// Unknown helper — warning\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\t\"warning\",\n\t\t\t\tcreateUnknownHelperMessage(helperName),\n\t\t\t\tstmt,\n\t\t\t\t{ helperName },\n\t\t\t);\n\t\t\t// Still validate the body with the current context (best-effort)\n\t\t\tinferProgramType(stmt.program, ctx);\n\t\t\tif (stmt.inverse) inferProgramType(stmt.inverse, ctx);\n\t\t\treturn { type: \"string\" };\n\t\t}\n\t}\n}\n\n// ─── Expression Resolution ───────────────────────────────────────────────────\n\n/**\n * Resolves an AST expression to a sub-schema, emitting a diagnostic\n * if the path cannot be resolved.\n *\n * Handles the `{{key:N}}` syntax:\n * - If the expression has an identifier N → resolution in `identifierSchemas[N]`\n * - If identifier N has no associated schema → error\n * - If no identifier → resolution in `ctx.current` (standard behavior)\n *\n * @returns The resolved sub-schema, or `undefined` if the path is invalid.\n */\nfunction resolveExpressionWithDiagnostics(\n\texpr: hbs.AST.Expression,\n\tctx: AnalysisContext,\n\t/** Parent AST node (for diagnostic location) */\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\t// Handle `this` / `.` → return the current context\n\tif (isThisExpression(expr)) {\n\t\treturn ctx.current;\n\t}\n\n\t// ── SubExpression (nested helper call, e.g. `(lt account.balance 500)`) ──\n\tif (expr.type === \"SubExpression\") {\n\t\treturn resolveSubExpression(expr as hbs.AST.SubExpression, ctx, parentNode);\n\t}\n\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\t// Expression that is not a PathExpression (e.g. literal)\n\t\tif (expr.type === \"StringLiteral\") return { type: \"string\" };\n\t\tif (expr.type === \"NumberLiteral\") return { type: \"number\" };\n\t\tif (expr.type === \"BooleanLiteral\") return { type: \"boolean\" };\n\t\tif (expr.type === \"NullLiteral\") return { type: \"null\" };\n\t\tif (expr.type === \"UndefinedLiteral\") return {};\n\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNANALYZABLE\",\n\t\t\t\"warning\",\n\t\t\tcreateUnanalyzableMessage(expr.type),\n\t\t\tparentNode ?? expr,\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// ── Identifier extraction ──────────────────────────────────────────────\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\tif (identifier !== null) {\n\t\t// The expression uses the {{key:N}} syntax — resolve from\n\t\t// the schema of identifier N.\n\t\treturn resolveWithIdentifier(\n\t\t\tcleanSegments,\n\t\t\tidentifier,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\t}\n\n\t// ── Standard resolution (no identifier) ────────────────────────────────\n\tconst resolved = resolveSchemaPath(ctx.current, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst fullPath = cleanSegments.join(\".\");\n\t\tconst availableProperties = getSchemaPropertyNames(ctx.current);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_PROPERTY\",\n\t\t\t\"error\",\n\t\t\tcreatePropertyNotFoundMessage(fullPath, availableProperties),\n\t\t\tparentNode ?? expr,\n\t\t\t{ path: fullPath, availableProperties },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n/**\n * Resolves an expression with identifier `{{key:N}}` by looking up the\n * schema associated with identifier N.\n *\n * Emits an error diagnostic if:\n * - No `identifierSchemas` were provided\n * - Identifier N has no associated schema\n * - The property does not exist in the identifier's schema\n */\nfunction resolveWithIdentifier(\n\tcleanSegments: string[],\n\tidentifier: number,\n\tctx: AnalysisContext,\n\tnode: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst fullPath = cleanSegments.join(\".\");\n\n\t// No identifierSchemas provided at all\n\tif (!ctx.identifierSchemas) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"MISSING_IDENTIFIER_SCHEMAS\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" uses an identifier but no identifier schemas were provided`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// The identifier does not exist in the provided schemas\n\tconst idSchema = ctx.identifierSchemas[identifier];\n\tif (!idSchema) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_IDENTIFIER\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}:${identifier}\" references identifier ${identifier} but no schema exists for this identifier`,\n\t\t\tnode,\n\t\t\t{ path: `${fullPath}:${identifier}`, identifier },\n\t\t);\n\t\treturn undefined;\n\t}\n\n\t// Resolve the path within the identifier's schema\n\tconst resolved = resolveSchemaPath(idSchema, cleanSegments);\n\tif (resolved === undefined) {\n\t\tconst availableProperties = getSchemaPropertyNames(idSchema);\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"IDENTIFIER_PROPERTY_NOT_FOUND\",\n\t\t\t\"error\",\n\t\t\t`Property \"${fullPath}\" does not exist in the schema for identifier ${identifier}`,\n\t\t\tnode,\n\t\t\t{\n\t\t\t\tpath: fullPath,\n\t\t\t\tidentifier,\n\t\t\t\tavailableProperties,\n\t\t\t},\n\t\t);\n\t\treturn undefined;\n\t}\n\n\treturn resolved;\n}\n\n// ─── Utilities ───────────────────────────────────────────────────────────────\n\n/**\n * Extracts the first argument of a BlockStatement.\n *\n * In the Handlebars AST, for `{{#if active}}`:\n * - `stmt.path` → PathExpression(\"if\") ← the helper name\n * - `stmt.params[0]` → PathExpression(\"active\") ← the actual argument\n *\n * @returns The argument expression, or `undefined` if the block has no argument.\n */\n// ─── SubExpression Resolution ────────────────────────────────────────────────\n\n/**\n * Resolves a SubExpression (nested helper call) such as `(lt account.balance 500)`.\n *\n * This mirrors the helper-call logic in `processMustache` but applies to\n * expressions used as arguments (e.g. inside `{{#if (lt a b)}}`).\n *\n * Steps:\n * 1. Extract the helper name from the SubExpression's path.\n * 2. Look up the helper in `ctx.helpers`.\n * 3. Validate argument count and types.\n * 4. Return the helper's declared `returnType` (defaults to `{ type: \"string\" }`).\n */\nfunction resolveSubExpression(\n\texpr: hbs.AST.SubExpression,\n\tctx: AnalysisContext,\n\tparentNode?: hbs.AST.Node,\n): JSONSchema7 | undefined {\n\tconst helperName = getExpressionName(expr.path);\n\n\tconst helper = ctx.helpers?.get(helperName);\n\tif (!helper) {\n\t\taddDiagnostic(\n\t\t\tctx,\n\t\t\t\"UNKNOWN_HELPER\",\n\t\t\t\"warning\",\n\t\t\t`Unknown sub-expression helper \"${helperName}\" — cannot analyze statically`,\n\t\t\tparentNode ?? expr,\n\t\t\t{ helperName },\n\t\t);\n\t\treturn { type: \"string\" };\n\t}\n\n\tconst helperParams = helper.params;\n\n\t// ── Check the number of required parameters ──────────────────────\n\tif (helperParams) {\n\t\tconst requiredCount = helperParams.filter((p) => !p.optional).length;\n\t\tif (expr.params.length < requiredCount) {\n\t\t\taddDiagnostic(\n\t\t\t\tctx,\n\t\t\t\t\"MISSING_ARGUMENT\",\n\t\t\t\t\"error\",\n\t\t\t\t`Helper \"${helperName}\" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,\n\t\t\t\tparentNode ?? expr,\n\t\t\t\t{\n\t\t\t\t\thelperName,\n\t\t\t\t\texpected: `${requiredCount} argument(s)`,\n\t\t\t\t\tactual: `${expr.params.length} argument(s)`,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\t}\n\n\t// ── Validate each parameter (existence + type) ───────────────────\n\tfor (let i = 0; i < expr.params.length; i++) {\n\t\tconst resolvedSchema = resolveExpressionWithDiagnostics(\n\t\t\texpr.params[i] as hbs.AST.Expression,\n\t\t\tctx,\n\t\t\tparentNode ?? expr,\n\t\t);\n\n\t\tconst helperParam = helperParams?.[i];\n\t\tif (resolvedSchema && helperParam?.type) {\n\t\t\tconst expectedType = helperParam.type;\n\t\t\tif (!isParamTypeCompatible(resolvedSchema, expectedType)) {\n\t\t\t\tconst paramName = helperParam.name;\n\t\t\t\taddDiagnostic(\n\t\t\t\t\tctx,\n\t\t\t\t\t\"TYPE_MISMATCH\",\n\t\t\t\t\t\"error\",\n\t\t\t\t\t`Helper \"${helperName}\" parameter \"${paramName}\" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,\n\t\t\t\t\tparentNode ?? expr,\n\t\t\t\t\t{\n\t\t\t\t\t\thelperName,\n\t\t\t\t\t\texpected: schemaTypeLabel(expectedType),\n\t\t\t\t\t\tactual: schemaTypeLabel(resolvedSchema),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn helper.returnType ?? { type: \"string\" };\n}\n\nfunction getBlockArgument(\n\tstmt: hbs.AST.BlockStatement,\n): hbs.AST.Expression | undefined {\n\treturn stmt.params[0] as hbs.AST.Expression | undefined;\n}\n\n/**\n * Retrieves the helper name from a BlockStatement (e.g. \"if\", \"each\", \"with\").\n */\nfunction getBlockHelperName(stmt: hbs.AST.BlockStatement): string {\n\tif (stmt.path.type === \"PathExpression\") {\n\t\treturn (stmt.path as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Retrieves the name of an expression (first segment of the PathExpression).\n * Used to identify inline helpers.\n */\nfunction getExpressionName(expr: hbs.AST.Expression): string {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).original;\n\t}\n\treturn \"\";\n}\n\n/**\n * Adds an enriched diagnostic to the analysis context.\n *\n * Each diagnostic includes:\n * - A machine-readable `code` for the frontend\n * - A human-readable `message` describing the problem\n * - A `source` snippet from the template (if the position is available)\n * - Structured `details` for debugging\n */\nfunction addDiagnostic(\n\tctx: AnalysisContext,\n\tcode: DiagnosticCode,\n\tseverity: \"error\" | \"warning\",\n\tmessage: string,\n\tnode?: hbs.AST.Node,\n\tdetails?: DiagnosticDetails,\n): void {\n\tconst diagnostic: TemplateDiagnostic = { severity, code, message };\n\n\t// Extract the position and source snippet if available\n\tif (node && \"loc\" in node && node.loc) {\n\t\tdiagnostic.loc = {\n\t\t\tstart: { line: node.loc.start.line, column: node.loc.start.column },\n\t\t\tend: { line: node.loc.end.line, column: node.loc.end.column },\n\t\t};\n\t\t// Extract the template fragment around the error\n\t\tdiagnostic.source = extractSourceSnippet(ctx.template, diagnostic.loc);\n\t}\n\n\tif (details) {\n\t\tdiagnostic.details = details;\n\t}\n\n\tctx.diagnostics.push(diagnostic);\n}\n\n/**\n * Returns a human-readable label for a schema's type (for error messages).\n */\nfunction schemaTypeLabel(schema: JSONSchema7): string {\n\tif (schema.type) {\n\t\treturn Array.isArray(schema.type) ? schema.type.join(\" | \") : schema.type;\n\t}\n\tif (schema.oneOf) return \"oneOf(...)\";\n\tif (schema.anyOf) return \"anyOf(...)\";\n\tif (schema.allOf) return \"allOf(...)\";\n\tif (schema.enum) return \"enum\";\n\treturn \"unknown\";\n}\n\n// ─── Export for Internal Use ─────────────────────────────────────────────────\n// `inferBlockType` is exported to allow targeted unit tests\n// on block type inference.\nexport { inferBlockType };\n"],"names":["createMissingArgumentMessage","createPropertyNotFoundMessage","createTypeMismatchMessage","createUnanalyzableMessage","createUnknownHelperMessage","detectLiteralType","extractExpressionIdentifier","extractPathSegments","getEffectiveBody","getEffectivelySingleBlock","getEffectivelySingleExpression","isThisExpression","parse","assertNoConditionalSchema","resolveArrayItems","resolveSchemaPath","simplifySchema","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","aggregateArrayAnalysis","aggregateObjectAnalysis","deepEqual","extractSourceSnippet","getSchemaPropertyNames","analyze","template","inputSchema","identifierSchemas","expectedOutputType","analyzeArrayTemplate","analyzeObjectTemplate","valid","diagnostics","outputSchema","ast","analyzeFromAst","length","index","schemaForProperties","Object","keys","key","propertySchema","options","id","idSchema","entries","ctx","root","current","helpers","inferProgramType","hasErrors","some","d","severity","processStatement","stmt","type","undefined","processMustache","inferBlockType","addDiagnostic","path","params","hash","helperName","getExpressionName","helper","get","helperParams","requiredCount","filter","p","optional","expected","actual","i","resolvedSchema","resolveExpressionWithDiagnostics","helperParam","expectedType","isParamTypeCompatible","paramName","name","schemaTypeLabel","returnType","resolved","expectedTypes","Array","isArray","resolvedTypes","rt","et","program","effective","singleExpr","singleBlock","allContent","every","s","text","map","value","join","trim","literalType","allBlocks","types","t","push","oneOf","body","getBlockHelperName","arg","getBlockArgument","thenType","inverse","elseType","saved","collectionSchema","itemSchema","result","innerSchema","param","expr","parentNode","resolveSubExpression","segments","cleanSegments","identifier","resolveWithIdentifier","fullPath","availableProperties","node","original","code","message","details","diagnostic","loc","start","line","column","end","source","schema","anyOf","allOf","enum"],"mappings":"AACA,OACCA,4BAA4B,CAC5BC,6BAA6B,CAC7BC,yBAAyB,CACzBC,yBAAyB,CACzBC,0BAA0B,KACpB,UAAW,AAClB,QACCC,iBAAiB,CACjBC,2BAA2B,CAC3BC,mBAAmB,CACnBC,gBAAgB,CAChBC,yBAAyB,CACzBC,8BAA8B,CAC9BC,gBAAgB,CAChBC,KAAK,KACC,UAAW,AAClB,QACCC,yBAAyB,CACzBC,iBAAiB,CACjBC,iBAAiB,CACjBC,cAAc,KACR,mBAAoB,AAW3B,QACCC,oBAAoB,CACpBC,YAAY,CACZC,cAAc,CACdC,aAAa,KACP,YAAa,AACpB,QACCC,sBAAsB,CACtBC,uBAAuB,CACvBC,SAAS,CACTC,oBAAoB,CACpBC,sBAAsB,KAChB,SAAU,AAmEjB,QAAO,SAASC,QACfC,QAAuB,CACvBC,WAAwB,CACxBC,iBAA+C,CAC/CC,kBAAgC,EAEhC,GAAIZ,aAAaS,UAAW,CAC3B,OAAOI,qBAAqBJ,SAAUC,YAAaC,kBACpD,CACA,GAAIT,cAAcO,UAAW,CAC5B,OAAOK,sBACNL,SACAC,YACAC,kBACAC,mBAEF,CACA,GAAIX,eAAeQ,UAAW,CAC7B,MAAO,CACNM,MAAO,KACPC,YAAa,EAAE,CACfC,aAAclB,qBAAqBU,SACpC,CACD,CACA,MAAMS,IAAMxB,MAAMe,UAKlB,OAAOU,eAAeD,IAAKT,SAAUC,YAAa,CACjDC,kBACAC,mBAAoBA,oBAAsBF,WAC3C,EACD,CAOA,SAASG,qBACRJ,QAA4B,CAC5BC,WAAwB,CACxBC,iBAA+C,EAE/C,OAAOR,uBAAuBM,SAASW,MAAM,CAAE,AAACC,OAC/Cb,QAAQC,QAAQ,CAACY,MAAM,CAAmBX,YAAaC,mBAEzD,CAOA,SAASG,sBACRL,QAA6B,CAC7BC,WAAwB,CACxBC,iBAA+C,CAC/CC,kBAAgC,EAOhC,MAAMU,oBAAsBV,oBAAsBF,YAClD,OAAON,wBAAwBmB,OAAOC,IAAI,CAACf,UAAW,AAACgB,MACtD,MAAMC,eAAiB7B,kBAAkByB,oBAAqB,CAACG,IAAI,EACnE,OAAOjB,QACNC,QAAQ,CAACgB,IAAI,CACbf,YACAC,kBACAe,eAEF,EACD,CAcA,OAAO,SAASP,eACfD,GAAoB,CACpBT,QAAgB,CAChBC,WAAwB,CACxBiB,OASC,EAMDhC,0BAA0Be,aAE1B,GAAIiB,SAAShB,kBAAmB,CAC/B,IAAK,KAAM,CAACiB,GAAIC,SAAS,GAAIN,OAAOO,OAAO,CAACH,QAAQhB,iBAAiB,EAAG,CACvEhB,0BAA0BkC,SAAU,CAAC,mBAAmB,EAAED,GAAG,CAAC,CAC/D,CACD,CAEA,MAAMG,IAAuB,CAC5BC,KAAMtB,YACNuB,QAASvB,YACTM,YAAa,EAAE,CACfP,SACAE,kBAAmBgB,SAAShB,kBAC5BuB,QAASP,SAASO,QAClBtB,mBAAoBe,SAASf,kBAC9B,EAGA,MAAMK,aAAekB,iBAAiBjB,IAAKa,KAE3C,MAAMK,UAAYL,IAAIf,WAAW,CAACqB,IAAI,CAAC,AAACC,GAAMA,EAAEC,QAAQ,GAAK,SAE7D,MAAO,CACNxB,MAAO,CAACqB,UACRpB,YAAae,IAAIf,WAAW,CAC5BC,aAAcnB,eAAemB,aAC9B,CACD,CAsBA,SAASuB,iBACRC,IAAuB,CACvBV,GAAoB,EAEpB,OAAQU,KAAKC,IAAI,EAChB,IAAK,mBACL,IAAK,mBAEJ,OAAOC,SAER,KAAK,oBACJ,OAAOC,gBAAgBH,KAAmCV,IAE3D,KAAK,iBACJ,OAAOc,eAAeJ,KAAgCV,IAEvD,SAGCe,cACCf,IACA,eACA,UACA,CAAC,4BAA4B,EAAEU,KAAKC,IAAI,CAAC,CAAC,CAAC,CAC3CD,MAED,OAAOE,SACT,CACD,CAWA,SAASC,gBACRH,IAA+B,CAC/BV,GAAoB,EAIpB,GAAIU,KAAKM,IAAI,CAACL,IAAI,GAAK,gBAAiB,CACvCI,cACCf,IACA,eACA,UACA,gDACAU,MAED,MAAO,CAAC,CACT,CAKA,GAAIA,KAAKO,MAAM,CAAC5B,MAAM,CAAG,GAAKqB,KAAKQ,IAAI,CAAE,CACxC,MAAMC,WAAaC,kBAAkBV,KAAKM,IAAI,EAG9C,MAAMK,OAASrB,IAAIG,OAAO,EAAEmB,IAAIH,YAChC,GAAIE,OAAQ,CACX,MAAME,aAAeF,OAAOJ,MAAM,CAGlC,GAAIM,aAAc,CACjB,MAAMC,cAAgBD,aAAaE,MAAM,CAAC,AAACC,GAAM,CAACA,EAAEC,QAAQ,EAAEtC,MAAM,CACpE,GAAIqB,KAAKO,MAAM,CAAC5B,MAAM,CAAGmC,cAAe,CACvCT,cACCf,IACA,mBACA,QACA,CAAC,QAAQ,EAAEmB,WAAW,mBAAmB,EAAEK,cAAc,sBAAsB,EAAEd,KAAKO,MAAM,CAAC5B,MAAM,CAAC,CAAC,CACrGqB,KACA,CACCS,WACAS,SAAU,CAAC,EAAEJ,cAAc,YAAY,CAAC,CACxCK,OAAQ,CAAC,EAAEnB,KAAKO,MAAM,CAAC5B,MAAM,CAAC,YAAY,CAAC,AAC5C,EAEF,CACD,CAGA,IAAK,IAAIyC,EAAI,EAAGA,EAAIpB,KAAKO,MAAM,CAAC5B,MAAM,CAAEyC,IAAK,CAC5C,MAAMC,eAAiBC,iCACtBtB,KAAKO,MAAM,CAACa,EAAE,CACd9B,IACAU,MAKD,MAAMuB,YAAcV,cAAc,CAACO,EAAE,CACrC,GAAIC,gBAAkBE,aAAatB,KAAM,CACxC,MAAMuB,aAAeD,YAAYtB,IAAI,CACrC,GAAI,CAACwB,sBAAsBJ,eAAgBG,cAAe,CACzD,MAAME,UAAYH,YAAYI,IAAI,CAClCtB,cACCf,IACA,gBACA,QACA,CAAC,QAAQ,EAAEmB,WAAW,aAAa,EAAEiB,UAAU,UAAU,EAAEE,gBAAgBJ,cAAc,UAAU,EAAEI,gBAAgBP,gBAAgB,CAAC,CACtIrB,KACA,CACCS,WACAS,SAAUU,gBAAgBJ,cAC1BL,OAAQS,gBAAgBP,eACzB,EAEF,CACD,CACD,CAEA,OAAOV,OAAOkB,UAAU,EAAI,CAAE5B,KAAM,QAAS,CAC9C,CAGAI,cACCf,IACA,iBACA,UACA,CAAC,uBAAuB,EAAEmB,WAAW,6BAA6B,CAAC,CACnET,KACA,CAAES,UAAW,GAEd,MAAO,CAAER,KAAM,QAAS,CACzB,CAGA,OAAOqB,iCAAiCtB,KAAKM,IAAI,CAAEhB,IAAKU,OAAS,CAAC,CACnE,CAYA,SAASyB,sBACRK,QAAqB,CACrBZ,QAAqB,EAGrB,GAAI,CAACA,SAASjB,IAAI,EAAI,CAAC6B,SAAS7B,IAAI,CAAE,OAAO,KAE7C,MAAM8B,cAAgBC,MAAMC,OAAO,CAACf,SAASjB,IAAI,EAC9CiB,SAASjB,IAAI,CACb,CAACiB,SAASjB,IAAI,CAAC,CAClB,MAAMiC,cAAgBF,MAAMC,OAAO,CAACH,SAAS7B,IAAI,EAC9C6B,SAAS7B,IAAI,CACb,CAAC6B,SAAS7B,IAAI,CAAC,CAGlB,OAAOiC,cAActC,IAAI,CAAC,AAACuC,IAC1BJ,cAAcnC,IAAI,CACjB,AAACwC,IACAD,KAAOC,IAENA,KAAO,UAAYD,KAAO,WAC1BC,KAAO,WAAaD,KAAO,UAGhC,CAeA,SAASzC,iBACR2C,OAAwB,CACxB/C,GAAoB,EAEpB,MAAMgD,UAAYzF,iBAAiBwF,SAGnC,GAAIC,UAAU3D,MAAM,GAAK,EAAG,CAC3B,MAAO,CAAEsB,KAAM,QAAS,CACzB,CAGA,MAAMsC,WAAaxF,+BAA+BsF,SAClD,GAAIE,WAAY,CACf,OAAOpC,gBAAgBoC,WAAYjD,IACpC,CAGA,MAAMkD,YAAc1F,0BAA0BuF,SAC9C,GAAIG,YAAa,CAChB,OAAOpC,eAAeoC,YAAalD,IACpC,CAKA,MAAMmD,WAAaH,UAAUI,KAAK,CAAC,AAACC,GAAMA,EAAE1C,IAAI,GAAK,oBACrD,GAAIwC,WAAY,CACf,MAAMG,KAAON,UACXO,GAAG,CAAC,AAACF,GAAM,AAACA,EAA+BG,KAAK,EAChDC,IAAI,CAAC,IACLC,IAAI,GAEN,GAAIJ,OAAS,GAAI,MAAO,CAAE3C,KAAM,QAAS,EAKzC,MAAMuB,aAAelC,IAAInB,kBAAkB,EAAE8B,KAC7C,GACC,OAAOuB,eAAiB,UACvBA,CAAAA,eAAiB,UACjBA,eAAiB,UACjBA,eAAiB,WACjBA,eAAiB,WACjBA,eAAiB,MAAK,EACtB,CACD,MAAO,CAAEvB,KAAMuB,YAAa,CAC7B,CAEA,MAAMyB,YAAcvG,kBAAkBkG,MACtC,GAAIK,YAAa,MAAO,CAAEhD,KAAMgD,WAAY,CAC7C,CAUA,MAAMC,UAAYZ,UAAUI,KAAK,CAAC,AAACC,GAAMA,EAAE1C,IAAI,GAAK,kBACpD,GAAIiD,UAAW,CACd,MAAMC,MAAuB,EAAE,CAC/B,IAAK,MAAMnD,QAAQsC,UAAW,CAC7B,MAAMc,EAAIhD,eAAeJ,KAAgCV,KACzD,GAAI8D,EAAGD,MAAME,IAAI,CAACD,EACnB,CACA,GAAID,MAAMxE,MAAM,GAAK,EAAG,OAAOwE,KAAK,CAAC,EAAE,CACvC,GAAIA,MAAMxE,MAAM,CAAG,EAAG,OAAOtB,eAAe,CAAEiG,MAAOH,KAAM,GAC3D,MAAO,CAAElD,KAAM,QAAS,CACzB,CAKA,IAAK,MAAMD,QAAQqC,QAAQkB,IAAI,CAAE,CAChCxD,iBAAiBC,KAAMV,IACxB,CACA,MAAO,CAAEW,KAAM,QAAS,CACzB,CAaA,SAASG,eACRJ,IAA4B,CAC5BV,GAAoB,EAEpB,MAAMmB,WAAa+C,mBAAmBxD,MAEtC,OAAQS,YAGP,IAAK,KACL,IAAK,SAAU,CACd,MAAMgD,IAAMC,iBAAiB1D,MAC7B,GAAIyD,IAAK,CACRnC,iCAAiCmC,IAAKnE,IAAKU,KAC5C,KAAO,CACNK,cACCf,IACA,mBACA,QACAjD,6BAA6BoE,YAC7BT,KACA,CAAES,UAAW,EAEf,CAGA,MAAMkD,SAAWjE,iBAAiBM,KAAKqC,OAAO,CAAE/C,KAEhD,GAAIU,KAAK4D,OAAO,CAAE,CACjB,MAAMC,SAAWnE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KAEhD,GAAI1B,UAAU+F,SAAUE,UAAW,OAAOF,SAE1C,OAAOtG,eAAe,CAAEiG,MAAO,CAACK,SAAUE,SAAS,AAAC,EACrD,CAIA,OAAOF,QACR,CAKA,IAAK,OAAQ,CACZ,MAAMF,IAAMC,iBAAiB1D,MAC7B,GAAI,CAACyD,IAAK,CACTpD,cACCf,IACA,mBACA,QACAjD,6BAA6B,QAC7B2D,KACA,CAAES,WAAY,MAAO,GAGtB,MAAMqD,MAAQxE,IAAIE,OAAO,AACzBF,CAAAA,IAAIE,OAAO,CAAG,CAAC,EACfE,iBAAiBM,KAAKqC,OAAO,CAAE/C,IAC/BA,CAAAA,IAAIE,OAAO,CAAGsE,MACd,GAAI9D,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KACjD,MAAO,CAAEW,KAAM,QAAS,CACzB,CAEA,MAAM8D,iBAAmBzC,iCAAiCmC,IAAKnE,IAAKU,MACpE,GAAI,CAAC+D,iBAAkB,CAEtB,MAAMD,MAAQxE,IAAIE,OAAO,AACzBF,CAAAA,IAAIE,OAAO,CAAG,CAAC,EACfE,iBAAiBM,KAAKqC,OAAO,CAAE/C,IAC/BA,CAAAA,IAAIE,OAAO,CAAGsE,MACd,GAAI9D,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KACjD,MAAO,CAAEW,KAAM,QAAS,CACzB,CAGA,MAAM+D,WAAa7G,kBAAkB4G,iBAAkBzE,IAAIC,IAAI,EAC/D,GAAI,CAACyE,WAAY,CAChB3D,cACCf,IACA,gBACA,QACA/C,0BACC,OACA,WACAqF,gBAAgBmC,mBAEjB/D,KACA,CACCS,WAAY,OACZS,SAAU,QACVC,OAAQS,gBAAgBmC,iBACzB,GAGD,MAAMD,MAAQxE,IAAIE,OAAO,AACzBF,CAAAA,IAAIE,OAAO,CAAG,CAAC,EACfE,iBAAiBM,KAAKqC,OAAO,CAAE/C,IAC/BA,CAAAA,IAAIE,OAAO,CAAGsE,MACd,GAAI9D,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KACjD,MAAO,CAAEW,KAAM,QAAS,CACzB,CAGA,MAAM6D,MAAQxE,IAAIE,OAAO,AACzBF,CAAAA,IAAIE,OAAO,CAAGwE,WACdtE,iBAAiBM,KAAKqC,OAAO,CAAE/C,IAC/BA,CAAAA,IAAIE,OAAO,CAAGsE,MAGd,GAAI9D,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KAGjD,MAAO,CAAEW,KAAM,QAAS,CACzB,CAKA,IAAK,OAAQ,CACZ,MAAMwD,IAAMC,iBAAiB1D,MAC7B,GAAI,CAACyD,IAAK,CACTpD,cACCf,IACA,mBACA,QACAjD,6BAA6B,QAC7B2D,KACA,CAAES,WAAY,MAAO,GAGtB,MAAMqD,MAAQxE,IAAIE,OAAO,AACzBF,CAAAA,IAAIE,OAAO,CAAG,CAAC,EACf,MAAMyE,OAASvE,iBAAiBM,KAAKqC,OAAO,CAAE/C,IAC9CA,CAAAA,IAAIE,OAAO,CAAGsE,MACd,GAAI9D,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KACjD,OAAO2E,MACR,CAEA,MAAMC,YAAc5C,iCAAiCmC,IAAKnE,IAAKU,MAE/D,MAAM8D,MAAQxE,IAAIE,OAAO,AACzBF,CAAAA,IAAIE,OAAO,CAAG0E,aAAe,CAAC,EAC9B,MAAMD,OAASvE,iBAAiBM,KAAKqC,OAAO,CAAE/C,IAC9CA,CAAAA,IAAIE,OAAO,CAAGsE,MAGd,GAAI9D,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KAEjD,OAAO2E,MACR,CAGA,QAAS,CACR,MAAMtD,OAASrB,IAAIG,OAAO,EAAEmB,IAAIH,YAChC,GAAIE,OAAQ,CAEX,IAAK,MAAMwD,SAASnE,KAAKO,MAAM,CAAE,CAChCe,iCACC6C,MACA7E,IACAU,KAEF,CAEAN,iBAAiBM,KAAKqC,OAAO,CAAE/C,KAC/B,GAAIU,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KACjD,OAAOqB,OAAOkB,UAAU,EAAI,CAAE5B,KAAM,QAAS,CAC9C,CAGAI,cACCf,IACA,iBACA,UACA7C,2BAA2BgE,YAC3BT,KACA,CAAES,UAAW,GAGdf,iBAAiBM,KAAKqC,OAAO,CAAE/C,KAC/B,GAAIU,KAAK4D,OAAO,CAAElE,iBAAiBM,KAAK4D,OAAO,CAAEtE,KACjD,MAAO,CAAEW,KAAM,QAAS,CACzB,CACD,CACD,CAeA,SAASqB,iCACR8C,IAAwB,CACxB9E,GAAoB,CAEpB+E,UAAyB,EAGzB,GAAIrH,iBAAiBoH,MAAO,CAC3B,OAAO9E,IAAIE,OAAO,AACnB,CAGA,GAAI4E,KAAKnE,IAAI,GAAK,gBAAiB,CAClC,OAAOqE,qBAAqBF,KAA+B9E,IAAK+E,WACjE,CAEA,MAAME,SAAW3H,oBAAoBwH,MACrC,GAAIG,SAAS5F,MAAM,GAAK,EAAG,CAE1B,GAAIyF,KAAKnE,IAAI,GAAK,gBAAiB,MAAO,CAAEA,KAAM,QAAS,EAC3D,GAAImE,KAAKnE,IAAI,GAAK,gBAAiB,MAAO,CAAEA,KAAM,QAAS,EAC3D,GAAImE,KAAKnE,IAAI,GAAK,iBAAkB,MAAO,CAAEA,KAAM,SAAU,EAC7D,GAAImE,KAAKnE,IAAI,GAAK,cAAe,MAAO,CAAEA,KAAM,MAAO,EACvD,GAAImE,KAAKnE,IAAI,GAAK,mBAAoB,MAAO,CAAC,EAE9CI,cACCf,IACA,eACA,UACA9C,0BAA0B4H,KAAKnE,IAAI,EACnCoE,YAAcD,MAEf,OAAOlE,SACR,CAGA,KAAM,CAAEsE,aAAa,CAAEC,UAAU,CAAE,CAAG9H,4BAA4B4H,UAElE,GAAIE,aAAe,KAAM,CAGxB,OAAOC,sBACNF,cACAC,WACAnF,IACA+E,YAAcD,KAEhB,CAGA,MAAMtC,SAAW1E,kBAAkBkC,IAAIE,OAAO,CAAEgF,eAChD,GAAI1C,WAAa5B,UAAW,CAC3B,MAAMyE,SAAWH,cAAczB,IAAI,CAAC,KACpC,MAAM6B,oBAAsB9G,uBAAuBwB,IAAIE,OAAO,EAC9Da,cACCf,IACA,mBACA,QACAhD,8BAA8BqI,SAAUC,qBACxCP,YAAcD,KACd,CAAE9D,KAAMqE,SAAUC,mBAAoB,GAEvC,OAAO1E,SACR,CAEA,OAAO4B,QACR,CAWA,SAAS4C,sBACRF,aAAuB,CACvBC,UAAkB,CAClBnF,GAAoB,CACpBuF,IAAkB,EAElB,MAAMF,SAAWH,cAAczB,IAAI,CAAC,KAGpC,GAAI,CAACzD,IAAIpB,iBAAiB,CAAE,CAC3BmC,cACCf,IACA,6BACA,QACA,CAAC,UAAU,EAAEqF,SAAS,CAAC,EAAEF,WAAW,4DAA4D,CAAC,CACjGI,KACA,CAAEvE,KAAM,CAAC,EAAEqE,SAAS,CAAC,EAAEF,WAAW,CAAC,CAAEA,UAAW,GAEjD,OAAOvE,SACR,CAGA,MAAMd,SAAWE,IAAIpB,iBAAiB,CAACuG,WAAW,CAClD,GAAI,CAACrF,SAAU,CACdiB,cACCf,IACA,qBACA,QACA,CAAC,UAAU,EAAEqF,SAAS,CAAC,EAAEF,WAAW,wBAAwB,EAAEA,WAAW,yCAAyC,CAAC,CACnHI,KACA,CAAEvE,KAAM,CAAC,EAAEqE,SAAS,CAAC,EAAEF,WAAW,CAAC,CAAEA,UAAW,GAEjD,OAAOvE,SACR,CAGA,MAAM4B,SAAW1E,kBAAkBgC,SAAUoF,eAC7C,GAAI1C,WAAa5B,UAAW,CAC3B,MAAM0E,oBAAsB9G,uBAAuBsB,UACnDiB,cACCf,IACA,gCACA,QACA,CAAC,UAAU,EAAEqF,SAAS,8CAA8C,EAAEF,WAAW,CAAC,CAClFI,KACA,CACCvE,KAAMqE,SACNF,WACAG,mBACD,GAED,OAAO1E,SACR,CAEA,OAAO4B,QACR,CA2BA,SAASwC,qBACRF,IAA2B,CAC3B9E,GAAoB,CACpB+E,UAAyB,EAEzB,MAAM5D,WAAaC,kBAAkB0D,KAAK9D,IAAI,EAE9C,MAAMK,OAASrB,IAAIG,OAAO,EAAEmB,IAAIH,YAChC,GAAI,CAACE,OAAQ,CACZN,cACCf,IACA,iBACA,UACA,CAAC,+BAA+B,EAAEmB,WAAW,6BAA6B,CAAC,CAC3E4D,YAAcD,KACd,CAAE3D,UAAW,GAEd,MAAO,CAAER,KAAM,QAAS,CACzB,CAEA,MAAMY,aAAeF,OAAOJ,MAAM,CAGlC,GAAIM,aAAc,CACjB,MAAMC,cAAgBD,aAAaE,MAAM,CAAC,AAACC,GAAM,CAACA,EAAEC,QAAQ,EAAEtC,MAAM,CACpE,GAAIyF,KAAK7D,MAAM,CAAC5B,MAAM,CAAGmC,cAAe,CACvCT,cACCf,IACA,mBACA,QACA,CAAC,QAAQ,EAAEmB,WAAW,mBAAmB,EAAEK,cAAc,sBAAsB,EAAEsD,KAAK7D,MAAM,CAAC5B,MAAM,CAAC,CAAC,CACrG0F,YAAcD,KACd,CACC3D,WACAS,SAAU,CAAC,EAAEJ,cAAc,YAAY,CAAC,CACxCK,OAAQ,CAAC,EAAEiD,KAAK7D,MAAM,CAAC5B,MAAM,CAAC,YAAY,CAAC,AAC5C,EAEF,CACD,CAGA,IAAK,IAAIyC,EAAI,EAAGA,EAAIgD,KAAK7D,MAAM,CAAC5B,MAAM,CAAEyC,IAAK,CAC5C,MAAMC,eAAiBC,iCACtB8C,KAAK7D,MAAM,CAACa,EAAE,CACd9B,IACA+E,YAAcD,MAGf,MAAM7C,YAAcV,cAAc,CAACO,EAAE,CACrC,GAAIC,gBAAkBE,aAAatB,KAAM,CACxC,MAAMuB,aAAeD,YAAYtB,IAAI,CACrC,GAAI,CAACwB,sBAAsBJ,eAAgBG,cAAe,CACzD,MAAME,UAAYH,YAAYI,IAAI,CAClCtB,cACCf,IACA,gBACA,QACA,CAAC,QAAQ,EAAEmB,WAAW,aAAa,EAAEiB,UAAU,UAAU,EAAEE,gBAAgBJ,cAAc,UAAU,EAAEI,gBAAgBP,gBAAgB,CAAC,CACtIgD,YAAcD,KACd,CACC3D,WACAS,SAAUU,gBAAgBJ,cAC1BL,OAAQS,gBAAgBP,eACzB,EAEF,CACD,CACD,CAEA,OAAOV,OAAOkB,UAAU,EAAI,CAAE5B,KAAM,QAAS,CAC9C,CAEA,SAASyD,iBACR1D,IAA4B,EAE5B,OAAOA,KAAKO,MAAM,CAAC,EAAE,AACtB,CAKA,SAASiD,mBAAmBxD,IAA4B,EACvD,GAAIA,KAAKM,IAAI,CAACL,IAAI,GAAK,iBAAkB,CACxC,OAAO,AAACD,KAAKM,IAAI,CAA4BwE,QAAQ,AACtD,CACA,MAAO,EACR,CAMA,SAASpE,kBAAkB0D,IAAwB,EAClD,GAAIA,KAAKnE,IAAI,GAAK,iBAAkB,CACnC,OAAO,AAACmE,KAAgCU,QAAQ,AACjD,CACA,MAAO,EACR,CAWA,SAASzE,cACRf,GAAoB,CACpByF,IAAoB,CACpBjF,QAA6B,CAC7BkF,OAAe,CACfH,IAAmB,CACnBI,OAA2B,EAE3B,MAAMC,WAAiC,CAAEpF,SAAUiF,KAAMC,OAAQ,EAGjE,GAAIH,MAAQ,QAASA,MAAQA,KAAKM,GAAG,CAAE,CACtCD,WAAWC,GAAG,CAAG,CAChBC,MAAO,CAAEC,KAAMR,KAAKM,GAAG,CAACC,KAAK,CAACC,IAAI,CAAEC,OAAQT,KAAKM,GAAG,CAACC,KAAK,CAACE,MAAM,AAAC,EAClEC,IAAK,CAAEF,KAAMR,KAAKM,GAAG,CAACI,GAAG,CAACF,IAAI,CAAEC,OAAQT,KAAKM,GAAG,CAACI,GAAG,CAACD,MAAM,AAAC,CAC7D,CAEAJ,CAAAA,WAAWM,MAAM,CAAG3H,qBAAqByB,IAAItB,QAAQ,CAAEkH,WAAWC,GAAG,CACtE,CAEA,GAAIF,QAAS,CACZC,WAAWD,OAAO,CAAGA,OACtB,CAEA3F,IAAIf,WAAW,CAAC8E,IAAI,CAAC6B,WACtB,CAKA,SAAStD,gBAAgB6D,MAAmB,EAC3C,GAAIA,OAAOxF,IAAI,CAAE,CAChB,OAAO+B,MAAMC,OAAO,CAACwD,OAAOxF,IAAI,EAAIwF,OAAOxF,IAAI,CAAC8C,IAAI,CAAC,OAAS0C,OAAOxF,IAAI,AAC1E,CACA,GAAIwF,OAAOnC,KAAK,CAAE,MAAO,aACzB,GAAImC,OAAOC,KAAK,CAAE,MAAO,aACzB,GAAID,OAAOE,KAAK,CAAE,MAAO,aACzB,GAAIF,OAAOG,IAAI,CAAE,MAAO,OACxB,MAAO,SACR,CAKA,OAASxF,cAAc,CAAG"}
@@ -34,7 +34,7 @@ export declare class Typebars {
34
34
  * @param inputSchema - JSON Schema v7 describing the available variables
35
35
  * @param identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`
36
36
  */
37
- analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>): AnalysisResult;
37
+ analyze(template: TemplateInput, inputSchema: JSONSchema7, identifierSchemas?: Record<number, JSONSchema7>, expectedOutputType?: JSONSchema7): AnalysisResult;
38
38
  /**
39
39
  * Validates a template against a schema without returning the output type.
40
40
  *
@@ -88,7 +88,7 @@ export declare class Typebars {
88
88
  * @returns An object `{ analysis, value }` where `value` is `undefined`
89
89
  * if analysis failed.
90
90
  */
91
- analyzeAndExecute(template: TemplateInput, inputSchema: JSONSchema7, data: Record<string, unknown>, options?: AnalyzeAndExecuteOptions): {
91
+ analyzeAndExecute(template: TemplateInput, inputSchema: JSONSchema7, data: Record<string, unknown>, options?: AnalyzeAndExecuteOptions, expectedOutputType?: JSONSchema7): {
92
92
  analysis: AnalysisResult;
93
93
  value: unknown;
94
94
  };
@@ -1,2 +1,2 @@
1
- function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import Handlebars from"handlebars";import{analyzeFromAst}from"./analyzer.js";import{CompiledTemplate}from"./compiled-template.js";import{TemplateAnalysisError}from"./errors.js";import{executeFromAst}from"./executor.js";import{LogicalHelpers,MathHelpers}from"./helpers/index.js";import{parse}from"./parser.js";import{inferPrimitiveSchema,isArrayInput,isLiteralInput,isObjectInput}from"./types.js";import{aggregateArrayAnalysis,aggregateArrayAnalysisAndExecution,aggregateObjectAnalysis,aggregateObjectAnalysisAndExecution,LRUCache}from"./utils.js";export class Typebars{compile(template){if(isArrayInput(template)){const children=[];for(const element of template){children.push(this.compile(element))}return CompiledTemplate.fromArray(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if(isObjectInput(template)){const children={};for(const[key,value]of Object.entries(template)){children[key]=this.compile(value)}return CompiledTemplate.fromObject(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if(isLiteralInput(template)){return CompiledTemplate.fromLiteral(template,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}const ast=this.getCachedAst(template);const options={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return CompiledTemplate.fromTemplate(ast,template,options)}analyze(template,inputSchema,identifierSchemas){if(isArrayInput(template)){return aggregateArrayAnalysis(template.length,index=>this.analyze(template[index],inputSchema,identifierSchemas))}if(isObjectInput(template)){return aggregateObjectAnalysis(Object.keys(template),key=>this.analyze(template[key],inputSchema,identifierSchemas))}if(isLiteralInput(template)){return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)}}const ast=this.getCachedAst(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas,helpers:this.helpers})}validate(template,inputSchema,identifierSchemas){const analysis=this.analyze(template,inputSchema,identifierSchemas);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}isValidSyntax(template){if(isArrayInput(template)){return template.every(v=>this.isValidSyntax(v))}if(isObjectInput(template)){return Object.values(template).every(v=>this.isValidSyntax(v))}if(isLiteralInput(template))return true;try{this.getCachedAst(template);return true}catch{return false}}execute(template,data,options){if(isArrayInput(template)){const result=[];for(const element of template){result.push(this.execute(element,data,options))}return result}if(isObjectInput(template)){const result={};for(const[key,value]of Object.entries(template)){result[key]=this.execute(value,data,options)}return result}if(isLiteralInput(template))return template;const ast=this.getCachedAst(template);if(options?.schema){const analysis=analyzeFromAst(ast,template,options.schema,{identifierSchemas:options.identifierSchemas,helpers:this.helpers});if(!analysis.valid){throw new TemplateAnalysisError(analysis.diagnostics)}}return executeFromAst(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache})}analyzeAndExecute(template,inputSchema,data,options){if(isArrayInput(template)){return aggregateArrayAnalysisAndExecution(template.length,index=>this.analyzeAndExecute(template[index],inputSchema,data,options))}if(isObjectInput(template)){return aggregateObjectAnalysisAndExecution(Object.keys(template),key=>this.analyzeAndExecute(template[key],inputSchema,data,options))}if(isLiteralInput(template)){return{analysis:{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)},value:template}}const ast=this.getCachedAst(template);const analysis=analyzeFromAst(ast,template,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers});if(!analysis.valid){return{analysis,value:undefined}}const value=executeFromAst(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache});return{analysis,value}}registerHelper(name,definition){this.helpers.set(name,definition);this.hbs.registerHelper(name,definition.fn);this.compilationCache.clear();return this}unregisterHelper(name){this.helpers.delete(name);this.hbs.unregisterHelper(name);this.compilationCache.clear();return this}hasHelper(name){return this.helpers.has(name)}clearCaches(){this.astCache.clear();this.compilationCache.clear()}getCachedAst(template){let ast=this.astCache.get(template);if(!ast){ast=parse(template);this.astCache.set(template,ast)}return ast}constructor(options={}){_define_property(this,"hbs",void 0);_define_property(this,"astCache",void 0);_define_property(this,"compilationCache",void 0);_define_property(this,"helpers",new Map);this.hbs=Handlebars.create();this.astCache=new LRUCache(options.astCacheSize??256);this.compilationCache=new LRUCache(options.compilationCacheSize??256);new MathHelpers().register(this);new LogicalHelpers().register(this);if(options.helpers){for(const helper of options.helpers){const{name,...definition}=helper;this.registerHelper(name,definition)}}}}
1
+ function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import Handlebars from"handlebars";import{analyzeFromAst}from"./analyzer.js";import{CompiledTemplate}from"./compiled-template.js";import{TemplateAnalysisError}from"./errors.js";import{executeFromAst}from"./executor.js";import{LogicalHelpers,MathHelpers}from"./helpers/index.js";import{parse}from"./parser.js";import{resolveSchemaPath}from"./schema-resolver.js";import{inferPrimitiveSchema,isArrayInput,isLiteralInput,isObjectInput}from"./types.js";import{aggregateArrayAnalysis,aggregateArrayAnalysisAndExecution,aggregateObjectAnalysis,aggregateObjectAnalysisAndExecution,LRUCache}from"./utils.js";export class Typebars{compile(template){if(isArrayInput(template)){const children=[];for(const element of template){children.push(this.compile(element))}return CompiledTemplate.fromArray(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if(isObjectInput(template)){const children={};for(const[key,value]of Object.entries(template)){children[key]=this.compile(value)}return CompiledTemplate.fromObject(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if(isLiteralInput(template)){return CompiledTemplate.fromLiteral(template,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}const ast=this.getCachedAst(template);const options={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return CompiledTemplate.fromTemplate(ast,template,options)}analyze(template,inputSchema,identifierSchemas,expectedOutputType){if(isArrayInput(template)){return aggregateArrayAnalysis(template.length,index=>this.analyze(template[index],inputSchema,identifierSchemas))}if(isObjectInput(template)){const schemaForProperties=expectedOutputType??inputSchema;return aggregateObjectAnalysis(Object.keys(template),key=>{const propertySchema=resolveSchemaPath(schemaForProperties,[key]);return this.analyze(template[key],inputSchema,identifierSchemas,propertySchema)})}if(isLiteralInput(template)){return{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)}}const ast=this.getCachedAst(template);return analyzeFromAst(ast,template,inputSchema,{identifierSchemas,helpers:this.helpers,expectedOutputType:expectedOutputType??inputSchema})}validate(template,inputSchema,identifierSchemas){const analysis=this.analyze(template,inputSchema,identifierSchemas);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}isValidSyntax(template){if(isArrayInput(template)){return template.every(v=>this.isValidSyntax(v))}if(isObjectInput(template)){return Object.values(template).every(v=>this.isValidSyntax(v))}if(isLiteralInput(template))return true;try{this.getCachedAst(template);return true}catch{return false}}execute(template,data,options){if(isArrayInput(template)){const result=[];for(const element of template){result.push(this.execute(element,data,options))}return result}if(isObjectInput(template)){const result={};for(const[key,value]of Object.entries(template)){result[key]=this.execute(value,data,options)}return result}if(isLiteralInput(template))return template;const ast=this.getCachedAst(template);if(options?.schema){const analysis=analyzeFromAst(ast,template,options.schema,{identifierSchemas:options.identifierSchemas,helpers:this.helpers});if(!analysis.valid){throw new TemplateAnalysisError(analysis.diagnostics)}}return executeFromAst(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache})}analyzeAndExecute(template,inputSchema,data,options,expectedOutputType){if(isArrayInput(template)){return aggregateArrayAnalysisAndExecution(template.length,index=>this.analyzeAndExecute(template[index],inputSchema,data,options))}if(isObjectInput(template)){const schemaForProperties=expectedOutputType??inputSchema;return aggregateObjectAnalysisAndExecution(Object.keys(template),key=>{const propertySchema=resolveSchemaPath(schemaForProperties,[key]);return this.analyzeAndExecute(template[key],inputSchema,data,options,propertySchema)})}if(isLiteralInput(template)){return{analysis:{valid:true,diagnostics:[],outputSchema:inferPrimitiveSchema(template)},value:template}}const ast=this.getCachedAst(template);const analysis=analyzeFromAst(ast,template,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers,expectedOutputType:expectedOutputType??inputSchema});if(!analysis.valid){return{analysis,value:undefined}}const value=executeFromAst(ast,template,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache});return{analysis,value}}registerHelper(name,definition){this.helpers.set(name,definition);this.hbs.registerHelper(name,definition.fn);this.compilationCache.clear();return this}unregisterHelper(name){this.helpers.delete(name);this.hbs.unregisterHelper(name);this.compilationCache.clear();return this}hasHelper(name){return this.helpers.has(name)}clearCaches(){this.astCache.clear();this.compilationCache.clear()}getCachedAst(template){let ast=this.astCache.get(template);if(!ast){ast=parse(template);this.astCache.set(template,ast)}return ast}constructor(options={}){_define_property(this,"hbs",void 0);_define_property(this,"astCache",void 0);_define_property(this,"compilationCache",void 0);_define_property(this,"helpers",new Map);this.hbs=Handlebars.create();this.astCache=new LRUCache(options.astCacheSize??256);this.compilationCache=new LRUCache(options.compilationCacheSize??256);new MathHelpers().register(this);new LogicalHelpers().register(this);if(options.helpers){for(const helper of options.helpers){const{name,...definition}=helper;this.registerHelper(name,definition)}}}}
2
2
  //# sourceMappingURL=typebars.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport { LogicalHelpers, MathHelpers } from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateArrayAnalysisAndExecution,\n\taggregateObjectAnalysis,\n\taggregateObjectAnalysisAndExecution,\n\tLRUCache,\n} 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, { 1: node1Schema });\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\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 identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7,\n\t\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t): AnalysisResult {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\t\t\tthis.analyze(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn aggregateObjectAnalysis(Object.keys(template), (key) =>\n\t\t\t\tthis.analyze(\n\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tvalid: true,\n\t\t\t\tdiagnostics: [],\n\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t};\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas,\n\t\t\thelpers: this.helpers,\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\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, identifierSchemas);\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: Record<string, unknown>,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\t// ── Array template → recursive execution ─────────────────────────────\n\t\tif (isArrayInput(template)) {\n\t\t\tconst result: unknown[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tresult.push(this.execute(element, data, options));\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Object template → recursive execution ────────────────────────────\n\t\tif (isObjectInput(template)) {\n\t\t\tconst result: Record<string, unknown> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tresult[key] = this.execute(value, data, options);\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Passthrough for literal values ────────────────────────────────────\n\t\tif (isLiteralInput(template)) return template;\n\n\t\t// ── Parse once ───────────────────────────────────────────────────────\n\t\tconst ast = this.getCachedAst(template);\n\n\t\t// ── Pre-execution static validation ──────────────────────────────────\n\t\tif (options?.schema) {\n\t\t\tconst analysis = analyzeFromAst(ast, template, options.schema, {\n\t\t\t\tidentifierSchemas: options.identifierSchemas,\n\t\t\t\thelpers: this.helpers,\n\t\t\t});\n\t\t\tif (!analysis.valid) {\n\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t}\n\t\t}\n\n\t\t// ── Execution ────────────────────────────────────────────────────────\n\t\treturn executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\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: Record<string, unknown>,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysisAndExecution(template.length, (index) =>\n\t\t\t\tthis.analyzeAndExecute(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tdata,\n\t\t\t\t\toptions,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn aggregateObjectAnalysisAndExecution(Object.keys(template), (key) =>\n\t\t\t\tthis.analyzeAndExecute(\n\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tdata,\n\t\t\t\t\toptions,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tanalysis: {\n\t\t\t\t\tvalid: true,\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t\t},\n\t\t\t\tvalue: template,\n\t\t\t};\n\t\t}\n\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst analysis = analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\thelpers: this.helpers,\n\t\t});\n\n\t\tif (!analysis.valid) {\n\t\t\treturn { analysis, value: undefined };\n\t\t}\n\n\t\tconst value = executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t});\n\t\treturn { analysis, value };\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\t\tthis.hbs.registerHelper(name, definition.fn);\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":["Handlebars","analyzeFromAst","CompiledTemplate","TemplateAnalysisError","executeFromAst","LogicalHelpers","MathHelpers","parse","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","aggregateArrayAnalysis","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysis","aggregateObjectAnalysisAndExecution","LRUCache","Typebars","compile","template","children","element","push","fromArray","helpers","hbs","compilationCache","key","value","Object","entries","fromObject","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","identifierSchemas","length","index","keys","valid","diagnostics","outputSchema","validate","analysis","isValidSyntax","every","v","values","execute","data","result","schema","identifierData","analyzeAndExecute","undefined","registerHelper","name","definition","set","fn","clear","unregisterHelper","delete","hasHelper","has","clearCaches","astCache","get","Map","create","astCacheSize","compilationCacheSize","register","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 { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport { LogicalHelpers, MathHelpers } from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport { resolveSchemaPath } from \"./schema-resolver.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport {\n\tinferPrimitiveSchema,\n\tisArrayInput,\n\tisLiteralInput,\n\tisObjectInput,\n} from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateArrayAnalysisAndExecution,\n\taggregateObjectAnalysis,\n\taggregateObjectAnalysisAndExecution,\n\tLRUCache,\n} 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, { 1: node1Schema });\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\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 identifierSchemas - (optional) Schemas by identifier `{ [id]: JSONSchema7 }`\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7,\n\t\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t\texpectedOutputType?: JSONSchema7,\n\t): AnalysisResult {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysis(template.length, (index) =>\n\t\t\t\tthis.analyze(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\t// Use the expectedOutputType (if it describes an object) to resolve\n\t\t\t// child property schemas. This is critical for deeply nested objects:\n\t\t\t// when the template is `{ a: { b: { c: \"123\" } } }`, each level must\n\t\t\t// resolve its children from the *corresponding* sub-schema, not from\n\t\t\t// the root inputSchema.\n\t\t\tconst schemaForProperties = expectedOutputType ?? inputSchema;\n\t\t\treturn aggregateObjectAnalysis(Object.keys(template), (key) => {\n\t\t\t\tconst propertySchema = resolveSchemaPath(schemaForProperties, [key]);\n\t\t\t\treturn this.analyze(\n\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tidentifierSchemas,\n\t\t\t\t\tpropertySchema,\n\t\t\t\t);\n\t\t\t});\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tvalid: true,\n\t\t\t\tdiagnostics: [],\n\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t};\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\treturn analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas,\n\t\t\thelpers: this.helpers,\n\t\t\texpectedOutputType: expectedOutputType ?? inputSchema,\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\tidentifierSchemas?: Record<number, JSONSchema7>,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, identifierSchemas);\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: Record<string, unknown>,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\t// ── Array template → recursive execution ─────────────────────────────\n\t\tif (isArrayInput(template)) {\n\t\t\tconst result: unknown[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tresult.push(this.execute(element, data, options));\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Object template → recursive execution ────────────────────────────\n\t\tif (isObjectInput(template)) {\n\t\t\tconst result: Record<string, unknown> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tresult[key] = this.execute(value, data, options);\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\t// ── Passthrough for literal values ────────────────────────────────────\n\t\tif (isLiteralInput(template)) return template;\n\n\t\t// ── Parse once ───────────────────────────────────────────────────────\n\t\tconst ast = this.getCachedAst(template);\n\n\t\t// ── Pre-execution static validation ──────────────────────────────────\n\t\tif (options?.schema) {\n\t\t\tconst analysis = analyzeFromAst(ast, template, options.schema, {\n\t\t\t\tidentifierSchemas: options.identifierSchemas,\n\t\t\t\thelpers: this.helpers,\n\t\t\t});\n\t\t\tif (!analysis.valid) {\n\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t}\n\t\t}\n\n\t\t// ── Execution ────────────────────────────────────────────────────────\n\t\treturn executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\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: Record<string, unknown>,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t\texpectedOutputType?: JSONSchema7,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn aggregateArrayAnalysisAndExecution(template.length, (index) =>\n\t\t\t\tthis.analyzeAndExecute(\n\t\t\t\t\ttemplate[index] as TemplateInput,\n\t\t\t\t\tinputSchema,\n\t\t\t\t\tdata,\n\t\t\t\t\toptions,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\t// Use the expectedOutputType (if it describes an object) to resolve\n\t\t\t// child property schemas. This is critical for deeply nested objects.\n\t\t\tconst schemaForProperties = expectedOutputType ?? inputSchema;\n\t\t\treturn aggregateObjectAnalysisAndExecution(\n\t\t\t\tObject.keys(template),\n\t\t\t\t(key) => {\n\t\t\t\t\tconst propertySchema = resolveSchemaPath(schemaForProperties, [key]);\n\t\t\t\t\treturn this.analyzeAndExecute(\n\t\t\t\t\t\ttemplate[key] as TemplateInput,\n\t\t\t\t\t\tinputSchema,\n\t\t\t\t\t\tdata,\n\t\t\t\t\t\toptions,\n\t\t\t\t\t\tpropertySchema,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn {\n\t\t\t\tanalysis: {\n\t\t\t\t\tvalid: true,\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\toutputSchema: inferPrimitiveSchema(template),\n\t\t\t\t},\n\t\t\t\tvalue: template,\n\t\t\t};\n\t\t}\n\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst analysis = analyzeFromAst(ast, template, inputSchema, {\n\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\thelpers: this.helpers,\n\t\t\texpectedOutputType: expectedOutputType ?? inputSchema,\n\t\t});\n\n\t\tif (!analysis.valid) {\n\t\t\treturn { analysis, value: undefined };\n\t\t}\n\n\t\tconst value = executeFromAst(ast, template, data, {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t});\n\t\treturn { analysis, value };\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\t\tthis.hbs.registerHelper(name, definition.fn);\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":["Handlebars","analyzeFromAst","CompiledTemplate","TemplateAnalysisError","executeFromAst","LogicalHelpers","MathHelpers","parse","resolveSchemaPath","inferPrimitiveSchema","isArrayInput","isLiteralInput","isObjectInput","aggregateArrayAnalysis","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysis","aggregateObjectAnalysisAndExecution","LRUCache","Typebars","compile","template","children","element","push","fromArray","helpers","hbs","compilationCache","key","value","Object","entries","fromObject","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","identifierSchemas","expectedOutputType","length","index","schemaForProperties","keys","propertySchema","valid","diagnostics","outputSchema","validate","analysis","isValidSyntax","every","v","values","execute","data","result","schema","identifierData","analyzeAndExecute","undefined","registerHelper","name","definition","set","fn","clear","unregisterHelper","delete","hasHelper","has","clearCaches","astCache","get","Map","create","astCacheSize","compilationCacheSize","register","helper"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typebars",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "license": "MIT",
5
5
  "description": "Typebars is a type-safe handlebars based template engine for generating object or content with static type checking",
6
6
  "author": {