{"id":18064382,"url":"https://github.com/carlosnz/fig-tree-evaluator","last_synced_at":"2025-04-11T18:10:32.720Z","repository":{"id":61859568,"uuid":"493894429","full_name":"CarlosNZ/fig-tree-evaluator","owner":"CarlosNZ","description":"A highly configurable custom expression tree evaluator","archived":false,"fork":false,"pushed_at":"2025-04-05T08:28:08.000Z","size":20928,"stargazers_count":19,"open_issues_count":12,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-05T09:26:05.392Z","etag":null,"topics":["configuration","configuration-files","evaluation","evaluator","expression-evaluator","expression-tree","json","json-forms","tree"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CarlosNZ.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-19T02:33:46.000Z","updated_at":"2025-04-04T08:12:07.000Z","dependencies_parsed_at":"2024-06-01T12:26:58.929Z","dependency_job_id":"74af467a-cee2-495a-9f29-cc51e91b9ff5","html_url":"https://github.com/CarlosNZ/fig-tree-evaluator","commit_stats":{"total_commits":248,"total_committers":2,"mean_commits":124.0,"dds":"0.020161290322580627","last_synced_commit":"ea32f7ac0882cb9e7a7e4a73583fe127d1fef440"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarlosNZ%2Ffig-tree-evaluator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarlosNZ%2Ffig-tree-evaluator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarlosNZ%2Ffig-tree-evaluator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CarlosNZ%2Ffig-tree-evaluator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CarlosNZ","download_url":"https://codeload.github.com/CarlosNZ/fig-tree-evaluator/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248456365,"owners_count":21106603,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration","configuration-files","evaluation","evaluator","expression-evaluator","expression-tree","json","json-forms","tree"],"created_at":"2024-10-31T06:06:14.398Z","updated_at":"2025-04-11T18:10:32.693Z","avatar_url":"https://github.com/CarlosNZ.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fig-tree-evaluator\n\n![Logo](images/FigTreeEvaluator_logo_1000.png)\n\n**FigTree Evaluator** is a module to evaluate JSON-structured expression trees. \n\nA typical use case would be for evaluating **configuration** files, where you need to store dynamic values or arbitrary logic without allowing users to inject executable code (perhaps in a .json file, say). Examples could include: \n\n- a [form-builder app](https://github.com/openmsupply/conforma-web-app) might need to allow complex conditional logic for form element visibility based on previous responses, or for validation beyond what is available in standard validation libraries.\n- configure a [decision tree](https://en.wikipedia.org/wiki/Decision_tree) to implement branching logic. (See implementation in `20_match.test.ts`)\n- extend [JSON Forms](https://jsonforms.io) with more complex logic and dynamic lookups: https://github.com/CarlosNZ/jsonforms-with-figtree-demo\n\nA range of built-in operators are available, from simple logic, arithmetic and string manipulation, to data fetching from local sources or remote APIs. Plus, you can extend functionality with your own [custom operators](#custom-functionsoperators)\n\n\u003c!-- omit in toc --\u003e\n## [Try the Demo/Playground](https://carlosnz.github.io/fig-tree-evaluator/)\n\nThe demo is powered by [fig-tree-editor-react](https://github.com/CarlosNZ/fig-tree-editor-react), a React component for editing FigTree expressions.\n\n## Contents \u003c!-- omit in toc --\u003e\n\u003c!-- TOC --\u003e\n- [The basics](#the-basics)\n- [Install](#install)\n- [Usage](#usage)\n- [Available options](#available-options)\n- [Operator nodes](#operator-nodes)\n  - [Other common properties:](#other-common-properties)\n  - [Operator \\\u0026 Property Aliases](#operator--property-aliases)\n- [Operator reference](#operator-reference)\n  - [AND](#and)\n  - [OR](#or)\n  - [EQUAL](#equal)\n  - [NOT\\_EQUAL](#not_equal)\n  - [PLUS](#plus)\n  - [SUBTRACT](#subtract)\n  - [MULTIPLY](#multiply)\n  - [DIVIDE](#divide)\n  - [GREATER\\_THAN](#greater_than)\n  - [LESS\\_THAN](#less_than)\n  - [COUNT](#count)\n  - [CONDITIONAL](#conditional)\n  - [REGEX](#regex)\n  - [OBJECT\\_PROPERTIES](#object_properties)\n  - [STRING\\_SUBSTITUTION](#string_substitution)\n  - [SPLIT](#split)\n  - [HTTP requests](#http-requests)\n  - [GET](#get)\n  - [POST](#post)\n  - [GRAPHQL](#graphql)\n  - [SQL](#sql)\n  - [BUILD\\_OBJECT](#build_object)\n  - [MATCH](#match)\n  - [PASSTHRU](#passthru)\n- [Custom Functions/Operators](#custom-functionsoperators)\n  - [The CUSTOM\\_FUNCTIONS operator](#the-custom_functions-operator)\n  - [Custom Operators](#custom-operators)\n- [Alias Nodes](#alias-nodes)\n- [Fragments](#fragments)\n- [Shorthand syntax](#shorthand-syntax)\n- [Caching (Memoization)](#caching-memoization)\n- [Error handling](#error-handling)\n- [Metadata](#metadata)\n- [More examples](#more-examples)\n- [Development environment](#development-environment)\n- [Tests](#tests)\n- [Help, Feedback, Suggestions](#help-feedback-suggestions)\n- [Changelog](#changelog)\n- [Credit](#credit)\n\n\u003c!-- /TOC --\u003e\n## The basics\n\nFig-tree evaluates expressions structured in a JSON/Javascript object [expression tree](https://www.geeksforgeeks.org/expression-tree/). A single \"node\" of the tree consists of an **Operator**, with associated parameters (or child nodes), each of which can itself be another Operator node -- i.e. a recursive tree structure of arbitrary depth and complexity.\n\nA wide range of [operators are available](#operator-reference), but [custom functions/operators](#custom_functions) can be added to your implementation if you wish to extend the base functionality.\n\nFor example:\n\n```js\n{\n    operator: \"+\", // \"Addition\" operator\n    values: [1, 2, 3]\n}\n// -\u003e 6\n```\n\nOr, with a deeper structure that results in the same final output:\n```js\n{\n  operator: '+',\n  values: [\n    {\n      operator: '?', // conditional\n      condition: {\n        operator: '=', // equality\n        values: [\n          {\n            operator: 'objectProperties', // extracted from passed-in object\n            property: 'responses.Q1',\n          },\n          'correct',\n        ],\n      },\n      valueIfTrue: 1,\n      valueIfFalse: 0,\n    },\n    {\n      operator: 'GET', // API lookup\n      url: 'https://my.server.com/api/get-count',\n    },\n    3,\n  ],\n}\n// -\u003e 6\n```\n\nWhich would be represented diagrammatically with the following expression tree:\n\n![Example 2](/docs/img/Example_1.png)\n\nA playground for building and testing expressions is available [here](https://carlosnz.github.io/fig-tree-evaluator/)\n\n## Install\n\n`npm install fig-tree-evaluator`\\\nor\\\n`yarn add fig-tree-evaluator`\n\n## Usage\n\n```js\nimport { FigTreeEvaluator } from 'fig-tree-evaluator'\n\n// New evaluator instance\nconst fig = new FigTreeEvaluator([ options ]) // See available options below\n\n// Evaluate expressions\nfig.evaluate(expression, [options]) // Options over-ride initial options for this evaluation\n    .then((result) =\u003e { // \"evaluate\" is async method\n        // Do something with result\n    })\n\n// Or within async function:\nconst result = await fig.evaluate(expression, [options])\n```\n\nFigTreeEvaluator is written in **Typescript**, and the following types are available to import from the package:\n- `FigTreeOptions`: `options` object, as per [options](#available-options) below\n- `Operator`: string literal canonical [Operator](#operator-nodes) names (`AND`, `OR`, `EQUAL`, etc.)\n- `EvaluatorNode`: Evaluator input\n- `EvaluatorOutput`\n\n## Available options\n\nThe `options` parameter is an object with the following available properties (all optional):\n\n- `data` -- a single object containing any *objects* in your application that may wish to be inspected using the [objectProperties](#object_properties) operator. (See [playground](LINK) for examples). If these objects are regularly changing, you'll probably want to pass them into each separate evaluation rather than with the initial constructor.\n- `functions` -- a single object containing any *custom functions* available for use by [custom functions/operators](#custom_functions).\n- `fragments` -- commonly-used expressions (with optional parameters) that can be re-used in any other expression. See [Fragments](#fragments)\n- `httpClient` -- pass your http client in here in order to use the HTTP-based operators ([`GET`](#get), [`POST`](#post), [`GraphQL`](#graphql)) (uses browser's native `fetch` by default). \n- `graphQLConnection` -- a GraphQL connection object, if using the [`graphQL` operator](#graphql). See operator details below.\n- `sqlConnection` -- if you wish to make calls to an SQL database using the [`SQL` operator](#sql), pass a connection to the database here. See operator details below.\n- `baseEndpoint` -- If specified, any partial urls specified in the http-based operators (`GET`, `POST`) will be relative to to this base. Useful if you expect most http requests to be to the same server.\n- `headers` -- A general http headers object that will be passed to *all* http-based operators (`GET`, `POST`, `GraphQL`). Useful for authentication headers, for example. Each operator and instance can have its own headers, though, so see specific operator reference for details.\n- `returnErrorAsString` -- by default the evaluator will throw errors with invalid evaluation expressions (with helpful error messages indicating the node which threw the error and what the problem was). But if you have `returnErrorAsString: true` set, the evaluator will never throw, but instead return error messages as a valid string output. (See also the [`fallback`](#other-common-properties) parameter below). See [Error handling](#error-handling) section for more detail.\n- `allowJSONStringInput` -- the evaluator is expecting the input expression to be a javascript object. However, it will also accept JSON strings if this option is set to `true`. We have to perform additional logic on every evaluation input to determine if a string is a JSON expression or a standard string, so this is skipped by default for performance reasons. However, if you want to send (for example) user input directly to the evaluator without running it through your own `JSON.parse()`, then enable this option.\n- `skipRuntimeTypeCheck` -- we perform comprehensive type checking at runtime to ensure that each operator only performs its operation on valid inputs. If type checking fails, we throw an error detailing the explicit problem. However, if `skipRuntimeTypeCheck` is set to `true`, then all inputs are passed to the operator regardless, and any errors will come from whatever standard javascript errors might be encountered (e.g. trying to pass a primitive value when an array is expected =\u003e `.map is not a function`)\n- `caseInsensitive` -- this only affects the [`equal`/`notEqual` operators](#equal) (see there for more detail). \n- `nullEqualsUndefined` -- this only affects the [`equal`/`notEqual` operators](#equal) (see there for more detail). \n- `evaluateFullObject` -- by default, FigTree expects the root of an input expression to be an [Operator Node](#operator-nodes), and if not, will return the input unmodified. However, you may have cases where the evaluation expressions are deep within a larger structure (such as a JSON schema, for example). In this case, you can set `evaluateFullObject` to `true` and the evaluator will find *any* operator nodes within the structure and evaluate them within the object tree.\n- `excludeOperators` -- an array of operator names (or [aliases](#operator--property-aliases)) to prohibit from being used in expressions. You may wish to restrict (for example) database access via FigTree configurations, in which case these exclusions can be defined when instantiating the FigTree instance (or updated on the fly).\n- `useCache` -- caches the results from certain operators to avoid repeated network requests with the same input values. By default, this is set to `true`, and it can be overridden for specific nodes. See [Memoization/Caching section](#caching-memoization) for more detail\n- `maxCacheSize` -- the maximum number of results that will be held in the aforementioned cache (default: `50`)\n- `maxCacheTime` -- the maximum time (in seconds) that a result will be cached since last access (default: `1800` (30 minutes))\n- `noShorthand` -- there is a [shorthand syntax](#shorthand-syntax) available for writing expressions. Internally, this is pre-processed into the standard expression form before evaluation. If you have no use for this and you'd rather all expressions were written with full verbosity, set `noShorthand: true` to save a small amount in performance by skipping internal pre-processing.\n\nAs mentioned above, `options` can be provided as part of the constructor as part of each separate evaluation. You can also change the options permanently for a given evaluator instance with:\n\n`fig.updateOptions(options)`\n\nYou can also retrieve the current options state at any time with:\n\n`fig.getOptions()`\n\nIt's also possible to run one-off evaluations by importing the evaluation method directly rather than using the constructor:\n\n```js\nimport { evaluateExpression } from 'fig-tree-evaluator'\n\nevaluateExpression(expression, [options]).then((result) =\u003e {\n    // Do something with result\n})\n```\n\n## Operator nodes\n\nEach operator has a selections of input properties associated with it, some required, some optional. For example, the `conditional` operator requires inputs equivalent to the javascript ternary operator, and are expressed as follows:\n\n```js\n{\n    operator: \"conditional\", // or \"?\"\n    condition: \u003cboolean\u003e, // or fig-tree expression that returns boolean\n    valueIfTrue: \u003csomeValue\u003e,\n    valueIfFalse: \u003csomeOtherValue\u003e\n\n}\n```\n\nHowever, it is also possible to provide the operator properties (or \"operands\") as a single `children` array, in which case the specific properties are interpreted positionally.\n\nFor example, the following two representations are equivalent `conditional` operator nodes:\n\n```js\n{\n    operator: \"?\", // conditional (alias)\n    condition: 1 + 1 ==== 2,\n    valueIfTrue: \"True output\",\n    valueIfFalse: \"False output\"\n}\n\n// same as:\n\n{\n    operator: \"?\",\n    children: [ 1 + 2 === 2, \"True output\", \"False output\" ]\n}\n```\n\nMost of the time named properties would be preferable; however there are occasional cases where the \"children\" array might be easier to deal with, or to build up from child nodes.\n\n### Other common properties:\n\nIn each operator node, as well as the operator-specific properties, the following three optional properties can be provided:\n\n- `fallback`: if the operation throws an error, the `fallback` value will be returned instead. The `fallback` property can be provided at any level of the expression tree and bubbled up from where errors are caught to parent nodes. *Fallbacks are strongly recommended if there is any chance of an error (e.g. a network \"GET\" request that doesn't yet have its parameters defined).*  \nSee [Error handling](#error-handling)\n- `outputType` (or `type`): will convert the result of the current node to the specified `outputType`. Valid values are `string`, `number`, `boolean` (or `bool`), and `array`. You can experiment in the [demo app](https://carlosnz.github.io/fig-tree-evaluator/) to see the outcome of applying different `outputType` values to various results.\n- `useCache`: Overrides the global `useCache` value (from [options](#available-options)) for this node only. See [Caching/Memoization](#caching-memoization) below for more info.\n\nRemember that *all* operator node properties can themselves be operator nodes, *including* the `fallback` and `outputType` properties.\n\ne.g.\n\n```js\n// Dynamic outputType, which uses the fallback value due to missing property\n// for the conditional '?' operator:\n{\n  operator: '+',\n  values: [9, 10, 11],\n  outputType: {\n    operator: '?',\n    condition: {\n      operator: '=',\n      values: ['three', 'four'],\n    },\n    valueIfTrue: 'number',\n    fallback: 'string',\n  },\n}\n// =\u003e \"30\"\n\n```\n\n### Operator \u0026 Property Aliases\n\nFor maximal flexibility, all operator names are case-insensitive, and also come with a selection of \"aliases\" that can be used instead, based on context or preference (e.g. the `conditional` operator can also be aliased as `?` or `ifThen`). See specific operator reference for all available aliases.\n\nSimilarly, some property names accept aliases -- see individual operators for these.\n\n## Operator reference\n\nThe full list of available operators and their associated properties:\n\n\u003csup\u003e*\u003c/sup\u003e denotes \"required\" properties\n\n### AND\n\n*Logical AND*\n\nAliases: `and`, `\u0026`, `\u0026\u0026`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of elements; will be compared using Javascript `\u0026\u0026` operator\n\ne.g.\n```js\n{\n  operator: '\u0026',\n  values: [true, true, true],\n}\n// =\u003e true\n```\n\n`children` array: `[...values]`\n\n----\n### OR\n\n*Logical OR*\n\nAliases: `or`, `|`, `||`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of elements; will be compared using Javascript `||` operator\n\ne.g.\n```js\n{\n  operator: 'or',\n  values: [\n    true,\n    {\n      operator: 'and',\n      values: [true, false],\n    },\n    true,\n  ],\n}\n// =\u003e true\n```\n\n`children` array: `[...values]`\n\n----\n### EQUAL\n\n*Equality*\n\nAliases: `=`, `eq`, `equal`, `equals`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of elements; will be compared for strict equality. This includes simple types as well as deep equality of objects and arrays.\n- `caseInsensitive`: (boolean, default `false`) -- when comparing string values, if this property is `true`, the case of the strings will be ignored (e.g. `\"Monday\" == \"mONdAY\"`)\n- `nullEqualsUndefined`: (boolean, default `false`) -- there are times when it is convenient for `null` to be considered equal to `undefined`. If this is desired, set this property to `true`, otherwise all equality checks will be \"strict\" equality. If you find that you want this setting enabled globally, then you can set it in the overall [evaluator options](#available-options) instead of having to add this additional property to every equality expression.\n\ne.g.\n```js\n{\n  operator: '=',\n  values: [3, 3, 'three'],\n}\n// =\u003e false\n```\n\n`children` array: `[...values]`\n\n----\n### NOT_EQUAL\n\n*Non-equality*\n\nAliases: `!=`, `!`, `ne`, `notEqual`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of elements; will be compared for inequality. This includes simple types as well as deep comparison of objects and arrays.\n- `caseInsensitive`: (boolean, default `false`) -- as [above](#equal)\n- `nullEqualsUndefined`: (boolean, default `false`) -- as [above](#equal)\n\ne.g.\n```js\n{\n  operator: '=',\n  values: [3, 3, 'three'],\n}\n// =\u003e true\n```\n\n`children` array: `[...values]`\n\n----\n### PLUS\n\n*Addition, concatenation, merging*\n\nAliases: `+`, `add`, `concat`, `join`, `merge`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of elements. Will be added (numbers), concatenated (strings, arrays) or merged (objects) according their type.\n- `type`: (`'string' | 'array'`) -- if specified, operator will treat the `values` as though they were this type. E.g. if `string`, it will concatenate the values, even if they're all numbers. The difference between this property and the common [`outputType` property](#other-common-properties) is that `outputType` converts the result, whereas this `type` property converts each element *before* the \"PLUS\" operation. \n\ne.g.\n```js\n{\n  operator: '+',\n  values: [4, 5, 6],\n}\n// =\u003e 15\n\n{\n  operator: '+',\n  values: ['this', ' and ', 'that'],\n}\n// =\u003e 'this and that'\n\n{\n  operator: '+',\n  values: [{one: 1, two: 2}, {three: 3}],\n}\n// =\u003e {one: 1, two: 2, three: 3}\n\n{\n  operator: '+',\n  values: [4, 5, 6],\n  type: 'string'\n}\n// =\u003e \"456\"\n\n{\n  operator: '+',\n  values: [4, 5, 6],\n  type: 'array'\n}\n// =\u003e [4, 5, 6]\n\n```\n\n`children` array: `[...values]`\n\n----\n### SUBTRACT\n\n*Subtraction*\n\nAliases: `-`, `subtract`, `minus`, `takeaway`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- exactly 2 numerical elements; the second will be subtracted from the first. (If non-numerical elements are provided, the operator will return `NaN`)\n\ne.g.\n```js\n{\n  operator: '-',\n  values: [10, 8],\n}\n// =\u003e 2\n\n{\n  operator: 'minus',\n  values: [0, 3.5, 10], // additional elements after the first two ignored\n}\n// =\u003e -3.5\n\n{\n  operator: '-',\n  values: [4, \"three\"],\n}\n// =\u003e NaN\n```\n\n`children` array: `[originalValue, valueToSubtract]` (same as `values`)\n\n----\n### MULTIPLY\n\n*Multiplication*\n\nAliases: `*`, `x`, `multiply`, `times`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of numerical elements. Returns the product of all elements.  (If non-numerical elements are provided, the operator will return `NaN`)\n\ne.g.\n```js\n{\n  operator: '*',\n  values: [5, 7],\n}\n// =\u003e 35\n\n{\n  operator: 'x',\n  values: [2, 3.5, 10], // additional elements after the first two ignored\n}\n// =\u003e 70\n\n{\n  operator: 'times',\n  values: [4, \"three\"],\n}\n// =\u003e NaN\n```\n\n`children` array: `[...values]`\n\n----\n### DIVIDE\n\n*Division*\n\nAliases: `/`, `divide`, `÷`\n\n#### Properties\n\n- `values`: (array) -- exactly 2 numerical elements; the first will be divided by the second.  (If non-numerical elements are provided, the operator will return `NaN`)\n- `dividend` (or `divide`): (number) -- the number that will be divided\n- `divisor` (or `by`): (number) -- the number to divide `dividend` by\n- `output` (`'quotient' | 'remainder'`) -- by default, the operator returns a floating point value. However, if `quotient` is specified, it will return the integer part of the result; if `remainder` is specified, it will return the remainder after division (i.e. `value1 % value2`)\n\nNote that the input values can be provided as *either* a `values` array *or* `dividend`/`divisor` properties. If both are provided, `values` takes precedence.\n\ne.g.\n```js\n{\n  operator: '/',\n  values: [35, 7],\n}\n// =\u003e 5\n\n{\n  operator: '/',\n  divide: 20,\n  by: 3,\n  output: 'quotient' \n}\n// =\u003e 6\n\n{\n  operator: 'divide',\n  dividend: 20,\n  divisor: 3,\n  output: 'remainder'\n}\n// =\u003e 2\n```\n\n`children` array: `[dividend, divisor]` (same as `values`)\n\n----\n### GREATER_THAN\n\n*Greater than (or equal to)*\n\nAliases: `\u003e`, `greaterThan`, `higher`, `larger`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- exactly 2 values. Can be any type of value that can be compared with Javascript `\u003e` operator.\n- `strict`: (boolean, default `false`) -- if `true`, value 1 must be strictly greater than value 2 (i.e. `\u003e`). Otherwise it will be compared with \"greater than or equal to\" (i.e. `\u003e=`)\n\ne.g.\n```js\n{\n  operator: '\u003e',\n  values: [10, 8]\n}\n// =\u003e true\n\n{\n  operator: '\u003e',\n  values: [\"alpha\", \"beta\"]\n}\n// =\u003e false\n\n{\n  operator: '\u003e',\n  values: [4, 4],\n  strict: true\n}\n// =\u003e false\n```\n\n`children` array: `[firstValue, secondValue]` (same as `values`)\n\n----\n### LESS_THAN\n\n*Less than (or equal to)*\n\nAliases: `\u003c`, `lessThan`, `lower`, `smaller`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- exactly 2 values. Can be any type of value that can be compared with Javascript `\u003c` operator.\n- `strict`: (boolean, default `false`) -- if `true`, value 1 must be strictly lower than value 2 (i.e. `\u003c`). Otherwise it will be compared with \"less than or equal to\" (i.e. `\u003c=`)\n\ne.g.\n```js\n{\n  operator: '\u003c',\n  values: [10, 8]\n}\n// =\u003e false\n\n{\n  operator: '\u003c',\n  values: [\"alpha\", \"beta\"]\n}\n// =\u003e true\n\n{\n  operator: '\u003c',\n  values: [4, 4],\n  strict: false\n}\n// =\u003e true\n```\n\n`children` array: `[firstValue, secondValue]` (same as `values`)\n\n----\n### COUNT\n\n*Count elements in array*\n\nAliases: `count`, `length`\n\n#### Properties\n\n- `values`\u003csup\u003e*\u003c/sup\u003e: (array) -- any number of elements. Returns `array.length`\n\ne.g.\n```js\n{\n  operator: 'count',\n  values: [10, 8, \"three\", \"four\"]\n}\n// =\u003e 4\n```\n\n`children` array: `[...values]`\n\n----\n### CONDITIONAL\n\n*Return different values depending on a condition expression*\n\nAliases: `?`, `conditional`, `ifThen`\n\n#### Properties\n\n- `condition`\u003csup\u003e*\u003c/sup\u003e: (boolean) -- a boolean value (presumably the result of a child expression)\n- `valueIfTrue` (or `ifTrue`)\u003csup\u003e*\u003c/sup\u003e: the value returned if `condition` is `true`\n- `valueIfFalse` (or `ifFalse`)\u003csup\u003e*\u003c/sup\u003e: the value returned if `condition` is `false`\n\ne.g.\n```js\n{\n  operator: '?',\n  condition: {\n    operator: '=',\n    values: [\n      {\n        operator: '+',\n        values: [5, 5, 10],\n      },\n      20,\n    ],\n  },\n  ifTrue: 'YES',\n  ifFalse: 'NO',\n}\n// =\u003e YES\n```\n\n`children` array: `[condition, valueIfTrue, valueIfFalse]`\n\n**Note**: *For more complex branching logic, the [\"match\" operator](#match) can be used (it matches more than just a boolean condition)*\n\n----\n### REGEX\n\n*Compares an input string against a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) pattern*\n\nAliases: `regex`, `patternMatch`, `regexp`, `matchPattern`\n\n#### Properties\n\n- `testString` (or `string`, `value`)\u003csup\u003e*\u003c/sup\u003e: (string) -- the string to be compared against the regex pattern\n- `pattern` (or `regex`, `regexp`, `regExp`, `re`)\u003csup\u003e*\u003c/sup\u003e: (string) a regex pattern to test `testString` against\n\nReturns `true` (match found) or `false` (no match)\n\ne.g.\n```js\n{\n  operator: 'regex',\n  string: \"home@myplace.com\",\n  pattern: '^[A-Za-z0-9.]+@[A-Za-z0-9]+\\\\.[A-Za-z0-9.]+$'  // Simple Email validation\n}\n// =\u003e true\n```\n\n`children` array: `[testString, pattern]`\n\n----\n### OBJECT_PROPERTIES\n\n*Extracts values from data objects in your application*\n\nAliases: `objectProperties`, `dataProperties`,`data`, `getData`, `objProps`, `getProperty`, `getObjProp`\n\n#### Properties\n\n- `property` (or `path`, `propertyName`)\u003csup\u003e*\u003c/sup\u003e: (string) -- the path to the required property in the object\n- `additionalData` (or `additionalObjects`, `additional`, `data`): (object) -- any other objects whose properties can be referenced in `property` (see below)\n\nData objects are normally expected to be passed in to the evaluator as part of the [options](#available-options), not as part of the expression itself. This is because the source objects are expected to be values internal to your application, whereas the evaluator provides an externally configurable mechanism to extract (and process) application data. (However, it is possible to pass data objects directly as part of the expression using the `additionalObjects` property, so (in theory) data objects could be dynamically generated from other expressions.)\n\nFor example, consider a `user` object and an fig-tree evaluator instance: \n\n```js\nconst user = {\n  firstName: 'Peter',\n  lastName: 'Parker',\n  alias: 'Spider-man',\n  friends: ['Ned', 'MJ', 'Peter 2', 'Peter 3'],\n  enemies: [\n    { name: 'The Vulture', identity: 'Adrian Toomes' },\n    { name: 'Green Goblin', identity: 'Norman Osborne' },\n  ],\n}\n\nconst fig = new FigTreeEvaluator()\n\nconst expression = getExpressionFromConfig()\n\nfig.evaluate(expression, { data: { user } })\n```\n\nHere is the result of various values of `expression:`\n\n```js\n{\n  operator: 'objectProperties',\n  property: 'user.firstName',\n}\n// =\u003e \"Peter\"\n\n{\n  operator: 'getData',\n  path: 'user.friends[1]',\n}\n// =\u003e \"MJ\"\n\n{\n  operator: 'getProperty',\n  path: 'user.enemies.name',\n}\n// =\u003e [\"The Vulture\", \"Green Goblin\"]\n```\nNotice the last example pulls multiple values out of an array of objects, in this case the \"name\". This is essentially a shorthand for:\n\n```js\nfig.evaluate(\n    { operator: 'getProperty', path: 'user.enemies' },\n    { data: { user } }\n  ).map((e) =\u003e e.name)\n```\n\nThe \"objectProperties\" operator uses [`object-property-extractor`](https://www.npmjs.com/package/object-property-extractor) internally, so please see the documentation of that package for more information.\n\nThe \"objectProperties\" operator will throw an error if an invalid path is provided, so it is recommended to provide a [`fallback`](#other-common-properties) value for the expression:\n```js\n{\n  operator: 'objectProperties',\n  property: 'user.middleName',\n  fallback: 'Not found!'\n}\n// =\u003e \"Not found!\"\n```\n\n`children` array: `[property]`\n\n\nExample using \"data\" passed in dynamically as part of expression:\n\n```js\n{\n  operator: 'objectProperties',\n  property: 'user.name',\n  additionalData: {\n    operator: '?',\n    condition: { operator: '=', values: [{ operator: '+', values: [7, 8, 9] }, 25] },\n    valueIfTrue: { user: { name: 'Bilbo' } },\n    valueIfFalse: { user: { name: 'Frodo' } },\n  },\n}\n// =\u003e \"Frodo\"\n```\n\n----\n### STRING_SUBSTITUTION\n\n*Replace values in a string using simple parameter (positional or named properties) substitution*\n\nAliases: `stringSubstitution`, `substitute`, `stringSub`, `replace`\n\n#### Properties\n\n- `string`\u003csup\u003e*\u003c/sup\u003e: (string) -- a parameterized (`%1`, `%2`, or `{{named}}`) string, where the parameters are to be replaced by dynamic values. E.g. `\"My name is %1 (age %2)\"`, or `\"My name is {{name}} (age {{age}})\"`\n- `substitutions` (or `replacements`, `values`)\u003csup\u003e*\u003c/sup\u003e: (array | object) -- the values to be substituted into `string`. Will be either an array or object depending on whether you're using positional replacements or named properties (see [below](#positional-replacement)).\n- `trimWhiteSpace` (or `trimWhitespace`, `trim`): (boolean, default `true`) -- strips whitespace from the beginning or end of the substitution values\n- `substitutionCharacter` (or `subCharacter`, `subChar`): (`\"%\"` or `\"$\"`) -- by default, when using positional replacement, it looks for the `%` token (i.e `%1, %2, etc`), but this can be changed to `$` (i.e. `$1, $2, $3, etc`) by setting this property to `$`.\n- `numberMapping` (or `numMap`, `numberMap`, `pluralisation`, `pluralization`, `plurals`): (object) -- when replacing with named properties and you have replacement values that are numbers, it's possible to map values or ranges to specific string outputs. This can be used to produce correct pluralisation, for example. [See below](#named-property-replacement) for more details.\n\nSubstitution can be done using either **positional** replacement, or with **named properties**:\n\n#### Positional replacement\n\nThe values in the `substitutions` array are replaced in the original `string` by matching their order to the numerical order of the parameters.\n\ne.g.\n```js\n{\n  operator: 'stringSubstitution',\n  string: 'My name is %1 (age %2)',\n  substitutions: [\n    'Steve Rogers',\n    {\n      operator: '-',\n      values: [2023, 1918],\n    },\n  ],\n}\n// =\u003e \"My name is Steve Rogers (age 106)\"\n\n{\n  operator: 'replace',\n  // Using $1, $2 instead of %1, %2 this time:\n  string: '$1 is actually $2 $3',\n  substitutionCharacter: \"$\",\n  substitutions: [\n    // Using the 'user' object from above (OBJECT_PROPERTIES operator)\n    {\n      operator: 'objectProperties',\n      property: 'user.alias',\n    },\n    {\n      operator: 'objectProperties',\n      property: 'user.firstName',\n    },\n    {\n      operator: 'objectProperties',\n      property: 'user.lastName',\n    },\n  ],\n}\n// =\u003e \"Spider-man is actually Peter Parker\"\n\n// Parameters can be repeated:\n{\n  operator: 'stringSubstitution',\n  string: 'A %1 says: \"%2 %2 %2\"',\n  substitutions: ['bird', 'Tweet!'],\n}\n// =\u003e 'A bird says: \"Tweet! Tweet! Tweet!\"'\n\n// Replacement tokens can be escaped using the standard \"\\\" escape character:\n{\n  operator: 'stringSubstitution',\n  string: 'The price of $1 is \\$5',\n  subChar: '$',\n  substitutions: [ 'a coffee', 'not used' ],\n}\n// =\u003e The price of a coffee is $5\n```\n\n`children` array: `[string, ...substitutions]`  \n(`trimWhiteSpace` and `substitutionCharacter` not available, since `substitutions` can be an arbitrary number of items)\n\ne.g.\n```js\n{\n  operator: 'replace',\n  children: ['I am %1 %2', 'Iron', 'Man'],\n}\n// =\u003e \"I am Iron Man\"\n```\n\n#### Named property replacement\n\nReplacement tokens can be indicated in the main string with a named value, using `{{\u003cname\u003e}}` syntax, e.g. `\"Your name is {{firstName}} {{lastName}} and your best friend is {{friends[0]}}\"`. Then the `substitutions` property should be an object with those property names:\n\n```js\n{\n  operator: 'stringSubstitution',\n  string: 'Your name is {{firstName}} {{lastName}} and your best friend is {{friends[0]}}',\n  substitutions: {\n    firstName: 'Steve',\n    lastName: \"Rogers\"\n    friends: [ \"Bucky Barnes\", \"Peggy Carter\" ]\n  }\n}\n// =\u003e \"Your name is Steve Rogers and your best friend is Bucky Barnes\"\n```\nNote the use of `nested.properties` as per [objectProperties](#object_properties).\n\nAdditionally, the substitutions can actually be provided directly in the associated `data` object and the operator will search for them there if not found in the `substitutions` property. This could be achieved simply by nesting a [`getData` node](#object_properties) inside the `substitutions` property, but because this is a very common scenario (the values provided will normally be dynamic based on application state), this shorthand is provided as a convenience.\n\n```js\n// With \"data\" object:\n{\n  info: { where: \"Spain\", what: \"plain\" }\n}\n\n// These two expressions are equivalent:\n{\n  operator: 'stringSubstitution',\n  string: 'The rain in {{where}} falls mainly on the {{what}}',\n  substitutions: {\n    where: { operator: \"getData\", property: \"info.where\" },\n    what: { operator: \"getData\", property: \"info.where\" }\n  }\n}\n// or\n{\n  operator: 'stringSubstitution',\n  string: 'The rain in {{info.where}} falls mainly on the {{info.what}}',\n}\n\n// =\u003e \"The rain in Spain falls mainly on the plain\"\n```\n\n\n\n#### Number mapping\n\nIf the replacement values are numbers, we can extend this functionality with a special `numberMapping` object, which allows for different replacements depending on the value, which is handy for pluralisation, for example.\n\nThe syntax for the `numberMapping` property is:\n```js\n  {\n    propertyName1: {\n      1: \"Output if value is {}\",\n      2: \"Output if value is 2\",\n      \"\u003e5\": \"Output if value is greater than 5\",\n      \"\u003c0\": \"Output if value is less than 0\"\n      \"other\": \"Fallback output if none of the others match: {} count\"\n      // {} is a replacement for the numerical value itself\n    },\n    propertyName2: { ...etc }\n  }\n```\nThe number map can have as few or as many match options as desired -- if no match is found (or if no `numberMapping` property at all), the number will be returned as-is.\n\ne.g.\n```js\n{\n  operator: 'stringSubstitution',\n  string: 'Hi {{name}}, we have {{count}} attending this event.',\n  values: {\n    name: \"Tatiana\",\n    count: {operator: \"getData\", property: \"numOfPeople\" }\n  },\n  numberMap: {\n    count: {\n      0: \"no one\",\n      1: \"just one person\",\n      \"\u003e10\": \"too many people\",\n      \"other\": \"{} people\"\n    }\n  }\n}\n// Output with varying values for \"numOfPeople\" passed into evaluation\n// \"data\" object:\n\n// { numOfPeople: 5 }\n// =\u003e \"Hi Tatiana, we have 5 people attending this event.\"\n\n// { numOfPeople: 0 }\n// =\u003e \"Hi Tatiana, we have no one attending this event.\"\n\n// { numOfPeople: 100 }\n// =\u003e \"Hi Tatiana, we have too many people attending this event.\"\n\n// { numOfPeople: 1 }\n// =\u003e \"Hi Tatiana, we have just one person attending this event.\n```\n\n**Note**: `children` array not available for named properties\n\n\n----\n\n### SPLIT\n\n*Split strings into arrays*\n\nAliases: `split`, `arraySplit`\n\n#### Properties\n\n- `value` (or `string`)\u003csup\u003e*\u003c/sup\u003e: (string) -- string to be split\n- `delimiter` (or `separator`): (string) -- substring to split `value` on (Default: `\" \"` (space)) \n- `trimWhiteSpace` (or `trimWhitespace`, `trim`): (boolean, default `true`) -- strips whitespace from the beginning or end of resulting substrings \n- `excludeTrailing` (or `removeTrailing`, `excludeTrailingDelimiter`): (boolean, default `true`) -- if `false`, if the input string ends with the delimiter, the last member of the output array will be an empty string.  \n  i.e. `this, that, another,` (delimiter `\",\"`) =\u003e `[\"this\", \"that\", \"another\", \"\"]`\n\nThe last two parameters (`timeWhiteSpace` and `excludeTrailing`) should *rarely* be needed to be changed from their default values.\n\ne.g.\n```js\n{\n  operator: 'split',\n  children: ['Alpha, Beta, Gamma, Delta', ','],\n}\n// =\u003e ['Alpha', 'Beta', 'Gamma', 'Delta']\n\n```\n\n`children` array: `[value, delimiter]`  \n(`trimWhiteSpace` and `excludeTrailing` not available, since array can only support one optional parameter)\n\n---\n\n### HTTP requests\n\nThe following three operators (`GET`, `POST`, `GraphQL`) make http requests, so require an http client. If using fig-tree in the browser, it will use the native `fetch()` method by default, *so no configuration is required*. However, if using in `node`, or you wish to use a different http client (if your project is already using [`axios`](https://www.npmjs.com/package/axios), say), you can specify it with the `httpClient` option.\n\nThe `httpClient` object is an abstraction around an http package in order to standardise the implementation for use in fig-tree. Two such \"wrappers\" are provided in the FigTree package, for:\n\n- [`axios`](https://www.npmjs.com/package/axios)\n- [`node-fetch`](https://www.npmjs.com/package/node-fetch) (same API as browser `fetch`, but for `node`)\n\nTo specify one of these for use, just pass the client directly to the `httpClient` option, like so:\n\n#### Axios\n\n```js\nimport axios from 'axios'\nimport { FigTreeEvaluator, AxiosClient } from 'fig-tree-evaluator'\n\nconst fig = new FigTreeEvaluator({\n  httpClient: AxiosClient(axios),\n  ...otherOptions\n})\n```\n\n#### Node-Fetch\n\n```js\nimport fetch from 'node-fetch'\nimport { FigTreeEvaluator, FetchClient } from 'fig-tree-evaluator'\n\nconst fig = new FigTreeEvaluator({\n  httpClient: FetchClient(fetch),\n  ...otherOptions\n})\n```\n\nIf you wish to use a client other than these two, you must provide an function that takes the client as a parameter and returns an object of the following type structure:\n\n```ts\ninterface HttpClient {\n  get: (req: Omit\u003cHttpRequest, 'method'\u003e) =\u003e Promise\u003cunknown\u003e\n  post: (req: Omit\u003cHttpRequest, 'method'\u003e) =\u003e Promise\u003cunknown\u003e\n  throwError: (err: unknown) =\u003e void\n}\n\n// where HttpRequest is an input of the following shape:\ninterface HttpRequest {\n  url: string\n  params?: { [key: string]: string }\n  data?: Record\u003cstring, unknown\u003e\n  headers?: Record\u003cstring, unknown\u003e\n  method?: 'get' | 'post'\n}\n```\n\nAnd then in FigTree, for example:\n\n```js\nimport { MyHttpWrapper } from './customWrappers'\nimport { someClient } from 'some-library'\n\nconst fig = new FigTreeEvaluator({\n  httpClient: MyHttpWrapper(someClient),\n  ...otherOptions\n})\n\n```\n\nSee the implementation for `axios` and `node-fetch` [in the repo](https://github.com/CarlosNZ/fig-tree-evaluator/blob/main/src/httpClients.ts) for specific details.\n\n\n### GET\n\n*Http GET request*\n\nAliases: `get`, `api`\n\n*Note: if used in `node`, or you're using an http client other than `fetch`, you will need to explicitly provide an `httpClient` option. [See details](#http-requests)*\n\n#### Properties\n\n- `url` (or `endpoint`)\u003csup\u003e*\u003c/sup\u003e: (string) -- url to be queried\n- `parameters` (or `queryParams`, `queryParameters`, `urlQueries`): (object) -- key-value pairs for any query parameters for the request\n- `headers`: (object) -- any additional headers (such as authentication) required for the request\n- `returnProperty` (or `outputProperty`): (string) -- an object path for which property to extract from the returned data. E.g. if the API returns `{name: {first: \"Bruce\", last: \"Banner\"}, age: 35}` and you specify `returnProperty: \"name.first`, the operator will return `\"Bruce\"` (Uses the same logic as the [objectProperties](#object_properties) internally)\n\nAs mentioned in the [options reference](#available-options) above, a `baseEndpoint` string and `headers` object can be provided in the constructor. These are applied to all subsequent requests to save having to specify them in every evaluation. (Additional/override `headers` can always be added to a specific evaluation, too.)\n\ne.g.\n```js\n{\n  operator: 'GET',\n  url: 'https://restcountries.com/v3.1/name/zealand',\n  returnProperty: 'name.common',\n  outputType: 'string' // This extracts the string from the returned array value\n}\n// =\u003e \"New Zealand\"\n\n{\n  operator: 'get',\n  endpoint: {\n    operator: '+',\n    values: ['https://restcountries.com/v3.1/name/', 'india'],\n  },\n  parameters: { fullText: true },\n  outputProperty: '[0].name.nativeName.hin',\n}\n// =\u003e { \"official\": \"भारत गणराज्य\", \"common\": \"भारत\" }\n\n```\n\n`children` array: `[urlObject, parameterKeys, ...values, returnProperty]`\n\n- `urlObject`: either a url string, or an object structured as `{url: \u003cstring\u003e, headers: \u003cobject\u003e}` (if additional headers are required)\n- `parameterKeys`: an array of strings representing the keys of any query parameters (or just a single string if only one)\n- `...values`: one value for each key specified in `parameterKeys`\n- `returnProperty` (optional): as above\n\ne.g.\n```js\n{\n  operator: 'get',\n  children: [\n    'https://restcountries.com/v3.1/name/cuba', // url\n    ['fullText', 'fields'], // parameterKeys\n    'true', // parameter value 1\n    'name,capital,flag', // parameter value 2\n    'flag', // returnProperty\n  ],\n  outputType: 'string',\n}\n// =\u003e \"🇨🇺\"\n```\n\n----\n### POST\n\n*Http POST request*\n\nAliases: `post`\n\nThe \"POST\" operator is basically structurally the same as [GET](#get).\n\n*Note: if used in `node`, or you're using an http client other than `fetch`, you will need to explicitly provide an `httpClient` option. [See details](#http-requests)*\n\n#### Properties\n\n- `url`/`endpoint`\u003csup\u003e*\u003c/sup\u003e, `headers`, `returnProperty`/`outputProperty` -- same as \"GET\" operator.\n- `parameters` (or `bodyJson`, `data`) -- passed to the Post request as body JSON rather than url query parameters (hence the different aliases)\n\ne.g.\n```js\n{\n  operator: \"post\",\n  endpoint: \"https://jsonplaceholder.typicode.com/posts\",\n  parameters: {\n    title: \"New Blog Post\",\n    body: \"Just a short note...\",\n    userId: 2\n  },\n  returnProperty: \"id\"\n}\n// =\u003e 101\n\n```\n\n`children` array: `[urlObject, parameterKeys, ...values, returnProperty]` (same as \"GET\")\n\n----\n\n### GRAPHQL\n\n*Http GraphQL request (using POST)*\n\nAliases: `graphQL`, `graphQl`, `graphql`, `gql`\n\nThis operator is essentially a special case of the \"POST\" operator, but structured specifically for [GraphQL](https://graphql.org/) requests.\n\n*Note: if used in `node`, or you're using an http client other than `fetch`, you will need to explicitly provide an `httpClient` option. [See details](#http-requests)*\n\n#### Properties\n\n- `query`\u003csup\u003e*\u003c/sup\u003e: (string) -- the GraphQL query string\n- `variables`: (object) -- key-value pairs for any variables used in the `query`\n- `url` (or `endpoint`): (string) -- url to be queried (Only required if querying a different url to that specified in the GraphQLConnection object in fig-tree `options`)\n- `headers`: (object) -- any additional headers (such as authentication) required for the request\n- `returnNode` (or `returnProperty`, `outputProperty`): (string) -- an object path for which property to extract from the returned data (same as \"GET\" and \"POST\").\n\nAs mentioned in the [options reference](#available-options) above, a `headers` object can be provided in the constructor. These are applied to all subsequent requests to save having to specify them in every evaluation, although additional/override `headers` can always be added to a specific evaluation, too.\n\nOften, GraphQL queries will be to a single endpoint and only the query/variables will differ. In that case, it is recommended to pass a GraphQL connection object into the FigTreeEvaluator constructor [options](#available-options).\n\nThe required connection object is:\n```ts\n{\n  endpoint: string // url\n  headers?: { [key: string]: string } // key-value pairs\n}\n```\n\nThe following example expression uses the GraphQL connection (specified in constructor options): `{endpoint: 'https://countries.trevorblades.com/'}`\n```js\n{\n  operator: 'graphQL',\n  query: `query getCountry($code: String!) {\n      countries(filter: {code: {eq: $code}}) {\n        name\n        emoji\n      }\n    }`,\n  variables: { code: 'NZ' },\n  returnNode: 'countries[0]',\n}\n// =\u003e { \"name\": \"New Zealand\", \"emoji\": \"🇳🇿\" }\n\n```\n\n`children` array: `[query, endpoint, variableKeys, ...variableValues, returnNode]`\n\n- `query`: the GraphQL query (string)\n- `endpoint`: url string; to use the endpoint provided in the GraphQL connection options, pass empty string `\"\"` here\n- `variableKeys`: an array of strings representing the keys the GraphQL `variables` object\n- `...variableValues`: one value for each key specified in `variableKeys`\n- `returnNode` (optional): the return property, as per \"GET\" and \"POST\" operators\n\ne.g.\n```js\n{\n  operator: 'GraphQL',\n  children: [\n    `query getCountry($code: String!) {\n      countries(filter: {code: {eq: $code}}) {\n        name\n        emoji\n      }\n    }`,\n    \"\", // default endpoint\n    ['code'], // variable keys\n    'NZ', // variable value\n    'countries.emoji', // return node\n  ],\n  type: 'string',\n}\n// =\u003e \"🇨🇺\"\n```\n\n----\n### SQL\n\n*Query an SQL database*\n\nAliases: `sql`, `pgSql`, `postgres`, `pg`, `sqlLite`, `sqlite`, `mySql`\n\n#### Properties\n\n- `query`\u003csup\u003e*\u003c/sup\u003e: (string) -- SQL query string, with parameterised replacements (i.e. `$1`, `$2`, etc)\n- `values` (or `replacements`): (array / object) -- replacements for the `query` parameters\n- `single` (or `singleRecord`): (boolean) -- by default, results are returned as an array of objects. However, if your query is expected to just return a single record, you can set `single: true` and just the record object will be returned (i.e. not in an array). Note that if the query *does* fetch multiple records, only the first will be returned.\n- `flatten` (or `flat`): (boolean) -- Instead of returning an object, `flatten: true` will just return an array of values. e.g, instead of `{name: \"Tom\", age: 49}`, it will return `[\"Tom\", 49]`. This would usually be used in conjunction with the `single` property -- if not, it will return an array of flattened arrays.\n\n#### Examples\n\nThe following additional examples query a default installation of the [Northwind](https://github.com/pthom/northwind_psql) demo database.\n\n```js\n{\n  operator: 'sql',\n  query: \"SELECT contact_name FROM customers where customer_id = 'FAMIA';\",\n  single: true,\n  flatten: true\n}\n// =\u003e \"Aria Cruz\"\n\n{\n  operator: 'sql',\n  query: `SELECT product_name FROM public.products\n    WHERE category_id = $1 AND supplier_id != $2`,\n  values: [1, 16],\n  flatten: true,\n}\n// =\u003e [\"Chai\",\"Chang\",\"Guaraná Fantástica\",\"Côte de Blaye\",\"Chartreuse verte\",\n//     \"Ipoh Coffee\",\"Outback Lager\",\"Rhönbräu Klosterbier\",\"Lakkalikööri\"]\n\n{\n  operator: 'sql',\n  query: 'SELECT COUNT(*) FROM employees',\n  flatten: true,\n  type: 'number',\n}\n// =\u003e 9\n\n```\n\n`children` array: `[queryString, ...substitutions]`\n\n(`single` and `flatten` can not be part of `children` array)\n\n#### Connecting to the database\n\nIn order to query the SQL database, fig-tree must be provided with a database connection object in its `sqlConnection` option. An `SQLConnection` is an abstraction around a specific database connection in order to standardise the implementation for use in fig-tree. Two such \"wrappers\" are provided in the FigTree package for the following database connections:\n\n- **PostgreSQL** using [`node-postgres`](https://node-postgres.com/): `SQLNodePostgres` wrapper\n- **SQLite** using[`sqlite`/`sqlite3`](https://www.npmjs.com/package/sqlite): `SQLite` wrapper\n\nYou will need to have the appropriate package installed separately, and they can be implemented as follows:\n\n##### PostgreSQL\n\n```js\nimport { Client } from 'pg' // node-postgres\nimport { FigTreeEvaluator, SQLNodePostgres } from 'fig-tree-evaluator'\n\nconst pgConfig = {\n  // database config, see node-postgres documentation\n  user: 'postgres',\n  host: 'localhost',\n  database: 'northwind',\n  port: 5432,\n  ...etc\n}\n\nconst pgConnect = new Client(pgConfig)\n\npgConnect.connect()\n\nconst fig = new FigTreeEvaluator({\n  sqlConnection: SQLNodePostgres(pgConnect),\n  ...otherOptions\n})\n\nfig.evaluate({\n  operator: \"SQL\",\n  query: \"SELECT contact_name FROM customers where customer_id = 'FAMIA';\",\n  single: true,\n  flatten: true\n})\n.then((result) =\u003e console.log(result)) // =\u003e \"Aria Cruz\"\n```\n\n##### SQLite\n\n\n```js\nimport sqlite3 from 'sqlite3'\nimport { open, Database } from 'sqlite'\nimport { FigTreeEvaluator, SQLite } from 'fig-tree-evaluator'\n\nopen({\n    filename: '/path/to/sqlite.db',\n    driver: sqlite3.Database\n  }).then((db) =\u003e {\n    const fig = new FigTreeEvaluator({\n      sqlConnection: SQLite(db),\n      ...otherOptions\n    })\n\n    fig.evaluate(...) // Continue app operations\n})\n```\n\nTo create additional abstractions for other database connections, you need to provide a function that takes the library's connection object as a parameter and returns an object with a `.query()` method with the following type structure:\n\n```ts\ninterface SQLConnection {\n  query: (input: QueryInput) =\u003e Promise\u003cQueryOutput\u003e\n}\n\n// where `QueryInput` is:\ninterface QueryInput {\n  query: string\n  values?: (string | number | boolean)[] | object\n  single?: boolean\n  flatten?: boolean\n}\n\n// and `QueryOutput` is any FigTree output\n```\n\nYou then implement in FigTree options the same way as the two described above.\n\nCheck out `SQLNodePostgres` and `SQLite` [in the repo](https://github.com/CarlosNZ/fig-tree-evaluator/blob/main/src/databaseConnections.ts) for specific details.\n\n----\n### BUILD_OBJECT\n\n*Return an object constructed by separate keys and values*\n\nAliases: `buildObject`, `build`, `object`\n\nThe \"buildObject\" operator would primarily be used to construct an object input for another operator property (e.g. `variables` on \"GraphQL\") out of elements that are themselves evaluator expressions.\n\n#### Properties\n\n- `properties` (or `values`, `keyValPairs`, `keyValuePairs`)\u003csup\u003e*\u003c/sup\u003e: (array) -- array of either:\n  -  objects of the following shape:  \n    ```ts\n    {\n      key: string\n      value: any\n    }\n    ```\n  - key/value pairs in sequence, e.g. `[ \"key1\", \"value1\", \"key2\", \"value2\", ... ]`\n\n  Each object or pair of elements provides one key-value pair in the output object\n\ne.g.\n```js\n{\n  operator: 'buildObject',\n  properties: [\n    { key: 'one', value: 1 },\n    { key: 'two', value: 2 },\n    {\n      // Using \"user\" object from earlier\n      key: { operator: 'objectProperties', property: 'user.friends[0]' },\n      value: { operator: '+', values: [7, 8, 9] },\n    },\n  ],\n}\n// =\u003e { one: 1, two: 2, Ned: 24 }\n\n// OR, this is equivalent...\n\n{\n  operator: 'buildObject',\n  properties: [\n    \"one\", 1, \"two\", 2,\n    { operator: 'objectProperties', property: 'user.friends[0]' },\n    { operator: '+', values: [7, 8, 9] }\n  ]\n}\n// =\u003e { one: 1, two: 2, Ned: 24 }\n\n```\n\n`children`: `[...properties]` (same as properties array above)\n\n----\n\n### MATCH\n\n*Return different values depending on a matching expression*\n\nAliases: `match`, `switch`\n\nThe \"match\" operator is equivalent to a \"switch\"/\"case\" in Javascript. It is similar to the [\"conditional\"](#conditional) operator, but can handle matching to any number of values, not just `true`/`false`. This provides a way to construct elaborate [**decision trees**](https://en.wikipedia.org/wiki/Decision_tree).\n\n#### Properties\n\n- `matchExpression` (or `matchValue`)\u003csup\u003e*\u003c/sup\u003e: (string | number | boolean) -- a node that returns a value to be compared against possible cases.\n- `branches` (or `arms` or `cases`): (object) -- an object whose *keys* are compared against the `matchExpression`. The *value* of the matching key is returned.\n- `...branches` -- as an alternative to the `branches` object, matching key/values can be placed at the root of the node (see example)\n\ne.g.\n```js\n// Simple decision tree\n{\n  operator: 'match',\n  matchExpression: {\n    operator: 'objectProperties',\n    property: 'weather',\n  },\n  branches: {\n    sunny: {\n      operator: 'match',\n      match: {\n        operator: 'objectProperties',\n        property: 'humidity',\n      },\n      cases: { high: 'NO', normal: 'YES' },\n    },\n    cloudy: 'YES',\n  },\n  rainy: {\n    operator: 'match',\n    match: {\n      operator: 'objectProperties',\n      property: 'wind',\n    },\n    branches: { strong: 'NO', weak: 'YES' },\n  },\n}\n// With:\n// data = { weather: \"sunny\", humidity: \"high\" } =\u003e \"NO\"\n// data = { weather: \"rainy\", wind: \"weak\" } =\u003e \"YES\"\n\n```\n\nThis expression could also be written as (with branch/case keys at the root level)\"\n```js\n{\n  operator: 'match',\n  matchExpression: {\n    operator: 'objectProperties',\n    property: 'weather',\n  },\n  sunny: {\n    operator: 'match',\n    match: {\n      operator: 'objectProperties',\n      property: 'humidity',\n    },\n    high: 'NO',\n    normal: 'YES',\n  },\n  cloudy: 'YES',\n  rainy: {\n    operator: 'match',\n    match: {\n      operator: 'objectProperties',\n      property: 'wind',\n    },\n    strong: 'NO',\n    weak: 'YES',\n  },\n}\n```\n\n`children` array: `[matchExpression, key1, value1, key2, value2, ...]`\n\nThe pairs of `key`/`value`s are constructed into the `branches` object, the same way the objects are built using `children` in the [\"buildObject\"](#build_object) operator.\n\nFor an example of a complex decision tree implementation, which includes [aliases](#alias-nodes), [fallbacks](#other-common-properties) and a range of operators, see the \"match\" test case file (`20_match.test.ts`).\n\n----\n### PASSTHRU\n\n*Pass-thru (does nothing)*\n\nAliases: `passThru`, `_`, `pass`, `ignore`, `coerce`, `convert`\n\nThis operator simply returns its input. Its purpose is to allow an additional type conversion (using `outputType`) before passing up to a parent node.\n\n#### Properties\n\n- `value` (or `_`, `data`)\u003csup\u003e*\u003c/sup\u003e: (any) -- the value that is returned\n\ne.g.\n```js\n{\n  operator: 'pass',\n  value: { operator: '+', values: [50, 0], type: 'string' },\n  outputType: 'array',\n}\n// =\u003e [\"500\"]\n```\n\n----\n\n## Custom Functions/Operators\n\nThere is one final operator, `CUSTOM_FUNCTIONS`, which opens the door to extending FigTree's functionality. You can either call this operator directly, or make your functions \"custom operators\" in their own right.\n\nBut first you need to define your functions in FigTree's [options](#available-options), for example:\n\n```js\nconst fig = new FigTreeEvaluator({\n  functions: {\n    double: (x) =\u003e x * 2,\n    getCurrentYear: () =\u003e new Date().toLocaleString('en', { year: 'numeric' }),\n    changeCase: ({string, toCase}) =\u003e toCase === \"upper\" ? \n      string.toUpperCase() : string.toLowerCase()\n    average: (...numbers) =\u003e (numbers.reduce(\n      (a, n) =\u003e a + n, 0)) / numbers.length,\n  },\n})\n```\n\n*You can also define functions with extended metadata -- see [Metadata](#metadata) for more on that.*\n\n### The CUSTOM_FUNCTIONS operator\n\nAliases: `customFunctions`, `customFunction`, `functions`, `function`, `runFunction`\n\n#### Properties\n\n- `functionPath` (or `functionsPath`, `functionName`, `funcPath`\u003csup\u003e*\u003c/sup\u003e): (string) -- name of the function in the  `options.functions` object\n- `args` (or `arguments`, `variables`): (array | any) -- input arguments for the function. If an array, will be passed in as multiple arguments.\n- `input`: Input argument if function only takes a single argument. Note that, if your function takes a single array as its argument, you'll need to pass it in the `input` property -- the `args` property will spread the array into multiple arguments.\n\nHere is the result of various expressions:\n```js\n{\n  operator: 'functions',\n  functionPath: 'double',\n  input: 50,\n}\n// =\u003e 100\n\n{\n  operator: 'stringSubstitution',\n  string: \"The average age of our students is %1\"\n  replacements: [\n    {\n      operator: 'function',\n      functionPath: 'average',\n      args: [18, 35, 27, 55, 17, 28],\n    }\n  ]\n}\n// =\u003e \"The average age of our students is 30\"\n\n{\n  operator: '+',\n  values: [\n    {\n      operator: 'customFunctions',\n      functionPath: 'changeCase',\n      args: ['The current year is: ', 'upper'],\n    },\n    {\n      operator: 'customFunctions',\n      functionPath: 'getCurrentYear',\n    },\n  ],\n}\n// =\u003e \"THE CURRENT YEAR IS: 2023\"\n```\n\n`children` array: `[functionPath, ...args]`\n\n### Custom Operators\n\nYour expressions can actually refer to functions directly in the `operator` property, effectively allowing you to create any number of **custom operators**.\n\nConverting the above examples to this format:\n\n```js\n{\n  operator: 'double',\n  input: 50,\n}\n// =\u003e 100\n\n{\n  operator: 'stringSubstitution',\n  string: \"The average age of our students is %1\"\n  replacements: [\n    {\n      operator: 'average',\n      args: [18, 35, 27, 55, 17, 28],\n    }\n  ]\n}\n// =\u003e \"The average age of our students is 30\"\n\n{\n  operator: '+',\n  values: [\n    {\n      // Notice the properties of a single input object can be\n      // passed as properties on the main Operator node\n      operator: 'changeCase',\n      string: 'The current year is: ',\n      toCase: 'upper'\n    },\n    { operator: 'getCurrentYear' },\n  ],\n}\n// =\u003e \"THE CURRENT YEAR IS: 2023\"\n```\n\nThese custom operators even support the [shorthand syntax](#shorthand-syntax) -- they should behave just like standard operators.\n\n\n## Alias Nodes\n\nIf you have a node that is used more than once in a complex expression, it's possible to just evaluate the repeated node once, and refer to it throughout using an \"alias\" reference. This allows for a simpler expression (reduces code duplication) as well as a performance improvement, since the aliased node is only evaluated once, providing a simple [memoization](https://en.wikipedia.org/wiki/Memoization) mechanism. (See also [Caching/Memoization](#caching-memoization))\n\nFor example, if you have the expression:\n```js\n{\n  operator: \"?\",\n  condition: {\n    operator: \"!=\",\n    values: [\n      {\n        operator: \"GET\",\n        children: [\n          \"https://restcountries.com/v3.1/name/zealand\",\n          [],\n          \"name.common\"\n        ],\n        type: \"string\"\n      },\n      null\n    ]\n  },\n  valueIfTrue: {\n    operator: \"GET\",\n    children: [\n      \"https://restcountries.com/v3.1/name/zealand\",\n      [],\n      \"name.common\"\n    ],\n    type: \"string\"\n  },\n  valueIfFalse: \"Not New Zealand\"\n}\n```\n\nThe `GET` operation is used twice -- once to compare it for non-equality with `null`, and once again to return its value if `true`. This is particularly wasteful since it is a network request.\n\nWe can create an alias for this whole node, resulting in this equivalent expression:\n```js\n{\n  $getCountry: {\n        operator: \"GET\",\n        children: [\n          \"https://restcountries.com/v3.1/name/zealand\",\n          [],\n          \"name.common\"\n        ],\n        type: \"string\"\n      },\n  operator: \"?\",\n  condition: {\n    operator: \"!=\",\n    values: [\n     \"$getCountry\",\n      null\n    ]\n  },\n  valueIfTrue: \"$getCountry\",\n  valueIfFalse: \"Not New Zealand\"\n}\n```\n\nAlias nodes are defined as part of the expression object, using `$` prefix to identify them as such, in this case `$getCountry`, which returns the result of the network request that was called twice in the previous expression.\n\nAlias nodes are evaluated first, then the results are substituted in whenever they are referenced elsewhere.\n\nLike all expression nodes, alias nodes can themselves contain complex expressions with their own alias nodes defined within. As long as the alias references are descendent nodes of the alias definition, they will be resolved.\n\n(If an alias reference does not having a matching definition, the reference value will just be returned as a literal string, e.g. `\"$getCountry\"`)\n\n## Fragments\n\nYou may find that the expressions you are building for your configuration files often use very similar sub-expressions (but perhaps with only some input values that differ). For example, your expressions might be regularly looking up a \"countries\" database and fetching the capital city, such as:\n```js\n{\n  operator: 'GET',\n  url: {\n    operator: 'stringSubstitution',\n    string: 'https://restcountries.com/v3.1/name/%1',\n    replacements: ['New Zealand'],\n  },\n  returnProperty: '[0].capital',\n  outputType: 'string',\n}\n// =\u003e \"Wellington\"\n```\n\nIn this case, you can pre-define the main part of the expression as part of an expression \"**fragment**\" in the evaluator [options](#available-options).\n\nThe idea is that you can \"hard-code\" some common expressions into your app, so that external configuration expressions can be simpler when using these common elements.\n\nThe syntax is similar to [Alias Nodes](#alias-nodes), in that any string values prefixed with `$` will be treated as \"parameters\" that will be replaced during evaluation. In the above example, you would define the fragment when instantiating the evaluator like so:\n\n```js\nconst fig = new FigTreeEvaluator({\n  fragments: {\n    getCapital: {\n      operator: 'GET',\n      url: {\n        operator: 'stringSubstitution',\n        string: 'https://restcountries.com/v3.1/name/%1',\n        replacements: [ \"$country\" ],\n      },\n      returnProperty: '[0].capital',\n      outputType: 'string',\n      metadata: {\n        // Not required, but useful for external consumers -- see Metadata below\n        description: \"Fetches the capital city of a country\",\n        parameters: { $country: { type: 'string', required: true } },\n      }\n    },\n  },\n})\n```\n\nThen any subsequent expressions can use this fragment by specifying a special \"Fragment Node\", which contains the `fragment` and (optionally) `parameters` fields:\n```js\n{\n  fragment: \"getCapital\",\n  parameters: {\n    $country: { operator: \"getData\", property: \"path.to.country.name\" }\n  }\n}\n```\n\nLike the `branches` field in the [\"Match\" operator](#match), the properties of the `parameters` field can be specified at the root level as well -- it just depends on whichever is most appropriate for your use case. So the following is equivalent to the previous fragment node:\n```js\n{\n  fragment: \"getCapital\",\n  $country: { operator: \"getData\", property: \"path.to.country.name\" }\n}\n```\n\nSee `22_fragments.test.ts` for more complex examples.\n\nUnlike Alias Nodes, which are evaluated *once* and then the result re-used whenever the alias is referenced, Fragments are evaluated every time, as the input parameters may differ.\n\n## Shorthand syntax\n\nIt's possible to express FigTree expressions in a more compact syntax, as follows:\n\n- Fragment and Operator nodes can be represented by putting the name of the fragment or operator as a property name (prefixed by `$`), then putting the parameters in an object as the property value. For example:  \n  ```js\n  {\n    operator: 'regex',\n    string: \"home@myplace.com\",\n    pattern: '^[A-Za-z0-9.]+@[A-Za-z0-9]+\\\\.[A-Za-z0-9.]+$' \n  }\n  ```\n  can be written as:\n  ```js\n  {\n    $regex:\n      {\n        string: 'home@myplace.com',\n        pattern: '^[A-Za-z0-9.]+@[A-Za-z0-9]+\\\\.[A-Za-z0-9.]+$'\n      },\n  }\n  ```\n\n- For operator nodes (not fragments, as they always require named parameters), parameters can be represented positionally (equivalent to the `children` property in normal operator nodes) in an array. The above example could also be expressed as:  \n  ```js\n  {\n    $regex: [ 'home@myplace.com', '^[A-Za-z0-9.]+@[A-Za-z0-9]+\\\\.[A-Za-z0-9.]+$' ]\n  }\n  ```\n\n- Operator nodes with a single parameter can just be placed directly as the single property value. For example:  \n  ```js\n  {\n    operator: \"getData\",\n    property: \"user.firstName\"\n  }\n  ```\n  can become, simply:\n  ```js\n  { $getData: \"user.firstName\" }\n  ```\n\nFor more examples, see `23_shorthand.test.ts`, or have a play with the [demo app](https://carlosnz.github.io/fig-tree-evaluator/) app.\n\n\n## Caching (Memoization)\n\nFigTree Evaluator has basic [memoization](https://en.wikipedia.org/wiki/Memoization) functionality for certain nodes to speed up re-evaluation when the input parameters haven't changed. There is a single cache store per FigTree instance which persists for the lifetime of the instance. By default, it remembers the last 50 results with each result being valid for half and hour, but these values can be modified using the `maxCacheSize` and `maxCacheTime` [options](#available-options).\n\nCurrently, caching is only implemented for the following operators, since they perform requests to external resources, which are inherently slow:\n\n- GET (`useCache` default: `true`)\n- POST (`useCache` default: `false`) \n- PG_SQL (`useCache` default: `true`)\n- GRAPH_QL (`useCache` default: `true`)\n- CUSTOM_FUNCTIONS (`useCache` default: `false`)\n\nThis is different to the memoization provided by [Alias Nodes](#alias-nodes):\n- Alias nodes are still evaluated once for every evaluation -- they're more for re-use *within* a complex expression.\n- Cached nodes will persist *between* different evaluations as long as the input values are the same as a previously evaluated node.\n\nCaching is enabled by default for most of the above operators, but this can be overridden by setting `useCache: false` in [options](#available-options), either globally or per expression. If you're querying a database or API that is likely to have a different result for the same request (i.e. data has changed), then you probably want to turn the cache off.\n\nIt's possible to manually set/get the cache store, which can be used (for example) to save the cache in local storage to persist it between page reloads or new FigTree instances.\n\nUse the methods:\n\n```js\nfig.getCache() // returns key-value store\n\nfig.setCache(cache) // where \"cache\" is the object retrieved by .getCache()\n```\n\n## Error handling\n\nBy default, FigTree will throw errors that can be caught and handled by your application. The Error object is a `FigTreeError` type, which extends the standard `Error` object (with `name`, `message` and `stack` fields) with the following interface:\n\n```ts\ninterface FigTreeError extends Error {\n  errorData?: Record\u003cstring, unknown\u003e\n  operator?: Operator\n  expression?: EvaluatorNode\n  prettyPrint: string // nicely formatted summary\n}\n```\n\nThere are two alternatives to throwing:\n\n1. **Fallback**: If the `fallback` property is specified in the expression, this will be returned whenever an error occurs at that node (or below). See [Fallback option](#other-common-properties).\n2. **Formatted string**: By using the `returnErrorAsString: true` option, FigTree will return a nicely formatted string describing the error. *This is the same string returned in the `prettyPrint` property of the FigTreeError.* The formatted string will be structured like so:\n\n```\nOperator: \u003cOPERATOR_NAME\u003e: - \u003cerror.name (if specific)\u003e\n\u003cerror.message\u003e\n{\n  ...errorData\n}\n```\n`errorData` is specific data returned by the error thrown by an internal process (such as a network request using [fetch](#http-requests)).\n\nFor example, a 403 (forbidden) error thrown by the `GET` operator (with Axios HTTP client) would return a string like:\n```\nOperator: GET - AxiosError\nRequest failed with status code 403\n{\n  \"status\": 403,\n  \"error\": \"Forbidden\",\n  \"url\": \"http://httpstat.us/403\",\n  \"response\": {\n    \"success\": false,\n    \"message\": \"jwt must be provided\"\n  }\n}\n```\n\nAnd, if thrown, the error object would contain:\n```js\n{\n  name: \"AxiosError\",\n  message: \"Request failed with status code 403\"\n  operator: \"GET\",\n  errorData: {\n      status: 403,\n      error: 'Forbidden',\n      url: 'http://httpstat.us/403',\n      response: {\n        success: false,\n        message: \"jwt must be provided\"\n      }\n    }\n  expression: {\n      operator: 'API',\n      url: 'http://httpstat.us/403',\n    },\n  prettyPrint: '[...the above formatted string...]'\n    ...otherProperties // all AxiosError and standard Error properties\n}\n```\n\nIt may seem that returning the error as a string results in the same output as catching the error and displaying `error.prettyPrint`. There is one key difference, however: if the error occurs deeper in the expression tree, a thrown error will be bubbled up immediately, whereas if it's returned as a string (or a fallback, for that matter), it'll be treated as any other string, which means it will be continued to be operated on as the expression is evaluated (you could concatenate multiple error strings together in this manner, for example).\n\nIf extending FigTree with additional [HTTP Client](#http-requests), [SQL Connection](#connecting-to-the-database) or [Custom functions](#custom_functions), you don't need to specifically throw a `FigTreeError` -- just throw a normal error and FigTree will encapsulate it into a FigTreeError object further upstream. (The one exception is for detailed `errorData`, if you want it -- attach this as a property to your error object before throwing it.) See the [included http clients](https://github.com/CarlosNZ/fig-tree-evaluator/blob/main/src/httpClients.ts) for an example.\n\n## Metadata\n\nEvaluator expressions can be configured by hand, with [aliases](#alias-nodes), [fragments](#fragments) and [shorthand](#shorthand-syntax) available to make the job easier.\n\nHowever, you may wish to use an external UI (such as [this one](https://carlosnz.github.io/fig-tree-evaluator/) for React) for building FigTree expression (or create your own). To this end, the FigTree instance provides three methods that could be useful for populating the configuration UI:\n\n#### A new FigTree instance:\n\n- containing fragments and [custom functions](#custom_functions).\n\n```js\nconst fig = new FigTreeEvaluator({\n  fragments: {\n    getCapital: {\n      operator: 'GET',\n      url: {\n        operator: 'stringSubstitution',\n        string: 'https://restcountries.com/v3.1/name/%1',\n        replacements: [ \"$country\" ],\n      },\n      returnProperty: '[0].capital',\n      outputType: 'string',\n      // Metadata used by getFragments() (below)\n      metadata: {\n        description: \"Fetches the capital city of a country\",\n        parameters: { $country: { type: 'string', required: true } },\n        textColor: \"black\"\n        backgroundColor: \"orange\"\n}\n      }\n    },\n    ... // More fragments\n  },\n  functions: {\n    doubleArray: {\n      function: (...args) =\u003e args.map((e) =\u003e e + e),\n      description: \"Double each item in an array\",\n      argsDefault: [1, 2, 3, 4]\n    },\n    changeCase: {\n      function: ({string, toCase}) =\u003e toCase === \"upper\" ? \n        string.toUpperCase() : string.toLowerCase(),\n      description: \"Convert a string to either upper or lower case\",\n      inputDefault: {string: \"New string\", toCase: \"upper\"}\n    }\n     // ...More functions\n  }\n  //... More options\n)\n```\n\n#### Retrieve Operator info\n\n```js\nfig.getOperators()\n```\n\nThis will return an array of operators with detailed info about their [aliases](#operator--property-aliases), and parameter requirements:\n\n```js\n[\n  ...\n  {\n    name: 'PLUS',\n    description: 'Add, concatenate or merge multiple values',\n    aliases: ['+', 'plus', 'add', 'concat', 'join', 'merge'],\n    parameters: [\n      {\n        name: 'values',\n        description: 'Array of values to check to add together',\n        aliases: [],\n        required: true,\n        type: 'array',\n      },\n      {\n        name: 'type',\n        description: 'Data type to coerce input values to before addition',\n        aliases: [],\n        required: false,\n        type: 'string',\n      },\n    ],\n  },\n  ... // More operators\n]\n```\n\n#### Retrieve Fragment info\n\nBecause Fragments are defined within the FigTree instance, optional metadata can be provided to make working with these fragments easier in a configuration UI:\n\n```js\nfig.getFragments()\n```\n\nThis will return something like:\n\n```js\n[\n  {\n    name: 'getCapital',\n    description: 'Fetches the capital city of a country',\n    parameters: { $country: { type: 'string', required: true } },\n    textColor: \"black\"\n    backgroundColor: \"orange\"\n  },\n  { name: 'simpleFragment' }, // No metadata provided\n  ... // More fragments\n]\n```\n\n#### Retrieve customFunction info\n\nSimilarly, we can fetch basic info about custom functions in the current FigTree instance, although with more limited detail:\n```js\nfig.getCustomFunctions()\n```\n\nReturns:\n\n```js\n[\n  {\n    name: 'doubleArray',\n    numRequiredArgs: 1,\n    description: 'Double each item in an array',\n    argsDefault: [ 1, 2, 3, 4 ]\n  },\n  {\n    name: 'changeCase',\n    numRequiredArgs: 1,\n    description: 'Convert a string to either upper or lower case',\n    inputDefault: { string: 'New string', toCase: 'upper' }\n  }\n]\n```\n\n## More examples\n\nMore examples, included large, complex expressions can be found within the test suites in the [repository](https://github.com/CarlosNZ/fig-tree-evaluator).\n\n## Development environment\n\nGithub repo: https://github.com/CarlosNZ/fig-tree-evaluator\n\nAfter cloning:\n\n`yarn setup` -- installs required dependencies for both the main module and the demo app (runs `yarn install` within each)\n\n`yarn demo` -- launch a local version of the demo playground in your browser for building and testing expressions\n\n`yarn dev` -- execute anything in `/dev/playground.ts`. Use for testing/debugging.\n\n## Tests\n\nThere is a comprehensive [Jest](https://jestjs.io/) test suite for all aspects of fig-tree. To run all tests:\n\n`yarn test`\n\nIn order for the http-based tests to run, you'll need to be connected to the internet. For the Postgres tests, you'll need to have a postgres database running locally, with the [Northwind](https://github.com/pthom/northwind_psql) database installed.\n\nIndividual tests can be run by string matching the argument to the test filenames. E.g. `yarn test string` will run the test from `9_stringSubstitution.test.ts`.\n\n## Help, Feedback, Suggestions\n\nPlease open an issue: https://github.com/CarlosNZ/fig-tree-evaluator/issues\n\n## Changelog\n\n*Trivial upgrades (e.g. documentation, small re-factors, types, etc.) not included*\n\n\n- **v2.20.0**:\n  - Add helper scripts to convert V1 to V2 expressions, and to and from Shorthand syntax -- used in [FigTree Editor](https://github.com/CarlosNZ/fig-tree-editor-react) tool.\n  - Small tweaks to `buildObject` and `match` operators to make them a little more consistent in their behaviour.\n- **v2.19.0**: *Remove* \"string\" shorthand syntax (technically a breaking change, but I doubt anyone is affected by this) #124\n- **v2.18.0**: Prevent HTTP clients from being bundled with main package\n- **v2.17.0**: Allow Custom Functions to be expressed as Custom Operators\n- **v2.16.10**: Fix for when aliases reference other aliases at the same level\n- **v2.16.8**: Don't deep merge fragments, data, headers and functions when using `.updateOptions()`\n- **v2.16.5**: Make sure all parameters that are objects get pre-evaluated, even when \n`evaluateFullObject` is off.\n- **v2.16.0**: Standardise error response (see [Error handling](#error-handling))\n- **v2.15.0**:\n  - Remove `axios` package dependency and create HTTP client abstraction (with built-in wrappers for `axios` and `fetch`). *Results in significantly smaller bundle size.*\n  - Generalise `PG_SQL` operator to a client-agnostic `SQL` operator (with built-in abstractions for `node-postgres` and `SQLite`)\n  - ***Breaking changes*** as a result of the above: SQL client and HTTP client must be specified differently. See relevant operator details.\n  - Changes to `SQL` parameters to reflect the aforementioned agnosticism.\n- **v2.14.0**: Improvements to `stringSubstitution` operator:\n  - Can accept nested property references (e.g. `{{user.name}}`)\n  - Will also search for replacements from `data` object\n- **v2.13.4**: Bug fix for `objectProperties` operator when array index larger than `9` is used, and for sequential array indexes (e.g. `prop[1][2]`)\n- **v2.13.0**: Add default values to operator properties, export more types and helper methods\n- **v2.12.0**: Add `caseInsensitive` option to equality/non-equality operators\n- **v2.11.5**: Upgrade dependencies\n- **v2.11.4**: Bundle target ES6\n- **v2.11.0**: Improved package bundling (bundle size ~50%), with CommonJS and ESM outputs. Note: small **breaking change**: \"FigTreeEvaluator\" is no longer a default export, so need to import with: `import { FigTreeEvaluator } from 'fig-tree-evaluator'`\n- **v2.10.0**: Extended stringSubstitution functionality to included named\n  property substitution, trim whitespace option, and pluralisation (#97)\n- **v2.9.0**: Added ability to invalidate cache by time (#94)\n- **v2.8.6**: Small bug fix where `options` object would be mutated instead of replaced\n- **v2.8.5**: Small bug fix in [COUNT](#count) operator\n- **v2.8.4**: Refactor types, better compliance with [ESLint](https://eslint.org/) rules, add more tests\n- **v2.8.0**:\n  - **[Shorthand syntax](#shorthand-syntax)** (#80)\n  - **Methods to retrieve [metadata](#metadata)** about operators, fragments and\n    functions (#82)\n- **v2.7.0**: **Add `excludeOperators` option** to allow certain operators to be\n  prohibited (e.g. database lookups) (#54)\n- **v2.6.0**: Resolve alias nodes that are not part of an Operator node when `evaluateFullObject` is enabled (#78)\n- **v2.5.0**:\n  - Bug fixes for edge cases (mainly related to backwards compatibility)\n  - More backwards compatibility for very old (pre-v1) syntax (undocumented)\n- **v2.4.1**: Small bug fix for Fragments edge case\n- **v2.4.0**: **Implement [Fragments](#fragments)** (#74)\n- **v2.3.2**: Bug fix: alias nodes not working with `evaluateFullObject` (#72)\n- **v2.3.0**: **Implement [caching/memoization](#caching-memoization)** (#68)\n- **v2.2.3**: Change option `objects` name to `data` (but keep backward compatibility) (#66)\n- **v2.2.2**: **Option to evaluate whole object** if operator nodes are deep within it (#64)\n- **v2.2.1**: More efficient branch evaluation for condition/match operators (#63)\n- **v2.2.0**:\n  - **New \"[Match](#match)\" operator** (#61)\n  - Fix for regex incompatibility with Safari (#60)\n- **v2.1.4**: Upgrade dependencies\n- **v2.1.0**: **[Alias nodes](#alias-nodes)** (#57)\n- **v2.0.4**: Backwards compatibility for customFunctions (#53)\n- **v2.0.1**: **Add deep equality comparison** for objects/arrays in `=`/`!=` operators\n- **v2.0.0**: Re-write as stand-alone package. Major improvements include:\n  -  more [operators](#operator-reference)\n  -  operator (and property) [aliases](#operator--property-aliases)\n  -  more appropriately-named properties associated with each operator (as\n     opposed to a single `children` array)\n  -  class-based Evaluator instances\n  -  runtime type-checking\n  -  better error handling and error reporting\n  -  more flexible output conversion\n  -  more well-organised codebase\n- **v1.x.x**: created specifically for [Conforma](https://github.com/openmsupply/conforma-server/wiki/Query-Syntax) application manager by [mSupplyFoundation](https://github.com/openmsupply). v2 is a complete re-write with numerous improvements, but should be 99% backwards compatible.\n\n## Credit\nIcon: Tree by ka reemov from \u003ca href=\"https://thenounproject.com/icon/tree-2665898/\" target=\"_blank\" title=\"Tree Icon\"\u003eNoun Project\u003c/a\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarlosnz%2Ffig-tree-evaluator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcarlosnz%2Ffig-tree-evaluator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcarlosnz%2Ffig-tree-evaluator/lists"}