typebars 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cjs/analyzer.js +1 -1
  2. package/dist/cjs/analyzer.js.map +1 -1
  3. package/dist/cjs/compiled-template.js +1 -1
  4. package/dist/cjs/compiled-template.js.map +1 -1
  5. package/dist/cjs/executor.d.ts +3 -1
  6. package/dist/cjs/executor.js +1 -1
  7. package/dist/cjs/executor.js.map +1 -1
  8. package/dist/cjs/helpers/collection-helpers.d.ts +9 -0
  9. package/dist/cjs/helpers/collection-helpers.js +2 -0
  10. package/dist/cjs/helpers/collection-helpers.js.map +1 -0
  11. package/dist/cjs/helpers/index.d.ts +1 -0
  12. package/dist/cjs/helpers/index.js +1 -1
  13. package/dist/cjs/helpers/index.js.map +1 -1
  14. package/dist/cjs/parser.js +1 -1
  15. package/dist/cjs/parser.js.map +1 -1
  16. package/dist/cjs/schema-resolver.js +1 -1
  17. package/dist/cjs/schema-resolver.js.map +1 -1
  18. package/dist/cjs/typebars.js +1 -1
  19. package/dist/cjs/typebars.js.map +1 -1
  20. package/dist/esm/analyzer.js +1 -1
  21. package/dist/esm/analyzer.js.map +1 -1
  22. package/dist/esm/compiled-template.js +1 -1
  23. package/dist/esm/compiled-template.js.map +1 -1
  24. package/dist/esm/executor.d.ts +3 -1
  25. package/dist/esm/executor.js +1 -1
  26. package/dist/esm/executor.js.map +1 -1
  27. package/dist/esm/helpers/collection-helpers.d.ts +9 -0
  28. package/dist/esm/helpers/collection-helpers.js +2 -0
  29. package/dist/esm/helpers/collection-helpers.js.map +1 -0
  30. package/dist/esm/helpers/index.d.ts +1 -0
  31. package/dist/esm/helpers/index.js +1 -1
  32. package/dist/esm/helpers/index.js.map +1 -1
  33. package/dist/esm/parser.js +1 -1
  34. package/dist/esm/parser.js.map +1 -1
  35. package/dist/esm/schema-resolver.js +1 -1
  36. package/dist/esm/schema-resolver.js.map +1 -1
  37. package/dist/esm/typebars.js +1 -1
  38. package/dist/esm/typebars.js.map +1 -1
  39. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/compiled-template.ts"],"sourcesContent":["import type Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { AnalyzeOptions } from \"./analyzer.ts\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport { resolveChildCoerceSchema, shouldExcludeEntry } from \"./dispatch.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { type ExecutorContext, executeFromAst } from \"./executor.ts\";\nimport type {\n\tAnalysisResult,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateData,\n\tValidationResult,\n} from \"./types.ts\";\nimport { inferPrimitiveSchema } from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateArrayAnalysisAndExecution,\n\taggregateObjectAnalysis,\n\taggregateObjectAnalysisAndExecution,\n\ttype LRUCache,\n} from \"./utils\";\n\n// ─── CompiledTemplate ────────────────────────────────────────────────────────\n// Pre-parsed template ready to be executed or analyzed without re-parsing.\n//\n// The compile-once / execute-many pattern avoids the cost of Handlebars\n// parsing on every call. The AST is parsed once at compile time, and the\n// Handlebars template is lazily compiled on the first `execute()`.\n//\n// Usage:\n// const tpl = engine.compile(\"Hello {{name}}\");\n// tpl.execute({ name: \"Alice\" }); // no re-parsing\n// tpl.execute({ name: \"Bob\" }); // no re-parsing or recompilation\n// tpl.analyze(schema); // no re-parsing\n//\n// ─── Internal State (TemplateState) ──────────────────────────────────────────\n// CompiledTemplate operates in 4 exclusive modes, modeled by a discriminated\n// union `TemplateState`:\n//\n// - `\"template\"` — parsed Handlebars template (AST + source string)\n// - `\"literal\"` — primitive passthrough value (number, boolean, null)\n// - `\"object\"` — object where each property is a child CompiledTemplate\n// - `\"array\"` — array where each element is a child CompiledTemplate\n//\n// This design eliminates optional fields and `!` assertions in favor of\n// natural TypeScript narrowing via `switch (this.state.kind)`.\n//\n// ─── Advantages Over the Direct API ──────────────────────────────────────────\n// - **Performance**: parsing and compilation happen only once\n// - **Simplified API**: no need to re-pass the template string on each call\n// - **Consistency**: the same AST is used for both analysis and execution\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Internal options passed by Typebars during compilation */\nexport interface CompiledTemplateOptions {\n\t/** Custom helpers registered on the engine */\n\thelpers: Map<string, HelperDefinition>;\n\t/** Isolated Handlebars environment (with registered helpers) */\n\thbs: typeof Handlebars;\n\t/** Compilation cache shared by the engine */\n\tcompilationCache: LRUCache<string, HandlebarsTemplateDelegate>;\n}\n\n/** Discriminated internal state of the CompiledTemplate */\ntype TemplateState =\n\t| {\n\t\t\treadonly kind: \"template\";\n\t\t\treadonly ast: hbs.AST.Program;\n\t\t\treadonly source: string;\n\t }\n\t| { readonly kind: \"literal\"; readonly value: number | boolean | null }\n\t| {\n\t\t\treadonly kind: \"object\";\n\t\t\treadonly children: Record<string, CompiledTemplate>;\n\t }\n\t| {\n\t\t\treadonly kind: \"array\";\n\t\t\treadonly elements: CompiledTemplate[];\n\t };\n\n// ─── Public Class ────────────────────────────────────────────────────────────\n\nexport class CompiledTemplate {\n\t/** Discriminated internal state */\n\tprivate readonly state: TemplateState;\n\n\t/** Options inherited from the parent Typebars instance */\n\tprivate readonly options: CompiledTemplateOptions;\n\n\t/** Compiled Handlebars template (lazy — created on the first `execute()` that needs it) */\n\tprivate hbsCompiled: HandlebarsTemplateDelegate | null = null;\n\n\t// ─── Public Accessors (backward-compatible) ──────────────────────────\n\n\t/** The pre-parsed Handlebars AST — `null` in literal, object, or array mode */\n\tget ast(): hbs.AST.Program | null {\n\t\treturn this.state.kind === \"template\" ? this.state.ast : null;\n\t}\n\n\t/** The original template source — empty string in literal, object, or array mode */\n\tget template(): string {\n\t\treturn this.state.kind === \"template\" ? this.state.source : \"\";\n\t}\n\n\t// ─── Construction ────────────────────────────────────────────────────\n\n\tprivate constructor(state: TemplateState, options: CompiledTemplateOptions) {\n\t\tthis.state = state;\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate for a parsed Handlebars template.\n\t *\n\t * @param ast - The pre-parsed Handlebars AST\n\t * @param source - The original template source\n\t * @param options - Options inherited from Typebars\n\t */\n\tstatic fromTemplate(\n\t\tast: hbs.AST.Program,\n\t\tsource: string,\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"template\", ast, source }, options);\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate in passthrough mode for a literal value\n\t * (number, boolean, null). No parsing or compilation is performed.\n\t *\n\t * @param value - The primitive value\n\t * @param options - Options inherited from Typebars\n\t * @returns A CompiledTemplate that always returns `value`\n\t */\n\tstatic fromLiteral(\n\t\tvalue: number | boolean | null,\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"literal\", value }, options);\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate in object mode, where each property is a\n\t * child CompiledTemplate. All operations are recursively delegated\n\t * to the children.\n\t *\n\t * @param children - The compiled child templates `{ [key]: CompiledTemplate }`\n\t * @param options - Options inherited from Typebars\n\t * @returns A CompiledTemplate that delegates to children\n\t */\n\tstatic fromObject(\n\t\tchildren: Record<string, CompiledTemplate>,\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"object\", children }, options);\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate in array mode, where each element is a\n\t * child CompiledTemplate. All operations are recursively delegated\n\t * to the elements.\n\t *\n\t * @param elements - The compiled child templates (ordered array)\n\t * @param options - Options inherited from Typebars\n\t * @returns A CompiledTemplate that delegates to elements\n\t */\n\tstatic fromArray(\n\t\telements: CompiledTemplate[],\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"array\", elements }, options);\n\t}\n\n\t// ─── Static Analysis ─────────────────────────────────────────────────\n\n\t/**\n\t * Statically analyzes this template against a JSON Schema v7.\n\t *\n\t * Returns an `AnalysisResult` containing:\n\t * - `valid` — `true` if no errors\n\t * - `diagnostics` — list of diagnostics (errors + warnings)\n\t * - `outputSchema` — JSON Schema describing the return type\n\t *\n\t * Since the AST is pre-parsed, this method never re-parses the template.\n\t *\n\t * @param inputSchema - JSON Schema describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tanalyze(\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): AnalysisResult {\n\t\tconst exclude = options?.excludeTemplateExpression === true;\n\n\t\tswitch (this.state.kind) {\n\t\t\tcase \"array\": {\n\t\t\t\tconst { elements } = this.state;\n\n\t\t\t\tif (exclude) {\n\t\t\t\t\t// When excludeTemplateExpression is enabled, filter out elements\n\t\t\t\t\t// that are string templates containing Handlebars expressions.\n\t\t\t\t\tconst kept = elements.filter(\n\t\t\t\t\t\t(el) => !isCompiledTemplateWithExpression(el),\n\t\t\t\t\t);\n\t\t\t\t\treturn aggregateArrayAnalysis(kept.length, (index) => {\n\t\t\t\t\t\tconst element = kept[index];\n\t\t\t\t\t\tif (!element)\n\t\t\t\t\t\t\tthrow new Error(`unreachable: missing element at index ${index}`);\n\t\t\t\t\t\treturn element.analyze(inputSchema, options);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn aggregateArrayAnalysis(elements.length, (index) => {\n\t\t\t\t\tconst element = elements[index];\n\t\t\t\t\tif (!element)\n\t\t\t\t\t\tthrow new Error(`unreachable: missing element at index ${index}`);\n\t\t\t\t\treturn element.analyze(inputSchema, options);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"object\": {\n\t\t\t\tconst { children } = this.state;\n\t\t\t\tconst coerceSchema = options?.coerceSchema;\n\n\t\t\t\t// When excludeTemplateExpression is enabled, filter out keys whose\n\t\t\t\t// compiled children are string templates with Handlebars expressions.\n\t\t\t\tconst keys = exclude\n\t\t\t\t\t? Object.keys(children).filter((key) => {\n\t\t\t\t\t\t\tconst child = children[key];\n\t\t\t\t\t\t\treturn !child || !isCompiledTemplateWithExpression(child);\n\t\t\t\t\t\t})\n\t\t\t\t\t: Object.keys(children);\n\n\t\t\t\treturn aggregateObjectAnalysis(keys, (key) => {\n\t\t\t\t\tconst child = children[key];\n\t\t\t\t\tif (!child) throw new Error(`unreachable: missing child \"${key}\"`);\n\t\t\t\t\tconst childCoerceSchema = resolveChildCoerceSchema(coerceSchema, key);\n\t\t\t\t\treturn child.analyze(inputSchema, {\n\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\tcoerceSchema: childCoerceSchema,\n\t\t\t\t\t\texcludeTemplateExpression: options?.excludeTemplateExpression,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"literal\":\n\t\t\t\treturn {\n\t\t\t\t\tvalid: true,\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\toutputSchema: inferPrimitiveSchema(this.state.value),\n\t\t\t\t};\n\n\t\t\tcase \"template\":\n\t\t\t\treturn analyzeFromAst(this.state.ast, this.state.source, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.options.helpers,\n\t\t\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\t\t});\n\t\t}\n\t}\n\n\t// ─── Validation ──────────────────────────────────────────────────────\n\n\t/**\n\t * Validates the 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 inputSchema - JSON Schema describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tvalidate(\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(inputSchema, options);\n\t\treturn {\n\t\t\tvalid: analysis.valid,\n\t\t\tdiagnostics: analysis.diagnostics,\n\t\t};\n\t}\n\n\t// ─── Execution ───────────────────────────────────────────────────────\n\n\t/**\n\t * Executes this template with the provided data.\n\t *\n\t * The return type depends on the template structure:\n\t * - Single expression `{{expr}}` → raw value (number, boolean, object…)\n\t * - Mixed template or with blocks → `string`\n\t * - Primitive literal → the value as-is\n\t * - Object template → object with resolved values\n\t * - Array template → array with resolved values\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 data - The context data for rendering\n\t * @param options - Execution options (schema, identifierData, coerceSchema, etc.)\n\t * @returns The execution result\n\t */\n\texecute(data: TemplateData, options?: ExecuteOptions): unknown {\n\t\tswitch (this.state.kind) {\n\t\t\tcase \"array\": {\n\t\t\t\tconst { elements } = this.state;\n\t\t\t\tconst result: unknown[] = [];\n\t\t\t\tfor (const element of elements) {\n\t\t\t\t\tresult.push(element.execute(data, options));\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tcase \"object\": {\n\t\t\t\tconst { children } = this.state;\n\t\t\t\tconst coerceSchema = options?.coerceSchema;\n\t\t\t\tconst result: Record<string, unknown> = {};\n\t\t\t\tfor (const [key, child] of Object.entries(children)) {\n\t\t\t\t\tconst childCoerceSchema = resolveChildCoerceSchema(coerceSchema, key);\n\t\t\t\t\tresult[key] = child.execute(data, {\n\t\t\t\t\t\t...options,\n\t\t\t\t\t\tcoerceSchema: childCoerceSchema,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tcase \"literal\":\n\t\t\t\treturn this.state.value;\n\n\t\t\tcase \"template\": {\n\t\t\t\t// Pre-execution static validation if a schema is provided\n\t\t\t\tif (options?.schema) {\n\t\t\t\t\tconst analysis = this.analyze(options.schema, {\n\t\t\t\t\t\tidentifierSchemas: options.identifierSchemas,\n\t\t\t\t\t\tcoerceSchema: options.coerceSchema,\n\t\t\t\t\t});\n\t\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn executeFromAst(\n\t\t\t\t\tthis.state.ast,\n\t\t\t\t\tthis.state.source,\n\t\t\t\t\tdata,\n\t\t\t\t\tthis.buildExecutorContext(options),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Combined Shortcuts ──────────────────────────────────────────────\n\n\t/**\n\t * Analyzes and executes the template in a single call.\n\t *\n\t * Returns both the analysis result and the executed value.\n\t * If analysis fails, `value` is `undefined`.\n\t *\n\t * @param inputSchema - JSON Schema describing the available variables\n\t * @param data - The context data for rendering\n\t * @param options - Additional options (identifierSchemas, identifierData, coerceSchema)\n\t * @returns `{ analysis, value }`\n\t */\n\tanalyzeAndExecute(\n\t\tinputSchema: JSONSchema7 = {},\n\t\tdata: TemplateData,\n\t\toptions?: {\n\t\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\t\tidentifierData?: Record<number, Record<string, unknown>>;\n\t\t\tcoerceSchema?: JSONSchema7;\n\t\t},\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\tswitch (this.state.kind) {\n\t\t\tcase \"array\": {\n\t\t\t\tconst { elements } = this.state;\n\t\t\t\treturn aggregateArrayAnalysisAndExecution(elements.length, (index) => {\n\t\t\t\t\tconst element = elements[index];\n\t\t\t\t\tif (!element)\n\t\t\t\t\t\tthrow new Error(`unreachable: missing element at index ${index}`);\n\t\t\t\t\treturn element.analyzeAndExecute(inputSchema, data, options);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"object\": {\n\t\t\t\tconst { children } = this.state;\n\t\t\t\tconst coerceSchema = options?.coerceSchema;\n\t\t\t\treturn aggregateObjectAnalysisAndExecution(\n\t\t\t\t\tObject.keys(children),\n\t\t\t\t\t(key) => {\n\t\t\t\t\t\tconst child = children[key];\n\t\t\t\t\t\tif (!child) throw new Error(`unreachable: missing child \"${key}\"`);\n\t\t\t\t\t\tconst childCoerceSchema = resolveChildCoerceSchema(\n\t\t\t\t\t\t\tcoerceSchema,\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn child.analyzeAndExecute(inputSchema, data, {\n\t\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\t\t\tcoerceSchema: childCoerceSchema,\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\tcase \"literal\":\n\t\t\t\treturn {\n\t\t\t\t\tanalysis: {\n\t\t\t\t\t\tvalid: true,\n\t\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\t\toutputSchema: inferPrimitiveSchema(this.state.value),\n\t\t\t\t\t},\n\t\t\t\t\tvalue: this.state.value,\n\t\t\t\t};\n\n\t\t\tcase \"template\": {\n\t\t\t\tconst analysis = this.analyze(inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\t\t});\n\n\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\treturn { analysis, value: undefined };\n\t\t\t\t}\n\n\t\t\t\tconst value = executeFromAst(\n\t\t\t\t\tthis.state.ast,\n\t\t\t\t\tthis.state.source,\n\t\t\t\t\tdata,\n\t\t\t\t\tthis.buildExecutorContext({\n\t\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn { analysis, value };\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Internals ───────────────────────────────────────────────────────\n\n\t/**\n\t * Builds the execution context for `executeFromAst`.\n\t *\n\t * Uses lazy Handlebars compilation: the template is only compiled\n\t * on the first call that needs it (not for single expressions).\n\t */\n\tprivate buildExecutorContext(options?: ExecuteOptions): ExecutorContext {\n\t\treturn {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\tcompiledTemplate: this.getOrCompileHbs(),\n\t\t\thbs: this.options.hbs,\n\t\t\tcompilationCache: this.options.compilationCache,\n\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t};\n\t}\n\n\t/**\n\t * Lazily compiles the Handlebars template and caches it.\n\t *\n\t * Compilation happens only once — subsequent calls return the\n\t * in-memory compiled template.\n\t *\n\t * Precondition: this method is only called from \"template\" mode.\n\t */\n\tprivate getOrCompileHbs(): HandlebarsTemplateDelegate {\n\t\tif (!this.hbsCompiled) {\n\t\t\t// In \"template\" mode, `this.template` returns the source string\n\t\t\tthis.hbsCompiled = this.options.hbs.compile(this.template, {\n\t\t\t\tnoEscape: true,\n\t\t\t\tstrict: false,\n\t\t\t});\n\t\t}\n\t\treturn this.hbsCompiled;\n\t}\n}\n\n// ─── Internal Helpers ────────────────────────────────────────────────────────\n\n/**\n * Determines whether a `CompiledTemplate` represents a string template\n * containing Handlebars expressions (`{{…}}`).\n *\n * Used by `excludeTemplateExpression` filtering to skip dynamic entries\n * in object and array modes.\n */\nfunction isCompiledTemplateWithExpression(ct: CompiledTemplate): boolean {\n\t// Only \"template\" kind can contain expressions. Literals, objects,\n\t// and arrays are never excluded at the entry level — objects and\n\t// arrays are recursively filtered by the analysis method itself.\n\treturn ct.template !== \"\" && shouldExcludeEntry(ct.template);\n}\n"],"names":["analyzeFromAst","resolveChildCoerceSchema","shouldExcludeEntry","TemplateAnalysisError","executeFromAst","inferPrimitiveSchema","aggregateArrayAnalysis","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysis","aggregateObjectAnalysisAndExecution","CompiledTemplate","ast","state","kind","template","source","fromTemplate","options","fromLiteral","value","fromObject","children","fromArray","elements","analyze","inputSchema","exclude","excludeTemplateExpression","kept","filter","el","isCompiledTemplateWithExpression","length","index","element","Error","coerceSchema","keys","Object","key","child","childCoerceSchema","identifierSchemas","valid","diagnostics","outputSchema","helpers","validate","analysis","execute","data","result","push","entries","schema","buildExecutorContext","analyzeAndExecute","identifierData","undefined","compiledTemplate","getOrCompileHbs","hbs","compilationCache","hbsCompiled","compile","noEscape","strict","ct"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/compiled-template.ts"],"sourcesContent":["import type Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { AnalyzeOptions } from \"./analyzer.ts\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport { resolveChildCoerceSchema, shouldExcludeEntry } from \"./dispatch.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { type ExecutorContext, executeFromAst } from \"./executor.ts\";\nimport type {\n\tAnalysisResult,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateData,\n\tValidationResult,\n} from \"./types.ts\";\nimport { inferPrimitiveSchema } from \"./types.ts\";\nimport {\n\taggregateArrayAnalysis,\n\taggregateArrayAnalysisAndExecution,\n\taggregateObjectAnalysis,\n\taggregateObjectAnalysisAndExecution,\n\ttype LRUCache,\n} from \"./utils\";\n\n// ─── CompiledTemplate ────────────────────────────────────────────────────────\n// Pre-parsed template ready to be executed or analyzed without re-parsing.\n//\n// The compile-once / execute-many pattern avoids the cost of Handlebars\n// parsing on every call. The AST is parsed once at compile time, and the\n// Handlebars template is lazily compiled on the first `execute()`.\n//\n// Usage:\n// const tpl = engine.compile(\"Hello {{name}}\");\n// tpl.execute({ name: \"Alice\" }); // no re-parsing\n// tpl.execute({ name: \"Bob\" }); // no re-parsing or recompilation\n// tpl.analyze(schema); // no re-parsing\n//\n// ─── Internal State (TemplateState) ──────────────────────────────────────────\n// CompiledTemplate operates in 4 exclusive modes, modeled by a discriminated\n// union `TemplateState`:\n//\n// - `\"template\"` — parsed Handlebars template (AST + source string)\n// - `\"literal\"` — primitive passthrough value (number, boolean, null)\n// - `\"object\"` — object where each property is a child CompiledTemplate\n// - `\"array\"` — array where each element is a child CompiledTemplate\n//\n// This design eliminates optional fields and `!` assertions in favor of\n// natural TypeScript narrowing via `switch (this.state.kind)`.\n//\n// ─── Advantages Over the Direct API ──────────────────────────────────────────\n// - **Performance**: parsing and compilation happen only once\n// - **Simplified API**: no need to re-pass the template string on each call\n// - **Consistency**: the same AST is used for both analysis and execution\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\n/** Internal options passed by Typebars during compilation */\nexport interface CompiledTemplateOptions {\n\t/** Custom helpers registered on the engine */\n\thelpers: Map<string, HelperDefinition>;\n\t/** Isolated Handlebars environment (with registered helpers) */\n\thbs: typeof Handlebars;\n\t/** Compilation cache shared by the engine */\n\tcompilationCache: LRUCache<string, HandlebarsTemplateDelegate>;\n}\n\n/** Discriminated internal state of the CompiledTemplate */\ntype TemplateState =\n\t| {\n\t\t\treadonly kind: \"template\";\n\t\t\treadonly ast: hbs.AST.Program;\n\t\t\treadonly source: string;\n\t }\n\t| { readonly kind: \"literal\"; readonly value: number | boolean | null }\n\t| {\n\t\t\treadonly kind: \"object\";\n\t\t\treadonly children: Record<string, CompiledTemplate>;\n\t }\n\t| {\n\t\t\treadonly kind: \"array\";\n\t\t\treadonly elements: CompiledTemplate[];\n\t };\n\n// ─── Public Class ────────────────────────────────────────────────────────────\n\nexport class CompiledTemplate {\n\t/** Discriminated internal state */\n\tprivate readonly state: TemplateState;\n\n\t/** Options inherited from the parent Typebars instance */\n\tprivate readonly options: CompiledTemplateOptions;\n\n\t/** Compiled Handlebars template (lazy — created on the first `execute()` that needs it) */\n\tprivate hbsCompiled: HandlebarsTemplateDelegate | null = null;\n\n\t// ─── Public Accessors (backward-compatible) ──────────────────────────\n\n\t/** The pre-parsed Handlebars AST — `null` in literal, object, or array mode */\n\tget ast(): hbs.AST.Program | null {\n\t\treturn this.state.kind === \"template\" ? this.state.ast : null;\n\t}\n\n\t/** The original template source — empty string in literal, object, or array mode */\n\tget template(): string {\n\t\treturn this.state.kind === \"template\" ? this.state.source : \"\";\n\t}\n\n\t// ─── Construction ────────────────────────────────────────────────────\n\n\tprivate constructor(state: TemplateState, options: CompiledTemplateOptions) {\n\t\tthis.state = state;\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate for a parsed Handlebars template.\n\t *\n\t * @param ast - The pre-parsed Handlebars AST\n\t * @param source - The original template source\n\t * @param options - Options inherited from Typebars\n\t */\n\tstatic fromTemplate(\n\t\tast: hbs.AST.Program,\n\t\tsource: string,\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"template\", ast, source }, options);\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate in passthrough mode for a literal value\n\t * (number, boolean, null). No parsing or compilation is performed.\n\t *\n\t * @param value - The primitive value\n\t * @param options - Options inherited from Typebars\n\t * @returns A CompiledTemplate that always returns `value`\n\t */\n\tstatic fromLiteral(\n\t\tvalue: number | boolean | null,\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"literal\", value }, options);\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate in object mode, where each property is a\n\t * child CompiledTemplate. All operations are recursively delegated\n\t * to the children.\n\t *\n\t * @param children - The compiled child templates `{ [key]: CompiledTemplate }`\n\t * @param options - Options inherited from Typebars\n\t * @returns A CompiledTemplate that delegates to children\n\t */\n\tstatic fromObject(\n\t\tchildren: Record<string, CompiledTemplate>,\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"object\", children }, options);\n\t}\n\n\t/**\n\t * Creates a CompiledTemplate in array mode, where each element is a\n\t * child CompiledTemplate. All operations are recursively delegated\n\t * to the elements.\n\t *\n\t * @param elements - The compiled child templates (ordered array)\n\t * @param options - Options inherited from Typebars\n\t * @returns A CompiledTemplate that delegates to elements\n\t */\n\tstatic fromArray(\n\t\telements: CompiledTemplate[],\n\t\toptions: CompiledTemplateOptions,\n\t): CompiledTemplate {\n\t\treturn new CompiledTemplate({ kind: \"array\", elements }, options);\n\t}\n\n\t// ─── Static Analysis ─────────────────────────────────────────────────\n\n\t/**\n\t * Statically analyzes this template against a JSON Schema v7.\n\t *\n\t * Returns an `AnalysisResult` containing:\n\t * - `valid` — `true` if no errors\n\t * - `diagnostics` — list of diagnostics (errors + warnings)\n\t * - `outputSchema` — JSON Schema describing the return type\n\t *\n\t * Since the AST is pre-parsed, this method never re-parses the template.\n\t *\n\t * @param inputSchema - JSON Schema describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tanalyze(\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): AnalysisResult {\n\t\tconst exclude = options?.excludeTemplateExpression === true;\n\n\t\tswitch (this.state.kind) {\n\t\t\tcase \"array\": {\n\t\t\t\tconst { elements } = this.state;\n\n\t\t\t\tif (exclude) {\n\t\t\t\t\t// When excludeTemplateExpression is enabled, filter out elements\n\t\t\t\t\t// that are string templates containing Handlebars expressions.\n\t\t\t\t\tconst kept = elements.filter(\n\t\t\t\t\t\t(el) => !isCompiledTemplateWithExpression(el),\n\t\t\t\t\t);\n\t\t\t\t\treturn aggregateArrayAnalysis(kept.length, (index) => {\n\t\t\t\t\t\tconst element = kept[index];\n\t\t\t\t\t\tif (!element)\n\t\t\t\t\t\t\tthrow new Error(`unreachable: missing element at index ${index}`);\n\t\t\t\t\t\treturn element.analyze(inputSchema, options);\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\treturn aggregateArrayAnalysis(elements.length, (index) => {\n\t\t\t\t\tconst element = elements[index];\n\t\t\t\t\tif (!element)\n\t\t\t\t\t\tthrow new Error(`unreachable: missing element at index ${index}`);\n\t\t\t\t\treturn element.analyze(inputSchema, options);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"object\": {\n\t\t\t\tconst { children } = this.state;\n\t\t\t\tconst coerceSchema = options?.coerceSchema;\n\n\t\t\t\t// When excludeTemplateExpression is enabled, filter out keys whose\n\t\t\t\t// compiled children are string templates with Handlebars expressions.\n\t\t\t\tconst keys = exclude\n\t\t\t\t\t? Object.keys(children).filter((key) => {\n\t\t\t\t\t\t\tconst child = children[key];\n\t\t\t\t\t\t\treturn !child || !isCompiledTemplateWithExpression(child);\n\t\t\t\t\t\t})\n\t\t\t\t\t: Object.keys(children);\n\n\t\t\t\treturn aggregateObjectAnalysis(keys, (key) => {\n\t\t\t\t\tconst child = children[key];\n\t\t\t\t\tif (!child) throw new Error(`unreachable: missing child \"${key}\"`);\n\t\t\t\t\tconst childCoerceSchema = resolveChildCoerceSchema(coerceSchema, key);\n\t\t\t\t\treturn child.analyze(inputSchema, {\n\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\tcoerceSchema: childCoerceSchema,\n\t\t\t\t\t\texcludeTemplateExpression: options?.excludeTemplateExpression,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"literal\":\n\t\t\t\treturn {\n\t\t\t\t\tvalid: true,\n\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\toutputSchema: inferPrimitiveSchema(this.state.value),\n\t\t\t\t};\n\n\t\t\tcase \"template\":\n\t\t\t\treturn analyzeFromAst(this.state.ast, this.state.source, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.options.helpers,\n\t\t\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\t\t});\n\t\t}\n\t}\n\n\t// ─── Validation ──────────────────────────────────────────────────────\n\n\t/**\n\t * Validates the 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 inputSchema - JSON Schema describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tvalidate(\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(inputSchema, options);\n\t\treturn {\n\t\t\tvalid: analysis.valid,\n\t\t\tdiagnostics: analysis.diagnostics,\n\t\t};\n\t}\n\n\t// ─── Execution ───────────────────────────────────────────────────────\n\n\t/**\n\t * Executes this template with the provided data.\n\t *\n\t * The return type depends on the template structure:\n\t * - Single expression `{{expr}}` → raw value (number, boolean, object…)\n\t * - Mixed template or with blocks → `string`\n\t * - Primitive literal → the value as-is\n\t * - Object template → object with resolved values\n\t * - Array template → array with resolved values\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 data - The context data for rendering\n\t * @param options - Execution options (schema, identifierData, coerceSchema, etc.)\n\t * @returns The execution result\n\t */\n\texecute(data: TemplateData, options?: ExecuteOptions): unknown {\n\t\tswitch (this.state.kind) {\n\t\t\tcase \"array\": {\n\t\t\t\tconst { elements } = this.state;\n\t\t\t\tconst result: unknown[] = [];\n\t\t\t\tfor (const element of elements) {\n\t\t\t\t\tresult.push(element.execute(data, options));\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tcase \"object\": {\n\t\t\t\tconst { children } = this.state;\n\t\t\t\tconst coerceSchema = options?.coerceSchema;\n\t\t\t\tconst result: Record<string, unknown> = {};\n\t\t\t\tfor (const [key, child] of Object.entries(children)) {\n\t\t\t\t\tconst childCoerceSchema = resolveChildCoerceSchema(coerceSchema, key);\n\t\t\t\t\tresult[key] = child.execute(data, {\n\t\t\t\t\t\t...options,\n\t\t\t\t\t\tcoerceSchema: childCoerceSchema,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tcase \"literal\":\n\t\t\t\treturn this.state.value;\n\n\t\t\tcase \"template\": {\n\t\t\t\t// Pre-execution static validation if a schema is provided\n\t\t\t\tif (options?.schema) {\n\t\t\t\t\tconst analysis = this.analyze(options.schema, {\n\t\t\t\t\t\tidentifierSchemas: options.identifierSchemas,\n\t\t\t\t\t\tcoerceSchema: options.coerceSchema,\n\t\t\t\t\t});\n\t\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn executeFromAst(\n\t\t\t\t\tthis.state.ast,\n\t\t\t\t\tthis.state.source,\n\t\t\t\t\tdata,\n\t\t\t\t\tthis.buildExecutorContext(options),\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Combined Shortcuts ──────────────────────────────────────────────\n\n\t/**\n\t * Analyzes and executes the template in a single call.\n\t *\n\t * Returns both the analysis result and the executed value.\n\t * If analysis fails, `value` is `undefined`.\n\t *\n\t * @param inputSchema - JSON Schema describing the available variables\n\t * @param data - The context data for rendering\n\t * @param options - Additional options (identifierSchemas, identifierData, coerceSchema)\n\t * @returns `{ analysis, value }`\n\t */\n\tanalyzeAndExecute(\n\t\tinputSchema: JSONSchema7 = {},\n\t\tdata: TemplateData,\n\t\toptions?: {\n\t\t\tidentifierSchemas?: Record<number, JSONSchema7>;\n\t\t\tidentifierData?: Record<number, Record<string, unknown>>;\n\t\t\tcoerceSchema?: JSONSchema7;\n\t\t},\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\tswitch (this.state.kind) {\n\t\t\tcase \"array\": {\n\t\t\t\tconst { elements } = this.state;\n\t\t\t\treturn aggregateArrayAnalysisAndExecution(elements.length, (index) => {\n\t\t\t\t\tconst element = elements[index];\n\t\t\t\t\tif (!element)\n\t\t\t\t\t\tthrow new Error(`unreachable: missing element at index ${index}`);\n\t\t\t\t\treturn element.analyzeAndExecute(inputSchema, data, options);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tcase \"object\": {\n\t\t\t\tconst { children } = this.state;\n\t\t\t\tconst coerceSchema = options?.coerceSchema;\n\t\t\t\treturn aggregateObjectAnalysisAndExecution(\n\t\t\t\t\tObject.keys(children),\n\t\t\t\t\t(key) => {\n\t\t\t\t\t\tconst child = children[key];\n\t\t\t\t\t\tif (!child) throw new Error(`unreachable: missing child \"${key}\"`);\n\t\t\t\t\t\tconst childCoerceSchema = resolveChildCoerceSchema(\n\t\t\t\t\t\t\tcoerceSchema,\n\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn child.analyzeAndExecute(inputSchema, data, {\n\t\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\t\t\tcoerceSchema: childCoerceSchema,\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\tcase \"literal\":\n\t\t\t\treturn {\n\t\t\t\t\tanalysis: {\n\t\t\t\t\t\tvalid: true,\n\t\t\t\t\t\tdiagnostics: [],\n\t\t\t\t\t\toutputSchema: inferPrimitiveSchema(this.state.value),\n\t\t\t\t\t},\n\t\t\t\t\tvalue: this.state.value,\n\t\t\t\t};\n\n\t\t\tcase \"template\": {\n\t\t\t\tconst analysis = this.analyze(inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\t\t});\n\n\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\treturn { analysis, value: undefined };\n\t\t\t\t}\n\n\t\t\t\tconst value = executeFromAst(\n\t\t\t\t\tthis.state.ast,\n\t\t\t\t\tthis.state.source,\n\t\t\t\t\tdata,\n\t\t\t\t\tthis.buildExecutorContext({\n\t\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\t\t\treturn { analysis, value };\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Internals ───────────────────────────────────────────────────────\n\n\t/**\n\t * Builds the execution context for `executeFromAst`.\n\t *\n\t * Uses lazy Handlebars compilation: the template is only compiled\n\t * on the first call that needs it (not for single expressions).\n\t */\n\tprivate buildExecutorContext(options?: ExecuteOptions): ExecutorContext {\n\t\treturn {\n\t\t\tidentifierData: options?.identifierData,\n\t\t\tcompiledTemplate: this.getOrCompileHbs(),\n\t\t\thbs: this.options.hbs,\n\t\t\tcompilationCache: this.options.compilationCache,\n\t\t\tcoerceSchema: options?.coerceSchema,\n\t\t\thelpers: this.options.helpers,\n\t\t};\n\t}\n\n\t/**\n\t * Lazily compiles the Handlebars template and caches it.\n\t *\n\t * Compilation happens only once — subsequent calls return the\n\t * in-memory compiled template.\n\t *\n\t * Precondition: this method is only called from \"template\" mode.\n\t */\n\tprivate getOrCompileHbs(): HandlebarsTemplateDelegate {\n\t\tif (!this.hbsCompiled) {\n\t\t\t// In \"template\" mode, `this.template` returns the source string\n\t\t\tthis.hbsCompiled = this.options.hbs.compile(this.template, {\n\t\t\t\tnoEscape: true,\n\t\t\t\tstrict: false,\n\t\t\t});\n\t\t}\n\t\treturn this.hbsCompiled;\n\t}\n}\n\n// ─── Internal Helpers ────────────────────────────────────────────────────────\n\n/**\n * Determines whether a `CompiledTemplate` represents a string template\n * containing Handlebars expressions (`{{…}}`).\n *\n * Used by `excludeTemplateExpression` filtering to skip dynamic entries\n * in object and array modes.\n */\nfunction isCompiledTemplateWithExpression(ct: CompiledTemplate): boolean {\n\t// Only \"template\" kind can contain expressions. Literals, objects,\n\t// and arrays are never excluded at the entry level — objects and\n\t// arrays are recursively filtered by the analysis method itself.\n\treturn ct.template !== \"\" && shouldExcludeEntry(ct.template);\n}\n"],"names":["analyzeFromAst","resolveChildCoerceSchema","shouldExcludeEntry","TemplateAnalysisError","executeFromAst","inferPrimitiveSchema","aggregateArrayAnalysis","aggregateArrayAnalysisAndExecution","aggregateObjectAnalysis","aggregateObjectAnalysisAndExecution","CompiledTemplate","ast","state","kind","template","source","fromTemplate","options","fromLiteral","value","fromObject","children","fromArray","elements","analyze","inputSchema","exclude","excludeTemplateExpression","kept","filter","el","isCompiledTemplateWithExpression","length","index","element","Error","coerceSchema","keys","Object","key","child","childCoerceSchema","identifierSchemas","valid","diagnostics","outputSchema","helpers","validate","analysis","execute","data","result","push","entries","schema","buildExecutorContext","analyzeAndExecute","identifierData","undefined","compiledTemplate","getOrCompileHbs","hbs","compilationCache","hbsCompiled","compile","noEscape","strict","ct"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import Handlebars from "handlebars";
2
2
  import type { JSONSchema7 } from "json-schema";
3
- import type { TemplateInput } from "./types.js";
3
+ import type { HelperDefinition, TemplateInput } from "./types.js";
4
4
  import { LRUCache } from "./utils.js";
5
5
  /** Optional context for execution (used by Typebars/CompiledTemplate) */
6
6
  export interface ExecutorContext {
@@ -18,6 +18,8 @@ export interface ExecutorContext {
18
18
  * to match the declared type instead of using auto-detection.
19
19
  */
20
20
  coerceSchema?: JSONSchema7;
21
+ /** Registered helpers (for direct execution of special helpers like `collect`) */
22
+ helpers?: Map<string, HelperDefinition>;
21
23
  }
22
24
  /**
23
25
  * Executes a template with the provided data and returns the result.
@@ -1,2 +1,2 @@
1
- import Handlebars from"handlebars";import{dispatchExecute}from"./dispatch.js";import{TemplateRuntimeError}from"./errors.js";import{canUseFastPath,coerceLiteral,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isRootPathTraversal,isRootSegments,isSingleExpression,isThisExpression,parse,ROOT_TOKEN}from"./parser.js";import{LRUCache}from"./utils.js";const globalCompilationCache=new LRUCache(128);export function execute(template,data,identifierData){return dispatchExecute(template,undefined,tpl=>{const ast=parse(tpl);return executeFromAst(ast,tpl,data,{identifierData})},child=>execute(child,data,identifierData))}export function executeFromAst(ast,template,data,ctx){const identifierData=ctx?.identifierData;if(isSingleExpression(ast)){const stmt=ast.body[0];if(stmt.params.length===0&&!stmt.hash){return resolveExpression(stmt.path,data,identifierData)}}const singleExpr=getEffectivelySingleExpression(ast);if(singleExpr&&singleExpr.params.length===0&&!singleExpr.hash){return resolveExpression(singleExpr.path,data,identifierData)}if(singleExpr&&(singleExpr.params.length>0||singleExpr.hash)){const merged=mergeDataWithIdentifiers(data,identifierData);const raw=renderWithHandlebars(template,merged,ctx);return coerceValue(raw,ctx?.coerceSchema)}if(canUseFastPath(ast)&&ast.body.length>1){return executeFastPath(ast,data,identifierData)}const singleBlock=getEffectivelySingleBlock(ast);if(singleBlock){const merged=mergeDataWithIdentifiers(data,identifierData);const raw=renderWithHandlebars(template,merged,ctx);return coerceValue(raw,ctx?.coerceSchema)}const merged=mergeDataWithIdentifiers(data,identifierData);const raw=renderWithHandlebars(template,merged,ctx);const effective=getEffectiveBody(ast);const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){return coerceValue(raw,ctx?.coerceSchema)}return raw}function coerceValue(raw,coerceSchema){if(coerceSchema){const targetType=coerceSchema.type;if(typeof targetType==="string"){if(targetType==="string")return raw;if(targetType==="number"||targetType==="integer")return Number(raw.trim());if(targetType==="boolean")return raw.trim()==="true";if(targetType==="null")return null}}return coerceLiteral(raw)}function executeFastPath(ast,data,identifierData){let result="";for(const stmt of ast.body){if(stmt.type==="ContentStatement"){result+=stmt.value}else if(stmt.type==="MustacheStatement"){const value=resolveExpression(stmt.path,data,identifierData);if(value!=null){result+=String(value)}}}return result}function resolveExpression(expr,data,identifierData){if(isThisExpression(expr)){return data}if(expr.type==="StringLiteral")return expr.value;if(expr.type==="NumberLiteral")return expr.value;if(expr.type==="BooleanLiteral")return expr.value;if(expr.type==="NullLiteral")return null;if(expr.type==="UndefinedLiteral")return undefined;const segments=extractPathSegments(expr);if(segments.length===0){throw new TemplateRuntimeError(`Cannot resolve expression of type "${expr.type}"`)}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(isRootPathTraversal(cleanSegments)){return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null&&identifierData){const source=identifierData[identifier];return source??undefined}if(identifier!==null){return undefined}return data}if(identifier!==null&&identifierData){const source=identifierData[identifier];if(source){return resolveDataPath(source,cleanSegments)}return undefined}if(identifier!==null&&!identifierData){return undefined}return resolveDataPath(data,cleanSegments)}export function resolveDataPath(data,segments){let current=data;for(const segment of segments){if(current===null||current===undefined){return undefined}if(typeof current!=="object"){return undefined}current=current[segment]}return current}function mergeDataWithIdentifiers(data,identifierData){const base=data!==null&&typeof data==="object"&&!Array.isArray(data)?data:{};const merged={...base,[ROOT_TOKEN]:data};if(!identifierData)return merged;for(const[id,idData]of Object.entries(identifierData)){merged[`${ROOT_TOKEN}:${id}`]=idData;for(const[key,value]of Object.entries(idData)){merged[`${key}:${id}`]=value}}return merged}function renderWithHandlebars(template,data,ctx){try{if(ctx?.compiledTemplate){return ctx.compiledTemplate(data)}const cache=ctx?.compilationCache??globalCompilationCache;const hbs=ctx?.hbs??Handlebars;let compiled=cache.get(template);if(!compiled){compiled=hbs.compile(template,{noEscape:true,strict:false});cache.set(template,compiled)}return compiled(data)}catch(error){const message=error instanceof Error?error.message:String(error);throw new TemplateRuntimeError(message)}}export function clearCompilationCache(){globalCompilationCache.clear()}
1
+ import Handlebars from"handlebars";import{dispatchExecute}from"./dispatch.js";import{TemplateRuntimeError}from"./errors.js";import{CollectionHelpers}from"./helpers/collection-helpers.js";import{canUseFastPath,coerceLiteral,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isRootPathTraversal,isRootSegments,isSingleExpression,isThisExpression,parse,ROOT_TOKEN}from"./parser.js";import{LRUCache}from"./utils.js";const globalCompilationCache=new LRUCache(128);export function execute(template,data,identifierData){return dispatchExecute(template,undefined,tpl=>{const ast=parse(tpl);return executeFromAst(ast,tpl,data,{identifierData})},child=>execute(child,data,identifierData))}export function executeFromAst(ast,template,data,ctx){const identifierData=ctx?.identifierData;if(isSingleExpression(ast)){const stmt=ast.body[0];if(stmt.params.length===0&&!stmt.hash){return resolveExpression(stmt.path,data,identifierData,ctx?.helpers)}}const singleExpr=getEffectivelySingleExpression(ast);if(singleExpr&&singleExpr.params.length===0&&!singleExpr.hash){return resolveExpression(singleExpr.path,data,identifierData,ctx?.helpers)}if(singleExpr&&(singleExpr.params.length>0||singleExpr.hash)){const directResult=tryDirectHelperExecution(singleExpr,data,ctx);if(directResult!==undefined){return directResult.value}const merged=mergeDataWithIdentifiers(data,identifierData);const raw=renderWithHandlebars(template,merged,ctx);return coerceValue(raw,ctx?.coerceSchema)}if(canUseFastPath(ast)&&ast.body.length>1){return executeFastPath(ast,data,identifierData)}const singleBlock=getEffectivelySingleBlock(ast);if(singleBlock){const merged=mergeDataWithIdentifiers(data,identifierData);const raw=renderWithHandlebars(template,merged,ctx);return coerceValue(raw,ctx?.coerceSchema)}const merged=mergeDataWithIdentifiers(data,identifierData);const raw=renderWithHandlebars(template,merged,ctx);const effective=getEffectiveBody(ast);const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){return coerceValue(raw,ctx?.coerceSchema)}return raw}function coerceValue(raw,coerceSchema){if(coerceSchema){const targetType=coerceSchema.type;if(typeof targetType==="string"){if(targetType==="string")return raw;if(targetType==="number"||targetType==="integer")return Number(raw.trim());if(targetType==="boolean")return raw.trim()==="true";if(targetType==="null")return null}}return coerceLiteral(raw)}function executeFastPath(ast,data,identifierData){let result="";for(const stmt of ast.body){if(stmt.type==="ContentStatement"){result+=stmt.value}else if(stmt.type==="MustacheStatement"){const value=resolveExpression(stmt.path,data,identifierData);if(value!=null){result+=String(value)}}}return result}function resolveExpression(expr,data,identifierData,helpers){if(isThisExpression(expr)){return data}if(expr.type==="StringLiteral")return expr.value;if(expr.type==="NumberLiteral")return expr.value;if(expr.type==="BooleanLiteral")return expr.value;if(expr.type==="NullLiteral")return null;if(expr.type==="UndefinedLiteral")return undefined;if(expr.type==="SubExpression"){const subExpr=expr;if(subExpr.path.type==="PathExpression"){const helperName=subExpr.path.original;const helper=helpers?.get(helperName);if(helper){const isCollect=helperName===CollectionHelpers.COLLECT_HELPER_NAME;const resolvedArgs=[];for(let i=0;i<subExpr.params.length;i++){const param=subExpr.params[i];if(isCollect&&i===1&&param.type==="StringLiteral"){resolvedArgs.push(param.value)}else{resolvedArgs.push(resolveExpression(param,data,identifierData,helpers))}}return helper.fn(...resolvedArgs)}}return undefined}const segments=extractPathSegments(expr);if(segments.length===0){throw new TemplateRuntimeError(`Cannot resolve expression of type "${expr.type}"`)}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(isRootPathTraversal(cleanSegments)){return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null&&identifierData){const source=identifierData[identifier];return source??undefined}if(identifier!==null){return undefined}return data}if(identifier!==null&&identifierData){const source=identifierData[identifier];if(source){return resolveDataPath(source,cleanSegments)}return undefined}if(identifier!==null&&!identifierData){return undefined}return resolveDataPath(data,cleanSegments)}export function resolveDataPath(data,segments){let current=data;for(const segment of segments){if(current===null||current===undefined){return undefined}if(typeof current!=="object"){return undefined}current=current[segment]}return current}function mergeDataWithIdentifiers(data,identifierData){const base=data!==null&&typeof data==="object"&&!Array.isArray(data)?data:{};const merged={...base,[ROOT_TOKEN]:data};if(!identifierData)return merged;for(const[id,idData]of Object.entries(identifierData)){merged[`${ROOT_TOKEN}:${id}`]=idData;for(const[key,value]of Object.entries(idData)){merged[`${key}:${id}`]=value}}return merged}function renderWithHandlebars(template,data,ctx){try{if(ctx?.compiledTemplate){return ctx.compiledTemplate(data)}const cache=ctx?.compilationCache??globalCompilationCache;const hbs=ctx?.hbs??Handlebars;let compiled=cache.get(template);if(!compiled){compiled=hbs.compile(template,{noEscape:true,strict:false});cache.set(template,compiled)}return compiled(data)}catch(error){const message=error instanceof Error?error.message:String(error);throw new TemplateRuntimeError(message)}}export function clearCompilationCache(){globalCompilationCache.clear()}const DIRECT_EXECUTION_HELPERS=new Set([CollectionHelpers.COLLECT_HELPER_NAME]);function tryDirectHelperExecution(stmt,data,ctx){if(stmt.path.type!=="PathExpression")return undefined;const helperName=stmt.path.original;if(!DIRECT_EXECUTION_HELPERS.has(helperName))return undefined;const helper=ctx?.helpers?.get(helperName);if(!helper)return undefined;const isCollect=helperName===CollectionHelpers.COLLECT_HELPER_NAME;const resolvedArgs=[];for(let i=0;i<stmt.params.length;i++){const param=stmt.params[i];if(isCollect&&i===1){if(param.type==="StringLiteral"){resolvedArgs.push(param.value)}else{resolvedArgs.push(resolveExpression(param,data,ctx?.identifierData,ctx?.helpers))}}else{resolvedArgs.push(resolveExpression(param,data,ctx?.identifierData,ctx?.helpers))}}const value=helper.fn(...resolvedArgs);return{value}}
2
2
  //# sourceMappingURL=executor.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/executor.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { dispatchExecute } from \"./dispatch.ts\";\nimport { TemplateRuntimeError } from \"./errors.ts\";\nimport {\n\tcanUseFastPath,\n\tcoerceLiteral,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisRootPathTraversal,\n\tisRootSegments,\n\tisSingleExpression,\n\tisThisExpression,\n\tparse,\n\tROOT_TOKEN,\n} from \"./parser.ts\";\nimport type { TemplateInput } from \"./types.ts\";\nimport { LRUCache } from \"./utils.ts\";\n\n// ─── Template Executor ───────────────────────────────────────────────────────\n// Executes a Handlebars template with real data.\n//\n// Four execution modes (from fastest to most general):\n//\n// 1. **Single expression** (`{{value}}` or ` {{value}} `) → returns the raw\n// value without converting to string. This preserves the original type\n// (number, boolean, object, array, null).\n//\n// 2. **Fast-path** (text + simple expressions, no blocks or helpers) →\n// direct concatenation without going through Handlebars.compile(). Up to\n// 10-100x faster for simple templates like `Hello {{name}}`.\n//\n// 3. **Single block** (`{{#if x}}10{{else}}20{{/if}}` possibly surrounded\n// by whitespace) → rendered via Handlebars then intelligently coerced\n// (detecting number, boolean, null literals).\n//\n// 4. **Mixed template** (text + multiple blocks, helpers, …) →\n// delegates to Handlebars which always produces a string.\n//\n// ─── Caching ─────────────────────────────────────────────────────────────────\n// Handlebars-compiled templates are cached in an LRU cache to avoid costly\n// recompilation on repeated calls.\n//\n// Two cache levels:\n// - **Global cache** (module-level) for standalone `execute()` calls\n// - **Instance cache** for `Typebars` (passed via `ExecutorContext`)\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows resolving a variable from a specific data\n// source, identified by an integer N. The optional `identifierData` parameter\n// provides a mapping `{ [id]: { key: value, ... } }`.\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Optional context for execution (used by Typebars/CompiledTemplate) */\nexport interface ExecutorContext {\n\t/** Data by identifier `{ [id]: { key: value } }` */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n\t/** Pre-compiled Handlebars template (for CompiledTemplate) */\n\tcompiledTemplate?: HandlebarsTemplateDelegate;\n\t/** Isolated Handlebars environment (for custom helpers) */\n\thbs?: typeof Handlebars;\n\t/** Compilation cache shared by the engine */\n\tcompilationCache?: LRUCache<string, HandlebarsTemplateDelegate>;\n\t/**\n\t * Explicit coercion schema for the output value.\n\t * When set with a primitive type, the execution result will be coerced\n\t * to match the declared type instead of using auto-detection.\n\t */\n\tcoerceSchema?: JSONSchema7;\n}\n\n// ─── Global Compilation Cache ────────────────────────────────────────────────\n// Used by the standalone `execute()` function and `renderWithHandlebars()`.\n// `Typebars` instances use their own cache.\nconst globalCompilationCache = new LRUCache<string, HandlebarsTemplateDelegate>(\n\t128,\n);\n\n// ─── Public API (backward-compatible) ────────────────────────────────────────\n\n/**\n * Executes a template with the provided data and returns the result.\n *\n * The return type depends on the template structure:\n * - Single expression `{{expr}}` → raw value (any)\n * - Single block → coerced value (number, boolean, null, or string)\n * - Mixed template → `string`\n *\n * @param template - The template string\n * @param data - The main context data\n * @param identifierData - (optional) Data by identifier `{ [id]: { key: value } }`\n */\nexport function execute(\n\ttemplate: TemplateInput,\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): unknown {\n\treturn dispatchExecute(\n\t\ttemplate,\n\t\tundefined,\n\t\t// String handler — parse and execute the AST\n\t\t(tpl) => {\n\t\t\tconst ast = parse(tpl);\n\t\t\treturn executeFromAst(ast, tpl, data, { identifierData });\n\t\t},\n\t\t// Recursive handler — re-enter execute() for child elements\n\t\t(child) => execute(child, data, identifierData),\n\t);\n}\n\n// ─── Internal API (for Typebars / CompiledTemplate) ──────────────────────\n\n/**\n * Executes a template from an already-parsed AST.\n *\n * This function is the core of execution. It is used by:\n * - `execute()` (backward-compatible wrapper)\n * - `CompiledTemplate.execute()` (with pre-parsed AST and cache)\n * - `Typebars.execute()` (with cache and helpers)\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for Handlebars compilation if needed)\n * @param data - The main context data\n * @param ctx - Optional execution context\n */\nexport function executeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tdata: unknown,\n\tctx?: ExecutorContext,\n): unknown {\n\tconst identifierData = ctx?.identifierData;\n\n\t// ── Case 1: strict single expression `{{expr}}` ──────────────────────\n\t// Exclude helper calls (params > 0 or hash) because they must go\n\t// through Handlebars for correct execution.\n\tif (isSingleExpression(ast)) {\n\t\tconst stmt = ast.body[0] as hbs.AST.MustacheStatement;\n\t\tif (stmt.params.length === 0 && !stmt.hash) {\n\t\t\treturn resolveExpression(stmt.path, data, identifierData);\n\t\t}\n\t}\n\n\t// ── Case 1b: single expression with surrounding whitespace ` {{expr}} `\n\tconst singleExpr = getEffectivelySingleExpression(ast);\n\tif (singleExpr && singleExpr.params.length === 0 && !singleExpr.hash) {\n\t\treturn resolveExpression(singleExpr.path, data, identifierData);\n\t}\n\n\t// ── Case 1c: single expression with helper (params > 0) ──────────────\n\t// E.g. `{{ divide accountIds.length 10 }}` or `{{ math a \"+\" b }}`\n\t// The helper returns a typed value but Handlebars converts it to a\n\t// string. We render via Handlebars then coerce the result to recover\n\t// the original type (number, boolean, null).\n\tif (singleExpr && (singleExpr.params.length > 0 || singleExpr.hash)) {\n\t\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\t\tconst raw = renderWithHandlebars(template, merged, ctx);\n\t\treturn coerceValue(raw, ctx?.coerceSchema);\n\t}\n\n\t// ── Case 2: fast-path for simple templates (text + expressions) ──────\n\t// If the template only contains text and simple expressions (no blocks,\n\t// no helpers with parameters), we can do direct concatenation without\n\t// going through Handlebars.compile().\n\tif (canUseFastPath(ast) && ast.body.length > 1) {\n\t\treturn executeFastPath(ast, data, identifierData);\n\t}\n\n\t// ── Case 3: single block (possibly surrounded by whitespace) ─────────\n\t// Render via Handlebars then attempt to coerce the result to the\n\t// detected literal type (number, boolean, null).\n\tconst singleBlock = getEffectivelySingleBlock(ast);\n\tif (singleBlock) {\n\t\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\t\tconst raw = renderWithHandlebars(template, merged, ctx);\n\t\treturn coerceValue(raw, ctx?.coerceSchema);\n\t}\n\n\t// ── Case 4: mixed template ───────────────────────────────────────────\n\t// For purely static templates (only ContentStatements), coerce the\n\t// result to match the coerceSchema type or auto-detect the literal type.\n\t// For truly mixed templates (text + blocks + expressions), return string.\n\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\tconst raw = renderWithHandlebars(template, merged, ctx);\n\n\tconst effective = getEffectiveBody(ast);\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\treturn coerceValue(raw, ctx?.coerceSchema);\n\t}\n\n\treturn raw;\n}\n\n// ─── Value Coercion ──────────────────────────────────────────────────────────\n// Coerces a raw string from Handlebars rendering based on an optional\n// coerceSchema. When no schema is provided, falls back to auto-detection\n// via `coerceLiteral`.\n\n/**\n * Coerces a raw string value based on an optional coercion schema.\n *\n * - If `coerceSchema` declares a primitive type (`string`, `number`,\n * `integer`, `boolean`, `null`), the value is cast to that type.\n * - Otherwise, falls back to `coerceLiteral` (auto-detection).\n *\n * @param raw - The raw string from Handlebars rendering\n * @param coerceSchema - Optional schema declaring the desired output type\n * @returns The coerced value\n */\nfunction coerceValue(raw: string, coerceSchema?: JSONSchema7): unknown {\n\tif (coerceSchema) {\n\t\tconst targetType = coerceSchema.type;\n\t\tif (typeof targetType === \"string\") {\n\t\t\tif (targetType === \"string\") return raw;\n\t\t\tif (targetType === \"number\" || targetType === \"integer\")\n\t\t\t\treturn Number(raw.trim());\n\t\t\tif (targetType === \"boolean\") return raw.trim() === \"true\";\n\t\t\tif (targetType === \"null\") return null;\n\t\t}\n\t}\n\t// No coerceSchema or non-primitive type → auto-detect\n\treturn coerceLiteral(raw);\n}\n\n// ─── Fast-Path Execution ─────────────────────────────────────────────────────\n// For templates consisting only of text and simple expressions (no blocks,\n// no helpers), we bypass Handlebars and do direct concatenation.\n// This is significantly faster.\n\n/**\n * Executes a template via the fast-path (direct concatenation).\n *\n * Precondition: `canUseFastPath(ast)` must return `true`.\n *\n * @param ast - The template AST (only ContentStatement and simple MustacheStatement)\n * @param data - The context data\n * @param identifierData - Data by identifier (optional)\n * @returns The resulting string\n */\nfunction executeFastPath(\n\tast: hbs.AST.Program,\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): string {\n\tlet result = \"\";\n\n\tfor (const stmt of ast.body) {\n\t\tif (stmt.type === \"ContentStatement\") {\n\t\t\tresult += (stmt as hbs.AST.ContentStatement).value;\n\t\t} else if (stmt.type === \"MustacheStatement\") {\n\t\t\tconst value = resolveExpression(\n\t\t\t\t(stmt as hbs.AST.MustacheStatement).path,\n\t\t\t\tdata,\n\t\t\t\tidentifierData,\n\t\t\t);\n\t\t\t// Handlebars converts values to strings for rendering.\n\t\t\t// We replicate this behavior: null/undefined → \"\", otherwise String(value).\n\t\t\tif (value != null) {\n\t\t\t\tresult += String(value);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// ─── Direct Expression Resolution ────────────────────────────────────────────\n// Used for single-expression templates and the fast-path, to return the raw\n// value without going through the Handlebars engine.\n\n/**\n * Resolves an AST expression by following the path through the data.\n *\n * If the expression contains an identifier (e.g. `meetingId:1`), resolution\n * is performed in `identifierData[1]` instead of `data`.\n *\n * @param expr - The AST expression to resolve\n * @param data - The main data context\n * @param identifierData - Data by identifier (optional)\n * @returns The raw value pointed to by the expression\n */\nfunction resolveExpression(\n\texpr: hbs.AST.Expression,\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): unknown {\n\t// this / . → return the entire context\n\tif (isThisExpression(expr)) {\n\t\treturn data;\n\t}\n\n\t// Literals\n\tif (expr.type === \"StringLiteral\")\n\t\treturn (expr as hbs.AST.StringLiteral).value;\n\tif (expr.type === \"NumberLiteral\")\n\t\treturn (expr as hbs.AST.NumberLiteral).value;\n\tif (expr.type === \"BooleanLiteral\")\n\t\treturn (expr as hbs.AST.BooleanLiteral).value;\n\tif (expr.type === \"NullLiteral\") return null;\n\tif (expr.type === \"UndefinedLiteral\") return undefined;\n\n\t// PathExpression — navigate through segments in the data object\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\tthrow new TemplateRuntimeError(\n\t\t\t`Cannot resolve expression of type \"${expr.type}\"`,\n\t\t);\n\t}\n\n\t// Extract the potential identifier from the last segment BEFORE\n\t// checking for $root, so that both {{$root}} and {{$root:N}} are\n\t// handled uniformly.\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\t// $root path traversal ($root.name) — not supported, return undefined\n\t// (the analyzer already rejects it with a diagnostic).\n\tif (isRootPathTraversal(cleanSegments)) {\n\t\treturn undefined;\n\t}\n\n\t// $root → return the entire data context (or identifier data)\n\tif (isRootSegments(cleanSegments)) {\n\t\tif (identifier !== null && identifierData) {\n\t\t\tconst source = identifierData[identifier];\n\t\t\treturn source ?? undefined;\n\t\t}\n\t\tif (identifier !== null) {\n\t\t\t// Template uses an identifier but no identifierData was provided\n\t\t\treturn undefined;\n\t\t}\n\t\treturn data;\n\t}\n\n\tif (identifier !== null && identifierData) {\n\t\tconst source = identifierData[identifier];\n\t\tif (source) {\n\t\t\treturn resolveDataPath(source, cleanSegments);\n\t\t}\n\t\t// Source does not exist → undefined (like a missing key)\n\t\treturn undefined;\n\t}\n\n\tif (identifier !== null && !identifierData) {\n\t\t// Template uses an identifier but no identifierData was provided\n\t\treturn undefined;\n\t}\n\n\treturn resolveDataPath(data, cleanSegments);\n}\n\n/**\n * Navigates through a data object by following a path of segments.\n *\n * @param data - The data object\n * @param segments - The path segments (e.g. `[\"user\", \"address\", \"city\"]`)\n * @returns The value at the end of the path, or `undefined` if an\n * intermediate segment is null/undefined\n */\nexport function resolveDataPath(data: unknown, segments: string[]): unknown {\n\tlet current: unknown = data;\n\n\tfor (const segment of segments) {\n\t\tif (current === null || current === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tcurrent = (current as Record<string, unknown>)[segment];\n\t}\n\n\treturn current;\n}\n\n// ─── Data Merging ────────────────────────────────────────────────────────────\n// For Handlebars rendering (mixed templates / blocks), we cannot intercept\n// resolution on a per-expression basis. Instead, we merge identifier data\n// into the main object using the format `\"key:N\"`.\n//\n// Handlebars parses `{{meetingId:1}}` as a PathExpression with a single\n// segment `\"meetingId:1\"`, so it looks up the key `\"meetingId:1\"` in the\n// data object — which matches our flattened format exactly.\n\n/**\n * Merges the main data with identifier data.\n *\n * @param data - Main data\n * @param identifierData - Data by identifier\n * @returns A merged object where identifier data appears as `\"key:N\"` keys\n *\n * @example\n * ```\n * mergeDataWithIdentifiers(\n * { name: \"Alice\" },\n * { 1: { meetingId: \"val1\" }, 2: { meetingId: \"val2\" } }\n * )\n * // → { name: \"Alice\", \"meetingId:1\": \"val1\", \"meetingId:2\": \"val2\" }\n * ```\n */\nfunction mergeDataWithIdentifiers(\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): Record<string, unknown> {\n\t// Always include $root so that Handlebars can resolve {{$root}} in\n\t// mixed templates and block helpers (where we delegate to Handlebars\n\t// instead of resolving expressions ourselves).\n\t// When data is a primitive (e.g. number passed with {{$root}}), we\n\t// wrap it into an object so Handlebars can still function.\n\tconst base: Record<string, unknown> =\n\t\tdata !== null && typeof data === \"object\" && !Array.isArray(data)\n\t\t\t? (data as Record<string, unknown>)\n\t\t\t: {};\n\tconst merged: Record<string, unknown> = { ...base, [ROOT_TOKEN]: data };\n\n\tif (!identifierData) return merged;\n\n\tfor (const [id, idData] of Object.entries(identifierData)) {\n\t\t// Add `$root:N` so Handlebars can resolve {{$root:N}} in mixed/block\n\t\t// templates (where we delegate to Handlebars instead of resolving\n\t\t// expressions ourselves). The value is the entire identifier data object.\n\t\tmerged[`${ROOT_TOKEN}:${id}`] = idData;\n\n\t\tfor (const [key, value] of Object.entries(idData)) {\n\t\t\tmerged[`${key}:${id}`] = value;\n\t\t}\n\t}\n\n\treturn merged;\n}\n\n// ─── Handlebars Rendering ────────────────────────────────────────────────────\n// For complex templates (blocks, helpers), we delegate to Handlebars.\n// Compilation is cached to avoid costly recompilations.\n\n/**\n * Compiles and executes a template via Handlebars.\n *\n * Uses a compilation cache (LRU) to avoid recompiling the same template\n * on repeated calls. The cache is either:\n * - The global cache (for the standalone `execute()` function)\n * - The instance cache provided via `ExecutorContext` (for `Typebars`)\n *\n * @param template - The template string\n * @param data - The context data\n * @param ctx - Optional execution context (cache, Handlebars env)\n * @returns Always a string\n */\nfunction renderWithHandlebars(\n\ttemplate: string,\n\tdata: Record<string, unknown>,\n\tctx?: ExecutorContext,\n): string {\n\ttry {\n\t\t// 1. Use the pre-compiled template if available (CompiledTemplate)\n\t\tif (ctx?.compiledTemplate) {\n\t\t\treturn ctx.compiledTemplate(data);\n\t\t}\n\n\t\t// 2. Look up in the cache (instance or global)\n\t\tconst cache = ctx?.compilationCache ?? globalCompilationCache;\n\t\tconst hbs = ctx?.hbs ?? Handlebars;\n\n\t\tlet compiled = cache.get(template);\n\t\tif (!compiled) {\n\t\t\tcompiled = hbs.compile(template, {\n\t\t\t\t// Disable HTML-escaping by default — this engine is not\n\t\t\t\t// HTML-specific, we want raw values.\n\t\t\t\tnoEscape: true,\n\t\t\t\t// Strict mode: throws if a path does not exist in the data.\n\t\t\t\tstrict: false,\n\t\t\t});\n\t\t\tcache.set(template, compiled);\n\t\t}\n\n\t\treturn compiled(data);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new TemplateRuntimeError(message);\n\t}\n}\n\n/**\n * Clears the global Handlebars compilation cache.\n * Useful for tests or to free memory.\n */\nexport function clearCompilationCache(): void {\n\tglobalCompilationCache.clear();\n}\n"],"names":["Handlebars","dispatchExecute","TemplateRuntimeError","canUseFastPath","coerceLiteral","extractExpressionIdentifier","extractPathSegments","getEffectiveBody","getEffectivelySingleBlock","getEffectivelySingleExpression","isRootPathTraversal","isRootSegments","isSingleExpression","isThisExpression","parse","ROOT_TOKEN","LRUCache","globalCompilationCache","execute","template","data","identifierData","undefined","tpl","ast","executeFromAst","child","ctx","stmt","body","params","length","hash","resolveExpression","path","singleExpr","merged","mergeDataWithIdentifiers","raw","renderWithHandlebars","coerceValue","coerceSchema","executeFastPath","singleBlock","effective","allContent","every","s","type","targetType","Number","trim","result","value","String","expr","segments","cleanSegments","identifier","source","resolveDataPath","current","segment","base","Array","isArray","id","idData","Object","entries","key","compiledTemplate","cache","compilationCache","hbs","compiled","get","compile","noEscape","strict","set","error","message","Error","clearCompilationCache","clear"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/executor.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { dispatchExecute } from \"./dispatch.ts\";\nimport { TemplateRuntimeError } from \"./errors.ts\";\nimport { CollectionHelpers } from \"./helpers/collection-helpers.ts\";\nimport {\n\tcanUseFastPath,\n\tcoerceLiteral,\n\textractExpressionIdentifier,\n\textractPathSegments,\n\tgetEffectiveBody,\n\tgetEffectivelySingleBlock,\n\tgetEffectivelySingleExpression,\n\tisRootPathTraversal,\n\tisRootSegments,\n\tisSingleExpression,\n\tisThisExpression,\n\tparse,\n\tROOT_TOKEN,\n} from \"./parser.ts\";\nimport type { HelperDefinition, TemplateInput } from \"./types.ts\";\nimport { LRUCache } from \"./utils.ts\";\n\n// ─── Template Executor ───────────────────────────────────────────────────────\n// Executes a Handlebars template with real data.\n//\n// Four execution modes (from fastest to most general):\n//\n// 1. **Single expression** (`{{value}}` or ` {{value}} `) → returns the raw\n// value without converting to string. This preserves the original type\n// (number, boolean, object, array, null).\n//\n// 2. **Fast-path** (text + simple expressions, no blocks or helpers) →\n// direct concatenation without going through Handlebars.compile(). Up to\n// 10-100x faster for simple templates like `Hello {{name}}`.\n//\n// 3. **Single block** (`{{#if x}}10{{else}}20{{/if}}` possibly surrounded\n// by whitespace) → rendered via Handlebars then intelligently coerced\n// (detecting number, boolean, null literals).\n//\n// 4. **Mixed template** (text + multiple blocks, helpers, …) →\n// delegates to Handlebars which always produces a string.\n//\n// ─── Caching ─────────────────────────────────────────────────────────────────\n// Handlebars-compiled templates are cached in an LRU cache to avoid costly\n// recompilation on repeated calls.\n//\n// Two cache levels:\n// - **Global cache** (module-level) for standalone `execute()` calls\n// - **Instance cache** for `Typebars` (passed via `ExecutorContext`)\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows resolving a variable from a specific data\n// source, identified by an integer N. The optional `identifierData` parameter\n// provides a mapping `{ [id]: { key: value, ... } }`.\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Optional context for execution (used by Typebars/CompiledTemplate) */\nexport interface ExecutorContext {\n\t/** Data by identifier `{ [id]: { key: value } }` */\n\tidentifierData?: Record<number, Record<string, unknown>>;\n\t/** Pre-compiled Handlebars template (for CompiledTemplate) */\n\tcompiledTemplate?: HandlebarsTemplateDelegate;\n\t/** Isolated Handlebars environment (for custom helpers) */\n\thbs?: typeof Handlebars;\n\t/** Compilation cache shared by the engine */\n\tcompilationCache?: LRUCache<string, HandlebarsTemplateDelegate>;\n\t/**\n\t * Explicit coercion schema for the output value.\n\t * When set with a primitive type, the execution result will be coerced\n\t * to match the declared type instead of using auto-detection.\n\t */\n\tcoerceSchema?: JSONSchema7;\n\t/** Registered helpers (for direct execution of special helpers like `collect`) */\n\thelpers?: Map<string, HelperDefinition>;\n}\n\n// ─── Global Compilation Cache ────────────────────────────────────────────────\n// Used by the standalone `execute()` function and `renderWithHandlebars()`.\n// `Typebars` instances use their own cache.\nconst globalCompilationCache = new LRUCache<string, HandlebarsTemplateDelegate>(\n\t128,\n);\n\n// ─── Public API (backward-compatible) ────────────────────────────────────────\n\n/**\n * Executes a template with the provided data and returns the result.\n *\n * The return type depends on the template structure:\n * - Single expression `{{expr}}` → raw value (any)\n * - Single block → coerced value (number, boolean, null, or string)\n * - Mixed template → `string`\n *\n * @param template - The template string\n * @param data - The main context data\n * @param identifierData - (optional) Data by identifier `{ [id]: { key: value } }`\n */\nexport function execute(\n\ttemplate: TemplateInput,\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): unknown {\n\treturn dispatchExecute(\n\t\ttemplate,\n\t\tundefined,\n\t\t// String handler — parse and execute the AST\n\t\t(tpl) => {\n\t\t\tconst ast = parse(tpl);\n\t\t\treturn executeFromAst(ast, tpl, data, { identifierData });\n\t\t},\n\t\t// Recursive handler — re-enter execute() for child elements\n\t\t(child) => execute(child, data, identifierData),\n\t);\n}\n\n// ─── Internal API (for Typebars / CompiledTemplate) ──────────────────────\n\n/**\n * Executes a template from an already-parsed AST.\n *\n * This function is the core of execution. It is used by:\n * - `execute()` (backward-compatible wrapper)\n * - `CompiledTemplate.execute()` (with pre-parsed AST and cache)\n * - `Typebars.execute()` (with cache and helpers)\n *\n * @param ast - The already-parsed Handlebars AST\n * @param template - The template source (for Handlebars compilation if needed)\n * @param data - The main context data\n * @param ctx - Optional execution context\n */\nexport function executeFromAst(\n\tast: hbs.AST.Program,\n\ttemplate: string,\n\tdata: unknown,\n\tctx?: ExecutorContext,\n): unknown {\n\tconst identifierData = ctx?.identifierData;\n\n\t// ── Case 1: strict single expression `{{expr}}` ──────────────────────\n\t// Exclude helper calls (params > 0 or hash) because they must go\n\t// through Handlebars for correct execution.\n\tif (isSingleExpression(ast)) {\n\t\tconst stmt = ast.body[0] as hbs.AST.MustacheStatement;\n\t\tif (stmt.params.length === 0 && !stmt.hash) {\n\t\t\treturn resolveExpression(stmt.path, data, identifierData, ctx?.helpers);\n\t\t}\n\t}\n\n\t// ── Case 1b: single expression with surrounding whitespace ` {{expr}} `\n\tconst singleExpr = getEffectivelySingleExpression(ast);\n\tif (singleExpr && singleExpr.params.length === 0 && !singleExpr.hash) {\n\t\treturn resolveExpression(\n\t\t\tsingleExpr.path,\n\t\t\tdata,\n\t\t\tidentifierData,\n\t\t\tctx?.helpers,\n\t\t);\n\t}\n\n\t// ── Case 1c: single expression with helper (params > 0) ──────────────\n\t// E.g. `{{ divide accountIds.length 10 }}` or `{{ math a \"+\" b }}`\n\t// The helper returns a typed value but Handlebars converts it to a\n\t// string. We render via Handlebars then coerce the result to recover\n\t// the original type (number, boolean, null).\n\tif (singleExpr && (singleExpr.params.length > 0 || singleExpr.hash)) {\n\t\t// ── Special case: helpers that return non-primitive values ────────\n\t\t// Some helpers (e.g. `collect`) return arrays or objects. Handlebars\n\t\t// would stringify these, so we resolve their arguments directly and\n\t\t// call the helper's fn to preserve the raw return value.\n\t\tconst directResult = tryDirectHelperExecution(singleExpr, data, ctx);\n\t\tif (directResult !== undefined) {\n\t\t\treturn directResult.value;\n\t\t}\n\n\t\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\t\tconst raw = renderWithHandlebars(template, merged, ctx);\n\t\treturn coerceValue(raw, ctx?.coerceSchema);\n\t}\n\n\t// ── Case 2: fast-path for simple templates (text + expressions) ──────\n\t// If the template only contains text and simple expressions (no blocks,\n\t// no helpers with parameters), we can do direct concatenation without\n\t// going through Handlebars.compile().\n\tif (canUseFastPath(ast) && ast.body.length > 1) {\n\t\treturn executeFastPath(ast, data, identifierData);\n\t}\n\n\t// ── Case 3: single block (possibly surrounded by whitespace) ─────────\n\t// Render via Handlebars then attempt to coerce the result to the\n\t// detected literal type (number, boolean, null).\n\tconst singleBlock = getEffectivelySingleBlock(ast);\n\tif (singleBlock) {\n\t\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\t\tconst raw = renderWithHandlebars(template, merged, ctx);\n\t\treturn coerceValue(raw, ctx?.coerceSchema);\n\t}\n\n\t// ── Case 4: mixed template ───────────────────────────────────────────\n\t// For purely static templates (only ContentStatements), coerce the\n\t// result to match the coerceSchema type or auto-detect the literal type.\n\t// For truly mixed templates (text + blocks + expressions), return string.\n\tconst merged = mergeDataWithIdentifiers(data, identifierData);\n\tconst raw = renderWithHandlebars(template, merged, ctx);\n\n\tconst effective = getEffectiveBody(ast);\n\tconst allContent = effective.every((s) => s.type === \"ContentStatement\");\n\tif (allContent) {\n\t\treturn coerceValue(raw, ctx?.coerceSchema);\n\t}\n\n\treturn raw;\n}\n\n// ─── Value Coercion ──────────────────────────────────────────────────────────\n// Coerces a raw string from Handlebars rendering based on an optional\n// coerceSchema. When no schema is provided, falls back to auto-detection\n// via `coerceLiteral`.\n\n/**\n * Coerces a raw string value based on an optional coercion schema.\n *\n * - If `coerceSchema` declares a primitive type (`string`, `number`,\n * `integer`, `boolean`, `null`), the value is cast to that type.\n * - Otherwise, falls back to `coerceLiteral` (auto-detection).\n *\n * @param raw - The raw string from Handlebars rendering\n * @param coerceSchema - Optional schema declaring the desired output type\n * @returns The coerced value\n */\nfunction coerceValue(raw: string, coerceSchema?: JSONSchema7): unknown {\n\tif (coerceSchema) {\n\t\tconst targetType = coerceSchema.type;\n\t\tif (typeof targetType === \"string\") {\n\t\t\tif (targetType === \"string\") return raw;\n\t\t\tif (targetType === \"number\" || targetType === \"integer\")\n\t\t\t\treturn Number(raw.trim());\n\t\t\tif (targetType === \"boolean\") return raw.trim() === \"true\";\n\t\t\tif (targetType === \"null\") return null;\n\t\t}\n\t}\n\t// No coerceSchema or non-primitive type → auto-detect\n\treturn coerceLiteral(raw);\n}\n\n// ─── Fast-Path Execution ─────────────────────────────────────────────────────\n// For templates consisting only of text and simple expressions (no blocks,\n// no helpers), we bypass Handlebars and do direct concatenation.\n// This is significantly faster.\n\n/**\n * Executes a template via the fast-path (direct concatenation).\n *\n * Precondition: `canUseFastPath(ast)` must return `true`.\n *\n * @param ast - The template AST (only ContentStatement and simple MustacheStatement)\n * @param data - The context data\n * @param identifierData - Data by identifier (optional)\n * @returns The resulting string\n */\nfunction executeFastPath(\n\tast: hbs.AST.Program,\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): string {\n\tlet result = \"\";\n\n\tfor (const stmt of ast.body) {\n\t\tif (stmt.type === \"ContentStatement\") {\n\t\t\tresult += (stmt as hbs.AST.ContentStatement).value;\n\t\t} else if (stmt.type === \"MustacheStatement\") {\n\t\t\tconst value = resolveExpression(\n\t\t\t\t(stmt as hbs.AST.MustacheStatement).path,\n\t\t\t\tdata,\n\t\t\t\tidentifierData,\n\t\t\t);\n\t\t\t// Handlebars converts values to strings for rendering.\n\t\t\t// We replicate this behavior: null/undefined → \"\", otherwise String(value).\n\t\t\tif (value != null) {\n\t\t\t\tresult += String(value);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// ─── Direct Expression Resolution ────────────────────────────────────────────\n// Used for single-expression templates and the fast-path, to return the raw\n// value without going through the Handlebars engine.\n\n/**\n * Resolves an AST expression by following the path through the data.\n *\n * If the expression contains an identifier (e.g. `meetingId:1`), resolution\n * is performed in `identifierData[1]` instead of `data`.\n *\n * @param expr - The AST expression to resolve\n * @param data - The main data context\n * @param identifierData - Data by identifier (optional)\n * @returns The raw value pointed to by the expression\n */\nfunction resolveExpression(\n\texpr: hbs.AST.Expression,\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n\thelpers?: Map<string, HelperDefinition>,\n): unknown {\n\t// this / . → return the entire context\n\tif (isThisExpression(expr)) {\n\t\treturn data;\n\t}\n\n\t// Literals\n\tif (expr.type === \"StringLiteral\")\n\t\treturn (expr as hbs.AST.StringLiteral).value;\n\tif (expr.type === \"NumberLiteral\")\n\t\treturn (expr as hbs.AST.NumberLiteral).value;\n\tif (expr.type === \"BooleanLiteral\")\n\t\treturn (expr as hbs.AST.BooleanLiteral).value;\n\tif (expr.type === \"NullLiteral\") return null;\n\tif (expr.type === \"UndefinedLiteral\") return undefined;\n\n\t// ── SubExpression (nested helper call) ────────────────────────────────\n\t// E.g. `(collect users 'cartItems')` used as an argument to another helper.\n\t// Resolve all arguments recursively and call the helper's fn directly.\n\tif (expr.type === \"SubExpression\") {\n\t\tconst subExpr = expr as hbs.AST.SubExpression;\n\t\tif (subExpr.path.type === \"PathExpression\") {\n\t\t\tconst helperName = (subExpr.path as hbs.AST.PathExpression).original;\n\t\t\tconst helper = helpers?.get(helperName);\n\t\t\tif (helper) {\n\t\t\t\tconst isCollect = helperName === CollectionHelpers.COLLECT_HELPER_NAME;\n\t\t\t\tconst resolvedArgs: unknown[] = [];\n\t\t\t\tfor (let i = 0; i < subExpr.params.length; i++) {\n\t\t\t\t\tconst param = subExpr.params[i] as hbs.AST.Expression;\n\t\t\t\t\t// For `collect`, the second argument is a property name literal\n\t\t\t\t\tif (isCollect && i === 1 && param.type === \"StringLiteral\") {\n\t\t\t\t\t\tresolvedArgs.push((param as hbs.AST.StringLiteral).value);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresolvedArgs.push(\n\t\t\t\t\t\t\tresolveExpression(param, data, identifierData, helpers),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn helper.fn(...resolvedArgs);\n\t\t\t}\n\t\t}\n\t\t// Unknown sub-expression helper — return undefined\n\t\treturn undefined;\n\t}\n\n\t// PathExpression — navigate through segments in the data object\n\tconst segments = extractPathSegments(expr);\n\tif (segments.length === 0) {\n\t\tthrow new TemplateRuntimeError(\n\t\t\t`Cannot resolve expression of type \"${expr.type}\"`,\n\t\t);\n\t}\n\n\t// Extract the potential identifier from the last segment BEFORE\n\t// checking for $root, so that both {{$root}} and {{$root:N}} are\n\t// handled uniformly.\n\tconst { cleanSegments, identifier } = extractExpressionIdentifier(segments);\n\n\t// $root path traversal ($root.name) — not supported, return undefined\n\t// (the analyzer already rejects it with a diagnostic).\n\tif (isRootPathTraversal(cleanSegments)) {\n\t\treturn undefined;\n\t}\n\n\t// $root → return the entire data context (or identifier data)\n\tif (isRootSegments(cleanSegments)) {\n\t\tif (identifier !== null && identifierData) {\n\t\t\tconst source = identifierData[identifier];\n\t\t\treturn source ?? undefined;\n\t\t}\n\t\tif (identifier !== null) {\n\t\t\t// Template uses an identifier but no identifierData was provided\n\t\t\treturn undefined;\n\t\t}\n\t\treturn data;\n\t}\n\n\tif (identifier !== null && identifierData) {\n\t\tconst source = identifierData[identifier];\n\t\tif (source) {\n\t\t\treturn resolveDataPath(source, cleanSegments);\n\t\t}\n\t\t// Source does not exist → undefined (like a missing key)\n\t\treturn undefined;\n\t}\n\n\tif (identifier !== null && !identifierData) {\n\t\t// Template uses an identifier but no identifierData was provided\n\t\treturn undefined;\n\t}\n\n\treturn resolveDataPath(data, cleanSegments);\n}\n\n/**\n * Navigates through a data object by following a path of segments.\n *\n * @param data - The data object\n * @param segments - The path segments (e.g. `[\"user\", \"address\", \"city\"]`)\n * @returns The value at the end of the path, or `undefined` if an\n * intermediate segment is null/undefined\n */\nexport function resolveDataPath(data: unknown, segments: string[]): unknown {\n\tlet current: unknown = data;\n\n\tfor (const segment of segments) {\n\t\tif (current === null || current === undefined) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (typeof current !== \"object\") {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tcurrent = (current as Record<string, unknown>)[segment];\n\t}\n\n\treturn current;\n}\n\n// ─── Data Merging ────────────────────────────────────────────────────────────\n// For Handlebars rendering (mixed templates / blocks), we cannot intercept\n// resolution on a per-expression basis. Instead, we merge identifier data\n// into the main object using the format `\"key:N\"`.\n//\n// Handlebars parses `{{meetingId:1}}` as a PathExpression with a single\n// segment `\"meetingId:1\"`, so it looks up the key `\"meetingId:1\"` in the\n// data object — which matches our flattened format exactly.\n\n/**\n * Merges the main data with identifier data.\n *\n * @param data - Main data\n * @param identifierData - Data by identifier\n * @returns A merged object where identifier data appears as `\"key:N\"` keys\n *\n * @example\n * ```\n * mergeDataWithIdentifiers(\n * { name: \"Alice\" },\n * { 1: { meetingId: \"val1\" }, 2: { meetingId: \"val2\" } }\n * )\n * // → { name: \"Alice\", \"meetingId:1\": \"val1\", \"meetingId:2\": \"val2\" }\n * ```\n */\nfunction mergeDataWithIdentifiers(\n\tdata: unknown,\n\tidentifierData?: Record<number, Record<string, unknown>>,\n): Record<string, unknown> {\n\t// Always include $root so that Handlebars can resolve {{$root}} in\n\t// mixed templates and block helpers (where we delegate to Handlebars\n\t// instead of resolving expressions ourselves).\n\t// When data is a primitive (e.g. number passed with {{$root}}), we\n\t// wrap it into an object so Handlebars can still function.\n\tconst base: Record<string, unknown> =\n\t\tdata !== null && typeof data === \"object\" && !Array.isArray(data)\n\t\t\t? (data as Record<string, unknown>)\n\t\t\t: {};\n\tconst merged: Record<string, unknown> = { ...base, [ROOT_TOKEN]: data };\n\n\tif (!identifierData) return merged;\n\n\tfor (const [id, idData] of Object.entries(identifierData)) {\n\t\t// Add `$root:N` so Handlebars can resolve {{$root:N}} in mixed/block\n\t\t// templates (where we delegate to Handlebars instead of resolving\n\t\t// expressions ourselves). The value is the entire identifier data object.\n\t\tmerged[`${ROOT_TOKEN}:${id}`] = idData;\n\n\t\tfor (const [key, value] of Object.entries(idData)) {\n\t\t\tmerged[`${key}:${id}`] = value;\n\t\t}\n\t}\n\n\treturn merged;\n}\n\n// ─── Handlebars Rendering ────────────────────────────────────────────────────\n// For complex templates (blocks, helpers), we delegate to Handlebars.\n// Compilation is cached to avoid costly recompilations.\n\n/**\n * Compiles and executes a template via Handlebars.\n *\n * Uses a compilation cache (LRU) to avoid recompiling the same template\n * on repeated calls. The cache is either:\n * - The global cache (for the standalone `execute()` function)\n * - The instance cache provided via `ExecutorContext` (for `Typebars`)\n *\n * @param template - The template string\n * @param data - The context data\n * @param ctx - Optional execution context (cache, Handlebars env)\n * @returns Always a string\n */\nfunction renderWithHandlebars(\n\ttemplate: string,\n\tdata: Record<string, unknown>,\n\tctx?: ExecutorContext,\n): string {\n\ttry {\n\t\t// 1. Use the pre-compiled template if available (CompiledTemplate)\n\t\tif (ctx?.compiledTemplate) {\n\t\t\treturn ctx.compiledTemplate(data);\n\t\t}\n\n\t\t// 2. Look up in the cache (instance or global)\n\t\tconst cache = ctx?.compilationCache ?? globalCompilationCache;\n\t\tconst hbs = ctx?.hbs ?? Handlebars;\n\n\t\tlet compiled = cache.get(template);\n\t\tif (!compiled) {\n\t\t\tcompiled = hbs.compile(template, {\n\t\t\t\t// Disable HTML-escaping by default — this engine is not\n\t\t\t\t// HTML-specific, we want raw values.\n\t\t\t\tnoEscape: true,\n\t\t\t\t// Strict mode: throws if a path does not exist in the data.\n\t\t\t\tstrict: false,\n\t\t\t});\n\t\t\tcache.set(template, compiled);\n\t\t}\n\n\t\treturn compiled(data);\n\t} catch (error: unknown) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new TemplateRuntimeError(message);\n\t}\n}\n\n/**\n * Clears the global Handlebars compilation cache.\n * Useful for tests or to free memory.\n */\nexport function clearCompilationCache(): void {\n\tglobalCompilationCache.clear();\n}\n\n// ─── Direct Helper Execution ─────────────────────────────────────────────────\n// Some helpers (e.g. `collect`) return non-primitive values (arrays, objects)\n// that Handlebars would stringify. For these helpers, we resolve their\n// arguments directly and call the helper's `fn` to preserve the raw value.\n\n/** Set of helper names that must be executed directly (bypass Handlebars) */\nconst DIRECT_EXECUTION_HELPERS = new Set<string>([\n\tCollectionHelpers.COLLECT_HELPER_NAME,\n]);\n\n/**\n * Attempts to execute a helper directly (without Handlebars rendering).\n *\n * Returns `{ value }` if the helper was executed directly, or `undefined`\n * if the helper should go through the normal Handlebars rendering path.\n *\n * @param stmt - The MustacheStatement containing the helper call\n * @param data - The context data\n * @param ctx - Optional execution context (with helpers and identifierData)\n */\nfunction tryDirectHelperExecution(\n\tstmt: hbs.AST.MustacheStatement,\n\tdata: unknown,\n\tctx?: ExecutorContext,\n): { value: unknown } | undefined {\n\t// Get the helper name from the path\n\tif (stmt.path.type !== \"PathExpression\") return undefined;\n\tconst helperName = (stmt.path as hbs.AST.PathExpression).original;\n\n\t// Only intercept known direct-execution helpers\n\tif (!DIRECT_EXECUTION_HELPERS.has(helperName)) return undefined;\n\n\t// Look up the helper definition\n\tconst helper = ctx?.helpers?.get(helperName);\n\tif (!helper) return undefined;\n\n\t// Resolve each argument from the data context.\n\t// For the `collect` helper, the resolution strategy is:\n\t// - Arg 0 (collection): resolve as a data path (e.g. `users` → array)\n\t// - Arg 1 (property): must be a StringLiteral (e.g. `\"name\"`)\n\t// The analyzer enforces this — bare identifiers like `name` are\n\t// rejected at analysis time because Handlebars would resolve them\n\t// as a data path instead of a literal property name.\n\tconst isCollect = helperName === CollectionHelpers.COLLECT_HELPER_NAME;\n\n\tconst resolvedArgs: unknown[] = [];\n\tfor (let i = 0; i < stmt.params.length; i++) {\n\t\tconst param = stmt.params[i] as hbs.AST.Expression;\n\n\t\t// For `collect`, the second argument (index 1) is a property name —\n\t\t// it must be a StringLiteral (enforced by the analyzer).\n\t\tif (isCollect && i === 1) {\n\t\t\tif (param.type === \"StringLiteral\") {\n\t\t\t\tresolvedArgs.push((param as hbs.AST.StringLiteral).value);\n\t\t\t} else {\n\t\t\t\t// Fallback: resolve normally (will likely be undefined at runtime)\n\t\t\t\tresolvedArgs.push(\n\t\t\t\t\tresolveExpression(param, data, ctx?.identifierData, ctx?.helpers),\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tresolvedArgs.push(\n\t\t\t\tresolveExpression(param, data, ctx?.identifierData, ctx?.helpers),\n\t\t\t);\n\t\t}\n\t}\n\n\t// Call the helper's fn directly with the resolved arguments\n\tconst value = helper.fn(...resolvedArgs);\n\treturn { value };\n}\n"],"names":["Handlebars","dispatchExecute","TemplateRuntimeError","CollectionHelpers","canUseFastPath","coerceLiteral","extractExpressionIdentifier","extractPathSegments","getEffectiveBody","getEffectivelySingleBlock","getEffectivelySingleExpression","isRootPathTraversal","isRootSegments","isSingleExpression","isThisExpression","parse","ROOT_TOKEN","LRUCache","globalCompilationCache","execute","template","data","identifierData","undefined","tpl","ast","executeFromAst","child","ctx","stmt","body","params","length","hash","resolveExpression","path","helpers","singleExpr","directResult","tryDirectHelperExecution","value","merged","mergeDataWithIdentifiers","raw","renderWithHandlebars","coerceValue","coerceSchema","executeFastPath","singleBlock","effective","allContent","every","s","type","targetType","Number","trim","result","String","expr","subExpr","helperName","original","helper","get","isCollect","COLLECT_HELPER_NAME","resolvedArgs","i","param","push","fn","segments","cleanSegments","identifier","source","resolveDataPath","current","segment","base","Array","isArray","id","idData","Object","entries","key","compiledTemplate","cache","compilationCache","hbs","compiled","compile","noEscape","strict","set","error","message","Error","clearCompilationCache","clear","DIRECT_EXECUTION_HELPERS","Set","has"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { HelperDefinition } from "../types.js";
2
+ import { HelperFactory } from "./helper-factory.js";
3
+ export declare class CollectionHelpers extends HelperFactory {
4
+ /** The name used for special-case detection in the analyzer/executor */
5
+ static readonly COLLECT_HELPER_NAME = "collect";
6
+ protected buildDefinitions(defs: Map<string, HelperDefinition>): void;
7
+ /** Registers the `collect` helper */
8
+ private registerCollect;
9
+ }
@@ -0,0 +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{HelperFactory}from"./helper-factory.js";function collectProperty(collection,property){if(!Array.isArray(collection)){return[]}const prop=String(property);const flattened=collection.flat(1);return flattened.map(item=>{if(item!==null&&item!==undefined&&typeof item==="object"){return item[prop]}return undefined})}export class CollectionHelpers extends HelperFactory{buildDefinitions(defs){this.registerCollect(defs)}registerCollect(defs){defs.set(CollectionHelpers.COLLECT_HELPER_NAME,{fn:(collection,property)=>collectProperty(collection,property),params:[{name:"collection",type:{type:"array"},description:"The array of objects to extract values from"},{name:"property",type:{type:"string"},description:"The property name to extract from each element"}],returnType:{type:"array"},description:'Extracts a property from each element of an array: {{ collect users "name" }} → ["Alice", "Bob", "Charlie"]'})}}_define_property(CollectionHelpers,"COLLECT_HELPER_NAME","collect");
2
+ //# sourceMappingURL=collection-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/helpers/collection-helpers.ts"],"sourcesContent":["import type { HelperDefinition } from \"../types.ts\";\nimport { HelperFactory } from \"./helper-factory.ts\";\n\n// ─── CollectionHelpers ───────────────────────────────────────────────────────\n// Aggregates all collection-related helpers for the template engine.\n//\n// Provides helpers for working with arrays of objects:\n//\n// - **`collect`** — Extracts a specific property from each element of an\n// array, returning a new array of those values.\n// Usage: `{{ collect users \"name\" }}` → `[\"Alice\", \"Bob\", \"Charlie\"]`\n//\n// ─── Registration ────────────────────────────────────────────────────────────\n// CollectionHelpers are automatically pre-registered by the `Typebars`\n// constructor. They can also be registered manually on any object\n// implementing `HelperRegistry`:\n//\n// const factory = new CollectionHelpers();\n// factory.register(engine); // registers all helpers\n// factory.unregister(engine); // removes all helpers\n//\n// ─── Static Analysis ─────────────────────────────────────────────────────────\n// The `collect` helper has special static analysis handling in the analyzer:\n// - The first argument must resolve to an array of objects\n// - The second argument must be a quoted string literal (e.g. `\"name\"`, not `name`)\n// - The property must exist in the item schema of the array\n// - The inferred return type is `{ type: \"array\", items: <property schema> }`\n\n// ─── Internal utilities ─────────────────────────────────────────────────────\n\n/**\n * Extracts a property from each element of an array.\n *\n * @param collection - The array of objects\n * @param property - The property name to extract from each element\n * @returns A new array containing the extracted property values\n */\nfunction collectProperty(collection: unknown, property: unknown): unknown[] {\n\tif (!Array.isArray(collection)) {\n\t\treturn [];\n\t}\n\tconst prop = String(property);\n\t// Use flatMap semantics: if the collection contains nested arrays\n\t// (e.g. from a previous collect), flatten one level before extracting.\n\t// This enables chaining like `{{ collect (collect users 'cartItems') 'productId' }}`\n\t// where the inner collect returns an array of arrays.\n\tconst flattened = collection.flat(1);\n\treturn flattened.map((item: unknown) => {\n\t\tif (item !== null && item !== undefined && typeof item === \"object\") {\n\t\t\treturn (item as Record<string, unknown>)[prop];\n\t\t}\n\t\treturn undefined;\n\t});\n}\n\n// ─── Main class ─────────────────────────────────────────────────────────────\n\nexport class CollectionHelpers extends HelperFactory {\n\t/** The name used for special-case detection in the analyzer/executor */\n\tstatic readonly COLLECT_HELPER_NAME = \"collect\";\n\n\t// ─── buildDefinitions (required by HelperFactory) ──────────────────\n\n\tprotected buildDefinitions(defs: Map<string, HelperDefinition>): void {\n\t\tthis.registerCollect(defs);\n\t}\n\n\t// ── collect ──────────────────────────────────────────────────────────\n\n\t/** Registers the `collect` helper */\n\tprivate registerCollect(defs: Map<string, HelperDefinition>): void {\n\t\tdefs.set(CollectionHelpers.COLLECT_HELPER_NAME, {\n\t\t\tfn: (collection: unknown, property: unknown) =>\n\t\t\t\tcollectProperty(collection, property),\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"collection\",\n\t\t\t\t\ttype: { type: \"array\" },\n\t\t\t\t\tdescription: \"The array of objects to extract values from\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"property\",\n\t\t\t\t\ttype: { type: \"string\" },\n\t\t\t\t\tdescription: \"The property name to extract from each element\",\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"array\" },\n\t\t\tdescription:\n\t\t\t\t'Extracts a property from each element of an array: {{ collect users \"name\" }} → [\"Alice\", \"Bob\", \"Charlie\"]',\n\t\t});\n\t}\n}\n"],"names":["HelperFactory","collectProperty","collection","property","Array","isArray","prop","String","flattened","flat","map","item","undefined","CollectionHelpers","buildDefinitions","defs","registerCollect","set","COLLECT_HELPER_NAME","fn","params","name","type","description","returnType"],"mappings":"oLACA,OAASA,aAAa,KAAQ,qBAAsB,CAoCpD,SAASC,gBAAgBC,UAAmB,CAAEC,QAAiB,EAC9D,GAAI,CAACC,MAAMC,OAAO,CAACH,YAAa,CAC/B,MAAO,EAAE,AACV,CACA,MAAMI,KAAOC,OAAOJ,UAKpB,MAAMK,UAAYN,WAAWO,IAAI,CAAC,GAClC,OAAOD,UAAUE,GAAG,CAAC,AAACC,OACrB,GAAIA,OAAS,MAAQA,OAASC,WAAa,OAAOD,OAAS,SAAU,CACpE,OAAO,AAACA,IAAgC,CAACL,KAAK,AAC/C,CACA,OAAOM,SACR,EACD,CAIA,OAAO,MAAMC,0BAA0Bb,cAMtC,AAAUc,iBAAiBC,IAAmC,CAAQ,CACrE,IAAI,CAACC,eAAe,CAACD,KACtB,CAKA,AAAQC,gBAAgBD,IAAmC,CAAQ,CAClEA,KAAKE,GAAG,CAACJ,kBAAkBK,mBAAmB,CAAE,CAC/CC,GAAI,CAACjB,WAAqBC,WACzBF,gBAAgBC,WAAYC,UAC7BiB,OAAQ,CACP,CACCC,KAAM,aACNC,KAAM,CAAEA,KAAM,OAAQ,EACtBC,YAAa,6CACd,EACA,CACCF,KAAM,WACNC,KAAM,CAAEA,KAAM,QAAS,EACvBC,YAAa,gDACd,EACA,CACDC,WAAY,CAAEF,KAAM,OAAQ,EAC5BC,YACC,6GACF,EACD,CACD,CAhCC,iBAFYV,kBAEIK,sBAAsB"}
@@ -1,3 +1,4 @@
1
+ export { CollectionHelpers } from "./collection-helpers.js";
1
2
  export { HelperFactory, type HelperRegistry } from "./helper-factory.js";
2
3
  export { LogicalHelpers } from "./logical-helpers.js";
3
4
  export { MathHelpers } from "./math-helpers.js";
@@ -1,2 +1,2 @@
1
- export{HelperFactory}from"./helper-factory.js";export{LogicalHelpers}from"./logical-helpers.js";export{MathHelpers}from"./math-helpers.js";export{toNumber}from"./utils.js";
1
+ export{CollectionHelpers}from"./collection-helpers.js";export{HelperFactory}from"./helper-factory.js";export{LogicalHelpers}from"./logical-helpers.js";export{MathHelpers}from"./math-helpers.js";export{toNumber}from"./utils.js";
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/helpers/index.ts"],"sourcesContent":["export { HelperFactory, type HelperRegistry } from \"./helper-factory\";\nexport { LogicalHelpers } from \"./logical-helpers\";\nexport { MathHelpers } from \"./math-helpers\";\nexport { toNumber } from \"./utils\";\n"],"names":["HelperFactory","LogicalHelpers","MathHelpers","toNumber"],"mappings":"AAAA,OAASA,aAAa,KAA6B,kBAAmB,AACtE,QAASC,cAAc,KAAQ,mBAAoB,AACnD,QAASC,WAAW,KAAQ,gBAAiB,AAC7C,QAASC,QAAQ,KAAQ,SAAU"}
1
+ {"version":3,"sources":["../../../src/helpers/index.ts"],"sourcesContent":["export { CollectionHelpers } from \"./collection-helpers\";\nexport { HelperFactory, type HelperRegistry } from \"./helper-factory\";\nexport { LogicalHelpers } from \"./logical-helpers\";\nexport { MathHelpers } from \"./math-helpers\";\nexport { toNumber } from \"./utils\";\n"],"names":["CollectionHelpers","HelperFactory","LogicalHelpers","MathHelpers","toNumber"],"mappings":"AAAA,OAASA,iBAAiB,KAAQ,sBAAuB,AACzD,QAASC,aAAa,KAA6B,kBAAmB,AACtE,QAASC,cAAc,KAAQ,mBAAoB,AACnD,QAASC,WAAW,KAAQ,gBAAiB,AAC7C,QAASC,QAAQ,KAAQ,SAAU"}
@@ -1,2 +1,2 @@
1
- import Handlebars from"handlebars";import{TemplateParseError}from"./errors.js";export const ROOT_TOKEN="$root";const IDENTIFIER_RE=/^(.+):(\d+)$/;const NUMERIC_LITERAL_RE=/^-?\d+(\.\d+)?$/;export function parse(template){try{return Handlebars.parse(template)}catch(error){const message=error instanceof Error?error.message:String(error);const locMatch=message.match(/line\s+(\d+).*?column\s+(\d+)/i);const loc=locMatch?{line:parseInt(locMatch[1]??"0",10),column:parseInt(locMatch[2]??"0",10)}:undefined;throw new TemplateParseError(message,loc)}}export function isSingleExpression(ast){const{body}=ast;return body.length===1&&body[0]?.type==="MustacheStatement"}export function extractPathSegments(expr){if(expr.type==="PathExpression"){return expr.parts}return[]}export function isThisExpression(expr){if(expr.type!=="PathExpression")return false;const path=expr;return path.original==="this"||path.original==="."}export function isRootExpression(expr){if(expr.type!=="PathExpression")return false;const path=expr;return path.parts.length>0&&path.parts[0]===ROOT_TOKEN}export function isRootSegments(cleanSegments){return cleanSegments.length===1&&cleanSegments[0]===ROOT_TOKEN}export function isRootPathTraversal(cleanSegments){return cleanSegments.length>1&&cleanSegments[0]===ROOT_TOKEN}export function getEffectiveBody(program){return program.body.filter(s=>!(s.type==="ContentStatement"&&s.value.trim()===""))}export function getEffectivelySingleBlock(program){const effective=getEffectiveBody(program);if(effective.length===1&&effective[0]?.type==="BlockStatement"){return effective[0]}return null}export function getEffectivelySingleExpression(program){const effective=getEffectiveBody(program);if(effective.length===1&&effective[0]?.type==="MustacheStatement"){return effective[0]}return null}export function hasHandlebarsExpression(value){return value.includes("{{")}export function canUseFastPath(ast){return ast.body.every(s=>s.type==="ContentStatement"||s.type==="MustacheStatement"&&s.params.length===0&&!s.hash)}export function detectLiteralType(text){if(NUMERIC_LITERAL_RE.test(text))return"number";if(text==="true"||text==="false")return"boolean";if(text==="null")return"null";return null}export function coerceLiteral(raw){const trimmed=raw.trim();const type=detectLiteralType(trimmed);if(type==="number")return Number(trimmed);if(type==="boolean")return trimmed==="true";if(type==="null")return null;return raw}export function parseIdentifier(segment){const match=segment.match(IDENTIFIER_RE);if(match){return{key:match[1]??segment,identifier:parseInt(match[2]??"0",10)}}return{key:segment,identifier:null}}export function extractExpressionIdentifier(segments){if(segments.length===0){return{cleanSegments:[],identifier:null}}const lastSegment=segments[segments.length-1];const parsed=parseIdentifier(lastSegment);if(parsed.identifier!==null){const cleanSegments=[...segments.slice(0,-1),parsed.key];return{cleanSegments,identifier:parsed.identifier}}return{cleanSegments:segments,identifier:null}}
1
+ import Handlebars from"handlebars";import{TemplateParseError}from"./errors.js";export const ROOT_TOKEN="$root";const IDENTIFIER_RE=/^(.+):(-?\d+)$/;const NUMERIC_LITERAL_RE=/^-?\d+(\.\d+)?$/;export function parse(template){try{return Handlebars.parse(template)}catch(error){const message=error instanceof Error?error.message:String(error);const locMatch=message.match(/line\s+(\d+).*?column\s+(\d+)/i);const loc=locMatch?{line:parseInt(locMatch[1]??"0",10),column:parseInt(locMatch[2]??"0",10)}:undefined;throw new TemplateParseError(message,loc)}}export function isSingleExpression(ast){const{body}=ast;return body.length===1&&body[0]?.type==="MustacheStatement"}export function extractPathSegments(expr){if(expr.type==="PathExpression"){return expr.parts}return[]}export function isThisExpression(expr){if(expr.type!=="PathExpression")return false;const path=expr;return path.original==="this"||path.original==="."}export function isRootExpression(expr){if(expr.type!=="PathExpression")return false;const path=expr;return path.parts.length>0&&path.parts[0]===ROOT_TOKEN}export function isRootSegments(cleanSegments){return cleanSegments.length===1&&cleanSegments[0]===ROOT_TOKEN}export function isRootPathTraversal(cleanSegments){return cleanSegments.length>1&&cleanSegments[0]===ROOT_TOKEN}export function getEffectiveBody(program){return program.body.filter(s=>!(s.type==="ContentStatement"&&s.value.trim()===""))}export function getEffectivelySingleBlock(program){const effective=getEffectiveBody(program);if(effective.length===1&&effective[0]?.type==="BlockStatement"){return effective[0]}return null}export function getEffectivelySingleExpression(program){const effective=getEffectiveBody(program);if(effective.length===1&&effective[0]?.type==="MustacheStatement"){return effective[0]}return null}export function hasHandlebarsExpression(value){return value.includes("{{")}export function canUseFastPath(ast){return ast.body.every(s=>s.type==="ContentStatement"||s.type==="MustacheStatement"&&s.params.length===0&&!s.hash)}export function detectLiteralType(text){if(NUMERIC_LITERAL_RE.test(text))return"number";if(text==="true"||text==="false")return"boolean";if(text==="null")return"null";return null}export function coerceLiteral(raw){const trimmed=raw.trim();const type=detectLiteralType(trimmed);if(type==="number")return Number(trimmed);if(type==="boolean")return trimmed==="true";if(type==="null")return null;return raw}export function parseIdentifier(segment){const match=segment.match(IDENTIFIER_RE);if(match){return{key:match[1]??segment,identifier:parseInt(match[2]??"0",10)}}return{key:segment,identifier:null}}export function extractExpressionIdentifier(segments){if(segments.length===0){return{cleanSegments:[],identifier:null}}const lastSegment=segments[segments.length-1];const parsed=parseIdentifier(lastSegment);if(parsed.identifier!==null){const cleanSegments=[...segments.slice(0,-1),parsed.key];return{cleanSegments,identifier:parsed.identifier}}return{cleanSegments:segments,identifier:null}}
2
2
  //# sourceMappingURL=parser.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/parser.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport { TemplateParseError } from \"./errors.ts\";\n\n// ─── Root Token ──────────────────────────────────────────────────────────────\n// Special token `$root` references the entire input schema / data context.\n// It cannot be followed by property access (e.g. `$root.name` is invalid).\nexport const ROOT_TOKEN = \"$root\";\n\n// ─── Regex for detecting a template identifier (e.g. \"meetingId:1\") ──────────\n// The identifier is always a positive integer or zero, separated from the\n// variable name by a `:`. The `:` and number are on the **last** segment\n// of the path (Handlebars splits on `.`).\nconst IDENTIFIER_RE = /^(.+):(\\d+)$/;\n\n// ─── Template Parser ─────────────────────────────────────────────────────────\n// Thin wrapper around the Handlebars parser. Centralizing the parser call\n// here allows us to:\n// 1. Wrap errors into our own hierarchy (`TemplateParseError`)\n// 2. Expose AST introspection helpers (e.g. `isSingleExpression`)\n// 3. Isolate the direct Handlebars dependency from the rest of the codebase\n//\n// AST caching is handled at the `Typebars` instance level (via its own\n// configurable LRU cache), not here. This module only parses and wraps errors.\n\n// ─── Regex for detecting a numeric literal (integer or decimal, signed) ──────\n// Intentionally conservative: no scientific notation (1e5), no hex (0xFF),\n// no separators (1_000). We only want to recognize what a human would write\n// as a numeric value in a template.\nconst NUMERIC_LITERAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Parses a template string and returns the Handlebars AST.\n *\n * This function does not cache results — caching is managed at the\n * `Typebars` instance level via its own configurable LRU cache.\n *\n * @param template - The template string to parse (e.g. `\"Hello {{name}}\"`)\n * @returns The root AST node (`hbs.AST.Program`)\n * @throws {TemplateParseError} if the template syntax is invalid\n */\nexport function parse(template: string): hbs.AST.Program {\n\ttry {\n\t\treturn Handlebars.parse(template);\n\t} catch (error: unknown) {\n\t\t// Handlebars throws a plain Error with a descriptive message.\n\t\t// We transform it into a TemplateParseError for uniform handling.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t// Handlebars sometimes includes the position in the message —\n\t\t// attempt to extract it to enrich our error.\n\t\tconst locMatch = message.match(/line\\s+(\\d+).*?column\\s+(\\d+)/i);\n\t\tconst loc = locMatch\n\t\t\t? {\n\t\t\t\t\tline: parseInt(locMatch[1] ?? \"0\", 10),\n\t\t\t\t\tcolumn: parseInt(locMatch[2] ?? \"0\", 10),\n\t\t\t\t}\n\t\t\t: undefined;\n\n\t\tthrow new TemplateParseError(message, loc);\n\t}\n}\n\n/**\n * Determines whether the AST represents a template consisting of a single\n * expression `{{expression}}` with no text content around it.\n *\n * This matters for return type inference:\n * - Template `{{value}}` → returns the raw type of `value` (number, object…)\n * - Template `Hello {{name}}` → always returns `string` (concatenation)\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template is a single expression\n */\nexport function isSingleExpression(ast: hbs.AST.Program): boolean {\n\tconst { body } = ast;\n\n\t// Exactly one node, and it's a MustacheStatement (not a block, not text)\n\treturn body.length === 1 && body[0]?.type === \"MustacheStatement\";\n}\n\n/**\n * Extracts the path segments from a Handlebars `PathExpression`.\n *\n * Handlebars decomposes `user.address.city` into `{ parts: [\"user\", \"address\", \"city\"] }`.\n * This function safely extracts those segments.\n *\n * @param expr - The expression to extract the path from\n * @returns The path segments, or an empty array if the expression is not\n * a `PathExpression`\n */\nexport function extractPathSegments(expr: hbs.AST.Expression): string[] {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).parts;\n\t}\n\treturn [];\n}\n\n/**\n * Checks whether an AST expression is a `PathExpression` pointing to `this`\n * (used inside `{{#each}}` blocks).\n */\nexport function isThisExpression(expr: hbs.AST.Expression): boolean {\n\tif (expr.type !== \"PathExpression\") return false;\n\tconst path = expr as hbs.AST.PathExpression;\n\treturn path.original === \"this\" || path.original === \".\";\n}\n\n/**\n * Checks whether an AST expression is a `PathExpression` whose first\n * segment is the `$root` token.\n *\n * This covers both the valid `{{$root}}` case (single segment) and the\n * invalid `{{$root.name}}` case (multiple segments). The caller must\n * check `parts.length` to distinguish valid from invalid usage.\n *\n * **Note:** This does NOT match `{{$root:2}}` because Handlebars parses\n * it as `parts: [\"$root:2\"]` (identifier still attached). Use\n * `isRootSegments()` on cleaned segments instead when identifier\n * extraction has already been performed.\n */\nexport function isRootExpression(expr: hbs.AST.Expression): boolean {\n\tif (expr.type !== \"PathExpression\") return false;\n\tconst path = expr as hbs.AST.PathExpression;\n\treturn path.parts.length > 0 && path.parts[0] === ROOT_TOKEN;\n}\n\n/**\n * Checks whether an array of **cleaned** path segments represents a\n * `$root` reference (i.e. exactly one segment equal to `\"$root\"`).\n *\n * This is the segment-level counterpart of `isRootExpression()` and is\n * meant to be called **after** `extractExpressionIdentifier()` has\n * stripped the `:N` suffix. It correctly handles both `{{$root}}` and\n * `{{$root:2}}`.\n *\n * @param cleanSegments - Path segments with identifiers already removed\n * @returns `true` if the segments represent a `$root` reference\n */\nexport function isRootSegments(cleanSegments: string[]): boolean {\n\treturn cleanSegments.length === 1 && cleanSegments[0] === ROOT_TOKEN;\n}\n\n/**\n * Checks whether cleaned segments represent a **path traversal** on\n * `$root` (e.g. `$root.name`, `$root.address.city`).\n *\n * Path traversal on `$root` is forbidden — users should write `{{name}}`\n * instead of `{{$root.name}}`.\n *\n * @param cleanSegments - Path segments with identifiers already removed\n * @returns `true` if the first segment is `$root` and there are additional segments\n */\nexport function isRootPathTraversal(cleanSegments: string[]): boolean {\n\treturn cleanSegments.length > 1 && cleanSegments[0] === ROOT_TOKEN;\n}\n\n// ─── Filtering Semantically Significant Nodes ───────────────────────────────\n// In a Handlebars AST, formatting (newlines, indentation) produces\n// `ContentStatement` nodes whose value is purely whitespace. These nodes\n// have no semantic impact and must be ignored during type inference to\n// correctly detect \"effectively a single block\" or \"effectively a single\n// expression\" cases.\n\n/**\n * Returns the semantically significant statements of a Program by\n * filtering out `ContentStatement` nodes that contain only whitespace.\n */\nexport function getEffectiveBody(\n\tprogram: hbs.AST.Program,\n): hbs.AST.Statement[] {\n\treturn program.body.filter(\n\t\t(s) =>\n\t\t\t!(\n\t\t\t\ts.type === \"ContentStatement\" &&\n\t\t\t\t(s as hbs.AST.ContentStatement).value.trim() === \"\"\n\t\t\t),\n\t);\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `BlockStatement` (ignoring surrounding whitespace).\n *\n * Recognized examples:\n * ```\n * {{#if x}}...{{/if}}\n *\n * {{#each items}}...{{/each}}\n * ```\n *\n * @returns The single `BlockStatement`, or `null` if the program contains\n * other significant nodes.\n */\nexport function getEffectivelySingleBlock(\n\tprogram: hbs.AST.Program,\n): hbs.AST.BlockStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"BlockStatement\") {\n\t\treturn effective[0] as hbs.AST.BlockStatement;\n\t}\n\treturn null;\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `MustacheStatement` (ignoring surrounding whitespace).\n *\n * Example: ` {{age}} ` → true\n */\nexport function getEffectivelySingleExpression(\n\tprogram: hbs.AST.Program,\n): hbs.AST.MustacheStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"MustacheStatement\") {\n\t\treturn effective[0] as hbs.AST.MustacheStatement;\n\t}\n\treturn null;\n}\n\n// ─── Handlebars Expression Detection ─────────────────────────────────────────\n// Fast heuristic to determine whether a string contains Handlebars expressions.\n// Used by `excludeTemplateExpression` filtering to skip dynamic entries.\n\n/**\n * Determines whether a string contains Handlebars expressions.\n *\n * A string contains template expressions if it includes `{{` (opening\n * delimiter for Handlebars mustache/block statements). This is a fast\n * substring check — **no parsing is performed**.\n *\n * Used by `excludeTemplateExpression` to filter out properties whose\n * values are dynamic templates.\n *\n * **Limitation:** This is a simple `includes(\"{{\")` check, not a full\n * parser. It will produce false positives for strings that contain `{{`\n * as literal text (e.g. `\"Use {{name}} syntax\"` in documentation strings)\n * or in escaped contexts. For the current use case (filtering object/array\n * entries that are likely templates), this trade-off is acceptable because:\n * - It avoids the cost of parsing every string value\n * - False positives only cause an entry to be excluded from the output\n * schema (conservative behavior)\n *\n * @param value - The string to check\n * @returns `true` if the string contains at least one `{{` sequence\n */\nexport function hasHandlebarsExpression(value: string): boolean {\n\treturn value.includes(\"{{\");\n}\n\n// ─── Fast-Path Detection ─────────────────────────────────────────────────────\n// For templates consisting only of text and simple expressions (no blocks,\n// no helpers with parameters), we can bypass Handlebars entirely and perform\n// a simple variable replacement via string concatenation.\n\n/**\n * Determines whether an AST can be executed via the fast-path (direct\n * concatenation without going through `Handlebars.compile()`).\n *\n * The fast-path is possible when the template only contains:\n * - `ContentStatement` nodes (static text)\n * - Simple `MustacheStatement` nodes (no params, no hash)\n *\n * This excludes:\n * - Block helpers (`{{#if}}`, `{{#each}}`, etc.)\n * - Inline helpers (`{{uppercase name}}`)\n * - Sub-expressions\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template can use the fast-path\n */\nexport function canUseFastPath(ast: hbs.AST.Program): boolean {\n\treturn ast.body.every(\n\t\t(s) =>\n\t\t\ts.type === \"ContentStatement\" ||\n\t\t\t(s.type === \"MustacheStatement\" &&\n\t\t\t\t(s as hbs.AST.MustacheStatement).params.length === 0 &&\n\t\t\t\t!(s as hbs.AST.MustacheStatement).hash),\n\t);\n}\n\n// ─── Literal Detection in Text Content ───────────────────────────────────────\n// When a program contains only ContentStatements (no expressions), we try\n// to detect whether the concatenated and trimmed text is a typed literal\n// (number, boolean, null). This enables correct type inference for branches\n// like `{{#if x}} 42 {{/if}}`.\n\n/**\n * Attempts to detect the type of a raw text literal.\n *\n * @param text - The trimmed text from a ContentStatement or group of ContentStatements\n * @returns The detected JSON Schema type, or `null` if it's free-form text (string).\n */\nexport function detectLiteralType(\n\ttext: string,\n): \"number\" | \"boolean\" | \"null\" | null {\n\tif (NUMERIC_LITERAL_RE.test(text)) return \"number\";\n\tif (text === \"true\" || text === \"false\") return \"boolean\";\n\tif (text === \"null\") return \"null\";\n\treturn null;\n}\n\n/**\n * Coerces a raw string from Handlebars rendering to its actual type\n * if it represents a literal (number, boolean, null).\n * Returns the raw (untrimmed) string otherwise.\n */\nexport function coerceLiteral(raw: string): unknown {\n\tconst trimmed = raw.trim();\n\tconst type = detectLiteralType(trimmed);\n\tif (type === \"number\") return Number(trimmed);\n\tif (type === \"boolean\") return trimmed === \"true\";\n\tif (type === \"null\") return null;\n\t// Not a typed literal — return the raw string without trimming,\n\t// as whitespace may be significant (e.g. output of an #each block).\n\treturn raw;\n}\n\n// ─── Template Identifier Parsing ─────────────────────────────────────────────\n// Syntax `{{key:N}}` where N is a positive integer or zero.\n// The identifier allows resolving a variable from a specific data source\n// (e.g. a workflow node identified by its number).\n\n/** Result of parsing a path segment with a potential identifier */\nexport interface ParsedIdentifier {\n\t/** The variable name, without the `:N` suffix */\n\tkey: string;\n\t/** The numeric identifier, or `null` if absent */\n\tidentifier: number | null;\n}\n\n/**\n * Parses an individual path segment to extract the key and optional identifier.\n *\n * @param segment - A raw path segment (e.g. `\"meetingId:1\"` or `\"meetingId\"`)\n * @returns An object `{ key, identifier }`\n *\n * @example\n * ```\n * parseIdentifier(\"meetingId:1\") // → { key: \"meetingId\", identifier: 1 }\n * parseIdentifier(\"meetingId\") // → { key: \"meetingId\", identifier: null }\n * parseIdentifier(\"meetingId:0\") // → { key: \"meetingId\", identifier: 0 }\n * ```\n */\nexport function parseIdentifier(segment: string): ParsedIdentifier {\n\tconst match = segment.match(IDENTIFIER_RE);\n\tif (match) {\n\t\treturn {\n\t\t\tkey: match[1] ?? segment,\n\t\t\tidentifier: parseInt(match[2] ?? \"0\", 10),\n\t\t};\n\t}\n\treturn { key: segment, identifier: null };\n}\n\n/** Result of extracting the identifier from a complete expression */\nexport interface ExpressionIdentifier {\n\t/** Cleaned path segments (without the `:N` suffix on the last one) */\n\tcleanSegments: string[];\n\t/** The numeric identifier extracted from the last segment, or `null` */\n\tidentifier: number | null;\n}\n\n/**\n * Extracts the identifier from a complete expression (array of segments).\n *\n * The identifier is always on the **last** segment of the path, because\n * Handlebars splits on `.` before the `:`.\n *\n * @param segments - The raw path segments (e.g. `[\"user\", \"name:1\"]`)\n * @returns An object `{ cleanSegments, identifier }`\n *\n * @example\n * ```\n * extractExpressionIdentifier([\"meetingId:1\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"user\", \"name:1\"])\n * // → { cleanSegments: [\"user\", \"name\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"meetingId\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: null }\n * ```\n */\nexport function extractExpressionIdentifier(\n\tsegments: string[],\n): ExpressionIdentifier {\n\tif (segments.length === 0) {\n\t\treturn { cleanSegments: [], identifier: null };\n\t}\n\n\tconst lastSegment = segments[segments.length - 1] as string;\n\tconst parsed = parseIdentifier(lastSegment);\n\n\tif (parsed.identifier !== null) {\n\t\tconst cleanSegments = [...segments.slice(0, -1), parsed.key];\n\t\treturn { cleanSegments, identifier: parsed.identifier };\n\t}\n\n\treturn { cleanSegments: segments, identifier: null };\n}\n"],"names":["Handlebars","TemplateParseError","ROOT_TOKEN","IDENTIFIER_RE","NUMERIC_LITERAL_RE","parse","template","error","message","Error","String","locMatch","match","loc","line","parseInt","column","undefined","isSingleExpression","ast","body","length","type","extractPathSegments","expr","parts","isThisExpression","path","original","isRootExpression","isRootSegments","cleanSegments","isRootPathTraversal","getEffectiveBody","program","filter","s","value","trim","getEffectivelySingleBlock","effective","getEffectivelySingleExpression","hasHandlebarsExpression","includes","canUseFastPath","every","params","hash","detectLiteralType","text","test","coerceLiteral","raw","trimmed","Number","parseIdentifier","segment","key","identifier","extractExpressionIdentifier","segments","lastSegment","parsed","slice"],"mappings":"AAAA,OAAOA,eAAgB,YAAa,AACpC,QAASC,kBAAkB,KAAQ,aAAc,AAKjD,QAAO,MAAMC,WAAa,OAAQ,CAMlC,MAAMC,cAAgB,eAgBtB,MAAMC,mBAAqB,iBAY3B,QAAO,SAASC,MAAMC,QAAgB,EACrC,GAAI,CACH,OAAON,WAAWK,KAAK,CAACC,SACzB,CAAE,MAAOC,MAAgB,CAGxB,MAAMC,QAAUD,iBAAiBE,MAAQF,MAAMC,OAAO,CAAGE,OAAOH,OAIhE,MAAMI,SAAWH,QAAQI,KAAK,CAAC,kCAC/B,MAAMC,IAAMF,SACT,CACAG,KAAMC,SAASJ,QAAQ,CAAC,EAAE,EAAI,IAAK,IACnCK,OAAQD,SAASJ,QAAQ,CAAC,EAAE,EAAI,IAAK,GACtC,EACCM,SAEH,OAAM,IAAIhB,mBAAmBO,QAASK,IACvC,CACD,CAaA,OAAO,SAASK,mBAAmBC,GAAoB,EACtD,KAAM,CAAEC,IAAI,CAAE,CAAGD,IAGjB,OAAOC,KAAKC,MAAM,GAAK,GAAKD,IAAI,CAAC,EAAE,EAAEE,OAAS,mBAC/C,CAYA,OAAO,SAASC,oBAAoBC,IAAwB,EAC3D,GAAIA,KAAKF,IAAI,GAAK,iBAAkB,CACnC,OAAO,AAACE,KAAgCC,KAAK,AAC9C,CACA,MAAO,EAAE,AACV,CAMA,OAAO,SAASC,iBAAiBF,IAAwB,EACxD,GAAIA,KAAKF,IAAI,GAAK,iBAAkB,OAAO,MAC3C,MAAMK,KAAOH,KACb,OAAOG,KAAKC,QAAQ,GAAK,QAAUD,KAAKC,QAAQ,GAAK,GACtD,CAeA,OAAO,SAASC,iBAAiBL,IAAwB,EACxD,GAAIA,KAAKF,IAAI,GAAK,iBAAkB,OAAO,MAC3C,MAAMK,KAAOH,KACb,OAAOG,KAAKF,KAAK,CAACJ,MAAM,CAAG,GAAKM,KAAKF,KAAK,CAAC,EAAE,GAAKvB,UACnD,CAcA,OAAO,SAAS4B,eAAeC,aAAuB,EACrD,OAAOA,cAAcV,MAAM,GAAK,GAAKU,aAAa,CAAC,EAAE,GAAK7B,UAC3D,CAYA,OAAO,SAAS8B,oBAAoBD,aAAuB,EAC1D,OAAOA,cAAcV,MAAM,CAAG,GAAKU,aAAa,CAAC,EAAE,GAAK7B,UACzD,CAaA,OAAO,SAAS+B,iBACfC,OAAwB,EAExB,OAAOA,QAAQd,IAAI,CAACe,MAAM,CACzB,AAACC,GACA,CACCA,CAAAA,EAAEd,IAAI,GAAK,oBACX,AAACc,EAA+BC,KAAK,CAACC,IAAI,KAAO,EAAC,EAGtD,CAgBA,OAAO,SAASC,0BACfL,OAAwB,EAExB,MAAMM,UAAYP,iBAAiBC,SACnC,GAAIM,UAAUnB,MAAM,GAAK,GAAKmB,SAAS,CAAC,EAAE,EAAElB,OAAS,iBAAkB,CACtE,OAAOkB,SAAS,CAAC,EAAE,AACpB,CACA,OAAO,IACR,CAQA,OAAO,SAASC,+BACfP,OAAwB,EAExB,MAAMM,UAAYP,iBAAiBC,SACnC,GAAIM,UAAUnB,MAAM,GAAK,GAAKmB,SAAS,CAAC,EAAE,EAAElB,OAAS,oBAAqB,CACzE,OAAOkB,SAAS,CAAC,EAAE,AACpB,CACA,OAAO,IACR,CA4BA,OAAO,SAASE,wBAAwBL,KAAa,EACpD,OAAOA,MAAMM,QAAQ,CAAC,KACvB,CAuBA,OAAO,SAASC,eAAezB,GAAoB,EAClD,OAAOA,IAAIC,IAAI,CAACyB,KAAK,CACpB,AAACT,GACAA,EAAEd,IAAI,GAAK,oBACVc,EAAEd,IAAI,GAAK,qBACX,AAACc,EAAgCU,MAAM,CAACzB,MAAM,GAAK,GACnD,CAAC,AAACe,EAAgCW,IAAI,CAE1C,CAcA,OAAO,SAASC,kBACfC,IAAY,EAEZ,GAAI7C,mBAAmB8C,IAAI,CAACD,MAAO,MAAO,SAC1C,GAAIA,OAAS,QAAUA,OAAS,QAAS,MAAO,UAChD,GAAIA,OAAS,OAAQ,MAAO,OAC5B,OAAO,IACR,CAOA,OAAO,SAASE,cAAcC,GAAW,EACxC,MAAMC,QAAUD,IAAId,IAAI,GACxB,MAAMhB,KAAO0B,kBAAkBK,SAC/B,GAAI/B,OAAS,SAAU,OAAOgC,OAAOD,SACrC,GAAI/B,OAAS,UAAW,OAAO+B,UAAY,OAC3C,GAAI/B,OAAS,OAAQ,OAAO,KAG5B,OAAO8B,GACR,CA4BA,OAAO,SAASG,gBAAgBC,OAAe,EAC9C,MAAM5C,MAAQ4C,QAAQ5C,KAAK,CAACT,eAC5B,GAAIS,MAAO,CACV,MAAO,CACN6C,IAAK7C,KAAK,CAAC,EAAE,EAAI4C,QACjBE,WAAY3C,SAASH,KAAK,CAAC,EAAE,EAAI,IAAK,GACvC,CACD,CACA,MAAO,CAAE6C,IAAKD,QAASE,WAAY,IAAK,CACzC,CA+BA,OAAO,SAASC,4BACfC,QAAkB,EAElB,GAAIA,SAASvC,MAAM,GAAK,EAAG,CAC1B,MAAO,CAAEU,cAAe,EAAE,CAAE2B,WAAY,IAAK,CAC9C,CAEA,MAAMG,YAAcD,QAAQ,CAACA,SAASvC,MAAM,CAAG,EAAE,CACjD,MAAMyC,OAASP,gBAAgBM,aAE/B,GAAIC,OAAOJ,UAAU,GAAK,KAAM,CAC/B,MAAM3B,cAAgB,IAAI6B,SAASG,KAAK,CAAC,EAAG,CAAC,GAAID,OAAOL,GAAG,CAAC,CAC5D,MAAO,CAAE1B,cAAe2B,WAAYI,OAAOJ,UAAU,AAAC,CACvD,CAEA,MAAO,CAAE3B,cAAe6B,SAAUF,WAAY,IAAK,CACpD"}
