typebars 1.0.3 → 1.0.5
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/README.md +1 -2
- package/dist/{analyzer.d.ts → cjs/analyzer.d.ts} +1 -1
- package/dist/cjs/analyzer.js +2 -0
- package/dist/cjs/analyzer.js.map +1 -0
- package/dist/{compiled-template.d.ts → cjs/compiled-template.d.ts} +2 -2
- package/dist/cjs/compiled-template.js +2 -0
- package/dist/cjs/compiled-template.js.map +1 -0
- package/dist/{errors.d.ts → cjs/errors.d.ts} +1 -1
- package/dist/cjs/errors.js +3 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/{executor.d.ts → cjs/executor.d.ts} +2 -2
- package/dist/cjs/executor.js +2 -0
- package/dist/cjs/executor.js.map +1 -0
- package/dist/{helpers → cjs/helpers}/helper-factory.d.ts +1 -1
- package/dist/cjs/helpers/helper-factory.js +2 -0
- package/dist/cjs/helpers/helper-factory.js.map +1 -0
- package/dist/cjs/helpers/index.d.ts +4 -0
- package/dist/cjs/helpers/index.js +2 -0
- package/dist/cjs/helpers/index.js.map +1 -0
- package/dist/{helpers → cjs/helpers}/logical-helpers.d.ts +2 -2
- package/dist/cjs/helpers/logical-helpers.js +2 -0
- package/dist/cjs/helpers/logical-helpers.js.map +1 -0
- package/dist/{helpers → cjs/helpers}/math-helpers.d.ts +2 -2
- package/dist/cjs/helpers/math-helpers.js +2 -0
- package/dist/cjs/helpers/math-helpers.js.map +1 -0
- package/dist/cjs/helpers/utils.js +2 -0
- package/dist/cjs/helpers/utils.js.map +1 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/parser.js +2 -0
- package/dist/cjs/parser.js.map +1 -0
- package/dist/cjs/schema-resolver.js +2 -0
- package/dist/cjs/schema-resolver.js.map +1 -0
- package/dist/{typebars.d.ts → cjs/typebars.d.ts} +2 -2
- package/dist/cjs/typebars.js +2 -0
- package/dist/cjs/typebars.js.map +1 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/{utils.d.ts → cjs/utils.d.ts} +1 -1
- package/dist/cjs/utils.js +2 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/esm/analyzer.d.ts +59 -0
- package/dist/esm/analyzer.js +2 -0
- package/dist/esm/analyzer.js.map +1 -0
- package/dist/esm/compiled-template.d.ts +130 -0
- package/dist/esm/compiled-template.js +2 -0
- package/dist/esm/compiled-template.js.map +1 -0
- package/dist/esm/errors.d.ts +105 -0
- package/dist/esm/errors.js +3 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/executor.d.ts +55 -0
- package/dist/esm/executor.js +2 -0
- package/dist/esm/executor.js.map +1 -0
- package/dist/esm/helpers/helper-factory.d.ts +56 -0
- package/dist/esm/helpers/helper-factory.js +2 -0
- package/dist/esm/helpers/helper-factory.js.map +1 -0
- package/dist/esm/helpers/index.d.ts +4 -0
- package/dist/esm/helpers/index.js +2 -0
- package/dist/esm/helpers/index.js.map +1 -0
- package/dist/esm/helpers/logical-helpers.d.ts +15 -0
- package/dist/esm/helpers/logical-helpers.js +2 -0
- package/dist/esm/helpers/logical-helpers.js.map +1 -0
- package/dist/esm/helpers/math-helpers.d.ts +13 -0
- package/dist/esm/helpers/math-helpers.js +2 -0
- package/dist/esm/helpers/math-helpers.js.map +1 -0
- package/dist/esm/helpers/utils.d.ts +19 -0
- package/dist/esm/helpers/utils.js +2 -0
- package/dist/esm/helpers/utils.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/parser.d.ts +146 -0
- package/dist/esm/parser.js +2 -0
- package/dist/esm/parser.js.map +1 -0
- package/dist/esm/schema-resolver.d.ts +88 -0
- package/dist/esm/schema-resolver.js +2 -0
- package/dist/esm/schema-resolver.js.map +1 -0
- package/dist/esm/typebars.d.ts +130 -0
- package/dist/esm/typebars.js +2 -0
- package/dist/esm/typebars.js.map +1 -0
- package/dist/esm/types.d.ts +334 -0
- package/dist/esm/types.js +2 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils.d.ts +113 -0
- package/dist/esm/utils.js +2 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +29 -6
- package/dist/analyzer.js +0 -4
- package/dist/analyzer.js.map +0 -9
- package/dist/chunk-5znsrn10.js +0 -5
- package/dist/chunk-5znsrn10.js.map +0 -10
- package/dist/chunk-867xywnk.js +0 -5
- package/dist/chunk-867xywnk.js.map +0 -10
- package/dist/chunk-8xza8tca.js +0 -5
- package/dist/chunk-8xza8tca.js.map +0 -10
- package/dist/chunk-9jxzj2h4.js +0 -7
- package/dist/chunk-9jxzj2h4.js.map +0 -10
- package/dist/chunk-dpffacsy.js +0 -4
- package/dist/chunk-dpffacsy.js.map +0 -10
- package/dist/chunk-gayk9ew1.js +0 -7
- package/dist/chunk-gayk9ew1.js.map +0 -10
- package/dist/chunk-s96k41p3.js +0 -5
- package/dist/chunk-s96k41p3.js.map +0 -10
- package/dist/chunk-wvnn9g55.js +0 -5
- package/dist/chunk-wvnn9g55.js.map +0 -10
- package/dist/compiled-template.js +0 -4
- package/dist/compiled-template.js.map +0 -9
- package/dist/errors.js +0 -4
- package/dist/errors.js.map +0 -9
- package/dist/executor.js +0 -4
- package/dist/executor.js.map +0 -9
- package/dist/helpers/index.d.ts +0 -4
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -4
- package/dist/index.js.map +0 -9
- package/dist/parser.js +0 -4
- package/dist/parser.js.map +0 -9
- package/dist/schema-resolver.js +0 -4
- package/dist/schema-resolver.js.map +0 -9
- package/dist/typebars.js +0 -4
- package/dist/typebars.js.map +0 -14
- package/dist/types.js +0 -4
- package/dist/types.js.map +0 -9
- package/dist/utils.js +0 -4
- package/dist/utils.js.map +0 -9
- /package/dist/{helpers → cjs/helpers}/utils.d.ts +0 -0
- /package/dist/{parser.d.ts → cjs/parser.d.ts} +0 -0
- /package/dist/{schema-resolver.d.ts → cjs/schema-resolver.d.ts} +0 -0
- /package/dist/{types.d.ts → cjs/types.d.ts} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { HelperDefinition } from "../types.js";
|
|
2
|
+
/** Minimal registration interface — avoids tight coupling with Typebars */
|
|
3
|
+
export interface HelperRegistry {
|
|
4
|
+
registerHelper(name: string, definition: HelperDefinition): unknown;
|
|
5
|
+
unregisterHelper(name: string): unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare abstract class HelperFactory {
|
|
8
|
+
private _definitions;
|
|
9
|
+
private _helperNames;
|
|
10
|
+
private _helperNamesSet;
|
|
11
|
+
/**
|
|
12
|
+
* Populates the `defs` map with all helper definitions.
|
|
13
|
+
*
|
|
14
|
+
* Subclasses implement this method to register their helpers:
|
|
15
|
+
* ```
|
|
16
|
+
* protected buildDefinitions(defs: Map<string, HelperDefinition>): void {
|
|
17
|
+
* defs.set("myHelper", {
|
|
18
|
+
* fn: (a: unknown) => String(a).toUpperCase(),
|
|
19
|
+
* params: [{ name: "a", type: { type: "string" } }],
|
|
20
|
+
* returnType: { type: "string" },
|
|
21
|
+
* description: "Converts to uppercase",
|
|
22
|
+
* });
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @param defs - The map to populate with `[name, HelperDefinition]` entries
|
|
27
|
+
*/
|
|
28
|
+
protected abstract buildDefinitions(defs: Map<string, HelperDefinition>): void;
|
|
29
|
+
/**
|
|
30
|
+
* Returns all definitions as a `Map<name, HelperDefinition>`.
|
|
31
|
+
* The map is lazily built and cached on first access.
|
|
32
|
+
*/
|
|
33
|
+
getDefinitions(): Map<string, HelperDefinition>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns the list of all helper names provided by this factory.
|
|
36
|
+
*/
|
|
37
|
+
getHelperNames(): readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* Checks whether a helper name belongs to this factory.
|
|
40
|
+
*
|
|
41
|
+
* @param name - The helper name to check
|
|
42
|
+
*/
|
|
43
|
+
isHelper(name: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Registers all helpers from this factory on the given registry.
|
|
46
|
+
*
|
|
47
|
+
* @param registry - The engine or target registry
|
|
48
|
+
*/
|
|
49
|
+
register(registry: HelperRegistry): void;
|
|
50
|
+
/**
|
|
51
|
+
* Removes all helpers from this factory from the given registry.
|
|
52
|
+
*
|
|
53
|
+
* @param registry - The engine or target registry
|
|
54
|
+
*/
|
|
55
|
+
unregister(registry: HelperRegistry): void;
|
|
56
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}export class HelperFactory{getDefinitions(){if(!this._definitions){this._definitions=new Map;this.buildDefinitions(this._definitions)}return this._definitions}getHelperNames(){if(!this._helperNames){this._helperNames=[...this.getDefinitions().keys()]}return this._helperNames}isHelper(name){if(!this._helperNamesSet){this._helperNamesSet=new Set(this.getHelperNames())}return this._helperNamesSet.has(name)}register(registry){for(const[name,def]of this.getDefinitions()){registry.registerHelper(name,def)}}unregister(registry){for(const name of this.getHelperNames()){registry.unregisterHelper(name)}}constructor(){_define_property(this,"_definitions",null);_define_property(this,"_helperNames",null);_define_property(this,"_helperNamesSet",null)}}
|
|
2
|
+
//# sourceMappingURL=helper-factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/helper-factory.ts"],"sourcesContent":["import type { HelperDefinition } from \"../types.ts\";\n\n// ─── HelperFactory ───────────────────────────────────────────────────────────\n// Abstract base class that enforces a consistent pattern for creating,\n// registering, and managing groups of related helpers.\n//\n// Every helper factory (MathHelpers, LogicalHelpers, …) must extend this class\n// and implement the `buildDefinitions()` method to populate its helpers.\n//\n// The base class provides:\n// - Lazy-cached definitions, helper names, and name set\n// - `register()` / `unregister()` for any `HelperRegistry`\n// - `getHelperNames()` / `isHelper()` for introspection\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Minimal registration interface — avoids tight coupling with Typebars */\nexport interface HelperRegistry {\n\tregisterHelper(name: string, definition: HelperDefinition): unknown;\n\tunregisterHelper(name: string): unknown;\n}\n\n// ─── Abstract Base Class ─────────────────────────────────────────────────────\n\nexport abstract class HelperFactory {\n\t// ── Lazy caches (populated on first access) ──────────────────────────\n\tprivate _definitions: Map<string, HelperDefinition> | null = null;\n\tprivate _helperNames: readonly string[] | null = null;\n\tprivate _helperNamesSet: ReadonlySet<string> | null = null;\n\n\t// ── Abstract method — must be implemented by each factory ────────────\n\n\t/**\n\t * Populates the `defs` map with all helper definitions.\n\t *\n\t * Subclasses implement this method to register their helpers:\n\t * ```\n\t * protected buildDefinitions(defs: Map<string, HelperDefinition>): void {\n\t * defs.set(\"myHelper\", {\n\t * fn: (a: unknown) => String(a).toUpperCase(),\n\t * params: [{ name: \"a\", type: { type: \"string\" } }],\n\t * returnType: { type: \"string\" },\n\t * description: \"Converts to uppercase\",\n\t * });\n\t * }\n\t * ```\n\t *\n\t * @param defs - The map to populate with `[name, HelperDefinition]` entries\n\t */\n\tprotected abstract buildDefinitions(\n\t\tdefs: Map<string, HelperDefinition>,\n\t): void;\n\n\t// ── Public API ───────────────────────────────────────────────────────\n\n\t/**\n\t * Returns all definitions as a `Map<name, HelperDefinition>`.\n\t * The map is lazily built and cached on first access.\n\t */\n\tgetDefinitions(): Map<string, HelperDefinition> {\n\t\tif (!this._definitions) {\n\t\t\tthis._definitions = new Map();\n\t\t\tthis.buildDefinitions(this._definitions);\n\t\t}\n\t\treturn this._definitions;\n\t}\n\n\t/**\n\t * Returns the list of all helper names provided by this factory.\n\t */\n\tgetHelperNames(): readonly string[] {\n\t\tif (!this._helperNames) {\n\t\t\tthis._helperNames = [...this.getDefinitions().keys()];\n\t\t}\n\t\treturn this._helperNames;\n\t}\n\n\t/**\n\t * Checks whether a helper name belongs to this factory.\n\t *\n\t * @param name - The helper name to check\n\t */\n\tisHelper(name: string): boolean {\n\t\tif (!this._helperNamesSet) {\n\t\t\tthis._helperNamesSet = new Set(this.getHelperNames());\n\t\t}\n\t\treturn this._helperNamesSet.has(name);\n\t}\n\n\t/**\n\t * Registers all helpers from this factory on the given registry.\n\t *\n\t * @param registry - The engine or target registry\n\t */\n\tregister(registry: HelperRegistry): void {\n\t\tfor (const [name, def] of this.getDefinitions()) {\n\t\t\tregistry.registerHelper(name, def);\n\t\t}\n\t}\n\n\t/**\n\t * Removes all helpers from this factory from the given registry.\n\t *\n\t * @param registry - The engine or target registry\n\t */\n\tunregister(registry: HelperRegistry): void {\n\t\tfor (const name of this.getHelperNames()) {\n\t\t\tregistry.unregisterHelper(name);\n\t\t}\n\t}\n}\n"],"names":["HelperFactory","getDefinitions","_definitions","Map","buildDefinitions","getHelperNames","_helperNames","keys","isHelper","name","_helperNamesSet","Set","has","register","registry","def","registerHelper","unregister","unregisterHelper"],"mappings":"oLAwBA,OAAO,MAAeA,cAmCrBC,gBAAgD,CAC/C,GAAI,CAAC,IAAI,CAACC,YAAY,CAAE,CACvB,IAAI,CAACA,YAAY,CAAG,IAAIC,IACxB,IAAI,CAACC,gBAAgB,CAAC,IAAI,CAACF,YAAY,CACxC,CACA,OAAO,IAAI,CAACA,YAAY,AACzB,CAKAG,gBAAoC,CACnC,GAAI,CAAC,IAAI,CAACC,YAAY,CAAE,CACvB,IAAI,CAACA,YAAY,CAAG,IAAI,IAAI,CAACL,cAAc,GAAGM,IAAI,GAAG,AACtD,CACA,OAAO,IAAI,CAACD,YAAY,AACzB,CAOAE,SAASC,IAAY,CAAW,CAC/B,GAAI,CAAC,IAAI,CAACC,eAAe,CAAE,CAC1B,IAAI,CAACA,eAAe,CAAG,IAAIC,IAAI,IAAI,CAACN,cAAc,GACnD,CACA,OAAO,IAAI,CAACK,eAAe,CAACE,GAAG,CAACH,KACjC,CAOAI,SAASC,QAAwB,CAAQ,CACxC,IAAK,KAAM,CAACL,KAAMM,IAAI,GAAI,IAAI,CAACd,cAAc,GAAI,CAChDa,SAASE,cAAc,CAACP,KAAMM,IAC/B,CACD,CAOAE,WAAWH,QAAwB,CAAQ,CAC1C,IAAK,MAAML,QAAQ,IAAI,CAACJ,cAAc,GAAI,CACzCS,SAASI,gBAAgB,CAACT,KAC3B,CACD,eAnFA,sBAAQP,eAAqD,MAC7D,sBAAQI,eAAyC,MACjD,sBAAQI,kBAA8C,MAkFvD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/index.ts"],"sourcesContent":["export { HelperFactory, type HelperRegistry } from \"./helper-factory\";\nexport { LogicalHelpers } from \"./logical-helpers\";\nexport { MathHelpers } from \"./math-helpers\";\nexport { toNumber } from \"./utils\";\n"],"names":["HelperFactory","LogicalHelpers","MathHelpers","toNumber"],"mappings":"AAAA,OAASA,aAAa,KAA6B,kBAAmB,AACtE,QAASC,cAAc,KAAQ,mBAAoB,AACnD,QAASC,WAAW,KAAQ,gBAAiB,AAC7C,QAASC,QAAQ,KAAQ,SAAU"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HelperDefinition } from "../types.js";
|
|
2
|
+
import { HelperFactory } from "./helper-factory.js";
|
|
3
|
+
export declare class LogicalHelpers extends HelperFactory {
|
|
4
|
+
protected buildDefinitions(defs: Map<string, HelperDefinition>): void;
|
|
5
|
+
/** Registers eq, ne / neq */
|
|
6
|
+
private registerEquality;
|
|
7
|
+
/** Registers lt, lte / le, gt, gte / ge */
|
|
8
|
+
private registerComparison;
|
|
9
|
+
/** Registers not, and, or */
|
|
10
|
+
private registerLogicalOperators;
|
|
11
|
+
/** Registers contains, in */
|
|
12
|
+
private registerCollectionHelpers;
|
|
13
|
+
/** Registers the generic `compare` helper with operator as a parameter */
|
|
14
|
+
private registerGenericCompare;
|
|
15
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{HelperFactory}from"./helper-factory.js";import{toNumber}from"./utils.js";const SUPPORTED_OPERATORS=new Set(["==","===","!=","!==","<","<=",">",">="]);function applyOperator(a,op,b){switch(op){case"==":return a==b;case"===":return a===b;case"!=":return a!=b;case"!==":return a!==b;case"<":return toNumber(a)<toNumber(b);case"<=":return toNumber(a)<=toNumber(b);case">":return toNumber(a)>toNumber(b);case">=":return toNumber(a)>=toNumber(b)}}function isHandlebarsOptions(value){return value!==null&&typeof value==="object"&&"hash"in value&&"name"in value}export class LogicalHelpers extends HelperFactory{buildDefinitions(defs){this.registerEquality(defs);this.registerComparison(defs);this.registerLogicalOperators(defs);this.registerCollectionHelpers(defs);this.registerGenericCompare(defs)}registerEquality(defs){defs.set("eq",{fn:(a,b)=>a===b,params:[{name:"a",description:"Left value"},{name:"b",description:"Right value"}],returnType:{type:"boolean"},description:"Returns true if a is strictly equal to b: {{#if (eq a b)}}"});const neDef={fn:(a,b)=>a!==b,params:[{name:"a",description:"Left value"},{name:"b",description:"Right value"}],returnType:{type:"boolean"},description:"Returns true if a is not strictly equal to b: {{#if (ne a b)}}"};defs.set("ne",neDef);defs.set("neq",neDef)}registerComparison(defs){defs.set("lt",{fn:(a,b)=>toNumber(a)<toNumber(b),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a < b: {{#if (lt a b)}}"});const lteDef={fn:(a,b)=>toNumber(a)<=toNumber(b),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a <= b: {{#if (lte a b)}}"};defs.set("lte",lteDef);defs.set("le",lteDef);defs.set("gt",{fn:(a,b)=>toNumber(a)>toNumber(b),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a > b: {{#if (gt a b)}}"});const gteDef={fn:(a,b)=>toNumber(a)>=toNumber(b),params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"boolean"},description:"Returns true if a >= b: {{#if (gte a b)}}"};defs.set("gte",gteDef);defs.set("ge",gteDef)}registerLogicalOperators(defs){defs.set("not",{fn:value=>!value,params:[{name:"value",description:"Value to negate"}],returnType:{type:"boolean"},description:"Returns true if the value is falsy: {{#if (not active)}}"});defs.set("and",{fn:(a,b)=>!!a&&!!b,params:[{name:"a",description:"First condition"},{name:"b",description:"Second condition"}],returnType:{type:"boolean"},description:"Returns true if both values are truthy: {{#if (and a b)}}"});defs.set("or",{fn:(a,b)=>!!a||!!b,params:[{name:"a",description:"First condition"},{name:"b",description:"Second condition"}],returnType:{type:"boolean"},description:"Returns true if at least one value is truthy: {{#if (or a b)}}"})}registerCollectionHelpers(defs){defs.set("contains",{fn:(haystack,needle)=>{if(typeof haystack==="string"){return haystack.includes(String(needle))}if(Array.isArray(haystack)){return haystack.includes(needle)}return false},params:[{name:"haystack",description:"String or array to search in"},{name:"needle",description:"Value to search for"}],returnType:{type:"boolean"},description:'Checks if a string contains a substring or an array contains an element: {{#if (contains name "ali")}}'});defs.set("in",{fn:(...args)=>{if(args.length<2)return false;const value=args[0];const candidates=args.slice(1).filter(a=>!isHandlebarsOptions(a));return candidates.some(c=>c===value)},params:[{name:"value",description:"Value to look for"},{name:"candidates",description:'One or more candidate values (variadic): {{#if (in status "active" "pending")}}'}],returnType:{type:"boolean"},description:'Checks if a value is one of the provided options: {{#if (in status "active" "pending" "draft")}}'})}registerGenericCompare(defs){defs.set("compare",{fn:(a,operator,b)=>{const op=String(operator);if(!SUPPORTED_OPERATORS.has(op)){throw new Error(`[compare helper] Unknown operator "${op}". `+`Supported: ${[...SUPPORTED_OPERATORS].join(", ")} `)}return applyOperator(a,op,b)},params:[{name:"a",description:"Left operand"},{name:"operator",type:{type:"string",enum:["==","===","!=","!==","<","<=",">",">="]},description:'Comparison operator: "==", "===", "!=", "!==", "<", "<=", ">", ">="'},{name:"b",description:"Right operand"}],returnType:{type:"boolean"},description:'Generic comparison helper with operator as parameter: {{#if (compare a "<" b)}}. '+"Supported operators: ==, ===, !=, !==, <, <=, >, >="})}}
|
|
2
|
+
//# sourceMappingURL=logical-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/logical-helpers.ts"],"sourcesContent":["import type { HelperDefinition } from \"../types.ts\";\nimport { HelperFactory } from \"./helper-factory.ts\";\nimport { toNumber } from \"./utils.ts\";\n\n// ─── LogicalHelpers ──────────────────────────────────────────────────────────\n// Aggregates all logical / comparison helpers for the template engine.\n//\n// Provides two kinds of helpers:\n//\n// 1. **Named helpers** — one helper per operation (`eq`, `lt`, `not`, …)\n// Usage: `{{#if (eq status \"active\")}}`, `{{#if (lt price 100)}}`\n//\n// 2. **Generic `compare` helper** — single helper with the operator as a param\n// Usage: `{{#if (compare a \"<\" b)}}`, `{{#if (compare name \"==\" \"Alice\")}}`\n//\n// ─── Registration ────────────────────────────────────────────────────────────\n// LogicalHelpers are automatically pre-registered by the `Typebars` constructor.\n// They can also be registered manually on any object implementing\n// `HelperRegistry`:\n//\n// const factory = new LogicalHelpers();\n// factory.register(engine); // registers all helpers\n// factory.unregister(engine); // removes all helpers\n//\n// ─── Supported operators (generic `compare` helper) ──────────────────────────\n// == Loose equality\n// === Strict equality\n// != Loose inequality\n// !== Strict inequality\n// < Less than\n// <= Less than or equal\n// > Greater than\n// >= Greater than or equal\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Operators supported by the generic `compare` helper */\ntype CompareOperator = \"==\" | \"===\" | \"!=\" | \"!==\" | \"<\" | \"<=\" | \">\" | \">=\";\n\nconst SUPPORTED_OPERATORS = new Set<string>([\n\t\"==\",\n\t\"===\",\n\t\"!=\",\n\t\"!==\",\n\t\"<\",\n\t\"<=\",\n\t\">\",\n\t\">=\",\n]);\n\n// ─── Internal utilities ─────────────────────────────────────────────────────\n\n/**\n * Applies a comparison operator to two operands.\n */\nfunction applyOperator(a: unknown, op: CompareOperator, b: unknown): boolean {\n\tswitch (op) {\n\t\tcase \"==\":\n\t\t\t// biome-ignore lint/suspicious/noDoubleEquals: intentional loose equality\n\t\t\treturn a == b;\n\t\tcase \"===\":\n\t\t\treturn a === b;\n\t\tcase \"!=\":\n\t\t\t// biome-ignore lint/suspicious/noDoubleEquals: intentional loose inequality\n\t\t\treturn a != b;\n\t\tcase \"!==\":\n\t\t\treturn a !== b;\n\t\tcase \"<\":\n\t\t\treturn toNumber(a) < toNumber(b);\n\t\tcase \"<=\":\n\t\t\treturn toNumber(a) <= toNumber(b);\n\t\tcase \">\":\n\t\t\treturn toNumber(a) > toNumber(b);\n\t\tcase \">=\":\n\t\t\treturn toNumber(a) >= toNumber(b);\n\t}\n}\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// ─── Main class ─────────────────────────────────────────────────────────────\n\nexport class LogicalHelpers extends HelperFactory {\n\t// ─── buildDefinitions (required by HelperFactory) ──────────────────\n\n\tprotected buildDefinitions(defs: Map<string, HelperDefinition>): void {\n\t\tthis.registerEquality(defs);\n\t\tthis.registerComparison(defs);\n\t\tthis.registerLogicalOperators(defs);\n\t\tthis.registerCollectionHelpers(defs);\n\t\tthis.registerGenericCompare(defs);\n\t}\n\n\t// ── Equality helpers ─────────────────────────────────────────────\n\n\t/** Registers eq, ne / neq */\n\tprivate registerEquality(defs: Map<string, HelperDefinition>): void {\n\t\t// eq — Strict equality: {{#if (eq a b)}}\n\t\tdefs.set(\"eq\", {\n\t\t\tfn: (a: unknown, b: unknown) => a === b,\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", description: \"Left value\" },\n\t\t\t\t{ name: \"b\", description: \"Right value\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if a is strictly equal to b: {{#if (eq a b)}}\",\n\t\t});\n\n\t\t// ne / neq — Strict inequality: {{#if (ne a b)}}\n\t\tconst neDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => a !== b,\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", description: \"Left value\" },\n\t\t\t\t{ name: \"b\", description: \"Right value\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription:\n\t\t\t\t\"Returns true if a is not strictly equal to b: {{#if (ne a b)}}\",\n\t\t};\n\t\tdefs.set(\"ne\", neDef);\n\t\tdefs.set(\"neq\", neDef);\n\t}\n\n\t// ── Comparison helpers ───────────────────────────────────────────\n\n\t/** Registers lt, lte / le, gt, gte / ge */\n\tprivate registerComparison(defs: Map<string, HelperDefinition>): void {\n\t\t// lt — Less than: {{#if (lt a b)}}\n\t\tdefs.set(\"lt\", {\n\t\t\tfn: (a: unknown, b: unknown) => toNumber(a) < toNumber(b),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Left operand\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Right operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if a < b: {{#if (lt a b)}}\",\n\t\t});\n\n\t\t// lte / le — Less than or equal: {{#if (lte a b)}}\n\t\tconst lteDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => toNumber(a) <= toNumber(b),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Left operand\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Right operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if a <= b: {{#if (lte a b)}}\",\n\t\t};\n\t\tdefs.set(\"lte\", lteDef);\n\t\tdefs.set(\"le\", lteDef);\n\n\t\t// gt — Greater than: {{#if (gt a b)}}\n\t\tdefs.set(\"gt\", {\n\t\t\tfn: (a: unknown, b: unknown) => toNumber(a) > toNumber(b),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Left operand\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Right operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if a > b: {{#if (gt a b)}}\",\n\t\t});\n\n\t\t// gte / ge — Greater than or equal: {{#if (gte a b)}}\n\t\tconst gteDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => toNumber(a) >= toNumber(b),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Left operand\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Right operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if a >= b: {{#if (gte a b)}}\",\n\t\t};\n\t\tdefs.set(\"gte\", gteDef);\n\t\tdefs.set(\"ge\", gteDef);\n\t}\n\n\t// ── Logical operators ────────────────────────────────────────────\n\n\t/** Registers not, and, or */\n\tprivate registerLogicalOperators(defs: Map<string, HelperDefinition>): void {\n\t\t// not — Logical negation: {{#if (not active)}}\n\t\tdefs.set(\"not\", {\n\t\t\tfn: (value: unknown) => !value,\n\t\t\tparams: [{ name: \"value\", description: \"Value to negate\" }],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if the value is falsy: {{#if (not active)}}\",\n\t\t});\n\n\t\t// and — Logical AND: {{#if (and a b)}}\n\t\tdefs.set(\"and\", {\n\t\t\tfn: (a: unknown, b: unknown) => !!a && !!b,\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", description: \"First condition\" },\n\t\t\t\t{ name: \"b\", description: \"Second condition\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription: \"Returns true if both values are truthy: {{#if (and a b)}}\",\n\t\t});\n\n\t\t// or — Logical OR: {{#if (or a b)}}\n\t\tdefs.set(\"or\", {\n\t\t\tfn: (a: unknown, b: unknown) => !!a || !!b,\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", description: \"First condition\" },\n\t\t\t\t{ name: \"b\", description: \"Second condition\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription:\n\t\t\t\t\"Returns true if at least one value is truthy: {{#if (or a b)}}\",\n\t\t});\n\t}\n\n\t// ── Collection / String helpers ──────────────────────────────────\n\n\t/** Registers contains, in */\n\tprivate registerCollectionHelpers(defs: Map<string, HelperDefinition>): void {\n\t\t// contains — Checks if a string contains a substring or an array contains an element\n\t\t// Usage: {{#if (contains name \"ali\")}} or {{#if (contains tags \"admin\")}}\n\t\tdefs.set(\"contains\", {\n\t\t\tfn: (haystack: unknown, needle: unknown) => {\n\t\t\t\tif (typeof haystack === \"string\") {\n\t\t\t\t\treturn haystack.includes(String(needle));\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(haystack)) {\n\t\t\t\t\treturn haystack.includes(needle);\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"haystack\",\n\t\t\t\t\tdescription: \"String or array to search in\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"needle\",\n\t\t\t\t\tdescription: \"Value to search for\",\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription:\n\t\t\t\t'Checks if a string contains a substring or an array contains an element: {{#if (contains name \"ali\")}}',\n\t\t});\n\n\t\t// in — Checks if a value is one of the provided options (variadic)\n\t\t// Usage: {{#if (in status \"active\" \"pending\" \"draft\")}}\n\t\tdefs.set(\"in\", {\n\t\t\tfn: (...args: unknown[]) => {\n\t\t\t\t// Handlebars always passes an options object as the last argument.\n\t\t\t\t// We need to exclude it from the candidate list.\n\t\t\t\tif (args.length < 2) return false;\n\n\t\t\t\tconst value = args[0];\n\t\t\t\t// Filter out the trailing Handlebars options object\n\t\t\t\tconst candidates = args.slice(1).filter((a) => !isHandlebarsOptions(a));\n\n\t\t\t\treturn candidates.some((c) => c === value);\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"value\",\n\t\t\t\t\tdescription: \"Value to look for\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"candidates\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'One or more candidate values (variadic): {{#if (in status \"active\" \"pending\")}}',\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription:\n\t\t\t\t'Checks if a value is one of the provided options: {{#if (in status \"active\" \"pending\" \"draft\")}}',\n\t\t});\n\t}\n\n\t// ── Generic helper ───────────────────────────────────────────────\n\n\t/** Registers the generic `compare` helper with operator as a parameter */\n\tprivate registerGenericCompare(defs: Map<string, HelperDefinition>): void {\n\t\t// Usage: {{#if (compare a \"<\" b)}}, {{#if (compare name \"===\" \"Alice\")}}\n\t\tdefs.set(\"compare\", {\n\t\t\tfn: (a: unknown, operator: unknown, b: unknown) => {\n\t\t\t\tconst op = String(operator);\n\t\t\t\tif (!SUPPORTED_OPERATORS.has(op)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[compare helper] Unknown operator \"${op}\". ` +\n\t\t\t\t\t\t\t`Supported: ${[...SUPPORTED_OPERATORS].join(\", \")} `,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn applyOperator(a, op as CompareOperator, b);\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", description: \"Left operand\" },\n\t\t\t\t{\n\t\t\t\t\tname: \"operator\",\n\t\t\t\t\ttype: {\n\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\tenum: [\"==\", \"===\", \"!=\", \"!==\", \"<\", \"<=\", \">\", \">=\"],\n\t\t\t\t\t},\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t'Comparison operator: \"==\", \"===\", \"!=\", \"!==\", \"<\", \"<=\", \">\", \">=\"',\n\t\t\t\t},\n\t\t\t\t{ name: \"b\", description: \"Right operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"boolean\" },\n\t\t\tdescription:\n\t\t\t\t'Generic comparison helper with operator as parameter: {{#if (compare a \"<\" b)}}. ' +\n\t\t\t\t\"Supported operators: ==, ===, !=, !==, <, <=, >, >=\",\n\t\t});\n\t}\n}\n"],"names":["HelperFactory","toNumber","SUPPORTED_OPERATORS","Set","applyOperator","a","op","b","isHandlebarsOptions","value","LogicalHelpers","buildDefinitions","defs","registerEquality","registerComparison","registerLogicalOperators","registerCollectionHelpers","registerGenericCompare","set","fn","params","name","description","returnType","type","neDef","lteDef","gteDef","haystack","needle","includes","String","Array","isArray","args","length","candidates","slice","filter","some","c","operator","has","Error","join","enum"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HelperDefinition } from "../types.js";
|
|
2
|
+
import { HelperFactory } from "./helper-factory.js";
|
|
3
|
+
export declare class MathHelpers extends HelperFactory {
|
|
4
|
+
protected buildDefinitions(defs: Map<string, HelperDefinition>): void;
|
|
5
|
+
/** Registers add, subtract/sub, multiply/mul, divide/div, modulo/mod, pow */
|
|
6
|
+
private registerBinaryOperators;
|
|
7
|
+
/** Registers abs, ceil, floor, round, sqrt */
|
|
8
|
+
private registerUnaryFunctions;
|
|
9
|
+
/** Registers min and max */
|
|
10
|
+
private registerMinMax;
|
|
11
|
+
/** Registers the generic `math` helper with operator as a parameter */
|
|
12
|
+
private registerGenericMath;
|
|
13
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{HelperFactory}from"./helper-factory.js";import{toNumber}from"./utils.js";const SUPPORTED_OPERATORS=new Set(["+","-","*","/","%","**"]);const num=value=>toNumber(value,0);function applyOperator(a,op,b){switch(op){case"+":return a+b;case"-":return a-b;case"*":return a*b;case"/":return b===0?Infinity:a/b;case"%":return b===0?NaN:a%b;case"**":return a**b}}export class MathHelpers extends HelperFactory{buildDefinitions(defs){this.registerBinaryOperators(defs);this.registerUnaryFunctions(defs);this.registerMinMax(defs);this.registerGenericMath(defs)}registerBinaryOperators(defs){const addDef={fn:(a,b)=>num(a)+num(b),params:[{name:"a",type:{type:"number"},description:"First operand"},{name:"b",type:{type:"number"},description:"Second operand"}],returnType:{type:"number"},description:"Adds two numbers: {{ add a b }}"};defs.set("add",addDef);const subtractDef={fn:(a,b)=>num(a)-num(b),params:[{name:"a",type:{type:"number"},description:"Value to subtract from"},{name:"b",type:{type:"number"},description:"Value to subtract"}],returnType:{type:"number"},description:"Subtracts b from a: {{ subtract a b }}"};defs.set("subtract",subtractDef);defs.set("sub",subtractDef);const multiplyDef={fn:(a,b)=>num(a)*num(b),params:[{name:"a",type:{type:"number"},description:"First factor"},{name:"b",type:{type:"number"},description:"Second factor"}],returnType:{type:"number"},description:"Multiplies two numbers: {{ multiply a b }}"};defs.set("multiply",multiplyDef);defs.set("mul",multiplyDef);const divideDef={fn:(a,b)=>{const divisor=num(b);return divisor===0?Infinity:num(a)/divisor},params:[{name:"a",type:{type:"number"},description:"Dividend"},{name:"b",type:{type:"number"},description:"Divisor"}],returnType:{type:"number"},description:"Divides a by b: {{ divide a b }}. Returns Infinity if b is 0."};defs.set("divide",divideDef);defs.set("div",divideDef);const moduloDef={fn:(a,b)=>{const divisor=num(b);return divisor===0?NaN:num(a)%divisor},params:[{name:"a",type:{type:"number"},description:"Dividend"},{name:"b",type:{type:"number"},description:"Divisor"}],returnType:{type:"number"},description:"Returns the remainder of a divided by b: {{ modulo a b }}"};defs.set("modulo",moduloDef);defs.set("mod",moduloDef);defs.set("pow",{fn:(base,exponent)=>num(base)**num(exponent),params:[{name:"base",type:{type:"number"},description:"The base"},{name:"exponent",type:{type:"number"},description:"The exponent"}],returnType:{type:"number"},description:"Raises base to the power of exponent: {{ pow base exponent }}"})}registerUnaryFunctions(defs){defs.set("abs",{fn:value=>Math.abs(num(value)),params:[{name:"value",type:{type:"number"},description:"The number"}],returnType:{type:"number"},description:"Returns the absolute value: {{ abs value }}"});defs.set("ceil",{fn:value=>Math.ceil(num(value)),params:[{name:"value",type:{type:"number"},description:"The number to round up"}],returnType:{type:"number"},description:"Rounds up to the nearest integer: {{ ceil value }}"});defs.set("floor",{fn:value=>Math.floor(num(value)),params:[{name:"value",type:{type:"number"},description:"The number to round down"}],returnType:{type:"number"},description:"Rounds down to the nearest integer: {{ floor value }}"});defs.set("round",{fn:(value,precision)=>{const n=num(value);if(precision===undefined||precision===null||typeof precision==="object"){return Math.round(n)}const p=num(precision);const factor=10**p;return Math.round(n*factor)/factor},params:[{name:"value",type:{type:"number"},description:"The number to round"},{name:"precision",type:{type:"number"},description:"Number of decimal places (default: 0)",optional:true}],returnType:{type:"number"},description:"Rounds to the nearest integer or to a given precision: {{ round value }} or {{ round value 2 }}"});defs.set("sqrt",{fn:value=>Math.sqrt(num(value)),params:[{name:"value",type:{type:"number"},description:"The number"}],returnType:{type:"number"},description:"Returns the square root: {{ sqrt value }}"})}registerMinMax(defs){defs.set("min",{fn:(a,b)=>Math.min(num(a),num(b)),params:[{name:"a",type:{type:"number"},description:"First number"},{name:"b",type:{type:"number"},description:"Second number"}],returnType:{type:"number"},description:"Returns the smaller of two numbers: {{ min a b }}"});defs.set("max",{fn:(a,b)=>Math.max(num(a),num(b)),params:[{name:"a",type:{type:"number"},description:"First number"},{name:"b",type:{type:"number"},description:"Second number"}],returnType:{type:"number"},description:"Returns the larger of two numbers: {{ max a b }}"})}registerGenericMath(defs){defs.set("math",{fn:(a,operator,b)=>{const op=String(operator);if(!SUPPORTED_OPERATORS.has(op)){throw new Error(`[math helper] Unknown operator "${op}". `+`Supported: ${[...SUPPORTED_OPERATORS].join(", ")} `)}return applyOperator(num(a),op,num(b))},params:[{name:"a",type:{type:"number"},description:"Left operand"},{name:"operator",type:{type:"string",enum:["+","-","*","/","%","**"]},description:'Arithmetic operator: "+", "-", "*", "/", "%", "**"'},{name:"b",type:{type:"number"},description:"Right operand"}],returnType:{type:"number"},description:'Generic math helper with operator as parameter: {{ math a "+" b }}, {{ math a "/" b }}. '+"Supported operators: +, -, *, /, %, **"})}}
|
|
2
|
+
//# sourceMappingURL=math-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/math-helpers.ts"],"sourcesContent":["import type { HelperDefinition } from \"../types.ts\";\nimport { HelperFactory } from \"./helper-factory.ts\";\nimport { toNumber } from \"./utils.ts\";\n\n// ─── MathHelpers ─────────────────────────────────────────────────────────────\n// Aggregates all math-related helpers for the template engine.\n//\n// Provides two kinds of helpers:\n//\n// 1. **Named helpers** — one helper per operation (`add`, `subtract`, `divide`, …)\n// Usage: `{{ add a b }}`, `{{ abs value }}`, `{{ round value 2 }}`\n//\n// 2. **Generic `math` helper** — single helper with the operator as a parameter\n// Usage: `{{ math a \"+\" b }}`, `{{ math a \"/\" b }}`, `{{ math a \"**\" b }}`\n//\n// ─── Registration ────────────────────────────────────────────────────────────\n// MathHelpers are automatically pre-registered by the `Typebars` constructor.\n// They can also be registered manually on any object implementing\n// `HelperRegistry`:\n//\n// const factory = new MathHelpers();\n// factory.register(engine); // registers all helpers\n// factory.unregister(engine); // removes all helpers\n//\n// ─── Supported operators (generic `math` helper) ─────────────────────────────\n// + Addition\n// - Subtraction\n// * Multiplication\n// / Division\n// % Modulo\n// ** Exponentiation\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Operators supported by the generic `math` helper */\ntype MathOperator = \"+\" | \"-\" | \"*\" | \"/\" | \"%\" | \"**\";\n\nconst SUPPORTED_OPERATORS = new Set<string>([\"+\", \"-\", \"*\", \"/\", \"%\", \"**\"]);\n\n// ─── Internal utilities ─────────────────────────────────────────────────────\n\n/** Converts a value to a number with a fallback of `0` for math operations. */\nconst num = (value: unknown): number => toNumber(value, 0);\n\n/**\n * Applies a binary operator to two operands.\n */\nfunction applyOperator(a: number, op: MathOperator, b: number): number {\n\tswitch (op) {\n\t\tcase \"+\":\n\t\t\treturn a + b;\n\t\tcase \"-\":\n\t\t\treturn a - b;\n\t\tcase \"*\":\n\t\t\treturn a * b;\n\t\tcase \"/\":\n\t\t\treturn b === 0 ? Infinity : a / b;\n\t\tcase \"%\":\n\t\t\treturn b === 0 ? NaN : a % b;\n\t\tcase \"**\":\n\t\t\treturn a ** b;\n\t}\n}\n\n// ─── Main class ─────────────────────────────────────────────────────────────\n\nexport class MathHelpers extends HelperFactory {\n\t// ─── buildDefinitions (required by HelperFactory) ──────────────────\n\n\tprotected buildDefinitions(defs: Map<string, HelperDefinition>): void {\n\t\tthis.registerBinaryOperators(defs);\n\t\tthis.registerUnaryFunctions(defs);\n\t\tthis.registerMinMax(defs);\n\t\tthis.registerGenericMath(defs);\n\t}\n\n\t// ── Binary operators ─────────────────────────────────────────────\n\n\t/** Registers add, subtract/sub, multiply/mul, divide/div, modulo/mod, pow */\n\tprivate registerBinaryOperators(defs: Map<string, HelperDefinition>): void {\n\t\t// add — Addition : {{ add a b }}\n\t\tconst addDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => num(a) + num(b),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"First operand\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Second operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Adds two numbers: {{ add a b }}\",\n\t\t};\n\t\tdefs.set(\"add\", addDef);\n\n\t\t// subtract / sub — Subtraction: {{ subtract a b }} or {{ sub a b }}\n\t\tconst subtractDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => num(a) - num(b),\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"a\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"Value to subtract from\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"b\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"Value to subtract\",\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Subtracts b from a: {{ subtract a b }}\",\n\t\t};\n\t\tdefs.set(\"subtract\", subtractDef);\n\t\tdefs.set(\"sub\", subtractDef);\n\n\t\t// multiply / mul — Multiplication: {{ multiply a b }} or {{ mul a b }}\n\t\tconst multiplyDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => num(a) * num(b),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"First factor\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Second factor\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Multiplies two numbers: {{ multiply a b }}\",\n\t\t};\n\t\tdefs.set(\"multiply\", multiplyDef);\n\t\tdefs.set(\"mul\", multiplyDef);\n\n\t\t// divide / div — Division: {{ divide a b }} or {{ div a b }}\n\t\tconst divideDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => {\n\t\t\t\tconst divisor = num(b);\n\t\t\t\treturn divisor === 0 ? Infinity : num(a) / divisor;\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Dividend\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Divisor\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription:\n\t\t\t\t\"Divides a by b: {{ divide a b }}. Returns Infinity if b is 0.\",\n\t\t};\n\t\tdefs.set(\"divide\", divideDef);\n\t\tdefs.set(\"div\", divideDef);\n\n\t\t// modulo / mod — Modulo: {{ modulo a b }} or {{ mod a b }}\n\t\tconst moduloDef: HelperDefinition = {\n\t\t\tfn: (a: unknown, b: unknown) => {\n\t\t\t\tconst divisor = num(b);\n\t\t\t\treturn divisor === 0 ? NaN : num(a) % divisor;\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Dividend\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Divisor\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Returns the remainder of a divided by b: {{ modulo a b }}\",\n\t\t};\n\t\tdefs.set(\"modulo\", moduloDef);\n\t\tdefs.set(\"mod\", moduloDef);\n\n\t\t// pow — Exponentiation : {{ pow base exponent }}\n\t\tdefs.set(\"pow\", {\n\t\t\tfn: (base: unknown, exponent: unknown) => num(base) ** num(exponent),\n\t\t\tparams: [\n\t\t\t\t{ name: \"base\", type: { type: \"number\" }, description: \"The base\" },\n\t\t\t\t{\n\t\t\t\t\tname: \"exponent\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"The exponent\",\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription:\n\t\t\t\t\"Raises base to the power of exponent: {{ pow base exponent }}\",\n\t\t});\n\t}\n\n\t// ── Unary functions ──────────────────────────────────────────────\n\n\t/** Registers abs, ceil, floor, round, sqrt */\n\tprivate registerUnaryFunctions(defs: Map<string, HelperDefinition>): void {\n\t\t// abs — Absolute value: {{ abs value }}\n\t\tdefs.set(\"abs\", {\n\t\t\tfn: (value: unknown) => Math.abs(num(value)),\n\t\t\tparams: [\n\t\t\t\t{ name: \"value\", type: { type: \"number\" }, description: \"The number\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Returns the absolute value: {{ abs value }}\",\n\t\t});\n\n\t\t// ceil — Round up: {{ ceil value }}\n\t\tdefs.set(\"ceil\", {\n\t\t\tfn: (value: unknown) => Math.ceil(num(value)),\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"value\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"The number to round up\",\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Rounds up to the nearest integer: {{ ceil value }}\",\n\t\t});\n\n\t\t// floor — Round down: {{ floor value }}\n\t\tdefs.set(\"floor\", {\n\t\t\tfn: (value: unknown) => Math.floor(num(value)),\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"value\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"The number to round down\",\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Rounds down to the nearest integer: {{ floor value }}\",\n\t\t});\n\n\t\t// round — Rounding: {{ round value }} or {{ round value precision }}\n\t\t// With precision: {{ round 3.14159 2 }} → 3.14\n\t\tdefs.set(\"round\", {\n\t\t\tfn: (value: unknown, precision: unknown) => {\n\t\t\t\tconst n = num(value);\n\t\t\t\t// If precision is a Handlebars options object (not a number),\n\t\t\t\t// it means the second parameter was not provided.\n\t\t\t\tif (\n\t\t\t\t\tprecision === undefined ||\n\t\t\t\t\tprecision === null ||\n\t\t\t\t\ttypeof precision === \"object\"\n\t\t\t\t) {\n\t\t\t\t\treturn Math.round(n);\n\t\t\t\t}\n\t\t\t\tconst p = num(precision);\n\t\t\t\tconst factor = 10 ** p;\n\t\t\t\treturn Math.round(n * factor) / factor;\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{\n\t\t\t\t\tname: \"value\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"The number to round\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: \"precision\",\n\t\t\t\t\ttype: { type: \"number\" },\n\t\t\t\t\tdescription: \"Number of decimal places (default: 0)\",\n\t\t\t\t\toptional: true,\n\t\t\t\t},\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription:\n\t\t\t\t\"Rounds to the nearest integer or to a given precision: {{ round value }} or {{ round value 2 }}\",\n\t\t});\n\n\t\t// sqrt — Square root: {{ sqrt value }}\n\t\tdefs.set(\"sqrt\", {\n\t\t\tfn: (value: unknown) => Math.sqrt(num(value)),\n\t\t\tparams: [\n\t\t\t\t{ name: \"value\", type: { type: \"number\" }, description: \"The number\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Returns the square root: {{ sqrt value }}\",\n\t\t});\n\t}\n\n\t// ── Min / Max ────────────────────────────────────────────────────\n\n\t/** Registers min and max */\n\tprivate registerMinMax(defs: Map<string, HelperDefinition>): void {\n\t\t// min — Minimum : {{ min a b }}\n\t\tdefs.set(\"min\", {\n\t\t\tfn: (a: unknown, b: unknown) => Math.min(num(a), num(b)),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"First number\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Second number\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Returns the smaller of two numbers: {{ min a b }}\",\n\t\t});\n\n\t\t// max — Maximum : {{ max a b }}\n\t\tdefs.set(\"max\", {\n\t\t\tfn: (a: unknown, b: unknown) => Math.max(num(a), num(b)),\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"First number\" },\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Second number\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription: \"Returns the larger of two numbers: {{ max a b }}\",\n\t\t});\n\t}\n\n\t// ── Generic helper ───────────────────────────────────────────────\n\n\t/** Registers the generic `math` helper with operator as a parameter */\n\tprivate registerGenericMath(defs: Map<string, HelperDefinition>): void {\n\t\t// Usage : {{ math a \"+\" b }}, {{ math a \"/\" b }}, {{ math a \"**\" b }}\n\t\tdefs.set(\"math\", {\n\t\t\tfn: (a: unknown, operator: unknown, b: unknown) => {\n\t\t\t\tconst op = String(operator);\n\t\t\t\tif (!SUPPORTED_OPERATORS.has(op)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`[math helper] Unknown operator \"${op}\". ` +\n\t\t\t\t\t\t\t`Supported: ${[...SUPPORTED_OPERATORS].join(\", \")} `,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn applyOperator(num(a), op as MathOperator, num(b));\n\t\t\t},\n\t\t\tparams: [\n\t\t\t\t{ name: \"a\", type: { type: \"number\" }, description: \"Left operand\" },\n\t\t\t\t{\n\t\t\t\t\tname: \"operator\",\n\t\t\t\t\ttype: { type: \"string\", enum: [\"+\", \"-\", \"*\", \"/\", \"%\", \"**\"] },\n\t\t\t\t\tdescription: 'Arithmetic operator: \"+\", \"-\", \"*\", \"/\", \"%\", \"**\"',\n\t\t\t\t},\n\t\t\t\t{ name: \"b\", type: { type: \"number\" }, description: \"Right operand\" },\n\t\t\t],\n\t\t\treturnType: { type: \"number\" },\n\t\t\tdescription:\n\t\t\t\t'Generic math helper with operator as parameter: {{ math a \"+\" b }}, {{ math a \"/\" b }}. ' +\n\t\t\t\t\"Supported operators: +, -, *, /, %, **\",\n\t\t});\n\t}\n}\n"],"names":["HelperFactory","toNumber","SUPPORTED_OPERATORS","Set","num","value","applyOperator","a","op","b","Infinity","NaN","MathHelpers","buildDefinitions","defs","registerBinaryOperators","registerUnaryFunctions","registerMinMax","registerGenericMath","addDef","fn","params","name","type","description","returnType","set","subtractDef","multiplyDef","divideDef","divisor","moduloDef","base","exponent","Math","abs","ceil","floor","precision","n","undefined","round","p","factor","optional","sqrt","min","max","operator","String","has","Error","join","enum"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an unknown value to a number.
|
|
3
|
+
*
|
|
4
|
+
* Conversion rules:
|
|
5
|
+
* - `number` → returned as-is
|
|
6
|
+
* - `string` → parsed via `Number()`; returns `fallback` if result is `NaN`
|
|
7
|
+
* - `boolean` → `true` → `1`, `false` → `0`
|
|
8
|
+
* - anything else → `fallback`
|
|
9
|
+
*
|
|
10
|
+
* The `fallback` parameter lets each consumer choose the right semantics:
|
|
11
|
+
* - **Math helpers** pass `0` so that invalid inputs silently become zero
|
|
12
|
+
* (e.g. `add("abc", 5)` → `5`).
|
|
13
|
+
* - **Logical helpers** pass `NaN` (the default) so that invalid comparisons
|
|
14
|
+
* evaluate to `false` (e.g. `lt("abc", 5)` → `false`).
|
|
15
|
+
*
|
|
16
|
+
* @param value - The value to convert
|
|
17
|
+
* @param fallback - Value returned when conversion fails (default: `NaN`)
|
|
18
|
+
*/
|
|
19
|
+
export declare function toNumber(value: unknown, fallback?: number): number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/helpers/utils.ts"],"sourcesContent":["// ─── Shared Helper Utilities ─────────────────────────────────────────────────\n// Common utilities used across helper packs (MathHelpers, LogicalHelpers, …).\n\n/**\n * Converts an unknown value to a number.\n *\n * Conversion rules:\n * - `number` → returned as-is\n * - `string` → parsed via `Number()`; returns `fallback` if result is `NaN`\n * - `boolean` → `true` → `1`, `false` → `0`\n * - anything else → `fallback`\n *\n * The `fallback` parameter lets each consumer choose the right semantics:\n * - **Math helpers** pass `0` so that invalid inputs silently become zero\n * (e.g. `add(\"abc\", 5)` → `5`).\n * - **Logical helpers** pass `NaN` (the default) so that invalid comparisons\n * evaluate to `false` (e.g. `lt(\"abc\", 5)` → `false`).\n *\n * @param value - The value to convert\n * @param fallback - Value returned when conversion fails (default: `NaN`)\n */\nexport function toNumber(value: unknown, fallback: number = NaN): number {\n\tif (typeof value === \"number\") return value;\n\tif (typeof value === \"string\") {\n\t\tconst n = Number(value);\n\t\treturn Number.isNaN(n) ? fallback : n;\n\t}\n\tif (typeof value === \"boolean\") return value ? 1 : 0;\n\treturn fallback;\n}\n"],"names":["toNumber","value","fallback","NaN","n","Number","isNaN"],"mappings":"AAqBA,OAAO,SAASA,SAASC,KAAc,CAAEC,SAAmBC,GAAG,EAC9D,GAAI,OAAOF,QAAU,SAAU,OAAOA,MACtC,GAAI,OAAOA,QAAU,SAAU,CAC9B,MAAMG,EAAIC,OAAOJ,OACjB,OAAOI,OAAOC,KAAK,CAACF,GAAKF,SAAWE,CACrC,CACA,GAAI,OAAOH,QAAU,UAAW,OAAOA,MAAQ,EAAI,EACnD,OAAOC,QACR"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts"],"sourcesContent":["export * from \"./errors\";\nexport { Typebars } from \"./typebars\";\nexport { defineHelper } from \"./types\";\n"],"names":["Typebars","defineHelper"],"mappings":"AAAA,WAAc,UAAW,AACzB,QAASA,QAAQ,KAAQ,YAAa,AACtC,QAASC,YAAY,KAAQ,SAAU"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a template string and returns the Handlebars AST.
|
|
3
|
+
*
|
|
4
|
+
* This function does not cache results — caching is managed at the
|
|
5
|
+
* `Typebars` instance level via its own configurable LRU cache.
|
|
6
|
+
*
|
|
7
|
+
* @param template - The template string to parse (e.g. `"Hello {{name}}"`)
|
|
8
|
+
* @returns The root AST node (`hbs.AST.Program`)
|
|
9
|
+
* @throws {TemplateParseError} if the template syntax is invalid
|
|
10
|
+
*/
|
|
11
|
+
export declare function parse(template: string): hbs.AST.Program;
|
|
12
|
+
/**
|
|
13
|
+
* Determines whether the AST represents a template consisting of a single
|
|
14
|
+
* expression `{{expression}}` with no text content around it.
|
|
15
|
+
*
|
|
16
|
+
* This matters for return type inference:
|
|
17
|
+
* - Template `{{value}}` → returns the raw type of `value` (number, object…)
|
|
18
|
+
* - Template `Hello {{name}}` → always returns `string` (concatenation)
|
|
19
|
+
*
|
|
20
|
+
* @param ast - The parsed AST of the template
|
|
21
|
+
* @returns `true` if the template is a single expression
|
|
22
|
+
*/
|
|
23
|
+
export declare function isSingleExpression(ast: hbs.AST.Program): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Extracts the path segments from a Handlebars `PathExpression`.
|
|
26
|
+
*
|
|
27
|
+
* Handlebars decomposes `user.address.city` into `{ parts: ["user", "address", "city"] }`.
|
|
28
|
+
* This function safely extracts those segments.
|
|
29
|
+
*
|
|
30
|
+
* @param expr - The expression to extract the path from
|
|
31
|
+
* @returns The path segments, or an empty array if the expression is not
|
|
32
|
+
* a `PathExpression`
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractPathSegments(expr: hbs.AST.Expression): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether an AST expression is a `PathExpression` pointing to `this`
|
|
37
|
+
* (used inside `{{#each}}` blocks).
|
|
38
|
+
*/
|
|
39
|
+
export declare function isThisExpression(expr: hbs.AST.Expression): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Returns the semantically significant statements of a Program by
|
|
42
|
+
* filtering out `ContentStatement` nodes that contain only whitespace.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getEffectiveBody(program: hbs.AST.Program): hbs.AST.Statement[];
|
|
45
|
+
/**
|
|
46
|
+
* Determines whether a Program effectively consists of a single
|
|
47
|
+
* `BlockStatement` (ignoring surrounding whitespace).
|
|
48
|
+
*
|
|
49
|
+
* Recognized examples:
|
|
50
|
+
* ```
|
|
51
|
+
* {{#if x}}...{{/if}}
|
|
52
|
+
*
|
|
53
|
+
* {{#each items}}...{{/each}}
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @returns The single `BlockStatement`, or `null` if the program contains
|
|
57
|
+
* other significant nodes.
|
|
58
|
+
*/
|
|
59
|
+
export declare function getEffectivelySingleBlock(program: hbs.AST.Program): hbs.AST.BlockStatement | null;
|
|
60
|
+
/**
|
|
61
|
+
* Determines whether a Program effectively consists of a single
|
|
62
|
+
* `MustacheStatement` (ignoring surrounding whitespace).
|
|
63
|
+
*
|
|
64
|
+
* Example: ` {{age}} ` → true
|
|
65
|
+
*/
|
|
66
|
+
export declare function getEffectivelySingleExpression(program: hbs.AST.Program): hbs.AST.MustacheStatement | null;
|
|
67
|
+
/**
|
|
68
|
+
* Determines whether an AST can be executed via the fast-path (direct
|
|
69
|
+
* concatenation without going through `Handlebars.compile()`).
|
|
70
|
+
*
|
|
71
|
+
* The fast-path is possible when the template only contains:
|
|
72
|
+
* - `ContentStatement` nodes (static text)
|
|
73
|
+
* - Simple `MustacheStatement` nodes (no params, no hash)
|
|
74
|
+
*
|
|
75
|
+
* This excludes:
|
|
76
|
+
* - Block helpers (`{{#if}}`, `{{#each}}`, etc.)
|
|
77
|
+
* - Inline helpers (`{{uppercase name}}`)
|
|
78
|
+
* - Sub-expressions
|
|
79
|
+
*
|
|
80
|
+
* @param ast - The parsed AST of the template
|
|
81
|
+
* @returns `true` if the template can use the fast-path
|
|
82
|
+
*/
|
|
83
|
+
export declare function canUseFastPath(ast: hbs.AST.Program): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Attempts to detect the type of a raw text literal.
|
|
86
|
+
*
|
|
87
|
+
* @param text - The trimmed text from a ContentStatement or group of ContentStatements
|
|
88
|
+
* @returns The detected JSON Schema type, or `null` if it's free-form text (string).
|
|
89
|
+
*/
|
|
90
|
+
export declare function detectLiteralType(text: string): "number" | "boolean" | "null" | null;
|
|
91
|
+
/**
|
|
92
|
+
* Coerces a raw string from Handlebars rendering to its actual type
|
|
93
|
+
* if it represents a literal (number, boolean, null).
|
|
94
|
+
* Returns the raw (untrimmed) string otherwise.
|
|
95
|
+
*/
|
|
96
|
+
export declare function coerceLiteral(raw: string): unknown;
|
|
97
|
+
/** Result of parsing a path segment with a potential identifier */
|
|
98
|
+
export interface ParsedIdentifier {
|
|
99
|
+
/** The variable name, without the `:N` suffix */
|
|
100
|
+
key: string;
|
|
101
|
+
/** The numeric identifier, or `null` if absent */
|
|
102
|
+
identifier: number | null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parses an individual path segment to extract the key and optional identifier.
|
|
106
|
+
*
|
|
107
|
+
* @param segment - A raw path segment (e.g. `"meetingId:1"` or `"meetingId"`)
|
|
108
|
+
* @returns An object `{ key, identifier }`
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```
|
|
112
|
+
* parseIdentifier("meetingId:1") // → { key: "meetingId", identifier: 1 }
|
|
113
|
+
* parseIdentifier("meetingId") // → { key: "meetingId", identifier: null }
|
|
114
|
+
* parseIdentifier("meetingId:0") // → { key: "meetingId", identifier: 0 }
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function parseIdentifier(segment: string): ParsedIdentifier;
|
|
118
|
+
/** Result of extracting the identifier from a complete expression */
|
|
119
|
+
export interface ExpressionIdentifier {
|
|
120
|
+
/** Cleaned path segments (without the `:N` suffix on the last one) */
|
|
121
|
+
cleanSegments: string[];
|
|
122
|
+
/** The numeric identifier extracted from the last segment, or `null` */
|
|
123
|
+
identifier: number | null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Extracts the identifier from a complete expression (array of segments).
|
|
127
|
+
*
|
|
128
|
+
* The identifier is always on the **last** segment of the path, because
|
|
129
|
+
* Handlebars splits on `.` before the `:`.
|
|
130
|
+
*
|
|
131
|
+
* @param segments - The raw path segments (e.g. `["user", "name:1"]`)
|
|
132
|
+
* @returns An object `{ cleanSegments, identifier }`
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```
|
|
136
|
+
* extractExpressionIdentifier(["meetingId:1"])
|
|
137
|
+
* // → { cleanSegments: ["meetingId"], identifier: 1 }
|
|
138
|
+
*
|
|
139
|
+
* extractExpressionIdentifier(["user", "name:1"])
|
|
140
|
+
* // → { cleanSegments: ["user", "name"], identifier: 1 }
|
|
141
|
+
*
|
|
142
|
+
* extractExpressionIdentifier(["meetingId"])
|
|
143
|
+
* // → { cleanSegments: ["meetingId"], identifier: null }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export declare function extractExpressionIdentifier(segments: string[]): ExpressionIdentifier;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import Handlebars from"handlebars";import{TemplateParseError}from"./errors.js";const IDENTIFIER_RE=/^(.+):(\d+)$/;const NUMERIC_LITERAL_RE=/^-?\d+(\.\d+)?$/;export function parse(template){try{return Handlebars.parse(template)}catch(error){const message=error instanceof Error?error.message:String(error);const locMatch=message.match(/line\s+(\d+).*?column\s+(\d+)/i);const loc=locMatch?{line:parseInt(locMatch[1]??"0",10),column:parseInt(locMatch[2]??"0",10)}:undefined;throw new TemplateParseError(message,loc)}}export function isSingleExpression(ast){const{body}=ast;return body.length===1&&body[0]?.type==="MustacheStatement"}export function extractPathSegments(expr){if(expr.type==="PathExpression"){return expr.parts}return[]}export function isThisExpression(expr){if(expr.type!=="PathExpression")return false;const path=expr;return path.original==="this"||path.original==="."}export function getEffectiveBody(program){return program.body.filter(s=>!(s.type==="ContentStatement"&&s.value.trim()===""))}export function getEffectivelySingleBlock(program){const effective=getEffectiveBody(program);if(effective.length===1&&effective[0]?.type==="BlockStatement"){return effective[0]}return null}export function getEffectivelySingleExpression(program){const effective=getEffectiveBody(program);if(effective.length===1&&effective[0]?.type==="MustacheStatement"){return effective[0]}return null}export function canUseFastPath(ast){return ast.body.every(s=>s.type==="ContentStatement"||s.type==="MustacheStatement"&&s.params.length===0&&!s.hash)}export function detectLiteralType(text){if(NUMERIC_LITERAL_RE.test(text))return"number";if(text==="true"||text==="false")return"boolean";if(text==="null")return"null";return null}export function coerceLiteral(raw){const trimmed=raw.trim();const type=detectLiteralType(trimmed);if(type==="number")return Number(trimmed);if(type==="boolean")return trimmed==="true";if(type==="null")return null;return raw}export function parseIdentifier(segment){const match=segment.match(IDENTIFIER_RE);if(match){return{key:match[1]??segment,identifier:parseInt(match[2]??"0",10)}}return{key:segment,identifier:null}}export function extractExpressionIdentifier(segments){if(segments.length===0){return{cleanSegments:[],identifier:null}}const lastSegment=segments[segments.length-1];const parsed=parseIdentifier(lastSegment);if(parsed.identifier!==null){const cleanSegments=[...segments.slice(0,-1),parsed.key];return{cleanSegments,identifier:parsed.identifier}}return{cleanSegments:segments,identifier:null}}
|
|
2
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/parser.ts"],"sourcesContent":["import Handlebars from \"handlebars\";\nimport { TemplateParseError } from \"./errors.ts\";\n\n// ─── Regex for detecting a template identifier (e.g. \"meetingId:1\") ──────────\n// The identifier is always a positive integer or zero, separated from the\n// variable name by a `:`. The `:` and number are on the **last** segment\n// of the path (Handlebars splits on `.`).\nconst IDENTIFIER_RE = /^(.+):(\\d+)$/;\n\n// ─── Template Parser ─────────────────────────────────────────────────────────\n// Thin wrapper around the Handlebars parser. Centralizing the parser call\n// here allows us to:\n// 1. Wrap errors into our own hierarchy (`TemplateParseError`)\n// 2. Expose AST introspection helpers (e.g. `isSingleExpression`)\n// 3. Isolate the direct Handlebars dependency from the rest of the codebase\n//\n// AST caching is handled at the `Typebars` instance level (via its own\n// configurable LRU cache), not here. This module only parses and wraps errors.\n\n// ─── Regex for detecting a numeric literal (integer or decimal, signed) ──────\n// Intentionally conservative: no scientific notation (1e5), no hex (0xFF),\n// no separators (1_000). We only want to recognize what a human would write\n// as a numeric value in a template.\nconst NUMERIC_LITERAL_RE = /^-?\\d+(\\.\\d+)?$/;\n\n/**\n * Parses a template string and returns the Handlebars AST.\n *\n * This function does not cache results — caching is managed at the\n * `Typebars` instance level via its own configurable LRU cache.\n *\n * @param template - The template string to parse (e.g. `\"Hello {{name}}\"`)\n * @returns The root AST node (`hbs.AST.Program`)\n * @throws {TemplateParseError} if the template syntax is invalid\n */\nexport function parse(template: string): hbs.AST.Program {\n\ttry {\n\t\treturn Handlebars.parse(template);\n\t} catch (error: unknown) {\n\t\t// Handlebars throws a plain Error with a descriptive message.\n\t\t// We transform it into a TemplateParseError for uniform handling.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\n\t\t// Handlebars sometimes includes the position in the message —\n\t\t// attempt to extract it to enrich our error.\n\t\tconst locMatch = message.match(/line\\s+(\\d+).*?column\\s+(\\d+)/i);\n\t\tconst loc = locMatch\n\t\t\t? {\n\t\t\t\t\tline: parseInt(locMatch[1] ?? \"0\", 10),\n\t\t\t\t\tcolumn: parseInt(locMatch[2] ?? \"0\", 10),\n\t\t\t\t}\n\t\t\t: undefined;\n\n\t\tthrow new TemplateParseError(message, loc);\n\t}\n}\n\n/**\n * Determines whether the AST represents a template consisting of a single\n * expression `{{expression}}` with no text content around it.\n *\n * This matters for return type inference:\n * - Template `{{value}}` → returns the raw type of `value` (number, object…)\n * - Template `Hello {{name}}` → always returns `string` (concatenation)\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template is a single expression\n */\nexport function isSingleExpression(ast: hbs.AST.Program): boolean {\n\tconst { body } = ast;\n\n\t// Exactly one node, and it's a MustacheStatement (not a block, not text)\n\treturn body.length === 1 && body[0]?.type === \"MustacheStatement\";\n}\n\n/**\n * Extracts the path segments from a Handlebars `PathExpression`.\n *\n * Handlebars decomposes `user.address.city` into `{ parts: [\"user\", \"address\", \"city\"] }`.\n * This function safely extracts those segments.\n *\n * @param expr - The expression to extract the path from\n * @returns The path segments, or an empty array if the expression is not\n * a `PathExpression`\n */\nexport function extractPathSegments(expr: hbs.AST.Expression): string[] {\n\tif (expr.type === \"PathExpression\") {\n\t\treturn (expr as hbs.AST.PathExpression).parts;\n\t}\n\treturn [];\n}\n\n/**\n * Checks whether an AST expression is a `PathExpression` pointing to `this`\n * (used inside `{{#each}}` blocks).\n */\nexport function isThisExpression(expr: hbs.AST.Expression): boolean {\n\tif (expr.type !== \"PathExpression\") return false;\n\tconst path = expr as hbs.AST.PathExpression;\n\treturn path.original === \"this\" || path.original === \".\";\n}\n\n// ─── Filtering Semantically Significant Nodes ───────────────────────────────\n// In a Handlebars AST, formatting (newlines, indentation) produces\n// `ContentStatement` nodes whose value is purely whitespace. These nodes\n// have no semantic impact and must be ignored during type inference to\n// correctly detect \"effectively a single block\" or \"effectively a single\n// expression\" cases.\n\n/**\n * Returns the semantically significant statements of a Program by\n * filtering out `ContentStatement` nodes that contain only whitespace.\n */\nexport function getEffectiveBody(\n\tprogram: hbs.AST.Program,\n): hbs.AST.Statement[] {\n\treturn program.body.filter(\n\t\t(s) =>\n\t\t\t!(\n\t\t\t\ts.type === \"ContentStatement\" &&\n\t\t\t\t(s as hbs.AST.ContentStatement).value.trim() === \"\"\n\t\t\t),\n\t);\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `BlockStatement` (ignoring surrounding whitespace).\n *\n * Recognized examples:\n * ```\n * {{#if x}}...{{/if}}\n *\n * {{#each items}}...{{/each}}\n * ```\n *\n * @returns The single `BlockStatement`, or `null` if the program contains\n * other significant nodes.\n */\nexport function getEffectivelySingleBlock(\n\tprogram: hbs.AST.Program,\n): hbs.AST.BlockStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"BlockStatement\") {\n\t\treturn effective[0] as hbs.AST.BlockStatement;\n\t}\n\treturn null;\n}\n\n/**\n * Determines whether a Program effectively consists of a single\n * `MustacheStatement` (ignoring surrounding whitespace).\n *\n * Example: ` {{age}} ` → true\n */\nexport function getEffectivelySingleExpression(\n\tprogram: hbs.AST.Program,\n): hbs.AST.MustacheStatement | null {\n\tconst effective = getEffectiveBody(program);\n\tif (effective.length === 1 && effective[0]?.type === \"MustacheStatement\") {\n\t\treturn effective[0] as hbs.AST.MustacheStatement;\n\t}\n\treturn null;\n}\n\n// ─── Fast-Path Detection ─────────────────────────────────────────────────────\n// For templates consisting only of text and simple expressions (no blocks,\n// no helpers with parameters), we can bypass Handlebars entirely and perform\n// a simple variable replacement via string concatenation.\n\n/**\n * Determines whether an AST can be executed via the fast-path (direct\n * concatenation without going through `Handlebars.compile()`).\n *\n * The fast-path is possible when the template only contains:\n * - `ContentStatement` nodes (static text)\n * - Simple `MustacheStatement` nodes (no params, no hash)\n *\n * This excludes:\n * - Block helpers (`{{#if}}`, `{{#each}}`, etc.)\n * - Inline helpers (`{{uppercase name}}`)\n * - Sub-expressions\n *\n * @param ast - The parsed AST of the template\n * @returns `true` if the template can use the fast-path\n */\nexport function canUseFastPath(ast: hbs.AST.Program): boolean {\n\treturn ast.body.every(\n\t\t(s) =>\n\t\t\ts.type === \"ContentStatement\" ||\n\t\t\t(s.type === \"MustacheStatement\" &&\n\t\t\t\t(s as hbs.AST.MustacheStatement).params.length === 0 &&\n\t\t\t\t!(s as hbs.AST.MustacheStatement).hash),\n\t);\n}\n\n// ─── Literal Detection in Text Content ───────────────────────────────────────\n// When a program contains only ContentStatements (no expressions), we try\n// to detect whether the concatenated and trimmed text is a typed literal\n// (number, boolean, null). This enables correct type inference for branches\n// like `{{#if x}} 42 {{/if}}`.\n\n/**\n * Attempts to detect the type of a raw text literal.\n *\n * @param text - The trimmed text from a ContentStatement or group of ContentStatements\n * @returns The detected JSON Schema type, or `null` if it's free-form text (string).\n */\nexport function detectLiteralType(\n\ttext: string,\n): \"number\" | \"boolean\" | \"null\" | null {\n\tif (NUMERIC_LITERAL_RE.test(text)) return \"number\";\n\tif (text === \"true\" || text === \"false\") return \"boolean\";\n\tif (text === \"null\") return \"null\";\n\treturn null;\n}\n\n/**\n * Coerces a raw string from Handlebars rendering to its actual type\n * if it represents a literal (number, boolean, null).\n * Returns the raw (untrimmed) string otherwise.\n */\nexport function coerceLiteral(raw: string): unknown {\n\tconst trimmed = raw.trim();\n\tconst type = detectLiteralType(trimmed);\n\tif (type === \"number\") return Number(trimmed);\n\tif (type === \"boolean\") return trimmed === \"true\";\n\tif (type === \"null\") return null;\n\t// Not a typed literal — return the raw string without trimming,\n\t// as whitespace may be significant (e.g. output of an #each block).\n\treturn raw;\n}\n\n// ─── Template Identifier Parsing ─────────────────────────────────────────────\n// Syntax `{{key:N}}` where N is a positive integer or zero.\n// The identifier allows resolving a variable from a specific data source\n// (e.g. a workflow node identified by its number).\n\n/** Result of parsing a path segment with a potential identifier */\nexport interface ParsedIdentifier {\n\t/** The variable name, without the `:N` suffix */\n\tkey: string;\n\t/** The numeric identifier, or `null` if absent */\n\tidentifier: number | null;\n}\n\n/**\n * Parses an individual path segment to extract the key and optional identifier.\n *\n * @param segment - A raw path segment (e.g. `\"meetingId:1\"` or `\"meetingId\"`)\n * @returns An object `{ key, identifier }`\n *\n * @example\n * ```\n * parseIdentifier(\"meetingId:1\") // → { key: \"meetingId\", identifier: 1 }\n * parseIdentifier(\"meetingId\") // → { key: \"meetingId\", identifier: null }\n * parseIdentifier(\"meetingId:0\") // → { key: \"meetingId\", identifier: 0 }\n * ```\n */\nexport function parseIdentifier(segment: string): ParsedIdentifier {\n\tconst match = segment.match(IDENTIFIER_RE);\n\tif (match) {\n\t\treturn {\n\t\t\tkey: match[1] ?? segment,\n\t\t\tidentifier: parseInt(match[2] ?? \"0\", 10),\n\t\t};\n\t}\n\treturn { key: segment, identifier: null };\n}\n\n/** Result of extracting the identifier from a complete expression */\nexport interface ExpressionIdentifier {\n\t/** Cleaned path segments (without the `:N` suffix on the last one) */\n\tcleanSegments: string[];\n\t/** The numeric identifier extracted from the last segment, or `null` */\n\tidentifier: number | null;\n}\n\n/**\n * Extracts the identifier from a complete expression (array of segments).\n *\n * The identifier is always on the **last** segment of the path, because\n * Handlebars splits on `.` before the `:`.\n *\n * @param segments - The raw path segments (e.g. `[\"user\", \"name:1\"]`)\n * @returns An object `{ cleanSegments, identifier }`\n *\n * @example\n * ```\n * extractExpressionIdentifier([\"meetingId:1\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"user\", \"name:1\"])\n * // → { cleanSegments: [\"user\", \"name\"], identifier: 1 }\n *\n * extractExpressionIdentifier([\"meetingId\"])\n * // → { cleanSegments: [\"meetingId\"], identifier: null }\n * ```\n */\nexport function extractExpressionIdentifier(\n\tsegments: string[],\n): ExpressionIdentifier {\n\tif (segments.length === 0) {\n\t\treturn { cleanSegments: [], identifier: null };\n\t}\n\n\tconst lastSegment = segments[segments.length - 1] as string;\n\tconst parsed = parseIdentifier(lastSegment);\n\n\tif (parsed.identifier !== null) {\n\t\tconst cleanSegments = [...segments.slice(0, -1), parsed.key];\n\t\treturn { cleanSegments, identifier: parsed.identifier };\n\t}\n\n\treturn { cleanSegments: segments, identifier: null };\n}\n"],"names":["Handlebars","TemplateParseError","IDENTIFIER_RE","NUMERIC_LITERAL_RE","parse","template","error","message","Error","String","locMatch","match","loc","line","parseInt","column","undefined","isSingleExpression","ast","body","length","type","extractPathSegments","expr","parts","isThisExpression","path","original","getEffectiveBody","program","filter","s","value","trim","getEffectivelySingleBlock","effective","getEffectivelySingleExpression","canUseFastPath","every","params","hash","detectLiteralType","text","test","coerceLiteral","raw","trimmed","Number","parseIdentifier","segment","key","identifier","extractExpressionIdentifier","segments","cleanSegments","lastSegment","parsed","slice"],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { JSONSchema7 } from "json-schema";
|
|
2
|
+
/**
|
|
3
|
+
* Recursively validates that a JSON Schema does not contain `if/then/else`
|
|
4
|
+
* conditional keywords. Throws an `UnsupportedSchemaError` if any are found.
|
|
5
|
+
*
|
|
6
|
+
* This check traverses the entire schema tree, including:
|
|
7
|
+
* - `properties` values
|
|
8
|
+
* - `additionalProperties` (when it's a schema)
|
|
9
|
+
* - `items` (single schema or tuple)
|
|
10
|
+
* - `allOf`, `anyOf`, `oneOf` branches
|
|
11
|
+
* - `not`
|
|
12
|
+
* - `definitions` / `$defs` values
|
|
13
|
+
*
|
|
14
|
+
* A `Set<object>` is used to track visited schemas and prevent infinite loops
|
|
15
|
+
* from circular structures.
|
|
16
|
+
*
|
|
17
|
+
* @param schema - The JSON Schema to validate
|
|
18
|
+
* @param path - The current JSON pointer path (for error reporting)
|
|
19
|
+
* @param visited - Set of already-visited schema objects (cycle protection)
|
|
20
|
+
*
|
|
21
|
+
* @throws {UnsupportedSchemaError} if `if`, `then`, or `else` is found
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```
|
|
25
|
+
* // Throws UnsupportedSchemaError:
|
|
26
|
+
* assertNoConditionalSchema({
|
|
27
|
+
* type: "object",
|
|
28
|
+
* if: { properties: { kind: { const: "a" } } },
|
|
29
|
+
* then: { properties: { a: { type: "string" } } },
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // OK — no conditional keywords:
|
|
33
|
+
* assertNoConditionalSchema({
|
|
34
|
+
* type: "object",
|
|
35
|
+
* properties: { name: { type: "string" } },
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertNoConditionalSchema(schema: JSONSchema7, path?: string, visited?: Set<object>): void;
|
|
40
|
+
/**
|
|
41
|
+
* Recursively resolves `$ref` in a schema using the root schema as the
|
|
42
|
+
* source of definitions.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveRef(schema: JSONSchema7, root: JSONSchema7): JSONSchema7;
|
|
45
|
+
/**
|
|
46
|
+
* Resolves a full path (e.g. ["user", "address", "city"]) within a JSON
|
|
47
|
+
* Schema and returns the corresponding sub-schema.
|
|
48
|
+
*
|
|
49
|
+
* @param schema - The root schema describing the template context
|
|
50
|
+
* @param path - Array of segments (property names)
|
|
51
|
+
* @returns The sub-schema at the end of the path, or `undefined` if the path
|
|
52
|
+
* cannot be resolved.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```
|
|
56
|
+
* const schema = {
|
|
57
|
+
* type: "object",
|
|
58
|
+
* properties: {
|
|
59
|
+
* user: {
|
|
60
|
+
* type: "object",
|
|
61
|
+
* properties: {
|
|
62
|
+
* name: { type: "string" }
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* };
|
|
67
|
+
* resolveSchemaPath(schema, ["user", "name"]);
|
|
68
|
+
* // → { type: "string" }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveSchemaPath(schema: JSONSchema7, path: string[]): JSONSchema7 | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Resolves the item schema of an array.
|
|
74
|
+
* If the schema is not of type `array` or has no `items`, returns `undefined`.
|
|
75
|
+
*
|
|
76
|
+
* @param schema - The array schema
|
|
77
|
+
* @param root - The root schema (for resolving $refs)
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveArrayItems(schema: JSONSchema7, root: JSONSchema7): JSONSchema7 | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Simplifies an output schema to avoid unnecessarily complex constructs
|
|
82
|
+
* (e.g. `oneOf` with a single element, duplicates, etc.).
|
|
83
|
+
*
|
|
84
|
+
* Uses `deepEqual` for deduplication — more robust and performant than
|
|
85
|
+
* `JSON.stringify` (independent of key order, no intermediate string
|
|
86
|
+
* allocations).
|
|
87
|
+
*/
|
|
88
|
+
export declare function simplifySchema(schema: JSONSchema7): JSONSchema7;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{UnsupportedSchemaError}from"./errors.js";import{deepEqual}from"./utils.js";export function assertNoConditionalSchema(schema,path="",visited=new Set){if(visited.has(schema))return;visited.add(schema);if(schema.if!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.then!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.else!==undefined){throw new UnsupportedSchemaError("if/then/else",path||"/")}if(schema.properties){for(const[key,prop]of Object.entries(schema.properties)){if(prop&&typeof prop!=="boolean"){assertNoConditionalSchema(prop,`${path}/properties/${key}`,visited)}}}if(schema.additionalProperties&&typeof schema.additionalProperties==="object"){assertNoConditionalSchema(schema.additionalProperties,`${path}/additionalProperties`,visited)}if(schema.items){if(Array.isArray(schema.items)){for(let i=0;i<schema.items.length;i++){const item=schema.items[i];if(item&&typeof item!=="boolean"){assertNoConditionalSchema(item,`${path}/items/${i}`,visited)}}}else if(typeof schema.items!=="boolean"){assertNoConditionalSchema(schema.items,`${path}/items`,visited)}}for(const keyword of["allOf","anyOf","oneOf"]){const branches=schema[keyword];if(branches){for(let i=0;i<branches.length;i++){const branch=branches[i];if(branch&&typeof branch!=="boolean"){assertNoConditionalSchema(branch,`${path}/${keyword}/${i}`,visited)}}}}if(schema.not&&typeof schema.not!=="boolean"){assertNoConditionalSchema(schema.not,`${path}/not`,visited)}for(const defsKey of["definitions","$defs"]){const defs=schema[defsKey];if(defs){for(const[name,def]of Object.entries(defs)){if(def&&typeof def!=="boolean"){assertNoConditionalSchema(def,`${path}/${defsKey}/${name}`,visited)}}}}}export function resolveRef(schema,root){if(!schema.$ref)return schema;const ref=schema.$ref;const match=ref.match(/^#\/(definitions|\$defs)\/(.+)$/);if(!match){throw new Error(`Unsupported $ref format: "${ref}". Only internal #/definitions/ references are supported.`)}const defsKey=match[1];const name=match[2]??"";const defs=defsKey==="definitions"?root.definitions:root.$defs;if(!defs||!(name in defs)){throw new Error(`Cannot resolve $ref "${ref}": definition "${name}" not found.`)}const def=defs[name];if(!def||typeof def==="boolean"){throw new Error(`Cannot resolve $ref "${ref}": definition "${name}" not found.`)}return resolveRef(def,root)}function resolveSegment(schema,segment,root){const resolved=resolveRef(schema,root);if(resolved.properties&&segment in resolved.properties){const prop=resolved.properties[segment];if(prop&&typeof prop!=="boolean")return resolveRef(prop,root);if(prop===true)return{}}if(resolved.additionalProperties!==undefined&&resolved.additionalProperties!==false){if(resolved.additionalProperties===true){return{}}return resolveRef(resolved.additionalProperties,root)}const schemaType=resolved.type;const isArray=schemaType==="array"||Array.isArray(schemaType)&&schemaType.includes("array");if(isArray&&segment==="length"){return{type:"integer"}}const combinatorResult=resolveInCombinators(resolved,segment,root);if(combinatorResult)return combinatorResult;return undefined}function resolveInCombinators(schema,segment,root){if(schema.allOf){const matches=schema.allOf.filter(b=>typeof b!=="boolean").map(branch=>resolveSegment(branch,segment,root)).filter(s=>s!==undefined);if(matches.length===1)return matches[0];if(matches.length>1)return{allOf:matches}}for(const key of["anyOf","oneOf"]){if(!schema[key])continue;const matches=schema[key].filter(b=>typeof b!=="boolean").map(branch=>resolveSegment(branch,segment,root)).filter(s=>s!==undefined);if(matches.length===1)return matches[0];if(matches.length>1)return{[key]:matches}}return undefined}export function resolveSchemaPath(schema,path){if(path.length===0)return resolveRef(schema,schema);let current=resolveRef(schema,schema);const root=schema;for(const segment of path){const next=resolveSegment(current,segment,root);if(next===undefined)return undefined;current=next}return current}export function resolveArrayItems(schema,root){const resolved=resolveRef(schema,root);const schemaType=resolved.type;const isArray=schemaType==="array"||Array.isArray(schemaType)&&schemaType.includes("array");if(!isArray&&resolved.items===undefined){return undefined}if(resolved.items===undefined){return{}}if(typeof resolved.items==="boolean"){return{}}if(Array.isArray(resolved.items)){const schemas=resolved.items.filter(item=>typeof item!=="boolean").map(item=>resolveRef(item,root));if(schemas.length===0)return{};return{oneOf:schemas}}return resolveRef(resolved.items,root)}export function simplifySchema(schema){for(const key of["oneOf","anyOf"]){const arr=schema[key];if(arr&&arr.length===1){const first=arr[0];if(first!==undefined&&typeof first!=="boolean")return simplifySchema(first)}}if(schema.allOf&&schema.allOf.length===1){const first=schema.allOf[0];if(first!==undefined&&typeof first!=="boolean")return simplifySchema(first)}for(const key of["oneOf","anyOf"]){const arr=schema[key];if(arr&&arr.length>1){const unique=[];for(const entry of arr){if(typeof entry==="boolean")continue;const isDuplicate=unique.some(existing=>deepEqual(existing,entry));if(!isDuplicate){unique.push(simplifySchema(entry))}}if(unique.length===1)return unique[0];return{...schema,[key]:unique}}}return schema}
|
|
2
|
+
//# sourceMappingURL=schema-resolver.js.map
|