fig-tree-evaluator
Advanced tools
Comparing version 2.3.3 to 2.4.0
@@ -54,6 +54,6 @@ "use strict"; | ||
var evaluatorFunction = function (input, config) { return __awaiter(void 0, void 0, void 0, function () { | ||
var options, operators, operatorAliases, expression, _a, fallback, returnErrorAsString, operator, _b, _c, requiredProperties, propertyAliases, evaluate, parseChildren, _d, _e, validationError, _f, _g, _h, result, err_1, _j, outputType, evaluatedOutputType, _k, err_2, _l; | ||
var _m, _o; | ||
return __generator(this, function (_p) { | ||
switch (_p.label) { | ||
var options, operators, operatorAliases, expression, _a, fallback, returnErrorAsString, _b, fragment, parameters, fragmentReplacement, _c, operator, _d, _e, requiredProperties, propertyAliases, evaluate, parseChildren, _f, _g, validationError, _h, _j, _k, result, err_1, _l, outputType, evaluatedOutputType, _m, err_2, _o; | ||
var _p, _q, _r; | ||
return __generator(this, function (_s) { | ||
switch (_s.label) { | ||
case 0: | ||
@@ -65,88 +65,103 @@ options = config.options, operators = config.operators, operatorAliases = config.operatorAliases; | ||
case 1: | ||
expression = _p.sent(); | ||
_p.label = 2; | ||
expression = _s.sent(); | ||
_s.label = 2; | ||
case 2: | ||
if (!(options.evaluateFullObject && !(0, helpers_1.isOperatorNode)(expression))) return [3, 4]; | ||
if (!(options.evaluateFullObject && !(0, helpers_1.isOperatorNode)(expression) && !(0, helpers_1.isFragmentNode)(expression))) return [3, 4]; | ||
_a = helpers_1.replaceAliasNodeValues; | ||
return [4, (0, helpers_1.evaluateObject)(expression, config)]; | ||
case 3: return [2, _a.apply(void 0, [_p.sent(), config])]; | ||
case 3: return [2, _a.apply(void 0, [_s.sent(), config])]; | ||
case 4: | ||
if (!(0, helpers_1.isOperatorNode)(expression)) | ||
if (!(0, helpers_1.isOperatorNode)(expression) && !(0, helpers_1.isFragmentNode)(expression)) | ||
return [2, (0, helpers_1.replaceAliasNodeValues)(expression, config)]; | ||
fallback = expression.fallback; | ||
returnErrorAsString = (_m = options === null || options === void 0 ? void 0 : options.returnErrorAsString) !== null && _m !== void 0 ? _m : false; | ||
_p.label = 5; | ||
returnErrorAsString = (_p = options === null || options === void 0 ? void 0 : options.returnErrorAsString) !== null && _p !== void 0 ? _p : false; | ||
if (!(0, helpers_1.isFragmentNode)(expression)) return [3, 8]; | ||
return [4, (0, _operatorUtils_1.evaluateArray)([expression.fragment, expression.parameters], config)]; | ||
case 5: | ||
_p.trys.push([5, 26, , 28]); | ||
operator = (0, helpers_1.getOperatorName)(expression.operator, operatorAliases); | ||
if (!!operator) return [3, 7]; | ||
_b = helpers_1.fallbackOrError; | ||
_b = (_s.sent()), fragment = _b[0], parameters = _b[1]; | ||
fragmentReplacement = (_q = options === null || options === void 0 ? void 0 : options.fragments) === null || _q === void 0 ? void 0 : _q[fragment]; | ||
if (!!fragmentReplacement) return [3, 7]; | ||
_c = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 6: return [2, _b.apply(void 0, [_p.sent(), "Invalid operator: ".concat(expression.operator), returnErrorAsString])]; | ||
case 6: return [2, _c.apply(void 0, [_s.sent(), "Fragment not defined: ".concat(fragment), returnErrorAsString])]; | ||
case 7: | ||
_c = operators[operator], requiredProperties = _c.requiredProperties, propertyAliases = _c.propertyAliases, evaluate = _c.evaluate, parseChildren = _c.parseChildren; | ||
expression = (0, helpers_1.mapPropertyAliases)(propertyAliases, expression); | ||
_d = config; | ||
_e = [__assign({}, config.resolvedAliasNodes)]; | ||
return [4, (0, helpers_1.evaluateNodeAliases)(expression, config)]; | ||
if (!(0, helpers_1.isOperatorNode)(fragmentReplacement)) | ||
return [2, fragmentReplacement]; | ||
expression = __assign(__assign(__assign({}, expression), fragmentReplacement), parameters); | ||
delete expression.fragment; | ||
delete expression.parameters; | ||
_s.label = 8; | ||
case 8: | ||
_d.resolvedAliasNodes = __assign.apply(void 0, _e.concat([(_p.sent())])); | ||
validationError = (0, helpers_1.checkRequiredNodes)(requiredProperties, expression); | ||
if (!validationError) return [3, 10]; | ||
_f = helpers_1.fallbackOrError; | ||
_s.trys.push([8, 29, , 31]); | ||
operator = (0, helpers_1.getOperatorName)(expression.operator, operatorAliases); | ||
if (!!operator) return [3, 10]; | ||
_d = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 9: return [2, _f.apply(void 0, [_p.sent(), "Operator: ".concat(operator, "\n- ").concat(validationError), returnErrorAsString])]; | ||
case 9: return [2, _d.apply(void 0, [_s.sent(), "Invalid operator: ".concat(expression.operator), returnErrorAsString])]; | ||
case 10: | ||
if (!('children' in expression)) return [3, 16]; | ||
if (!!Array.isArray(expression.children)) return [3, 12]; | ||
_g = expression; | ||
return [4, (0, exports.evaluatorFunction)(expression.children, config)]; | ||
_e = operators[operator], requiredProperties = _e.requiredProperties, propertyAliases = _e.propertyAliases, evaluate = _e.evaluate, parseChildren = _e.parseChildren; | ||
expression = (0, helpers_1.mapPropertyAliases)(propertyAliases, expression); | ||
_f = config; | ||
_g = [__assign({}, config.resolvedAliasNodes)]; | ||
return [4, (0, helpers_1.evaluateNodeAliases)(expression, config)]; | ||
case 11: | ||
_g.children = _p.sent(); | ||
_p.label = 12; | ||
case 12: | ||
if (!!Array.isArray(expression.children)) return [3, 14]; | ||
_f.resolvedAliasNodes = __assign.apply(void 0, _g.concat([(_s.sent())])); | ||
validationError = (0, helpers_1.checkRequiredNodes)(requiredProperties, expression); | ||
if (!validationError) return [3, 13]; | ||
_h = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 13: return [2, _h.apply(void 0, [_p.sent(), "Operator: ".concat(operator, "\n- Property \"children\" is not of type: array"), returnErrorAsString])]; | ||
case 14: return [4, parseChildren(expression, config)]; | ||
case 12: return [2, _h.apply(void 0, [_s.sent(), "Operator: ".concat(operator, "\n- ").concat(validationError), returnErrorAsString])]; | ||
case 13: | ||
if (!('children' in expression)) return [3, 19]; | ||
if (!!Array.isArray(expression.children)) return [3, 15]; | ||
_j = expression; | ||
return [4, (0, exports.evaluatorFunction)(expression.children, config)]; | ||
case 14: | ||
_j.children = _s.sent(); | ||
_s.label = 15; | ||
case 15: | ||
expression = _p.sent(); | ||
_p.label = 16; | ||
case 16: | ||
result = void 0; | ||
_p.label = 17; | ||
case 17: | ||
_p.trys.push([17, 19, , 21]); | ||
return [4, evaluate(expression, config)]; | ||
if (!!Array.isArray(expression.children)) return [3, 17]; | ||
_k = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 16: return [2, _k.apply(void 0, [_s.sent(), "Operator: ".concat(operator, "\n- Property \"children\" is not of type: array"), returnErrorAsString])]; | ||
case 17: return [4, parseChildren(expression, config)]; | ||
case 18: | ||
result = _p.sent(); | ||
return [3, 21]; | ||
expression = _s.sent(); | ||
_s.label = 19; | ||
case 19: | ||
err_1 = _p.sent(); | ||
_j = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
result = void 0; | ||
_s.label = 20; | ||
case 20: | ||
result = _j.apply(void 0, [_p.sent(), "Operator: ".concat(operator, "\n").concat((0, helpers_1.errorMessage)(err_1)), returnErrorAsString]); | ||
return [3, 21]; | ||
_s.trys.push([20, 22, , 24]); | ||
return [4, evaluate(expression, config)]; | ||
case 21: | ||
outputType = (_o = expression === null || expression === void 0 ? void 0 : expression.outputType) !== null && _o !== void 0 ? _o : expression === null || expression === void 0 ? void 0 : expression.type; | ||
result = _s.sent(); | ||
return [3, 24]; | ||
case 22: | ||
err_1 = _s.sent(); | ||
_l = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 23: | ||
result = _l.apply(void 0, [_s.sent(), "Operator: ".concat(operator, "\n").concat((0, helpers_1.errorMessage)(err_1)), returnErrorAsString]); | ||
return [3, 24]; | ||
case 24: | ||
outputType = (_r = expression === null || expression === void 0 ? void 0 : expression.outputType) !== null && _r !== void 0 ? _r : expression === null || expression === void 0 ? void 0 : expression.type; | ||
if (!outputType) | ||
return [2, result]; | ||
return [4, (0, exports.evaluatorFunction)(outputType, config)]; | ||
case 22: | ||
evaluatedOutputType = (_p.sent()); | ||
if (!!(evaluatedOutputType in helpers_1.convertOutputMethods)) return [3, 24]; | ||
_k = helpers_1.fallbackOrError; | ||
case 25: | ||
evaluatedOutputType = (_s.sent()); | ||
if (!!(evaluatedOutputType in helpers_1.convertOutputMethods)) return [3, 27]; | ||
_m = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 23: return [2, _k.apply(void 0, [_p.sent(), "Operator: ".concat(operator, "\n- Invalid output type: ").concat(evaluatedOutputType), returnErrorAsString])]; | ||
case 24: return [2, helpers_1.convertOutputMethods[evaluatedOutputType](result)]; | ||
case 25: return [3, 28]; | ||
case 26: | ||
err_2 = _p.sent(); | ||
_l = helpers_1.fallbackOrError; | ||
case 26: return [2, _m.apply(void 0, [_s.sent(), "Operator: ".concat(operator, "\n- Invalid output type: ").concat(evaluatedOutputType), returnErrorAsString])]; | ||
case 27: return [2, helpers_1.convertOutputMethods[evaluatedOutputType](result)]; | ||
case 28: return [3, 31]; | ||
case 29: | ||
err_2 = _s.sent(); | ||
_o = helpers_1.fallbackOrError; | ||
return [4, (0, exports.evaluatorFunction)(fallback, config)]; | ||
case 27: return [2, _l.apply(void 0, [_p.sent(), (0, helpers_1.errorMessage)(err_2), | ||
case 30: return [2, _o.apply(void 0, [_s.sent(), (0, helpers_1.errorMessage)(err_2), | ||
returnErrorAsString])]; | ||
case 28: return [2]; | ||
case 31: return [2]; | ||
} | ||
@@ -153,0 +168,0 @@ }); |
import { OutputType, EvaluatorNode, CombinedOperatorNode, Operator, EvaluatorOutput, FigTreeOptions, OperatorNodeUnion, FigTreeConfig } from './types'; | ||
export declare const parseIfJson: (input: EvaluatorNode) => any; | ||
export declare const isOperatorNode: (input: EvaluatorNode) => boolean; | ||
export declare const isFragmentNode: (input: EvaluatorNode) => boolean; | ||
export declare const getOperatorName: (operator: string, operatorAliases: { | ||
@@ -5,0 +6,0 @@ [key: string]: "AND" | "OR" | "EQUAL" | "NOT_EQUAL" | "PLUS" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" | "GREATER_THAN" | "LESS_THAN" | "CONDITIONAL" | "REGEX" | "OBJECT_PROPERTIES" | "STRING_SUBSTITUTION" | "SPLIT" | "COUNT" | "GET" | "POST" | "PG_SQL" | "GRAPHQL" | "BUILD_OBJECT" | "MATCH" | "CUSTOM_FUNCTIONS" | "PASSTHRU"; |
@@ -50,3 +50,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.evaluateObject = exports.errorMessage = exports.convertOutputMethods = exports.mergeOptions = exports.checkRequiredNodes = exports.replaceAliasNodeValues = exports.evaluateNodeAliases = exports.mapPropertyAliases = exports.fallbackOrError = exports.truncateString = exports.getOperatorName = exports.isOperatorNode = exports.parseIfJson = void 0; | ||
exports.evaluateObject = exports.errorMessage = exports.convertOutputMethods = exports.mergeOptions = exports.checkRequiredNodes = exports.replaceAliasNodeValues = exports.evaluateNodeAliases = exports.mapPropertyAliases = exports.fallbackOrError = exports.truncateString = exports.getOperatorName = exports.isFragmentNode = exports.isOperatorNode = exports.parseIfJson = void 0; | ||
var change_case_1 = require("change-case"); | ||
@@ -71,2 +71,6 @@ var evaluate_1 = require("./evaluate"); | ||
exports.isOperatorNode = isOperatorNode; | ||
var isFragmentNode = function (input) { | ||
return input instanceof Object && 'fragment' in input; | ||
}; | ||
exports.isFragmentNode = isFragmentNode; | ||
var standardiseOperatorName = function (name) { | ||
@@ -151,2 +155,4 @@ var camelCaseName = (0, change_case_1.camelCase)(name); | ||
combinedOptions.functions = __assign(__assign({}, origOptions.functions), newOptions.functions); | ||
if (origOptions.fragments || newOptions.fragments) | ||
combinedOptions.fragments = __assign(__assign({}, origOptions.fragments), newOptions.fragments); | ||
if (origOptions.headers || newOptions.headers) | ||
@@ -153,0 +159,0 @@ combinedOptions.headers = __assign(__assign({}, origOptions.headers), newOptions.headers); |
"use strict"; | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
@@ -130,3 +119,3 @@ if (k2 === undefined) k2 = k; | ||
FigTreeEvaluator.prototype.updateOptions = function (options) { | ||
this.options = __assign(__assign({}, this.options), standardiseOptionNames(options)); | ||
this.options = (0, helpers_1.mergeOptions)(this.options, standardiseOptionNames(options)); | ||
}; | ||
@@ -133,0 +122,0 @@ return FigTreeEvaluator; |
@@ -15,2 +15,5 @@ import FigTreeCache from './cache'; | ||
}; | ||
fragments?: { | ||
[key: string]: EvaluatorNode; | ||
}; | ||
pgConnection?: PGConnection; | ||
@@ -51,6 +54,13 @@ graphQLConnection?: GraphQLConnection; | ||
} | ||
export interface FragmentNode { | ||
fragment: string; | ||
parameters?: { | ||
[key: string]: EvaluatorNode; | ||
}; | ||
[key: string]: EvaluatorNode; | ||
} | ||
export type CombinedOperatorNode = BaseOperatorNode & BasicExtendedNode & SubtractionNode & DivisionNode & ComparatorNode & ConditionalNode & RegexNode & StringSubNode & SplitNode & ObjPropNode & APINode & PGNode & GraphQLNode & BuildObjectNode & MatchNode & FunctionNode & PassThruNode; | ||
export type OperatorNodeUnion = BasicExtendedNode | SubtractionNode | DivisionNode | ComparatorNode | ConditionalNode | RegexNode | StringSubNode | SplitNode | ObjPropNode | PGNode | GraphQLNode | APINode | BuildObjectNode | MatchNode | FunctionNode | PassThruNode; | ||
export type EvaluatorOutput = string | boolean | number | GenericObject | null | undefined | any[]; | ||
export type EvaluatorNode = CombinedOperatorNode | EvaluatorOutput; | ||
export type EvaluatorNode = CombinedOperatorNode | FragmentNode | EvaluatorOutput; | ||
export type OperatorObject = { | ||
@@ -57,0 +67,0 @@ requiredProperties: readonly string[]; |
{ | ||
"name": "fig-tree-evaluator", | ||
"version": "2.3.3", | ||
"version": "2.4.0", | ||
"description": "Module to evaluate JSON-structured expression trees", | ||
@@ -40,3 +40,3 @@ "main": "build/index.js", | ||
"dequal": "^2.0.3", | ||
"object-property-extractor": "^1.0.3" | ||
"object-property-extractor": "^1.0.6" | ||
}, | ||
@@ -43,0 +43,0 @@ "bugs": { |
@@ -52,2 +52,3 @@ # fig-tree-evaluator | ||
- [Alias Nodes](#alias-nodes) | ||
- [Fragments](#fragments) | ||
- [Caching (Memoization)](#caching-memoization) | ||
@@ -144,2 +145,3 @@ - [More examples](#more-examples) | ||
- `functions` -- a single object containing any *custom functions* available for use by the [customFunctions](#custom_functions) operator. | ||
- `fragments` -- commonly-used expressions (with optional parameters) that can be re-used in any other expression. See [Fragments](#fragments) | ||
- `pgConnection` -- if you wish to make calls to a Postgres database using the [`pgSQL` operator](#pg_sql), pass a [node-postres](https://node-postgres.com/) connection object here. | ||
@@ -1393,2 +1395,64 @@ - `graphQLConnection` -- a GraphQL connection object, if using the [`graphQL` operator](#graphql). See operator details below. | ||
## Fragments | ||
You 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: | ||
```js | ||
{ | ||
operator: 'GET', | ||
url: { | ||
operator: 'stringSubstitution', | ||
string: 'https://restcountries.com/v3.1/name/%1', | ||
replacements: ['New Zealand'], | ||
}, | ||
returnProperty: '[0].capital', | ||
outputType: 'string', | ||
} | ||
// => "Wellington" | ||
``` | ||
In this case, you can pre-define the main part of the expression as part of an expression "**fragment**" in the evaluator [options](#available-options). | ||
The 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. | ||
The 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: | ||
```js | ||
const exp = new FigTreeEvaluator({ | ||
fragments: { | ||
getCapital: { | ||
operator: 'GET', | ||
url: { | ||
operator: 'stringSubstitution', | ||
string: 'https://restcountries.com/v3.1/name/%1', | ||
replacements: [ "$country" ], | ||
}, | ||
returnProperty: '[0].capital', | ||
outputType: 'string', | ||
}, | ||
}, | ||
}) | ||
``` | ||
Then any subsequent expressions can use this fragment by specifying a special "Fragment Node", which contains the `fragment` and (optionally) `parameters` fields: | ||
```js | ||
{ | ||
fragment: "getCapital", | ||
parameters: { | ||
$country: { operator: "getData", property: "path.to.country.name" } | ||
} | ||
} | ||
``` | ||
Like 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: | ||
```js | ||
{ | ||
fragment: "getCapital", | ||
$country: { operator: "getData", property: "path.to.country.name" } | ||
} | ||
``` | ||
See `22_fragments.test.ts` for more complex examples. | ||
Unlike 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. | ||
## Caching (Memoization) | ||
@@ -1442,2 +1506,5 @@ | ||
*Trivial upgrades (e.g. documentation only) not included* | ||
- **v2.4.0**: Implement [Fragments](#fragments) (#74) | ||
- **v2.3.2**: Bug fix: alias nodes not working with `evaluateFullObject` (#72) | ||
@@ -1467,4 +1534,3 @@ - **v2.3.0**: Implement [caching/memoization](#caching-memoization) (#68) | ||
## Credit | ||
Icon: Tree by ka reemov from <a href="https://thenounproject.com/icon/tree-2665898/" target="_blank" title="Tree Icon">Noun Project</a> |
235134
3482
1531