@stackone/expressions 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,13 +1,12 @@
1
1
  # @stackone/expressions
2
+
2
3
  ## Description
3
4
 
4
5
  This package can be used to parse and evaluate string expressions with support for variables replacement, functions and operators.
5
6
 
6
- This package uses the tech stack provided by the **Connect Monorepo** global setup. Please check the [root README](../../README.md) for more information.
7
-
8
7
  ## Requirements
9
8
 
10
- Please check the [root README](../../README.md) for requirements.
9
+ Node.js 20+ is required to run this project.
11
10
 
12
11
  ## Installation
13
12
 
@@ -47,3 +46,169 @@ $ npm run lint
47
46
  # run linter and try to fix any error
48
47
  $ npm run lint:fix
49
48
  ```
49
+
50
+ ## API Reference
51
+
52
+ ### evaluate(expression: string, context?: object)
53
+
54
+ Evaluates the given expression using the provided context.
55
+
56
+ - Returns the evaluated result
57
+ - Throws an error if the expression is invalid or evaluation fails
58
+
59
+ ```js
60
+ evaluate("$.user.name", { user: { name: "John" } }); // Returns "John"
61
+ evaluate("x + y", { x: 1, y: 2 }); // Returns 3
62
+ ```
63
+
64
+ ### isValidExpression(expression: string)
65
+
66
+ Checks if the given expression is valid.
67
+
68
+ - Returns `true` if the expression is valid
69
+ - Returns `false` otherwise
70
+
71
+ ```js
72
+ isValidExpression("$.user.name"); // Returns true
73
+ isValidExpression("invalid $$$ expression"); // Returns false
74
+ ```
75
+
76
+ ### safeEvaluate(expression: string, context?: object)
77
+
78
+ Safely evaluates the expression without throwing errors.
79
+
80
+ - Returns the evaluated result if successful
81
+ - Returns `null` if evaluation fails or the expression is invalid
82
+
83
+ ```js
84
+ safeEvaluate("$.user.name", { user: { name: "John" } }); // Returns "John"
85
+ safeEvaluate("$ invalid expression", {}); // Returns null
86
+ ```
87
+
88
+ ## Expression language syntax
89
+
90
+ There are three types of expressions supported:
91
+
92
+ ### JSON Path Expressions
93
+
94
+ When the expression starts with `$`, it is treated as a JSON Path expression and will be evaluated as such.
95
+
96
+ #### JSON Path Syntax
97
+
98
+ | JSON Path | Description |
99
+ | --- | --- |
100
+ | `$` | The root object |
101
+ | `.` | Child operator |
102
+ | `@` | The current object |
103
+ | `*` | Wildcard. All elements in an array, or all properties of an object |
104
+ | `..` | Recursive descent |
105
+ | `[]` | Subscript operator |
106
+ | `[,]` | Union operator |
107
+ | `[start:end:step]` | Array slice operator |
108
+ | `?(expression)` | Filter expression |
109
+ | `()` | Script expression |
110
+
111
+ Examples:
112
+
113
+ ```js
114
+ // Given the context: { user: { name: "John", age: 30 }, "info/email": "info@email.com" }
115
+ '$.user.name' // Returns "John"
116
+ '$.user.age' // Returns 30
117
+ '$.user[*]' // Returns ["John", 30]
118
+ '$["info/email"]' // Returns "info@email.com"
119
+ ```
120
+
121
+ For more information on JSON Path syntax, refer to the original [JSON Path documentation](https://goessner.net/articles/JsonPath/).
122
+
123
+ ### JEXL Expressions
124
+
125
+ This kind of expression is enclosed in double brackets `{{expression}}`. It supports variables and operators.
126
+
127
+ #### Operators
128
+
129
+ | Operator | Description |
130
+ | --- | --- |
131
+ | `!` | Logical NOT |
132
+ | `+` | Addition, string concatenation |
133
+ | `-` | Subtraction |
134
+ | `*` | Multiplication |
135
+ | `/` | Division |
136
+ | `//` | Floor division |
137
+ | `%` | Modulus |
138
+ | `^` | Exponentiation |
139
+ | `&&` | Logical AND |
140
+ | `\|\|` | Logical OR |
141
+ | `==` | Equal |
142
+ | `!=` | Not equal |
143
+ | `>` | Greater than |
144
+ | `>=` | Greater than or equal |
145
+ | `<` | Less than |
146
+ | `<=` | Less than or equal |
147
+ | `in` | Element of string or array |
148
+ | `? :` | Ternary operator |
149
+
150
+ Examples:
151
+
152
+ ```js
153
+ // Given the context: { x: 10, y: 5 }
154
+ '{{x + y}}' // Returns 15
155
+ '{{x * 2}}' // Returns 20
156
+ '{{x > y}}' // Returns true
157
+ '{{x == 10 ? "yes" : "no"}}' // Returns "yes"
158
+ '{{x in [1, 2, 3]}}' // Returns false
159
+ '{{x != y}}' // Returns true
160
+ ```
161
+
162
+ #### Identifiers
163
+
164
+ Identifiers can be used to reference variables in the context.
165
+
166
+ ```js
167
+ // Given the context:
168
+ // {
169
+ // name: {
170
+ // first: "John",
171
+ // last: "Smith"
172
+ // },
173
+ // jobs: ["Developer", "Designer"]
174
+ // }
175
+ `{{name.first}}` // Returns "John"
176
+ `{{jobs[1]}}` // Returns "Designer"
177
+ ```
178
+
179
+ #### Collections
180
+
181
+ Collections, or arrays of objects, can be filtered by including a filter expression in brackets.
182
+
183
+ ```js
184
+ // Given the context:
185
+ // {
186
+ // users: [
187
+ // { name: "John", age: 30 },
188
+ // { name: "Jane", age: 25 }
189
+ // ]
190
+ // }
191
+ `{{users[.name == "John"].age}}` // Returns 30
192
+ `{{users[.age > 25].name}}` // Returns ["John"]
193
+ ```
194
+
195
+ ### String Interpolation
196
+
197
+ To simplify strings usage, a more straightforward syntax is provided for string interpolation of variables using the `${var}` syntax.
198
+
199
+ Examples:
200
+
201
+ ```js
202
+ // Given the context: { name: "John", age: 30 }
203
+ "Hello ${name}" // Returns "Hello John"
204
+ "User is ${age}" // Returns "User is 30"
205
+ ```
206
+
207
+ Note: If the expression is a string without any of the patterns described above, it will be returned as is.
208
+
209
+ ```js
210
+ // Given the context: { name: "John", age: 30 }
211
+ "Hello world" // Returns "Hello world"
212
+ ```
213
+
214
+ For more information on the JEXL syntax, refer to the [JEXL Syntax documentation](https://commons.apache.org/proper/commons-jexl/reference/syntax.html).
package/dist/index.es.mjs CHANGED
@@ -1 +1 @@
1
- import{isObject as t,notMissing as r}from"@stackone/utils";import e from"lodash.get";import*as n from"jexl";const o=/\${([^}]+)}/g,c=(t=()=>new n.Jexl)=>t(),l=t=>t.replace(/\$\./g,"").trim(),a=(t,r)=>{try{return t.compile(r)}catch(t){return null}},s=(r,e)=>{const n=t(e)?e:{},o=c(),s=l(r),p=u(s,n);if(p)return p;const i=a(o,s);if(!i)return null;try{return i.evalSync(n)}catch(t){return null}},u=(t,n)=>{const c=t.match(o)?.map((t=>({toReplace:t,path:t.slice(2,-1)})));if(!c)return;let l=String(t);for(const t of c){const o=e(n,t.path);r(o)&&(l=l.replace(t.toReplace,`${String(o)}`))}return l},p=t=>{const r=c(),e=l(t),n=a(r,e);return!!i(e)||!(!n||"."===e)},i=t=>new RegExp(o).test(t);export{s as evaluate,i as isInterpolatedExpression,p as isValidExpression};
1
+ import{notMissing as r,isObject as t}from"@stackone/utils";import*as e from"jexl";import n from"jsonpath";import o from"lodash.get";const a=/\${([^}]+)}/g,c=(c,s)=>{const i=c?.trim();if(null==i||""===i)throw new Error("Empty expression");const l=t(s)?s:{},u=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(i),p=u??i,h=((r=()=>new e.Jexl)=>r())(),f=(r=>r.replace(/\$\./g,"").trim())(p),m=((t,e)=>{const n=t.match(a)?.map((r=>({toReplace:r,path:r.slice(2,-1)})));if(!n)return;let c=String(t);for(const t of n){const n=o(e,t.path);r(n)&&(c=c.replace(t.toReplace,`${String(n)}`))}return c})(f,l);if(m)return m;try{if(!u&&i.startsWith("$"))return((r,t)=>(n.parse(r),n.query(t,r)[0]))(i,l)}catch(r){throw new Error(`Invalid JSON path: "${i}"`)}if(!m&&!u)return c;const y=((r,t)=>{try{return r.compile(t)}catch(r){return null}})(h,f);if(!y||"."===f)throw new Error(`Invalid expression: "${i}"`);try{return y.evalSync(l)}catch(r){return}},s=(r,t)=>{try{return c(r,t)}catch(r){return null}},i=r=>{try{return c(r),!0}catch(r){return!1}};export{c as evaluate,i as isValidExpression,s as safeEvaluate};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("@stackone/utils"),t=require("lodash.get");function r(e){var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}var n=r(require("jexl"));const c=/\${([^}]+)}/g,o=(e=()=>new n.Jexl)=>e(),s=e=>e.replace(/\$\./g,"").trim(),i=(e,t)=>{try{return e.compile(t)}catch(e){return null}},u=(r,n)=>{const o=r.match(c)?.map((e=>({toReplace:e,path:e.slice(2,-1)})));if(!o)return;let s=String(r);for(const r of o){const c=t(n,r.path);e.notMissing(c)&&(s=s.replace(r.toReplace,`${String(c)}`))}return s},a=e=>new RegExp(c).test(e);exports.evaluate=(t,r)=>{const n=e.isObject(r)?r:{},c=o(),a=s(t),l=u(a,n);if(l)return l;const p=i(c,a);if(!p)return null;try{return p.evalSync(n)}catch(e){return null}},exports.isInterpolatedExpression=a,exports.isValidExpression=e=>{const t=o(),r=s(e),n=i(t,r);return!!a(r)||!(!n||"."===r)};
1
+ "use strict";var r=require("@stackone/utils"),e=require("jexl"),t=require("jsonpath"),n=require("lodash.get");function c(r){var e=Object.create(null);return r&&Object.keys(r).forEach((function(t){if("default"!==t){var n=Object.getOwnPropertyDescriptor(r,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:function(){return r[t]}})}})),e.default=r,Object.freeze(e)}var i=c(e);const a=/\${([^}]+)}/g,s=(e,c)=>{const s=e?.trim();if(null==s||""===s)throw new Error("Empty expression");const o=r.isObject(c)?c:{},u=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(s),l=u??s,p=((r=()=>new i.Jexl)=>r())(),f=(r=>r.replace(/\$\./g,"").trim())(l),h=((e,t)=>{const c=e.match(a)?.map((r=>({toReplace:r,path:r.slice(2,-1)})));if(!c)return;let i=String(e);for(const e of c){const c=n(t,e.path);r.notMissing(c)&&(i=i.replace(e.toReplace,`${String(c)}`))}return i})(f,o);if(h)return h;try{if(!u&&s.startsWith("$"))return((r,e)=>(t.parse(r),t.query(e,r)[0]))(s,o)}catch(r){throw new Error(`Invalid JSON path: "${s}"`)}if(!h&&!u)return e;const y=((r,e)=>{try{return r.compile(e)}catch(r){return null}})(p,f);if(!y||"."===f)throw new Error(`Invalid expression: "${s}"`);try{return y.evalSync(o)}catch(r){return}};exports.evaluate=s,exports.isValidExpression=r=>{try{return s(r),!0}catch(r){return!1}},exports.safeEvaluate=(r,e)=>{try{return s(r,e)}catch(r){return null}};
@@ -1 +1,2 @@
1
- export declare const evaluateExpression: (expression: string, context?: unknown) => unknown;
1
+ export declare const evaluateExpression: (expression?: string | null, context?: unknown) => unknown;
2
+ export declare const safeEvaluateExpression: (expression?: string | null, context?: unknown) => unknown;
@@ -1,2 +1,3 @@
1
1
  export { evaluateExpression as evaluate } from './evaluate';
