@fibery/expression-utils 9.1.5 → 9.2.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.
- package/lib/expression-utils.js +51 -5
- package/lib/paramsPlaceholders.js +5 -1
- package/lib/utils.js +46 -4
- package/package.json +3 -3
- package/types.d.ts +10 -1
package/lib/expression-utils.js
CHANGED
|
@@ -127,7 +127,11 @@ const parseRelativeDatePlaceholder = placeholder => {
|
|
|
127
127
|
let unit = relativeDatePlaceholderMatch[2];
|
|
128
128
|
return {
|
|
129
129
|
isStartOfInterval,
|
|
130
|
-
unitForStart:
|
|
130
|
+
unitForStart:
|
|
131
|
+
// Usually the "start" unit is the same as the unit for relative date calculation.
|
|
132
|
+
// e.g. when we calculate the date "1 week ago", we want the beginning of the previous week.
|
|
133
|
+
// But sometimes they are different. We have hardcoded "day" for now, but this can be extended to any unit.
|
|
134
|
+
fullMatch.startsWith("$start-of-day") || fullMatch.startsWith("$end-of-day") ? "day" : isStartOfInterval !== undefined ? unit : undefined,
|
|
131
135
|
amount: Number.parseInt(relativeDatePlaceholderMatch[1]),
|
|
132
136
|
unit,
|
|
133
137
|
isBeforeNow: relativeDatePlaceholderMatch[3] === "before-now",
|
|
@@ -249,6 +253,11 @@ const assertIsValidExpression = expression => {
|
|
|
249
253
|
});
|
|
250
254
|
trace.assert(expression.length > 0, "empty expression does not make any sense");
|
|
251
255
|
};
|
|
256
|
+
const textTypes = ["fibery/text", "fibery/email", "fibery/url", "fibery/emoji"];
|
|
257
|
+
const dateTypes = ["fibery/date", "fibery/date-time"];
|
|
258
|
+
const dateRangeTypes = ["fibery/date-range", "fibery/date-time-range"];
|
|
259
|
+
const convertableDateTypes = ["fibery/date", "fibery/date-range"];
|
|
260
|
+
const numberTypes = ["fibery/int", "fibery/decimal"];
|
|
252
261
|
const dateRangeFunctions = new Set(["q/start", "q/end"]);
|
|
253
262
|
const firstLastFunctions = new Set(["q/first", "q/last"]);
|
|
254
263
|
const collectionOps = new Set(["q/count", "q/count-distinct", "q/sum", "q/min", "q/max", "q/avg", "q/join", "q/first", "q/last"]);
|
|
@@ -260,9 +269,6 @@ const binaryOperations = new Set(["=", "!=", "<", ">", "<=", ">=", "in",
|
|
|
260
269
|
"q/contains", "q/not-contains", "+", "-", "q/+", "q/-", "*", "/", "and", "or",
|
|
261
270
|
//asc: obsolete. use q/and, q/or
|
|
262
271
|
"q/and", "q/or", "q/in", "q/not-in"]);
|
|
263
|
-
|
|
264
|
-
// TODO: get rid of this. Use visitors everywhere
|
|
265
|
-
const naryOperations = new Set(["and", "or", "q/and", "q/or"]);
|
|
266
272
|
const logicalOperators = new Set(["and", "or", "q/and", "q/or"]);
|
|
267
273
|
const relationalOperators = new Set(["=", "!=", "<", ">", "<=", ">="]);
|
|
268
274
|
const mathOperators = new Set(["+", "-", "*", "/", "q/+", "q/-", "q/concat"]);
|
|
@@ -275,7 +281,7 @@ const isCollectionFunctionExpression = expression =>
|
|
|
275
281
|
(expression.length === 2 || expression.length === 3) && collectionOps.has(expression[0]);
|
|
276
282
|
const isAccessFunctionExpression = expresion => expresion.length === 2 && expresion[0] === "q/access?" && isFieldExpression(expresion[1]);
|
|
277
283
|
const isBinaryExpression = expression => expression.length === 3 && binaryOperations.has(expression[0]);
|
|
278
|
-
const isNaryExpression = expression => expression.length > 1 &&
|
|
284
|
+
const isNaryExpression = expression => expression.length > 1 && logicalOperators.has(expression[0]);
|
|
279
285
|
const isVariableExpression = expression => ___default["default"].isString(expression) && expression.startsWith("$");
|
|
280
286
|
const isMultiFieldAccess = expression => ___default["default"].isArray(expression) && expression.length === 2 && !isFunctionCallExpression(expression) && expression.every(x => ___default["default"].isString(x));
|
|
281
287
|
const isMultiFieldExpression = expression => Array.isArray(expression) && !isFunctionCallExpression(expression) && expression.some(x => isMultiFieldAccess(x)) && expression.every(x => !isVariableExpression(x) && !binaryOperations.has(x) && (___default["default"].isString(x) || isMultiFieldAccess(x)));
|
|
@@ -313,6 +319,38 @@ const collectFieldExpressions = (memo, expression) => {
|
|
|
313
319
|
throw new NotImplementedError(expression, "expression");
|
|
314
320
|
}
|
|
315
321
|
};
|
|
322
|
+
const getFieldObjectsByFieldExpression = ({
|
|
323
|
+
typeObject,
|
|
324
|
+
expression
|
|
325
|
+
}) => {
|
|
326
|
+
assertIsValidExpression(expression);
|
|
327
|
+
const fieldObjects = [];
|
|
328
|
+
let currentTypeObject = typeObject;
|
|
329
|
+
for (const fieldOrMultiFieldAccess of expression) {
|
|
330
|
+
if (isMultiFieldAccess(fieldOrMultiFieldAccess)) {
|
|
331
|
+
const [multiField, type] = fieldOrMultiFieldAccess;
|
|
332
|
+
const fieldObject = currentTypeObject.fieldObjectsByName[multiField];
|
|
333
|
+
fieldObjects.push(fieldObject);
|
|
334
|
+
const foundField = fieldObject.multiRelatedFieldObjects.find(f => f.holderType === type);
|
|
335
|
+
trace.assert(foundField, "incorrect expression for multi-field");
|
|
336
|
+
currentTypeObject = foundField.holderTypeObject;
|
|
337
|
+
} else {
|
|
338
|
+
const fieldObject = currentTypeObject.fieldObjectsByName[fieldOrMultiFieldAccess];
|
|
339
|
+
fieldObjects.push(fieldObject);
|
|
340
|
+
currentTypeObject = fieldObject.typeObject;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return fieldObjects;
|
|
344
|
+
};
|
|
345
|
+
const getFieldObjectByFieldExpression = (x // TODO: review types. using type assertion now to avoid rewriting half of app
|
|
346
|
+
) => ___default["default"].last(getFieldObjectsByFieldExpression(x));
|
|
347
|
+
const getFieldObjects = (expression, typeObject) => {
|
|
348
|
+
const fieldExpression = extractFieldExpressions(expression)[0];
|
|
349
|
+
return getFieldObjectsByFieldExpression({
|
|
350
|
+
typeObject,
|
|
351
|
+
expression: fieldExpression
|
|
352
|
+
});
|
|
353
|
+
};
|
|
316
354
|
|
|
317
355
|
/** @deprecated
|
|
318
356
|
* This method checks few expression forms, that we do not generate on frontend anymore(field-access shortcut, direct value as parameter).
|
|
@@ -387,6 +425,11 @@ const createExpressionVisitor = visitor => {
|
|
|
387
425
|
var utils = {
|
|
388
426
|
__proto__: null,
|
|
389
427
|
assertIsValidExpression: assertIsValidExpression,
|
|
428
|
+
textTypes: textTypes,
|
|
429
|
+
dateTypes: dateTypes,
|
|
430
|
+
dateRangeTypes: dateRangeTypes,
|
|
431
|
+
convertableDateTypes: convertableDateTypes,
|
|
432
|
+
numberTypes: numberTypes,
|
|
390
433
|
dateRangeFunctions: dateRangeFunctions,
|
|
391
434
|
firstLastFunctions: firstLastFunctions,
|
|
392
435
|
logicalOperators: logicalOperators,
|
|
@@ -405,6 +448,9 @@ var utils = {
|
|
|
405
448
|
isMultiFieldExpression: isMultiFieldExpression,
|
|
406
449
|
isFieldExpression: isFieldExpression,
|
|
407
450
|
isQueryExpression: isQueryExpression,
|
|
451
|
+
getFieldObjectsByFieldExpression: getFieldObjectsByFieldExpression,
|
|
452
|
+
getFieldObjectByFieldExpression: getFieldObjectByFieldExpression,
|
|
453
|
+
getFieldObjects: getFieldObjects,
|
|
408
454
|
extractFieldExpressions: extractFieldExpressions,
|
|
409
455
|
createExpressionVisitor: createExpressionVisitor
|
|
410
456
|
};
|
|
@@ -126,7 +126,11 @@ const parseRelativeDatePlaceholder = placeholder => {
|
|
|
126
126
|
let unit = relativeDatePlaceholderMatch[2];
|
|
127
127
|
return {
|
|
128
128
|
isStartOfInterval,
|
|
129
|
-
unitForStart:
|
|
129
|
+
unitForStart:
|
|
130
|
+
// Usually the "start" unit is the same as the unit for relative date calculation.
|
|
131
|
+
// e.g. when we calculate the date "1 week ago", we want the beginning of the previous week.
|
|
132
|
+
// But sometimes they are different. We have hardcoded "day" for now, but this can be extended to any unit.
|
|
133
|
+
fullMatch.startsWith("$start-of-day") || fullMatch.startsWith("$end-of-day") ? "day" : isStartOfInterval !== undefined ? unit : undefined,
|
|
130
134
|
amount: Number.parseInt(relativeDatePlaceholderMatch[1]),
|
|
131
135
|
unit,
|
|
132
136
|
isBeforeNow: relativeDatePlaceholderMatch[3] === "before-now",
|
package/lib/utils.js
CHANGED
|
@@ -23,6 +23,11 @@ const assertIsValidExpression = expression => {
|
|
|
23
23
|
});
|
|
24
24
|
trace.assert(expression.length > 0, "empty expression does not make any sense");
|
|
25
25
|
};
|
|
26
|
+
const textTypes = ["fibery/text", "fibery/email", "fibery/url", "fibery/emoji"];
|
|
27
|
+
const dateTypes = ["fibery/date", "fibery/date-time"];
|
|
28
|
+
const dateRangeTypes = ["fibery/date-range", "fibery/date-time-range"];
|
|
29
|
+
const convertableDateTypes = ["fibery/date", "fibery/date-range"];
|
|
30
|
+
const numberTypes = ["fibery/int", "fibery/decimal"];
|
|
26
31
|
const dateRangeFunctions = new Set(["q/start", "q/end"]);
|
|
27
32
|
const firstLastFunctions = new Set(["q/first", "q/last"]);
|
|
28
33
|
const collectionOps = new Set(["q/count", "q/count-distinct", "q/sum", "q/min", "q/max", "q/avg", "q/join", "q/first", "q/last"]);
|
|
@@ -34,9 +39,6 @@ const binaryOperations = new Set(["=", "!=", "<", ">", "<=", ">=", "in",
|
|
|
34
39
|
"q/contains", "q/not-contains", "+", "-", "q/+", "q/-", "*", "/", "and", "or",
|
|
35
40
|
//asc: obsolete. use q/and, q/or
|
|
36
41
|
"q/and", "q/or", "q/in", "q/not-in"]);
|
|
37
|
-
|
|
38
|
-
// TODO: get rid of this. Use visitors everywhere
|
|
39
|
-
const naryOperations = new Set(["and", "or", "q/and", "q/or"]);
|
|
40
42
|
const logicalOperators = new Set(["and", "or", "q/and", "q/or"]);
|
|
41
43
|
const relationalOperators = new Set(["=", "!=", "<", ">", "<=", ">="]);
|
|
42
44
|
const mathOperators = new Set(["+", "-", "*", "/", "q/+", "q/-", "q/concat"]);
|
|
@@ -49,7 +51,7 @@ const isCollectionFunctionExpression = expression =>
|
|
|
49
51
|
(expression.length === 2 || expression.length === 3) && collectionOps.has(expression[0]);
|
|
50
52
|
const isAccessFunctionExpression = expresion => expresion.length === 2 && expresion[0] === "q/access?" && isFieldExpression(expresion[1]);
|
|
51
53
|
const isBinaryExpression = expression => expression.length === 3 && binaryOperations.has(expression[0]);
|
|
52
|
-
const isNaryExpression = expression => expression.length > 1 &&
|
|
54
|
+
const isNaryExpression = expression => expression.length > 1 && logicalOperators.has(expression[0]);
|
|
53
55
|
const isVariableExpression = expression => ___default["default"].isString(expression) && expression.startsWith("$");
|
|
54
56
|
const isMultiFieldAccess = expression => ___default["default"].isArray(expression) && expression.length === 2 && !isFunctionCallExpression(expression) && expression.every(x => ___default["default"].isString(x));
|
|
55
57
|
const isMultiFieldExpression = expression => Array.isArray(expression) && !isFunctionCallExpression(expression) && expression.some(x => isMultiFieldAccess(x)) && expression.every(x => !isVariableExpression(x) && !binaryOperations.has(x) && (___default["default"].isString(x) || isMultiFieldAccess(x)));
|
|
@@ -87,6 +89,38 @@ const collectFieldExpressions = (memo, expression) => {
|
|
|
87
89
|
throw new NotImplementedError(expression, "expression");
|
|
88
90
|
}
|
|
89
91
|
};
|
|
92
|
+
const getFieldObjectsByFieldExpression = ({
|
|
93
|
+
typeObject,
|
|
94
|
+
expression
|
|
95
|
+
}) => {
|
|
96
|
+
assertIsValidExpression(expression);
|
|
97
|
+
const fieldObjects = [];
|
|
98
|
+
let currentTypeObject = typeObject;
|
|
99
|
+
for (const fieldOrMultiFieldAccess of expression) {
|
|
100
|
+
if (isMultiFieldAccess(fieldOrMultiFieldAccess)) {
|
|
101
|
+
const [multiField, type] = fieldOrMultiFieldAccess;
|
|
102
|
+
const fieldObject = currentTypeObject.fieldObjectsByName[multiField];
|
|
103
|
+
fieldObjects.push(fieldObject);
|
|
104
|
+
const foundField = fieldObject.multiRelatedFieldObjects.find(f => f.holderType === type);
|
|
105
|
+
trace.assert(foundField, "incorrect expression for multi-field");
|
|
106
|
+
currentTypeObject = foundField.holderTypeObject;
|
|
107
|
+
} else {
|
|
108
|
+
const fieldObject = currentTypeObject.fieldObjectsByName[fieldOrMultiFieldAccess];
|
|
109
|
+
fieldObjects.push(fieldObject);
|
|
110
|
+
currentTypeObject = fieldObject.typeObject;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return fieldObjects;
|
|
114
|
+
};
|
|
115
|
+
const getFieldObjectByFieldExpression = (x // TODO: review types. using type assertion now to avoid rewriting half of app
|
|
116
|
+
) => ___default["default"].last(getFieldObjectsByFieldExpression(x));
|
|
117
|
+
const getFieldObjects = (expression, typeObject) => {
|
|
118
|
+
const fieldExpression = extractFieldExpressions(expression)[0];
|
|
119
|
+
return getFieldObjectsByFieldExpression({
|
|
120
|
+
typeObject,
|
|
121
|
+
expression: fieldExpression
|
|
122
|
+
});
|
|
123
|
+
};
|
|
90
124
|
|
|
91
125
|
/** @deprecated
|
|
92
126
|
* This method checks few expression forms, that we do not generate on frontend anymore(field-access shortcut, direct value as parameter).
|
|
@@ -159,11 +193,17 @@ const createExpressionVisitor = visitor => {
|
|
|
159
193
|
};
|
|
160
194
|
|
|
161
195
|
exports.assertIsValidExpression = assertIsValidExpression;
|
|
196
|
+
exports.convertableDateTypes = convertableDateTypes;
|
|
162
197
|
exports.createExpressionVisitor = createExpressionVisitor;
|
|
163
198
|
exports.dateRangeFunctions = dateRangeFunctions;
|
|
199
|
+
exports.dateRangeTypes = dateRangeTypes;
|
|
200
|
+
exports.dateTypes = dateTypes;
|
|
164
201
|
exports.extractFieldExpressions = extractFieldExpressions;
|
|
165
202
|
exports.firstLastFunctions = firstLastFunctions;
|
|
166
203
|
exports.fromRootKeyword = fromRootKeyword;
|
|
204
|
+
exports.getFieldObjectByFieldExpression = getFieldObjectByFieldExpression;
|
|
205
|
+
exports.getFieldObjects = getFieldObjects;
|
|
206
|
+
exports.getFieldObjectsByFieldExpression = getFieldObjectsByFieldExpression;
|
|
167
207
|
exports.isAccessFunctionExpression = isAccessFunctionExpression;
|
|
168
208
|
exports.isBinaryExpression = isBinaryExpression;
|
|
169
209
|
exports.isCollectionFunctionExpression = isCollectionFunctionExpression;
|
|
@@ -178,4 +218,6 @@ exports.isQueryExpression = isQueryExpression;
|
|
|
178
218
|
exports.isVariableExpression = isVariableExpression;
|
|
179
219
|
exports.logicalOperators = logicalOperators;
|
|
180
220
|
exports.mathOperators = mathOperators;
|
|
221
|
+
exports.numberTypes = numberTypes;
|
|
181
222
|
exports.relationalOperators = relationalOperators;
|
|
223
|
+
exports.textTypes = textTypes;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fibery/expression-utils",
|
|
3
|
-
"version": "9.1
|
|
3
|
+
"version": "9.2.1",
|
|
4
4
|
"description": "utils for working with fibery api expressions",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./lib/expression-utils.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"lodash": "4.17.21",
|
|
28
28
|
"moment": "2.29.4",
|
|
29
|
-
"@fibery/helpers": "1.3.
|
|
29
|
+
"@fibery/helpers": "1.3.3"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@babel/core": "7.23.9",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@fibery/eslint-config": "8.6.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@fibery/schema": "10.2.
|
|
41
|
+
"@fibery/schema": "10.2.8"
|
|
42
42
|
},
|
|
43
43
|
"jest": {
|
|
44
44
|
"testEnvironment": "node",
|
package/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import {Schema, TypeObject
|
|
2
|
+
import {FieldObject, Schema, TypeObject} from "@fibery/schema";
|
|
3
3
|
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
5
|
export type $TSFixMe = any;
|
|
@@ -61,6 +61,14 @@ declare module "@fibery/expression-utils" {
|
|
|
61
61
|
firstLastFunctions: Set<string>;
|
|
62
62
|
isQueryExpression: (expression: $TSFixMe) => boolean;
|
|
63
63
|
isVariableExpression: (expression: $TSFixMe) => boolean;
|
|
64
|
+
isFunctionCallExpression: (expression: $TSFixMe) => boolean;
|
|
65
|
+
getFieldObjectByFieldExpression: (x: {typeObject: TypeObject; expression: Expression}) => FieldObject | undefined;
|
|
66
|
+
isFieldExpression: (expression: $TSFixMe) => boolean;
|
|
67
|
+
isBinaryExpression: (expression: $TSFixMe) => boolean;
|
|
68
|
+
isMultiFieldAccess: (expression: $TSFixMe) => boolean;
|
|
69
|
+
logicalOperators: Set<string>;
|
|
70
|
+
relationalOperators: Set<string>;
|
|
71
|
+
mathOperators: Set<string>;
|
|
64
72
|
};
|
|
65
73
|
export const contextVariables: {
|
|
66
74
|
getEntityQueryVariables: (
|
|
@@ -69,6 +77,7 @@ declare module "@fibery/expression-utils" {
|
|
|
69
77
|
};
|
|
70
78
|
export const paramsPlaceholders: {
|
|
71
79
|
formulaTodayDateParamPlaceholder: string;
|
|
80
|
+
formulaNowDateTimeParamPlaceholder: string;
|
|
72
81
|
dynamicFilterParamPrefix: string;
|
|
73
82
|
|
|
74
83
|
isDynamicFilterParam(paramValue: string): boolean;
|