typebars 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/analyzer.js +1 -1
- package/dist/cjs/analyzer.js.map +1 -1
- package/dist/cjs/executor.js +1 -1
- package/dist/cjs/executor.js.map +1 -1
- package/dist/cjs/helpers/array-helpers.d.ts +9 -0
- package/dist/cjs/helpers/array-helpers.js +2 -0
- package/dist/cjs/helpers/array-helpers.js.map +1 -0
- package/dist/cjs/helpers/index.d.ts +1 -0
- package/dist/cjs/helpers/index.js +1 -1
- package/dist/cjs/helpers/index.js.map +1 -1
- package/dist/cjs/typebars.js +1 -1
- package/dist/cjs/typebars.js.map +1 -1
- package/dist/esm/analyzer.js +1 -1
- package/dist/esm/analyzer.js.map +1 -1
- package/dist/esm/executor.js +1 -1
- package/dist/esm/executor.js.map +1 -1
- package/dist/esm/helpers/array-helpers.d.ts +9 -0
- package/dist/esm/helpers/array-helpers.js +2 -0
- package/dist/esm/helpers/array-helpers.js.map +1 -0
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +1 -1
- package/dist/esm/helpers/index.js.map +1 -1
- package/dist/esm/typebars.js +1 -1
- package/dist/esm/typebars.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/executor.js.map
CHANGED
|
@@ -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 { DefaultHelpers } from \"./helpers/default-helpers.ts\";\nimport { MapHelpers } from \"./helpers/map-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 {\n\tHelperDefinition,\n\tIdentifierData,\n\tTemplateInput,\n} 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/**\n\t * Data by identifier `{ [id]: { key: value } }`.\n\t *\n\t * Each identifier can map to a single object (standard) or an array\n\t * of objects (aggregated multi-version data). When the value is an\n\t * array, `{{key:N}}` extracts the property from each element.\n\t */\n\tidentifierData?: IdentifierData;\n\t/** 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 `map`) */\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?: IdentifierData,\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. `map`) 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// For conditional blocks (#if/#unless), try to evaluate the condition\n\t// and execute the selected branch directly to preserve non-string types\n\t// (e.g. arrays from `map`). Falls back to Handlebars rendering.\n\tconst singleBlock = getEffectivelySingleBlock(ast);\n\tif (singleBlock) {\n\t\tconst directResult = tryDirectBlockExecution(singleBlock, 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 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\tconst trimmed = raw.trim();\n\t\t\t\tif (trimmed === \"\") return undefined;\n\t\t\t\tconst num = Number(trimmed);\n\t\t\t\tif (Number.isNaN(num)) return undefined;\n\t\t\t\tif (targetType === \"integer\" && !Number.isInteger(num))\n\t\t\t\t\treturn undefined;\n\t\t\t\treturn num;\n\t\t\t}\n\t\t\tif (targetType === \"boolean\") {\n\t\t\t\tconst lower = raw.trim().toLowerCase();\n\t\t\t\tif (lower === \"true\") return true;\n\t\t\t\tif (lower === \"false\") return false;\n\t\t\t\treturn undefined;\n\t\t\t}\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?: IdentifierData,\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?: IdentifierData,\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. `(map 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 isMap = helperName === MapHelpers.MAP_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 `map`, the second argument is a property name literal\n\t\t\t\t\tif (isMap && 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\t// ── Aggregated identifier (array of objects) ──────────────────\n\t\t\t// When the identifier maps to an array of objects (multi-version\n\t\t\t// data), extract the property from each element to produce a\n\t\t\t// result array. E.g. {{accountId:4}} on [{accountId:\"A\"},{accountId:\"B\"}]\n\t\t\t// → [\"A\", \"B\"]\n\t\t\tif (Array.isArray(source)) {\n\t\t\t\treturn source\n\t\t\t\t\t.map((item) => resolveDataPath(item, cleanSegments))\n\t\t\t\t\t.filter((v) => v !== undefined);\n\t\t\t}\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?: IdentifierData,\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\n\t\t// object (or array for aggregated identifiers).\n\t\tmerged[`${ROOT_TOKEN}:${id}`] = idData;\n\n\t\t// ── Aggregated identifier (array of objects) ─────────────────\n\t\t// When the identifier data is an array (multi-version), we cannot\n\t\t// flatten individual properties into `\"key:N\"` keys because there\n\t\t// are multiple values per key. The array is only accessible via\n\t\t// `$root:N` (already set above). Handlebars helpers like `map`\n\t\t// can then consume it: `{{ map ($root:4) \"accountId\" }}`.\n\t\tif (Array.isArray(idData)) {\n\t\t\tcontinue;\n\t\t}\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 Block Execution ──────────────────────────────────────────────────\n// For conditional blocks (#if/#unless), we can evaluate the condition directly\n// and execute the selected branch through the type-preserving execution paths.\n// This avoids Handlebars stringification when the branch contains helpers that\n// return non-primitive values (e.g. `map` returning arrays).\n\n/**\n * Attempts to execute a conditional block directly by evaluating its condition\n * and executing the selected branch through type-preserving paths.\n *\n * Only handles `#if` and `#unless` blocks. Returns `{ value }` if the branch\n * was executed directly, or `undefined` to fall back to Handlebars rendering.\n */\nfunction tryDirectBlockExecution(\n\tblock: hbs.AST.BlockStatement,\n\tdata: unknown,\n\tctx?: ExecutorContext,\n): { value: unknown } | undefined {\n\tif (block.path.type !== \"PathExpression\") return undefined;\n\tconst helperName = (block.path as hbs.AST.PathExpression).original;\n\n\t// Only handle built-in conditional blocks\n\tif (helperName !== \"if\" && helperName !== \"unless\") return undefined;\n\tif (block.params.length !== 1) return undefined;\n\n\t// Evaluate the condition\n\tconst condition = resolveExpression(\n\t\tblock.params[0] as hbs.AST.Expression,\n\t\tdata,\n\t\tctx?.identifierData,\n\t\tctx?.helpers,\n\t);\n\n\t// Handlebars truthiness: empty arrays are falsy\n\tlet isTruthy: boolean;\n\tif (Array.isArray(condition)) {\n\t\tisTruthy = condition.length > 0;\n\t} else {\n\t\tisTruthy = !!condition;\n\t}\n\tif (helperName === \"unless\") isTruthy = !isTruthy;\n\n\tconst branch = isTruthy ? block.program : block.inverse;\n\tif (!branch) {\n\t\t// No matching branch (e.g. falsy #if with no {{else}}) → empty string\n\t\treturn { value: \"\" };\n\t}\n\n\t// Try to execute the branch as a single expression (preserves types)\n\tconst singleExpr = getEffectivelySingleExpression(branch);\n\tif (singleExpr) {\n\t\tif (singleExpr.params.length === 0 && !singleExpr.hash) {\n\t\t\treturn {\n\t\t\t\tvalue: resolveExpression(\n\t\t\t\t\tsingleExpr.path,\n\t\t\t\t\tdata,\n\t\t\t\t\tctx?.identifierData,\n\t\t\t\t\tctx?.helpers,\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t\t// Single expression with helper (e.g. {{map users \"name\"}})\n\t\tif (singleExpr.params.length > 0 || singleExpr.hash) {\n\t\t\tconst directResult = tryDirectHelperExecution(singleExpr, data, ctx);\n\t\t\tif (directResult !== undefined) return directResult;\n\t\t}\n\t}\n\n\t// Try to execute the branch as a nested conditional block (recursive)\n\tconst nestedBlock = getEffectivelySingleBlock(branch);\n\tif (nestedBlock) {\n\t\treturn tryDirectBlockExecution(nestedBlock, data, ctx);\n\t}\n\n\t// Branch is too complex for direct execution → fall back\n\treturn undefined;\n}\n\n// ─── Direct Helper Execution ─────────────────────────────────────────────────\n// Some helpers (e.g. `map`) 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\tDefaultHelpers.DEFAULT_HELPER_NAME,\n\tMapHelpers.MAP_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 `map` helper, the resolution strategy is:\n\t// - Arg 0 (array): 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 isMap = helperName === MapHelpers.MAP_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 `map`, the second argument (index 1) is a property name —\n\t\t// it must be a StringLiteral (enforced by the analyzer).\n\t\tif (isMap && 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":["clearCompilationCache","execute","executeFromAst","resolveDataPath","globalCompilationCache","LRUCache","template","data","identifierData","dispatchExecute","undefined","tpl","ast","parse","child","ctx","isSingleExpression","stmt","body","params","length","hash","resolveExpression","path","helpers","singleExpr","getEffectivelySingleExpression","directResult","tryDirectHelperExecution","value","merged","mergeDataWithIdentifiers","raw","renderWithHandlebars","coerceValue","coerceSchema","canUseFastPath","executeFastPath","singleBlock","getEffectivelySingleBlock","tryDirectBlockExecution","effective","getEffectiveBody","allContent","every","s","type","targetType","trimmed","trim","num","Number","isNaN","isInteger","lower","toLowerCase","coerceLiteral","result","String","expr","isThisExpression","subExpr","helperName","original","helper","get","isMap","MapHelpers","MAP_HELPER_NAME","resolvedArgs","i","param","push","fn","segments","extractPathSegments","TemplateRuntimeError","cleanSegments","identifier","extractExpressionIdentifier","isRootPathTraversal","isRootSegments","source","Array","isArray","map","item","filter","v","current","segment","base","ROOT_TOKEN","id","idData","Object","entries","key","compiledTemplate","cache","compilationCache","hbs","Handlebars","compiled","compile","noEscape","strict","set","error","message","Error","clear","block","condition","isTruthy","branch","program","inverse","nestedBlock","DIRECT_EXECUTION_HELPERS","Set","DefaultHelpers","DEFAULT_HELPER_NAME","has"],"mappings":"mPA6kBgBA,+BAAAA,2BA/dAC,iBAAAA,aAiCAC,wBAAAA,oBAkTAC,yBAAAA,mFAjcO,yCAES,yCACK,+CACN,4DACJ,oDAepB,sCAMkB,kGAkEzB,MAAMC,uBAAyB,IAAIC,iBAAQ,CAC1C,KAiBM,SAASJ,QACfK,QAAuB,CACvBC,IAAa,CACbC,cAA+B,EAE/B,MAAOC,GAAAA,2BAAe,EACrBH,SACAI,UAEA,AAACC,MACA,MAAMC,IAAMC,GAAAA,eAAK,EAACF,KAClB,OAAOT,eAAeU,IAAKD,IAAKJ,KAAM,CAAEC,cAAe,EACxD,EAEA,AAACM,OAAUb,QAAQa,MAAOP,KAAMC,gBAElC,CAiBO,SAASN,eACfU,GAAoB,CACpBN,QAAgB,CAChBC,IAAa,CACbQ,GAAqB,EAErB,MAAMP,eAAiBO,KAAKP,eAK5B,GAAIQ,GAAAA,4BAAkB,EAACJ,KAAM,CAC5B,MAAMK,KAAOL,IAAIM,IAAI,CAAC,EAAE,CACxB,GAAID,KAAKE,MAAM,CAACC,MAAM,GAAK,GAAK,CAACH,KAAKI,IAAI,CAAE,CAC3C,OAAOC,kBAAkBL,KAAKM,IAAI,CAAEhB,KAAMC,eAAgBO,KAAKS,QAChE,CACD,CAGA,MAAMC,WAAaC,GAAAA,wCAA8B,EAACd,KAClD,GAAIa,YAAcA,WAAWN,MAAM,CAACC,MAAM,GAAK,GAAK,CAACK,WAAWJ,IAAI,CAAE,CACrE,OAAOC,kBACNG,WAAWF,IAAI,CACfhB,KACAC,eACAO,KAAKS,QAEP,CAOA,GAAIC,YAAeA,CAAAA,WAAWN,MAAM,CAACC,MAAM,CAAG,GAAKK,WAAWJ,IAAI,AAAD,EAAI,CAKpE,MAAMM,aAAeC,yBAAyBH,WAAYlB,KAAMQ,KAChE,GAAIY,eAAiBjB,UAAW,CAC/B,OAAOiB,aAAaE,KAAK,AAC1B,CAEA,MAAMC,OAASC,yBAAyBxB,KAAMC,gBAC9C,MAAMwB,IAAMC,qBAAqB3B,SAAUwB,OAAQf,KACnD,OAAOmB,YAAYF,IAAKjB,KAAKoB,aAC9B,CAMA,GAAIC,GAAAA,wBAAc,EAACxB,MAAQA,IAAIM,IAAI,CAACE,MAAM,CAAG,EAAG,CAC/C,OAAOiB,gBAAgBzB,IAAKL,KAAMC,eACnC,CAMA,MAAM8B,YAAcC,GAAAA,mCAAyB,EAAC3B,KAC9C,GAAI0B,YAAa,CAChB,MAAMX,aAAea,wBAAwBF,YAAa/B,KAAMQ,KAChE,GAAIY,eAAiBjB,UAAW,CAC/B,OAAOiB,aAAaE,KAAK,AAC1B,CAEA,MAAMC,OAASC,yBAAyBxB,KAAMC,gBAC9C,MAAMwB,IAAMC,qBAAqB3B,SAAUwB,OAAQf,KACnD,OAAOmB,YAAYF,IAAKjB,KAAKoB,aAC9B,CAMA,MAAML,OAASC,yBAAyBxB,KAAMC,gBAC9C,MAAMwB,IAAMC,qBAAqB3B,SAAUwB,OAAQf,KAEnD,MAAM0B,UAAYC,GAAAA,0BAAgB,EAAC9B,KACnC,MAAM+B,WAAaF,UAAUG,KAAK,CAAC,AAACC,GAAMA,EAAEC,IAAI,GAAK,oBACrD,GAAIH,WAAY,CACf,OAAOT,YAAYF,IAAKjB,KAAKoB,aAC9B,CAEA,OAAOH,GACR,CAkBA,SAASE,YAAYF,GAAW,CAAEG,YAA0B,EAC3D,GAAIA,aAAc,CACjB,MAAMY,WAAaZ,aAAaW,IAAI,CACpC,GAAI,OAAOC,aAAe,SAAU,CACnC,GAAIA,aAAe,SAAU,OAAOf,IACpC,GAAIe,aAAe,UAAYA,aAAe,UAAW,CACxD,MAAMC,QAAUhB,IAAIiB,IAAI,GACxB,GAAID,UAAY,GAAI,OAAOtC,UAC3B,MAAMwC,IAAMC,OAAOH,SACnB,GAAIG,OAAOC,KAAK,CAACF,KAAM,OAAOxC,UAC9B,GAAIqC,aAAe,WAAa,CAACI,OAAOE,SAAS,CAACH,KACjD,OAAOxC,UACR,OAAOwC,GACR,CACA,GAAIH,aAAe,UAAW,CAC7B,MAAMO,MAAQtB,IAAIiB,IAAI,GAAGM,WAAW,GACpC,GAAID,QAAU,OAAQ,OAAO,KAC7B,GAAIA,QAAU,QAAS,OAAO,MAC9B,OAAO5C,SACR,CACA,GAAIqC,aAAe,OAAQ,OAAO,IACnC,CACD,CAEA,MAAOS,GAAAA,uBAAa,EAACxB,IACtB,CAiBA,SAASK,gBACRzB,GAAoB,CACpBL,IAAa,CACbC,cAA+B,EAE/B,IAAIiD,OAAS,GAEb,IAAK,MAAMxC,QAAQL,IAAIM,IAAI,CAAE,CAC5B,GAAID,KAAK6B,IAAI,GAAK,mBAAoB,CACrCW,QAAU,AAACxC,KAAkCY,KAAK,AACnD,MAAO,GAAIZ,KAAK6B,IAAI,GAAK,oBAAqB,CAC7C,MAAMjB,MAAQP,kBACb,AAACL,KAAmCM,IAAI,CACxChB,KACAC,gBAID,GAAIqB,OAAS,KAAM,CAClB4B,QAAUC,OAAO7B,MAClB,CACD,CACD,CAEA,OAAO4B,MACR,CAiBA,SAASnC,kBACRqC,IAAwB,CACxBpD,IAAa,CACbC,cAA+B,CAC/BgB,OAAuC,EAGvC,GAAIoC,GAAAA,0BAAgB,EAACD,MAAO,CAC3B,OAAOpD,IACR,CAGA,GAAIoD,KAAKb,IAAI,GAAK,gBACjB,OAAO,AAACa,KAA+B9B,KAAK,CAC7C,GAAI8B,KAAKb,IAAI,GAAK,gBACjB,OAAO,AAACa,KAA+B9B,KAAK,CAC7C,GAAI8B,KAAKb,IAAI,GAAK,iBACjB,OAAO,AAACa,KAAgC9B,KAAK,CAC9C,GAAI8B,KAAKb,IAAI,GAAK,cAAe,OAAO,KACxC,GAAIa,KAAKb,IAAI,GAAK,mBAAoB,OAAOpC,UAK7C,GAAIiD,KAAKb,IAAI,GAAK,gBAAiB,CAClC,MAAMe,QAAUF,KAChB,GAAIE,QAAQtC,IAAI,CAACuB,IAAI,GAAK,iBAAkB,CAC3C,MAAMgB,WAAa,AAACD,QAAQtC,IAAI,CAA4BwC,QAAQ,CACpE,MAAMC,OAASxC,SAASyC,IAAIH,YAC5B,GAAIE,OAAQ,CACX,MAAME,MAAQJ,aAAeK,wBAAU,CAACC,eAAe,CACvD,MAAMC,aAA0B,EAAE,CAClC,IAAK,IAAIC,EAAI,EAAGA,EAAIT,QAAQ1C,MAAM,CAACC,MAAM,CAAEkD,IAAK,CAC/C,MAAMC,MAAQV,QAAQ1C,MAAM,CAACmD,EAAE,CAE/B,GAAIJ,OAASI,IAAM,GAAKC,MAAMzB,IAAI,GAAK,gBAAiB,CACvDuB,aAAaG,IAAI,CAAC,AAACD,MAAgC1C,KAAK,CACzD,KAAO,CACNwC,aAAaG,IAAI,CAChBlD,kBAAkBiD,MAAOhE,KAAMC,eAAgBgB,SAEjD,CACD,CACA,OAAOwC,OAAOS,EAAE,IAAIJ,aACrB,CACD,CAEA,OAAO3D,SACR,CAGA,MAAMgE,SAAWC,GAAAA,6BAAmB,EAAChB,MACrC,GAAIe,SAAStD,MAAM,GAAK,EAAG,CAC1B,MAAM,IAAIwD,8BAAoB,CAC7B,CAAC,mCAAmC,EAAEjB,KAAKb,IAAI,CAAC,CAAC,CAAC,CAEpD,CAKA,KAAM,CAAE+B,aAAa,CAAEC,UAAU,CAAE,CAAGC,GAAAA,qCAA2B,EAACL,UAIlE,GAAIM,GAAAA,6BAAmB,EAACH,eAAgB,CACvC,OAAOnE,SACR,CAGA,GAAIuE,GAAAA,wBAAc,EAACJ,eAAgB,CAClC,GAAIC,aAAe,MAAQtE,eAAgB,CAC1C,MAAM0E,OAAS1E,cAAc,CAACsE,WAAW,CACzC,OAAOI,QAAUxE,SAClB,CACA,GAAIoE,aAAe,KAAM,CAExB,OAAOpE,SACR,CACA,OAAOH,IACR,CAEA,GAAIuE,aAAe,MAAQtE,eAAgB,CAC1C,MAAM0E,OAAS1E,cAAc,CAACsE,WAAW,CACzC,GAAII,OAAQ,CAMX,GAAIC,MAAMC,OAAO,CAACF,QAAS,CAC1B,OAAOA,OACLG,GAAG,CAAC,AAACC,MAASnF,gBAAgBmF,KAAMT,gBACpCU,MAAM,CAAC,AAACC,GAAMA,IAAM9E,UACvB,CACA,OAAOP,gBAAgB+E,OAAQL,cAChC,CAEA,OAAOnE,SACR,CAEA,GAAIoE,aAAe,MAAQ,CAACtE,eAAgB,CAE3C,OAAOE,SACR,CAEA,OAAOP,gBAAgBI,KAAMsE,cAC9B,CAUO,SAAS1E,gBAAgBI,IAAa,CAAEmE,QAAkB,EAChE,IAAIe,QAAmBlF,KAEvB,IAAK,MAAMmF,WAAWhB,SAAU,CAC/B,GAAIe,UAAY,MAAQA,UAAY/E,UAAW,CAC9C,OAAOA,SACR,CAEA,GAAI,OAAO+E,UAAY,SAAU,CAChC,OAAO/E,SACR,CAEA+E,QAAU,AAACA,OAAmC,CAACC,QAAQ,AACxD,CAEA,OAAOD,OACR,CA2BA,SAAS1D,yBACRxB,IAAa,CACbC,cAA+B,EAO/B,MAAMmF,KACLpF,OAAS,MAAQ,OAAOA,OAAS,UAAY,CAAC4E,MAAMC,OAAO,CAAC7E,MACxDA,KACD,CAAC,EACL,MAAMuB,OAAkC,CAAE,GAAG6D,IAAI,CAAE,CAACC,oBAAU,CAAC,CAAErF,IAAK,EAEtE,GAAI,CAACC,eAAgB,OAAOsB,OAE5B,IAAK,KAAM,CAAC+D,GAAIC,OAAO,GAAIC,OAAOC,OAAO,CAACxF,gBAAiB,CAK1DsB,MAAM,CAAC,CAAC,EAAE8D,oBAAU,CAAC,CAAC,EAAEC,GAAG,CAAC,CAAC,CAAGC,OAQhC,GAAIX,MAAMC,OAAO,CAACU,QAAS,CAC1B,QACD,CAEA,IAAK,KAAM,CAACG,IAAKpE,MAAM,GAAIkE,OAAOC,OAAO,CAACF,QAAS,CAClDhE,MAAM,CAAC,CAAC,EAAEmE,IAAI,CAAC,EAAEJ,GAAG,CAAC,CAAC,CAAGhE,KAC1B,CACD,CAEA,OAAOC,MACR,CAmBA,SAASG,qBACR3B,QAAgB,CAChBC,IAA6B,CAC7BQ,GAAqB,EAErB,GAAI,CAEH,GAAIA,KAAKmF,iBAAkB,CAC1B,OAAOnF,IAAImF,gBAAgB,CAAC3F,KAC7B,CAGA,MAAM4F,MAAQpF,KAAKqF,kBAAoBhG,uBACvC,MAAMiG,IAAMtF,KAAKsF,KAAOC,mBAAU,CAElC,IAAIC,SAAWJ,MAAMlC,GAAG,CAAC3D,UACzB,GAAI,CAACiG,SAAU,CACdA,SAAWF,IAAIG,OAAO,CAAClG,SAAU,CAGhCmG,SAAU,KAEVC,OAAQ,KACT,GACAP,MAAMQ,GAAG,CAACrG,SAAUiG,SACrB,CAEA,OAAOA,SAAShG,KACjB,CAAE,MAAOqG,MAAgB,CACxB,MAAMC,QAAUD,iBAAiBE,MAAQF,MAAMC,OAAO,CAAGnD,OAAOkD,MAChE,OAAM,IAAIhC,8BAAoB,CAACiC,QAChC,CACD,CAMO,SAAS7G,wBACfI,uBAAuB2G,KAAK,EAC7B,CAeA,SAASvE,wBACRwE,KAA6B,CAC7BzG,IAAa,CACbQ,GAAqB,EAErB,GAAIiG,MAAMzF,IAAI,CAACuB,IAAI,GAAK,iBAAkB,OAAOpC,UACjD,MAAMoD,WAAa,AAACkD,MAAMzF,IAAI,CAA4BwC,QAAQ,CAGlE,GAAID,aAAe,MAAQA,aAAe,SAAU,OAAOpD,UAC3D,GAAIsG,MAAM7F,MAAM,CAACC,MAAM,GAAK,EAAG,OAAOV,UAGtC,MAAMuG,UAAY3F,kBACjB0F,MAAM7F,MAAM,CAAC,EAAE,CACfZ,KACAQ,KAAKP,eACLO,KAAKS,SAIN,IAAI0F,SACJ,GAAI/B,MAAMC,OAAO,CAAC6B,WAAY,CAC7BC,SAAWD,UAAU7F,MAAM,CAAG,CAC/B,KAAO,CACN8F,SAAW,CAAC,CAACD,SACd,CACA,GAAInD,aAAe,SAAUoD,SAAW,CAACA,SAEzC,MAAMC,OAASD,SAAWF,MAAMI,OAAO,CAAGJ,MAAMK,OAAO,CACvD,GAAI,CAACF,OAAQ,CAEZ,MAAO,CAAEtF,MAAO,EAAG,CACpB,CAGA,MAAMJ,WAAaC,GAAAA,wCAA8B,EAACyF,QAClD,GAAI1F,WAAY,CACf,GAAIA,WAAWN,MAAM,CAACC,MAAM,GAAK,GAAK,CAACK,WAAWJ,IAAI,CAAE,CACvD,MAAO,CACNQ,MAAOP,kBACNG,WAAWF,IAAI,CACfhB,KACAQ,KAAKP,eACLO,KAAKS,QAEP,CACD,CAEA,GAAIC,WAAWN,MAAM,CAACC,MAAM,CAAG,GAAKK,WAAWJ,IAAI,CAAE,CACpD,MAAMM,aAAeC,yBAAyBH,WAAYlB,KAAMQ,KAChE,GAAIY,eAAiBjB,UAAW,OAAOiB,YACxC,CACD,CAGA,MAAM2F,YAAc/E,GAAAA,mCAAyB,EAAC4E,QAC9C,GAAIG,YAAa,CAChB,OAAO9E,wBAAwB8E,YAAa/G,KAAMQ,IACnD,CAGA,OAAOL,SACR,CAQA,MAAM6G,yBAA2B,IAAIC,IAAY,CAChDC,gCAAc,CAACC,mBAAmB,CAClCvD,wBAAU,CAACC,eAAe,CAC1B,EAYD,SAASxC,yBACRX,IAA+B,CAC/BV,IAAa,CACbQ,GAAqB,EAGrB,GAAIE,KAAKM,IAAI,CAACuB,IAAI,GAAK,iBAAkB,OAAOpC,UAChD,MAAMoD,WAAa,AAAC7C,KAAKM,IAAI,CAA4BwC,QAAQ,CAGjE,GAAI,CAACwD,yBAAyBI,GAAG,CAAC7D,YAAa,OAAOpD,UAGtD,MAAMsD,OAASjD,KAAKS,SAASyC,IAAIH,YACjC,GAAI,CAACE,OAAQ,OAAOtD,UASpB,MAAMwD,MAAQJ,aAAeK,wBAAU,CAACC,eAAe,CAEvD,MAAMC,aAA0B,EAAE,CAClC,IAAK,IAAIC,EAAI,EAAGA,EAAIrD,KAAKE,MAAM,CAACC,MAAM,CAAEkD,IAAK,CAC5C,MAAMC,MAAQtD,KAAKE,MAAM,CAACmD,EAAE,CAI5B,GAAIJ,OAASI,IAAM,EAAG,CACrB,GAAIC,MAAMzB,IAAI,GAAK,gBAAiB,CACnCuB,aAAaG,IAAI,CAAC,AAACD,MAAgC1C,KAAK,CACzD,KAAO,CAENwC,aAAaG,IAAI,CAChBlD,kBAAkBiD,MAAOhE,KAAMQ,KAAKP,eAAgBO,KAAKS,SAE3D,CACD,KAAO,CACN6C,aAAaG,IAAI,CAChBlD,kBAAkBiD,MAAOhE,KAAMQ,KAAKP,eAAgBO,KAAKS,SAE3D,CACD,CAGA,MAAMK,MAAQmC,OAAOS,EAAE,IAAIJ,cAC3B,MAAO,CAAExC,KAAM,CAChB"}
|
|
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 { ArrayHelpers } from \"./helpers/array-helpers.ts\";\nimport { DefaultHelpers } from \"./helpers/default-helpers.ts\";\nimport { MapHelpers } from \"./helpers/map-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 {\n\tHelperDefinition,\n\tIdentifierData,\n\tTemplateInput,\n} 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/**\n\t * Data by identifier `{ [id]: { key: value } }`.\n\t *\n\t * Each identifier can map to a single object (standard) or an array\n\t * of objects (aggregated multi-version data). When the value is an\n\t * array, `{{key:N}}` extracts the property from each element.\n\t */\n\tidentifierData?: IdentifierData;\n\t/** 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 `map`) */\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?: IdentifierData,\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. `map`) 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// For conditional blocks (#if/#unless), try to evaluate the condition\n\t// and execute the selected branch directly to preserve non-string types\n\t// (e.g. arrays from `map`). Falls back to Handlebars rendering.\n\tconst singleBlock = getEffectivelySingleBlock(ast);\n\tif (singleBlock) {\n\t\tconst directResult = tryDirectBlockExecution(singleBlock, 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 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\tconst trimmed = raw.trim();\n\t\t\t\tif (trimmed === \"\") return undefined;\n\t\t\t\tconst num = Number(trimmed);\n\t\t\t\tif (Number.isNaN(num)) return undefined;\n\t\t\t\tif (targetType === \"integer\" && !Number.isInteger(num))\n\t\t\t\t\treturn undefined;\n\t\t\t\treturn num;\n\t\t\t}\n\t\t\tif (targetType === \"boolean\") {\n\t\t\t\tconst lower = raw.trim().toLowerCase();\n\t\t\t\tif (lower === \"true\") return true;\n\t\t\t\tif (lower === \"false\") return false;\n\t\t\t\treturn undefined;\n\t\t\t}\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?: IdentifierData,\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?: IdentifierData,\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. `(map 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 isMap = helperName === MapHelpers.MAP_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 `map`, the second argument is a property name literal\n\t\t\t\t\tif (isMap && 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\t// ── Aggregated identifier (array of objects) ──────────────────\n\t\t\t// When the identifier maps to an array of objects (multi-version\n\t\t\t// data), extract the property from each element to produce a\n\t\t\t// result array. E.g. {{accountId:4}} on [{accountId:\"A\"},{accountId:\"B\"}]\n\t\t\t// → [\"A\", \"B\"]\n\t\t\tif (Array.isArray(source)) {\n\t\t\t\treturn source\n\t\t\t\t\t.map((item) => resolveDataPath(item, cleanSegments))\n\t\t\t\t\t.filter((v) => v !== undefined);\n\t\t\t}\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?: IdentifierData,\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\n\t\t// object (or array for aggregated identifiers).\n\t\tmerged[`${ROOT_TOKEN}:${id}`] = idData;\n\n\t\t// ── Aggregated identifier (array of objects) ─────────────────\n\t\t// When the identifier data is an array (multi-version), we cannot\n\t\t// flatten individual properties into `\"key:N\"` keys because there\n\t\t// are multiple values per key. The array is only accessible via\n\t\t// `$root:N` (already set above). Handlebars helpers like `map`\n\t\t// can then consume it: `{{ map ($root:4) \"accountId\" }}`.\n\t\tif (Array.isArray(idData)) {\n\t\t\tcontinue;\n\t\t}\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 Block Execution ──────────────────────────────────────────────────\n// For conditional blocks (#if/#unless), we can evaluate the condition directly\n// and execute the selected branch through the type-preserving execution paths.\n// This avoids Handlebars stringification when the branch contains helpers that\n// return non-primitive values (e.g. `map` returning arrays).\n\n/**\n * Attempts to execute a conditional block directly by evaluating its condition\n * and executing the selected branch through type-preserving paths.\n *\n * Only handles `#if` and `#unless` blocks. Returns `{ value }` if the branch\n * was executed directly, or `undefined` to fall back to Handlebars rendering.\n */\nfunction tryDirectBlockExecution(\n\tblock: hbs.AST.BlockStatement,\n\tdata: unknown,\n\tctx?: ExecutorContext,\n): { value: unknown } | undefined {\n\tif (block.path.type !== \"PathExpression\") return undefined;\n\tconst helperName = (block.path as hbs.AST.PathExpression).original;\n\n\t// Only handle built-in conditional blocks\n\tif (helperName !== \"if\" && helperName !== \"unless\") return undefined;\n\tif (block.params.length !== 1) return undefined;\n\n\t// Evaluate the condition\n\tconst condition = resolveExpression(\n\t\tblock.params[0] as hbs.AST.Expression,\n\t\tdata,\n\t\tctx?.identifierData,\n\t\tctx?.helpers,\n\t);\n\n\t// Handlebars truthiness: empty arrays are falsy\n\tlet isTruthy: boolean;\n\tif (Array.isArray(condition)) {\n\t\tisTruthy = condition.length > 0;\n\t} else {\n\t\tisTruthy = !!condition;\n\t}\n\tif (helperName === \"unless\") isTruthy = !isTruthy;\n\n\tconst branch = isTruthy ? block.program : block.inverse;\n\tif (!branch) {\n\t\t// No matching branch (e.g. falsy #if with no {{else}}) → empty string\n\t\treturn { value: \"\" };\n\t}\n\n\t// Try to execute the branch as a single expression (preserves types)\n\tconst singleExpr = getEffectivelySingleExpression(branch);\n\tif (singleExpr) {\n\t\tif (singleExpr.params.length === 0 && !singleExpr.hash) {\n\t\t\treturn {\n\t\t\t\tvalue: resolveExpression(\n\t\t\t\t\tsingleExpr.path,\n\t\t\t\t\tdata,\n\t\t\t\t\tctx?.identifierData,\n\t\t\t\t\tctx?.helpers,\n\t\t\t\t),\n\t\t\t};\n\t\t}\n\t\t// Single expression with helper (e.g. {{map users \"name\"}})\n\t\tif (singleExpr.params.length > 0 || singleExpr.hash) {\n\t\t\tconst directResult = tryDirectHelperExecution(singleExpr, data, ctx);\n\t\t\tif (directResult !== undefined) return directResult;\n\t\t}\n\t}\n\n\t// Try to execute the branch as a nested conditional block (recursive)\n\tconst nestedBlock = getEffectivelySingleBlock(branch);\n\tif (nestedBlock) {\n\t\treturn tryDirectBlockExecution(nestedBlock, data, ctx);\n\t}\n\n\t// Branch is too complex for direct execution → fall back\n\treturn undefined;\n}\n\n// ─── Direct Helper Execution ─────────────────────────────────────────────────\n// Some helpers (e.g. `map`) 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\tArrayHelpers.ARRAY_HELPER_NAME,\n\tDefaultHelpers.DEFAULT_HELPER_NAME,\n\tMapHelpers.MAP_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 `map` helper, the resolution strategy is:\n\t// - Arg 0 (array): 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 isMap = helperName === MapHelpers.MAP_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 `map`, the second argument (index 1) is a property name —\n\t\t// it must be a StringLiteral (enforced by the analyzer).\n\t\tif (isMap && 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":["clearCompilationCache","execute","executeFromAst","resolveDataPath","globalCompilationCache","LRUCache","template","data","identifierData","dispatchExecute","undefined","tpl","ast","parse","child","ctx","isSingleExpression","stmt","body","params","length","hash","resolveExpression","path","helpers","singleExpr","getEffectivelySingleExpression","directResult","tryDirectHelperExecution","value","merged","mergeDataWithIdentifiers","raw","renderWithHandlebars","coerceValue","coerceSchema","canUseFastPath","executeFastPath","singleBlock","getEffectivelySingleBlock","tryDirectBlockExecution","effective","getEffectiveBody","allContent","every","s","type","targetType","trimmed","trim","num","Number","isNaN","isInteger","lower","toLowerCase","coerceLiteral","result","String","expr","isThisExpression","subExpr","helperName","original","helper","get","isMap","MapHelpers","MAP_HELPER_NAME","resolvedArgs","i","param","push","fn","segments","extractPathSegments","TemplateRuntimeError","cleanSegments","identifier","extractExpressionIdentifier","isRootPathTraversal","isRootSegments","source","Array","isArray","map","item","filter","v","current","segment","base","ROOT_TOKEN","id","idData","Object","entries","key","compiledTemplate","cache","compilationCache","hbs","Handlebars","compiled","compile","noEscape","strict","set","error","message","Error","clear","block","condition","isTruthy","branch","program","inverse","nestedBlock","DIRECT_EXECUTION_HELPERS","Set","ArrayHelpers","ARRAY_HELPER_NAME","DefaultHelpers","DEFAULT_HELPER_NAME","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 ArrayHelpers extends HelperFactory {
|
|
4
|
+
/** The name used for special-case detection in the analyzer/executor */
|
|
5
|
+
static readonly ARRAY_HELPER_NAME = "array";
|
|
6
|
+
protected buildDefinitions(defs: Map<string, HelperDefinition>): void;
|
|
7
|
+
/** Registers the `array` helper */
|
|
8
|
+
private registerArray;
|
|
9
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"ArrayHelpers",{enumerable:true,get:function(){return ArrayHelpers}});const _helperfactoryts=require("./helper-factory.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function isHandlebarsOptions(value){return value!==null&&typeof value==="object"&&"hash"in value&&"name"in value}function arrayValue(...args){return args.filter(a=>!isHandlebarsOptions(a))}class ArrayHelpers extends _helperfactoryts.HelperFactory{buildDefinitions(defs){this.registerArray(defs)}registerArray(defs){defs.set(ArrayHelpers.ARRAY_HELPER_NAME,{fn:arrayValue,params:[{name:"values",description:"One or more values to collect into an array (variadic)"}],description:"Constructs an array from its arguments: {{ array name status }}"})}}_define_property(ArrayHelpers,"ARRAY_HELPER_NAME","array");
|
|
2
|
+
//# sourceMappingURL=array-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/array-helpers.ts"],"sourcesContent":["import type { HelperDefinition } from \"../types.ts\";\nimport { HelperFactory } from \"./helper-factory.ts\";\n\n// ─── ArrayHelpers ───────────────────────────────────────────────────────────\n// Provides a variadic helper that constructs an array from its arguments.\n//\n// - **`array`** — Collects all arguments into a single array.\n// Usage: `{{ array name status }}`\n// `{{ array \"a\" \"b\" \"c\" }}`\n// `{{ array (add count 1) 42 }}`\n//\n// ─── Registration ────────────────────────────────────────────────────────────\n// ArrayHelpers 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 ArrayHelpers();\n// factory.register(engine); // registers all helpers\n// factory.unregister(engine); // removes all helpers\n//\n// ─── Static Analysis ─────────────────────────────────────────────────────────\n// The `array` helper has special static analysis handling in the analyzer:\n// - At least 1 argument is required\n// - All arguments must have compatible types\n// - The inferred return type is `{ type: \"array\", items: <union of arg types> }`\n\n// ─── Internal utilities ─────────────────────────────────────────────────────\n\n/**\n * Checks whether a value is a Handlebars options object.\n * Handlebars always passes an options object as the last argument to helpers.\n */\nfunction isHandlebarsOptions(value: unknown): boolean {\n\treturn (\n\t\tvalue !== null &&\n\t\ttypeof value === \"object\" &&\n\t\t\"hash\" in (value as Record<string, unknown>) &&\n\t\t\"name\" in (value as Record<string, unknown>)\n\t);\n}\n\n/**\n * Collects all arguments into an array.\n * The trailing Handlebars options object is automatically excluded.\n */\nfunction arrayValue(...args: unknown[]): unknown[] {\n\treturn args.filter((a) => !isHandlebarsOptions(a));\n}\n\n// ─── Main class ─────────────────────────────────────────────────────────────\n\nexport class ArrayHelpers extends HelperFactory {\n\t/** The name used for special-case detection in the analyzer/executor */\n\tstatic readonly ARRAY_HELPER_NAME = \"array\";\n\n\t// ─── buildDefinitions (required by HelperFactory) ──────────────────\n\n\tprotected buildDefinitions(defs: Map<string, HelperDefinition>): void {\n\t\tthis.registerArray(defs);\n\t}\n\n\t// ── array ──────────────────────────────────────────────────────────\n\n\t/** Registers the `array` helper */\n\tprivate registerArray(defs: Map<string, HelperDefinition>): void {\n\t\tdefs.set(ArrayHelpers.ARRAY_HELPER_NAME, {\n\t\t\tfn: arrayValue,\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"values\",\n\t\t\t\t\tdescription: \"One or more values to collect into an array (variadic)\",\n\t\t\t\t},\n\t\t\t],\n\t\t\t// No static returnType — the analyzer infers it from the arguments\n\t\t\tdescription:\n\t\t\t\t\"Constructs an array from its arguments: {{ array name status }}\",\n\t\t});\n\t}\n}\n"],"names":["ArrayHelpers","isHandlebarsOptions","value","arrayValue","args","filter","a","HelperFactory","buildDefinitions","defs","registerArray","set","ARRAY_HELPER_NAME","fn","params","name","description"],"mappings":"oGAmDaA,sDAAAA,+CAlDiB,2MA+B9B,SAASC,oBAAoBC,KAAc,EAC1C,OACCA,QAAU,MACV,OAAOA,QAAU,UACjB,SAAWA,OACX,SAAWA,KAEb,CAMA,SAASC,WAAW,GAAGC,IAAe,EACrC,OAAOA,KAAKC,MAAM,CAAC,AAACC,GAAM,CAACL,oBAAoBK,GAChD,CAIO,MAAMN,qBAAqBO,8BAAa,CAM9C,AAAUC,iBAAiBC,IAAmC,CAAQ,CACrE,IAAI,CAACC,aAAa,CAACD,KACpB,CAKA,AAAQC,cAAcD,IAAmC,CAAQ,CAChEA,KAAKE,GAAG,CAACX,aAAaY,iBAAiB,CAAE,CACxCC,GAAIV,WACJW,OAAQ,CACP,CACCC,KAAM,SACNC,YAAa,wDACd,EACA,CAEDA,YACC,iEACF,EACD,CACD,CAzBC,iBAFYhB,aAEIY,oBAAoB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:Object.getOwnPropertyDescriptor(all,name).get})}_export(exports,{get DefaultHelpers(){return _defaulthelpers.DefaultHelpers},get HelperFactory(){return _helperfactory.HelperFactory},get LogicalHelpers(){return _logicalhelpers.LogicalHelpers},get MapHelpers(){return _maphelpers.MapHelpers},get MathHelpers(){return _mathhelpers.MathHelpers},get toNumber(){return _utils.toNumber}});const _defaulthelpers=require("./default-helpers.js");const _helperfactory=require("./helper-factory.js");const _logicalhelpers=require("./logical-helpers.js");const _maphelpers=require("./map-helpers.js");const _mathhelpers=require("./math-helpers.js");const _utils=require("./utils.js");
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:Object.getOwnPropertyDescriptor(all,name).get})}_export(exports,{get ArrayHelpers(){return _arrayhelpers.ArrayHelpers},get DefaultHelpers(){return _defaulthelpers.DefaultHelpers},get HelperFactory(){return _helperfactory.HelperFactory},get LogicalHelpers(){return _logicalhelpers.LogicalHelpers},get MapHelpers(){return _maphelpers.MapHelpers},get MathHelpers(){return _mathhelpers.MathHelpers},get toNumber(){return _utils.toNumber}});const _arrayhelpers=require("./array-helpers.js");const _defaulthelpers=require("./default-helpers.js");const _helperfactory=require("./helper-factory.js");const _logicalhelpers=require("./logical-helpers.js");const _maphelpers=require("./map-helpers.js");const _mathhelpers=require("./math-helpers.js");const _utils=require("./utils.js");
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/helpers/index.ts"],"sourcesContent":["export { DefaultHelpers } from \"./default-helpers\";\nexport { HelperFactory, type HelperRegistry } from \"./helper-factory\";\nexport { LogicalHelpers } from \"./logical-helpers\";\nexport { MapHelpers } from \"./map-helpers\";\nexport { MathHelpers } from \"./math-helpers\";\nexport { toNumber } from \"./utils\";\n"],"names":["DefaultHelpers","HelperFactory","LogicalHelpers","MapHelpers","MathHelpers","toNumber"],"mappings":"mPAASA,wBAAAA,8BAAc,MACdC,uBAAAA,4BAAa,MACbC,wBAAAA,8BAAc,MACdC,oBAAAA,sBAAU,MACVC,qBAAAA,wBAAW,MACXC,kBAAAA,eAAQ,
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/index.ts"],"sourcesContent":["export { ArrayHelpers } from \"./array-helpers\";\nexport { DefaultHelpers } from \"./default-helpers\";\nexport { HelperFactory, type HelperRegistry } from \"./helper-factory\";\nexport { LogicalHelpers } from \"./logical-helpers\";\nexport { MapHelpers } from \"./map-helpers\";\nexport { MathHelpers } from \"./math-helpers\";\nexport { toNumber } from \"./utils\";\n"],"names":["ArrayHelpers","DefaultHelpers","HelperFactory","LogicalHelpers","MapHelpers","MathHelpers","toNumber"],"mappings":"mPAASA,sBAAAA,0BAAY,MACZC,wBAAAA,8BAAc,MACdC,uBAAAA,4BAAa,MACbC,wBAAAA,8BAAc,MACdC,oBAAAA,sBAAU,MACVC,qBAAAA,wBAAW,MACXC,kBAAAA,eAAQ,gCANY,iDACE,kDACoB,kDACpB,+CACJ,4CACC,uCACH"}
|
package/dist/cjs/typebars.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"Typebars",{enumerable:true,get:function(){return Typebars}});const _handlebars=/*#__PURE__*/_interop_require_default(require("handlebars"));const _analyzerts=require("./analyzer.js");const _compiledtemplatets=require("./compiled-template.js");const _dispatchts=require("./dispatch.js");const _errorsts=require("./errors.js");const _executorts=require("./executor.js");const _indexts=require("./helpers/index.js");const _parserts=require("./parser.js");const _typests=require("./types.js");const _utils=require("./utils.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function _interop_require_default(obj){return obj&&obj.__esModule?obj:{default:obj}}const DIRECT_EXECUTION_HELPER_NAMES=new Set([_indexts.MapHelpers.MAP_HELPER_NAME]);function stringifyForTemplate(value){if(value===null||value===undefined)return"";if(Array.isArray(value)){return value.map(item=>{if(item===null||item===undefined)return"";if(typeof item==="object")return JSON.stringify(item);return String(item)}).join(", ")}return String(value)}class Typebars{compile(template){if((0,_typests.isArrayInput)(template)){const children=[];for(const element of template){children.push(this.compile(element))}return _compiledtemplatets.CompiledTemplate.fromArray(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isObjectInput)(template)){const children={};for(const[key,value]of Object.entries(template)){children[key]=this.compile(value)}return _compiledtemplatets.CompiledTemplate.fromObject(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isLiteralInput)(template)){return _compiledtemplatets.CompiledTemplate.fromLiteral(template,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}const ast=this.getCachedAst(template);const options={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return _compiledtemplatets.CompiledTemplate.fromTemplate(ast,template,options)}analyze(template,inputSchema={},options){return(0,_dispatchts.dispatchAnalyze)(template,options,(tpl,coerceSchema)=>{const ast=this.getCachedAst(tpl);return(0,_analyzerts.analyzeFromAst)(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers,coerceSchema})},(child,childOptions)=>this.analyze(child,inputSchema,childOptions))}validate(template,inputSchema={},options){const analysis=this.analyze(template,inputSchema,options);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}isValidSyntax(template){if((0,_typests.isArrayInput)(template)){return template.every(v=>this.isValidSyntax(v))}if((0,_typests.isObjectInput)(template)){return Object.values(template).every(v=>this.isValidSyntax(v))}if((0,_typests.isLiteralInput)(template))return true;try{this.getCachedAst(template);return true}catch{return false}}execute(template,data,options){return(0,_dispatchts.dispatchExecute)(template,options,(tpl,coerceSchema)=>{const ast=this.getCachedAst(tpl);if(options?.schema){const analysis=(0,_analyzerts.analyzeFromAst)(ast,tpl,options.schema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers});if(!analysis.valid){throw new _errorsts.TemplateAnalysisError(analysis.diagnostics)}}return(0,_executorts.executeFromAst)(ast,tpl,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache,coerceSchema,helpers:this.helpers})},(child,childOptions)=>this.execute(child,data,{...options,...childOptions}))}analyzeAndExecute(template,inputSchema={},data,options){return(0,_dispatchts.dispatchAnalyzeAndExecute)(template,options,(tpl,coerceSchema)=>{const ast=this.getCachedAst(tpl);const analysis=(0,_analyzerts.analyzeFromAst)(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers,coerceSchema});if(!analysis.valid){return{analysis,value:undefined}}const value=(0,_executorts.executeFromAst)(ast,tpl,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache,coerceSchema,helpers:this.helpers});return{analysis,value}},(child,childOptions)=>this.analyzeAndExecute(child,inputSchema,data,childOptions))}registerHelper(name,definition){this.helpers.set(name,definition);if(DIRECT_EXECUTION_HELPER_NAMES.has(name)){this.hbs.registerHelper(name,(...args)=>{const hbsArgs=args.slice(0,-1);const raw=definition.fn(...hbsArgs);return stringifyForTemplate(raw)})}else{this.hbs.registerHelper(name,definition.fn)}this.compilationCache.clear();return this}unregisterHelper(name){this.helpers.delete(name);this.hbs.unregisterHelper(name);this.compilationCache.clear();return this}hasHelper(name){return this.helpers.has(name)}clearCaches(){this.astCache.clear();this.compilationCache.clear()}getCachedAst(template){let ast=this.astCache.get(template);if(!ast){ast=(0,_parserts.parse)(template);this.astCache.set(template,ast)}return ast}constructor(options={}){_define_property(this,"hbs",void 0);_define_property(this,"astCache",void 0);_define_property(this,"compilationCache",void 0);_define_property(this,"helpers",new Map);this.hbs=_handlebars.default.create();this.astCache=new _utils.LRUCache(options.astCacheSize??256);this.compilationCache=new _utils.LRUCache(options.compilationCacheSize??256);new _indexts.MathHelpers().register(this);new _indexts.LogicalHelpers().register(this);new _indexts.MapHelpers().register(this);new _indexts.DefaultHelpers().register(this);if(options.helpers){for(const helper of options.helpers){const{name,...definition}=helper;this.registerHelper(name,definition)}}}}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"Typebars",{enumerable:true,get:function(){return Typebars}});const _handlebars=/*#__PURE__*/_interop_require_default(require("handlebars"));const _analyzerts=require("./analyzer.js");const _compiledtemplatets=require("./compiled-template.js");const _dispatchts=require("./dispatch.js");const _errorsts=require("./errors.js");const _executorts=require("./executor.js");const _indexts=require("./helpers/index.js");const _parserts=require("./parser.js");const _typests=require("./types.js");const _utils=require("./utils.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}function _interop_require_default(obj){return obj&&obj.__esModule?obj:{default:obj}}const DIRECT_EXECUTION_HELPER_NAMES=new Set([_indexts.ArrayHelpers.ARRAY_HELPER_NAME,_indexts.MapHelpers.MAP_HELPER_NAME]);function stringifyForTemplate(value){if(value===null||value===undefined)return"";if(Array.isArray(value)){return value.map(item=>{if(item===null||item===undefined)return"";if(typeof item==="object")return JSON.stringify(item);return String(item)}).join(", ")}return String(value)}class Typebars{compile(template){if((0,_typests.isArrayInput)(template)){const children=[];for(const element of template){children.push(this.compile(element))}return _compiledtemplatets.CompiledTemplate.fromArray(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isObjectInput)(template)){const children={};for(const[key,value]of Object.entries(template)){children[key]=this.compile(value)}return _compiledtemplatets.CompiledTemplate.fromObject(children,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}if((0,_typests.isLiteralInput)(template)){return _compiledtemplatets.CompiledTemplate.fromLiteral(template,{helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache})}const ast=this.getCachedAst(template);const options={helpers:this.helpers,hbs:this.hbs,compilationCache:this.compilationCache};return _compiledtemplatets.CompiledTemplate.fromTemplate(ast,template,options)}analyze(template,inputSchema={},options){return(0,_dispatchts.dispatchAnalyze)(template,options,(tpl,coerceSchema)=>{const ast=this.getCachedAst(tpl);return(0,_analyzerts.analyzeFromAst)(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers,coerceSchema})},(child,childOptions)=>this.analyze(child,inputSchema,childOptions))}validate(template,inputSchema={},options){const analysis=this.analyze(template,inputSchema,options);return{valid:analysis.valid,diagnostics:analysis.diagnostics}}isValidSyntax(template){if((0,_typests.isArrayInput)(template)){return template.every(v=>this.isValidSyntax(v))}if((0,_typests.isObjectInput)(template)){return Object.values(template).every(v=>this.isValidSyntax(v))}if((0,_typests.isLiteralInput)(template))return true;try{this.getCachedAst(template);return true}catch{return false}}execute(template,data,options){return(0,_dispatchts.dispatchExecute)(template,options,(tpl,coerceSchema)=>{const ast=this.getCachedAst(tpl);if(options?.schema){const analysis=(0,_analyzerts.analyzeFromAst)(ast,tpl,options.schema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers});if(!analysis.valid){throw new _errorsts.TemplateAnalysisError(analysis.diagnostics)}}return(0,_executorts.executeFromAst)(ast,tpl,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache,coerceSchema,helpers:this.helpers})},(child,childOptions)=>this.execute(child,data,{...options,...childOptions}))}analyzeAndExecute(template,inputSchema={},data,options){return(0,_dispatchts.dispatchAnalyzeAndExecute)(template,options,(tpl,coerceSchema)=>{const ast=this.getCachedAst(tpl);const analysis=(0,_analyzerts.analyzeFromAst)(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,helpers:this.helpers,coerceSchema});if(!analysis.valid){return{analysis,value:undefined}}const value=(0,_executorts.executeFromAst)(ast,tpl,data,{identifierData:options?.identifierData,hbs:this.hbs,compilationCache:this.compilationCache,coerceSchema,helpers:this.helpers});return{analysis,value}},(child,childOptions)=>this.analyzeAndExecute(child,inputSchema,data,childOptions))}registerHelper(name,definition){this.helpers.set(name,definition);if(DIRECT_EXECUTION_HELPER_NAMES.has(name)){this.hbs.registerHelper(name,(...args)=>{const hbsArgs=args.slice(0,-1);const raw=definition.fn(...hbsArgs);return stringifyForTemplate(raw)})}else{this.hbs.registerHelper(name,definition.fn)}this.compilationCache.clear();return this}unregisterHelper(name){this.helpers.delete(name);this.hbs.unregisterHelper(name);this.compilationCache.clear();return this}hasHelper(name){return this.helpers.has(name)}clearCaches(){this.astCache.clear();this.compilationCache.clear()}getCachedAst(template){let ast=this.astCache.get(template);if(!ast){ast=(0,_parserts.parse)(template);this.astCache.set(template,ast)}return ast}constructor(options={}){_define_property(this,"hbs",void 0);_define_property(this,"astCache",void 0);_define_property(this,"compilationCache",void 0);_define_property(this,"helpers",new Map);this.hbs=_handlebars.default.create();this.astCache=new _utils.LRUCache(options.astCacheSize??256);this.compilationCache=new _utils.LRUCache(options.compilationCacheSize??256);new _indexts.ArrayHelpers().register(this);new _indexts.MathHelpers().register(this);new _indexts.LogicalHelpers().register(this);new _indexts.MapHelpers().register(this);new _indexts.DefaultHelpers().register(this);if(options.helpers){for(const helper of options.helpers){const{name,...definition}=helper;this.registerHelper(name,definition)}}}}
|
|
2
2
|
//# sourceMappingURL=typebars.js.map
|
package/dist/cjs/typebars.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { AnalyzeOptions } from \"./analyzer.ts\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport {\n\tdispatchAnalyze,\n\tdispatchAnalyzeAndExecute,\n\tdispatchExecute,\n} from \"./dispatch.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport {\n\tDefaultHelpers,\n\tLogicalHelpers,\n\tMapHelpers,\n\tMathHelpers,\n} from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateData,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport { isArrayInput, isLiteralInput, isObjectInput } from \"./types.ts\";\nimport { LRUCache } from \"./utils\";\n\n// ─── Typebars ────────────────────────────────────────────────────────────────\n// Public entry point of the template engine. Orchestrates three phases:\n//\n// 1. **Parsing** — transforms the template string into an AST (via Handlebars)\n// 2. **Analysis** — static validation + return type inference\n// 3. **Execution** — renders the template with real data\n//\n// ─── Architecture v2 ─────────────────────────────────────────────────────────\n// - **LRU cache** for parsed ASTs and compiled Handlebars templates\n// - **Isolated Handlebars environment** per instance (custom helpers)\n// - **`compile()` pattern**: parse-once / execute-many\n// - **`validate()` method**: API shortcut without `outputSchema`\n// - **`registerHelper()`**: custom helpers with static typing\n// - **`ExecuteOptions`**: options object for `execute()`\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing variables from specific data\n// sources, identified by an integer N.\n//\n// - `identifierSchemas`: mapping `{ [id]: JSONSchema7 }` for static analysis\n// - `identifierData`: mapping `{ [id]: Record<string, unknown> }` for execution\n//\n// Usage:\n// engine.execute(\"{{meetingId:1}}\", data, { identifierData: { 1: node1Data } });\n// engine.analyze(\"{{meetingId:1}}\", schema, { identifierSchemas: { 1: node1Schema } });\n\n// ─── Direct Execution Helper Names ───────────────────────────────────────────\n// Helpers whose return values are non-primitive (arrays, objects) and must be\n// wrapped when registered with Handlebars so that mixed/block templates get a\n// human-readable string (e.g. `\", \"` joined) instead of JS default toString().\n// In single-expression mode the executor bypasses Handlebars entirely, so the\n// raw value is preserved.\nconst DIRECT_EXECUTION_HELPER_NAMES = new Set<string>([\n\tMapHelpers.MAP_HELPER_NAME,\n]);\n\n/**\n * Converts a value to a string suitable for embedding in a Handlebars template.\n *\n * - Arrays of primitives are joined with `\", \"`\n * - Arrays of objects are JSON-stringified per element, then joined\n * - Primitives use `String(value)`\n * - null/undefined become `\"\"`\n */\nfunction stringifyForTemplate(value: unknown): string {\n\tif (value === null || value === undefined) return \"\";\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map((item) => {\n\t\t\t\tif (item === null || item === undefined) return \"\";\n\t\t\t\tif (typeof item === \"object\") return JSON.stringify(item);\n\t\t\t\treturn String(item);\n\t\t\t})\n\t\t\t.join(\", \");\n\t}\n\treturn String(value);\n}\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n\nexport class Typebars {\n\t/** Isolated Handlebars environment — each engine has its own helpers */\n\tprivate readonly hbs: typeof Handlebars;\n\n\t/** LRU cache of parsed ASTs (avoids re-parsing) */\n\tprivate readonly astCache: LRUCache<string, hbs.AST.Program>;\n\n\t/** LRU cache of compiled Handlebars templates (avoids recompilation) */\n\tprivate readonly compilationCache: LRUCache<\n\t\tstring,\n\t\tHandlebarsTemplateDelegate\n\t>;\n\n\t/** Custom helpers registered on this instance */\n\tprivate readonly helpers = new Map<string, HelperDefinition>();\n\n\tconstructor(options: TemplateEngineOptions = {}) {\n\t\tthis.hbs = Handlebars.create();\n\t\tthis.astCache = new LRUCache(options.astCacheSize ?? 256);\n\t\tthis.compilationCache = new LRUCache(options.compilationCacheSize ?? 256);\n\n\t\t// ── Built-in helpers ─────────────────────────────────────────────\n\t\tnew MathHelpers().register(this);\n\t\tnew LogicalHelpers().register(this);\n\t\tnew MapHelpers().register(this);\n\t\tnew DefaultHelpers().register(this);\n\n\t\t// ── Custom helpers via options ───────────────────────────────────\n\t\tif (options.helpers) {\n\t\t\tfor (const helper of options.helpers) {\n\t\t\t\tconst { name, ...definition } = helper;\n\t\t\t\tthis.registerHelper(name, definition);\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Compilation ───────────────────────────────────────────────────────\n\n\t/**\n\t * Compiles a template and returns a `CompiledTemplate` ready to be\n\t * executed or analyzed without re-parsing.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is compiled recursively.\n\t *\n\t * @param template - The template to compile\n\t * @returns A reusable `CompiledTemplate`\n\t */\n\tcompile(template: TemplateInput): CompiledTemplate {\n\t\tif (isArrayInput(template)) {\n\t\t\tconst children: CompiledTemplate[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tchildren.push(this.compile(element));\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromArray(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\tconst children: Record<string, CompiledTemplate> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tchildren[key] = this.compile(value);\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromObject(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn CompiledTemplate.fromLiteral(template, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst options: CompiledTemplateOptions = {\n\t\t\thelpers: this.helpers,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t};\n\t\treturn CompiledTemplate.fromTemplate(ast, template, options);\n\t}\n\n\t// ─── Static Analysis ─────────────────────────────────────────────────────\n\n\t/**\n\t * Statically analyzes a template against a JSON Schema v7 describing\n\t * the available context.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is analyzed recursively and the\n\t * `outputSchema` reflects the object structure with resolved types.\n\t *\n\t * @param template - The template to analyze\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): AnalysisResult {\n\t\treturn dispatchAnalyze(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse with cache and analyze the AST\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\treturn analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyze() for child elements\n\t\t\t(child, childOptions) => this.analyze(child, inputSchema, childOptions),\n\t\t);\n\t}\n\n\t// ─── Validation ──────────────────────────────────────────────────────────\n\n\t/**\n\t * Validates a template against a schema without returning the output type.\n\t *\n\t * This is an API shortcut for `analyze()` that only returns `valid` and\n\t * `diagnostics`, without `outputSchema`. The full analysis (including type\n\t * inference) is executed internally — this method provides no performance\n\t * gain, only a simplified API.\n\t *\n\t * @param template - The template to validate\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param identifierSchemas - (optional) Schemas by identifier\n\t */\n\tvalidate(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, options);\n\t\treturn {\n\t\t\tvalid: analysis.valid,\n\t\t\tdiagnostics: analysis.diagnostics,\n\t\t};\n\t}\n\n\t// ─── Syntax Validation ───────────────────────────────────────────────────\n\n\t/**\n\t * Checks only that the template syntax is valid (parsing).\n\t * Does not require a schema — useful for quick feedback in an editor.\n\t *\n\t * For objects, recursively checks each property.\n\t *\n\t * @param template - The template to validate\n\t * @returns `true` if the template is syntactically correct\n\t */\n\tisValidSyntax(template: TemplateInput): boolean {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn template.every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn Object.values(template).every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isLiteralInput(template)) return true;\n\t\ttry {\n\t\t\tthis.getCachedAst(template);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ─── Execution ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Executes a template with the provided data.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is executed recursively and an object with\n\t * resolved values is returned.\n\t *\n\t * If a `schema` is provided in options, static analysis is performed\n\t * before execution. A `TemplateAnalysisError` is thrown on errors.\n\t *\n\t * @param template - The template to execute\n\t * @param data - The context data for rendering\n\t * @param options - Execution options (schema, identifierData, identifierSchemas)\n\t * @returns The execution result\n\t */\n\texecute(\n\t\ttemplate: TemplateInput,\n\t\tdata?: TemplateData,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\treturn dispatchExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse, optionally validate, then execute\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\n\t\t\t\t// Pre-execution static validation if a schema is provided\n\t\t\t\tif (options?.schema) {\n\t\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, options.schema, {\n\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\t});\n\t\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter execute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.execute(child, data, { ...options, ...childOptions }),\n\t\t);\n\t}\n\n\t// ─── Combined Shortcuts ──────────────────────────────────────────────────\n\n\t/**\n\t * Analyzes a template and, if valid, executes it with the provided data.\n\t * Returns both the analysis result and the executed value.\n\t *\n\t * For objects, each property is analyzed and executed recursively.\n\t * The entire object is considered invalid if at least one property is.\n\t *\n\t * @param template - The template\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param data - The context data for rendering\n\t * @param options - (optional) Options for template identifiers\n\t * @returns An object `{ analysis, value }` where `value` is `undefined`\n\t * if analysis failed.\n\t */\n\tanalyzeAndExecute(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\tdata: TemplateData,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\treturn dispatchAnalyzeAndExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — analyze then execute if valid\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\n\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\treturn { analysis, value: undefined };\n\t\t\t\t}\n\n\t\t\t\tconst value = executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t\treturn { analysis, value };\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyzeAndExecute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.analyzeAndExecute(child, inputSchema, data, childOptions),\n\t\t);\n\t}\n\n\t// ─── Custom Helper Management ──────────────────────────────────────────\n\n\t/**\n\t * Registers a custom helper on this engine instance.\n\t *\n\t * The helper is available for both execution (via Handlebars) and\n\t * static analysis (via its declared `returnType`).\n\t *\n\t * @param name - Helper name (e.g. `\"uppercase\"`)\n\t * @param definition - Helper definition (implementation + return type)\n\t * @returns `this` to allow chaining\n\t */\n\tregisterHelper(name: string, definition: HelperDefinition): this {\n\t\tthis.helpers.set(name, definition);\n\n\t\t// For helpers that return non-primitive values (arrays, objects),\n\t\t// register a Handlebars-friendly wrapper that converts the result\n\t\t// to a string. In single-expression mode the executor bypasses\n\t\t// Handlebars entirely (via tryDirectHelperExecution) and returns\n\t\t// the raw value, so this wrapper only affects mixed/block templates\n\t\t// where Handlebars renders the result into a larger string.\n\t\tif (DIRECT_EXECUTION_HELPER_NAMES.has(name)) {\n\t\t\tthis.hbs.registerHelper(name, (...args: unknown[]) => {\n\t\t\t\t// Handlebars appends an `options` object as the last argument.\n\t\t\t\t// Strip it before calling the real fn.\n\t\t\t\tconst hbsArgs = args.slice(0, -1);\n\t\t\t\tconst raw = definition.fn(...hbsArgs);\n\t\t\t\treturn stringifyForTemplate(raw);\n\t\t\t});\n\t\t} else {\n\t\t\tthis.hbs.registerHelper(name, definition.fn);\n\t\t}\n\n\t\t// Invalidate the compilation cache because helpers have changed\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a custom helper from this engine instance.\n\t *\n\t * @param name - Name of the helper to remove\n\t * @returns `this` to allow chaining\n\t */\n\tunregisterHelper(name: string): this {\n\t\tthis.helpers.delete(name);\n\t\tthis.hbs.unregisterHelper(name);\n\n\t\t// Invalidate the compilation cache\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Checks whether a helper is registered on this instance.\n\t *\n\t * @param name - Helper name\n\t * @returns `true` if the helper is registered\n\t */\n\thasHelper(name: string): boolean {\n\t\treturn this.helpers.has(name);\n\t}\n\n\t// ─── Cache Management ──────────────────────────────────────────────────\n\n\t/**\n\t * Clears all internal caches (AST + compilation).\n\t *\n\t * Useful after a configuration change or to free memory.\n\t */\n\tclearCaches(): void {\n\t\tthis.astCache.clear();\n\t\tthis.compilationCache.clear();\n\t}\n\n\t// ─── Internals ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Retrieves the AST of a template from the cache, or parses and caches it.\n\t */\n\tprivate getCachedAst(template: string): hbs.AST.Program {\n\t\tlet ast = this.astCache.get(template);\n\t\tif (!ast) {\n\t\t\tast = parse(template);\n\t\t\tthis.astCache.set(template, ast);\n\t\t}\n\t\treturn ast;\n\t}\n}\n"],"names":["Typebars","DIRECT_EXECUTION_HELPER_NAMES","Set","MapHelpers","MAP_HELPER_NAME","stringifyForTemplate","value","undefined","Array","isArray","map","item","JSON","stringify","String","join","compile","template","isArrayInput","children","element","push","CompiledTemplate","fromArray","helpers","hbs","compilationCache","isObjectInput","key","Object","entries","fromObject","isLiteralInput","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","dispatchAnalyze","tpl","coerceSchema","analyzeFromAst","identifierSchemas","child","childOptions","validate","analysis","valid","diagnostics","isValidSyntax","every","v","values","execute","data","dispatchExecute","schema","TemplateAnalysisError","executeFromAst","identifierData","analyzeAndExecute","dispatchAnalyzeAndExecute","registerHelper","name","definition","set","has","args","hbsArgs","slice","raw","fn","clear","unregisterHelper","delete","hasHelper","clearCaches","astCache","get","parse","Map","Handlebars","create","LRUCache","astCacheSize","compilationCacheSize","MathHelpers","register","LogicalHelpers","DefaultHelpers","helper"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/typebars.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport type { AnalyzeOptions } from \"./analyzer.ts\";\nimport { analyzeFromAst } from \"./analyzer.ts\";\nimport {\n\tCompiledTemplate,\n\ttype CompiledTemplateOptions,\n} from \"./compiled-template.ts\";\nimport {\n\tdispatchAnalyze,\n\tdispatchAnalyzeAndExecute,\n\tdispatchExecute,\n} from \"./dispatch.ts\";\nimport { TemplateAnalysisError } from \"./errors.ts\";\nimport { executeFromAst } from \"./executor.ts\";\nimport {\n\tArrayHelpers,\n\tDefaultHelpers,\n\tLogicalHelpers,\n\tMapHelpers,\n\tMathHelpers,\n} from \"./helpers/index.ts\";\nimport { parse } from \"./parser.ts\";\nimport type {\n\tAnalysisResult,\n\tAnalyzeAndExecuteOptions,\n\tExecuteOptions,\n\tHelperDefinition,\n\tTemplateData,\n\tTemplateEngineOptions,\n\tTemplateInput,\n\tValidationResult,\n} from \"./types.ts\";\nimport { isArrayInput, isLiteralInput, isObjectInput } from \"./types.ts\";\nimport { LRUCache } from \"./utils\";\n\n// ─── Typebars ────────────────────────────────────────────────────────────────\n// Public entry point of the template engine. Orchestrates three phases:\n//\n// 1. **Parsing** — transforms the template string into an AST (via Handlebars)\n// 2. **Analysis** — static validation + return type inference\n// 3. **Execution** — renders the template with real data\n//\n// ─── Architecture v2 ─────────────────────────────────────────────────────────\n// - **LRU cache** for parsed ASTs and compiled Handlebars templates\n// - **Isolated Handlebars environment** per instance (custom helpers)\n// - **`compile()` pattern**: parse-once / execute-many\n// - **`validate()` method**: API shortcut without `outputSchema`\n// - **`registerHelper()`**: custom helpers with static typing\n// - **`ExecuteOptions`**: options object for `execute()`\n//\n// ─── Template Identifiers ────────────────────────────────────────────────────\n// The `{{key:N}}` syntax allows referencing variables from specific data\n// sources, identified by an integer N.\n//\n// - `identifierSchemas`: mapping `{ [id]: JSONSchema7 }` for static analysis\n// - `identifierData`: mapping `{ [id]: Record<string, unknown> }` for execution\n//\n// Usage:\n// engine.execute(\"{{meetingId:1}}\", data, { identifierData: { 1: node1Data } });\n// engine.analyze(\"{{meetingId:1}}\", schema, { identifierSchemas: { 1: node1Schema } });\n\n// ─── Direct Execution Helper Names ───────────────────────────────────────────\n// Helpers whose return values are non-primitive (arrays, objects) and must be\n// wrapped when registered with Handlebars so that mixed/block templates get a\n// human-readable string (e.g. `\", \"` joined) instead of JS default toString().\n// In single-expression mode the executor bypasses Handlebars entirely, so the\n// raw value is preserved.\nconst DIRECT_EXECUTION_HELPER_NAMES = new Set<string>([\n\tArrayHelpers.ARRAY_HELPER_NAME,\n\tMapHelpers.MAP_HELPER_NAME,\n]);\n\n/**\n * Converts a value to a string suitable for embedding in a Handlebars template.\n *\n * - Arrays of primitives are joined with `\", \"`\n * - Arrays of objects are JSON-stringified per element, then joined\n * - Primitives use `String(value)`\n * - null/undefined become `\"\"`\n */\nfunction stringifyForTemplate(value: unknown): string {\n\tif (value === null || value === undefined) return \"\";\n\tif (Array.isArray(value)) {\n\t\treturn value\n\t\t\t.map((item) => {\n\t\t\t\tif (item === null || item === undefined) return \"\";\n\t\t\t\tif (typeof item === \"object\") return JSON.stringify(item);\n\t\t\t\treturn String(item);\n\t\t\t})\n\t\t\t.join(\", \");\n\t}\n\treturn String(value);\n}\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n\nexport class Typebars {\n\t/** Isolated Handlebars environment — each engine has its own helpers */\n\tprivate readonly hbs: typeof Handlebars;\n\n\t/** LRU cache of parsed ASTs (avoids re-parsing) */\n\tprivate readonly astCache: LRUCache<string, hbs.AST.Program>;\n\n\t/** LRU cache of compiled Handlebars templates (avoids recompilation) */\n\tprivate readonly compilationCache: LRUCache<\n\t\tstring,\n\t\tHandlebarsTemplateDelegate\n\t>;\n\n\t/** Custom helpers registered on this instance */\n\tprivate readonly helpers = new Map<string, HelperDefinition>();\n\n\tconstructor(options: TemplateEngineOptions = {}) {\n\t\tthis.hbs = Handlebars.create();\n\t\tthis.astCache = new LRUCache(options.astCacheSize ?? 256);\n\t\tthis.compilationCache = new LRUCache(options.compilationCacheSize ?? 256);\n\n\t\t// ── Built-in helpers ─────────────────────────────────────────────\n\t\tnew ArrayHelpers().register(this);\n\t\tnew MathHelpers().register(this);\n\t\tnew LogicalHelpers().register(this);\n\t\tnew MapHelpers().register(this);\n\t\tnew DefaultHelpers().register(this);\n\n\t\t// ── Custom helpers via options ───────────────────────────────────\n\t\tif (options.helpers) {\n\t\t\tfor (const helper of options.helpers) {\n\t\t\t\tconst { name, ...definition } = helper;\n\t\t\t\tthis.registerHelper(name, definition);\n\t\t\t}\n\t\t}\n\t}\n\n\t// ─── Compilation ───────────────────────────────────────────────────────\n\n\t/**\n\t * Compiles a template and returns a `CompiledTemplate` ready to be\n\t * executed or analyzed without re-parsing.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is compiled recursively.\n\t *\n\t * @param template - The template to compile\n\t * @returns A reusable `CompiledTemplate`\n\t */\n\tcompile(template: TemplateInput): CompiledTemplate {\n\t\tif (isArrayInput(template)) {\n\t\t\tconst children: CompiledTemplate[] = [];\n\t\t\tfor (const element of template) {\n\t\t\t\tchildren.push(this.compile(element));\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromArray(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\tconst children: Record<string, CompiledTemplate> = {};\n\t\t\tfor (const [key, value] of Object.entries(template)) {\n\t\t\t\tchildren[key] = this.compile(value);\n\t\t\t}\n\t\t\treturn CompiledTemplate.fromObject(children, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tif (isLiteralInput(template)) {\n\t\t\treturn CompiledTemplate.fromLiteral(template, {\n\t\t\t\thelpers: this.helpers,\n\t\t\t\thbs: this.hbs,\n\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t});\n\t\t}\n\t\tconst ast = this.getCachedAst(template);\n\t\tconst options: CompiledTemplateOptions = {\n\t\t\thelpers: this.helpers,\n\t\t\thbs: this.hbs,\n\t\t\tcompilationCache: this.compilationCache,\n\t\t};\n\t\treturn CompiledTemplate.fromTemplate(ast, template, options);\n\t}\n\n\t// ─── Static Analysis ─────────────────────────────────────────────────────\n\n\t/**\n\t * Statically analyzes a template against a JSON Schema v7 describing\n\t * the available context.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is analyzed recursively and the\n\t * `outputSchema` reflects the object structure with resolved types.\n\t *\n\t * @param template - The template to analyze\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param options - (optional) Analysis options (identifierSchemas, coerceSchema)\n\t */\n\tanalyze(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): AnalysisResult {\n\t\treturn dispatchAnalyze(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse with cache and analyze the AST\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\treturn analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyze() for child elements\n\t\t\t(child, childOptions) => this.analyze(child, inputSchema, childOptions),\n\t\t);\n\t}\n\n\t// ─── Validation ──────────────────────────────────────────────────────────\n\n\t/**\n\t * Validates a template against a schema without returning the output type.\n\t *\n\t * This is an API shortcut for `analyze()` that only returns `valid` and\n\t * `diagnostics`, without `outputSchema`. The full analysis (including type\n\t * inference) is executed internally — this method provides no performance\n\t * gain, only a simplified API.\n\t *\n\t * @param template - The template to validate\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param identifierSchemas - (optional) Schemas by identifier\n\t */\n\tvalidate(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\toptions?: AnalyzeOptions,\n\t): ValidationResult {\n\t\tconst analysis = this.analyze(template, inputSchema, options);\n\t\treturn {\n\t\t\tvalid: analysis.valid,\n\t\t\tdiagnostics: analysis.diagnostics,\n\t\t};\n\t}\n\n\t// ─── Syntax Validation ───────────────────────────────────────────────────\n\n\t/**\n\t * Checks only that the template syntax is valid (parsing).\n\t * Does not require a schema — useful for quick feedback in an editor.\n\t *\n\t * For objects, recursively checks each property.\n\t *\n\t * @param template - The template to validate\n\t * @returns `true` if the template is syntactically correct\n\t */\n\tisValidSyntax(template: TemplateInput): boolean {\n\t\tif (isArrayInput(template)) {\n\t\t\treturn template.every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isObjectInput(template)) {\n\t\t\treturn Object.values(template).every((v) => this.isValidSyntax(v));\n\t\t}\n\t\tif (isLiteralInput(template)) return true;\n\t\ttry {\n\t\t\tthis.getCachedAst(template);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ─── Execution ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Executes a template with the provided data.\n\t *\n\t * Accepts a `TemplateInput`: string, number, boolean, null, or object.\n\t * For objects, each property is executed recursively and an object with\n\t * resolved values is returned.\n\t *\n\t * If a `schema` is provided in options, static analysis is performed\n\t * before execution. A `TemplateAnalysisError` is thrown on errors.\n\t *\n\t * @param template - The template to execute\n\t * @param data - The context data for rendering\n\t * @param options - Execution options (schema, identifierData, identifierSchemas)\n\t * @returns The execution result\n\t */\n\texecute(\n\t\ttemplate: TemplateInput,\n\t\tdata?: TemplateData,\n\t\toptions?: ExecuteOptions,\n\t): unknown {\n\t\treturn dispatchExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — parse, optionally validate, then execute\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\n\t\t\t\t// Pre-execution static validation if a schema is provided\n\t\t\t\tif (options?.schema) {\n\t\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, options.schema, {\n\t\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\t});\n\t\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\t\tthrow new TemplateAnalysisError(analysis.diagnostics);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t},\n\t\t\t// Recursive handler — re-enter execute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.execute(child, data, { ...options, ...childOptions }),\n\t\t);\n\t}\n\n\t// ─── Combined Shortcuts ──────────────────────────────────────────────────\n\n\t/**\n\t * Analyzes a template and, if valid, executes it with the provided data.\n\t * Returns both the analysis result and the executed value.\n\t *\n\t * For objects, each property is analyzed and executed recursively.\n\t * The entire object is considered invalid if at least one property is.\n\t *\n\t * @param template - The template\n\t * @param inputSchema - JSON Schema v7 describing the available variables\n\t * @param data - The context data for rendering\n\t * @param options - (optional) Options for template identifiers\n\t * @returns An object `{ analysis, value }` where `value` is `undefined`\n\t * if analysis failed.\n\t */\n\tanalyzeAndExecute(\n\t\ttemplate: TemplateInput,\n\t\tinputSchema: JSONSchema7 = {},\n\t\tdata: TemplateData,\n\t\toptions?: AnalyzeAndExecuteOptions,\n\t): { analysis: AnalysisResult; value: unknown } {\n\t\treturn dispatchAnalyzeAndExecute(\n\t\t\ttemplate,\n\t\t\toptions,\n\t\t\t// String handler — analyze then execute if valid\n\t\t\t(tpl, coerceSchema) => {\n\t\t\t\tconst ast = this.getCachedAst(tpl);\n\t\t\t\tconst analysis = analyzeFromAst(ast, tpl, inputSchema, {\n\t\t\t\t\tidentifierSchemas: options?.identifierSchemas,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t});\n\n\t\t\t\tif (!analysis.valid) {\n\t\t\t\t\treturn { analysis, value: undefined };\n\t\t\t\t}\n\n\t\t\t\tconst value = executeFromAst(ast, tpl, data, {\n\t\t\t\t\tidentifierData: options?.identifierData,\n\t\t\t\t\thbs: this.hbs,\n\t\t\t\t\tcompilationCache: this.compilationCache,\n\t\t\t\t\tcoerceSchema,\n\t\t\t\t\thelpers: this.helpers,\n\t\t\t\t});\n\t\t\t\treturn { analysis, value };\n\t\t\t},\n\t\t\t// Recursive handler — re-enter analyzeAndExecute() for child elements\n\t\t\t(child, childOptions) =>\n\t\t\t\tthis.analyzeAndExecute(child, inputSchema, data, childOptions),\n\t\t);\n\t}\n\n\t// ─── Custom Helper Management ──────────────────────────────────────────\n\n\t/**\n\t * Registers a custom helper on this engine instance.\n\t *\n\t * The helper is available for both execution (via Handlebars) and\n\t * static analysis (via its declared `returnType`).\n\t *\n\t * @param name - Helper name (e.g. `\"uppercase\"`)\n\t * @param definition - Helper definition (implementation + return type)\n\t * @returns `this` to allow chaining\n\t */\n\tregisterHelper(name: string, definition: HelperDefinition): this {\n\t\tthis.helpers.set(name, definition);\n\n\t\t// For helpers that return non-primitive values (arrays, objects),\n\t\t// register a Handlebars-friendly wrapper that converts the result\n\t\t// to a string. In single-expression mode the executor bypasses\n\t\t// Handlebars entirely (via tryDirectHelperExecution) and returns\n\t\t// the raw value, so this wrapper only affects mixed/block templates\n\t\t// where Handlebars renders the result into a larger string.\n\t\tif (DIRECT_EXECUTION_HELPER_NAMES.has(name)) {\n\t\t\tthis.hbs.registerHelper(name, (...args: unknown[]) => {\n\t\t\t\t// Handlebars appends an `options` object as the last argument.\n\t\t\t\t// Strip it before calling the real fn.\n\t\t\t\tconst hbsArgs = args.slice(0, -1);\n\t\t\t\tconst raw = definition.fn(...hbsArgs);\n\t\t\t\treturn stringifyForTemplate(raw);\n\t\t\t});\n\t\t} else {\n\t\t\tthis.hbs.registerHelper(name, definition.fn);\n\t\t}\n\n\t\t// Invalidate the compilation cache because helpers have changed\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a custom helper from this engine instance.\n\t *\n\t * @param name - Name of the helper to remove\n\t * @returns `this` to allow chaining\n\t */\n\tunregisterHelper(name: string): this {\n\t\tthis.helpers.delete(name);\n\t\tthis.hbs.unregisterHelper(name);\n\n\t\t// Invalidate the compilation cache\n\t\tthis.compilationCache.clear();\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Checks whether a helper is registered on this instance.\n\t *\n\t * @param name - Helper name\n\t * @returns `true` if the helper is registered\n\t */\n\thasHelper(name: string): boolean {\n\t\treturn this.helpers.has(name);\n\t}\n\n\t// ─── Cache Management ──────────────────────────────────────────────────\n\n\t/**\n\t * Clears all internal caches (AST + compilation).\n\t *\n\t * Useful after a configuration change or to free memory.\n\t */\n\tclearCaches(): void {\n\t\tthis.astCache.clear();\n\t\tthis.compilationCache.clear();\n\t}\n\n\t// ─── Internals ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Retrieves the AST of a template from the cache, or parses and caches it.\n\t */\n\tprivate getCachedAst(template: string): hbs.AST.Program {\n\t\tlet ast = this.astCache.get(template);\n\t\tif (!ast) {\n\t\t\tast = parse(template);\n\t\t\tthis.astCache.set(template, ast);\n\t\t}\n\t\treturn ast;\n\t}\n}\n"],"names":["Typebars","DIRECT_EXECUTION_HELPER_NAMES","Set","ArrayHelpers","ARRAY_HELPER_NAME","MapHelpers","MAP_HELPER_NAME","stringifyForTemplate","value","undefined","Array","isArray","map","item","JSON","stringify","String","join","compile","template","isArrayInput","children","element","push","CompiledTemplate","fromArray","helpers","hbs","compilationCache","isObjectInput","key","Object","entries","fromObject","isLiteralInput","fromLiteral","ast","getCachedAst","options","fromTemplate","analyze","inputSchema","dispatchAnalyze","tpl","coerceSchema","analyzeFromAst","identifierSchemas","child","childOptions","validate","analysis","valid","diagnostics","isValidSyntax","every","v","values","execute","data","dispatchExecute","schema","TemplateAnalysisError","executeFromAst","identifierData","analyzeAndExecute","dispatchAnalyzeAndExecute","registerHelper","name","definition","set","has","args","hbsArgs","slice","raw","fn","clear","unregisterHelper","delete","hasHelper","clearCaches","astCache","get","parse","Map","Handlebars","create","LRUCache","astCacheSize","compilationCacheSize","register","MathHelpers","LogicalHelpers","DefaultHelpers","helper"],"mappings":"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"}
|
package/dist/esm/analyzer.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{dispatchAnalyze}from"./dispatch.js";import{createMissingArgumentMessage,createPropertyNotFoundMessage,createRootPathTraversalMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{DefaultHelpers}from"./helpers/default-helpers.js";import{MapHelpers}from"./helpers/map-helpers.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isDataExpression,isRootPathTraversal,isRootSegments,isThisExpression,parse}from"./parser.js";import{findConditionalSchemaLocations,isPropertyRequired,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";function coerceTextValue(text,targetType){switch(targetType){case"number":case"integer":{if(text==="")return undefined;const num=Number(text);if(Number.isNaN(num))return undefined;if(targetType==="integer"&&!Number.isInteger(num))return undefined;return num}case"boolean":{const lower=text.toLowerCase();if(lower==="true")return true;if(lower==="false")return false;return undefined}case"null":return null;default:return text}}export function analyze(template,inputSchema={},options){return dispatchAnalyze(template,options,(tpl,coerceSchema)=>{const ast=parse(tpl);return analyzeFromAst(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema})},(child,childOptions)=>analyze(child,inputSchema,childOptions))}export function analyzeFromAst(ast,template,inputSchema={},options){const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,coerceSchema:options?.coerceSchema};const conditionalLocations=findConditionalSchemaLocations(inputSchema);for(const loc of conditionalLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){const idLocations=findConditionalSchemaLocations(idSchema,`/identifierSchemas/${id}`);for(const loc of idLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}}}if(ctx.diagnostics.length>0){return{valid:false,diagnostics:ctx.diagnostics,outputSchema:{}}}const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:simplifySchema(outputSchema)}}function processStatement(stmt,ctx){switch(stmt.type){case"ContentStatement":case"CommentStatement":return undefined;case"MustacheStatement":return processMustache(stmt,ctx);case"BlockStatement":return inferBlockType(stmt,ctx);default:addDiagnostic(ctx,"UNANALYZABLE","warning",`Unsupported AST node type: "${stmt.type}"`,stmt);return undefined}}function processMustache(stmt,ctx){if(stmt.path.type==="SubExpression"){addDiagnostic(ctx,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",stmt);return{}}if(stmt.params.length>0||stmt.hash){const helperName=getExpressionName(stmt.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapHelper(stmt,ctx)}if(helperName===DefaultHelpers.DEFAULT_HELPER_NAME){return processDefaultHelper(stmt,ctx)}const helper=ctx.helpers?.get(helperName);if(helper){const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(stmt.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:`${requiredCount} argument(s)`,actual:`${stmt.params.length} argument(s)`})}}for(let i=0;i<stmt.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(stmt.params[i],ctx,stmt);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,stmt,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown inline helper "${helperName}" — cannot analyze statically`,stmt,{helperName});return{type:"string"}}const resolved=resolveExpressionWithDiagnostics(stmt.path,ctx,stmt)??{};if(isDataExpression(stmt.path)){return resolved}if(stmt.path.type==="PathExpression"){const segments=extractPathSegments(stmt.path);if(segments.length>0){const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(!isRootSegments(cleanSegments)){let targetSchema=identifier!==null?ctx.identifierSchemas?.[identifier]:ctx.current;if(targetSchema&&identifier!==null){const itemSchema=resolveArrayItems(targetSchema,ctx.root);if(itemSchema!==undefined){targetSchema=itemSchema}}if(targetSchema&&!isPathFullyRequired(targetSchema,cleanSegments)){return withNullType(resolved)}}}}return resolved}function processMapHelper(stmt,ctx){const helperName=MapHelpers.MAP_HELPER_NAME;if(stmt.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:"2 argument(s)",actual:`${stmt.params.length} argument(s)`});return{type:"array"}}const collectionExpr=stmt.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,stmt);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,stmt,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems;addDiagnostic(ctx,"MAP_IMPLICIT_FLATTEN","warning",`The "${helperName}" helper will automatically flatten the input array one level before mapping. `+`The item type "${Array.isArray(itemType)?itemType.join(" | "):itemType}" was unwrapped to its inner items schema.`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)})}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=stmt.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use {{ ${helperName} … "${bare}" }} instead of {{ ${helperName} … ${bare} }}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),stmt,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function processDefaultHelper(stmt,ctx){return analyzeDefaultArgs(stmt.params,ctx,stmt)}function processDefaultSubExpression(expr,ctx,parentNode){return analyzeDefaultArgs(expr.params,ctx,parentNode??expr)}function analyzeDefaultArgs(params,ctx,node){const helperName=DefaultHelpers.DEFAULT_HELPER_NAME;if(params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${params.length} argument(s)`});return{}}const resolvedSchemas=[];let hasGuaranteedValue=false;for(let i=0;i<params.length;i++){const param=params[i];const resolvedSchema=resolveExpressionWithDiagnostics(param,ctx,node);if(resolvedSchema){resolvedSchemas.push(resolvedSchema)}if(isGuaranteedExpression(param,ctx)){hasGuaranteedValue=true}}if(resolvedSchemas.length>=2){const firstWithType=resolvedSchemas.find(s=>s.type);if(firstWithType){for(let i=0;i<resolvedSchemas.length;i++){const schema=resolvedSchemas[i];if(schema.type&&!isParamTypeCompatible(schema,firstWithType)){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" argument ${i+1} has type ${schemaTypeLabel(schema)}, incompatible with ${schemaTypeLabel(firstWithType)}`,node,{helperName,expected:schemaTypeLabel(firstWithType),actual:schemaTypeLabel(schema)})}}}}if(!hasGuaranteedValue){addDiagnostic(ctx,"DEFAULT_NO_GUARANTEED_VALUE","error",`Helper "${helperName}" argument chain has no guaranteed fallback — `+"the last argument must be a literal or a non-optional property",node,{helperName})}if(resolvedSchemas.length===0)return{};if(resolvedSchemas.length===1)return resolvedSchemas[0];return simplifySchema({oneOf:resolvedSchemas})}function isGuaranteedExpression(expr,ctx){if(expr.type==="StringLiteral"||expr.type==="NumberLiteral"||expr.type==="BooleanLiteral"){return true}if(expr.type==="SubExpression"){return true}if(expr.type==="PathExpression"){const segments=extractPathSegments(expr);if(segments.length===0)return false;const{cleanSegments}=extractExpressionIdentifier(segments);return isPropertyRequired(ctx.current,cleanSegments)}return false}function isParamTypeCompatible(resolved,expected){if(!expected.type||!resolved.type)return true;const expectedTypes=Array.isArray(expected.type)?expected.type:[expected.type];const resolvedTypes=Array.isArray(resolved.type)?resolved.type:[resolved.type];return resolvedTypes.some(rt=>expectedTypes.some(et=>rt===et||et==="number"&&rt==="integer"||et==="integer"&&rt==="number"))}function inferProgramType(program,ctx){const effective=getEffectiveBody(program);if(effective.length===0){return{type:"string"}}const singleExpr=getEffectivelySingleExpression(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=getEffectivelySingleBlock(program);if(singleBlock){return inferBlockType(singleBlock,ctx)}const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){const text=effective.map(s=>s.value).join("").trim();if(text==="")return{type:"string"};const coercedType=ctx.coerceSchema?.type;if(typeof coercedType==="string"&&(coercedType==="string"||coercedType==="number"||coercedType==="integer"||coercedType==="boolean"||coercedType==="null")){const coercedValue=coerceTextValue(text,coercedType);return{type:coercedType,const:coercedValue}}const literalType=detectLiteralType(text);if(literalType)return{type:literalType}}const allBlocks=effective.every(s=>s.type==="BlockStatement");if(allBlocks){const types=[];for(const stmt of effective){const t=inferBlockType(stmt,ctx);if(t)types.push(t)}if(types.length===1)return types[0];if(types.length>1)return simplifySchema({oneOf:types});return{type:"string"}}for(const stmt of program.body){processStatement(stmt,ctx)}return{type:"string"}}function inferBlockType(stmt,ctx){const helperName=getBlockHelperName(stmt);switch(helperName){case"if":case"unless":{const arg=getBlockArgument(stmt);if(arg){resolveExpressionWithDiagnostics(arg,ctx,stmt)}else{addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if(deepEqual(thenType,elseType))return thenType;return simplifySchema({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("each"),stmt,{helperName:"each"});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const collectionSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);if(!collectionSchema){const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",createTypeMismatchMessage("each","an array",schemaTypeLabel(collectionSchema)),stmt,{helperName:"each",expected:"array",actual:schemaTypeLabel(collectionSchema)});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const saved=ctx.current;ctx.current=itemSchema;inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}case"with":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("with"),stmt,{helperName:"with"});const saved=ctx.current;ctx.current={};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}const innerSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);const saved=ctx.current;ctx.current=innerSchema??{};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}default:{const helper=ctx.helpers?.get(helperName);if(helper){for(const param of stmt.params){resolveExpressionWithDiagnostics(param,ctx,stmt)}inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",createUnknownHelperMessage(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}const DATA_VARIABLE_SCHEMAS={index:{type:"number"},first:{type:"boolean"},last:{type:"boolean"},key:{type:"string"}};function resolveDataExpression(expr){const name=expr.parts[0];if(name&&name in DATA_VARIABLE_SCHEMAS){return DATA_VARIABLE_SCHEMAS[name]}return{}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if(isThisExpression(expr)){return ctx.current}if(isDataExpression(expr)){return resolveDataExpression(expr)}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=extractPathSegments(expr);if(segments.length===0){if(expr.type==="StringLiteral")return{type:"string"};if(expr.type==="NumberLiteral")return{type:"number"};if(expr.type==="BooleanLiteral")return{type:"boolean"};if(expr.type==="NullLiteral")return{type:"null"};if(expr.type==="UndefinedLiteral")return{};addDiagnostic(ctx,"UNANALYZABLE","warning",createUnanalyzableMessage(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(isRootPathTraversal(cleanSegments)){const fullPath=cleanSegments.join(".");addDiagnostic(ctx,"ROOT_PATH_TRAVERSAL","error",createRootPathTraversalMessage(fullPath),parentNode??expr,{path:fullPath});return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null){return resolveRootWithIdentifier(identifier,ctx,parentNode??expr)}return ctx.current}if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=resolveSchemaPath(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=getSchemaPropertyNames(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}function resolveRootWithIdentifier(identifier,ctx,node){if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "$root:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`$root:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "$root:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`$root:${identifier}`,identifier});return undefined}return idSchema}function resolveWithIdentifier(cleanSegments,identifier,ctx,node){const fullPath=cleanSegments.join(".");if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${fullPath}:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "${fullPath}:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const itemSchema=resolveArrayItems(idSchema,ctx.root);if(itemSchema!==undefined){const resolved=resolveSchemaPath(itemSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(itemSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the items schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return{type:"array",items:resolved}}const resolved=resolveSchemaPath(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(idSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return resolved}function withNullType(schema){if(schema.type==="null")return schema;if(typeof schema.type==="string"){return{...schema,type:[schema.type,"null"]}}if(Array.isArray(schema.type)){if(schema.type.includes("null"))return schema;return{...schema,type:[...schema.type,"null"]}}return simplifySchema({oneOf:[schema,{type:"null"}]})}function isPathFullyRequired(schema,segments){for(let i=1;i<=segments.length;i++){if(!isPropertyRequired(schema,segments.slice(0,i))){if(i>=2){const parentSchema=resolveSchemaPath(schema,segments.slice(0,i-1));if(parentSchema&&parentSchema.type==="array"){continue}}return false}}return true}function resolveSubExpression(expr,ctx,parentNode){const helperName=getExpressionName(expr.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapSubExpression(expr,ctx,parentNode)}if(helperName===DefaultHelpers.DEFAULT_HELPER_NAME){return processDefaultSubExpression(expr,ctx,parentNode)}const helper=ctx.helpers?.get(helperName);if(!helper){addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${helperName}" — cannot analyze statically`,parentNode??expr,{helperName});return{type:"string"}}const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(expr.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,parentNode??expr,{helperName,expected:`${requiredCount} argument(s)`,actual:`${expr.params.length} argument(s)`})}}for(let i=0;i<expr.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(expr.params[i],ctx,parentNode??expr);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,parentNode??expr,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}function processMapSubExpression(expr,ctx,parentNode){const helperName=MapHelpers.MAP_HELPER_NAME;const node=parentNode??expr;if(expr.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${expr.params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${expr.params.length} argument(s)`});return{type:"array"}}const collectionExpr=expr.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,node);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,node,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,node,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=expr.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use (${helperName} … "${bare}") instead of (${helperName} … ${bare})`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),node,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function getBlockArgument(stmt){return stmt.params[0]}function getBlockHelperName(stmt){if(stmt.path.type==="PathExpression"){return stmt.path.original}return""}function getExpressionName(expr){if(expr.type==="PathExpression"){return expr.original}return""}function addDiagnostic(ctx,code,severity,message,node,details){const diagnostic={severity,code,message};if(node&&"loc"in node&&node.loc){diagnostic.loc={start:{line:node.loc.start.line,column:node.loc.start.column},end:{line:node.loc.end.line,column:node.loc.end.column}};diagnostic.source=extractSourceSnippet(ctx.template,diagnostic.loc)}if(details){diagnostic.details=details}ctx.diagnostics.push(diagnostic)}function schemaTypeLabel(schema){if(schema.type){return Array.isArray(schema.type)?schema.type.join(" | "):schema.type}if(schema.oneOf)return"oneOf(...)";if(schema.anyOf)return"anyOf(...)";if(schema.allOf)return"allOf(...)";if(schema.enum)return"enum";return"unknown"}export{inferBlockType};
|
|
1
|
+
import{dispatchAnalyze}from"./dispatch.js";import{createMissingArgumentMessage,createPropertyNotFoundMessage,createRootPathTraversalMessage,createTypeMismatchMessage,createUnanalyzableMessage,createUnknownHelperMessage}from"./errors.js";import{ArrayHelpers}from"./helpers/array-helpers.js";import{DefaultHelpers}from"./helpers/default-helpers.js";import{MapHelpers}from"./helpers/map-helpers.js";import{detectLiteralType,extractExpressionIdentifier,extractPathSegments,getEffectiveBody,getEffectivelySingleBlock,getEffectivelySingleExpression,isDataExpression,isRootPathTraversal,isRootSegments,isThisExpression,parse}from"./parser.js";import{findConditionalSchemaLocations,isPropertyRequired,resolveArrayItems,resolveSchemaPath,simplifySchema}from"./schema-resolver.js";import{deepEqual,extractSourceSnippet,getSchemaPropertyNames}from"./utils.js";function coerceTextValue(text,targetType){switch(targetType){case"number":case"integer":{if(text==="")return undefined;const num=Number(text);if(Number.isNaN(num))return undefined;if(targetType==="integer"&&!Number.isInteger(num))return undefined;return num}case"boolean":{const lower=text.toLowerCase();if(lower==="true")return true;if(lower==="false")return false;return undefined}case"null":return null;default:return text}}export function analyze(template,inputSchema={},options){return dispatchAnalyze(template,options,(tpl,coerceSchema)=>{const ast=parse(tpl);return analyzeFromAst(ast,tpl,inputSchema,{identifierSchemas:options?.identifierSchemas,coerceSchema})},(child,childOptions)=>analyze(child,inputSchema,childOptions))}export function analyzeFromAst(ast,template,inputSchema={},options){const ctx={root:inputSchema,current:inputSchema,diagnostics:[],template,identifierSchemas:options?.identifierSchemas,helpers:options?.helpers,coerceSchema:options?.coerceSchema};const conditionalLocations=findConditionalSchemaLocations(inputSchema);for(const loc of conditionalLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}if(options?.identifierSchemas){for(const[id,idSchema]of Object.entries(options.identifierSchemas)){const idLocations=findConditionalSchemaLocations(idSchema,`/identifierSchemas/${id}`);for(const loc of idLocations){addDiagnostic(ctx,"UNSUPPORTED_SCHEMA","error",`Unsupported JSON Schema feature: "${loc.keyword}" at "${loc.schemaPath}". `+"Conditional schemas (if/then/else) cannot be resolved during static analysis "+"because they depend on runtime data. Consider using oneOf/anyOf combinators instead.",undefined,{path:loc.schemaPath})}}}if(ctx.diagnostics.length>0){return{valid:false,diagnostics:ctx.diagnostics,outputSchema:{}}}const outputSchema=inferProgramType(ast,ctx);const hasErrors=ctx.diagnostics.some(d=>d.severity==="error");return{valid:!hasErrors,diagnostics:ctx.diagnostics,outputSchema:simplifySchema(outputSchema)}}function processStatement(stmt,ctx){switch(stmt.type){case"ContentStatement":case"CommentStatement":return undefined;case"MustacheStatement":return processMustache(stmt,ctx);case"BlockStatement":return inferBlockType(stmt,ctx);default:addDiagnostic(ctx,"UNANALYZABLE","warning",`Unsupported AST node type: "${stmt.type}"`,stmt);return undefined}}function processMustache(stmt,ctx){if(stmt.path.type==="SubExpression"){addDiagnostic(ctx,"UNANALYZABLE","warning","Sub-expressions are not statically analyzable",stmt);return{}}if(stmt.params.length>0||stmt.hash){const helperName=getExpressionName(stmt.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapHelper(stmt,ctx)}if(helperName===DefaultHelpers.DEFAULT_HELPER_NAME){return processDefaultHelper(stmt,ctx)}if(helperName===ArrayHelpers.ARRAY_HELPER_NAME){return processArrayHelper(stmt,ctx)}const helper=ctx.helpers?.get(helperName);if(helper){const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(stmt.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:`${requiredCount} argument(s)`,actual:`${stmt.params.length} argument(s)`})}}for(let i=0;i<stmt.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(stmt.params[i],ctx,stmt);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,stmt,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown inline helper "${helperName}" — cannot analyze statically`,stmt,{helperName});return{type:"string"}}const resolved=resolveExpressionWithDiagnostics(stmt.path,ctx,stmt)??{};if(isDataExpression(stmt.path)){return resolved}if(stmt.path.type==="PathExpression"){const segments=extractPathSegments(stmt.path);if(segments.length>0){const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(!isRootSegments(cleanSegments)){let targetSchema=identifier!==null?ctx.identifierSchemas?.[identifier]:ctx.current;if(targetSchema&&identifier!==null){const itemSchema=resolveArrayItems(targetSchema,ctx.root);if(itemSchema!==undefined){targetSchema=itemSchema}}if(targetSchema&&!isPathFullyRequired(targetSchema,cleanSegments)){return withNullType(resolved)}}}}return resolved}function processMapHelper(stmt,ctx){const helperName=MapHelpers.MAP_HELPER_NAME;if(stmt.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${stmt.params.length}`,stmt,{helperName,expected:"2 argument(s)",actual:`${stmt.params.length} argument(s)`});return{type:"array"}}const collectionExpr=stmt.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,stmt);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,stmt,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems;addDiagnostic(ctx,"MAP_IMPLICIT_FLATTEN","warning",`The "${helperName}" helper will automatically flatten the input array one level before mapping. `+`The item type "${Array.isArray(itemType)?itemType.join(" | "):itemType}" was unwrapped to its inner items schema.`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)})}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,stmt,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=stmt.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use {{ ${helperName} … "${bare}" }} instead of {{ ${helperName} … ${bare} }}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,stmt,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),stmt,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function processDefaultHelper(stmt,ctx){return analyzeDefaultArgs(stmt.params,ctx,stmt)}function processDefaultSubExpression(expr,ctx,parentNode){return analyzeDefaultArgs(expr.params,ctx,parentNode??expr)}function analyzeDefaultArgs(params,ctx,node){const helperName=DefaultHelpers.DEFAULT_HELPER_NAME;if(params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${params.length} argument(s)`});return{}}const resolvedSchemas=[];let hasGuaranteedValue=false;for(let i=0;i<params.length;i++){const param=params[i];const resolvedSchema=resolveExpressionWithDiagnostics(param,ctx,node);if(resolvedSchema){resolvedSchemas.push(resolvedSchema)}if(isGuaranteedExpression(param,ctx)){hasGuaranteedValue=true}}if(resolvedSchemas.length>=2){const firstWithType=resolvedSchemas.find(s=>s.type);if(firstWithType){for(let i=0;i<resolvedSchemas.length;i++){const schema=resolvedSchemas[i];if(schema.type&&!isParamTypeCompatible(schema,firstWithType)){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" argument ${i+1} has type ${schemaTypeLabel(schema)}, incompatible with ${schemaTypeLabel(firstWithType)}`,node,{helperName,expected:schemaTypeLabel(firstWithType),actual:schemaTypeLabel(schema)})}}}}if(!hasGuaranteedValue){addDiagnostic(ctx,"DEFAULT_NO_GUARANTEED_VALUE","error",`Helper "${helperName}" argument chain has no guaranteed fallback — `+"the last argument must be a literal or a non-optional property",node,{helperName})}if(resolvedSchemas.length===0)return{};if(resolvedSchemas.length===1)return resolvedSchemas[0];return simplifySchema({oneOf:resolvedSchemas})}function isGuaranteedExpression(expr,ctx){if(expr.type==="StringLiteral"||expr.type==="NumberLiteral"||expr.type==="BooleanLiteral"){return true}if(expr.type==="SubExpression"){return true}if(expr.type==="PathExpression"){const segments=extractPathSegments(expr);if(segments.length===0)return false;const{cleanSegments}=extractExpressionIdentifier(segments);return isPropertyRequired(ctx.current,cleanSegments)}return false}function processArrayHelper(stmt,ctx){return analyzeArrayArgs(stmt.params,ctx,stmt)}function processArraySubExpression(expr,ctx,parentNode){return analyzeArrayArgs(expr.params,ctx,parentNode??expr)}function analyzeArrayArgs(params,ctx,node){const helperName=ArrayHelpers.ARRAY_HELPER_NAME;if(params.length<1){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 1 argument(s), but got 0`,node,{helperName,expected:"1 argument(s)",actual:"0 argument(s)"});return{type:"array"}}const resolvedSchemas=[];for(let i=0;i<params.length;i++){const param=params[i];const resolvedSchema=resolveExpressionWithDiagnostics(param,ctx,node);if(resolvedSchema){resolvedSchemas.push(resolvedSchema)}}if(resolvedSchemas.length===0)return{type:"array"};const itemSchema=resolvedSchemas.length===1?resolvedSchemas[0]:simplifySchema({oneOf:resolvedSchemas});return{type:"array",items:itemSchema}}function isParamTypeCompatible(resolved,expected){if(!expected.type||!resolved.type)return true;const expectedTypes=Array.isArray(expected.type)?expected.type:[expected.type];const resolvedTypes=Array.isArray(resolved.type)?resolved.type:[resolved.type];return resolvedTypes.some(rt=>expectedTypes.some(et=>rt===et||et==="number"&&rt==="integer"||et==="integer"&&rt==="number"))}function inferProgramType(program,ctx){const effective=getEffectiveBody(program);if(effective.length===0){return{type:"string"}}const singleExpr=getEffectivelySingleExpression(program);if(singleExpr){return processMustache(singleExpr,ctx)}const singleBlock=getEffectivelySingleBlock(program);if(singleBlock){return inferBlockType(singleBlock,ctx)}const allContent=effective.every(s=>s.type==="ContentStatement");if(allContent){const text=effective.map(s=>s.value).join("").trim();if(text==="")return{type:"string"};const coercedType=ctx.coerceSchema?.type;if(typeof coercedType==="string"&&(coercedType==="string"||coercedType==="number"||coercedType==="integer"||coercedType==="boolean"||coercedType==="null")){const coercedValue=coerceTextValue(text,coercedType);return{type:coercedType,const:coercedValue}}const literalType=detectLiteralType(text);if(literalType)return{type:literalType}}const allBlocks=effective.every(s=>s.type==="BlockStatement");if(allBlocks){const types=[];for(const stmt of effective){const t=inferBlockType(stmt,ctx);if(t)types.push(t)}if(types.length===1)return types[0];if(types.length>1)return simplifySchema({oneOf:types});return{type:"string"}}for(const stmt of program.body){processStatement(stmt,ctx)}return{type:"string"}}function inferBlockType(stmt,ctx){const helperName=getBlockHelperName(stmt);switch(helperName){case"if":case"unless":{const arg=getBlockArgument(stmt);if(arg){resolveExpressionWithDiagnostics(arg,ctx,stmt)}else{addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage(helperName),stmt,{helperName})}const thenType=inferProgramType(stmt.program,ctx);if(stmt.inverse){const elseType=inferProgramType(stmt.inverse,ctx);if(deepEqual(thenType,elseType))return thenType;return simplifySchema({oneOf:[thenType,elseType]})}return thenType}case"each":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("each"),stmt,{helperName:"each"});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const collectionSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);if(!collectionSchema){const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",createTypeMismatchMessage("each","an array",schemaTypeLabel(collectionSchema)),stmt,{helperName:"each",expected:"array",actual:schemaTypeLabel(collectionSchema)});const saved=ctx.current;ctx.current={};inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}const saved=ctx.current;ctx.current=itemSchema;inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}case"with":{const arg=getBlockArgument(stmt);if(!arg){addDiagnostic(ctx,"MISSING_ARGUMENT","error",createMissingArgumentMessage("with"),stmt,{helperName:"with"});const saved=ctx.current;ctx.current={};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}const innerSchema=resolveExpressionWithDiagnostics(arg,ctx,stmt);const saved=ctx.current;ctx.current=innerSchema??{};const result=inferProgramType(stmt.program,ctx);ctx.current=saved;if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return result}default:{const helper=ctx.helpers?.get(helperName);if(helper){for(const param of stmt.params){resolveExpressionWithDiagnostics(param,ctx,stmt)}inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return helper.returnType??{type:"string"}}addDiagnostic(ctx,"UNKNOWN_HELPER","warning",createUnknownHelperMessage(helperName),stmt,{helperName});inferProgramType(stmt.program,ctx);if(stmt.inverse)inferProgramType(stmt.inverse,ctx);return{type:"string"}}}}const DATA_VARIABLE_SCHEMAS={index:{type:"number"},first:{type:"boolean"},last:{type:"boolean"},key:{type:"string"}};function resolveDataExpression(expr){const name=expr.parts[0];if(name&&name in DATA_VARIABLE_SCHEMAS){return DATA_VARIABLE_SCHEMAS[name]}return{}}function resolveExpressionWithDiagnostics(expr,ctx,parentNode){if(isThisExpression(expr)){return ctx.current}if(isDataExpression(expr)){return resolveDataExpression(expr)}if(expr.type==="SubExpression"){return resolveSubExpression(expr,ctx,parentNode)}const segments=extractPathSegments(expr);if(segments.length===0){if(expr.type==="StringLiteral")return{type:"string"};if(expr.type==="NumberLiteral")return{type:"number"};if(expr.type==="BooleanLiteral")return{type:"boolean"};if(expr.type==="NullLiteral")return{type:"null"};if(expr.type==="UndefinedLiteral")return{};addDiagnostic(ctx,"UNANALYZABLE","warning",createUnanalyzableMessage(expr.type),parentNode??expr);return undefined}const{cleanSegments,identifier}=extractExpressionIdentifier(segments);if(isRootPathTraversal(cleanSegments)){const fullPath=cleanSegments.join(".");addDiagnostic(ctx,"ROOT_PATH_TRAVERSAL","error",createRootPathTraversalMessage(fullPath),parentNode??expr,{path:fullPath});return undefined}if(isRootSegments(cleanSegments)){if(identifier!==null){return resolveRootWithIdentifier(identifier,ctx,parentNode??expr)}return ctx.current}if(identifier!==null){return resolveWithIdentifier(cleanSegments,identifier,ctx,parentNode??expr)}const resolved=resolveSchemaPath(ctx.current,cleanSegments);if(resolved===undefined){const fullPath=cleanSegments.join(".");const availableProperties=getSchemaPropertyNames(ctx.current);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(fullPath,availableProperties),parentNode??expr,{path:fullPath,availableProperties});return undefined}return resolved}function resolveRootWithIdentifier(identifier,ctx,node){if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "$root:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`$root:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "$root:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`$root:${identifier}`,identifier});return undefined}return idSchema}function resolveWithIdentifier(cleanSegments,identifier,ctx,node){const fullPath=cleanSegments.join(".");if(!ctx.identifierSchemas){addDiagnostic(ctx,"MISSING_IDENTIFIER_SCHEMAS","error",`Property "${fullPath}:${identifier}" uses an identifier but no identifier schemas were provided`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const idSchema=ctx.identifierSchemas[identifier];if(!idSchema){addDiagnostic(ctx,"UNKNOWN_IDENTIFIER","error",`Property "${fullPath}:${identifier}" references identifier ${identifier} but no schema exists for this identifier`,node,{path:`${fullPath}:${identifier}`,identifier});return undefined}const itemSchema=resolveArrayItems(idSchema,ctx.root);if(itemSchema!==undefined){const resolved=resolveSchemaPath(itemSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(itemSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the items schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return{type:"array",items:resolved}}const resolved=resolveSchemaPath(idSchema,cleanSegments);if(resolved===undefined){const availableProperties=getSchemaPropertyNames(idSchema);addDiagnostic(ctx,"IDENTIFIER_PROPERTY_NOT_FOUND","error",`Property "${fullPath}" does not exist in the schema for identifier ${identifier}`,node,{path:fullPath,identifier,availableProperties});return undefined}return resolved}function withNullType(schema){if(schema.type==="null")return schema;if(typeof schema.type==="string"){return{...schema,type:[schema.type,"null"]}}if(Array.isArray(schema.type)){if(schema.type.includes("null"))return schema;return{...schema,type:[...schema.type,"null"]}}return simplifySchema({oneOf:[schema,{type:"null"}]})}function isPathFullyRequired(schema,segments){for(let i=1;i<=segments.length;i++){if(!isPropertyRequired(schema,segments.slice(0,i))){if(i>=2){const parentSchema=resolveSchemaPath(schema,segments.slice(0,i-1));if(parentSchema&&parentSchema.type==="array"){continue}}return false}}return true}function resolveSubExpression(expr,ctx,parentNode){const helperName=getExpressionName(expr.path);if(helperName===MapHelpers.MAP_HELPER_NAME){return processMapSubExpression(expr,ctx,parentNode)}if(helperName===DefaultHelpers.DEFAULT_HELPER_NAME){return processDefaultSubExpression(expr,ctx,parentNode)}if(helperName===ArrayHelpers.ARRAY_HELPER_NAME){return processArraySubExpression(expr,ctx,parentNode)}const helper=ctx.helpers?.get(helperName);if(!helper){addDiagnostic(ctx,"UNKNOWN_HELPER","warning",`Unknown sub-expression helper "${helperName}" — cannot analyze statically`,parentNode??expr,{helperName});return{type:"string"}}const helperParams=helper.params;if(helperParams){const requiredCount=helperParams.filter(p=>!p.optional).length;if(expr.params.length<requiredCount){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least ${requiredCount} argument(s), but got ${expr.params.length}`,parentNode??expr,{helperName,expected:`${requiredCount} argument(s)`,actual:`${expr.params.length} argument(s)`})}}for(let i=0;i<expr.params.length;i++){const resolvedSchema=resolveExpressionWithDiagnostics(expr.params[i],ctx,parentNode??expr);const helperParam=helperParams?.[i];if(resolvedSchema&&helperParam?.type){const expectedType=helperParam.type;if(!isParamTypeCompatible(resolvedSchema,expectedType)){const paramName=helperParam.name;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "${paramName}" expects ${schemaTypeLabel(expectedType)}, but got ${schemaTypeLabel(resolvedSchema)}`,parentNode??expr,{helperName,expected:schemaTypeLabel(expectedType),actual:schemaTypeLabel(resolvedSchema)})}}}return helper.returnType??{type:"string"}}function processMapSubExpression(expr,ctx,parentNode){const helperName=MapHelpers.MAP_HELPER_NAME;const node=parentNode??expr;if(expr.params.length<2){addDiagnostic(ctx,"MISSING_ARGUMENT","error",`Helper "${helperName}" expects at least 2 argument(s), but got ${expr.params.length}`,node,{helperName,expected:"2 argument(s)",actual:`${expr.params.length} argument(s)`});return{type:"array"}}const collectionExpr=expr.params[0];const collectionSchema=resolveExpressionWithDiagnostics(collectionExpr,ctx,node);if(!collectionSchema){return{type:"array"}}const itemSchema=resolveArrayItems(collectionSchema,ctx.root);if(!itemSchema){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "collection" expects an array, but got ${schemaTypeLabel(collectionSchema)}`,node,{helperName,expected:"array",actual:schemaTypeLabel(collectionSchema)});return{type:"array"}}let effectiveItemSchema=itemSchema;const itemType=effectiveItemSchema.type;if(itemType==="array"||Array.isArray(itemType)&&itemType.includes("array")){const innerItems=resolveArrayItems(effectiveItemSchema,ctx.root);if(innerItems){effectiveItemSchema=innerItems}}const effectiveItemType=effectiveItemSchema.type;const isObject=effectiveItemType==="object"||Array.isArray(effectiveItemType)&&effectiveItemType.includes("object")||!effectiveItemType&&effectiveItemSchema.properties!==undefined;if(!isObject&&effectiveItemType!==undefined){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" expects an array of objects, but the array items have type "${schemaTypeLabel(effectiveItemSchema)}"`,node,{helperName,expected:"object",actual:schemaTypeLabel(effectiveItemSchema)});return{type:"array"}}const propertyExpr=expr.params[1];let propertyName;if(propertyExpr.type==="PathExpression"){const bare=propertyExpr.original;addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" must be a quoted string. `+`Use (${helperName} … "${bare}") instead of (${helperName} … ${bare})`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:`PathExpression (${bare})`});return{type:"array"}}if(propertyExpr.type==="StringLiteral"){propertyName=propertyExpr.value}if(!propertyName){addDiagnostic(ctx,"TYPE_MISMATCH","error",`Helper "${helperName}" parameter "property" expects a quoted string literal, but got ${propertyExpr.type}`,node,{helperName,expected:'StringLiteral (e.g. "property")',actual:propertyExpr.type});return{type:"array"}}const propertySchema=resolveSchemaPath(effectiveItemSchema,[propertyName]);if(!propertySchema){const availableProperties=getSchemaPropertyNames(effectiveItemSchema);addDiagnostic(ctx,"UNKNOWN_PROPERTY","error",createPropertyNotFoundMessage(propertyName,availableProperties),node,{path:propertyName,availableProperties});return{type:"array"}}return{type:"array",items:propertySchema}}function getBlockArgument(stmt){return stmt.params[0]}function getBlockHelperName(stmt){if(stmt.path.type==="PathExpression"){return stmt.path.original}return""}function getExpressionName(expr){if(expr.type==="PathExpression"){return expr.original}return""}function addDiagnostic(ctx,code,severity,message,node,details){const diagnostic={severity,code,message};if(node&&"loc"in node&&node.loc){diagnostic.loc={start:{line:node.loc.start.line,column:node.loc.start.column},end:{line:node.loc.end.line,column:node.loc.end.column}};diagnostic.source=extractSourceSnippet(ctx.template,diagnostic.loc)}if(details){diagnostic.details=details}ctx.diagnostics.push(diagnostic)}function schemaTypeLabel(schema){if(schema.type){return Array.isArray(schema.type)?schema.type.join(" | "):schema.type}if(schema.oneOf)return"oneOf(...)";if(schema.anyOf)return"anyOf(...)";if(schema.allOf)return"allOf(...)";if(schema.enum)return"enum";return"unknown"}export{inferBlockType};
|
|
2
2
|
//# sourceMappingURL=analyzer.js.map
|