2
- export { isValidExpression, isInterpolatedExpression } from './validate';
2
+ export { safeEvaluateExpression as safeEvaluate } from './evaluate';
3
+ export { isValidExpression } from './validate';
@@ -1,5 +1,9 @@
1
1
  import Expression from 'jexl/Expression';
2
+ import { ExpressionContext } from './types';
2
3
  export declare const cleanExpression: (expression: string) => string;
3
4
  export declare const compileExpression: (expressionHandler: {
4
5
  compile: (expr: string) => Expression;
5
6
  }, expression: string) => Expression | null;
7
+ export declare const extractExpressionBetweenDoubleCurlyBraces: (expression: string) => string | undefined;
8
+ export declare const evaluateJsonPath: (expression: string, context: unknown) => unknown;
9
+ export declare const evaluateStringInterpolations: (expression: string, context: ExpressionContext) => string | undefined;
@@ -1,2 +1 @@
1
- export declare const isValidExpression: (expression: string) => boolean;
2
- export declare const isInterpolatedExpression: (expression: string) => boolean;
1
+ export declare const isValidExpression: (expression?: string | null) => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackone/expressions",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.mjs",
@@ -34,10 +34,12 @@
34
34
  "dependencies": {
35
35
  "@stackone/utils": "*",
36
36
  "jexl": "^2.3.0",
37
+ "jsonpath": "^1.1.1",
37
38
  "lodash.get": "^4.4.2"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/jexl": "^2.3.4",
42
+ "@types/jsonpath": "^0.2.4",
41
43
  "@types/lodash.get": "^4.4.9"
42
44
  }
43
45
  }