1
+ {"version":3,"sources":["../../src/parser.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport { TemplateParseError } from \"./errors.ts\";\n\n// ─── Root Token ──────────────────────────────────────────────────────────────\n// Special token `$root` references the entire input schema / data context.\n// It cannot be followed by property access (e.g. `$root.name` is invalid).\nexport const ROOT_TOKEN = \"$root\";\n\n// ─── Regex for detecting a template identifier (e.g. \"meetingId:1\") ──────────\n// The identifier is always an integer (positive, zero, or negative),\n// separated from the variable name by a `:`. The `:` and number are on\n// the **last** segment of the path (Handlebars splits on `.`).\nconst IDENTIFIER_RE = /^(.+):(-?\\d+)$/;\n\n// ─── Template Parser ─────────────────────────────────────────────────────────\n// Thin wrapper around the Handlebars parser. Centralizing the parser call\n// here allows us to:\n// 1. Wrap errors into our own hierarchy (`TemplateParseError`)\n// 2. Expose AST introspection helpers (e.g. `isSingleExpression`)\n// 3. Isolate the direct Handlebars dependency from the rest of the codebase\n//\n// AST caching is handled at the `Typebars` instance level (via its own\n// configurable LRU cache), not here. This module only parses and wraps errors.\n\n// ─── Regex for detecting a numeric literal (integer or decimal, signed) ──────\n// Intentionally conservative: no scientific notation (1e5), no hex (0xFF),\n// no separators (1_000). We only want to recognize what a human would write\n// as a numeric value in a template.\nconst NUMERIC_LITERAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Parses a template string and returns the Handlebars AST.\n *\n * This function does not cache results — caching is managed at the\n * `Typebars` instance level via its own configurable LRU cache.\n *\n * @param template - The template string to parse (e.g. `\"Hello {{name}}\"`)\n * @returns The root AST node (`hbs.AST.Program`)\n * @throws {TemplateParseError} if the template syntax is invalid\n */\nexport function parse(template: string): hbs.AST.Program {\n\ttry {\n\t\treturn Handlebars.parse(template);\n\t} catch (error: unknown) {\n\t\t// Handlebars throws a plain Error with a descriptive message.\n\t\t// We transform it into a TemplateParseError for uniform handling.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t// Handlebars sometimes includes the position in the message —\n\t\t// attempt to extract it to enrich our error.\n\t\tconst locMatch = message.match(/line\\s+(\\d+).*?column\\s+(\\d+)/i);\n\t\tconst loc = locMatch\n\t\t\t? {\n\t\t\t\t\tline: parseInt(locMatch[1] ?? \"0\", 10),\n\t\t\t\t\tcolumn: parseInt(locMatch[2] ?? \"0\", 10),\n\t\t\t\t}\n\t\t\t: undefined;\n\n\t\tthrow new TemplateParseError(message, loc);\n\t}\n}\n\n/**\n * Determines whether the AST represents a template consisting of a single\n * expression `{{expression}}` with no text content around it.\n *\n * This matters for return type inference:\n * - Template `{{value}}` → returns the raw type of `value` (number, object…)\n * - Template `Hello {{name}}` → always returns `string` (concatenation)\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template is a single expression\n */\nexport function isSingleExpression(ast: hbs.AST.Program): boolean {\n\tconst { body } = ast;\n\n\t// Exactly one node, and it's a MustacheStatement (not a block, not text)\n\treturn body.length === 1 && body[0]?.type === \"MustacheStatement\";\n}\n\n/**\n * Extracts the path segments from a Handlebars `PathExpression`.\n *\n * Handlebars decomposes `user.address.city` into `{ parts: [\"user\", \"address\", \"city\"] }`.\n * This function safely extracts those segments.\n *\n * @param expr - The expression to extract the path from\n * @returns The path segments, or an empty array if the expression is not\n * a `PathExpression`\n */\nexport function extractPathSegments(expr: hbs.AST.Expression): string[] {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).parts;\n\t}\n\treturn [];\n}\n\n/**\n * Checks whether an AST expression is a `PathExpression` pointing to `this`\n * (used inside `{{#each}}` blocks).\n */\nexport function isThisExpression(expr: hbs.AST.Expression): boolean {\n\tif (expr.type !== \"PathExpression\") return false;\n\tconst path = expr as hbs.AST.PathExpression;\n\treturn path.original === \"this\" || path.original === \".\";\n}\n\n/**\n * Checks whether an AST expression is a `PathExpression` whose first\n * segment is the `$root` token.\n *\n * This covers both the valid `{{$root}}` case (single segment) and the\n * invalid `{{$root.name}}` case (multiple segments). The caller must\n * check `parts.length` to distinguish valid from invalid usage.\n *\n * **Note:** This does NOT match `{{$root:2}}` because Handlebars parses\n * it as `parts: [\"$root:2\"]` (identifier still attached). Use\n * `isRootSegments()` on cleaned segments instead when identifier\n * extraction has already been performed.\n */\nexport function isRootExpression(expr: hbs.AST.Expression): boolean {\n\tif (expr.type !== \"PathExpression\") return false;\n\tconst path = expr as hbs.AST.PathExpression;\n\treturn path.parts.length > 0 && path.parts[0] === ROOT_TOKEN;\n}\n\n/**\n * Checks whether an array of **cleaned** path segments represents a\n * `$root` reference (i.e. exactly one segment equal to `\"$root\"`).\n *\n * This is the segment-level counterpart of `isRootExpression()` and is\n * meant to be called **after** `extractExpressionIdentifier()` has\n * stripped the `:N` suffix. It correctly handles both `{{$root}}` and\n * `{{$root:2}}`.\n *\n * @param cleanSegments - Path segments with identifiers already removed\n * @returns `true` if the segments represent a `$root` reference\n */\nexport function isRootSegments(cleanSegments: string[]): boolean {\n\treturn cleanSegments.length === 1 && cleanSegments[0] === ROOT_TOKEN;\n}\n\n/**\n * Checks whether cleaned segments represent a **path traversal** on\n * `$root` (e.g. `$root.name`, `$root.address.city`).\n *\n * Path traversal on `$root` is forbidden — users should write `{{name}}`\n * instead of `{{$root.name}}`.\n *\n * @param cleanSegments - Path segments with identifiers already removed\n * @returns `true` if the first segment is `$root` and there are additional segments\n */\nexport function isRootPathTraversal(cleanSegments: string[]): boolean {\n\treturn cleanSegments.length > 1 && cleanSegments[0] === ROOT_TOKEN;\n}\n\n// ─── Filtering Semantically Significant Nodes ───────────────────────────────\n// In a Handlebars AST, formatting (newlines, indentation) produces\n// `ContentStatement` nodes whose value is purely whitespace. These nodes\n// have no semantic impact and must be ignored during type inference to\n// correctly detect \"effectively a single block\" or \"effectively a single\n// expression\" cases.\n\n/**\n * Returns the semantically significant statements of a Program by\n * filtering out `ContentStatement` nodes that contain only whitespace.\n */\nexport function getEffectiveBody(\n\tprogram: hbs.AST.Program,\n): hbs.AST.Statement[] {\n\treturn program.body.filter(\n\t\t(s) =>\n\t\t\t!(\n\t\t\t\ts.type === \"ContentStatement\" &&\n\t\t\t\t(s as hbs.AST.ContentStatement).value.trim() === \"\"\n\t\t\t),\n\t);\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `BlockStatement` (ignoring surrounding whitespace).\n *\n * Recognized examples:\n * ```\n * {{#if x}}...{{/if}}\n *\n * {{#each items}}...{{/each}}\n * ```\n *\n * @returns The single `BlockStatement`, or `null` if the program contains\n * other significant nodes.\n */\nexport function getEffectivelySingleBlock(\n\tprogram: hbs.AST.Program,\n): hbs.AST.BlockStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"BlockStatement\") {\n\t\treturn effective[0] as hbs.AST.BlockStatement;\n\t}\n\treturn null;\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `MustacheStatement` (ignoring surrounding whitespace).\n *\n * Example: ` {{age}} ` → true\n */\nexport function getEffectivelySingleExpression(\n\tprogram: hbs.AST.Program,\n): hbs.AST.MustacheStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"MustacheStatement\") {\n\t\treturn effective[0] as hbs.AST.MustacheStatement;\n\t}\n\treturn null;\n}\n\n// ─── Handlebars Expression Detection ─────────────────────────────────────────\n// Fast heuristic to determine whether a string contains Handlebars expressions.\n// Used by `excludeTemplateExpression` filtering to skip dynamic entries.\n\n/**\n * Determines whether a string contains Handlebars expressions.\n *\n * A string contains template expressions if it includes `{{` (opening\n * delimiter for Handlebars mustache/block statements). This is a fast\n * substring check — **no parsing is performed**.\n *\n * Used by `excludeTemplateExpression` to filter out properties whose\n * values are dynamic templates.\n *\n * **Limitation:** This is a simple `includes(\"{{\")` check, not a full\n * parser. It will produce false positives for strings that contain `{{`\n * as literal text (e.g. `\"Use {{name}} syntax\"` in documentation strings)\n * or in escaped contexts. For the current use case (filtering object/array\n * entries that are likely templates), this trade-off is acceptable because:\n * - It avoids the cost of parsing every string value\n * - False positives only cause an entry to be excluded from the output\n * schema (conservative behavior)\n *\n * @param value - The string to check\n * @returns `true` if the string contains at least one `{{` sequence\n */\nexport function hasHandlebarsExpression(value: string): boolean {\n\treturn value.includes(\"{{\");\n}\n\n// ─── Fast-Path Detection ─────────────────────────────────────────────────────\n// For templates consisting only of text and simple expressions (no blocks,\n// no helpers with parameters), we can bypass Handlebars entirely and perform\n// a simple variable replacement via string concatenation.\n\n/**\n * Determines whether an AST can be executed via the fast-path (direct\n * concatenation without going through `Handlebars.compile()`).\n *\n * The fast-path is possible when the template only contains:\n * - `ContentStatement` nodes (static text)\n * - Simple `MustacheStatement` nodes (no params, no hash)\n *\n * This excludes:\n * - Block helpers (`{{#if}}`, `{{#each}}`, etc.)\n * - Inline helpers (`{{uppercase name}}`)\n * - Sub-expressions\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template can use the fast-path\n */\nexport function canUseFastPath(ast: hbs.AST.Program): boolean {\n\treturn ast.body.every(\n\t\t(s) =>\n\t\t\ts.type === \"ContentStatement\" ||\n\t\t\t(s.type === \"MustacheStatement\" &&\n\t\t\t\t(s as hbs.AST.MustacheStatement).params.length === 0 &&\n\t\t\t\t!(s as hbs.AST.MustacheStatement).hash),\n\t);\n}\n\n// ─── Literal Detection in Text Content ───────────────────────────────────────\n// When a program contains only ContentStatements (no expressions), we try\n// to detect whether the concatenated and trimmed text is a typed literal\n// (number, boolean, null). This enables correct type inference for branches\n// like `{{#if x}} 42 {{/if}}`.\n\n/**\n * Attempts to detect the type of a raw text literal.\n *\n * @param text - The trimmed text from a ContentStatement or group of ContentStatements\n * @returns The detected JSON Schema type, or `null` if it's free-form text (string).\n */\nexport function detectLiteralType(\n\ttext: string,\n): \"number\" | \"boolean\" | \"null\" | null {\n\tif (NUMERIC_LITERAL_RE.test(text)) return \"number\";\n\tif (text === \"true\" || text === \"false\") return \"boolean\";\n\tif (text === \"null\") return \"null\";\n\treturn null;\n}\n\n/**\n * Coerces a raw string from Handlebars rendering to its actual type\n * if it represents a literal (number, boolean, null).\n * Returns the raw (untrimmed) string otherwise.\n */\nexport function coerceLiteral(raw: string): unknown {\n\tconst trimmed = raw.trim();\n\tconst type = detectLiteralType(trimmed);\n\tif (type === \"number\") return Number(trimmed);\n\tif (type === \"boolean\") return trimmed === \"true\";\n\tif (type === \"null\") return null;\n\t// Not a typed literal — return the raw string without trimming,\n\t// as whitespace may be significant (e.g. output of an #each block).\n\treturn raw;\n}\n\n// ─── Template Identifier Parsing ─────────────────────────────────────────────\n// Syntax `{{key:N}}` where N is an integer (positive, zero, or negative).\n// The identifier allows resolving a variable from a specific data source\n// (e.g. a workflow node identified by its number).\n\n/** Result of parsing a path segment with a potential identifier */\nexport interface ParsedIdentifier {\n\t/** The variable name, without the `:N` suffix */\n\tkey: string;\n\t/** The numeric identifier, or `null` if absent */\n\tidentifier: number | null;\n}\n\n/**\n * Parses an individual path segment to extract the key and optional identifier.\n *\n * @param segment - A raw path segment (e.g. `\"meetingId:1\"` or `\"meetingId\"`)\n * @returns An object `{ key, identifier }`\n *\n * @example\n * ```\n * parseIdentifier(\"meetingId:1\") // → { key: \"meetingId\", identifier: 1 }\n * parseIdentifier(\"meetingId\") // → { key: \"meetingId\", identifier: null }\n * parseIdentifier(\"meetingId:0\") // → { key: \"meetingId\", identifier: 0 }\n * ```\n */\nexport function parseIdentifier(segment: string): ParsedIdentifier {\n\tconst match = segment.match(IDENTIFIER_RE);\n\tif (match) {\n\t\treturn {\n\t\t\tkey: match[1] ?? segment,\n\t\t\tidentifier: parseInt(match[2] ?? \"0\", 10),\n\t\t};\n\t}\n\treturn { key: segment, identifier: null };\n}\n\n/** Result of extracting the identifier from a complete expression */\nexport interface ExpressionIdentifier {\n\t/** Cleaned path segments (without the `:N` suffix on the last one) */\n\tcleanSegments: string[];\n\t/** The numeric identifier extracted from the last segment, or `null` */\n\tidentifier: number | null;\n}\n\n/**\n * Extracts the identifier from a complete expression (array of segments).\n *\n * The identifier is always on the **last** segment of the path, because\n * Handlebars splits on `.` before the `:`.\n *\n * @param segments - The raw path segments (e.g. `[\"user\", \"name:1\"]`)\n * @returns An object `{ cleanSegments, identifier }`\n *\n * @example\n * ```\n * extractExpressionIdentifier([\"meetingId:1\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"user\", \"name:1\"])\n * // → { cleanSegments: [\"user\", \"name\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"meetingId\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: null }\n * ```\n */\nexport function extractExpressionIdentifier(\n\tsegments: string[],\n): ExpressionIdentifier {\n\tif (segments.length === 0) {\n\t\treturn { cleanSegments: [], identifier: null };\n\t}\n\n\tconst lastSegment = segments[segments.length - 1] as string;\n\tconst parsed = parseIdentifier(lastSegment);\n\n\tif (parsed.identifier !== null) {\n\t\tconst cleanSegments = [...segments.slice(0, -1), parsed.key];\n\t\treturn { cleanSegments, identifier: parsed.identifier };\n\t}\n\n\treturn { cleanSegments: segments, identifier: null };\n}\n"],"names":["Handlebars","TemplateParseError","ROOT_TOKEN","IDENTIFIER_RE","NUMERIC_LITERAL_RE","parse","template","error","message","Error","String","locMatch","match","loc","line","parseInt","column","undefined","isSingleExpression","ast","body","length","type","extractPathSegments","expr","parts","isThisExpression","path","original","isRootExpression","isRootSegments","cleanSegments","isRootPathTraversal","getEffectiveBody","program","filter","s","value","trim","getEffectivelySingleBlock","effective","getEffectivelySingleExpression","hasHandlebarsExpression","includes","canUseFastPath","every","params","hash","detectLiteralType","text","test","coerceLiteral","raw","trimmed","Number","parseIdentifier","segment","key","identifier","extractExpressionIdentifier","segments","lastSegment","parsed","slice"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{UnsupportedSchemaError}from"./errors.js";import{deepEqual}from"./utils.js";export function assertNoConditionalSchema(schema,path="",visited=new Set){if(visited.has(schema))return;visited.add(schema);if(schema.if!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.then!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.else!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.properties){for(const[key,prop]of Object.entries(schema.properties)){if(prop&&typeof prop!=="boolean"){assertNoConditionalSchema(prop,`${path}/properties/${key}`,visited)}}}if(schema.additionalProperties&&typeof schema.additionalProperties==="object"){assertNoConditionalSchema(schema.additionalProperties,`${path}/additionalProperties`,visited)}if(schema.items){if(Array.isArray(schema.items)){for(let i=0;i<schema.items.length;i++){const item=schema.items[i];if(item&&typeof item!=="boolean"){assertNoConditionalSchema(item,`${path}/items/${i}`,visited)}}}else if(typeof schema.items!=="boolean"){assertNoConditionalSchema(schema.items,`${path}/items`,visited)}}for(const keyword of["allOf","anyOf","oneOf"]){const branches=schema[keyword];if(branches){for(let i=0;i<branches.length;i++){const branch=branches[i];if(branch&&typeof branch!=="boolean"){assertNoConditionalSchema(branch,`${path}/${keyword}/${i}`,visited)}}}}if(schema.not&&typeof schema.not!=="boolean"){assertNoConditionalSchema(schema.not,`${path}/not`,visited)}for(const defsKey of["definitions","$defs"]){const defs=schema[defsKey];if(defs){for(const[name,def]of Object.entries(defs)){if(def&&typeof def!=="boolean"){assertNoConditionalSchema(def,`${path}/${defsKey}/${name}`,visited)}}}}}export function resolveRef(schema,root){if(!schema.$ref)return schema;const ref=schema.$ref;const match=ref.match(/^#\/(definitions|\$defs)\/(.+)$/);if(!match){throw new Error(`Unsupported $ref format: "${ref}". Only internal #/definitions/ references are supported.`)}const defsKey=match[1];const name=match[2]??"";const defs=defsKey==="definitions"?root.definitions:root.$defs;if(!defs||!(name in defs)){throw new Error(`Cannot resolve $ref "${ref}": definition "${name}" not found.`)}const def=defs[name];if(!def||typeof def==="boolean"){throw new Error(`Cannot resolve $ref "${ref}": definition "${name}" not found.`)}return resolveRef(def,root)}function resolveSegment(schema,segment,root){const resolved=resolveRef(schema,root);if(resolved.properties&&segment in resolved.properties){const prop=resolved.properties[segment];if(prop&&typeof prop!=="boolean")return resolveRef(prop,root);if(prop===true)return{}}if(resolved.additionalProperties!==undefined&&resolved.additionalProperties!==false){if(resolved.additionalProperties===true){return{}}return resolveRef(resolved.additionalProperties,root)}const schemaType=resolved.type;const isArray=schemaType==="array"||Array.isArray(schemaType)&&schemaType.includes("array");if(isArray&&segment==="length"){return{type:"integer"}}const combinatorResult=resolveInCombinators(resolved,segment,root);if(combinatorResult)return combinatorResult;return undefined}function resolveInCombinators(schema,segment,root){if(schema.allOf){const matches=schema.allOf.filter(b=>typeof b!=="boolean").map(branch=>resolveSegment(branch,segment,root)).filter(s=>s!==undefined);if(matches.length===1)return matches[0];if(matches.length>1)return{allOf:matches}}for(const key of["anyOf","oneOf"]){if(!schema[key])continue;const matches=schema[key].filter(b=>typeof b!=="boolean").map(branch=>resolveSegment(branch,segment,root)).filter(s=>s!==undefined);if(matches.length===1)return matches[0];if(matches.length>1)return{[key]:matches}}return undefined}export function resolveSchemaPath(schema,path){if(path.length===0)return resolveRef(schema,schema);let current=resolveRef(schema,schema);const root=schema;for(const segment of path){const next=resolveSegment(current,segment,root);if(next===undefined)return undefined;current=next}return current}export function resolveArrayItems(schema,root){const resolved=resolveRef(schema,root);const schemaType=resolved.type;const isArray=schemaType==="array"||Array.isArray(schemaType)&&schemaType.includes("array");if(!isArray&&resolved.items===undefined){return undefined}if(resolved.items===undefined){return{}}if(typeof resolved.items==="boolean"){return{}}if(Array.isArray(resolved.items)){const schemas=resolved.items.filter(item=>typeof item!=="boolean").map(item=>resolveRef(item,root));if(schemas.length===0)return{};return{oneOf:schemas}}return resolveRef(resolved.items,root)}export function simplifySchema(schema){for(const key of["oneOf","anyOf"]){const arr=schema[key];if(arr&&arr.length===1){const first=arr[0];if(first!==undefined&&typeof first!=="boolean")return simplifySchema(first)}}if(schema.allOf&&schema.allOf.length===1){const first=schema.allOf[0];if(first!==undefined&&typeof first!=="boolean")return simplifySchema(first)}for(const key of["oneOf","anyOf"]){const arr=schema[key];if(arr&&arr.length>1){const unique=[];for(const entry of arr){if(typeof entry==="boolean")continue;const isDuplicate=unique.some(existing=>deepEqual(existing,entry));if(!isDuplicate){unique.push(simplifySchema(entry))}}if(unique.length===1)return unique[0];return{...schema,[key]:unique}}}return schema}
1
+ import{UnsupportedSchemaError}from"./errors.js";import{deepEqual}from"./utils.js";export function assertNoConditionalSchema(schema,path="",visited=new Set){if(visited.has(schema))return;visited.add(schema);if(schema.if!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.then!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.else!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.properties){for(const[key,prop]of Object.entries(schema.properties)){if(prop&&typeof prop!=="boolean"){assertNoConditionalSchema(prop,`${path}/properties/${key}`,visited)}}}if(schema.additionalProperties&&typeof schema.additionalProperties==="object"){assertNoConditionalSchema(schema.additionalProperties,`${path}/additionalProperties`,visited)}if(schema.items){if(Array.isArray(schema.items)){for(let i=0;i<schema.items.length;i++){const item=schema.items[i];if(item&&typeof item!=="boolean"){assertNoConditionalSchema(item,`${path}/items/${i}`,visited)}}}else if(typeof schema.items!=="boolean"){assertNoConditionalSchema(schema.items,`${path}/items`,visited)}}for(const keyword of["allOf","anyOf","oneOf"]){const branches=schema[keyword];if(branches){for(let i=0;i<branches.length;i++){const branch=branches[i];if(branch&&typeof branch!=="boolean"){assertNoConditionalSchema(branch,`${path}/${keyword}/${i}`,visited)}}}}if(schema.not&&typeof schema.not!=="boolean"){assertNoConditionalSchema(schema.not,`${path}/not`,visited)}for(const defsKey of["definitions","$defs"]){const defs=schema[defsKey];if(defs){for(const[name,def]of Object.entries(defs)){if(def&&typeof def!=="boolean"){assertNoConditionalSchema(def,`${path}/${defsKey}/${name}`,visited)}}}}}export function resolveRef(schema,root){if(!schema.$ref)return schema;const ref=schema.$ref;const match=ref.match(/^#\/(definitions|\$defs)\/(.+)$/);if(!match){throw new Error(`Unsupported $ref format: "${ref}". Only internal #/definitions/ references are supported.`)}const defsKey=match[1];const name=match[2]??"";const defs=defsKey==="definitions"?root.definitions:root.$defs;if(!defs||!(name in defs)){throw new Error(`Cannot resolve $ref "${ref}": definition "${name}" not found.`)}const def=defs[name];if(!def||typeof def==="boolean"){throw new Error(`Cannot resolve $ref "${ref}": definition "${name}" not found.`)}return resolveRef(def,root)}function resolveSegment(schema,segment,root){const resolved=resolveRef(schema,root);if(resolved.properties&&segment in resolved.properties){const prop=resolved.properties[segment];if(prop&&typeof prop!=="boolean")return resolveRef(prop,root);if(prop===true)return{}}if(resolved.additionalProperties!==undefined&&resolved.additionalProperties!==false){if(resolved.additionalProperties===true){return{}}return resolveRef(resolved.additionalProperties,root)}const schemaType=resolved.type;const isArray=schemaType==="array"||Array.isArray(schemaType)&&schemaType.includes("array");if(isArray&&segment==="length"){return{type:"integer"}}if(isArray&&/^\d+$/.test(segment)){if(resolved.items===undefined){return{}}if(typeof resolved.items==="boolean"){return{}}if(Array.isArray(resolved.items)){const idx=Number.parseInt(segment,10);const item=resolved.items[idx];if(item!==undefined&&typeof item!=="boolean"){return resolveRef(item,root)}if(item!==undefined&&typeof item==="boolean"){return{}}if(resolved.additionalItems===false){return undefined}if(resolved.additionalItems!==undefined&&resolved.additionalItems!==true&&typeof resolved.additionalItems==="object"){return resolveRef(resolved.additionalItems,root)}return{}}return resolveRef(resolved.items,root)}const combinatorResult=resolveInCombinators(resolved,segment,root);if(combinatorResult)return combinatorResult;return undefined}function resolveInCombinators(schema,segment,root){if(schema.allOf){const matches=schema.allOf.filter(b=>typeof b!=="boolean").map(branch=>resolveSegment(branch,segment,root)).filter(s=>s!==undefined);if(matches.length===1)return matches[0];if(matches.length>1)return{allOf:matches}}for(const key of["anyOf","oneOf"]){if(!schema[key])continue;const matches=schema[key].filter(b=>typeof b!=="boolean").map(branch=>resolveSegment(branch,segment,root)).filter(s=>s!==undefined);if(matches.length===1)return matches[0];if(matches.length>1)return{[key]:matches}}return undefined}export function resolveSchemaPath(schema,path){if(path.length===0)return resolveRef(schema,schema);let current=resolveRef(schema,schema);const root=schema;for(const segment of path){const next=resolveSegment(current,segment,root);if(next===undefined)return undefined;current=next}return current}export function resolveArrayItems(schema,root){const resolved=resolveRef(schema,root);const schemaType=resolved.type;const isArray=schemaType==="array"||Array.isArray(schemaType)&&schemaType.includes("array");if(!isArray&&resolved.items===undefined){return undefined}if(resolved.items===undefined){return{}}if(typeof resolved.items==="boolean"){return{}}if(Array.isArray(resolved.items)){const schemas=resolved.items.filter(item=>typeof item!=="boolean").map(item=>resolveRef(item,root));if(schemas.length===0)return{};return{oneOf:schemas}}return resolveRef(resolved.items,root)}export function simplifySchema(schema){for(const key of["oneOf","anyOf"]){const arr=schema[key];if(arr&&arr.length===1){const first=arr[0];if(first!==undefined&&typeof first!=="boolean")return simplifySchema(first)}}if(schema.allOf&&schema.allOf.length===1){const first=schema.allOf[0];if(first!==undefined&&typeof first!=="boolean")return simplifySchema(first)}for(const key of["oneOf","anyOf"]){const arr=schema[key];if(arr&&arr.length>1){const unique=[];for(const entry of arr){if(typeof entry==="boolean")continue;const isDuplicate=unique.some(existing=>deepEqual(existing,entry));if(!isDuplicate){unique.push(simplifySchema(entry))}}if(unique.length===1)return unique[0];return{...schema,[key]:unique}}}return schema}
2
2
  //# sourceMappingURL=schema-resolver.js.map