@stackone/expressions 0.10.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -31
- package/dist/index.es.mjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,6 +154,7 @@ This kind of expression is enclosed in double brackets `{{expression}}`. It supp
|
|
|
154
154
|
| `<=` | Less than or equal |
|
|
155
155
|
| `in` | Element of string or array |
|
|
156
156
|
| `? :` | Ternary operator |
|
|
157
|
+
| `??` | Nullish coalescing operator |
|
|
157
158
|
|
|
158
159
|
Examples:
|
|
159
160
|
|
|
@@ -165,6 +166,7 @@ Examples:
|
|
|
165
166
|
'{{x == 10 ? "yes" : "no"}}' // Returns "yes"
|
|
166
167
|
'{{x in [1, 2, 3]}}' // Returns false
|
|
167
168
|
'{{x != y}}' // Returns true
|
|
169
|
+
'{{x ?? y}}' // Returns 10
|
|
168
170
|
```
|
|
169
171
|
|
|
170
172
|
#### Identifiers
|
|
@@ -200,35 +202,13 @@ Collections, or arrays of objects, can be filtered by including a filter express
|
|
|
200
202
|
`{{users[.age > 25].name}}` // Returns ["John"]
|
|
201
203
|
```
|
|
202
204
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
To simplify strings usage, a more straightforward syntax is provided for string interpolation of variables using the `${var}` syntax.
|
|
206
|
-
|
|
207
|
-
Examples:
|
|
208
|
-
|
|
209
|
-
```js
|
|
210
|
-
// Given the context: { name: "John", age: 30 }
|
|
211
|
-
"Hello ${name}" // Returns "Hello John"
|
|
212
|
-
"User is ${age}" // Returns "User is 30"
|
|
213
|
-
// You can also use JEXL inside string syntax
|
|
214
|
-
"Status: ${age > 18 ? 'Adult' : 'Minor'}" // Returns "Status: Adult"
|
|
215
|
-
"Age in 5 years: ${age + 5}" // Returns "Age in 5 years: 35"
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Note: If the expression is a string without any of the patterns described above, it will be returned as is.
|
|
219
|
-
|
|
220
|
-
```js
|
|
221
|
-
// Given the context: { name: "John", age: 30 }
|
|
222
|
-
"Hello world" // Returns "Hello world"
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## Built-in Functions
|
|
205
|
+
#### Built-in Functions
|
|
226
206
|
|
|
227
207
|
The expression handler provides several built-in functions you can use in your expressions:
|
|
228
208
|
|
|
229
|
-
|
|
209
|
+
##### Date Functions
|
|
230
210
|
|
|
231
|
-
|
|
211
|
+
###### nextAnniversary(initialDate, format)
|
|
232
212
|
|
|
233
213
|
Calculates the next anniversary date for a given date.
|
|
234
214
|
|
|
@@ -246,7 +226,7 @@ Calculates the next anniversary date for a given date.
|
|
|
246
226
|
// Returns Date object for February 15, 2026 (next year's anniversary)
|
|
247
227
|
```
|
|
248
228
|
|
|
249
|
-
|
|
229
|
+
###### yearsElapsed(startDate, format, endDate?)
|
|
250
230
|
|
|
251
231
|
Calculates the number of complete years elapsed between two dates.
|
|
252
232
|
|
|
@@ -263,7 +243,7 @@ Calculates the number of complete years elapsed between two dates.
|
|
|
263
243
|
"{{yearsElapsed('01/01/2015', 'dd/MM/yyyy')}}" // Returns 10
|
|
264
244
|
```
|
|
265
245
|
|
|
266
|
-
|
|
246
|
+
###### hasPassed(date, format, yearsToAdd?)
|
|
267
247
|
|
|
268
248
|
Determines if a given date (optionally with added years) has passed.
|
|
269
249
|
|
|
@@ -284,14 +264,14 @@ Determines if a given date (optionally with added years) has passed.
|
|
|
284
264
|
// Returns true if April 10, 2025 is after January 1, 2025
|
|
285
265
|
```
|
|
286
266
|
|
|
287
|
-
|
|
267
|
+
##### Array Functions
|
|
288
268
|
|
|
289
|
-
|
|
269
|
+
###### includes(array, value)
|
|
290
270
|
|
|
291
|
-
Checks if an array includes a specific value.
|
|
271
|
+
Checks if an array includes a specific value or all values from another array.
|
|
292
272
|
|
|
293
273
|
- `array` (array): The array to check
|
|
294
|
-
- `value` (any): The value to search for in the array
|
|
274
|
+
- `value` (any | array): The value(s) to search for in the array
|
|
295
275
|
- Returns: boolean
|
|
296
276
|
|
|
297
277
|
```js
|
|
@@ -304,4 +284,84 @@ Checks if an array includes a specific value.
|
|
|
304
284
|
// Can be used with context variables
|
|
305
285
|
"{{includes($.allowedRoles, $.currentUser.role)}}"
|
|
306
286
|
|
|
287
|
+
// Check if an array includes all of values of the second array
|
|
288
|
+
"{{includes([1, 2, 3, 4], [2, 4])}}" // Returns true
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
###### includesSome(array, value)
|
|
292
|
+
|
|
293
|
+
Checks if an array includes at least one value from another array or a single value.
|
|
294
|
+
|
|
295
|
+
- `array` (array): The array to check
|
|
296
|
+
- `value` (any | array): The value(s) to search for in the array
|
|
297
|
+
- Returns: boolean
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
// Check if an array includes at least one value
|
|
301
|
+
"{{includesSome([1, 2, 3], 2)}}" // Returns true
|
|
302
|
+
|
|
303
|
+
// Check if an array includes at least one value from another array
|
|
304
|
+
"{{includesSome([1, 2, 3], [2, 5])}}" // Returns true
|
|
305
|
+
|
|
306
|
+
// Check if an array includes at least one value from another array (none match)
|
|
307
|
+
"{{includesSome([1, 2, 3], [4, 5])}}" // Returns false
|
|
308
|
+
|
|
309
|
+
// Can be used with context variables
|
|
310
|
+
"{{includesSome($.allowedRoles, $.currentUser.roles)}}"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
##### Object Functions
|
|
314
|
+
|
|
315
|
+
###### keys(object)
|
|
316
|
+
|
|
317
|
+
Returns the keys of an object as an array. If the input is not an object, null, or undefined, returns an empty array.
|
|
318
|
+
|
|
319
|
+
- `object` (object): The object to get keys from
|
|
320
|
+
- Returns: array of keys (string[])
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
// Get keys from an object
|
|
324
|
+
"{{keys({ a: 1, b: 2 })}}" // Returns ["a", "b"]
|
|
325
|
+
|
|
326
|
+
// Get keys from a context variable
|
|
327
|
+
"{{keys($.user)}}"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
###### values(object)
|
|
331
|
+
|
|
332
|
+
Returns the values of an object as an array. If the input is not an object, null, or undefined, returns an empty array.
|
|
333
|
+
|
|
334
|
+
- `object` (object): The object to get values from
|
|
335
|
+
- Returns: array of values (any[])
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
// Get values from an object
|
|
339
|
+
"{{values({ a: 1, b: 2 })}}" // Returns [1, 2]
|
|
340
|
+
|
|
341
|
+
// Get values from a context variable
|
|
342
|
+
"{{values($.user)}}"
|
|
343
|
+
```
|
|
344
|
+
|
|
307
345
|
For more information on the JEXL syntax, refer to the [JEXL Syntax documentation](https://commons.apache.org/proper/commons-jexl/reference/syntax.html).
|
|
346
|
+
|
|
347
|
+
### String Interpolation
|
|
348
|
+
|
|
349
|
+
To simplify strings usage, a more straightforward syntax is provided for string interpolation of variables using the `${var}` syntax.
|
|
350
|
+
|
|
351
|
+
Examples:
|
|
352
|
+
|
|
353
|
+
```js
|
|
354
|
+
// Given the context: { name: "John", age: 30 }
|
|
355
|
+
"Hello ${name}" // Returns "Hello John"
|
|
356
|
+
"User is ${age}" // Returns "User is 30"
|
|
357
|
+
// You can also use JEXL inside string syntax
|
|
358
|
+
"Status: ${age > 18 ? 'Adult' : 'Minor'}" // Returns "Status: Adult"
|
|
359
|
+
"Age in 5 years: ${age + 5}" // Returns "Age in 5 years: 35"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Note: If the expression is a string without any of the patterns described above, it will be returned as is.
|
|
363
|
+
|
|
364
|
+
```js
|
|
365
|
+
// Given the context: { name: "John", age: 30 }
|
|
366
|
+
"Hello world" // Returns "Hello world"
|
|
367
|
+
```
|
package/dist/index.es.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{calculateNextAnniversary as r,calculateYearsElapsed as t,dateHasPassed as e,isMissing as a,notMissing as n,isObject as o}from"@stackone/utils";import*as i from"jexl";import s from"jsonpath";import l from"lodash.get";const c=/\${([^}]+)}/g,u=(r,t)=>{try{return r.compile(t)}catch{return null}},
|
|
1
|
+
import{calculateNextAnniversary as r,calculateYearsElapsed as t,dateHasPassed as e,isMissing as a,notMissing as n,isObject as o}from"@stackone/utils";import*as i from"jexl";import s from"jsonpath";import l from"lodash.get";const c=/\${([^}]+)}/g,u=(r,t)=>{try{return r.compile(t)}catch{return null}},y=(r,t)=>{s.parse(r);const e=s.query(t,r);if(0!==e.length)return 1===e.length?e[0]:e},d=(r,t,e)=>r.replace(t,String(e)),p=(r,t,e)=>{const a=r.match(c);if(!a)return;const o=a.length,i=new Array(o);for(let r=0;r<o;r++)i[r]={toReplace:a[r],path:a[r].slice(2,-1)};return i.reduce(((r,a)=>((r,t,e,a)=>{const o=r.path.trim();if(!o)return a;try{const i=u(t,o);if(i){const t=i.evalSync(e);if(void 0!==t)return d(a,r.toReplace,t)}const s=l(e,o);return n(s)?d(a,r.toReplace,s):a}catch{return a}})(a,e,t,r)),String(r))},h=(r,t,e,a)=>{if(Array.isArray(t)){const r=parseInt(e,10);if(!isNaN(r)&&(r<0||r>=t.length))return{value:void 0,error:`Invalid array index '${e}' at '${a}'`,availableKeys:t.map(((r,t)=>t.toString()))}}return o(t)?{value:void 0,error:`Key '${e}' not found at '${a}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${r} at '${a}'`,availableKeys:[]}},v=(r,t)=>{const e=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(e.test(r)){const e=y(r,t);return void 0===e?{value:void 0,error:`Invalid or empty JSONPath: '${r}'`}:{value:e}}const a=r.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[];return((r,t)=>{if("$"!==r[0])return{value:void 0,error:"JSON path must start with $"};let e=t,a="$";for(let t=1;t<r.length;t++){const n=r[t],i=n.startsWith("[")?`${a}${n}`:`${a}.${n}`;if(Array.isArray(e)){const r=parseInt(n,10);if(isNaN(r)||r<0||r>=e.length)return h("Invalid array index",e,n,a);e=e[r],a=i}else{if(!o(e))return{value:void 0,error:`Cannot access '${n}' at '${a}' - parent is not an object`,availableKeys:[]};if(!Object.prototype.hasOwnProperty.call(e,n))return h("Key not found",e,n,a);e=e[n],a=i}}return{value:e}})(a.map((r=>r.replace(/^\['([^']+)'\]$/,"$1").replace(/^\["([^"]+)"\]$/,"$1").replace(/^\[(\d+)\]$/,"$1"))),t)}catch(r){return{value:void 0,error:`Something went wrong with evaluation of JSON path: ${String(r)}`}}},f=(n,s,l)=>{const c=n?.trim();if(null==c||""===c)throw new Error("Empty expression");const d=o(s)?s:{},h=((n=()=>new i.Jexl)=>{const o=n();return o.addFunction("nextAnniversary",((t,e)=>r({initialDate:t,format:e}))),o.addFunction("yearsElapsed",((r,e,a)=>t({startDate:r,endDate:a,format:e}))),o.addFunction("hasPassed",((r,t,a)=>e({date:r,yearsToAdd:a,format:t}))),o.addFunction("includes",((r,t)=>!(a(r)||!Array.isArray(r)||0===r.length||a(t))&&(Array.isArray(t)?t.every((t=>r.includes(t))):r.includes(t)))),o.addFunction("includesSome",((r,t)=>!(a(r)||!Array.isArray(r)||0===r.length||a(t))&&(Array.isArray(t)?t.some((t=>r.includes(t))):r.includes(t)))),o.addFunction("keys",(r=>a(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.keys(r))),o.addFunction("values",(r=>a(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.values(r))),o.addBinaryOp("??",0,((r,t)=>a(r)?t:a(t)?r:r??t)),o})(),f=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(c),$=(r=>r.replace(/\$\./g,"").trim())(f??c),m=p($,d,h);if(m)return m;if(!f&&c.startsWith("$"))return l?.incrementalJsonPath?((r,t)=>{const e=v(r,t);if(e.error)throw new Error(`${e.error}${e.availableKeys?.length?`. Available keys: ${e.availableKeys.join(", ")}`:""}`);return e.value})(c,d):((r,t)=>{try{return y(r,t)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(c,d);if(!m&&!f)return n;const A=u(h,$);if(!A||"."===$)throw new Error(`Invalid expression: "${c}"`);try{return A.evalSync(d)}catch{return}},$=(r,t)=>{try{return f(r,t)}catch{return null}},m=(r,t)=>{const e=r=>Array.isArray(r)?r.map(e):"object"==typeof r&&null!==r?m(r,t):"string"==typeof r?$(r,t):r;return Object.fromEntries(Object.entries(r).map((([r,t])=>[r,e(t)])))},A=r=>{try{return f(r),!0}catch{return!1}};export{f as evaluate,A as isValidExpression,$ as safeEvaluate,m as safeEvaluateRecord};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var r=require("@stackone/utils"),e=require("jexl"),t=require("jsonpath"),a=require("lodash.get");function n(r){var e=Object.create(null);return r&&Object.keys(r).forEach((function(t){if("default"!==t){var a=Object.getOwnPropertyDescriptor(r,t);Object.defineProperty(e,t,a.get?a:{enumerable:!0,get:function(){return r[t]}})}})),e.default=r,Object.freeze(e)}var i=n(e);const s=/\${([^}]+)}/g,o=(r,e)=>{try{return r.compile(e)}catch{return null}},c=(r,e)=>{t.parse(r);const a=t.query(e,r);if(0!==a.length)return 1===a.length?a[0]:a},l=(r,e,t)=>r.replace(e,String(t)),u=(e,t,n)=>{const i=e.match(s);if(!i)return;const c=i.length,u=new Array(c);for(let r=0;r<c;r++)u[r]={toReplace:i[r],path:i[r].slice(2,-1)};return u.reduce(((e,i)=>((e,t,n,i)=>{const s=e.path.trim();if(!s)return i;try{const c=o(t,s);if(c){const r=c.evalSync(n);if(void 0!==r)return l(i,e.toReplace,r)}const u=a(n,s);return r.notMissing(u)?l(i,e.toReplace,u):i}catch{return i}})(i,n,t,e)),String(e))},d=(e,t,a,n)=>{if(Array.isArray(t)){const r=parseInt(a,10);if(!isNaN(r)&&(r<0||r>=t.length))return{value:void 0,error:`Invalid array index '${a}' at '${n}'`,availableKeys:t.map(((r,e)=>e.toString()))}}return r.isObject(t)?{value:void 0,error:`Key '${a}' not found at '${n}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${e} at '${n}'`,availableKeys:[]}},y=(e,t)=>{const a=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(a.test(e)){const r=c(e,t);return void 0===r?{value:void 0,error:`Invalid or empty JSONPath: '${e}'`}:{value:r}}const n=e.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[];return((e,t)=>{if("$"!==e[0])return{value:void 0,error:"JSON path must start with $"};let a=t,n="$";for(let t=1;t<e.length;t++){const i=e[t],s=i.startsWith("[")?`${n}${i}`:`${n}.${i}`;if(Array.isArray(a)){const r=parseInt(i,10);if(isNaN(r)||r<0||r>=a.length)return d("Invalid array index",a,i,n);a=a[r],n=s}else{if(!r.isObject(a))return{value:void 0,error:`Cannot access '${i}' at '${n}' - parent is not an object`,availableKeys:[]};if(!Object.prototype.hasOwnProperty.call(a,i))return d("Key not found",a,i,n);a=a[i],n=s}}return{value:a}})(n.map((r=>r.replace(/^\['([^']+)'\]$/,"$1").replace(/^\["([^"]+)"\]$/,"$1").replace(/^\[(\d+)\]$/,"$1"))),t)}catch(r){return{value:void 0,error:`Something went wrong with evaluation of JSON path: ${String(r)}`}}},v=(e,t,a)=>{const n=e?.trim();if(null==n||""===n)throw new Error("Empty expression");const s=r.isObject(t)?t:{},l=((e=()=>new i.Jexl)=>{const t=e();return t.addFunction("nextAnniversary",((e,t)=>r.calculateNextAnniversary({initialDate:e,format:t}))),t.addFunction("yearsElapsed",((e,t,a)=>r.calculateYearsElapsed({startDate:e,endDate:a,format:t}))),t.addFunction("hasPassed",((e,t,a)=>r.dateHasPassed({date:e,yearsToAdd:a,format:t}))),t.addFunction("includes",((e,t)=>!(r.isMissing(e)||!Array.isArray(e)||0===e.length||r.isMissing(t))&&(Array.isArray(t)?t.every((r=>e.includes(r))):e.includes(t)))),t.addFunction("includesSome",((e,t)=>!(r.isMissing(e)||!Array.isArray(e)||0===e.length||r.isMissing(t))&&(Array.isArray(t)?t.some((r=>e.includes(r))):e.includes(t)))),t.addFunction("keys",(e=>r.isMissing(e)||"object"!=typeof e||Array.isArray(e)?[]:Object.keys(e))),t.addFunction("values",(e=>r.isMissing(e)||"object"!=typeof e||Array.isArray(e)?[]:Object.values(e))),t.addBinaryOp("??",0,((e,t)=>r.isMissing(e)?t:r.isMissing(t)?e:e??t)),t})(),d=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(n),v=(r=>r.replace(/\$\./g,"").trim())(d??n),p=u(v,s,l);if(p)return p;if(!d&&n.startsWith("$"))return a?.incrementalJsonPath?((r,e)=>{const t=y(r,e);if(t.error)throw new Error(`${t.error}${t.availableKeys?.length?`. Available keys: ${t.availableKeys.join(", ")}`:""}`);return t.value})(n,s):((r,e)=>{try{return c(r,e)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(n,s);if(!p&&!d)return e;const f=o(l,v);if(!f||"."===v)throw new Error(`Invalid expression: "${n}"`);try{return f.evalSync(s)}catch{return}},p=(r,e)=>{try{return v(r,e)}catch{return null}},f=(r,e)=>{const t=r=>Array.isArray(r)?r.map(t):"object"==typeof r&&null!==r?f(r,e):"string"==typeof r?p(r,e):r;return Object.fromEntries(Object.entries(r).map((([r,e])=>[r,t(e)])))};exports.evaluate=v,exports.isValidExpression=r=>{try{return v(r),!0}catch{return!1}},exports.safeEvaluate=p,exports.safeEvaluateRecord=f;
|