fig-tree-evaluator 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +1233 -0
  2. package/build/.DS_Store +0 -0
  3. package/build/FigTreeEvaluator.d.ts +13 -0
  4. package/build/FigTreeEvaluator.js +75 -0
  5. package/build/evaluate.d.ts +2 -0
  6. package/build/evaluate.js +63 -0
  7. package/build/evaluator.d.ts +13 -0
  8. package/build/evaluator.js +75 -0
  9. package/build/helpers.d.ts +16 -0
  10. package/build/helpers.js +63 -0
  11. package/build/index.d.ts +13 -0
  12. package/build/index.js +75 -0
  13. package/build/operators/_operatorAliases.json +86 -0
  14. package/build/operators/_operatorUtils.d.ts +16 -0
  15. package/build/operators/_operatorUtils.js +83 -0
  16. package/build/operators/buildObject.d.ts +11 -0
  17. package/build/operators/buildObject.js +48 -0
  18. package/build/operators/conditional.d.ts +7 -0
  19. package/build/operators/conditional.js +31 -0
  20. package/build/operators/count.d.ts +2 -0
  21. package/build/operators/count.js +29 -0
  22. package/build/operators/customFunctions.d.ts +9 -0
  23. package/build/operators/customFunctions.js +53 -0
  24. package/build/operators/divide.d.ts +11 -0
  25. package/build/operators/divide.js +42 -0
  26. package/build/operators/equal.d.ts +2 -0
  27. package/build/operators/equal.js +29 -0
  28. package/build/operators/getRequest.d.ts +12 -0
  29. package/build/operators/getRequest.js +47 -0
  30. package/build/operators/graphQL.d.ts +18 -0
  31. package/build/operators/graphQL.js +55 -0
  32. package/build/operators/greaterThan.d.ts +6 -0
  33. package/build/operators/greaterThan.js +35 -0
  34. package/build/operators/index.d.ts +23 -0
  35. package/build/operators/index.js +39 -0
  36. package/build/operators/lessThan.d.ts +2 -0
  37. package/build/operators/lessThan.js +35 -0
  38. package/build/operators/logicalAnd.d.ts +8 -0
  39. package/build/operators/logicalAnd.js +33 -0
  40. package/build/operators/logicalOr.d.ts +2 -0
  41. package/build/operators/logicalOr.js +29 -0
  42. package/build/operators/multiply.d.ts +2 -0
  43. package/build/operators/multiply.js +29 -0
  44. package/build/operators/notEqual.d.ts +2 -0
  45. package/build/operators/notEqual.js +29 -0
  46. package/build/operators/objectFunctions.d.ts +9 -0
  47. package/build/operators/objectFunctions.js +38 -0
  48. package/build/operators/objectProperties.d.ts +9 -0
  49. package/build/operators/objectProperties.js +44 -0
  50. package/build/operators/passThru.d.ts +7 -0
  51. package/build/operators/passThru.js +30 -0
  52. package/build/operators/pgSQL.d.ts +24 -0
  53. package/build/operators/pgSQL.js +66 -0
  54. package/build/operators/plus.d.ts +10 -0
  55. package/build/operators/plus.js +39 -0
  56. package/build/operators/postRequest.d.ts +2 -0
  57. package/build/operators/postRequest.js +39 -0
  58. package/build/operators/regex.d.ts +7 -0
  59. package/build/operators/regex.js +46 -0
  60. package/build/operators/split.d.ts +11 -0
  61. package/build/operators/split.js +51 -0
  62. package/build/operators/stringSubstitution.d.ts +7 -0
  63. package/build/operators/stringSubstitution.js +40 -0
  64. package/build/operators/subtract.d.ts +9 -0
  65. package/build/operators/subtract.js +32 -0
  66. package/build/typeCheck.d.ts +9 -0
  67. package/build/typeCheck.js +48 -0
  68. package/build/types.d.ts +53 -0
  69. package/build/types.js +28 -0
  70. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,1233 @@
