@stackone/expressions 0.11.0 → 0.13.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 +134 -31
- package/dist/index.es.mjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,35 +202,13 @@ Collections, or arrays of objects, can be filtered by including a filter express
|
|
|
202
202
|
`{{users[.age > 25].name}}` // Returns ["John"]
|
|
203
203
|
```
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
To simplify strings usage, a more straightforward syntax is provided for string interpolation of variables using the `${var}` syntax.
|
|
208
|
-
|
|
209
|
-
Examples:
|
|
210
|
-
|
|
211
|
-
```js
|
|
212
|
-
// Given the context: { name: "John", age: 30 }
|
|
213
|
-
"Hello ${name}" // Returns "Hello John"
|
|
214
|
-
"User is ${age}" // Returns "User is 30"
|
|
215
|
-
// You can also use JEXL inside string syntax
|
|
216
|
-
"Status: ${age > 18 ? 'Adult' : 'Minor'}" // Returns "Status: Adult"
|
|
217
|
-
"Age in 5 years: ${age + 5}" // Returns "Age in 5 years: 35"
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
Note: If the expression is a string without any of the patterns described above, it will be returned as is.
|
|
221
|
-
|
|
222
|
-
```js
|
|
223
|
-
// Given the context: { name: "John", age: 30 }
|
|
224
|
-
"Hello world" // Returns "Hello world"
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## Built-in Functions
|
|
205
|
+
#### Built-in Functions
|
|
228
206
|
|
|
229
207
|
The expression handler provides several built-in functions you can use in your expressions:
|
|
230
208
|
|
|
231
|
-
|
|
209
|
+
##### Date Functions
|
|
232
210
|
|
|
233
|
-
|
|
211
|
+
###### nextAnniversary(initialDate, format)
|
|
234
212
|
|
|
235
213
|
Calculates the next anniversary date for a given date.
|
|
236
214
|
|
|
@@ -248,7 +226,7 @@ Calculates the next anniversary date for a given date.
|
|
|
248
226
|
// Returns Date object for February 15, 2026 (next year's anniversary)
|
|
249
227
|
```
|
|
250
228
|
|
|
251
|
-
|
|
229
|
+
###### yearsElapsed(startDate, format, endDate?)
|
|
252
230
|
|
|
253
231
|
Calculates the number of complete years elapsed between two dates.
|
|
254
232
|
|
|
@@ -265,7 +243,7 @@ Calculates the number of complete years elapsed between two dates.
|
|
|
265
243
|
"{{yearsElapsed('01/01/2015', 'dd/MM/yyyy')}}" // Returns 10
|
|
266
244
|
```
|
|
267
245
|
|
|
268
|
-
|
|
246
|
+
###### hasPassed(date, format, yearsToAdd?)
|
|
269
247
|
|
|
270
248
|
Determines if a given date (optionally with added years) has passed.
|
|
271
249
|
|
|
@@ -286,14 +264,25 @@ Determines if a given date (optionally with added years) has passed.
|
|
|
286
264
|
// Returns true if April 10, 2025 is after January 1, 2025
|
|
287
265
|
```
|
|
288
266
|
|
|
289
|
-
|
|
267
|
+
###### now()
|
|
290
268
|
|
|
291
|
-
|
|
269
|
+
Returns the current date and time.
|
|
292
270
|
|
|
293
|
-
|
|
271
|
+
- Returns: string (ISO 8601 format)
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
// Get the current date and time
|
|
275
|
+
"{{now()}}" // Returns "2025-04-10T12:00:00.000Z" (example)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
##### Array Functions
|
|
279
|
+
|
|
280
|
+
###### includes(array, value)
|
|
281
|
+
|
|
282
|
+
Checks if an array includes a specific value or all values from another array.
|
|
294
283
|
|
|
295
284
|
- `array` (array): The array to check
|
|
296
|
-
- `value` (any): The value to search for in the array
|
|
285
|
+
- `value` (any | array): The value(s) to search for in the array
|
|
297
286
|
- Returns: boolean
|
|
298
287
|
|
|
299
288
|
```js
|
|
@@ -306,4 +295,118 @@ Checks if an array includes a specific value.
|
|
|
306
295
|
// Can be used with context variables
|
|
307
296
|
"{{includes($.allowedRoles, $.currentUser.role)}}"
|
|
308
297
|
|
|
298
|
+
// Check if an array includes all of values of the second array
|
|
299
|
+
"{{includes([1, 2, 3, 4], [2, 4])}}" // Returns true
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
###### includesSome(array, value)
|
|
303
|
+
|
|
304
|
+
Checks if an array includes at least one value from another array or a single value.
|
|
305
|
+
|
|
306
|
+
- `array` (array): The array to check
|
|
307
|
+
- `value` (any | array): The value(s) to search for in the array
|
|
308
|
+
- Returns: boolean
|
|
309
|
+
|
|
310
|
+
```js
|
|
311
|
+
// Check if an array includes at least one value
|
|
312
|
+
"{{includesSome([1, 2, 3], 2)}}" // Returns true
|
|
313
|
+
|
|
314
|
+
// Check if an array includes at least one value from another array
|
|
315
|
+
"{{includesSome([1, 2, 3], [2, 5])}}" // Returns true
|
|
316
|
+
|
|
317
|
+
// Check if an array includes at least one value from another array (none match)
|
|
318
|
+
"{{includesSome([1, 2, 3], [4, 5])}}" // Returns false
|
|
319
|
+
|
|
320
|
+
// Can be used with context variables
|
|
321
|
+
"{{includesSome($.allowedRoles, $.currentUser.roles)}}"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
##### Object Functions
|
|
325
|
+
|
|
326
|
+
###### present(object)
|
|
327
|
+
|
|
328
|
+
Checks if an object is present (not null or undefined).
|
|
329
|
+
|
|
330
|
+
- `object` (object): The object to check
|
|
331
|
+
- Returns: boolean
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
// Check if an object is present
|
|
335
|
+
"{{present({})}}" // Returns true
|
|
336
|
+
"{{present(null)}}" // Returns false
|
|
337
|
+
"{{present(undefined)}}" // Returns false
|
|
338
|
+
"{{present('string')}}" // Returns true
|
|
339
|
+
"{{present(0)}}" // Returns true
|
|
340
|
+
"{{present([])}}" // Returns true
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
###### missing(object)
|
|
344
|
+
|
|
345
|
+
Checks if an object is missing (null or undefined).
|
|
346
|
+
|
|
347
|
+
- `object` (object): The object to check
|
|
348
|
+
- Returns: boolean
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
// Check if an object is not present
|
|
352
|
+
"{{missing({})}}" // Returns false
|
|
353
|
+
"{{missing(null)}}" // Returns true
|
|
354
|
+
"{{missing(undefined)}}" // Returns true
|
|
355
|
+
"{{missing('string')}}" // Returns false
|
|
356
|
+
"{{missing(0)}}" // Returns false
|
|
357
|
+
"{{missing([])}}" // Returns false
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
###### keys(object)
|
|
361
|
+
|
|
362
|
+
Returns the keys of an object as an array. If the input is not an object, null, or undefined, returns an empty array.
|
|
363
|
+
|
|
364
|
+
- `object` (object): The object to get keys from
|
|
365
|
+
- Returns: array of keys (string[])
|
|
366
|
+
|
|
367
|
+
```js
|
|
368
|
+
// Get keys from an object
|
|
369
|
+
"{{keys({ a: 1, b: 2 })}}" // Returns ["a", "b"]
|
|
370
|
+
|
|
371
|
+
// Get keys from a context variable
|
|
372
|
+
"{{keys($.user)}}"
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
###### values(object)
|
|
376
|
+
|
|
377
|
+
Returns the values of an object as an array. If the input is not an object, null, or undefined, returns an empty array.
|
|
378
|
+
|
|
379
|
+
- `object` (object): The object to get values from
|
|
380
|
+
- Returns: array of values (any[])
|
|
381
|
+
|
|
382
|
+
```js
|
|
383
|
+
// Get values from an object
|
|
384
|
+
"{{values({ a: 1, b: 2 })}}" // Returns [1, 2]
|
|
385
|
+
|
|
386
|
+
// Get values from a context variable
|
|
387
|
+
"{{values($.user)}}"
|
|
388
|
+
```
|
|
389
|
+
|
|
309
390
|
For more information on the JEXL syntax, refer to the [JEXL Syntax documentation](https://commons.apache.org/proper/commons-jexl/reference/syntax.html).
|
|
391
|
+
|
|
392
|
+
### String Interpolation
|
|
393
|
+
|
|
394
|
+
To simplify strings usage, a more straightforward syntax is provided for string interpolation of variables using the `${var}` syntax.
|
|
395
|
+
|
|
396
|
+
Examples:
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
// Given the context: { name: "John", age: 30 }
|
|
400
|
+
"Hello ${name}" // Returns "Hello John"
|
|
401
|
+
"User is ${age}" // Returns "User is 30"
|
|
402
|
+
// You can also use JEXL inside string syntax
|
|
403
|
+
"Status: ${age > 18 ? 'Adult' : 'Minor'}" // Returns "Status: Adult"
|
|
404
|
+
"Age in 5 years: ${age + 5}" // Returns "Age in 5 years: 35"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Note: If the expression is a string without any of the patterns described above, it will be returned as is.
|
|
408
|
+
|
|
409
|
+
```js
|
|
410
|
+
// Given the context: { name: "John", age: 30 }
|
|
411
|
+
"Hello world" // Returns "Hello world"
|
|
412
|
+
```
|
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}},d=(r,t)=>{s.parse(r);const e=s.query(t,r);if(0!==e.length)return 1===e.length?e[0]:e},y=(r,t,e)=>r.replace(t,String(e)),
|
|
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}},d=(r,t)=>{s.parse(r);const e=s.query(t,r);if(0!==e.length)return 1===e.length?e[0]:e},y=(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 y(a,r.toReplace,t)}const s=l(e,o);return n(s)?y(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=d(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=(s,l,c)=>{const y=s?.trim();if(null==y||""===y)throw new Error("Empty expression");const h=o(l)?l:{},f=((o=()=>new i.Jexl)=>{const s=o();return s.addFunction("nextAnniversary",((t,e)=>r({initialDate:t,format:e}))),s.addFunction("yearsElapsed",((r,e,a)=>t({startDate:r,endDate:a,format:e}))),s.addFunction("hasPassed",((r,t,a)=>e({date:r,yearsToAdd:a,format:t}))),s.addFunction("now",(()=>(new Date).toISOString())),s.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)))),s.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)))),s.addFunction("present",(r=>n(r))),s.addFunction("missing",(r=>a(r))),s.addFunction("keys",(r=>a(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.keys(r))),s.addFunction("values",(r=>a(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.values(r))),s.addBinaryOp("??",0,((r,t)=>a(r)?t:a(t)?r:r??t)),s})(),$=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(y),m=(r=>r.replace(/\$\./g,"").trim())($??y),g=p(m,h,f);if(g)return g;if(!$&&y.startsWith("$"))return c?.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})(y,h):((r,t)=>{try{return d(r,t)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(y,h);if(!g&&!$)return s;const A=u(f,m);if(!A||"."===m)throw new Error(`Invalid expression: "${y}"`);try{return A.evalSync(h)}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)])))},g=r=>{try{return f(r),!0}catch{return!1}};export{f as evaluate,g as isValidExpression,$ as safeEvaluate,m as safeEvaluateRecord};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e=require("@stackone/utils"),r=require("jexl"),t=require("jsonpath"),
|
|
1
|
+
"use strict";var e=require("@stackone/utils"),r=require("jexl"),t=require("jsonpath"),n=require("lodash.get");function a(e){var r=Object.create(null);return e&&Object.keys(e).forEach((function(t){if("default"!==t){var n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,n.get?n:{enumerable:!0,get:function(){return e[t]}})}})),r.default=e,Object.freeze(r)}var i=a(r);const s=/\${([^}]+)}/g,o=(e,r)=>{try{return e.compile(r)}catch{return null}},c=(e,r)=>{t.parse(e);const n=t.query(r,e);if(0!==n.length)return 1===n.length?n[0]:n},l=(e,r,t)=>e.replace(r,String(t)),u=(r,t,a)=>{const i=r.match(s);if(!i)return;const c=i.length,u=new Array(c);for(let e=0;e<c;e++)u[e]={toReplace:i[e],path:i[e].slice(2,-1)};return u.reduce(((r,i)=>((r,t,a,i)=>{const s=r.path.trim();if(!s)return i;try{const c=o(t,s);if(c){const e=c.evalSync(a);if(void 0!==e)return l(i,r.toReplace,e)}const u=n(a,s);return e.notMissing(u)?l(i,r.toReplace,u):i}catch{return i}})(i,a,t,r)),String(r))},d=(r,t,n,a)=>{if(Array.isArray(t)){const e=parseInt(n,10);if(!isNaN(e)&&(e<0||e>=t.length))return{value:void 0,error:`Invalid array index '${n}' at '${a}'`,availableKeys:t.map(((e,r)=>r.toString()))}}return e.isObject(t)?{value:void 0,error:`Key '${n}' not found at '${a}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${r} at '${a}'`,availableKeys:[]}},y=(r,t)=>{const n=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(n.test(r)){const e=c(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 n=t,a="$";for(let t=1;t<r.length;t++){const i=r[t],s=i.startsWith("[")?`${a}${i}`:`${a}.${i}`;if(Array.isArray(n)){const e=parseInt(i,10);if(isNaN(e)||e<0||e>=n.length)return d("Invalid array index",n,i,a);n=n[e],a=s}else{if(!e.isObject(n))return{value:void 0,error:`Cannot access '${i}' at '${a}' - parent is not an object`,availableKeys:[]};if(!Object.prototype.hasOwnProperty.call(n,i))return d("Key not found",n,i,a);n=n[i],a=s}}return{value:n}})(a.map((e=>e.replace(/^\['([^']+)'\]$/,"$1").replace(/^\["([^"]+)"\]$/,"$1").replace(/^\[(\d+)\]$/,"$1"))),t)}catch(e){return{value:void 0,error:`Something went wrong with evaluation of JSON path: ${String(e)}`}}},p=(r,t,n)=>{const a=r?.trim();if(null==a||""===a)throw new Error("Empty expression");const s=e.isObject(t)?t:{},l=((r=()=>new i.Jexl)=>{const t=r();return t.addFunction("nextAnniversary",((r,t)=>e.calculateNextAnniversary({initialDate:r,format:t}))),t.addFunction("yearsElapsed",((r,t,n)=>e.calculateYearsElapsed({startDate:r,endDate:n,format:t}))),t.addFunction("hasPassed",((r,t,n)=>e.dateHasPassed({date:r,yearsToAdd:n,format:t}))),t.addFunction("now",(()=>(new Date).toISOString())),t.addFunction("includes",((r,t)=>!(e.isMissing(r)||!Array.isArray(r)||0===r.length||e.isMissing(t))&&(Array.isArray(t)?t.every((e=>r.includes(e))):r.includes(t)))),t.addFunction("includesSome",((r,t)=>!(e.isMissing(r)||!Array.isArray(r)||0===r.length||e.isMissing(t))&&(Array.isArray(t)?t.some((e=>r.includes(e))):r.includes(t)))),t.addFunction("present",(r=>e.notMissing(r))),t.addFunction("missing",(r=>e.isMissing(r))),t.addFunction("keys",(r=>e.isMissing(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.keys(r))),t.addFunction("values",(r=>e.isMissing(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.values(r))),t.addBinaryOp("??",0,((r,t)=>e.isMissing(r)?t:e.isMissing(t)?r:r??t)),t})(),d=(e=>{if(e.startsWith("{{")&&e.endsWith("}}"))return e.slice(2,-2)})(a),p=(e=>e.replace(/\$\./g,"").trim())(d??a),v=u(p,s,l);if(v)return v;if(!d&&a.startsWith("$"))return n?.incrementalJsonPath?((e,r)=>{const t=y(e,r);if(t.error)throw new Error(`${t.error}${t.availableKeys?.length?`. Available keys: ${t.availableKeys.join(", ")}`:""}`);return t.value})(a,s):((e,r)=>{try{return c(e,r)}catch{throw new Error(`Invalid JSON path: "${e}"`)}})(a,s);if(!v&&!d)return r;const f=o(l,p);if(!f||"."===p)throw new Error(`Invalid expression: "${a}"`);try{return f.evalSync(s)}catch{return}},v=(e,r)=>{try{return p(e,r)}catch{return null}},f=(e,r)=>{const t=e=>Array.isArray(e)?e.map(t):"object"==typeof e&&null!==e?f(e,r):"string"==typeof e?v(e,r):e;return Object.fromEntries(Object.entries(e).map((([e,r])=>[e,t(r)])))};exports.evaluate=p,exports.isValidExpression=e=>{try{return p(e),!0}catch{return!1}},exports.safeEvaluate=v,exports.safeEvaluateRecord=f;
|