@revisium/formula 0.9.0 → 0.10.0
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/editor/index.cjs +1 -1
- package/dist/editor/index.mjs +1 -1
- package/dist/formula-spec.cjs +27 -4
- package/dist/formula-spec.cjs.map +1 -1
- package/dist/formula-spec.d.ts.map +1 -1
- package/dist/formula-spec.mjs +27 -4
- package/dist/formula-spec.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{validate-syntax-CmqnFrsc.mjs → validate-syntax-Dttxs0ku.mjs} +8 -3
- package/dist/validate-syntax-Dttxs0ku.mjs.map +1 -0
- package/dist/{validate-syntax-BGrLewnG.cjs → validate-syntax-q3bM-DaM.cjs} +8 -3
- package/dist/validate-syntax-q3bM-DaM.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/validate-syntax-BGrLewnG.cjs.map +0 -1
- package/dist/validate-syntax-CmqnFrsc.mjs.map +0 -1
package/dist/editor/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_validate_syntax = require('../validate-syntax-
|
|
1
|
+
const require_validate_syntax = require('../validate-syntax-q3bM-DaM.cjs');
|
|
2
2
|
|
|
3
3
|
exports.parseExpression = require_validate_syntax.parseExpression;
|
|
4
4
|
exports.parseFormula = require_validate_syntax.parseFormula;
|
package/dist/editor/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { c as parseFormula, n as parseExpression, t as validateFormulaSyntax } from "../validate-syntax-
|
|
1
|
+
import { c as parseFormula, n as parseExpression, t as validateFormulaSyntax } from "../validate-syntax-Dttxs0ku.mjs";
|
|
2
2
|
|
|
3
3
|
export { parseExpression, parseFormula, validateFormulaSyntax };
|
package/dist/formula-spec.cjs
CHANGED
|
@@ -295,17 +295,21 @@ const formulaSpec = {
|
|
|
295
295
|
array: [
|
|
296
296
|
{
|
|
297
297
|
name: "sum",
|
|
298
|
-
description: "Calculate the sum of array elements",
|
|
298
|
+
description: "Calculate the sum of array elements. Supports wildcard property access to sum nested values.",
|
|
299
299
|
signature: "sum(array)",
|
|
300
300
|
returnType: "number",
|
|
301
|
-
examples: [
|
|
301
|
+
examples: [
|
|
302
|
+
"sum(prices) // total of all prices",
|
|
303
|
+
"sum(items[*].price) // sum prices from array of objects",
|
|
304
|
+
"sum(orders[*].items[*].amount) // sum nested arrays"
|
|
305
|
+
]
|
|
302
306
|
},
|
|
303
307
|
{
|
|
304
308
|
name: "avg",
|
|
305
|
-
description: "Calculate the average of array elements",
|
|
309
|
+
description: "Calculate the average of array elements. Supports wildcard property access.",
|
|
306
310
|
signature: "avg(array)",
|
|
307
311
|
returnType: "number",
|
|
308
|
-
examples: ["avg(scores) // average score"]
|
|
312
|
+
examples: ["avg(scores) // average score", "avg(items[*].rating) // average rating from array of objects"]
|
|
309
313
|
},
|
|
310
314
|
{
|
|
311
315
|
name: "count",
|
|
@@ -425,6 +429,21 @@ const formulaSpec = {
|
|
|
425
429
|
],
|
|
426
430
|
dependenciesExtracted: ["[\"items[0].price\"]", "[\"items[-1].name\"]"]
|
|
427
431
|
},
|
|
432
|
+
{
|
|
433
|
+
name: "array_wildcard_property",
|
|
434
|
+
description: "Access properties across all array elements using [*] wildcard. Property access after wildcard maps over all elements. Multiple wildcards flatten nested arrays.",
|
|
435
|
+
minVersion: "1.1",
|
|
436
|
+
examples: [
|
|
437
|
+
"items[*].price // [10, 20, 30] - map property",
|
|
438
|
+
"sum(items[*].price) // 60 - sum mapped values",
|
|
439
|
+
"avg(items[*].rating) // average of all ratings",
|
|
440
|
+
"values[*].nested.value // deeply nested property access",
|
|
441
|
+
"orders[*].items // [[1,2], [3,4]] - array of arrays",
|
|
442
|
+
"orders[*].items[*] // [1,2,3,4] - flattened",
|
|
443
|
+
"sum(orders[*].items[*].amount) // sum all nested amounts"
|
|
444
|
+
],
|
|
445
|
+
dependenciesExtracted: ["[\"items[*].price\"]", "[\"orders[*].items[*].amount\"]"]
|
|
446
|
+
},
|
|
428
447
|
{
|
|
429
448
|
name: "root_path",
|
|
430
449
|
description: "Absolute path reference starting with /. Always resolves from root data, even inside array item formulas",
|
|
@@ -517,6 +536,10 @@ const formulaSpec = {
|
|
|
517
536
|
feature: "Array index ([0], [-1])",
|
|
518
537
|
minVersion: "1.1"
|
|
519
538
|
},
|
|
539
|
+
{
|
|
540
|
+
feature: "Array wildcard property (items[*].price)",
|
|
541
|
+
minVersion: "1.1"
|
|
542
|
+
},
|
|
520
543
|
{
|
|
521
544
|
feature: "Absolute paths (/field)",
|
|
522
545
|
minVersion: "1.1"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formula-spec.cjs","names":[],"sources":["../src/formula-spec.ts"],"sourcesContent":["export interface FunctionSpec {\n name: string;\n description: string;\n signature: string;\n returnType: 'string' | 'number' | 'boolean' | 'any';\n examples: string[];\n}\n\nexport interface FormulaSpec {\n version: string;\n description: string;\n syntax: {\n fieldReferences: string[];\n arithmeticOperators: { operator: string; description: string }[];\n comparisonOperators: { operator: string; description: string }[];\n logicalOperators: { operator: string; description: string }[];\n other: string[];\n };\n functions: {\n string: FunctionSpec[];\n numeric: FunctionSpec[];\n boolean: FunctionSpec[];\n array: FunctionSpec[];\n conversion: FunctionSpec[];\n conditional: FunctionSpec[];\n };\n features: {\n name: string;\n description: string;\n minVersion: string;\n examples: string[];\n dependenciesExtracted?: string[];\n }[];\n versionDetection: { feature: string; minVersion: string }[];\n parseResult: {\n description: string;\n interface: string;\n };\n astUtilities: {\n name: string;\n description: string;\n signature: string;\n code: string;\n }[];\n examples: {\n expression: string;\n description: string;\n result?: string;\n }[];\n apiExamples: {\n name: string;\n description: string;\n code: string;\n }[];\n schemaUsage: {\n structure: string;\n fieldTypes: string[];\n rules: string[];\n };\n}\n\nexport const formulaSpec: FormulaSpec = {\n version: '1.2',\n description:\n 'Formula expressions for computed fields. Formulas reference other fields and calculate values automatically.',\n\n syntax: {\n fieldReferences: [\n 'Simple field: fieldName (e.g., price, quantity)',\n 'Nested path: object.property (e.g., stats.damage)',\n 'Array index: array[0] or array[-1] for last element',\n 'Bracket notation: [\"field-name\"] for field names containing hyphens',\n ' - Required when field name contains hyphen (-)',\n ' - Without brackets: field-name is parsed as \"field minus name\"',\n ' - With brackets: [\"field-name\"] references the field correctly',\n 'Combined: items[0].price, user.addresses[-1].city, obj[\"field-name\"].value',\n ],\n arithmeticOperators: [\n { operator: '+', description: 'Addition or string concatenation' },\n { operator: '-', description: 'Subtraction' },\n { operator: '*', description: 'Multiplication' },\n { operator: '/', description: 'Division' },\n { operator: '%', description: 'Modulo (remainder)' },\n ],\n comparisonOperators: [\n { operator: '==', description: 'Equal' },\n { operator: '!=', description: 'Not equal' },\n { operator: '>', description: 'Greater than' },\n { operator: '<', description: 'Less than' },\n { operator: '>=', description: 'Greater or equal' },\n { operator: '<=', description: 'Less or equal' },\n ],\n logicalOperators: [\n { operator: '&&', description: 'Logical AND' },\n { operator: '||', description: 'Logical OR' },\n { operator: '!', description: 'Logical NOT' },\n ],\n other: ['Parentheses: (a + b) * c', 'Unary minus: -value, a + -b'],\n },\n\n functions: {\n string: [\n {\n name: 'concat',\n description: 'Concatenate multiple values into a single string',\n signature: 'concat(value1, value2, ...)',\n returnType: 'string',\n examples: [\n 'concat(firstName, \" \", lastName) // \"John Doe\"',\n 'concat(\"Price: \", price, \" USD\") // \"Price: 100 USD\"',\n ],\n },\n {\n name: 'upper',\n description: 'Convert string to uppercase',\n signature: 'upper(text)',\n returnType: 'string',\n examples: ['upper(name) // \"HELLO\"'],\n },\n {\n name: 'lower',\n description: 'Convert string to lowercase',\n signature: 'lower(text)',\n returnType: 'string',\n examples: ['lower(name) // \"hello\"'],\n },\n {\n name: 'trim',\n description: 'Remove whitespace from both ends of a string',\n signature: 'trim(text)',\n returnType: 'string',\n examples: ['trim(name) // \"hello\" from \" hello \"'],\n },\n {\n name: 'left',\n description: 'Extract characters from the beginning of a string',\n signature: 'left(text, count)',\n returnType: 'string',\n examples: ['left(name, 3) // \"hel\" from \"hello\"'],\n },\n {\n name: 'right',\n description: 'Extract characters from the end of a string',\n signature: 'right(text, count)',\n returnType: 'string',\n examples: ['right(name, 3) // \"llo\" from \"hello\"'],\n },\n {\n name: 'replace',\n description: 'Replace first occurrence of a substring',\n signature: 'replace(text, search, replacement)',\n returnType: 'string',\n examples: ['replace(name, \"o\", \"0\") // \"hell0\" from \"hello\"'],\n },\n {\n name: 'join',\n description: 'Join array elements into a string',\n signature: 'join(array, separator?)',\n returnType: 'string',\n examples: ['join(tags) // \"a,b,c\"', 'join(tags, \" | \") // \"a | b | c\"'],\n },\n ],\n numeric: [\n {\n name: 'round',\n description: 'Round a number to specified decimal places',\n signature: 'round(number, decimals?)',\n returnType: 'number',\n examples: ['round(3.14159, 2) // 3.14', 'round(3.5) // 4'],\n },\n {\n name: 'floor',\n description: 'Round down to the nearest integer',\n signature: 'floor(number)',\n returnType: 'number',\n examples: ['floor(3.7) // 3'],\n },\n {\n name: 'ceil',\n description: 'Round up to the nearest integer',\n signature: 'ceil(number)',\n returnType: 'number',\n examples: ['ceil(3.2) // 4'],\n },\n {\n name: 'abs',\n description: 'Get the absolute value',\n signature: 'abs(number)',\n returnType: 'number',\n examples: ['abs(-5) // 5'],\n },\n {\n name: 'sqrt',\n description: 'Calculate the square root',\n signature: 'sqrt(number)',\n returnType: 'number',\n examples: ['sqrt(16) // 4'],\n },\n {\n name: 'pow',\n description: 'Raise a number to a power',\n signature: 'pow(base, exponent)',\n returnType: 'number',\n examples: ['pow(2, 3) // 8'],\n },\n {\n name: 'min',\n description: 'Get the minimum of multiple values',\n signature: 'min(value1, value2, ...)',\n returnType: 'number',\n examples: ['min(a, b, c) // smallest value'],\n },\n {\n name: 'max',\n description: 'Get the maximum of multiple values',\n signature: 'max(value1, value2, ...)',\n returnType: 'number',\n examples: ['max(a, b, c) // largest value'],\n },\n {\n name: 'log',\n description: 'Calculate the natural logarithm',\n signature: 'log(number)',\n returnType: 'number',\n examples: ['log(10) // 2.302...'],\n },\n {\n name: 'log10',\n description: 'Calculate the base-10 logarithm',\n signature: 'log10(number)',\n returnType: 'number',\n examples: ['log10(100) // 2'],\n },\n {\n name: 'exp',\n description: 'Calculate e raised to a power',\n signature: 'exp(number)',\n returnType: 'number',\n examples: ['exp(1) // 2.718...'],\n },\n {\n name: 'sign',\n description: 'Get the sign of a number (-1, 0, or 1)',\n signature: 'sign(number)',\n returnType: 'number',\n examples: ['sign(-5) // -1', 'sign(0) // 0', 'sign(5) // 1'],\n },\n {\n name: 'length',\n description: 'Get the length of a string or array',\n signature: 'length(value)',\n returnType: 'number',\n examples: ['length(name) // 5 from \"hello\"', 'length(items) // 3'],\n },\n ],\n boolean: [\n {\n name: 'and',\n description: 'Logical AND of two values',\n signature: 'and(a, b)',\n returnType: 'boolean',\n examples: ['and(isActive, hasPermission) // true if both true'],\n },\n {\n name: 'or',\n description: 'Logical OR of two values',\n signature: 'or(a, b)',\n returnType: 'boolean',\n examples: ['or(isAdmin, isOwner) // true if either true'],\n },\n {\n name: 'not',\n description: 'Logical NOT of a value',\n signature: 'not(value)',\n returnType: 'boolean',\n examples: ['not(isDeleted) // true if false'],\n },\n {\n name: 'contains',\n description: 'Check if a string contains a substring',\n signature: 'contains(text, search)',\n returnType: 'boolean',\n examples: ['contains(name, \"ell\") // true for \"hello\"'],\n },\n {\n name: 'startswith',\n description: 'Check if a string starts with a prefix',\n signature: 'startswith(text, prefix)',\n returnType: 'boolean',\n examples: ['startswith(name, \"hel\") // true for \"hello\"'],\n },\n {\n name: 'endswith',\n description: 'Check if a string ends with a suffix',\n signature: 'endswith(text, suffix)',\n returnType: 'boolean',\n examples: ['endswith(name, \"llo\") // true for \"hello\"'],\n },\n {\n name: 'isnull',\n description: 'Check if a value is null or undefined',\n signature: 'isnull(value)',\n returnType: 'boolean',\n examples: ['isnull(optionalField) // true if null/undefined'],\n },\n {\n name: 'includes',\n description: 'Check if an array contains a value',\n signature: 'includes(array, value)',\n returnType: 'boolean',\n examples: [\n 'includes(tags, \"featured\") // true if array contains value',\n ],\n },\n ],\n array: [\n {\n name: 'sum',\n description: 'Calculate the sum of array elements',\n signature: 'sum(array)',\n returnType: 'number',\n examples: ['sum(prices) // total of all prices'],\n },\n {\n name: 'avg',\n description: 'Calculate the average of array elements',\n signature: 'avg(array)',\n returnType: 'number',\n examples: ['avg(scores) // average score'],\n },\n {\n name: 'count',\n description: 'Get the number of elements in an array',\n signature: 'count(array)',\n returnType: 'number',\n examples: ['count(items) // number of items'],\n },\n {\n name: 'first',\n description: 'Get the first element of an array',\n signature: 'first(array)',\n returnType: 'any',\n examples: ['first(items) // first item'],\n },\n {\n name: 'last',\n description: 'Get the last element of an array',\n signature: 'last(array)',\n returnType: 'any',\n examples: ['last(items) // last item'],\n },\n ],\n conversion: [\n {\n name: 'tostring',\n description: 'Convert a value to string',\n signature: 'tostring(value)',\n returnType: 'string',\n examples: ['tostring(42) // \"42\"'],\n },\n {\n name: 'tonumber',\n description: 'Convert a value to number',\n signature: 'tonumber(value)',\n returnType: 'number',\n examples: ['tonumber(\"42\") // 42'],\n },\n {\n name: 'toboolean',\n description: 'Convert a value to boolean',\n signature: 'toboolean(value)',\n returnType: 'boolean',\n examples: ['toboolean(1) // true', 'toboolean(0) // false'],\n },\n ],\n conditional: [\n {\n name: 'if',\n description: 'Return one of two values based on a condition',\n signature: 'if(condition, valueIfTrue, valueIfFalse)',\n returnType: 'any',\n examples: [\n 'if(stock > 0, \"Available\", \"Out of Stock\")',\n 'if(price > 100, price * 0.9, price)',\n ],\n },\n {\n name: 'coalesce',\n description: 'Return the first non-null value',\n signature: 'coalesce(value1, value2, ...)',\n returnType: 'any',\n examples: ['coalesce(nickname, name, \"Anonymous\")'],\n },\n ],\n },\n\n features: [\n {\n name: 'simple_refs',\n description: 'Reference top-level fields by name',\n minVersion: '1.0',\n examples: ['price', 'quantity', 'baseDamage'],\n dependenciesExtracted: ['[\"price\"]', '[\"quantity\"]', '[\"baseDamage\"]'],\n },\n {\n name: 'arithmetic',\n description: 'Basic math operations (+, -, *, /)',\n minVersion: '1.0',\n examples: ['price * 1.1', 'a + b - c', 'quantity * price'],\n },\n {\n name: 'comparison',\n description: 'Compare values (>, <, >=, <=, ==, !=)',\n minVersion: '1.0',\n examples: ['price > 100', 'x == 10', 'quantity >= 5'],\n },\n {\n name: 'nested_path',\n description: 'Access nested object properties using dot notation',\n minVersion: '1.1',\n examples: ['stats.damage', 'user.profile.name', 'item.metadata.category'],\n dependenciesExtracted: ['[\"stats.damage\"]'],\n },\n {\n name: 'array_index',\n description:\n 'Access array elements by numeric index. Negative indices access from the end',\n minVersion: '1.1',\n examples: [\n 'items[0].price',\n 'inventory[1].quantity',\n 'items[-1].name // last element',\n 'items[-2].price // second to last',\n ],\n dependenciesExtracted: ['[\"items[0].price\"]', '[\"items[-1].name\"]'],\n },\n {\n name: 'root_path',\n description:\n 'Absolute path reference starting with /. Always resolves from root data, even inside array item formulas',\n minVersion: '1.1',\n examples: [\n '/taxRate',\n '/config.tax',\n 'price * (1 + /taxRate)',\n 'price * /config.multiplier',\n ],\n dependenciesExtracted: ['[\"/taxRate\"]', '[\"/config.tax\"]'],\n },\n {\n name: 'relative_path',\n description:\n 'Relative path reference starting with ../. Each ../ goes up one level in the path hierarchy. Works with nested objects, arrays, and combinations. Supports accessing nested properties after the relative prefix (e.g., ../config.value)',\n minVersion: '1.1',\n examples: [\n '../discount',\n '../../rootRate',\n '../config.multiplier',\n 'price * (1 - ../discount)',\n 'price * ../../globalRate',\n 'price * ../settings.tax.rate',\n ],\n dependenciesExtracted: [\n '[\"../discount\"]',\n '[\"../../rootRate\"]',\n '[\"../config.multiplier\"]',\n ],\n },\n {\n name: 'function_named_fields',\n description:\n 'Fields can have the same name as built-in functions (max, min, sum, etc.). Built-in functions are always checked first when a function call is made',\n minVersion: '1.0',\n examples: [\n 'max(max, 0)',\n 'min(min, 100)',\n 'max(max - field.min, 0)',\n 'round(round * 2)',\n ],\n },\n {\n name: 'bracket_notation',\n description:\n 'Access fields containing hyphens using bracket notation with quotes. Required because \"field-name\" would be parsed as \"field minus name\" (subtraction). Works like JavaScript object[\"key\"] syntax.',\n minVersion: '1.1',\n examples: [\n '[\"field-name\"] // Without brackets: field - name (subtraction!)',\n \"['field-name'] // Single quotes also work\",\n '[\"field-one\"][\"field-two\"]',\n 'obj[\"field-name\"].value',\n '[\"items-list\"][0][\"val\"]',\n '[\"price-new\"] * 2',\n ],\n dependenciesExtracted: ['[\"field-name\"]', \"['field-name']\"],\n },\n {\n name: 'context_token',\n description:\n 'Array context tokens provide information about current position and neighboring elements when evaluating formulas inside arrays. # prefix for scalar metadata (number/boolean), @ prefix for object references.',\n minVersion: '1.2',\n examples: [\n '#index // Current array index (0-based)',\n '#length // Array length',\n '#first // true if first element',\n '#last // true if last element',\n '@prev // Previous element (null if first)',\n '@next // Next element (null if last)',\n '#parent.index // Index in parent array (nested arrays)',\n '#parent.length // Length of parent array',\n '#root.index // Index in topmost array',\n '@root.prev // Previous element in topmost array',\n 'if(#first, value, @prev.total + value) // Running total',\n 'concat(#parent.index + 1, \".\", #index + 1) // \"1.1\", \"1.2\" numbering',\n ],\n },\n ],\n\n versionDetection: [\n { feature: 'Simple refs, arithmetic, comparisons', minVersion: '1.0' },\n { feature: 'Function-named fields (max(max, 0))', minVersion: '1.0' },\n { feature: 'Nested paths (a.b)', minVersion: '1.1' },\n { feature: 'Array index ([0], [-1])', minVersion: '1.1' },\n { feature: 'Absolute paths (/field)', minVersion: '1.1' },\n { feature: 'Relative paths (../field)', minVersion: '1.1' },\n { feature: 'Bracket notation ([\"field-name\"])', minVersion: '1.1' },\n { feature: 'Context tokens (#index, @prev)', minVersion: '1.2' },\n ],\n\n parseResult: {\n description:\n 'The parser automatically detects the minimum required version',\n interface: `interface ParseResult {\n ast: ASTNode; // Abstract syntax tree\n dependencies: string[]; // List of field dependencies\n features: string[]; // List of detected features\n minVersion: string; // Minimum required version (\"1.0\" or \"1.1\")\n}`,\n },\n\n astUtilities: [\n {\n name: 'serializeAst',\n description:\n 'Convert an AST back to a formula string. Useful for debugging or displaying parsed formulas.',\n signature: 'serializeAst(ast: ASTNode): string',\n code: `import { parseFormula, serializeAst } from '@revisium/formula';\n\nconst { ast } = parseFormula('price * (1 + taxRate)');\nserializeAst(ast)\n// \"price * (1 + taxRate)\"\n\n// After modifying AST nodes, serialize back to string\nconst { ast: ast2 } = parseFormula('a + b');\nserializeAst(ast2)\n// \"a + b\"`,\n },\n {\n name: 'replaceDependencies',\n description:\n 'Replace field references in an AST with new names. Useful for renaming fields or migrating formulas.',\n signature:\n 'replaceDependencies(ast: ASTNode, replacements: Record<string, string>): ASTNode',\n code: `import { parseFormula, replaceDependencies, serializeAst } from '@revisium/formula';\n\n// Rename a field in a formula\nconst { ast } = parseFormula('oldPrice * quantity');\nconst newAst = replaceDependencies(ast, { oldPrice: 'price' });\nserializeAst(newAst)\n// \"price * quantity\"\n\n// Rename multiple fields\nconst { ast: ast2 } = parseFormula('a + b * c');\nconst newAst2 = replaceDependencies(ast2, { a: 'x', b: 'y', c: 'z' });\nserializeAst(newAst2)\n// \"x + y * z\"\n\n// Works with nested paths\nconst { ast: ast3 } = parseFormula('stats.damage * multiplier');\nconst newAst3 = replaceDependencies(ast3, { 'stats.damage': 'stats.power' });\nserializeAst(newAst3)\n// \"stats.power * multiplier\"`,\n },\n ],\n\n examples: [\n {\n expression: 'price * quantity',\n description: 'Calculate total from price and quantity',\n result: 'number',\n },\n {\n expression: 'firstName + \" \" + lastName',\n description: 'Concatenate strings with space',\n result: 'string',\n },\n {\n expression: 'quantity > 0',\n description: 'Check if in stock',\n result: 'boolean',\n },\n {\n expression: 'if(stock > 0, \"Available\", \"Out of Stock\")',\n description: 'Conditional text based on stock',\n result: 'string',\n },\n {\n expression: 'price * (1 + taxRate)',\n description: 'Price with tax',\n result: 'number',\n },\n {\n expression: 'items[0].price + items[1].price',\n description: 'Sum first two item prices (v1.1)',\n result: 'number',\n },\n ],\n\n apiExamples: [\n {\n name: 'Simple Expression (v1.0)',\n description: 'Parse a basic arithmetic expression',\n code: `parseExpression('price * 1.1')\n// {\n// minVersion: \"1.0\",\n// features: [],\n// dependencies: [\"price\"]\n// }`,\n },\n {\n name: 'Nested Path (v1.1)',\n description: 'Parse expression with nested object access',\n code: `parseExpression('stats.damage * multiplier')\n// {\n// minVersion: \"1.1\",\n// features: [\"nested_path\"],\n// dependencies: [\"stats.damage\", \"multiplier\"]\n// }`,\n },\n {\n name: 'Array Access (v1.1)',\n description: 'Parse expression with array index access',\n code: `parseExpression('items[0].price + items[1].price')\n// {\n// minVersion: \"1.1\",\n// features: [\"array_index\", \"nested_path\"],\n// dependencies: [\"items[0].price\", \"items[1].price\"]\n// }`,\n },\n {\n name: 'Evaluate expressions',\n description: 'Execute formulas with context data',\n code: `evaluate('price * 1.1', { price: 100 })\n// 110\n\nevaluate('stats.damage', { stats: { damage: 50 } })\n// 50\n\nevaluate('items[0].price', { items: [{ price: 10 }] })\n// 10\n\nevaluate('price > 100', { price: 150 })\n// true\n\nevaluate('a + b * c', { a: 1, b: 2, c: 3 })\n// 7`,\n },\n {\n name: 'Function-named fields',\n description: 'Fields can have the same name as built-in functions',\n code: `// Built-in functions take precedence in function calls\nevaluate('max(max, 0)', { max: 10 })\n// 10 (max() function, then max field)\n\nevaluate('max(max - field.min, 0)', { max: 100, field: { min: 20 } })\n// 80\n\nevaluate('round(round * 2)', { round: 3.7 })\n// 7\n\n// Field named \"sum\" doesn't conflict with sum() function\nevaluate('sum(values) + sum', { values: [1, 2, 3], sum: 10 })\n// 16`,\n },\n {\n name: 'Evaluate with context (array items)',\n description:\n 'Use evaluateWithContext() for array item formulas with path resolution',\n code: `// Absolute path: /field always resolves from root\nevaluateWithContext('price * (1 + /taxRate)', {\n rootData: { taxRate: 0.1, items: [{ price: 100 }] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// 110\n\n// Nested absolute path\nevaluateWithContext('price * /config.multiplier', {\n rootData: { config: { multiplier: 1.5 }, items: [] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// 150\n\n// Relative path: ../field resolves from parent (root)\nevaluateWithContext('price * (1 - ../discount)', {\n rootData: { discount: 0.2, items: [] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// 80\n\n// itemData takes precedence over rootData for same field\nevaluateWithContext('value + 10', {\n rootData: { value: 100 },\n itemData: { value: 50 },\n currentPath: 'items[0]'\n})\n// 60`,\n },\n {\n name: 'Relative paths - path resolution',\n description:\n 'Understanding how ../ resolves based on currentPath. Each ../ goes up one segment (object property or array element counts as one segment)',\n code: `// Path structure explanation:\n// currentPath splits by \".\" (dots), keeping array indices attached to field names\n// \"items[0]\" = 1 segment\n// \"items[0].inner\" = 2 segments: [\"items[0]\", \"inner\"]\n// \"container.items[0]\" = 2 segments: [\"container\", \"items[0]\"]\n\n// Single ../ from array item -> goes to root\n// currentPath: \"items[0]\" (1 segment)\n// ../ goes up 1 level -> root\nevaluateWithContext('price * ../discount', {\n rootData: { discount: 0.2, items: [{ price: 100 }] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// Resolves ../discount to root.discount = 0.2\n// Result: 100 * 0.2 = 20\n\n// Single ../ from nested object in array -> goes to array item\n// currentPath: \"items[0].inner\" (2 segments)\n// ../ goes up 1 level -> \"items[0]\"\nevaluateWithContext('price * ../itemMultiplier', {\n rootData: { items: [{ itemMultiplier: 3, inner: { price: 10 } }] },\n itemData: { price: 10 },\n currentPath: 'items[0].inner'\n})\n// Resolves ../itemMultiplier to items[0].itemMultiplier = 3\n// Result: 10 * 3 = 30\n\n// Double ../../ from nested object in array -> goes to root\n// currentPath: \"items[0].inner\" (2 segments)\n// ../../ goes up 2 levels -> root\nevaluateWithContext('price * ../../rootRate', {\n rootData: { rootRate: 2, items: [{ inner: { price: 5 } }] },\n itemData: { price: 5 },\n currentPath: 'items[0].inner'\n})\n// Resolves ../../rootRate to root.rootRate = 2\n// Result: 5 * 2 = 10`,\n },\n {\n name: 'Relative paths - nested arrays',\n description:\n 'How relative paths work with arrays inside objects and nested arrays',\n code: `// Array inside nested object\n// currentPath: \"container.items[0]\" (2 segments: [\"container\", \"items[0]\"])\n// ../ goes up 1 level -> \"container\"\nevaluateWithContext('price * ../containerRate', {\n rootData: {\n container: {\n containerRate: 4,\n items: [{ price: 5 }]\n }\n },\n itemData: { price: 5 },\n currentPath: 'container.items[0]'\n})\n// Resolves ../containerRate to container.containerRate = 4\n// Result: 5 * 4 = 20\n\n// ../../ from array inside object -> goes to root\n// currentPath: \"container.items[0]\" (2 segments)\n// ../../ goes up 2 levels -> root\nevaluateWithContext('price * ../../rootVal', {\n rootData: {\n rootVal: 6,\n container: { items: [{ price: 5 }] }\n },\n itemData: { price: 5 },\n currentPath: 'container.items[0]'\n})\n// Resolves ../../rootVal to root.rootVal = 6\n// Result: 5 * 6 = 30\n\n// Nested arrays: items[].subItems[]\n// currentPath: \"items[0].subItems[0]\" (2 segments: [\"items[0]\", \"subItems[0]\"])\n// ../ goes up 1 level -> \"items[0]\"\nevaluateWithContext('qty * ../itemPrice', {\n rootData: {\n items: [{ itemPrice: 10, subItems: [{ qty: 3 }] }]\n },\n itemData: { qty: 3 },\n currentPath: 'items[0].subItems[0]'\n})\n// Resolves ../itemPrice to items[0].itemPrice = 10\n// Result: 3 * 10 = 30`,\n },\n {\n name: 'Relative paths - accessing nested properties',\n description:\n 'Relative paths can include nested property access after the ../ prefix',\n code: `// ../sibling.nested accesses a sibling with nested property\n// currentPath: \"items[0].products[0]\" (2 segments)\n// ../ goes to \"items[0]\", then accesses .config.discount\nevaluateWithContext('price * ../config.discount', {\n rootData: {\n items: [{\n config: { discount: 0.9 },\n products: [{ price: 100 }]\n }]\n },\n itemData: { price: 100 },\n currentPath: 'items[0].products[0]'\n})\n// Resolves ../config.discount to items[0].config.discount = 0.9\n// Result: 100 * 0.9 = 90\n\n// Deep nested: ../../settings.tax.rate\nevaluateWithContext('amount * ../../settings.tax.rate', {\n rootData: {\n settings: { tax: { rate: 0.1 } },\n orders: [{ items: [{ amount: 200 }] }]\n },\n itemData: { amount: 200 },\n currentPath: 'orders[0].items[0]'\n})\n// Resolves ../../settings.tax.rate to root.settings.tax.rate = 0.1\n// Result: 200 * 0.1 = 20`,\n },\n {\n name: 'Relative paths - complex nesting',\n description: 'Complex scenarios with arrays inside objects inside arrays',\n code: `// Array inside object inside array\n// Structure: items[].container.subItems[]\n// currentPath: \"items[0].container.subItems[0]\" (3 segments)\nevaluateWithContext('val * ../containerMultiplier', {\n rootData: {\n items: [{\n container: {\n containerMultiplier: 4,\n subItems: [{ val: 3 }]\n }\n }]\n },\n itemData: { val: 3 },\n currentPath: 'items[0].container.subItems[0]'\n})\n// ../ goes to \"items[0].container\"\n// Resolves ../containerMultiplier to items[0].container.containerMultiplier = 4\n// Result: 3 * 4 = 12\n\n// ../../ from same structure -> goes to array item\nevaluateWithContext('val * ../../itemRate', {\n rootData: {\n items: [{\n itemRate: 5,\n container: { subItems: [{ val: 2 }] }\n }]\n },\n itemData: { val: 2 },\n currentPath: 'items[0].container.subItems[0]'\n})\n// ../../ goes to \"items[0]\"\n// Resolves ../../itemRate to items[0].itemRate = 5\n// Result: 2 * 5 = 10\n\n// ../../../ from same structure -> goes to root\nevaluateWithContext('val * ../../../rootFactor', {\n rootData: {\n rootFactor: 3,\n items: [{\n container: { subItems: [{ val: 7 }] }\n }]\n },\n itemData: { val: 7 },\n currentPath: 'items[0].container.subItems[0]'\n})\n// ../../../ goes to root\n// Resolves ../../../rootFactor to root.rootFactor = 3\n// Result: 7 * 3 = 21`,\n },\n {\n name: 'Array context tokens - basic',\n description:\n 'Use #index, #length, #first, #last for position info; @prev, @next for neighbor access',\n code: `// arrayContext provides position info for array item formulas\nconst arrayContext = {\n levels: [{\n index: 2, // current position\n length: 5, // array length\n prev: { value: 20 }, // previous element\n next: { value: 40 }, // next element\n }]\n};\n\nevaluateWithContext('#index', { rootData: {}, arrayContext })\n// 2\n\nevaluateWithContext('#length', { rootData: {}, arrayContext })\n// 5\n\nevaluateWithContext('#first', { rootData: {}, arrayContext })\n// false (index !== 0)\n\nevaluateWithContext('#last', { rootData: {}, arrayContext })\n// false (index !== length - 1)\n\nevaluateWithContext('@prev.value', { rootData: {}, arrayContext })\n// 20\n\nevaluateWithContext('@next.value', { rootData: {}, arrayContext })\n// 40\n\n// At first element, @prev is null\nevaluateWithContext('@prev', {\n rootData: {},\n arrayContext: { levels: [{ index: 0, length: 3, prev: null, next: {} }] }\n})\n// null`,\n },\n {\n name: 'Array context tokens - nested arrays',\n description:\n 'Access parent array context with #parent.*, #root.*, @parent.*, @root.*',\n code: `// For nested arrays like orders[].items[]:\n// levels[0] = innermost (items), levels[1] = parent (orders)\nconst arrayContext = {\n levels: [\n { index: 1, length: 3, prev: {}, next: {} }, // items[1]\n { index: 2, length: 5, prev: {}, next: {} }, // orders[2]\n ]\n};\n\nevaluateWithContext('#index', { rootData: {}, arrayContext })\n// 1 (current item index)\n\nevaluateWithContext('#parent.index', { rootData: {}, arrayContext })\n// 2 (parent order index)\n\nevaluateWithContext('#parent.length', { rootData: {}, arrayContext })\n// 5 (number of orders)\n\n// #root.* is shortcut for topmost array (same as #parent.* for 2 levels)\nevaluateWithContext('#root.index', { rootData: {}, arrayContext })\n// 2\n\n// For 3+ levels, #root always points to outermost array\nconst threeLevel = {\n levels: [\n { index: 0, length: 2, prev: null, next: {} }, // innermost\n { index: 1, length: 3, prev: {}, next: {} }, // middle\n { index: 2, length: 4, prev: {}, next: null }, // outermost (root)\n ]\n};\n\nevaluateWithContext('#parent.parent.index', { rootData: {}, arrayContext: threeLevel })\n// 2 (outermost)\n\nevaluateWithContext('#root.index', { rootData: {}, arrayContext: threeLevel })\n// 2 (same as #parent.parent.index)`,\n },\n {\n name: 'Array context tokens - practical examples',\n description:\n 'Common patterns: running total, numbering, delta calculation',\n code: `// Running total pattern (like Excel)\n// rows[].runningTotal = if(#first, value, @prev.runningTotal + value)\nconst rows = [\n { value: 10 },\n { value: 20 },\n { value: 15 },\n];\n\n// For rows[2]:\nevaluateWithContext('if(#first, value, @prev.value + value)', {\n rootData: {},\n itemData: { value: 15 },\n arrayContext: {\n levels: [{\n index: 2,\n length: 3,\n prev: { value: 20 }, // Note: use non-computed field from prev\n next: null,\n }]\n }\n})\n// 35 (20 + 15)\n\n// Nested numbering like \"1.1\", \"1.2\", \"2.1\"\n// sections[].questions[].number\nevaluateWithContext('concat(#parent.index + 1, \".\", #index + 1)', {\n rootData: {},\n arrayContext: {\n levels: [\n { index: 1, length: 3, prev: {}, next: {} }, // question index\n { index: 0, length: 2, prev: null, next: {} }, // section index\n ]\n }\n})\n// \"1.2\"\n\n// Delta from previous\n// measurements[].delta = if(#first, 0, value - @prev.value)\nevaluateWithContext('if(#first, 0, value - @prev.value)', {\n rootData: {},\n itemData: { value: 105 },\n arrayContext: {\n levels: [{\n index: 1,\n length: 3,\n prev: { value: 100 },\n next: { value: 102 },\n }]\n }\n})\n// 5`,\n },\n ],\n\n schemaUsage: {\n structure:\n '{ \"x-formula\": { \"version\": 1, \"expression\": \"...\" }, \"readOnly\": true }',\n fieldTypes: ['string', 'number', 'boolean'],\n rules: [\n 'Add x-formula to string, number, or boolean field schema',\n 'readOnly: true is REQUIRED for fields with x-formula',\n 'Expression must reference existing fields in the same table',\n 'Circular dependencies are not allowed (a references b, b references a)',\n ],\n },\n};\n"],"mappings":"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U,CAAC,0CAAwC;GACpD,CACF;EACF;CAED,UAAU;EACR;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAS;IAAY;IAAa;GAC7C,uBAAuB;IAAC;IAAa;IAAgB;IAAiB;GACvE;EACD;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAe;IAAa;IAAmB;GAC3D;EACD;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAe;IAAW;IAAgB;GACtD;EACD;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAgB;IAAqB;IAAyB;GACzE,uBAAuB,CAAC,qBAAmB;GAC5C;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACD;GACD,uBAAuB,CAAC,wBAAsB,uBAAqB;GACpE;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACD;GACD,uBAAuB,CAAC,kBAAgB,oBAAkB;GAC3D;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACD;GACD,uBAAuB;IACrB;IACA;IACA;IACD;GACF;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACD;GACF;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACD;GACD,uBAAuB,CAAC,oBAAkB,iBAAiB;GAC5D;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACF;EACF;CAED,kBAAkB;EAChB;GAAE,SAAS;GAAwC,YAAY;GAAO;EACtE;GAAE,SAAS;GAAuC,YAAY;GAAO;EACrE;GAAE,SAAS;GAAsB,YAAY;GAAO;EACpD;GAAE,SAAS;GAA2B,YAAY;GAAO;EACzD;GAAE,SAAS;GAA2B,YAAY;GAAO;EACzD;GAAE,SAAS;GAA6B,YAAY;GAAO;EAC3D;GAAE,SAAS;GAAqC,YAAY;GAAO;EACnE;GAAE,SAAS;GAAkC,YAAY;GAAO;EACjE;CAED,aAAa;EACX,aACE;EACF,WAAW;;;;;;EAMZ;CAED,cAAc,CACZ;EACE,MAAM;EACN,aACE;EACF,WAAW;EACX,MAAM;;;;;;;;;;EAUP,EACD;EACE,MAAM;EACN,aACE;EACF,WACE;EACF,MAAM;;;;;;;;;;;;;;;;;;;EAmBP,CACF;CAED,UAAU;EACR;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACF;CAED,aAAa;EACX;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;GAMP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;GAMP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;GAMP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;;;;;;;;;GAcP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;;;;;;;;GAaP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDP;EACF;CAED,aAAa;EACX,WACE;EACF,YAAY;GAAC;GAAU;GAAU;GAAU;EAC3C,OAAO;GACL;GACA;GACA;GACA;GACD;EACF;CACF"}
|
|
1
|
+
{"version":3,"file":"formula-spec.cjs","names":[],"sources":["../src/formula-spec.ts"],"sourcesContent":["export interface FunctionSpec {\n name: string;\n description: string;\n signature: string;\n returnType: 'string' | 'number' | 'boolean' | 'any';\n examples: string[];\n}\n\nexport interface FormulaSpec {\n version: string;\n description: string;\n syntax: {\n fieldReferences: string[];\n arithmeticOperators: { operator: string; description: string }[];\n comparisonOperators: { operator: string; description: string }[];\n logicalOperators: { operator: string; description: string }[];\n other: string[];\n };\n functions: {\n string: FunctionSpec[];\n numeric: FunctionSpec[];\n boolean: FunctionSpec[];\n array: FunctionSpec[];\n conversion: FunctionSpec[];\n conditional: FunctionSpec[];\n };\n features: {\n name: string;\n description: string;\n minVersion: string;\n examples: string[];\n dependenciesExtracted?: string[];\n }[];\n versionDetection: { feature: string; minVersion: string }[];\n parseResult: {\n description: string;\n interface: string;\n };\n astUtilities: {\n name: string;\n description: string;\n signature: string;\n code: string;\n }[];\n examples: {\n expression: string;\n description: string;\n result?: string;\n }[];\n apiExamples: {\n name: string;\n description: string;\n code: string;\n }[];\n schemaUsage: {\n structure: string;\n fieldTypes: string[];\n rules: string[];\n };\n}\n\nexport const formulaSpec: FormulaSpec = {\n version: '1.2',\n description:\n 'Formula expressions for computed fields. Formulas reference other fields and calculate values automatically.',\n\n syntax: {\n fieldReferences: [\n 'Simple field: fieldName (e.g., price, quantity)',\n 'Nested path: object.property (e.g., stats.damage)',\n 'Array index: array[0] or array[-1] for last element',\n 'Bracket notation: [\"field-name\"] for field names containing hyphens',\n ' - Required when field name contains hyphen (-)',\n ' - Without brackets: field-name is parsed as \"field minus name\"',\n ' - With brackets: [\"field-name\"] references the field correctly',\n 'Combined: items[0].price, user.addresses[-1].city, obj[\"field-name\"].value',\n ],\n arithmeticOperators: [\n { operator: '+', description: 'Addition or string concatenation' },\n { operator: '-', description: 'Subtraction' },\n { operator: '*', description: 'Multiplication' },\n { operator: '/', description: 'Division' },\n { operator: '%', description: 'Modulo (remainder)' },\n ],\n comparisonOperators: [\n { operator: '==', description: 'Equal' },\n { operator: '!=', description: 'Not equal' },\n { operator: '>', description: 'Greater than' },\n { operator: '<', description: 'Less than' },\n { operator: '>=', description: 'Greater or equal' },\n { operator: '<=', description: 'Less or equal' },\n ],\n logicalOperators: [\n { operator: '&&', description: 'Logical AND' },\n { operator: '||', description: 'Logical OR' },\n { operator: '!', description: 'Logical NOT' },\n ],\n other: ['Parentheses: (a + b) * c', 'Unary minus: -value, a + -b'],\n },\n\n functions: {\n string: [\n {\n name: 'concat',\n description: 'Concatenate multiple values into a single string',\n signature: 'concat(value1, value2, ...)',\n returnType: 'string',\n examples: [\n 'concat(firstName, \" \", lastName) // \"John Doe\"',\n 'concat(\"Price: \", price, \" USD\") // \"Price: 100 USD\"',\n ],\n },\n {\n name: 'upper',\n description: 'Convert string to uppercase',\n signature: 'upper(text)',\n returnType: 'string',\n examples: ['upper(name) // \"HELLO\"'],\n },\n {\n name: 'lower',\n description: 'Convert string to lowercase',\n signature: 'lower(text)',\n returnType: 'string',\n examples: ['lower(name) // \"hello\"'],\n },\n {\n name: 'trim',\n description: 'Remove whitespace from both ends of a string',\n signature: 'trim(text)',\n returnType: 'string',\n examples: ['trim(name) // \"hello\" from \" hello \"'],\n },\n {\n name: 'left',\n description: 'Extract characters from the beginning of a string',\n signature: 'left(text, count)',\n returnType: 'string',\n examples: ['left(name, 3) // \"hel\" from \"hello\"'],\n },\n {\n name: 'right',\n description: 'Extract characters from the end of a string',\n signature: 'right(text, count)',\n returnType: 'string',\n examples: ['right(name, 3) // \"llo\" from \"hello\"'],\n },\n {\n name: 'replace',\n description: 'Replace first occurrence of a substring',\n signature: 'replace(text, search, replacement)',\n returnType: 'string',\n examples: ['replace(name, \"o\", \"0\") // \"hell0\" from \"hello\"'],\n },\n {\n name: 'join',\n description: 'Join array elements into a string',\n signature: 'join(array, separator?)',\n returnType: 'string',\n examples: ['join(tags) // \"a,b,c\"', 'join(tags, \" | \") // \"a | b | c\"'],\n },\n ],\n numeric: [\n {\n name: 'round',\n description: 'Round a number to specified decimal places',\n signature: 'round(number, decimals?)',\n returnType: 'number',\n examples: ['round(3.14159, 2) // 3.14', 'round(3.5) // 4'],\n },\n {\n name: 'floor',\n description: 'Round down to the nearest integer',\n signature: 'floor(number)',\n returnType: 'number',\n examples: ['floor(3.7) // 3'],\n },\n {\n name: 'ceil',\n description: 'Round up to the nearest integer',\n signature: 'ceil(number)',\n returnType: 'number',\n examples: ['ceil(3.2) // 4'],\n },\n {\n name: 'abs',\n description: 'Get the absolute value',\n signature: 'abs(number)',\n returnType: 'number',\n examples: ['abs(-5) // 5'],\n },\n {\n name: 'sqrt',\n description: 'Calculate the square root',\n signature: 'sqrt(number)',\n returnType: 'number',\n examples: ['sqrt(16) // 4'],\n },\n {\n name: 'pow',\n description: 'Raise a number to a power',\n signature: 'pow(base, exponent)',\n returnType: 'number',\n examples: ['pow(2, 3) // 8'],\n },\n {\n name: 'min',\n description: 'Get the minimum of multiple values',\n signature: 'min(value1, value2, ...)',\n returnType: 'number',\n examples: ['min(a, b, c) // smallest value'],\n },\n {\n name: 'max',\n description: 'Get the maximum of multiple values',\n signature: 'max(value1, value2, ...)',\n returnType: 'number',\n examples: ['max(a, b, c) // largest value'],\n },\n {\n name: 'log',\n description: 'Calculate the natural logarithm',\n signature: 'log(number)',\n returnType: 'number',\n examples: ['log(10) // 2.302...'],\n },\n {\n name: 'log10',\n description: 'Calculate the base-10 logarithm',\n signature: 'log10(number)',\n returnType: 'number',\n examples: ['log10(100) // 2'],\n },\n {\n name: 'exp',\n description: 'Calculate e raised to a power',\n signature: 'exp(number)',\n returnType: 'number',\n examples: ['exp(1) // 2.718...'],\n },\n {\n name: 'sign',\n description: 'Get the sign of a number (-1, 0, or 1)',\n signature: 'sign(number)',\n returnType: 'number',\n examples: ['sign(-5) // -1', 'sign(0) // 0', 'sign(5) // 1'],\n },\n {\n name: 'length',\n description: 'Get the length of a string or array',\n signature: 'length(value)',\n returnType: 'number',\n examples: ['length(name) // 5 from \"hello\"', 'length(items) // 3'],\n },\n ],\n boolean: [\n {\n name: 'and',\n description: 'Logical AND of two values',\n signature: 'and(a, b)',\n returnType: 'boolean',\n examples: ['and(isActive, hasPermission) // true if both true'],\n },\n {\n name: 'or',\n description: 'Logical OR of two values',\n signature: 'or(a, b)',\n returnType: 'boolean',\n examples: ['or(isAdmin, isOwner) // true if either true'],\n },\n {\n name: 'not',\n description: 'Logical NOT of a value',\n signature: 'not(value)',\n returnType: 'boolean',\n examples: ['not(isDeleted) // true if false'],\n },\n {\n name: 'contains',\n description: 'Check if a string contains a substring',\n signature: 'contains(text, search)',\n returnType: 'boolean',\n examples: ['contains(name, \"ell\") // true for \"hello\"'],\n },\n {\n name: 'startswith',\n description: 'Check if a string starts with a prefix',\n signature: 'startswith(text, prefix)',\n returnType: 'boolean',\n examples: ['startswith(name, \"hel\") // true for \"hello\"'],\n },\n {\n name: 'endswith',\n description: 'Check if a string ends with a suffix',\n signature: 'endswith(text, suffix)',\n returnType: 'boolean',\n examples: ['endswith(name, \"llo\") // true for \"hello\"'],\n },\n {\n name: 'isnull',\n description: 'Check if a value is null or undefined',\n signature: 'isnull(value)',\n returnType: 'boolean',\n examples: ['isnull(optionalField) // true if null/undefined'],\n },\n {\n name: 'includes',\n description: 'Check if an array contains a value',\n signature: 'includes(array, value)',\n returnType: 'boolean',\n examples: [\n 'includes(tags, \"featured\") // true if array contains value',\n ],\n },\n ],\n array: [\n {\n name: 'sum',\n description:\n 'Calculate the sum of array elements. Supports wildcard property access to sum nested values.',\n signature: 'sum(array)',\n returnType: 'number',\n examples: [\n 'sum(prices) // total of all prices',\n 'sum(items[*].price) // sum prices from array of objects',\n 'sum(orders[*].items[*].amount) // sum nested arrays',\n ],\n },\n {\n name: 'avg',\n description:\n 'Calculate the average of array elements. Supports wildcard property access.',\n signature: 'avg(array)',\n returnType: 'number',\n examples: [\n 'avg(scores) // average score',\n 'avg(items[*].rating) // average rating from array of objects',\n ],\n },\n {\n name: 'count',\n description: 'Get the number of elements in an array',\n signature: 'count(array)',\n returnType: 'number',\n examples: ['count(items) // number of items'],\n },\n {\n name: 'first',\n description: 'Get the first element of an array',\n signature: 'first(array)',\n returnType: 'any',\n examples: ['first(items) // first item'],\n },\n {\n name: 'last',\n description: 'Get the last element of an array',\n signature: 'last(array)',\n returnType: 'any',\n examples: ['last(items) // last item'],\n },\n ],\n conversion: [\n {\n name: 'tostring',\n description: 'Convert a value to string',\n signature: 'tostring(value)',\n returnType: 'string',\n examples: ['tostring(42) // \"42\"'],\n },\n {\n name: 'tonumber',\n description: 'Convert a value to number',\n signature: 'tonumber(value)',\n returnType: 'number',\n examples: ['tonumber(\"42\") // 42'],\n },\n {\n name: 'toboolean',\n description: 'Convert a value to boolean',\n signature: 'toboolean(value)',\n returnType: 'boolean',\n examples: ['toboolean(1) // true', 'toboolean(0) // false'],\n },\n ],\n conditional: [\n {\n name: 'if',\n description: 'Return one of two values based on a condition',\n signature: 'if(condition, valueIfTrue, valueIfFalse)',\n returnType: 'any',\n examples: [\n 'if(stock > 0, \"Available\", \"Out of Stock\")',\n 'if(price > 100, price * 0.9, price)',\n ],\n },\n {\n name: 'coalesce',\n description: 'Return the first non-null value',\n signature: 'coalesce(value1, value2, ...)',\n returnType: 'any',\n examples: ['coalesce(nickname, name, \"Anonymous\")'],\n },\n ],\n },\n\n features: [\n {\n name: 'simple_refs',\n description: 'Reference top-level fields by name',\n minVersion: '1.0',\n examples: ['price', 'quantity', 'baseDamage'],\n dependenciesExtracted: ['[\"price\"]', '[\"quantity\"]', '[\"baseDamage\"]'],\n },\n {\n name: 'arithmetic',\n description: 'Basic math operations (+, -, *, /)',\n minVersion: '1.0',\n examples: ['price * 1.1', 'a + b - c', 'quantity * price'],\n },\n {\n name: 'comparison',\n description: 'Compare values (>, <, >=, <=, ==, !=)',\n minVersion: '1.0',\n examples: ['price > 100', 'x == 10', 'quantity >= 5'],\n },\n {\n name: 'nested_path',\n description: 'Access nested object properties using dot notation',\n minVersion: '1.1',\n examples: ['stats.damage', 'user.profile.name', 'item.metadata.category'],\n dependenciesExtracted: ['[\"stats.damage\"]'],\n },\n {\n name: 'array_index',\n description:\n 'Access array elements by numeric index. Negative indices access from the end',\n minVersion: '1.1',\n examples: [\n 'items[0].price',\n 'inventory[1].quantity',\n 'items[-1].name // last element',\n 'items[-2].price // second to last',\n ],\n dependenciesExtracted: ['[\"items[0].price\"]', '[\"items[-1].name\"]'],\n },\n {\n name: 'array_wildcard_property',\n description:\n 'Access properties across all array elements using [*] wildcard. Property access after wildcard maps over all elements. Multiple wildcards flatten nested arrays.',\n minVersion: '1.1',\n examples: [\n 'items[*].price // [10, 20, 30] - map property',\n 'sum(items[*].price) // 60 - sum mapped values',\n 'avg(items[*].rating) // average of all ratings',\n 'values[*].nested.value // deeply nested property access',\n 'orders[*].items // [[1,2], [3,4]] - array of arrays',\n 'orders[*].items[*] // [1,2,3,4] - flattened',\n 'sum(orders[*].items[*].amount) // sum all nested amounts',\n ],\n dependenciesExtracted: [\n '[\"items[*].price\"]',\n '[\"orders[*].items[*].amount\"]',\n ],\n },\n {\n name: 'root_path',\n description:\n 'Absolute path reference starting with /. Always resolves from root data, even inside array item formulas',\n minVersion: '1.1',\n examples: [\n '/taxRate',\n '/config.tax',\n 'price * (1 + /taxRate)',\n 'price * /config.multiplier',\n ],\n dependenciesExtracted: ['[\"/taxRate\"]', '[\"/config.tax\"]'],\n },\n {\n name: 'relative_path',\n description:\n 'Relative path reference starting with ../. Each ../ goes up one level in the path hierarchy. Works with nested objects, arrays, and combinations. Supports accessing nested properties after the relative prefix (e.g., ../config.value)',\n minVersion: '1.1',\n examples: [\n '../discount',\n '../../rootRate',\n '../config.multiplier',\n 'price * (1 - ../discount)',\n 'price * ../../globalRate',\n 'price * ../settings.tax.rate',\n ],\n dependenciesExtracted: [\n '[\"../discount\"]',\n '[\"../../rootRate\"]',\n '[\"../config.multiplier\"]',\n ],\n },\n {\n name: 'function_named_fields',\n description:\n 'Fields can have the same name as built-in functions (max, min, sum, etc.). Built-in functions are always checked first when a function call is made',\n minVersion: '1.0',\n examples: [\n 'max(max, 0)',\n 'min(min, 100)',\n 'max(max - field.min, 0)',\n 'round(round * 2)',\n ],\n },\n {\n name: 'bracket_notation',\n description:\n 'Access fields containing hyphens using bracket notation with quotes. Required because \"field-name\" would be parsed as \"field minus name\" (subtraction). Works like JavaScript object[\"key\"] syntax.',\n minVersion: '1.1',\n examples: [\n '[\"field-name\"] // Without brackets: field - name (subtraction!)',\n \"['field-name'] // Single quotes also work\",\n '[\"field-one\"][\"field-two\"]',\n 'obj[\"field-name\"].value',\n '[\"items-list\"][0][\"val\"]',\n '[\"price-new\"] * 2',\n ],\n dependenciesExtracted: ['[\"field-name\"]', \"['field-name']\"],\n },\n {\n name: 'context_token',\n description:\n 'Array context tokens provide information about current position and neighboring elements when evaluating formulas inside arrays. # prefix for scalar metadata (number/boolean), @ prefix for object references.',\n minVersion: '1.2',\n examples: [\n '#index // Current array index (0-based)',\n '#length // Array length',\n '#first // true if first element',\n '#last // true if last element',\n '@prev // Previous element (null if first)',\n '@next // Next element (null if last)',\n '#parent.index // Index in parent array (nested arrays)',\n '#parent.length // Length of parent array',\n '#root.index // Index in topmost array',\n '@root.prev // Previous element in topmost array',\n 'if(#first, value, @prev.total + value) // Running total',\n 'concat(#parent.index + 1, \".\", #index + 1) // \"1.1\", \"1.2\" numbering',\n ],\n },\n ],\n\n versionDetection: [\n { feature: 'Simple refs, arithmetic, comparisons', minVersion: '1.0' },\n { feature: 'Function-named fields (max(max, 0))', minVersion: '1.0' },\n { feature: 'Nested paths (a.b)', minVersion: '1.1' },\n { feature: 'Array index ([0], [-1])', minVersion: '1.1' },\n { feature: 'Array wildcard property (items[*].price)', minVersion: '1.1' },\n { feature: 'Absolute paths (/field)', minVersion: '1.1' },\n { feature: 'Relative paths (../field)', minVersion: '1.1' },\n { feature: 'Bracket notation ([\"field-name\"])', minVersion: '1.1' },\n { feature: 'Context tokens (#index, @prev)', minVersion: '1.2' },\n ],\n\n parseResult: {\n description:\n 'The parser automatically detects the minimum required version',\n interface: `interface ParseResult {\n ast: ASTNode; // Abstract syntax tree\n dependencies: string[]; // List of field dependencies\n features: string[]; // List of detected features\n minVersion: string; // Minimum required version (\"1.0\" or \"1.1\")\n}`,\n },\n\n astUtilities: [\n {\n name: 'serializeAst',\n description:\n 'Convert an AST back to a formula string. Useful for debugging or displaying parsed formulas.',\n signature: 'serializeAst(ast: ASTNode): string',\n code: `import { parseFormula, serializeAst } from '@revisium/formula';\n\nconst { ast } = parseFormula('price * (1 + taxRate)');\nserializeAst(ast)\n// \"price * (1 + taxRate)\"\n\n// After modifying AST nodes, serialize back to string\nconst { ast: ast2 } = parseFormula('a + b');\nserializeAst(ast2)\n// \"a + b\"`,\n },\n {\n name: 'replaceDependencies',\n description:\n 'Replace field references in an AST with new names. Useful for renaming fields or migrating formulas.',\n signature:\n 'replaceDependencies(ast: ASTNode, replacements: Record<string, string>): ASTNode',\n code: `import { parseFormula, replaceDependencies, serializeAst } from '@revisium/formula';\n\n// Rename a field in a formula\nconst { ast } = parseFormula('oldPrice * quantity');\nconst newAst = replaceDependencies(ast, { oldPrice: 'price' });\nserializeAst(newAst)\n// \"price * quantity\"\n\n// Rename multiple fields\nconst { ast: ast2 } = parseFormula('a + b * c');\nconst newAst2 = replaceDependencies(ast2, { a: 'x', b: 'y', c: 'z' });\nserializeAst(newAst2)\n// \"x + y * z\"\n\n// Works with nested paths\nconst { ast: ast3 } = parseFormula('stats.damage * multiplier');\nconst newAst3 = replaceDependencies(ast3, { 'stats.damage': 'stats.power' });\nserializeAst(newAst3)\n// \"stats.power * multiplier\"`,\n },\n ],\n\n examples: [\n {\n expression: 'price * quantity',\n description: 'Calculate total from price and quantity',\n result: 'number',\n },\n {\n expression: 'firstName + \" \" + lastName',\n description: 'Concatenate strings with space',\n result: 'string',\n },\n {\n expression: 'quantity > 0',\n description: 'Check if in stock',\n result: 'boolean',\n },\n {\n expression: 'if(stock > 0, \"Available\", \"Out of Stock\")',\n description: 'Conditional text based on stock',\n result: 'string',\n },\n {\n expression: 'price * (1 + taxRate)',\n description: 'Price with tax',\n result: 'number',\n },\n {\n expression: 'items[0].price + items[1].price',\n description: 'Sum first two item prices (v1.1)',\n result: 'number',\n },\n ],\n\n apiExamples: [\n {\n name: 'Simple Expression (v1.0)',\n description: 'Parse a basic arithmetic expression',\n code: `parseExpression('price * 1.1')\n// {\n// minVersion: \"1.0\",\n// features: [],\n// dependencies: [\"price\"]\n// }`,\n },\n {\n name: 'Nested Path (v1.1)',\n description: 'Parse expression with nested object access',\n code: `parseExpression('stats.damage * multiplier')\n// {\n// minVersion: \"1.1\",\n// features: [\"nested_path\"],\n// dependencies: [\"stats.damage\", \"multiplier\"]\n// }`,\n },\n {\n name: 'Array Access (v1.1)',\n description: 'Parse expression with array index access',\n code: `parseExpression('items[0].price + items[1].price')\n// {\n// minVersion: \"1.1\",\n// features: [\"array_index\", \"nested_path\"],\n// dependencies: [\"items[0].price\", \"items[1].price\"]\n// }`,\n },\n {\n name: 'Evaluate expressions',\n description: 'Execute formulas with context data',\n code: `evaluate('price * 1.1', { price: 100 })\n// 110\n\nevaluate('stats.damage', { stats: { damage: 50 } })\n// 50\n\nevaluate('items[0].price', { items: [{ price: 10 }] })\n// 10\n\nevaluate('price > 100', { price: 150 })\n// true\n\nevaluate('a + b * c', { a: 1, b: 2, c: 3 })\n// 7`,\n },\n {\n name: 'Function-named fields',\n description: 'Fields can have the same name as built-in functions',\n code: `// Built-in functions take precedence in function calls\nevaluate('max(max, 0)', { max: 10 })\n// 10 (max() function, then max field)\n\nevaluate('max(max - field.min, 0)', { max: 100, field: { min: 20 } })\n// 80\n\nevaluate('round(round * 2)', { round: 3.7 })\n// 7\n\n// Field named \"sum\" doesn't conflict with sum() function\nevaluate('sum(values) + sum', { values: [1, 2, 3], sum: 10 })\n// 16`,\n },\n {\n name: 'Evaluate with context (array items)',\n description:\n 'Use evaluateWithContext() for array item formulas with path resolution',\n code: `// Absolute path: /field always resolves from root\nevaluateWithContext('price * (1 + /taxRate)', {\n rootData: { taxRate: 0.1, items: [{ price: 100 }] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// 110\n\n// Nested absolute path\nevaluateWithContext('price * /config.multiplier', {\n rootData: { config: { multiplier: 1.5 }, items: [] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// 150\n\n// Relative path: ../field resolves from parent (root)\nevaluateWithContext('price * (1 - ../discount)', {\n rootData: { discount: 0.2, items: [] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// 80\n\n// itemData takes precedence over rootData for same field\nevaluateWithContext('value + 10', {\n rootData: { value: 100 },\n itemData: { value: 50 },\n currentPath: 'items[0]'\n})\n// 60`,\n },\n {\n name: 'Relative paths - path resolution',\n description:\n 'Understanding how ../ resolves based on currentPath. Each ../ goes up one segment (object property or array element counts as one segment)',\n code: `// Path structure explanation:\n// currentPath splits by \".\" (dots), keeping array indices attached to field names\n// \"items[0]\" = 1 segment\n// \"items[0].inner\" = 2 segments: [\"items[0]\", \"inner\"]\n// \"container.items[0]\" = 2 segments: [\"container\", \"items[0]\"]\n\n// Single ../ from array item -> goes to root\n// currentPath: \"items[0]\" (1 segment)\n// ../ goes up 1 level -> root\nevaluateWithContext('price * ../discount', {\n rootData: { discount: 0.2, items: [{ price: 100 }] },\n itemData: { price: 100 },\n currentPath: 'items[0]'\n})\n// Resolves ../discount to root.discount = 0.2\n// Result: 100 * 0.2 = 20\n\n// Single ../ from nested object in array -> goes to array item\n// currentPath: \"items[0].inner\" (2 segments)\n// ../ goes up 1 level -> \"items[0]\"\nevaluateWithContext('price * ../itemMultiplier', {\n rootData: { items: [{ itemMultiplier: 3, inner: { price: 10 } }] },\n itemData: { price: 10 },\n currentPath: 'items[0].inner'\n})\n// Resolves ../itemMultiplier to items[0].itemMultiplier = 3\n// Result: 10 * 3 = 30\n\n// Double ../../ from nested object in array -> goes to root\n// currentPath: \"items[0].inner\" (2 segments)\n// ../../ goes up 2 levels -> root\nevaluateWithContext('price * ../../rootRate', {\n rootData: { rootRate: 2, items: [{ inner: { price: 5 } }] },\n itemData: { price: 5 },\n currentPath: 'items[0].inner'\n})\n// Resolves ../../rootRate to root.rootRate = 2\n// Result: 5 * 2 = 10`,\n },\n {\n name: 'Relative paths - nested arrays',\n description:\n 'How relative paths work with arrays inside objects and nested arrays',\n code: `// Array inside nested object\n// currentPath: \"container.items[0]\" (2 segments: [\"container\", \"items[0]\"])\n// ../ goes up 1 level -> \"container\"\nevaluateWithContext('price * ../containerRate', {\n rootData: {\n container: {\n containerRate: 4,\n items: [{ price: 5 }]\n }\n },\n itemData: { price: 5 },\n currentPath: 'container.items[0]'\n})\n// Resolves ../containerRate to container.containerRate = 4\n// Result: 5 * 4 = 20\n\n// ../../ from array inside object -> goes to root\n// currentPath: \"container.items[0]\" (2 segments)\n// ../../ goes up 2 levels -> root\nevaluateWithContext('price * ../../rootVal', {\n rootData: {\n rootVal: 6,\n container: { items: [{ price: 5 }] }\n },\n itemData: { price: 5 },\n currentPath: 'container.items[0]'\n})\n// Resolves ../../rootVal to root.rootVal = 6\n// Result: 5 * 6 = 30\n\n// Nested arrays: items[].subItems[]\n// currentPath: \"items[0].subItems[0]\" (2 segments: [\"items[0]\", \"subItems[0]\"])\n// ../ goes up 1 level -> \"items[0]\"\nevaluateWithContext('qty * ../itemPrice', {\n rootData: {\n items: [{ itemPrice: 10, subItems: [{ qty: 3 }] }]\n },\n itemData: { qty: 3 },\n currentPath: 'items[0].subItems[0]'\n})\n// Resolves ../itemPrice to items[0].itemPrice = 10\n// Result: 3 * 10 = 30`,\n },\n {\n name: 'Relative paths - accessing nested properties',\n description:\n 'Relative paths can include nested property access after the ../ prefix',\n code: `// ../sibling.nested accesses a sibling with nested property\n// currentPath: \"items[0].products[0]\" (2 segments)\n// ../ goes to \"items[0]\", then accesses .config.discount\nevaluateWithContext('price * ../config.discount', {\n rootData: {\n items: [{\n config: { discount: 0.9 },\n products: [{ price: 100 }]\n }]\n },\n itemData: { price: 100 },\n currentPath: 'items[0].products[0]'\n})\n// Resolves ../config.discount to items[0].config.discount = 0.9\n// Result: 100 * 0.9 = 90\n\n// Deep nested: ../../settings.tax.rate\nevaluateWithContext('amount * ../../settings.tax.rate', {\n rootData: {\n settings: { tax: { rate: 0.1 } },\n orders: [{ items: [{ amount: 200 }] }]\n },\n itemData: { amount: 200 },\n currentPath: 'orders[0].items[0]'\n})\n// Resolves ../../settings.tax.rate to root.settings.tax.rate = 0.1\n// Result: 200 * 0.1 = 20`,\n },\n {\n name: 'Relative paths - complex nesting',\n description: 'Complex scenarios with arrays inside objects inside arrays',\n code: `// Array inside object inside array\n// Structure: items[].container.subItems[]\n// currentPath: \"items[0].container.subItems[0]\" (3 segments)\nevaluateWithContext('val * ../containerMultiplier', {\n rootData: {\n items: [{\n container: {\n containerMultiplier: 4,\n subItems: [{ val: 3 }]\n }\n }]\n },\n itemData: { val: 3 },\n currentPath: 'items[0].container.subItems[0]'\n})\n// ../ goes to \"items[0].container\"\n// Resolves ../containerMultiplier to items[0].container.containerMultiplier = 4\n// Result: 3 * 4 = 12\n\n// ../../ from same structure -> goes to array item\nevaluateWithContext('val * ../../itemRate', {\n rootData: {\n items: [{\n itemRate: 5,\n container: { subItems: [{ val: 2 }] }\n }]\n },\n itemData: { val: 2 },\n currentPath: 'items[0].container.subItems[0]'\n})\n// ../../ goes to \"items[0]\"\n// Resolves ../../itemRate to items[0].itemRate = 5\n// Result: 2 * 5 = 10\n\n// ../../../ from same structure -> goes to root\nevaluateWithContext('val * ../../../rootFactor', {\n rootData: {\n rootFactor: 3,\n items: [{\n container: { subItems: [{ val: 7 }] }\n }]\n },\n itemData: { val: 7 },\n currentPath: 'items[0].container.subItems[0]'\n})\n// ../../../ goes to root\n// Resolves ../../../rootFactor to root.rootFactor = 3\n// Result: 7 * 3 = 21`,\n },\n {\n name: 'Array context tokens - basic',\n description:\n 'Use #index, #length, #first, #last for position info; @prev, @next for neighbor access',\n code: `// arrayContext provides position info for array item formulas\nconst arrayContext = {\n levels: [{\n index: 2, // current position\n length: 5, // array length\n prev: { value: 20 }, // previous element\n next: { value: 40 }, // next element\n }]\n};\n\nevaluateWithContext('#index', { rootData: {}, arrayContext })\n// 2\n\nevaluateWithContext('#length', { rootData: {}, arrayContext })\n// 5\n\nevaluateWithContext('#first', { rootData: {}, arrayContext })\n// false (index !== 0)\n\nevaluateWithContext('#last', { rootData: {}, arrayContext })\n// false (index !== length - 1)\n\nevaluateWithContext('@prev.value', { rootData: {}, arrayContext })\n// 20\n\nevaluateWithContext('@next.value', { rootData: {}, arrayContext })\n// 40\n\n// At first element, @prev is null\nevaluateWithContext('@prev', {\n rootData: {},\n arrayContext: { levels: [{ index: 0, length: 3, prev: null, next: {} }] }\n})\n// null`,\n },\n {\n name: 'Array context tokens - nested arrays',\n description:\n 'Access parent array context with #parent.*, #root.*, @parent.*, @root.*',\n code: `// For nested arrays like orders[].items[]:\n// levels[0] = innermost (items), levels[1] = parent (orders)\nconst arrayContext = {\n levels: [\n { index: 1, length: 3, prev: {}, next: {} }, // items[1]\n { index: 2, length: 5, prev: {}, next: {} }, // orders[2]\n ]\n};\n\nevaluateWithContext('#index', { rootData: {}, arrayContext })\n// 1 (current item index)\n\nevaluateWithContext('#parent.index', { rootData: {}, arrayContext })\n// 2 (parent order index)\n\nevaluateWithContext('#parent.length', { rootData: {}, arrayContext })\n// 5 (number of orders)\n\n// #root.* is shortcut for topmost array (same as #parent.* for 2 levels)\nevaluateWithContext('#root.index', { rootData: {}, arrayContext })\n// 2\n\n// For 3+ levels, #root always points to outermost array\nconst threeLevel = {\n levels: [\n { index: 0, length: 2, prev: null, next: {} }, // innermost\n { index: 1, length: 3, prev: {}, next: {} }, // middle\n { index: 2, length: 4, prev: {}, next: null }, // outermost (root)\n ]\n};\n\nevaluateWithContext('#parent.parent.index', { rootData: {}, arrayContext: threeLevel })\n// 2 (outermost)\n\nevaluateWithContext('#root.index', { rootData: {}, arrayContext: threeLevel })\n// 2 (same as #parent.parent.index)`,\n },\n {\n name: 'Array context tokens - practical examples',\n description:\n 'Common patterns: running total, numbering, delta calculation',\n code: `// Running total pattern (like Excel)\n// rows[].runningTotal = if(#first, value, @prev.runningTotal + value)\nconst rows = [\n { value: 10 },\n { value: 20 },\n { value: 15 },\n];\n\n// For rows[2]:\nevaluateWithContext('if(#first, value, @prev.value + value)', {\n rootData: {},\n itemData: { value: 15 },\n arrayContext: {\n levels: [{\n index: 2,\n length: 3,\n prev: { value: 20 }, // Note: use non-computed field from prev\n next: null,\n }]\n }\n})\n// 35 (20 + 15)\n\n// Nested numbering like \"1.1\", \"1.2\", \"2.1\"\n// sections[].questions[].number\nevaluateWithContext('concat(#parent.index + 1, \".\", #index + 1)', {\n rootData: {},\n arrayContext: {\n levels: [\n { index: 1, length: 3, prev: {}, next: {} }, // question index\n { index: 0, length: 2, prev: null, next: {} }, // section index\n ]\n }\n})\n// \"1.2\"\n\n// Delta from previous\n// measurements[].delta = if(#first, 0, value - @prev.value)\nevaluateWithContext('if(#first, 0, value - @prev.value)', {\n rootData: {},\n itemData: { value: 105 },\n arrayContext: {\n levels: [{\n index: 1,\n length: 3,\n prev: { value: 100 },\n next: { value: 102 },\n }]\n }\n})\n// 5`,\n },\n ],\n\n schemaUsage: {\n structure:\n '{ \"x-formula\": { \"version\": 1, \"expression\": \"...\" }, \"readOnly\": true }',\n fieldTypes: ['string', 'number', 'boolean'],\n rules: [\n 'Add x-formula to string, number, or boolean field schema',\n 'readOnly: true is REQUIRED for fields with x-formula',\n 'Expression must reference existing fields in the same table',\n 'Circular dependencies are not allowed (a references b, b references a)',\n ],\n },\n};\n"],"mappings":";;AA6DA,MAAa,cAA2B;CACtC,SAAS;CACT,aACE;CAEF,QAAQ;EACN,iBAAiB;GACf;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,qBAAqB;GACnB;IAAE,UAAU;IAAK,aAAa;IAAoC;GAClE;IAAE,UAAU;IAAK,aAAa;IAAe;GAC7C;IAAE,UAAU;IAAK,aAAa;IAAkB;GAChD;IAAE,UAAU;IAAK,aAAa;IAAY;GAC1C;IAAE,UAAU;IAAK,aAAa;IAAsB;GACrD;EACD,qBAAqB;GACnB;IAAE,UAAU;IAAM,aAAa;IAAS;GACxC;IAAE,UAAU;IAAM,aAAa;IAAa;GAC5C;IAAE,UAAU;IAAK,aAAa;IAAgB;GAC9C;IAAE,UAAU;IAAK,aAAa;IAAa;GAC3C;IAAE,UAAU;IAAM,aAAa;IAAoB;GACnD;IAAE,UAAU;IAAM,aAAa;IAAiB;GACjD;EACD,kBAAkB;GAChB;IAAE,UAAU;IAAM,aAAa;IAAe;GAC9C;IAAE,UAAU;IAAM,aAAa;IAAc;GAC7C;IAAE,UAAU;IAAK,aAAa;IAAe;GAC9C;EACD,OAAO,CAAC,4BAA4B,8BAA8B;EACnE;CAED,WAAW;EACT,QAAQ;GACN;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CACR,sDACA,6DACD;IACF;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,2BAAyB;IACrC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,2BAAyB;IACrC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,6CAAyC;IACrD;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,0CAAsC;IAClD;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,2CAAuC;IACnD;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,0DAAkD;IAC9D;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,2BAAyB,uCAAmC;IACxE;GACF;EACD,SAAS;GACP;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,6BAA6B,kBAAkB;IAC3D;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,kBAAkB;IAC9B;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,iBAAiB;IAC7B;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,eAAe;IAC3B;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,gBAAgB;IAC5B;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,iBAAiB;IAC7B;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,iCAAiC;IAC7C;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,gCAAgC;IAC5C;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,sBAAsB;IAClC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,kBAAkB;IAC9B;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,qBAAqB;IACjC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU;KAAC;KAAkB;KAAgB;KAAe;IAC7D;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,oCAAkC,qBAAqB;IACnE;GACF;EACD,SAAS;GACP;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,oDAAoD;IAChE;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,8CAA8C;IAC1D;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,kCAAkC;IAC9C;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,gDAA4C;IACxD;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,kDAA8C;IAC1D;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,gDAA4C;IACxD;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,kDAAkD;IAC9D;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CACR,+DACD;IACF;GACF;EACD,OAAO;GACL;IACE,MAAM;IACN,aACE;IACF,WAAW;IACX,YAAY;IACZ,UAAU;KACR;KACA;KACA;KACD;IACF;GACD;IACE,MAAM;IACN,aACE;IACF,WAAW;IACX,YAAY;IACZ,UAAU,CACR,gCACA,+DACD;IACF;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,kCAAkC;IAC9C;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,6BAA6B;IACzC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,2BAA2B;IACvC;GACF;EACD,YAAY;GACV;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,yBAAuB;IACnC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,yBAAuB;IACnC;GACD;IACE,MAAM;IACN,aAAa;IACb,WAAW;IACX,YAAY;IACZ,UAAU,CAAC,wBAAwB,wBAAwB;IAC5D;GACF;EACD,aAAa,CACX;GACE,MAAM;GACN,aAAa;GACb,WAAW;GACX,YAAY;GACZ,UAAU,CACR,kDACA,sCACD;GACF,EACD;GACE,MAAM;GACN,aAAa;GACb,WAAW;GACX,YAAY;GACZ,UAAU,CAAC,0CAAwC;GACpD,CACF;EACF;CAED,UAAU;EACR;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAS;IAAY;IAAa;GAC7C,uBAAuB;IAAC;IAAa;IAAgB;IAAiB;GACvE;EACD;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAe;IAAa;IAAmB;GAC3D;EACD;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAe;IAAW;IAAgB;GACtD;EACD;GACE,MAAM;GACN,aAAa;GACb,YAAY;GACZ,UAAU;IAAC;IAAgB;IAAqB;IAAyB;GACzE,uBAAuB,CAAC,qBAAmB;GAC5C;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACD;GACD,uBAAuB,CAAC,wBAAsB,uBAAqB;GACpE;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACD,uBAAuB,CACrB,wBACA,kCACD;GACF;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACD;GACD,uBAAuB,CAAC,kBAAgB,oBAAkB;GAC3D;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACD;GACD,uBAAuB;IACrB;IACA;IACA;IACD;GACF;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACD;GACF;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACD;GACD,uBAAuB,CAAC,oBAAkB,iBAAiB;GAC5D;EACD;GACE,MAAM;GACN,aACE;GACF,YAAY;GACZ,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACD;GACF;EACF;CAED,kBAAkB;EAChB;GAAE,SAAS;GAAwC,YAAY;GAAO;EACtE;GAAE,SAAS;GAAuC,YAAY;GAAO;EACrE;GAAE,SAAS;GAAsB,YAAY;GAAO;EACpD;GAAE,SAAS;GAA2B,YAAY;GAAO;EACzD;GAAE,SAAS;GAA4C,YAAY;GAAO;EAC1E;GAAE,SAAS;GAA2B,YAAY;GAAO;EACzD;GAAE,SAAS;GAA6B,YAAY;GAAO;EAC3D;GAAE,SAAS;GAAqC,YAAY;GAAO;EACnE;GAAE,SAAS;GAAkC,YAAY;GAAO;EACjE;CAED,aAAa;EACX,aACE;EACF,WAAW;;;;;;EAMZ;CAED,cAAc,CACZ;EACE,MAAM;EACN,aACE;EACF,WAAW;EACX,MAAM;;;;;;;;;;EAUP,EACD;EACE,MAAM;EACN,aACE;EACF,WACE;EACF,MAAM;;;;;;;;;;;;;;;;;;;EAmBP,CACF;CAED,UAAU;EACR;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACD;GACE,YAAY;GACZ,aAAa;GACb,QAAQ;GACT;EACF;CAED,aAAa;EACX;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;GAMP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;GAMP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;GAMP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;;;;;;;;;GAcP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;;;;;;;;GAaP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BP;EACD;GACE,MAAM;GACN,aAAa;GACb,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCP;EACD;GACE,MAAM;GACN,aACE;GACF,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDP;EACF;CAED,aAAa;EACX,WACE;EACF,YAAY;GAAC;GAAU;GAAU;GAAU;EAC3C,OAAO;GACL;GACA;GACA;GACA;GACD;EACF;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formula-spec.d.ts","sourceRoot":"","sources":["../src/formula-spec.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;IACpD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QACN,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,mBAAmB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACjE,mBAAmB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACjE,gBAAgB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IACF,SAAS,EAAE;QACT,MAAM,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,EAAE,YAAY,EAAE,CAAC;QACxB,OAAO,EAAE,YAAY,EAAE,CAAC;QACxB,KAAK,EAAE,YAAY,EAAE,CAAC;QACtB,UAAU,EAAE,YAAY,EAAE,CAAC;QAC3B,WAAW,EAAE,YAAY,EAAE,CAAC;KAC7B,CAAC;IACF,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;KAClC,EAAE,CAAC;IACJ,gBAAgB,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;KACd,EAAE,CAAC;IACJ,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,EAAE,CAAC;IACJ,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,EAAE,CAAC;IACJ,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;CACH;AAED,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"formula-spec.d.ts","sourceRoot":"","sources":["../src/formula-spec.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;IACpD,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE;QACN,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,mBAAmB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACjE,mBAAmB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACjE,gBAAgB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IACF,SAAS,EAAE;QACT,MAAM,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,EAAE,YAAY,EAAE,CAAC;QACxB,OAAO,EAAE,YAAY,EAAE,CAAC;QACxB,KAAK,EAAE,YAAY,EAAE,CAAC;QACtB,UAAU,EAAE,YAAY,EAAE,CAAC;QAC3B,WAAW,EAAE,YAAY,EAAE,CAAC;KAC7B,CAAC;IACF,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;KAClC,EAAE,CAAC;IACJ,gBAAgB,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,WAAW,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;KACd,EAAE,CAAC;IACJ,QAAQ,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,EAAE,CAAC;IACJ,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;KACd,EAAE,CAAC;IACJ,WAAW,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,KAAK,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;CACH;AAED,eAAO,MAAM,WAAW,EAAE,WAk/BzB,CAAC"}
|
package/dist/formula-spec.mjs
CHANGED
|
@@ -294,17 +294,21 @@ const formulaSpec = {
|
|
|
294
294
|
array: [
|
|
295
295
|
{
|
|
296
296
|
name: "sum",
|
|
297
|
-
description: "Calculate the sum of array elements",
|
|
297
|
+
description: "Calculate the sum of array elements. Supports wildcard property access to sum nested values.",
|
|
298
298
|
signature: "sum(array)",
|
|
299
299
|
returnType: "number",
|
|
300
|
-
examples: [
|
|
300
|
+
examples: [
|
|
301
|
+
"sum(prices) // total of all prices",
|
|
302
|
+
"sum(items[*].price) // sum prices from array of objects",
|
|
303
|
+
"sum(orders[*].items[*].amount) // sum nested arrays"
|
|
304
|
+
]
|
|
301
305
|
},
|
|
302
306
|
{
|
|
303
307
|
name: "avg",
|
|
304
|
-
description: "Calculate the average of array elements",
|
|
308
|
+
description: "Calculate the average of array elements. Supports wildcard property access.",
|
|
305
309
|
signature: "avg(array)",
|
|
306
310
|
returnType: "number",
|
|
307
|
-
examples: ["avg(scores) // average score"]
|
|
311
|
+
examples: ["avg(scores) // average score", "avg(items[*].rating) // average rating from array of objects"]
|
|
308
312
|
},
|
|
309
313
|
{
|
|
310
314
|
name: "count",
|
|
@@ -424,6 +428,21 @@ const formulaSpec = {
|
|
|
424
428
|
],
|
|
425
429
|
dependenciesExtracted: ["[\"items[0].price\"]", "[\"items[-1].name\"]"]
|
|
426
430
|
},
|
|
431
|
+
{
|
|
432
|
+
name: "array_wildcard_property",
|
|
433
|
+
description: "Access properties across all array elements using [*] wildcard. Property access after wildcard maps over all elements. Multiple wildcards flatten nested arrays.",
|
|
434
|
+
minVersion: "1.1",
|
|
435
|
+
examples: [
|
|
436
|
+
"items[*].price // [10, 20, 30] - map property",
|
|
437
|
+
"sum(items[*].price) // 60 - sum mapped values",
|
|
438
|
+
"avg(items[*].rating) // average of all ratings",
|
|
439
|
+
"values[*].nested.value // deeply nested property access",
|
|
440
|
+
"orders[*].items // [[1,2], [3,4]] - array of arrays",
|
|
441
|
+
"orders[*].items[*] // [1,2,3,4] - flattened",
|
|
442
|
+
"sum(orders[*].items[*].amount) // sum all nested amounts"
|
|
443
|
+
],
|
|
444
|
+
dependenciesExtracted: ["[\"items[*].price\"]", "[\"orders[*].items[*].amount\"]"]
|
|
445
|
+
},
|
|
427
446
|
{
|
|
428
447
|
name: "root_path",
|
|
429
448
|
description: "Absolute path reference starting with /. Always resolves from root data, even inside array item formulas",
|
|
@@ -516,6 +535,10 @@ const formulaSpec = {
|
|
|
516
535
|
feature: "Array index ([0], [-1])",
|
|
517
536
|
minVersion: "1.1"
|
|
518
537
|
},
|
|
538
|
+
{
|
|
539
|
+
feature: "Array wildcard property (items[*].price)",
|
|
540
|
+
minVersion: "1.1"
|
|
541
|
+
},
|
|
519
542
|
{
|
|
520
543
|
feature: "Absolute paths (/field)",
|
|
521
544
|
minVersion: "1.1"
|