@stackone/expressions 0.8.0 → 0.10.2

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
@@ -222,4 +222,86 @@ Note: If the expression is a string without any of the patterns described above,
222
222
  "Hello world" // Returns "Hello world"
223
223
  ```
224
224
 
225
+ ## Built-in Functions
226
+
227
+ The expression handler provides several built-in functions you can use in your expressions:
228
+
229
+ ### Date Functions
230
+
231
+ #### nextAnniversary(initialDate, format)
232
+
233
+ Calculates the next anniversary date for a given date.
234
+
235
+ - `initialDate` (string): The initial date string
236
+ - `format` (string): The format of the date string (uses date-fns format)
237
+ - Returns: Date object or null
238
+
239
+ ```js
240
+ // If today is April 10, 2025, and a birthday is December 25, 2000
241
+ "{{nextAnniversary('25/12/2000', 'dd/MM/yyyy')}}"
242
+ // Returns Date object for December 25, 2025 (this year's anniversary)
243
+
244
+ // If today is April 10, 2025, and a birthday is February 15, 1990
245
+ "{{nextAnniversary('15/02/1990', 'dd/MM/yyyy')}}"
246
+ // Returns Date object for February 15, 2026 (next year's anniversary)
247
+ ```
248
+
249
+ #### yearsElapsed(startDate, format, endDate?)
250
+
251
+ Calculates the number of complete years elapsed between two dates.
252
+
253
+ - `startDate` (string): The start date string
254
+ - `format` (string): The format of the date string (uses date-fns format)
255
+ - `endDate` (string, optional): The end date string, defaults to current date if omitted
256
+ - Returns: number of years or null
257
+
258
+ ```js
259
+ // Calculate years between two specific dates
260
+ "{{yearsElapsed('01/01/2015', 'dd/MM/yyyy', '01/01/2025')}}" // Returns 10
261
+
262
+ // Calculate years from a date to today (assuming today is April 10, 2025)
263
+ "{{yearsElapsed('01/01/2015', 'dd/MM/yyyy')}}" // Returns 10
264
+ ```
265
+
266
+ #### hasPassed(date, format, yearsToAdd?)
267
+
268
+ Determines if a given date (optionally with added years) has passed.
269
+
270
+ - `date` (string): The date to check
271
+ - `format` (string): The format of the date string (uses date-fns format)
272
+ - `yearsToAdd` (number, optional): Number of years to add to the date before comparing
273
+ - Returns: boolean
274
+
275
+ ```js
276
+ // Check if a date has passed (assuming today is April 10, 2025)
277
+ "{{hasPassed('01/01/2020', 'dd/MM/yyyy')}}" // Returns true
278
+
279
+ // Check if a date has passed (assuming today is April 10, 2025)
280
+ "{{hasPassed('01/01/2026', 'dd/MM/yyyy')}}" // Returns false
281
+
282
+ // Check if a date + 5 years has passed (2020 + 5 = 2025)
283
+ "{{hasPassed('01/01/2020', 'dd/MM/yyyy', 5)}}"
284
+ // Returns true if April 10, 2025 is after January 1, 2025
285
+ ```
286
+
287
+ ### Array Functions
288
+
289
+ #### includes(array, value)
290
+
291
+ Checks if an array includes a specific value.
292
+
293
+ - `array` (array): The array to check
294
+ - `value` (any): The value to search for in the array
295
+ - Returns: boolean
296
+
297
+ ```js
298
+ // Check if an array includes a specific value
299
+ "{{includes([1, 2, 3], 2)}}" // Returns true
300
+
301
+ // Check if an array includes a specific value
302
+ "{{includes([1, 2, 3], 5)}}" // Returns false
303
+
304
+ // Can be used with context variables
305
+ "{{includes($.allowedRoles, $.currentUser.role)}}"
306
+
225
307
  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{notMissing as r,isObject as t}from"@stackone/utils";import*as e from"jexl";import a from"jsonpath";import n from"lodash.get";const o=/\${([^}]+)}/g,i=(r,t)=>{try{return r.compile(t)}catch{return null}},l=(r,t)=>{a.parse(r);const e=a.query(t,r);if(0!==e.length)return 1===e.length?e[0]:e},s=(r,t,e)=>r.replace(t,String(e)),c=(t,e,a)=>{const l=t.match(o);if(!l)return;const c=l.length,u=new Array(c);for(let r=0;r<c;r++)u[r]={toReplace:l[r],path:l[r].slice(2,-1)};return u.reduce(((t,o)=>((t,e,a,o)=>{const l=t.path.trim();if(!l)return o;try{const c=i(e,l);if(c){const r=c.evalSync(a);if(void 0!==r)return s(o,t.toReplace,r)}const u=n(a,l);return r(u)?s(o,t.toReplace,u):o}catch{return o}})(o,a,e,t)),String(t))},u=(r,e,a,n)=>{if(Array.isArray(e)){const r=parseInt(a,10);if(!isNaN(r)&&(r<0||r>=e.length))return{value:void 0,error:`Invalid array index '${a}' at '${n}'`,availableKeys:e.map(((r,t)=>t.toString()))}}return t(e)?{value:void 0,error:`Key '${a}' not found at '${n}'`,availableKeys:Object.keys(e)}:{value:void 0,error:`${r} at '${n}'`,availableKeys:[]}},h=(r,e)=>{const a=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(a.test(r)){const t=l(r,e);return void 0===t?{value:void 0,error:`Invalid or empty JSONPath: '${r}'`}:{value:t}}const n=r.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[];return((r,e)=>{if("$"!==r[0])return{value:void 0,error:"JSON path must start with $"};let a=e,n="$";for(let e=1;e<r.length;e++){const o=r[e],i=o.startsWith("[")?`${n}${o}`:`${n}.${o}`;if(Array.isArray(a)){const r=parseInt(o,10);if(isNaN(r)||r<0||r>=a.length)return u("Invalid array index",a,o,n);a=a[r],n=i}else{if(!t(a))return{value:void 0,error:`Cannot access '${o}' at '${n}' - parent is not an object`,availableKeys:[]};if(!Object.prototype.hasOwnProperty.call(a,o))return u("Key not found",a,o,n);a=a[o],n=i}}return{value:a}})(n.map((r=>r.replace(/^\['([^']+)'\]$/,"$1").replace(/^\["([^"]+)"\]$/,"$1").replace(/^\[(\d+)\]$/,"$1"))),e)}catch(r){return{value:void 0,error:`Something went wrong with evaluation of JSON path: ${String(r)}`}}},p=(r,a,n)=>{const o=r?.trim();if(null==o||""===o)throw new Error("Empty expression");const s=t(a)?a:{},u=((r=()=>new e.Jexl)=>r())(),p=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(o),v=(r=>r.replace(/\$\./g,"").trim())(p??o),y=c(v,s,u);if(y)return y;if(!p&&o.startsWith("$"))return n?.incrementalJsonPath?((r,t)=>{const e=h(r,t);if(e.error)throw new Error(`${e.error}${e.availableKeys?.length?`. Available keys: ${e.availableKeys.join(", ")}`:""}`);return e.value})(o,s):((r,t)=>{try{return l(r,t)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(o,s);if(!y&&!p)return r;const f=i(u,v);if(!f||"."===v)throw new Error(`Invalid expression: "${o}"`);try{return f.evalSync(s)}catch{return}},v=(r,t)=>{try{return p(r,t)}catch{return null}},y=(r,t)=>{const e=r=>Array.isArray(r)?r.map(e):"object"==typeof r&&null!==r?y(r,t):"string"==typeof r?v(r,t):r;return Object.fromEntries(Object.entries(r).map((([r,t])=>[r,e(t)])))},f=r=>{try{return p(r),!0}catch{return!1}};export{p as evaluate,f as isValidExpression,v as safeEvaluate,y as safeEvaluateRecord};
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)),h=(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))},p=(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 p("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 p("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 y=o(s)?s:{},p=((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))&&r.includes(t))),o})(),f=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(c),$=(r=>r.replace(/\$\./g,"").trim())(f??c),m=h($,y,p);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,y):((r,t)=>{try{return d(r,t)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(c,y);if(!m&&!f)return n;const g=u(p,$);if(!g||"."===$)throw new Error(`Invalid expression: "${c}"`);try{return g.evalSync(y)}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 r=require("@stackone/utils"),e=require("jexl"),t=require("jsonpath"),n=require("lodash.get");function a(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=a(e);const o=/\${([^}]+)}/g,s=(r,e)=>{try{return r.compile(e)}catch{return null}},l=(r,e)=>{t.parse(r);const n=t.query(e,r);if(0!==n.length)return 1===n.length?n[0]:n},c=(r,e,t)=>r.replace(e,String(t)),u=(e,t,a)=>{const i=e.match(o);if(!i)return;const l=i.length,u=new Array(l);for(let r=0;r<l;r++)u[r]={toReplace:i[r],path:i[r].slice(2,-1)};return u.reduce(((e,i)=>((e,t,a,i)=>{const o=e.path.trim();if(!o)return i;try{const l=s(t,o);if(l){const r=l.evalSync(a);if(void 0!==r)return c(i,e.toReplace,r)}const u=n(a,o);return r.notMissing(u)?c(i,e.toReplace,u):i}catch{return i}})(i,a,t,e)),String(e))},v=(e,t,n,a)=>{if(Array.isArray(t)){const r=parseInt(n,10);if(!isNaN(r)&&(r<0||r>=t.length))return{value:void 0,error:`Invalid array index '${n}' at '${a}'`,availableKeys:t.map(((r,e)=>e.toString()))}}return r.isObject(t)?{value:void 0,error:`Key '${n}' not found at '${a}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${e} at '${a}'`,availableKeys:[]}},p=(e,t)=>{const n=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(n.test(e)){const r=l(e,t);return void 0===r?{value:void 0,error:`Invalid or empty JSONPath: '${e}'`}:{value:r}}const a=e.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[];return((e,t)=>{if("$"!==e[0])return{value:void 0,error:"JSON path must start with $"};let n=t,a="$";for(let t=1;t<e.length;t++){const i=e[t],o=i.startsWith("[")?`${a}${i}`:`${a}.${i}`;if(Array.isArray(n)){const r=parseInt(i,10);if(isNaN(r)||r<0||r>=n.length)return v("Invalid array index",n,i,a);n=n[r],a=o}else{if(!r.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 v("Key not found",n,i,a);n=n[i],a=o}}return{value:n}})(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=(e,t,n)=>{const a=e?.trim();if(null==a||""===a)throw new Error("Empty expression");const o=r.isObject(t)?t:{},c=((r=()=>new i.Jexl)=>r())(),v=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(a),f=(r=>r.replace(/\$\./g,"").trim())(v??a),h=u(f,o,c);if(h)return h;if(!v&&a.startsWith("$"))return n?.incrementalJsonPath?((r,e)=>{const t=p(r,e);if(t.error)throw new Error(`${t.error}${t.availableKeys?.length?`. Available keys: ${t.availableKeys.join(", ")}`:""}`);return t.value})(a,o):((r,e)=>{try{return l(r,e)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(a,o);if(!h&&!v)return e;const y=s(c,f);if(!y||"."===f)throw new Error(`Invalid expression: "${a}"`);try{return y.evalSync(o)}catch{return}},h=(r,e)=>{try{return f(r,e)}catch{return null}},y=(r,e)=>{const t=r=>Array.isArray(r)?r.map(t):"object"==typeof r&&null!==r?y(r,e):"string"==typeof r?h(r,e):r;return Object.fromEntries(Object.entries(r).map((([r,e])=>[r,t(e)])))};exports.evaluate=f,exports.isValidExpression=r=>{try{return f(r),!0}catch{return!1}},exports.safeEvaluate=h,exports.safeEvaluateRecord=y;
1
+ "use strict";var e=require("@stackone/utils"),r=require("jexl"),t=require("jsonpath"),a=require("lodash.get");function n(e){var r=Object.create(null);return e&&Object.keys(e).forEach((function(t){if("default"!==t){var a=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,a.get?a:{enumerable:!0,get:function(){return e[t]}})}})),r.default=e,Object.freeze(r)}var i=n(r);const s=/\${([^}]+)}/g,o=(e,r)=>{try{return e.compile(r)}catch{return null}},l=(e,r)=>{t.parse(e);const a=t.query(r,e);if(0!==a.length)return 1===a.length?a[0]:a},c=(e,r,t)=>e.replace(r,String(t)),u=(r,t,n)=>{const i=r.match(s);if(!i)return;const l=i.length,u=new Array(l);for(let e=0;e<l;e++)u[e]={toReplace:i[e],path:i[e].slice(2,-1)};return u.reduce(((r,i)=>((r,t,n,i)=>{const s=r.path.trim();if(!s)return i;try{const l=o(t,s);if(l){const e=l.evalSync(n);if(void 0!==e)return c(i,r.toReplace,e)}const u=a(n,s);return e.notMissing(u)?c(i,r.toReplace,u):i}catch{return i}})(i,n,t,r)),String(r))},d=(r,t,a,n)=>{if(Array.isArray(t)){const e=parseInt(a,10);if(!isNaN(e)&&(e<0||e>=t.length))return{value:void 0,error:`Invalid array index '${a}' at '${n}'`,availableKeys:t.map(((e,r)=>r.toString()))}}return e.isObject(t)?{value:void 0,error:`Key '${a}' not found at '${n}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${r} at '${n}'`,availableKeys:[]}},y=(r,t)=>{const a=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(a.test(r)){const e=l(r,t);return void 0===e?{value:void 0,error:`Invalid or empty JSONPath: '${r}'`}:{value:e}}const n=r.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[];return((r,t)=>{if("$"!==r[0])return{value:void 0,error:"JSON path must start with $"};let a=t,n="$";for(let t=1;t<r.length;t++){const i=r[t],s=i.startsWith("[")?`${n}${i}`:`${n}.${i}`;if(Array.isArray(a)){const e=parseInt(i,10);if(isNaN(e)||e<0||e>=a.length)return d("Invalid array index",a,i,n);a=a[e],n=s}else{if(!e.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((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)}`}}},v=(r,t,a)=>{const n=r?.trim();if(null==n||""===n)throw new Error("Empty expression");const s=e.isObject(t)?t:{},c=((r=()=>new i.Jexl)=>{const t=r();return t.addFunction("nextAnniversary",((r,t)=>e.calculateNextAnniversary({initialDate:r,format:t}))),t.addFunction("yearsElapsed",((r,t,a)=>e.calculateYearsElapsed({startDate:r,endDate:a,format:t}))),t.addFunction("hasPassed",((r,t,a)=>e.dateHasPassed({date:r,yearsToAdd:a,format:t}))),t.addFunction("includes",((r,t)=>!(e.isMissing(r)||!Array.isArray(r)||0===r.length||e.isMissing(t))&&r.includes(t))),t})(),d=(e=>{if(e.startsWith("{{")&&e.endsWith("}}"))return e.slice(2,-2)})(n),v=(e=>e.replace(/\$\./g,"").trim())(d??n),f=u(v,s,c);if(f)return f;if(!d&&n.startsWith("$"))return a?.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})(n,s):((e,r)=>{try{return l(e,r)}catch{throw new Error(`Invalid JSON path: "${e}"`)}})(n,s);if(!f&&!d)return r;const p=o(c,v);if(!p||"."===v)throw new Error(`Invalid expression: "${n}"`);try{return p.evalSync(s)}catch{return}},f=(e,r)=>{try{return v(e,r)}catch{return null}},p=(e,r)=>{const t=e=>Array.isArray(e)?e.map(t):"object"==typeof e&&null!==e?p(e,r):"string"==typeof e?f(e,r):e;return Object.fromEntries(Object.entries(e).map((([e,r])=>[e,t(r)])))};exports.evaluate=v,exports.isValidExpression=e=>{try{return v(e),!0}catch{return!1}},exports.safeEvaluate=f,exports.safeEvaluateRecord=p;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackone/expressions",
3
- "version": "0.8.0",
3
+ "version": "0.10.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.mjs",