@stackone/expressions 0.15.2 → 0.16.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/dist/index.js CHANGED
@@ -1,274 +1 @@
1
- //#region rolldown:runtime
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __copyProps = (to, from, except, desc) => {
9
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
- key = keys[i];
11
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
- get: ((k) => from[k]).bind(null, key),
13
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
- });
15
- }
16
- return to;
17
- };
18
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
- value: mod,
20
- enumerable: true
21
- }) : target, mod));
22
-
23
- //#endregion
24
- const __stackone_utils = __toESM(require("@stackone/utils"));
25
- const jexl = __toESM(require("jexl"));
26
- const jsonpath_plus = __toESM(require("jsonpath-plus"));
27
- const lodash_get = __toESM(require("lodash.get"));
28
-
29
- //#region src/factory.ts
30
- const createExpressionHandler = (instanceBuilder = () => new jexl.Jexl()) => {
31
- const expressionHandler = instanceBuilder();
32
- expressionHandler.addFunction("nextAnniversary", (initialDate, format) => (0, __stackone_utils.calculateNextAnniversary)({
33
- initialDate,
34
- format
35
- }));
36
- expressionHandler.addFunction("yearsElapsed", (startDate, format, endDate) => (0, __stackone_utils.calculateYearsElapsed)({
37
- startDate,
38
- endDate,
39
- format
40
- }));
41
- expressionHandler.addFunction("hasPassed", (date, format, yearsToAdd) => (0, __stackone_utils.dateHasPassed)({
42
- date,
43
- yearsToAdd,
44
- format
45
- }));
46
- expressionHandler.addFunction("now", () => (/* @__PURE__ */ new Date()).toISOString());
47
- expressionHandler.addFunction("includes", (array, value) => {
48
- if ((0, __stackone_utils.isMissing)(array) || !Array.isArray(array) || array.length === 0 || (0, __stackone_utils.isMissing)(value)) return false;
49
- if (Array.isArray(value)) return value.every((val) => array.includes(val));
50
- return array.includes(value);
51
- });
52
- expressionHandler.addFunction("includesSome", (array, value) => {
53
- if ((0, __stackone_utils.isMissing)(array) || !Array.isArray(array) || array.length === 0 || (0, __stackone_utils.isMissing)(value)) return false;
54
- if (Array.isArray(value)) return value.some((val) => array.includes(val));
55
- return array.includes(value);
56
- });
57
- expressionHandler.addFunction("present", (value) => (0, __stackone_utils.notMissing)(value));
58
- expressionHandler.addFunction("missing", (value) => (0, __stackone_utils.isMissing)(value));
59
- expressionHandler.addFunction("keys", (value) => {
60
- if ((0, __stackone_utils.isMissing)(value)) return [];
61
- else if (typeof value === "object" && !Array.isArray(value)) return Object.keys(value);
62
- else return [];
63
- });
64
- expressionHandler.addFunction("values", (value) => {
65
- if ((0, __stackone_utils.isMissing)(value)) return [];
66
- else if (typeof value === "object" && !Array.isArray(value)) return Object.values(value);
67
- else return [];
68
- });
69
- expressionHandler.addBinaryOp("??", 0, (left, right) => {
70
- if ((0, __stackone_utils.isMissing)(left)) return right;
71
- else if ((0, __stackone_utils.isMissing)(right)) return left;
72
- else return left ?? right;
73
- });
74
- return expressionHandler;
75
- };
76
-
77
- //#endregion
78
- //#region src/constants.ts
79
- const INTERPOLATED_EXPRESSION_REGEX = /\${([^}]+)}/g;
80
-
81
- //#endregion
82
- //#region src/utils.ts
83
- const cleanExpression = (expression) => {
84
- return expression.replace(/\$\./g, "").trim();
85
- };
86
- const compileExpression = (expressionHandler, expression) => {
87
- try {
88
- const compiledExpression = expressionHandler.compile(expression);
89
- return compiledExpression;
90
- } catch {
91
- return null;
92
- }
93
- };
94
- const extractExpressionBetweenDoubleCurlyBraces = (expression) => {
95
- if (expression.startsWith("{{") && expression.endsWith("}}")) return expression.slice(2, -2);
96
- };
97
- const evaluateJsonPath = (expression, context) => {
98
- try {
99
- if (!expression.startsWith("$")) throw new Error(`JSONPath expression must start with '$': ${expression}`);
100
- if (expression.includes("$_") || expression.match(/\$[^.[]/g) || expression.includes("[") && !expression.includes("]") || expression.includes(" ") && !expression.includes("[")) throw new Error(`Invalid JSONPath expression: ${expression}`);
101
- const result = (0, jsonpath_plus.JSONPath)({
102
- path: expression,
103
- json: context
104
- });
105
- if (result.length === 0) return void 0;
106
- if (result.length === 1) return result[0];
107
- return result;
108
- } catch {
109
- throw new Error(`Invalid JSONPath expression: ${expression}`);
110
- }
111
- };
112
- const replaceInterpolation = (expression, toReplace, value) => expression.replace(toReplace, String(value));
113
- const handleSingleInterpolation = (interpolation, expressionHandler, context, currentExpression) => {
114
- const path = interpolation.path.trim();
115
- if (!path) return currentExpression;
116
- try {
117
- const compiledExpression = compileExpression(expressionHandler, path);
118
- if (compiledExpression) {
119
- const value = compiledExpression.evalSync(context);
120
- if (value !== void 0) return replaceInterpolation(currentExpression, interpolation.toReplace, value);
121
- }
122
- const pathValue = (0, lodash_get.default)(context, path);
123
- return (0, __stackone_utils.notMissing)(pathValue) ? replaceInterpolation(currentExpression, interpolation.toReplace, pathValue) : currentExpression;
124
- } catch {
125
- return currentExpression;
126
- }
127
- };
128
- const evaluateStringInterpolations = (expression, context, expressionHandler) => {
129
- const matches = expression.match(INTERPOLATED_EXPRESSION_REGEX);
130
- if (!matches) return void 0;
131
- const len = matches.length;
132
- const interpolations = new Array(len);
133
- for (let i = 0; i < len; i++) interpolations[i] = {
134
- toReplace: matches[i],
135
- path: matches[i].slice(2, -1)
136
- };
137
- return interpolations.reduce((acc, interpolation) => handleSingleInterpolation(interpolation, expressionHandler, context, acc), String(expression));
138
- };
139
- const handleSegmentPathError = (err, parentVal, segment, parentPath) => {
140
- if (Array.isArray(parentVal)) {
141
- const parsedIndex = parseInt(segment, 10);
142
- if (!isNaN(parsedIndex) && (parsedIndex < 0 || parsedIndex >= parentVal.length)) return {
143
- value: void 0,
144
- error: `Invalid array index '${segment}' at '${parentPath}'`,
145
- availableKeys: parentVal.map((_, idx) => idx.toString())
146
- };
147
- }
148
- if ((0, __stackone_utils.isObject)(parentVal)) return {
149
- value: void 0,
150
- error: `Key '${segment}' not found at '${parentPath}'`,
151
- availableKeys: Object.keys(parentVal)
152
- };
153
- return {
154
- value: void 0,
155
- error: `${err} at '${parentPath}'`,
156
- availableKeys: []
157
- };
158
- };
159
- const handleJsonPathSegments = (segments, ctx) => {
160
- if (segments[0] !== "$") return {
161
- value: void 0,
162
- error: "JSON path must start with $"
163
- };
164
- let currentValue = ctx;
165
- let currentPath = "$";
166
- for (let i = 1; i < segments.length; i++) {
167
- const segment = segments[i];
168
- const nextPath = segment.startsWith("[") ? `${currentPath}${segment}` : `${currentPath}.${segment}`;
169
- if (Array.isArray(currentValue)) {
170
- const idx = parseInt(segment, 10);
171
- if (isNaN(idx) || idx < 0 || idx >= currentValue.length) return handleSegmentPathError("Invalid array index", currentValue, segment, currentPath);
172
- currentValue = currentValue[idx];
173
- currentPath = nextPath;
174
- } else if ((0, __stackone_utils.isObject)(currentValue)) {
175
- if (!Object.prototype.hasOwnProperty.call(currentValue, segment)) return handleSegmentPathError("Key not found", currentValue, segment, currentPath);
176
- currentValue = currentValue[segment];
177
- currentPath = nextPath;
178
- } else return {
179
- value: void 0,
180
- error: `Cannot access '${segment}' at '${currentPath}' - parent is not an object`,
181
- availableKeys: []
182
- };
183
- }
184
- return { value: currentValue };
185
- };
186
- const evaluateJsonPathIncremental = (path, context) => {
187
- const SPECIAL_JSONPATH_REGEX = /(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;
188
- try {
189
- if (SPECIAL_JSONPATH_REGEX.test(path)) {
190
- const result = evaluateJsonPath(path, context);
191
- if (result === void 0) return {
192
- value: void 0,
193
- error: `Invalid or empty JSONPath: '${path}'`
194
- };
195
- return { value: result };
196
- }
197
- const segments = path.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g) || [];
198
- const cleanedSegments = segments.map((seg) => seg.replace(/^\['([^']+)'\]$/, "$1").replace(/^\["([^"]+)"\]$/, "$1").replace(/^\[(\d+)\]$/, "$1"));
199
- return handleJsonPathSegments(cleanedSegments, context);
200
- } catch (error) {
201
- return {
202
- value: void 0,
203
- error: `Something went wrong with evaluation of JSON path: ${String(error)}`
204
- };
205
- }
206
- };
207
-
208
- //#endregion
209
- //#region src/evaluate.ts
210
- const handleIncrementalJsonPath = (expr, ctx) => {
211
- const result = evaluateJsonPathIncremental(expr, ctx);
212
- if (result.error) throw new Error(`${result.error}${result.availableKeys?.length ? `. Available keys: ${result.availableKeys.join(", ")}` : ""}`);
213
- return result.value;
214
- };
215
- const handleNormalJsonPath = (expr, ctx) => {
216
- try {
217
- return evaluateJsonPath(expr, ctx);
218
- } catch {
219
- throw new Error(`Invalid JSON path: "${expr}"`);
220
- }
221
- };
222
- const evaluateExpression = (expression, context, options) => {
223
- const trimmedExpression = expression?.trim();
224
- if (trimmedExpression === null || trimmedExpression === void 0 || trimmedExpression === "") throw new Error("Empty expression");
225
- const expressionContext = (0, __stackone_utils.isObject)(context) ? context : {};
226
- const expressionHandler = createExpressionHandler();
227
- const expressionBetweenDoubleCurlyBraces = extractExpressionBetweenDoubleCurlyBraces(trimmedExpression);
228
- const extractedExpression = expressionBetweenDoubleCurlyBraces ?? trimmedExpression;
229
- const cleanedExpression = cleanExpression(extractedExpression);
230
- const interpolatedExpression = evaluateStringInterpolations(cleanedExpression, expressionContext, expressionHandler);
231
- if (interpolatedExpression) return interpolatedExpression;
232
- if (!expressionBetweenDoubleCurlyBraces && trimmedExpression.startsWith("$")) return options?.incrementalJsonPath ? handleIncrementalJsonPath(trimmedExpression, expressionContext) : handleNormalJsonPath(trimmedExpression, expressionContext);
233
- if (!interpolatedExpression && !expressionBetweenDoubleCurlyBraces) return expression;
234
- const compiledExpression = compileExpression(expressionHandler, cleanedExpression);
235
- if (!compiledExpression || cleanedExpression === ".") throw new Error(`Invalid expression: "${trimmedExpression}"`);
236
- try {
237
- return compiledExpression.evalSync(expressionContext);
238
- } catch {
239
- return void 0;
240
- }
241
- };
242
- const safeEvaluateExpression = (expression, context) => {
243
- try {
244
- return evaluateExpression(expression, context);
245
- } catch {
246
- return null;
247
- }
248
- };
249
- const safeEvaluateRecord = (record, context) => {
250
- const evaluateValue = (value) => {
251
- if (Array.isArray(value)) return value.map(evaluateValue);
252
- else if (typeof value === "object" && value !== null) return safeEvaluateRecord(value, context);
253
- else if (typeof value === "string") return safeEvaluateExpression(value, context);
254
- return value;
255
- };
256
- return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, evaluateValue(value)]));
257
- };
258
-
259
- //#endregion
260
- //#region src/validate.ts
261
- const isValidExpression = (expression) => {
262
- try {
263
- evaluateExpression(expression);
264
- return true;
265
- } catch {
266
- return false;
267
- }
268
- };
269
-
270
- //#endregion
271
- exports.evaluate = evaluateExpression;
272
- exports.isValidExpression = isValidExpression;
273
- exports.safeEvaluate = safeEvaluateExpression;
274
- exports.safeEvaluateRecord = safeEvaluateRecord;
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`@stackone/utils`)),l=s(require(`jexl`)),u=s(require(`jsonpath-plus`)),d=(e=()=>new l.Jexl)=>{let t=e();return t.addFunction(`nextAnniversary`,(e,t)=>(0,c.calculateNextAnniversary)({initialDate:e,format:t})),t.addFunction(`yearsElapsed`,(e,t,n)=>(0,c.calculateYearsElapsed)({startDate:e,endDate:n,format:t})),t.addFunction(`hasPassed`,(e,t,n)=>(0,c.dateHasPassed)({date:e,yearsToAdd:n,format:t})),t.addFunction(`now`,()=>new Date().toISOString()),t.addFunction(`includes`,(e,t)=>(0,c.isMissing)(e)||!Array.isArray(e)||e.length===0||(0,c.isMissing)(t)?!1:Array.isArray(t)?t.every(t=>e.includes(t)):e.includes(t)),t.addFunction(`includesSome`,(e,t)=>(0,c.isMissing)(e)||!Array.isArray(e)||e.length===0||(0,c.isMissing)(t)?!1:Array.isArray(t)?t.some(t=>e.includes(t)):e.includes(t)),t.addFunction(`present`,e=>(0,c.notMissing)(e)),t.addFunction(`missing`,e=>(0,c.isMissing)(e)),t.addFunction(`keys`,e=>(0,c.isMissing)(e)?[]:typeof e==`object`&&!Array.isArray(e)?Object.keys(e):[]),t.addFunction(`values`,e=>(0,c.isMissing)(e)?[]:typeof e==`object`&&!Array.isArray(e)?Object.values(e):[]),t.addBinaryOp(`??`,0,(e,t)=>(0,c.isMissing)(e)?t:(0,c.isMissing)(t)?e:e??t),t},f=/\${([^}]+)}/g,p=e=>e.replace(/\$\./g,``).trim(),m=(e,t)=>{try{let n=e.compile(t);return n}catch{return null}},h=e=>{if(e.startsWith(`{{`)&&e.endsWith(`}}`))return e.slice(2,-2)},g=(e,t)=>{try{if(!e.startsWith(`$`))throw Error(`JSONPath expression must start with '$': ${e}`);if(e.includes(`$_`)||e.match(/\$[^.[]/g)||e.includes(`[`)&&!e.includes(`]`)||e.includes(` `)&&!e.includes(`[`))throw Error(`Invalid JSONPath expression: ${e}`);let n=(0,u.JSONPath)({path:e,json:t});return n.length===0?void 0:n.length===1?n[0]:n}catch{throw Error(`Invalid JSONPath expression: ${e}`)}},_=(e,t,n)=>e.replace(t,String(n)),v=(e,t,n,r)=>{let i=e.path.trim();if(!i)return r;try{let a=m(t,i);if(a){let t=a.evalSync(n);if(t!==void 0)return _(r,e.toReplace,t)}let o=S(`$.${i}`,n),s=o.value;return(0,c.notMissing)(s)?_(r,e.toReplace,s):r}catch{return r}},y=(e,t,n)=>{let r=e.match(f);if(!r)return;let i=r.length,a=Array(i);for(let e=0;e<i;e++)a[e]={toReplace:r[e],path:r[e].slice(2,-1)};return a.reduce((e,r)=>v(r,n,t,e),String(e))},b=(e,t,n,r)=>{if(Array.isArray(t)){let e=parseInt(n,10);if(!isNaN(e)&&(e<0||e>=t.length))return{value:void 0,error:`Invalid array index '${n}' at '${r}'`,availableKeys:t.map((e,t)=>t.toString())}}return(0,c.isObject)(t)?{value:void 0,error:`Key '${n}' not found at '${r}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${e} at '${r}'`,availableKeys:[]}},x=(e,t)=>{if(e[0]!==`$`)return{value:void 0,error:`JSON path must start with $`};let n=t,r=`$`;for(let t=1;t<e.length;t++){let i=e[t],a=i.startsWith(`[`)?`${r}${i}`:`${r}.${i}`;if(Array.isArray(n)){let e=parseInt(i,10);if(isNaN(e)||e<0||e>=n.length)return b(`Invalid array index`,n,i,r);n=n[e],r=a}else if((0,c.isObject)(n)){if(!Object.prototype.hasOwnProperty.call(n,i))return b(`Key not found`,n,i,r);n=n[i],r=a}else return{value:void 0,error:`Cannot access '${i}' at '${r}' - parent is not an object`,availableKeys:[]}}return{value:n}},S=(e,t)=>{let n=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(n.test(e)){let n=g(e,t);return n===void 0?{value:void 0,error:`Invalid or empty JSONPath: '${e}'`}:{value:n}}let r=e.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[],i=r.map(e=>e.replace(/^\['([^']+)'\]$/,`$1`).replace(/^\["([^"]+)"\]$/,`$1`).replace(/^\[(\d+)\]$/,`$1`));return x(i,t)}catch(e){return{value:void 0,error:`Something went wrong with evaluation of JSON path: ${String(e)}`}}},C=(e,t)=>{let n=S(e,t);if(n.error)throw Error(`${n.error}${n.availableKeys?.length?`. Available keys: ${n.availableKeys.join(`, `)}`:``}`);return n.value},w=(e,t)=>{try{return g(e,t)}catch{throw Error(`Invalid JSON path: "${e}"`)}},T=(e,t,n)=>{let r=e?.trim();if(r==null||r===``)throw Error(`Empty expression`);let i=(0,c.isObject)(t)?t:{},a=d(),o=h(r),s=o??r,l=p(s),u=y(l,i,a);if(u)return u;if(!o&&r.startsWith(`$`))return n?.incrementalJsonPath?C(r,i):w(r,i);if(!u&&!o)return e;let f=m(a,l);if(!f||l===`.`)throw Error(`Invalid expression: "${r}"`);try{return f.evalSync(i)}catch{return}},E=(e,t)=>{try{return T(e,t)}catch{return null}},D=(e,t)=>{let n=e=>Array.isArray(e)?e.map(n):typeof e==`object`&&e?D(e,t):typeof e==`string`?E(e,t):e;return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,n(t)]))},O=e=>{try{return T(e),!0}catch{return!1}};exports.evaluate=T,exports.isValidExpression=O,exports.safeEvaluate=E,exports.safeEvaluateRecord=D;
package/dist/index.mjs CHANGED
@@ -1,248 +1 @@
1
- import { calculateNextAnniversary, calculateYearsElapsed, dateHasPassed, isMissing, isObject, notMissing } from "@stackone/utils";
2
- import * as jexl from "jexl";
3
- import { JSONPath } from "jsonpath-plus";
4
- import get from "lodash.get";
5
-
6
- //#region src/factory.ts
7
- const createExpressionHandler = (instanceBuilder = () => new jexl.Jexl()) => {
8
- const expressionHandler = instanceBuilder();
9
- expressionHandler.addFunction("nextAnniversary", (initialDate, format) => calculateNextAnniversary({
10
- initialDate,
11
- format
12
- }));
13
- expressionHandler.addFunction("yearsElapsed", (startDate, format, endDate) => calculateYearsElapsed({
14
- startDate,
15
- endDate,
16
- format
17
- }));
18
- expressionHandler.addFunction("hasPassed", (date, format, yearsToAdd) => dateHasPassed({
19
- date,
20
- yearsToAdd,
21
- format
22
- }));
23
- expressionHandler.addFunction("now", () => (/* @__PURE__ */ new Date()).toISOString());
24
- expressionHandler.addFunction("includes", (array, value) => {
25
- if (isMissing(array) || !Array.isArray(array) || array.length === 0 || isMissing(value)) return false;
26
- if (Array.isArray(value)) return value.every((val) => array.includes(val));
27
- return array.includes(value);
28
- });
29
- expressionHandler.addFunction("includesSome", (array, value) => {
30
- if (isMissing(array) || !Array.isArray(array) || array.length === 0 || isMissing(value)) return false;
31
- if (Array.isArray(value)) return value.some((val) => array.includes(val));
32
- return array.includes(value);
33
- });
34
- expressionHandler.addFunction("present", (value) => notMissing(value));
35
- expressionHandler.addFunction("missing", (value) => isMissing(value));
36
- expressionHandler.addFunction("keys", (value) => {
37
- if (isMissing(value)) return [];
38
- else if (typeof value === "object" && !Array.isArray(value)) return Object.keys(value);
39
- else return [];
40
- });
41
- expressionHandler.addFunction("values", (value) => {
42
- if (isMissing(value)) return [];
43
- else if (typeof value === "object" && !Array.isArray(value)) return Object.values(value);
44
- else return [];
45
- });
46
- expressionHandler.addBinaryOp("??", 0, (left, right) => {
47
- if (isMissing(left)) return right;
48
- else if (isMissing(right)) return left;
49
- else return left ?? right;
50
- });
51
- return expressionHandler;
52
- };
53
-
54
- //#endregion
55
- //#region src/constants.ts
56
- const INTERPOLATED_EXPRESSION_REGEX = /\${([^}]+)}/g;
57
-
58
- //#endregion
59
- //#region src/utils.ts
60
- const cleanExpression = (expression) => {
61
- return expression.replace(/\$\./g, "").trim();
62
- };
63
- const compileExpression = (expressionHandler, expression) => {
64
- try {
65
- const compiledExpression = expressionHandler.compile(expression);
66
- return compiledExpression;
67
- } catch {
68
- return null;
69
- }
70
- };
71
- const extractExpressionBetweenDoubleCurlyBraces = (expression) => {
72
- if (expression.startsWith("{{") && expression.endsWith("}}")) return expression.slice(2, -2);
73
- };
74
- const evaluateJsonPath = (expression, context) => {
75
- try {
76
- if (!expression.startsWith("$")) throw new Error(`JSONPath expression must start with '$': ${expression}`);
77
- if (expression.includes("$_") || expression.match(/\$[^.[]/g) || expression.includes("[") && !expression.includes("]") || expression.includes(" ") && !expression.includes("[")) throw new Error(`Invalid JSONPath expression: ${expression}`);
78
- const result = JSONPath({
79
- path: expression,
80
- json: context
81
- });
82
- if (result.length === 0) return void 0;
83
- if (result.length === 1) return result[0];
84
- return result;
85
- } catch {
86
- throw new Error(`Invalid JSONPath expression: ${expression}`);
87
- }
88
- };
89
- const replaceInterpolation = (expression, toReplace, value) => expression.replace(toReplace, String(value));
90
- const handleSingleInterpolation = (interpolation, expressionHandler, context, currentExpression) => {
91
- const path = interpolation.path.trim();
92
- if (!path) return currentExpression;
93
- try {
94
- const compiledExpression = compileExpression(expressionHandler, path);
95
- if (compiledExpression) {
96
- const value = compiledExpression.evalSync(context);
97
- if (value !== void 0) return replaceInterpolation(currentExpression, interpolation.toReplace, value);
98
- }
99
- const pathValue = get(context, path);
100
- return notMissing(pathValue) ? replaceInterpolation(currentExpression, interpolation.toReplace, pathValue) : currentExpression;
101
- } catch {
102
- return currentExpression;
103
- }
104
- };
105
- const evaluateStringInterpolations = (expression, context, expressionHandler) => {
106
- const matches = expression.match(INTERPOLATED_EXPRESSION_REGEX);
107
- if (!matches) return void 0;
108
- const len = matches.length;
109
- const interpolations = new Array(len);
110
- for (let i = 0; i < len; i++) interpolations[i] = {
111
- toReplace: matches[i],
112
- path: matches[i].slice(2, -1)
113
- };
114
- return interpolations.reduce((acc, interpolation) => handleSingleInterpolation(interpolation, expressionHandler, context, acc), String(expression));
115
- };
116
- const handleSegmentPathError = (err, parentVal, segment, parentPath) => {
117
- if (Array.isArray(parentVal)) {
118
- const parsedIndex = parseInt(segment, 10);
119
- if (!isNaN(parsedIndex) && (parsedIndex < 0 || parsedIndex >= parentVal.length)) return {
120
- value: void 0,
121
- error: `Invalid array index '${segment}' at '${parentPath}'`,
122
- availableKeys: parentVal.map((_, idx) => idx.toString())
123
- };
124
- }
125
- if (isObject(parentVal)) return {
126
- value: void 0,
127
- error: `Key '${segment}' not found at '${parentPath}'`,
128
- availableKeys: Object.keys(parentVal)
129
- };
130
- return {
131
- value: void 0,
132
- error: `${err} at '${parentPath}'`,
133
- availableKeys: []
134
- };
135
- };
136
- const handleJsonPathSegments = (segments, ctx) => {
137
- if (segments[0] !== "$") return {
138
- value: void 0,
139
- error: "JSON path must start with $"
140
- };
141
- let currentValue = ctx;
142
- let currentPath = "$";
143
- for (let i = 1; i < segments.length; i++) {
144
- const segment = segments[i];
145
- const nextPath = segment.startsWith("[") ? `${currentPath}${segment}` : `${currentPath}.${segment}`;
146
- if (Array.isArray(currentValue)) {
147
- const idx = parseInt(segment, 10);
148
- if (isNaN(idx) || idx < 0 || idx >= currentValue.length) return handleSegmentPathError("Invalid array index", currentValue, segment, currentPath);
149
- currentValue = currentValue[idx];
150
- currentPath = nextPath;
151
- } else if (isObject(currentValue)) {
152
- if (!Object.prototype.hasOwnProperty.call(currentValue, segment)) return handleSegmentPathError("Key not found", currentValue, segment, currentPath);
153
- currentValue = currentValue[segment];
154
- currentPath = nextPath;
155
- } else return {
156
- value: void 0,
157
- error: `Cannot access '${segment}' at '${currentPath}' - parent is not an object`,
158
- availableKeys: []
159
- };
160
- }
161
- return { value: currentValue };
162
- };
163
- const evaluateJsonPathIncremental = (path, context) => {
164
- const SPECIAL_JSONPATH_REGEX = /(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;
165
- try {
166
- if (SPECIAL_JSONPATH_REGEX.test(path)) {
167
- const result = evaluateJsonPath(path, context);
168
- if (result === void 0) return {
169
- value: void 0,
170
- error: `Invalid or empty JSONPath: '${path}'`
171
- };
172
- return { value: result };
173
- }
174
- const segments = path.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g) || [];
175
- const cleanedSegments = segments.map((seg) => seg.replace(/^\['([^']+)'\]$/, "$1").replace(/^\["([^"]+)"\]$/, "$1").replace(/^\[(\d+)\]$/, "$1"));
176
- return handleJsonPathSegments(cleanedSegments, context);
177
- } catch (error) {
178
- return {
179
- value: void 0,
180
- error: `Something went wrong with evaluation of JSON path: ${String(error)}`
181
- };
182
- }
183
- };
184
-
185
- //#endregion
186
- //#region src/evaluate.ts
187
- const handleIncrementalJsonPath = (expr, ctx) => {
188
- const result = evaluateJsonPathIncremental(expr, ctx);
189
- if (result.error) throw new Error(`${result.error}${result.availableKeys?.length ? `. Available keys: ${result.availableKeys.join(", ")}` : ""}`);
190
- return result.value;
191
- };
192
- const handleNormalJsonPath = (expr, ctx) => {
193
- try {
194
- return evaluateJsonPath(expr, ctx);
195
- } catch {
196
- throw new Error(`Invalid JSON path: "${expr}"`);
197
- }
198
- };
199
- const evaluateExpression = (expression, context, options) => {
200
- const trimmedExpression = expression?.trim();
201
- if (trimmedExpression === null || trimmedExpression === void 0 || trimmedExpression === "") throw new Error("Empty expression");
202
- const expressionContext = isObject(context) ? context : {};
203
- const expressionHandler = createExpressionHandler();
204
- const expressionBetweenDoubleCurlyBraces = extractExpressionBetweenDoubleCurlyBraces(trimmedExpression);
205
- const extractedExpression = expressionBetweenDoubleCurlyBraces ?? trimmedExpression;
206
- const cleanedExpression = cleanExpression(extractedExpression);
207
- const interpolatedExpression = evaluateStringInterpolations(cleanedExpression, expressionContext, expressionHandler);
208
- if (interpolatedExpression) return interpolatedExpression;
209
- if (!expressionBetweenDoubleCurlyBraces && trimmedExpression.startsWith("$")) return options?.incrementalJsonPath ? handleIncrementalJsonPath(trimmedExpression, expressionContext) : handleNormalJsonPath(trimmedExpression, expressionContext);
210
- if (!interpolatedExpression && !expressionBetweenDoubleCurlyBraces) return expression;
211
- const compiledExpression = compileExpression(expressionHandler, cleanedExpression);
212
- if (!compiledExpression || cleanedExpression === ".") throw new Error(`Invalid expression: "${trimmedExpression}"`);
213
- try {
214
- return compiledExpression.evalSync(expressionContext);
215
- } catch {
216
- return void 0;
217
- }
218
- };
219
- const safeEvaluateExpression = (expression, context) => {
220
- try {
221
- return evaluateExpression(expression, context);
222
- } catch {
223
- return null;
224
- }
225
- };
226
- const safeEvaluateRecord = (record, context) => {
227
- const evaluateValue = (value) => {
228
- if (Array.isArray(value)) return value.map(evaluateValue);
229
- else if (typeof value === "object" && value !== null) return safeEvaluateRecord(value, context);
230
- else if (typeof value === "string") return safeEvaluateExpression(value, context);
231
- return value;
232
- };
233
- return Object.fromEntries(Object.entries(record).map(([key, value]) => [key, evaluateValue(value)]));
234
- };
235
-
236
- //#endregion
237
- //#region src/validate.ts
238
- const isValidExpression = (expression) => {
239
- try {
240
- evaluateExpression(expression);
241
- return true;
242
- } catch {
243
- return false;
244
- }
245
- };
246
-
247
- //#endregion
248
- export { evaluateExpression as evaluate, isValidExpression, safeEvaluateExpression as safeEvaluate, safeEvaluateRecord };
1
+ import{calculateNextAnniversary as e,calculateYearsElapsed as t,dateHasPassed as n,isMissing as r,isObject as i,notMissing as a}from"@stackone/utils";import*as o from"jexl";import{JSONPath as s}from"jsonpath-plus";const c=(i=()=>new o.Jexl)=>{let s=i();return s.addFunction(`nextAnniversary`,(t,n)=>e({initialDate:t,format:n})),s.addFunction(`yearsElapsed`,(e,n,r)=>t({startDate:e,endDate:r,format:n})),s.addFunction(`hasPassed`,(e,t,r)=>n({date:e,yearsToAdd:r,format:t})),s.addFunction(`now`,()=>new Date().toISOString()),s.addFunction(`includes`,(e,t)=>r(e)||!Array.isArray(e)||e.length===0||r(t)?!1:Array.isArray(t)?t.every(t=>e.includes(t)):e.includes(t)),s.addFunction(`includesSome`,(e,t)=>r(e)||!Array.isArray(e)||e.length===0||r(t)?!1:Array.isArray(t)?t.some(t=>e.includes(t)):e.includes(t)),s.addFunction(`present`,e=>a(e)),s.addFunction(`missing`,e=>r(e)),s.addFunction(`keys`,e=>r(e)?[]:typeof e==`object`&&!Array.isArray(e)?Object.keys(e):[]),s.addFunction(`values`,e=>r(e)?[]:typeof e==`object`&&!Array.isArray(e)?Object.values(e):[]),s.addBinaryOp(`??`,0,(e,t)=>r(e)?t:r(t)?e:e??t),s},l=/\${([^}]+)}/g,u=e=>e.replace(/\$\./g,``).trim(),d=(e,t)=>{try{let n=e.compile(t);return n}catch{return null}},f=e=>{if(e.startsWith(`{{`)&&e.endsWith(`}}`))return e.slice(2,-2)},p=(e,t)=>{try{if(!e.startsWith(`$`))throw Error(`JSONPath expression must start with '$': ${e}`);if(e.includes(`$_`)||e.match(/\$[^.[]/g)||e.includes(`[`)&&!e.includes(`]`)||e.includes(` `)&&!e.includes(`[`))throw Error(`Invalid JSONPath expression: ${e}`);let n=s({path:e,json:t});return n.length===0?void 0:n.length===1?n[0]:n}catch{throw Error(`Invalid JSONPath expression: ${e}`)}},m=(e,t,n)=>e.replace(t,String(n)),h=(e,t,n,r)=>{let i=e.path.trim();if(!i)return r;try{let o=d(t,i);if(o){let t=o.evalSync(n);if(t!==void 0)return m(r,e.toReplace,t)}let s=y(`$.${i}`,n),c=s.value;return a(c)?m(r,e.toReplace,c):r}catch{return r}},g=(e,t,n)=>{let r=e.match(l);if(!r)return;let i=r.length,a=Array(i);for(let e=0;e<i;e++)a[e]={toReplace:r[e],path:r[e].slice(2,-1)};return a.reduce((e,r)=>h(r,n,t,e),String(e))},_=(e,t,n,r)=>{if(Array.isArray(t)){let e=parseInt(n,10);if(!isNaN(e)&&(e<0||e>=t.length))return{value:void 0,error:`Invalid array index '${n}' at '${r}'`,availableKeys:t.map((e,t)=>t.toString())}}return i(t)?{value:void 0,error:`Key '${n}' not found at '${r}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${e} at '${r}'`,availableKeys:[]}},v=(e,t)=>{if(e[0]!==`$`)return{value:void 0,error:`JSON path must start with $`};let n=t,r=`$`;for(let t=1;t<e.length;t++){let a=e[t],o=a.startsWith(`[`)?`${r}${a}`:`${r}.${a}`;if(Array.isArray(n)){let e=parseInt(a,10);if(isNaN(e)||e<0||e>=n.length)return _(`Invalid array index`,n,a,r);n=n[e],r=o}else if(i(n)){if(!Object.prototype.hasOwnProperty.call(n,a))return _(`Key not found`,n,a,r);n=n[a],r=o}else return{value:void 0,error:`Cannot access '${a}' at '${r}' - parent is not an object`,availableKeys:[]}}return{value:n}},y=(e,t)=>{let n=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(n.test(e)){let n=p(e,t);return n===void 0?{value:void 0,error:`Invalid or empty JSONPath: '${e}'`}:{value:n}}let r=e.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[],i=r.map(e=>e.replace(/^\['([^']+)'\]$/,`$1`).replace(/^\["([^"]+)"\]$/,`$1`).replace(/^\[(\d+)\]$/,`$1`));return v(i,t)}catch(e){return{value:void 0,error:`Something went wrong with evaluation of JSON path: ${String(e)}`}}},b=(e,t)=>{let n=y(e,t);if(n.error)throw Error(`${n.error}${n.availableKeys?.length?`. Available keys: ${n.availableKeys.join(`, `)}`:``}`);return n.value},x=(e,t)=>{try{return p(e,t)}catch{throw Error(`Invalid JSON path: "${e}"`)}},S=(e,t,n)=>{let r=e?.trim();if(r==null||r===``)throw Error(`Empty expression`);let a=i(t)?t:{},o=c(),s=f(r),l=s??r,p=u(l),m=g(p,a,o);if(m)return m;if(!s&&r.startsWith(`$`))return n?.incrementalJsonPath?b(r,a):x(r,a);if(!m&&!s)return e;let h=d(o,p);if(!h||p===`.`)throw Error(`Invalid expression: "${r}"`);try{return h.evalSync(a)}catch{return}},C=(e,t)=>{try{return S(e,t)}catch{return null}},w=(e,t)=>{let n=e=>Array.isArray(e)?e.map(n):typeof e==`object`&&e?w(e,t):typeof e==`string`?C(e,t):e;return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,n(t)]))},T=e=>{try{return S(e),!0}catch{return!1}};export{S as evaluate,T as isValidExpression,C as safeEvaluate,w as safeEvaluateRecord};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackone/expressions",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -21,7 +21,7 @@
21
21
  "scripts": {
22
22
  "clean": "rimraf dist",
23
23
  "prebuild": "npm run clean",
24
- "build": "tsdown --env.NODE_ENV=production",
24
+ "build": "tsdown --env.NODE_ENV=production --minify",
25
25
  "prebuild:dev": "npm run clean",
26
26
  "build:dev": "tsdown --env.NODE_ENV=development",
27
27
  "code:format": "biome format ./src ./*.mjs",
@@ -34,6 +34,7 @@
34
34
  "lint:fix": "npm run code:check:fix",
35
35
  "test": "vitest run",
36
36
  "test:watch": "vitest watch",
37
+ "test:typecheck": "tsc --noEmit --project tsconfig.tests.json",
37
38
  "publish-release": "npm publish --access=public"
38
39
  },
39
40
  "keywords": [],
@@ -42,8 +43,7 @@
42
43
  "dependencies": {
43
44
  "@stackone/utils": "*",
44
45
  "jexl": "^2.3.0",
45
- "jsonpath-plus": "^10.3.0",
46
- "lodash.get": "^4.4.2"
46
+ "jsonpath-plus": "^10.3.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/jexl": "^2.3.4",