@stackone/expressions 0.14.0 → 0.15.1

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.
@@ -0,0 +1,11 @@
1
+ //#region src/evaluate.d.ts
2
+ declare const evaluateExpression: (expression?: string | null, context?: unknown, options?: {
3
+ incrementalJsonPath?: boolean;
4
+ }) => unknown;
5
+ declare const safeEvaluateExpression: (expression?: string | null, context?: unknown) => unknown;
6
+ declare const safeEvaluateRecord: (record: Record<string, unknown>, context?: unknown) => Record<string, unknown>;
7
+ //#endregion
8
+ //#region src/validate.d.ts
9
+ declare const isValidExpression: (expression?: string | null) => boolean;
10
+ //#endregion
11
+ export { evaluateExpression as evaluate, isValidExpression, safeEvaluateExpression as safeEvaluate, safeEvaluateRecord };
@@ -0,0 +1,11 @@
1
+ //#region src/evaluate.d.ts
2
+ declare const evaluateExpression: (expression?: string | null, context?: unknown, options?: {
3
+ incrementalJsonPath?: boolean;
4
+ }) => unknown;
5
+ declare const safeEvaluateExpression: (expression?: string | null, context?: unknown) => unknown;
6
+ declare const safeEvaluateRecord: (record: Record<string, unknown>, context?: unknown) => Record<string, unknown>;
7
+ //#endregion
8
+ //#region src/validate.d.ts
9
+ declare const isValidExpression: (expression?: string | null) => boolean;
10
+ //#endregion
11
+ export { evaluateExpression as evaluate, isValidExpression, safeEvaluateExpression as safeEvaluate, safeEvaluateRecord };
package/dist/index.js CHANGED
@@ -1 +1,274 @@
1
- "use strict";var r=require("@stackone/utils"),e=require("jexl"),t=require("jsonpath-plus"),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 s=/\${([^}]+)}/g,o=(r,e)=>{try{return r.compile(e)}catch{return null}},c=(r,e)=>{try{if(!r.startsWith("$"))throw new Error(`JSONPath expression must start with '$': ${r}`);if(r.includes("$_")||r.match(/\$[^.\[]/g)||r.includes("[")&&!r.includes("]")||r.includes(" ")&&!r.includes("["))throw new Error(`Invalid JSONPath expression: ${r}`);const n=t.JSONPath({path:r,json:e});if(0===n.length)return;return 1===n.length?n[0]:n}catch{throw new Error(`Invalid JSONPath expression: ${r}`)}},l=(r,e,t)=>r.replace(e,String(t)),u=(e,t,a)=>{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,a,i)=>{const s=e.path.trim();if(!s)return i;try{const c=o(t,s);if(c){const r=c.evalSync(a);if(void 0!==r)return l(i,e.toReplace,r)}const u=n(a,s);return r.notMissing(u)?l(i,e.toReplace,u):i}catch{return i}})(i,a,t,e)),String(e))},d=(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:[]}},y=(e,t)=>{const n=/(\.\.)|(\[\*\])|(\[\?\()|(\[\d+:\d+\])/;try{if(n.test(e)){const r=c(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],s=i.startsWith("[")?`${a}${i}`:`${a}.${i}`;if(Array.isArray(n)){const r=parseInt(i,10);if(isNaN(r)||r<0||r>=n.length)return d("Invalid array index",n,i,a);n=n[r],a=s}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 d("Key not found",n,i,a);n=n[i],a=s}}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)}`}}},h=(e,t,n)=>{const a=e?.trim();if(null==a||""===a)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,n)=>r.calculateYearsElapsed({startDate:e,endDate:n,format:t}))),t.addFunction("hasPassed",((e,t,n)=>r.dateHasPassed({date:e,yearsToAdd:n,format:t}))),t.addFunction("now",(()=>(new Date).toISOString())),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("present",(e=>r.notMissing(e))),t.addFunction("missing",(e=>r.isMissing(e))),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)})(a),h=(r=>r.replace(/\$\./g,"").trim())(d??a),p=u(h,s,l);if(p)return p;if(!d&&a.startsWith("$"))return n?.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})(a,s):((r,e)=>{try{return c(r,e)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(a,s);if(!p&&!d)return e;const v=o(l,h);if(!v||"."===h)throw new Error(`Invalid expression: "${a}"`);try{return v.evalSync(s)}catch{return}},p=(r,e)=>{try{return h(r,e)}catch{return null}},v=(r,e)=>{const t=r=>Array.isArray(r)?r.map(t):"object"==typeof r&&null!==r?v(r,e):"string"==typeof r?p(r,e):r;return Object.fromEntries(Object.entries(r).map((([r,e])=>[r,t(e)])))};exports.evaluate=h,exports.isValidExpression=r=>{try{return h(r),!0}catch{return!1}},exports.safeEvaluate=p,exports.safeEvaluateRecord=v;
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;
package/dist/index.mjs ADDED
@@ -0,0 +1,248 @@
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackone/expressions",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.mjs",
@@ -10,12 +10,20 @@
10
10
  "package.json",
11
11
  "README.md"
12
12
  ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/types/index.d.ts",
16
+ "import": "./dist/index.es.mjs",
17
+ "require": "./dist/index.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
13
21
  "scripts": {
14
22
  "clean": "rimraf dist",
15
23
  "prebuild": "npm run clean",
16
- "build": "rollup -c --environment NODE_ENV:production",
24
+ "build": "tsdown --env.NODE_ENV=production",
17
25
  "prebuild:dev": "npm run clean",
18
- "build:dev": "rollup -c --environment NODE_ENV:development",
26
+ "build:dev": "tsdown --env.NODE_ENV=development",
19
27
  "code:format": "biome format ./src ./*.mjs",
20
28
  "code:format:fix": "biome format --write ./src ./*.mjs",
21
29
  "code:lint": "biome lint --error-on-warnings ./src ./*.mjs",
@@ -40,5 +48,12 @@
40
48
  "devDependencies": {
41
49
  "@types/jexl": "^2.3.4",
42
50
  "@types/lodash.get": "^4.4.9"
51
+ },
52
+ "tsdown": {
53
+ "dts": true,
54
+ "format": [
55
+ "esm",
56
+ "cjs"
57
+ ]
43
58
  }
44
59
  }
package/dist/index.es.mjs DELETED
@@ -1 +0,0 @@
1
- import{calculateNextAnniversary as r,calculateYearsElapsed as t,dateHasPassed as e,isMissing as n,notMissing as a,isObject as i}from"@stackone/utils";import*as o from"jexl";import{JSONPath as s}from"jsonpath-plus";import l from"lodash.get";const c=/\${([^}]+)}/g,u=(r,t)=>{try{return r.compile(t)}catch{return null}},d=(r,t)=>{try{if(!r.startsWith("$"))throw new Error(`JSONPath expression must start with '$': ${r}`);if(r.includes("$_")||r.match(/\$[^.\[]/g)||r.includes("[")&&!r.includes("]")||r.includes(" ")&&!r.includes("["))throw new Error(`Invalid JSONPath expression: ${r}`);const e=s({path:r,json:t});if(0===e.length)return;return 1===e.length?e[0]:e}catch{throw new Error(`Invalid JSONPath expression: ${r}`)}},y=(r,t,e)=>r.replace(t,String(e)),h=(r,t,e)=>{const n=r.match(c);if(!n)return;const i=n.length,o=new Array(i);for(let r=0;r<i;r++)o[r]={toReplace:n[r],path:n[r].slice(2,-1)};return o.reduce(((r,n)=>((r,t,e,n)=>{const i=r.path.trim();if(!i)return n;try{const o=u(t,i);if(o){const t=o.evalSync(e);if(void 0!==t)return y(n,r.toReplace,t)}const s=l(e,i);return a(s)?y(n,r.toReplace,s):n}catch{return n}})(n,e,t,r)),String(r))},p=(r,t,e,n)=>{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 '${n}'`,availableKeys:t.map(((r,t)=>t.toString()))}}return i(t)?{value:void 0,error:`Key '${e}' not found at '${n}'`,availableKeys:Object.keys(t)}:{value:void 0,error:`${r} at '${n}'`,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 n=r.match(/\$|(?:\['([^']+)'\])|(?:\["([^"]+)"\])|\[\d+\]|[^[\].]+/g)||[];return((r,t)=>{if("$"!==r[0])return{value:void 0,error:"JSON path must start with $"};let e=t,n="$";for(let t=1;t<r.length;t++){const a=r[t],o=a.startsWith("[")?`${n}${a}`:`${n}.${a}`;if(Array.isArray(e)){const r=parseInt(a,10);if(isNaN(r)||r<0||r>=e.length)return p("Invalid array index",e,a,n);e=e[r],n=o}else{if(!i(e))return{value:void 0,error:`Cannot access '${a}' at '${n}' - parent is not an object`,availableKeys:[]};if(!Object.prototype.hasOwnProperty.call(e,a))return p("Key not found",e,a,n);e=e[a],n=o}}return{value:e}})(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)}`}}},f=(s,l,c)=>{const y=s?.trim();if(null==y||""===y)throw new Error("Empty expression");const p=i(l)?l:{},f=((i=()=>new o.Jexl)=>{const s=i();return s.addFunction("nextAnniversary",((t,e)=>r({initialDate:t,format:e}))),s.addFunction("yearsElapsed",((r,e,n)=>t({startDate:r,endDate:n,format:e}))),s.addFunction("hasPassed",((r,t,n)=>e({date:r,yearsToAdd:n,format:t}))),s.addFunction("now",(()=>(new Date).toISOString())),s.addFunction("includes",((r,t)=>!(n(r)||!Array.isArray(r)||0===r.length||n(t))&&(Array.isArray(t)?t.every((t=>r.includes(t))):r.includes(t)))),s.addFunction("includesSome",((r,t)=>!(n(r)||!Array.isArray(r)||0===r.length||n(t))&&(Array.isArray(t)?t.some((t=>r.includes(t))):r.includes(t)))),s.addFunction("present",(r=>a(r))),s.addFunction("missing",(r=>n(r))),s.addFunction("keys",(r=>n(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.keys(r))),s.addFunction("values",(r=>n(r)||"object"!=typeof r||Array.isArray(r)?[]:Object.values(r))),s.addBinaryOp("??",0,((r,t)=>n(r)?t:n(t)?r:r??t)),s})(),$=(r=>{if(r.startsWith("{{")&&r.endsWith("}}"))return r.slice(2,-2)})(y),m=(r=>r.replace(/\$\./g,"").trim())($??y),w=h(m,p,f);if(w)return w;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,p):((r,t)=>{try{return d(r,t)}catch{throw new Error(`Invalid JSON path: "${r}"`)}})(y,p);if(!w&&!$)return s;const g=u(f,m);if(!g||"."===m)throw new Error(`Invalid expression: "${y}"`);try{return g.evalSync(p)}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)])))},w=r=>{try{return f(r),!0}catch{return!1}};export{f as evaluate,w as isValidExpression,$ as safeEvaluate,m as safeEvaluateRecord};
@@ -1 +0,0 @@
1
- export declare const INTERPOLATED_EXPRESSION_REGEX: RegExp;
@@ -1,5 +0,0 @@
1
- export declare const evaluateExpression: (expression?: string | null, context?: unknown, options?: {
2
- incrementalJsonPath?: boolean;
3
- }) => unknown;
4
- export declare const safeEvaluateExpression: (expression?: string | null, context?: unknown) => unknown;
5
- export declare const safeEvaluateRecord: (record: Record<string, unknown>, context?: unknown) => Record<string, unknown>;
@@ -1,39 +0,0 @@
1
- export declare const createExpressionHandler: (instanceBuilder?: () => {
2
- addBinaryOp(operator: string, precedence: number, fn: (left: any, right: any) => any): void;
3
- addUnaryOp(operator: string, fn: (right: any) => any): void;
4
- addTransform(name: string, fn: (value: any, ...args: any[]) => any): void;
5
- addTransforms(map: {
6
- [key: string]: (value: any, ...args: any[]) => any;
7
- }): void;
8
- getTransform(name: string): (value: any, ...args: any[]) => any;
9
- addFunction(name: string, fn: (value: any, ...args: any[]) => any): void;
10
- addFunctions(map: {
11
- [key: string]: (value: any, ...args: any[]) => any;
12
- }): void;
13
- getFunction(name: string): (value: any, ...args: any[]) => any;
14
- eval(expression: string, context?: import("jexl/Expression").Context): Promise<any>;
15
- evalSync(expression: string, context?: import("jexl/Expression").Context): any;
16
- compile(expression: string): import("jexl/Expression").default;
17
- createExpression(expression: string): import("jexl/Expression").default;
18
- removeOp(operator: string): void;
19
- _grammar: import("jexl/Grammar").default;
20
- }) => {
21
- addBinaryOp(operator: string, precedence: number, fn: (left: any, right: any) => any): void;
22
- addUnaryOp(operator: string, fn: (right: any) => any): void;
23
- addTransform(name: string, fn: (value: any, ...args: any[]) => any): void;
24
- addTransforms(map: {
25
- [key: string]: (value: any, ...args: any[]) => any;
26
- }): void;
27
- getTransform(name: string): (value: any, ...args: any[]) => any;
28
- addFunction(name: string, fn: (value: any, ...args: any[]) => any): void;
29
- addFunctions(map: {
30
- [key: string]: (value: any, ...args: any[]) => any;
31
- }): void;
32
- getFunction(name: string): (value: any, ...args: any[]) => any;
33
- eval(expression: string, context?: import("jexl/Expression").Context): Promise<any>;
34
- evalSync(expression: string, context?: import("jexl/Expression").Context): any;
35
- compile(expression: string): import("jexl/Expression").default;
36
- createExpression(expression: string): import("jexl/Expression").default;
37
- removeOp(operator: string): void;
38
- _grammar: import("jexl/Grammar").default;
39
- };
@@ -1,3 +0,0 @@
1
- export { evaluateExpression as evaluate } from './evaluate';
2
- export { safeEvaluateExpression as safeEvaluate, safeEvaluateRecord } from './evaluate';
3
- export { isValidExpression } from './validate';
@@ -1,8 +0,0 @@
1
- export type ExpressionContext = {
2
- [key: string]: unknown;
3
- };
4
- export type JSONPathEvalResult = {
5
- value: unknown;
6
- error?: string;
7
- availableKeys?: string[];
8
- };
@@ -1,16 +0,0 @@
1
- import Expression from 'jexl/Expression';
2
- import { ExpressionContext } from './types';
3
- export declare const cleanExpression: (expression: string) => string;
4
- export declare const compileExpression: (expressionHandler: {
5
- compile: (expr: string) => Expression;
6
- }, expression: string) => Expression | null;
7
- export declare const extractExpressionBetweenDoubleCurlyBraces: (expression: string) => string | undefined;
8
- export declare const evaluateJsonPath: (expression: string, context: unknown) => unknown;
9
- export declare const evaluateStringInterpolations: (expression: string, context: ExpressionContext, expressionHandler: {
10
- compile: (expr: string) => Expression;
11
- }) => string | undefined;
12
- export declare const evaluateJsonPathIncremental: (path: string, context: unknown) => {
13
- value: unknown;
14
- error?: string;
15
- availableKeys?: string[];
16
- };
@@ -1 +0,0 @@
1
- export declare const isValidExpression: (expression?: string | null) => boolean;