1
+ # fig-tree-evaluator
2
+
3
+ **FigTree Evaluator** is a module to evaluate JSON-structured expression trees.
4
+
5
+ A 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. For example, 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.
6
+
7
+ A range of built-in operators are available, from simple logic, arithmetic and string manipulation, to data fetching from local sources or remote APIs.
8
+
9
+ [**Demo/Playground**](LINK)
10
+
11
+ ## Contents <!-- omit in toc -->
12
+ <!-- TOC -->
13
+ - [The basics](#the-basics)
14
+ - [Install](#install)
15
+ - [Usage](#usage)
16
+ - [Available options](#available-options)
17
+ - [Operator nodes](#operator-nodes)
18
+ - [Other common properties:](#other-common-properties)
19
+ - [Operator & Property Aliases](#operator--property-aliases)
20
+ - [Operator reference](#operator-reference)
21
+ - [AND](#and)
22
+ - [OR](#or)
23
+ - [EQUAL](#equal)
24
+ - [NOT_EQUAL](#not_equal)
25
+ - [PLUS](#plus)
26
+ - [SUBTRACT](#subtract)
27
+ - [MULTIPLY](#multiply)
28
+ - [DIVIDE](#divide)
29
+ - [GREATER_THAN](#greater_than)
30
+ - [LESS_THAN](#less_than)
31
+ - [COUNT](#count)
32
+ - [CONDITIONAL](#conditional)
33
+ - [REGEX](#regex)
34
+ - [OBJECT_PROPERTIES](#object_properties)
35
+ - [STRING_SUBSTITUTION](#string_substitution)
36
+ - [SPLIT](#split)
37
+ - [GET](#get)
38
+ - [POST](#post)
39
+ - [GRAPHQL](#graphql)
40
+ - [PG_SQL](#pg_sql)
41
+ - [BUILD_OBJECT](#build_object)
42
+ - [PASSTHRU](#passthru)
43
+ - [CUSTOM_FUNCTIONS](#custom_functions)
44
+ - [More examples](#more-examples)
45
+ - [Development environment](#development-environment)
46
+ - [Tests](#tests)
47
+ - [Help, Feedback, Suggestions](#help-feedback-suggestions)
48
+
49
+ <!-- /TOC -->
50
+ ## The basics
51
+
52
+ Fig-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.
53
+
54
+ A wide range of [operators are available](#operator-reference), but [custom fuctions](#custom_functions) can be added to your implementation if you wish to extend the base functionality.
55
+
56
+ For example:
57
+
58
+ ```js
59
+ {
60
+ operator: "+", // "Addition" operator
61
+ values: [1, 2, 3]
62
+ }
63
+ // -> 6
64
+ ```
65
+
66
+ Or, with a deeper structure that results in the same final output:
67
+ ```js
68
+ {
69
+ operator: '+',
70
+ values: [
71
+ {
72
+ operator: '?', // conditional
73
+ condition: {
74
+ operator: '=', // equality
75
+ values: [
76
+ {
77
+ operator: 'objectProperties', // extracted from passed-in object
78
+ property: 'responses.Q1',
79
+ },
80
+ 'correct',
81
+ ],
82
+ },
83
+ valueIfTrue: 1,
84
+ valueIfFalse: 0,
85
+ },
86
+ {
87
+ operator: 'GET', // API lookup
88
+ url: 'https://my.server.com/api/get-count',
89
+ },
90
+ 3,
91
+ ],
92
+ }
93
+ // -> 6
94
+ ```
95
+
96
+ Which would be represented diagramatically with the following expression tree:
97
+
98
+ ![Example 2](/docs/img/Example_1.png)
99
+
100
+ A playground for building and testing expressions is available [here](LINK)
101
+
102
+ ## Install
103
+
104
+ `npm install fig-tree`\
105
+ or\
106
+ `yarn add fig-tree`
107
+
108
+ ## Usage
109
+
110
+ ```js
111
+ import FigTreeEvaluator from 'fig-tree'
112
+
113
+ // New evaluator instance
114
+ const exp = new FigTreeEvaluator([ options ]) // See available options below
115
+
116
+ // Evaluate expressions
117
+ exp.evaluate(expression, [options]) // Options over-ride initial options for this evaluation
118
+ .then((result) => { // "evaluate" is async method
119
+ // Do something with result
120
+ })
121
+
122
+ // Or within async function:
123
+ const result = await exp.evaluate(expression, [options])
124
+ ```
125
+
126
+ ## Available options
127
+
128
+ The `options` parameter is an object with the following available properties (all optional):
129
+
130
+ - `objects` -- 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.
131
+ - `functions` -- a single object containing any *custom functions* available for use by the [customFunctions](#custom_functions) operator.
132
+ - `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.
133
+ - `graphQLConnection` -- a GraphQL connection object, if using the [`graphQL` operator](#graphql). See operator details below.
134
+ - `baseEndpoint` -- A general http headers object that will be passed to *all* http-based operators (`GET`, `POST`, `GraphQL`). Useful if all http queries are to a common server -- then each individual node will only require a relative url. See specific operator for more details.
135
+ - `headers` -- A general http headers object that will be passed to *all* http-based operators. Useful for authenticatian headers, for example. Each operator and instance can have its own headers, though, so see specific operator reference for details.
136
+ - `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)
137
+ - `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.
138
+ - `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 encoutered (e.g. trying to pass a primitive value when an array is expected => `.map is not a function`)
139
+
140
+ As mentioned above, `options` can be provided as part of the constructor as part of each seperate evaluation. You can also change the options permanently for a given evaluator instance with:
141
+
142
+ `exp.updateOptions(options)`
143
+
144
+ You can also retrieve the current options state at any time with:
145
+
146
+ `exp.getOptions()`
147
+
148
+ EVALUATE WITHOUT CONSTRUCTOR???
149
+
150
+
151
+ ## Operator nodes
152
+
153
+ Each 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:
154
+
155
+ ```js
156
+ {
157
+ operator: "conditional", // or "?"
158
+ condition: <boolean>, // or fig-tree expression that returns boolean
159
+ valueIfTrue: <someValue>,
160
+ valueIfFalse: <someOtherValue>
161
+
162
+ }
163
+ ```
164
+
165
+ However, 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.
166
+
167
+ For example, the following two representations are equivalent `conditional` operator nodes:
168
+
169
+ ```js
170
+ {
171
+ operator: "?", // conditional (alias)
172
+ condition: 1 + 1 ==== 2
173
+ valueIfTrue: "True output",
174
+ valueIfFalse: "False output"
175
+ }
176
+
177
+ // same as:
178
+
179
+ {
180
+ operator: "?",
181
+ children: [ 1 + 2 === 2, "True output", "False output" ]
182
+ }
183
+ ```
184
+
185
+ Most of the time named properties is preferable; however there are situations where the "children" array might be easier to deal with, or to generate from child nodes.
186
+
187
+ ### Other common properties:
188
+
189
+ In each operator node, as well as the operator-specific properties, the following two optional properties can be provided:
190
+
191
+ - `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.
192
+ - `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](LINK) to see the outcome of applying different `outputType` values to various results.
193
+
194
+ Remember that *all* operator node properties can themselves be operator nodes, *including* the `fallback` and `outputType` properties.
195
+
196
+ e.g.
197
+
198
+ ```js
199
+ // Dynamic outputType, which uses the fallback value due to missing property
200
+ // for the conditional '?' operator:
201
+ {
202
+ operator: '+',
203
+ values: [9, 10, 11],
204
+ outputType: {
205
+ operator: '?',
206
+ condition: {
207
+ operator: '=',
208
+ values: ['three', 'four'],
209
+ },
210
+ valueIfTrue: 'number',
211
+ fallback: 'string',
212
+ },
213
+ }
214
+ // => "30"
215
+
216
+ ```
217
+
218
+ ### Operator & Property Aliases
219
+
220
+ For 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.
221
+
222
+ Similarly, some property names accept aliases -- see individual operators for these.
223
+
224
+ ## Operator reference
225
+
226
+ The full list of available operators and their associated properties:
227
+
228
+ <sup>*</sup> denotes "required" properties
229
+
230
+ ### AND
231
+
232
+ *Logical AND*
233
+
234
+ Aliases: `and`, `&`, `&&`
235
+
236
+ #### Properties
237
+
238
+ - `values`<sup>*</sup>: (array) -- any number of elements; will be compared using Javascript `&&` operator
239
+
240
+ e.g.
241
+ ```js
242
+ {
243
+ operator: '&',
244
+ values: [true, true, true],
245
+ }
246
+ // => true
247
+ ```
248
+
249
+ `children` array: `[...values]`
250
+
251
+ ----
252
+ ### OR
253
+
254
+ *Logical OR*
255
+
256
+ Aliases: `or`, `|`, `||`
257
+
258
+ #### Properties
259
+
260
+ - `values`<sup>*</sup>: (array) -- any number of elements; will be compared using Javascript `||` operator
261
+
262
+ e.g.
263
+ ```js
264
+ {
265
+ operator: 'or',
266
+ values: [
267
+ true,
268
+ {
269
+ operator: 'and',
270
+ values: [true, false],
271
+ },
272
+ true,
273
+ ],
274
+ }
275
+ // => true
276
+ ```
277
+
278
+ `children` array: `[...values]`
279
+
280
+ ----
281
+ ### EQUAL
282
+
283
+ *Equality*
284
+
285
+ Aliases: `=`, `eq`, `equal`, `equals`
286
+
287
+ #### Properties
288
+
289
+ - `values`<sup>*</sup>: (array) -- any number of elements; will be compared using Javascript `==` operator
290
+
291
+ e.g.
292
+ ```js
293
+ {
294
+ operator: '=',
295
+ values: [3, 3, 'three'],
296
+ }
297
+ // => false
298
+ ```
299
+
300
+ `children` array: `[...values]`
301
+
302
+ ----
303
+ ### NOT_EQUAL
304
+
305
+ *Non-equality*
306
+
307
+ Aliases: `!=`, `!`, `ne`, `notEqual`
308
+
309
+ #### Properties
310
+
311
+ - `values`<sup>*</sup>: (array) -- any number of elements; will be compared using Javascript `!=` operator
312
+
313
+ e.g.
314
+ ```js
315
+ {
316
+ operator: '=',
317
+ values: [3, 3, 'three'],
318
+ }
319
+ // => true
320
+ ```
321
+
322
+ `children` array: `[...values]`
323
+
324
+ ----
325
+ ### PLUS
326
+
327
+ *Addition, concatenation, merging*
328
+
329
+ Aliases: `+`, `add`, `concat`, `join`, `merge`
330
+
331
+ #### Properties
332
+
333
+ - `values`<sup>*</sup>: (array) -- any number of elements. Will be added (numbers), concatenated (strings, arrays) or merged (objects) according their type.
334
+ - `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.
335
+
336
+ e.g.
337
+ ```js
338
+ {
339
+ operator: '+',
340
+ values: [4, 5, 6],
341
+ }
342
+ // => 15
343
+
344
+ {
345
+ operator: '+',
346
+ values: ['this', ' and ', 'that'],
347
+ }
348
+ // => 'this and that'
349
+
350
+ {
351
+ operator: '+',
352
+ values: [{one: 1, two: 2}, {three: 3}],
353
+ }
354
+ // => {one: 1, two: 2, three: 3}
355
+
356
+ {
357
+ operator: '+',
358
+ values: [4, 5, 6],
359
+ type: 'string'
360
+ }
361
+ // => "456"
362
+
363
+ {
364
+ operator: '+',
365
+ values: [4, 5, 6],
366
+ type: 'array'
367
+ }
368
+ // => [4, 5, 6]
369
+
370
+ ```
371
+
372
+ `children` array: `[...values]`
373
+
374
+ ----
375
+ ### SUBTRACT
376
+
377
+ *Subtraction*
378
+
379
+ Aliases: `-`, `subtract`, `minus`, `takeaway`
380
+
381
+ #### Properties
382
+
383
+ - `values`<sup>*</sup>: (array) -- exactly 2 numerical elements; the second will be subtracted from the first. (If non-numerical elements are provided, the operator will return `NaN`)
384
+
385
+ e.g.
386
+ ```js
387
+ {
388
+ operator: '-',
389
+ values: [10, 8],
390
+ }
391
+ // => 2
392
+
393
+ {
394
+ operator: 'minus',
395
+ values: [0, 3.5, 10], // additional elements after the first two ignored
396
+ }
397
+ // => -3.5
398
+
399
+ {
400
+ operator: '-',
401
+ values: [4, "three"],
402
+ }
403
+ // => NaN
404
+ ```
405
+
406
+ `children` array: `[originalValue, valueToSubtract]` (same as `values`)
407
+
408
+ ----
409
+ ### MULTIPLY
410
+
411
+ *Multiplcation*
412
+
413
+ Aliases: `*`, `x`, `multiply`, `times`
414
+
415
+ #### Properties
416
+
417
+ - `values`<sup>*</sup>: (array) -- any number of numerical elements. Returns the product of all elements. (If non-numerical elements are provided, the operator will return `NaN`)
418
+
419
+ e.g.
420
+ ```js
421
+ {
422
+ operator: '*',
423
+ values: [5, 7],
424
+ }
425
+ // => 35
426
+
427
+ {
428
+ operator: 'x',
429
+ values: [2, 3.5, 10], // additional elements after the first two ignored
430
+ }
431
+ // => 70
432
+
433
+ {
434
+ operator: 'times',
435
+ values: [4, "three"],
436
+ }
437
+ // => NaN
438
+ ```
439
+
440
+ `children` array: `[...values]`
441
+
442
+ ----
443
+ ### DIVIDE
444
+
445
+ *Division*
446
+
447
+ Aliases: `/`, `divide`, `÷`
448
+
449
+ #### Properties
450
+
451
+ - `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`)
452
+ - `dividend` (or `divide`): (number) -- the number that will be divided
453
+ - `divisor` (or `by`): (number) -- the number to divide `dividend` by
454
+ - `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`)
455
+
456
+ Note that the input values can be provided as *either* a `values` array *or* `dividend`/`divisor` properties. If both are provided, `values` takes precedence.
457
+
458
+ e.g.
459
+ ```js
460
+ {
461
+ operator: '/',
462
+ values: [35, 7],
463
+ }
464
+ // => 5
465
+
466
+ {
467
+ operator: '/',
468
+ divide: 20,
469
+ by: 3,
470
+ output: 'quotient'
471
+ }
472
+ // => 6
473
+
474
+ {
475
+ operator: 'divide',
476
+ dividend: 20,
477
+ divisor: 3,
478
+ output: 'remainder'
479
+ }
480
+ // => 2
481
+ ```
482
+
483
+ `children` array: `[dividend, divisor]` (same as `values`)
484
+
485
+ ----
486
+ ### GREATER_THAN
487
+
488
+ *Greater than (or equal to)*
489
+
490
+ Aliases: `>`, `greaterThan`, `higher`, `larger`
491
+
492
+ #### Properties
493
+
494
+ - `values`<sup>*</sup>: (array) -- exactly 2 values. Can be any type of value that can be compared with Javascript `>` operator.
495
+ - `strict`: (boolean, default `false`) -- if `true`, value 1 must be strictly greater than value 2 (i.e. `>`). Otherwise it will be compared with "greater than or equal to" (i.e. `>=`)
496
+
497
+ e.g.
498
+ ```js
499
+ {
500
+ operator: '>',
501
+ values: [10, 8]
502
+ }
503
+ // => true
504
+
505
+ {
506
+ operator: '>',
507
+ values: ["alpha", "beta"]
508
+ }
509
+ // => false
510
+
511
+ {
512
+ operator: '>',
513
+ values: [4, 4],
514
+ strict: true
515
+ }
516
+ // => false
517
+ ```
518
+
519
+ `children` array: `[firstValue, secondValue]` (same as `values`)
520
+
521
+ ----
522
+ ### LESS_THAN
523
+
524
+ *Less than (or equal to)*
525
+
526
+ Aliases: `<`, `lessThan`, `lower`, `smaller`
527
+
528
+ #### Properties
529
+
530
+ - `values`<sup>*</sup>: (array) -- exactly 2 values. Can be any type of value that can be compared with Javascript `<` operator.
531
+ - `strict`: (boolean, default `false`) -- if `true`, value 1 must be strictly lower than value 2 (i.e. `<`). Otherwise it will be compared with "less than or equal to" (i.e. `<=`)
532
+
533
+ e.g.
534
+ ```js
535
+ {
536
+ operator: '<',
537
+ values: [10, 8]
538
+ }
539
+ // => false
540
+
541
+ {
542
+ operator: '<',
543
+ values: ["alpha", "beta"]
544
+ }
545
+ // => true
546
+
547
+ {
548
+ operator: '<',
549
+ values: [4, 4],
550
+ strict: false
551
+ }
552
+ // => true
553
+ ```
554
+
555
+ `children` array: `[firstValue, secondValue]` (same as `values`)
556
+
557
+ ----
558
+ ### COUNT
559
+
560
+ *Count elements in array*
561
+
562
+ Aliases: `count`, `length`
563
+
564
+ #### Properties
565
+
566
+ - `values`<sup>*</sup>: (array) -- any number of elements. Returns `array.length`
567
+
568
+ e.g.
569
+ ```js
570
+ {
571
+ operator: 'count',
572
+ values: [10, 8, "three", "four"]
573
+ }
574
+ // => 4
575
+ ```
576
+
577
+ `children` array: `[...values]`
578
+
579
+ ----
580
+ ### CONDITIONAL
581
+
582
+ *Return different values depending on a condition expression*
583
+
584
+ Aliases: `?`, `conditional`, `ifThen`
585
+
586
+ #### Properties
587
+
588
+ - `condition`<sup>*</sup>: (boolean) -- a boolean value (presumably the result of a child expression)
589
+ - `valueIfTrue` (or `ifTrue`)<sup>*</sup>: the value returned if `condition` is `true`
590
+ - `valueIfFalse` (or `ifFalse`)<sup>*</sup>: the value returned if `condition` is `false`
591
+
592
+ e.g.
593
+ ```js
594
+ {
595
+ operator: '?',
596
+ condition: {
597
+ operator: '=',
598
+ values: [
599
+ {
600
+ operator: '+',
601
+ values: [5, 5, 10],
602
+ },
603
+ 20,
604
+ ],
605
+ },
606
+ ifTrue: 'YES',
607
+ ifFalse: 'NO',
608
+ }
609
+ // => YES
610
+ ```
611
+
612
+ `children` array: `[condition, valueIfTrue, valueIfFalse]`
613
+
614
+ ----
615
+ ### REGEX
616
+
617
+ *Compares an input string against a [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) pattern*
618
+
619
+ Aliases: `regex`, `patternMatch`, `regexp`, `matchPattern`
620
+
621
+ #### Properties
622
+
623
+ - `testString` (or `string`, `value`)<sup>*</sup>: (string) -- the string to be compared against the regex pattern
624
+ - `pattern` (or `regex`, `regexp`, `regExp`, `re`)<sup>*</sup>: (string) a regex pattern to test `testString` against
625
+
626
+ Returns `true` (match found) or `false` (no match)
627
+
628
+ e.g.
629
+ ```js
630
+ {
631
+ operator: 'regex',
632
+ string: "home@myplace.com",
633
+ pattern: '^[A-Za-z0-9.]+@[A-Za-z0-9]+\\.[A-Za-z0-9.]+$' // Simple Email validation
634
+ }
635
+ // => true
636
+ ```
637
+
638
+ `children` array: `[testString, pattern]`
639
+
640
+ ----
641
+ ### OBJECT_PROPERTIES
642
+
643
+ *Extracts values from objects in your application*
644
+
645
+ Aliases: `objectProperties`, `objProps`, `getProperty`, `getObjProp`
646
+
647
+ #### Properties
648
+
649
+ - `property` (or `path`, `propertyName`)<sup>*</sup>: (string) -- the path to the required property in the object
650
+ - `additionalObjects` (or `objects`, `additional`): (object) -- any other objects whose properties can be referenced in `property` (see below)
651
+
652
+ Objects are normamlly expected to be passed in to the evaluator as part of the [options](#available-options), not as part of the expression itself. The reason for this is that 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 objects directly as part of the expression using the `additionalObjects` property, so (in theory) objects could be dynamically generated from other expressions.)
653
+
654
+ For example, consider a `user` object and an fig-tree evaluator instance:
655
+
656
+ ```js
657
+ const user = {
658
+ firstName: 'Peter',
659
+ lastName: 'Parker',
660
+ alias: 'Spider-man',
661
+ friends: ['Ned', 'MJ', 'Peter 2', 'Peter 3'],
662
+ enemies: [
663
+ { name: 'The Vulture', identity: 'Adrian Toomes' },
664
+ { name: 'Green Goblin', identity: 'Norman Osborne' },
665
+ ],
666
+ }
667
+
668
+ const exp = new FigTreeEvaluator()
669
+
670
+ const expression = getExpressionFromConfig()
671
+
672
+ exp.evaluate(expression, { objects: { user } })
673
+ ```
674
+
675
+ Here is the result of various values of `expression:`
676
+
677
+ ```js
678
+ {
679
+ operator: 'objectProperties',
680
+ property: 'user.firstName',
681
+ }
682
+ // => "Peter"
683
+
684
+ {
685
+ operator: 'getProperty',
686
+ path: 'user.friends[1]',
687
+ }
688
+ // => "MJ"
689
+
690
+ {
691
+ operator: 'getProperty',
692
+ path: 'user.enemies.name',
693
+ }
694
+ // => ["The Vulture", "Green Goblin"]
695
+ ```
696
+ Notice the last example pulls multiple values out of an array of objects, in this case the "name". This is essentially a shorthand for:
697
+
698
+ ```js
699
+ const result = { operator: 'getProperty', path: 'user.enemies' }
700
+ result.map((e) => e.name)
701
+ ```
702
+
703
+ The "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.
704
+
705
+ The "objectProperties" operator will throw an error if an invalid path is provided, so it is recommended to provide a `fallback` value for the expression:
706
+ ```js
707
+ {
708
+ operator: 'objectProperties',
709
+ property: 'user.middleName',
710
+ fallback: 'Not found!'
711
+ }
712
+ // => "Not found!"
713
+ ```
714
+
715
+ `children` array: `[property]`
716
+
717
+
718
+ Example using "objects" passed in dynamically as part of expression:
719
+
720
+ ```js
721
+ {
722
+ operator: 'objectProperties',
723
+ property: 'user.name',
724
+ additionalObjects: {
725
+ operator: '?',
726
+ condition: { operator: '=', values: [{ operator: '+', values: [7, 8, 9] }, 25] },
727
+ valueIfTrue: { user: { name: 'Bilbo' } },
728
+ valueIfFalse: { user: { name: 'Frodo' } },
729
+ },
730
+ }
731
+ // => "Frodo"
732
+ ```
733
+
734
+ ----
735
+ ### STRING_SUBSTITUTION
736
+
737
+ *Replace values in a string using simple parameter substitution*
738
+
739
+ Aliases: `stringSubstitution`, `substitute`, `stringSub`, `replace`
740
+
741
+ #### Properties
742
+
743
+ - `string`<sup>*</sup>: (string) -- a parameterized (`%1`, `%2`) string, where the parameters are to be replaced by dynamic values. E.g. `"My name is %1 (age %2)"`
744
+ - `substitutions` (or `replacments`)<sup>*</sup>: (array) -- the values to be substituted into `string`
745
+
746
+ The values in the `substitutions` array are replaced in the original `string` by matching their order to the numerical order of the parameters.
747
+
748
+ e.g.
749
+ ```js
750
+ {
751
+ operator: 'stringSubstitution',
752
+ string: 'My name is %1 (age %2)',
753
+ substitutions: [
754
+ 'Steve Rogers',
755
+ {
756
+ operator: '-',
757
+ values: [2023, 1918],
758
+ },
759
+ ],
760
+ }
761
+ // => "My name is Steve Rogers (age 106)"
762
+
763
+ {
764
+ operator: 'replace',
765
+ string: '%1 is actually %2 %3',
766
+ substitutions: [
767
+ // Using the 'user' object from above
768
+ {
769
+ operator: 'objectProperties',
770
+ property: 'user.alias',
771
+ },
772
+ {
773
+ operator: 'objectProperties',
774
+ property: 'user.firstName',
775
+ },
776
+ {
777
+ operator: 'objectProperties',
778
+ property: 'user.lastName',
779
+ },
780
+ ],
781
+ }
782
+ // => "Spiderman is actually Peter Parker"
783
+
784
+ // Parameters can be repeated:
785
+ {
786
+ operator: 'stringSubstitution',
787
+ string: 'A %1 says: "%2 %2 %2"',
788
+ substitutions: ['bird', 'Tweet!'],
789
+ }
790
+ // => 'A bird says: "Tweet! Tweet! Tweet!"'
791
+ ```
792
+
793
+ `children` array: `[string, ...substitutions]`
794
+
795
+ e.g.
796
+ ```js
797
+ {
798
+ operator: 'replace',
799
+ children: ['I am %1 %2', 'Iron', 'Man'],
800
+ }
801
+ // => "I am Iron Man"
802
+ ```
803
+
804
+ ----
805
+
806
+ ### SPLIT
807
+
808
+ *Split strings into arrays*
809
+
810
+ Aliases: `split`, `arraySplit`
811
+
812
+ #### Properties
813
+
814
+ - `value` (or `string`)<sup>*</sup>: (string) -- string to be split
815
+ - `delimiter` (or `separator`): (string) -- substring to split `value` on (Default: `" "` (space))
816
+ - `trimWhiteSpace` (or `trimWhitespace`, `trim`): (boolean, default `true`) -- strips whitespace from the beginning or end of resulting substrings
817
+ - `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.
818
+ i.e. `this, that, another,` (delimiter `","`) => `["this", "that", "another", ""]`
819
+
820
+ The last two parameters (`timeWhiteSpace` and `excludeTrailing`) should rarely be needed.
821
+
822
+ e.g.
823
+ ```js
824
+ {
825
+ operator: 'split',
826
+ children: ['Alpha, Beta, Gamma, Delta', ','],
827
+ }
828
+ // => ['Alpha', 'Beta', 'Gamma', 'Delta']
829
+
830
+ ```
831
+
832
+ `children` array: `[value, delimiter]`
833
+ (`trimWhiteSpace` and `excludeTrailing` not available, since array can only support one optional parameter)
834
+
835
+ - `urlObject`: either a url string, or an object structured as `{url: <string>, headers: <object>}` (if additional headers are required)
836
+ - `parameterKeys`: an array of strings representing the keys of any query parameters
837
+ - `...values`: one value for each key specified in `parameterKeys`
838
+ - `returnProperty` (optional): as above
839
+
840
+ ---
841
+ ### GET
842
+
843
+ *Http GET request*
844
+
845
+ Aliases: `get`, `api`
846
+
847
+ #### Properties
848
+
849
+ - `url` (or `endpoint`)<sup>*</sup>: (string) -- url to be queried
850
+ - `parameters`: (object) -- key-value pairs for any query parameters for the request
851
+ - `headers`: (object) -- any additional headers (such as authentication) required for the request
852
+ - `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)
853
+
854
+ As 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.)
855
+
856
+ e.g.
857
+ ```js
858
+ {
859
+ operator: 'GET',
860
+ url: 'https://restcountries.com/v3.1/name/zealand',
861
+ returnProperty: 'name.common',
862
+ outputType: 'string' // This extracts the string from the returned array value
863
+ }
864
+ // => "New Zealand"
865
+
866
+ {
867
+ operator: 'get',
868
+ endpoint: {
869
+ operator: '+',
870
+ values: ['https://restcountries.com/v3.1/name/', 'india'],
871
+ },
872
+ parameters: { fullText: true },
873
+ outputProperty: '[0].name.nativeName.hin',
874
+ }
875
+ // => { "official": "भारत गणराज्य", "common": "भारत" }
876
+
877
+ ```
878
+
879
+ `children` array: `[urlObject, parameterKeys, ...values, returnProperty]`
880
+
881
+ - `urlObject`: either a url string, or an object structured as `{url: <string>, headers: <object>}` (if additional headers are required)
882
+ - `parameterKeys`: an array of strings representing the keys of any query parameters
883
+ - `...values`: one value for each key specified in `parameterKeys`
884
+ - `returnProperty` (optional): as above
885
+
886
+ e.g.
887
+ ```js
888
+ {
889
+ operator: 'get',
890
+ children: [
891
+ 'https://restcountries.com/v3.1/name/cuba', // url
892
+ ['fullText', 'fields'], // parameterKeys
893
+ 'true', // parameter value 1
894
+ 'name,capital,flag', // parameter value 2
895
+ 'flag', // returnProperty
896
+ ],
897
+ outputType: 'string',
898
+ }
899
+ // => "🇨🇺"
900
+ ```
901
+
902
+ ----
903
+ ### POST
904
+
905
+ *Http POST request*
906
+
907
+ Aliases: `post`
908
+
909
+ The "POST" operator is basically structurally the same as [GET](#get).
910
+
911
+ #### Properties
912
+
913
+ - `url`/`endpoint`<sup>*</sup>,`parameters`, `headers`, `returnProperty`/`outputProperty` -- same as "GET" operator (although the `parameters` object will be passed to the Post request as body JSON rather than url query parameters)
914
+
915
+ e.g.
916
+ ```js
917
+ {
918
+ operator: "post",
919
+ endpoint: "https://jsonplaceholder.typicode.com/posts",
920
+ parameters: {
921
+ title: "New Blog Post",
922
+ body: "Just a short note...",
923
+ userId: 2
924
+ },
925
+ returnProperty: "id"
926
+ }
927
+ // => 101
928
+
929
+ ```
930
+
931
+ `children` array: `[urlObject, parameterKeys, ...values, returnProperty]` (same as "GET")
932
+
933
+ ----
934
+
935
+ ### GRAPHQL
936
+
937
+ *Http GraphQL request (using POST)*
938
+
939
+ Aliases: `graphQl`, `graphql`, `gql`
940
+
941
+ This operator is essentially a special case of the "POST" operator, but structured specifically for [GraphQL](https://graphql.org/) requests.
942
+
943
+ #### Properties
944
+
945
+ - `query`<sup>*</sup>: (string) -- the GraphQL query string
946
+ - `variables`: (object) -- key-value pairs for any variables used in the `query`
947
+ - `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`)
948
+ - `headers`: (object) -- any additional headers (such as authentication) required for the request
949
+ - `returnNode` (or `returnProperty`, `outputProperty`): (string) -- an object path for which property to extract from the returned data (same as "GET" and "POST").
950
+
951
+ As 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.
952
+
953
+ Often, 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).
954
+
955
+ The required connection object is:
956
+ ```ts
957
+ {
958
+ endpoint: string // url
959
+ headers?: { [key: string]: string } // key-value pairs
960
+ }
961
+ ```
962
+
963
+ The following example expression uses the GraphQL connection: `{endpoint: 'https://countries.trevorblades.com/'}`
964
+ ```js
965
+ {
966
+ operator: 'graphQL',
967
+ query: `query getCountry($code: String!) {
968
+ countries(filter: {code: {eq: $code}}) {
969
+ name
970
+ emoji
971
+ }
972
+ }`,
973
+ variables: { code: 'NZ' },
974
+ returnNode: 'countries[0]',
975
+ }
976
+ // => { "name": "New Zealand", "emoji": "🇳🇿" }
977
+
978
+ ```
979
+
980
+ `children` array: `[query, endpoint, variableKeys, ...variableValues, returnNode]`
981
+
982
+ - `query`: the GraphQL query (string)
983
+ - `endpoint`: url string; to use the endpoint provided in the GraphQL connection options, pass empty string `""` here
984
+ - `variableKeys`: an array of strings representing the keys the GraphQL `variables` object
985
+ - `...variableValues`: one value for each key specified in `variableKeys`
986
+ - `returnNode` (optional): the return property, as per "GET" and "POST" operators
987
+
988
+ e.g.
989
+ ```js
990
+ {
991
+ operator: 'GraphQL',
992
+ children: [
993
+ `query getCountry($code: String!) {
994
+ countries(filter: {code: {eq: $code}}) {
995
+ name
996
+ emoji
997
+ }
998
+ }`,
999
+ "", // default endpoint
1000
+ ['code'], // variable keys
1001
+ 'NZ', // variable value
1002
+ 'countries.emoji', // return node
1003
+ ],
1004
+ type: 'string',
1005
+ }
1006
+ // => "🇨🇺"
1007
+ ```
1008
+
1009
+ ----
1010
+ ### PG_SQL
1011
+
1012
+ *Query a Postgres database using [`node-postgres`](https://node-postgres.com/)*
1013
+
1014
+ Aliases: `pgSql`, `sql`, `postgres`, `pg`, `pgDb`
1015
+
1016
+ #### Properties
1017
+
1018
+ - `query`<sup>*</sup>: (string) -- SQL query string, with parameterised replacements (i.e. `$1`, `$2`, etc)
1019
+ - `values` (or `replacements`): (array) -- replacements for the `query` parameters
1020
+ - `type`: (`"array" | "string" | "number"`) -- determines the shape of the resulting data. To quote `node-postgres`:
1021
+ > By default node-postgres reads rows and collects them into JavaScript objects with the keys matching the column names and the values matching the corresponding row value for each column. If you do not need or do not want this behavior you can pass rowMode: 'array' to a query object. This will inform the result parser to bypass collecting rows into a JavaScript object, and instead will return each row as an array of values.
1022
+
1023
+ We extend this a step further by flattening the array, and (if `"string"` or `"number"`) converting the result to a concatenated string or (if possible) number.
1024
+
1025
+ In order to query a postgres database, fig-tree must be provided with a database connection object -- specifically, a [`node-postgres`](https://node-postgres.com/) `Client` object:
1026
+
1027
+ ```js
1028
+ import { Client } from 'pg'
1029
+ const pgConnect = new Client(pgConfig) // pgConfig = database details, see node-postgres documentation
1030
+
1031
+ pgConnect.connect()
1032
+
1033
+ const exp = new FigTreeEvaluator({ pgConnection: pgConnect })
1034
+ ```
1035
+
1036
+ The following examples query a default installation of the [Northwind](https://github.com/pthom/northwind_psql) demo database.
1037
+
1038
+ e.g.
1039
+ ```js
1040
+ {
1041
+ operator: 'pgSql',
1042
+ query: "SELECT contact_name FROM customers where customer_id = 'FAMIA';",
1043
+ type: 'string',
1044
+ }
1045
+ // => "Aria Cruz"
1046
+
1047
+ {
1048
+ operator: 'pgSQL',
1049
+ query: 'SELECT product_name FROM public.products WHERE category_id = $1 AND supplier_id != $2',
1050
+ values: [1, 16],
1051
+ type: 'array',
1052
+ }
1053
+ // => ["Chai","Chang","Guaraná Fantástica","Côte de Blaye","Chartreuse verte",
1054
+ // "Ipoh Coffee","Outback Lager","Rhönbräu Klosterbier","Lakkalikööri"]
1055
+
1056
+ ```
1057
+
1058
+ `children` array: `[queryString, ...substitutions]`
1059
+
1060
+ (`type` is provided by the common `type`/`outputType` property)
1061
+
1062
+
1063
+ ----
1064
+ ### BUILD_OBJECT
1065
+
1066
+ *Return an object constructed by separate keys and values*
1067
+
1068
+ Aliases: `buildObject`, `build`, `object`
1069
+
1070
+ The "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.
1071
+
1072
+ #### Properties
1073
+
1074
+ - `properties` (or `values`, `keyValPairs`, `keyValuePairs`)<sup>*</sup>: (array) -- array of objects of the following shape:
1075
+ ```ts
1076
+ {
1077
+ key: string
1078
+ value: any
1079
+ }
1080
+ ```
1081
+ Each element provides one key-value pair in the output object
1082
+
1083
+ e.g.
1084
+ ```js
1085
+ {
1086
+ operator: 'buildObject',
1087
+ properties: [
1088
+ { key: 'one', value: 1 },
1089
+ { key: 'two', value: 2 },
1090
+ {
1091
+ // Using "user" object from earlier
1092
+ key: { operator: 'objectProperties', property: 'user.friends[0]' },
1093
+ value: {
1094
+ operator: '+',
1095
+ values: [7, 8, 9],
1096
+ },
1097
+ },
1098
+ ],
1099
+ }
1100
+ // => { one: 1, two: 2, Ned: 24 }
1101
+
1102
+ ```
1103
+
1104
+ `children` array: `[key1, value1, key2, value2, ...]`
1105
+
1106
+ This is one of the few operators where the `children` array might actually be simpler to define than the `properties` property, depending on how deep the array elements are themselves operator nodes.
1107
+
1108
+ e.g.
1109
+ ```js
1110
+ // This is the same as the previous expression
1111
+ {
1112
+ operator: 'buildObject',
1113
+ children: ['one', 1, 'two', 2,
1114
+ { operator: 'objectProperties', property: 'user.friends[0]' },
1115
+ { operator: '+', values: [7, 8, 9] },
1116
+ ],
1117
+ }
1118
+ // => { one: 1, two: 2, Ned: 24 }
1119
+ ```
1120
+
1121
+ ----
1122
+ ### PASSTHRU
1123
+
1124
+ *Pass-thru (does nothing)*
1125
+
1126
+ Aliases: `passThru`, `_`, `pass`, `ignore`, `coerce`, `convert`
1127
+
1128
+ This operator simply returns its input. Its purpose is to allow an additional type conversion (using `outputType`) before passing up to a parent node.
1129
+
1130
+ #### Properties
1131
+
1132
+ - `value` (or `_`, `data`)<sup>*</sup>: (any) -- the value that is returned
1133
+
1134
+ e.g.
1135
+ ```js
1136
+ {
1137
+ operator: 'pass',
1138
+ value: { operator: '+', values: [50, 0], type: 'string' },
1139
+ outputType: 'array',
1140
+ }
1141
+ // => ["500"]
1142
+ ```
1143
+
1144
+ ----
1145
+
1146
+ ### CUSTOM_FUNCTIONS
1147
+
1148
+ *Extend functionality by calling custom functions*
1149
+
1150
+ Aliases: `customFunctions`, `customFunction`, `objectFunctions`, `functions`, `function`, `runFunction`
1151
+
1152
+ #### Properties
1153
+
1154
+ - `functionPath` (or `functionsPath`, `functionName`, `funcPath`<sup>*</sup>: (string) -- path to where the function resides in the `options.functions` object
1155
+ - `args` (or `arguments`, `variables`): (array) -- input arguments for the function
1156
+
1157
+ Custom functions are stored in the evaluator `options`, in the `functions` property.
1158
+
1159
+ For examples, consider the following fig-tree instance:
1160
+ ```js
1161
+ const exp = new FigTreeEvaluator({
1162
+ functions: {
1163
+ double: (x) => x * 2,
1164
+ getCurrentYear: () => new Date().toLocaleString('en', { year: 'numeric' }),
1165
+ toUpperCase: (input) => input.toUpperCase(),
1166
+ },
1167
+ })
1168
+ ```
1169
+
1170
+ Here is the result of various expressions:
1171
+ ```js
1172
+ {
1173
+ operator: 'functions',
1174
+ functionPath: 'double',
1175
+ args: [50],
1176
+ }
1177
+ // => 100
1178
+
1179
+ {
1180
+ operator: '+',
1181
+ values: [
1182
+ {
1183
+ operator: 'customFunctions',
1184
+ functionPath: 'toUpperCase',
1185
+ args: ['The current year is: '],
1186
+ },
1187
+ {
1188
+ operator: 'customFunctions',
1189
+ functionPath: 'getCurrentYear',
1190
+ },
1191
+ ],
1192
+ }
1193
+ // => "THE CURRENT YEAR IS: 2022"
1194
+ ```
1195
+
1196
+ `children` array: `[functionPath, ...args]`
1197
+
1198
+ e.g.
1199
+ ```js
1200
+ {
1201
+ operator: 'functions',
1202
+ children: ['double', 99],
1203
+ }
1204
+ // => 198
1205
+ ```
1206
+
1207
+ ## More examples
1208
+
1209
+ More examples, included large, complex expressions can be found within the test suites in the [repository](https://github.com/CarlosNZ/fig-tree).
1210
+
1211
+ ## Development environment
1212
+
1213
+ Github repo: https://github.com/CarlosNZ/fig-tree
1214
+
1215
+ After cloning:
1216
+
1217
+ `yarn setup` -- installs required dependencies for both the main module and the demo app (runs `yarn install` within each)
1218
+
1219
+ `yarn demo` -- launch a local version of the demo playground in your browser for building and testing expressions
1220
+
1221
+ ## Tests
1222
+
1223
+ There is a comprehensive [Jest](https://jestjs.io/) test suite for all aspects of fig-tree. To run all tests:
1224
+
1225
+ `yarn test`
1226
+
1227
+ In 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.
1228
+
1229
+ Individual 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`.
1230
+
1231
+ ## Help, Feedback, Suggestions
1232
+
1233
+ Please open an issue: https://github.com/CarlosNZ/fig-tree/issues