@stackone/expressions 0.7.0 → 0.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.
- package/README.md +62 -0
- package/dist/index.es.mjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -222,4 +222,66 @@ 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
|
+
|
|
225
287
|
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{
|
|
1
|
+
import{calculateNextAnniversary as r,calculateYearsElapsed as t,dateHasPassed as e,notMissing as a,isObject as n}from"@stackone/utils";import*as o from"jexl";import i from"jsonpath";import s from"lodash.get";const l=/\${([^}]+)}/g,c=(r,t)=>{try{return r.compile(t)}catch{return null}},u=(r,t)=>{i.parse(r);const e=i.query(t,r);if(0!==e.length)return 1===e.length?e[0]:e},h=(r,t,e)=>r.replace(t,String(e)),p=(r,t,e)=>{const n=r.match(l);if(!n)return;const o=n.length,i=new Array(o);for(let r=0;r<o;r++)i[r]={toReplace:n[r],path:n[r].slice(2,-1)};return i.reduce(((r,n)=>((r,t,e,n)=>{const o=r.path.trim();if(!o)return n;try{const i=c(t,o);if(i){const t=i.evalSync(e);if(void 0!==t)return h(n,r.toReplace,t)}const l=s(e,o);return a(l)?h(n,r.toReplace,l):n}catch{return n}})(n,e,t,r)),String(r))},y=(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 n(t)?{value:void 0,error:`Key '${e}' not found at '${a}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${r} at '${a}'`,availableKeys:[]}},d=(r,t)=>{const e=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(e.test(r)){const e=u(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 o=r[t],i=o.startsWith("[")?`${a}${o}`:`${a}.${o}`;if(Array.isArray(e)){const r=parseInt(o,10);if(isNaN(r)||r<0||r>=e.length)return y("Invalid array index",e,o,a);e=e[r],a=i}else{if(!n(e))return{value:void 0,error:`Cannot access '${o}' at '${a}' - parent is not an object`,availableKeys:[]};if(!Object.prototype.hasOwnProperty.call(e,o))return y("Key not found",e,o,a);e=e[o],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)}`}}},v=(a,i,s)=>{const l=a?.trim();if(null==l||""===l)throw new Error("Empty expression");const h=n(i)?i:{},y=((a=()=>new o.Jexl)=>{const n=a();return n.addFunction("nextAnniversary",((t,e)=>r({initialDate:t,format:e}))),n.addFunction("yearsElapsed",((r,e,a)=>t({startDate:r,endDate:a,format:e}))),n.addFunction("hasPassed",((r,t,a)=>e({date:r,yearsToAdd:a,format:t}))),n})(),v=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(l),f=(r=>r.replace(/\$\./g,"").trim())(v??l),$=p(f,h,y);if($)return $;if(!v&&l.startsWith("$"))return s?.incrementalJsonPath?((r,t)=>{const e=d(r,t);if(e.error)throw new Error(`${e.error}${e.availableKeys?.length?`. Available keys: ${e.availableKeys.join(", ")}`:""}`);return e.value})(l,h):((r,t)=>{try{return u(r,t)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(l,h);if(!$&&!v)return a;const m=c(y,f);if(!m||"."===f)throw new Error(`Invalid expression: "${l}"`);try{return m.evalSync(h)}catch{return}},f=(r,t)=>{try{return v(r,t)}catch{return null}},$=(r,t)=>{const e=r=>Array.isArray(r)?r.map(e):"object"==typeof r&&null!==r?$(r,t):"string"==typeof r?f(r,t):r;return Object.fromEntries(Object.entries(r).map((([r,t])=>[r,e(t)])))},m=r=>{try{return v(r),!0}catch{return!1}};export{v as evaluate,m as isValidExpression,f as safeEvaluate,$ as safeEvaluateRecord};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
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 o=/\${([^}]+)}/g,s=(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(o);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 o=r.path.trim();if(!o)return i;try{const l=s(t,o);if(l){const e=l.evalSync(n);if(void 0!==e)return c(i,r.toReplace,e)}const u=a(n,o);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:[]}},v=(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],o=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=o}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=o}}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)}`}}},f=(r,t,a)=>{const n=r?.trim();if(null==n||""===n)throw new Error("Empty expression");const o=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})(),d=(e=>{if(e.startsWith("{{")&&e.endsWith("}}"))return e.slice(2,-2)})(n),f=(e=>e.replace(/\$\./g,"").trim())(d??n),p=u(f,o,c);if(p)return p;if(!d&&n.startsWith("$"))return a?.incrementalJsonPath?((e,r)=>{const t=v(e,r);if(t.error)throw new Error(`${t.error}${t.availableKeys?.length?`. Available keys: ${t.availableKeys.join(", ")}`:""}`);return t.value})(n,o):((e,r)=>{try{return l(e,r)}catch{throw new Error(`Invalid JSON path: "${e}"`)}})(n,o);if(!p&&!d)return r;const y=s(c,f);if(!y||"."===f)throw new Error(`Invalid expression: "${n}"`);try{return y.evalSync(o)}catch{return}},p=(e,r)=>{try{return f(e,r)}catch{return null}},y=(e,r)=>{const t=e=>Array.isArray(e)?e.map(t):"object"==typeof e&&null!==e?y(e,r):"string"==typeof e?p(e,r):e;return Object.fromEntries(Object.entries(e).map((([e,r])=>[e,t(r)])))};exports.evaluate=f,exports.isValidExpression=e=>{try{return f(e),!0}catch{return!1}},exports.safeEvaluate=p,exports.safeEvaluateRecord=y;
|