@fibery/expression-utils 1.1.15 → 1.1.16

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,469 @@
1
+ var _ = require('lodash');
2
+ require('@fibery/helpers/utils/trace');
3
+
4
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
5
+
6
+ var ___default = /*#__PURE__*/_interopDefaultLegacy(_);
7
+
8
+ function _extends() {
9
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
10
+ for (var i = 1; i < arguments.length; i++) {
11
+ var source = arguments[i];
12
+ for (var key in source) {
13
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
14
+ target[key] = source[key];
15
+ }
16
+ }
17
+ }
18
+ return target;
19
+ };
20
+ return _extends.apply(this, arguments);
21
+ }
22
+
23
+ const stringify = x => {
24
+ if (x === undefined) {
25
+ return "undefined";
26
+ }
27
+ return JSON.stringify(x);
28
+ };
29
+ class NotImplementedError extends Error {
30
+ constructor(value, itemType = undefined) {
31
+ super([`"${stringify(value)}"`, itemType, "is not implemented"].filter(x => x !== undefined).join(" "));
32
+ }
33
+ }
34
+
35
+ const firstLastFunctions = new Set(["q/first", "q/last"]);
36
+ const collectionOps = new Set(["q/count", "q/count-distinct", "q/sum", "q/min", "q/max", "q/avg", "q/join", "q/first", "q/last"]);
37
+ // [op, left, right]
38
+ // [=, $true, $false]
39
+ // [=, $my-id, ["fibery/id"]]
40
+ const binaryOperations = new Set(["=", "!=", "<", ">", "<=", ">=", "in",
41
+ //asc: obsolete,use q/in
42
+ "q/contains", "q/not-contains", "+", "-", "q/+", "q/-", "*", "/", "and", "or",
43
+ //asc: obsolete. use q/and, q/or
44
+ "q/and", "q/or", "q/in", "q/not-in"]);
45
+ const logicalOperators = new Set(["and", "or", "q/and", "q/or"]);
46
+ const isFunctionCallExpression = expression => expression.length > 1 && ___default["default"].isString(expression[0]) && (expression[0].startsWith("q/") || ["=", "!=", "<", ">", "<=", ">=", "+", "-", "*", "/", "in", "and", "or", "not-in"].includes(expression[0]));
47
+ const fromRootKeyword = "q/from-root";
48
+ const isFromRootFieldExpression = expression => ___default["default"].isArray(expression) && expression[0] === fromRootKeyword;
49
+ const isCollectionFunctionExpression = expression =>
50
+ //expression has length 3 in case of q/join
51
+ (expression.length === 2 || expression.length === 3) && collectionOps.has(expression[0]);
52
+ const isVariableExpression = expression => ___default["default"].isString(expression) && expression.startsWith("$");
53
+ const isFieldExpression = expression => Array.isArray(expression) && expression.every(x => !isVariableExpression(x) && !binaryOperations.has(x) && ___default["default"].isString(x));
54
+ const isQueryExpression = expression => {
55
+ if (___default["default"].isObject(expression) && "q/from" in expression) {
56
+ const fromExpression = expression["q/from"];
57
+ //asc: fromExpression === null for denormalizeSelect for reference collection case
58
+ return fromExpression === null || isFieldExpression(fromExpression);
59
+ }
60
+ return false;
61
+ };
62
+ const createExpressionVisitor = visitor => {
63
+ let visitorWithDefault = null;
64
+ const visitorDefault = {
65
+ visitVariableExpression: expression => expression,
66
+ visitFunctionCallExpression: ([fnName, ...args]) => [fnName, ...args.map(x => visitorWithDefault.visitExpression(x))],
67
+ visitFromRootFieldExpression: ([fromRootKeyword, ...rest]) => [fromRootKeyword, ...rest.map(x => visitorWithDefault.visitExpression(x))],
68
+ visitFieldExpression: expression => expression,
69
+ visitOrderByExpression: orderByExpression => orderByExpression.map(x => {
70
+ const [fieldExpression, orderDir] = x;
71
+ const fieldExpressionNew = visitorWithDefault.visitExpression(fieldExpression);
72
+ return [fieldExpressionNew, orderDir];
73
+ }),
74
+ visitQueryExpression: subQueryExpression => {
75
+ const {
76
+ "q/from": fromExpression,
77
+ "q/select": selectExpression,
78
+ "q/where": whereExpression,
79
+ "q/order-by": orderByExpression
80
+ } = subQueryExpression;
81
+ return _extends({}, subQueryExpression, fromExpression ? {
82
+ "q/from": visitorWithDefault.visitFieldExpression(fromExpression)
83
+ } : null, selectExpression ? {
84
+ "q/select": ___default["default"].isPlainObject(selectExpression) ? ___default["default"].mapValues(selectExpression, val => visitorWithDefault.visitExpression(val)) : visitorWithDefault.visitExpression(selectExpression)
85
+ } : null, whereExpression ? {
86
+ "q/where": visitorWithDefault.visitExpression(whereExpression)
87
+ } : null, orderByExpression ? {
88
+ "q/order-by": visitorWithDefault.visitOrderByExpression(orderByExpression)
89
+ } : null);
90
+ },
91
+ visitExpression: expression => {
92
+ if (expression === null) {
93
+ throw new NotImplementedError(expression, "expression");
94
+ } else if (isVariableExpression(expression)) {
95
+ return visitorWithDefault.visitVariableExpression(expression, visitorDefault);
96
+ } else if (isFromRootFieldExpression(expression)) {
97
+ return visitorWithDefault.visitFromRootFieldExpression(expression, visitorDefault);
98
+ } else if (isFunctionCallExpression(expression)) {
99
+ return visitorWithDefault.visitFunctionCallExpression(expression, visitorDefault);
100
+ } else if (isFieldExpression(expression)) {
101
+ return visitorWithDefault.visitFieldExpression(expression, visitorDefault);
102
+ } else if (isQueryExpression(expression)) {
103
+ return visitorWithDefault.visitQueryExpression(expression, visitorDefault);
104
+ } else {
105
+ throw new NotImplementedError(expression, "expression");
106
+ }
107
+ }
108
+ };
109
+ visitorWithDefault = _extends({}, visitorDefault, visitor);
110
+ return visitorWithDefault;
111
+ };
112
+
113
+ const defaultIdsWithNamesOnFieldNotFound = ({
114
+ fieldExpressionInNamesTerms,
115
+ fieldId
116
+ }) => {
117
+ return {
118
+ currentTypeObject: null,
119
+ fieldExpressionInNamesTerms: [...fieldExpressionInNamesTerms, fieldId]
120
+ };
121
+ };
122
+ const visitFieldExpressionForReplaceIdsWithNamesVisitor = ({
123
+ expression,
124
+ typeObject,
125
+ onFieldNotFound
126
+ }) => expression.reduce(({
127
+ currentTypeObject,
128
+ fieldExpressionInNamesTerms
129
+ }, fieldId) => {
130
+ if (currentTypeObject && currentTypeObject.fieldObjectsById.hasOwnProperty(fieldId)) {
131
+ const fieldObject = currentTypeObject.fieldObjectsById[fieldId];
132
+ return {
133
+ currentTypeObject: fieldObject.typeObject,
134
+ fieldExpressionInNamesTerms: [...fieldExpressionInNamesTerms, fieldObject.name]
135
+ };
136
+ } else {
137
+ return onFieldNotFound({
138
+ currentTypeObject,
139
+ fieldExpressionInNamesTerms,
140
+ fieldId,
141
+ expression
142
+ });
143
+ }
144
+ }, {
145
+ currentTypeObject: typeObject,
146
+ fieldExpressionInNamesTerms: []
147
+ });
148
+ const replaceIdsWithNamesVisitor = (typeObject, onFieldNotFound = defaultIdsWithNamesOnFieldNotFound) => {
149
+ const visitor = createExpressionVisitor({
150
+ visitFieldExpression: expression => visitFieldExpressionForReplaceIdsWithNamesVisitor({
151
+ expression,
152
+ typeObject,
153
+ onFieldNotFound
154
+ }).fieldExpressionInNamesTerms,
155
+ visitQueryExpression: subQueryExpression => {
156
+ const {
157
+ "q/from": fromExpression,
158
+ "q/select": selectExpression,
159
+ "q/where": whereExpression,
160
+ "q/order-by": orderByExpression
161
+ } = subQueryExpression;
162
+ const subQueryTypeObject = visitFieldExpressionForReplaceIdsWithNamesVisitor({
163
+ expression: fromExpression,
164
+ onFieldNotFound,
165
+ typeObject
166
+ }).currentTypeObject;
167
+ if (subQueryTypeObject) {
168
+ const subQueryVisitor = replaceIdsWithNamesVisitor(subQueryTypeObject, onFieldNotFound);
169
+ return _extends({}, subQueryExpression, {
170
+ "q/from": visitor.visitFieldExpression(fromExpression),
171
+ "q/select": ___default["default"].isPlainObject(selectExpression) ? ___default["default"].mapValues(selectExpression, val => subQueryVisitor.visitExpression(val)) : subQueryVisitor.visitExpression(selectExpression)
172
+ }, whereExpression ? {
173
+ "q/where": subQueryVisitor.visitExpression(whereExpression)
174
+ } : null, orderByExpression ? {
175
+ "q/order-by": subQueryVisitor.visitOrderByExpression(orderByExpression)
176
+ } : null);
177
+ }
178
+ return subQueryExpression;
179
+ }
180
+ });
181
+ return visitor;
182
+ };
183
+ const defaultNamesWithIdsOnFieldNotFound = ({
184
+ fieldExpressionInIdsTerms,
185
+ field
186
+ }) => {
187
+ return {
188
+ currentTypeObject: null,
189
+ fieldExpressionInIdsTerms: [...fieldExpressionInIdsTerms, field]
190
+ };
191
+ };
192
+ const visitFieldExpressionForReplaceNamesWithIdsVisitor = ({
193
+ expression,
194
+ onFieldNotFound,
195
+ typeObject
196
+ }) => expression.reduce(({
197
+ currentTypeObject,
198
+ fieldExpressionInIdsTerms
199
+ }, field) => {
200
+ if (currentTypeObject && currentTypeObject.fieldObjectsByName.hasOwnProperty(field)) {
201
+ const fieldObject = currentTypeObject.fieldObjectsByName[field];
202
+ return {
203
+ currentTypeObject: fieldObject.typeObject,
204
+ fieldExpressionInIdsTerms: [...fieldExpressionInIdsTerms, fieldObject.id]
205
+ };
206
+ } else {
207
+ return onFieldNotFound({
208
+ currentTypeObject,
209
+ fieldExpressionInIdsTerms,
210
+ field,
211
+ expression
212
+ });
213
+ }
214
+ }, {
215
+ currentTypeObject: typeObject,
216
+ fieldExpressionInIdsTerms: []
217
+ });
218
+ const replaceNamesWithIdsVisitor = (typeObject, onFieldNotFound = defaultNamesWithIdsOnFieldNotFound) => {
219
+ const visitor = createExpressionVisitor({
220
+ visitFieldExpression: expression => visitFieldExpressionForReplaceNamesWithIdsVisitor({
221
+ expression,
222
+ onFieldNotFound,
223
+ typeObject
224
+ }).fieldExpressionInIdsTerms,
225
+ visitQueryExpression: subQueryExpression => {
226
+ const {
227
+ "q/from": fromExpression,
228
+ "q/select": selectExpression,
229
+ "q/where": whereExpression,
230
+ "q/order-by": orderByExpression
231
+ } = subQueryExpression;
232
+ const subQueryTypeObject = visitFieldExpressionForReplaceNamesWithIdsVisitor({
233
+ expression: fromExpression,
234
+ onFieldNotFound,
235
+ typeObject
236
+ }).currentTypeObject;
237
+ if (subQueryTypeObject) {
238
+ const subQueryVisitor = replaceNamesWithIdsVisitor(subQueryTypeObject, onFieldNotFound);
239
+ return _extends({}, subQueryExpression, {
240
+ "q/from": visitor.visitFieldExpression(fromExpression),
241
+ "q/select": ___default["default"].isPlainObject(selectExpression) ? ___default["default"].mapValues(selectExpression, val => subQueryVisitor.visitExpression(val)) : subQueryVisitor.visitExpression(selectExpression)
242
+ }, whereExpression ? {
243
+ "q/where": subQueryVisitor.visitExpression(whereExpression)
244
+ } : null, orderByExpression ? {
245
+ "q/order-by": subQueryVisitor.visitOrderByExpression(orderByExpression)
246
+ } : null);
247
+ }
248
+ return subQueryExpression;
249
+ }
250
+ });
251
+ return visitor;
252
+ };
253
+ const deleteExpressionsWithNotFoundFieldsVisitor = typeObject => {
254
+ const visitor = createExpressionVisitor({
255
+ visitFunctionCallExpression: ([fnName, ...args]) => {
256
+ const argsNew = args.map(x => visitor.visitExpression(x)).filter(Boolean);
257
+ if (logicalOperators.has(fnName)) {
258
+ if (argsNew.length > 0) {
259
+ return argsNew.length === 1 ? argsNew[0] : [fnName, ...argsNew];
260
+ }
261
+ return null;
262
+ } else {
263
+ return argsNew.length === args.length ? [fnName, ...argsNew] : null;
264
+ }
265
+ },
266
+ visitFieldExpression: expression => {
267
+ const fieldTypeObject = expression.reduce((holderTypeObject, field) => holderTypeObject && holderTypeObject.fieldObjectsByName.hasOwnProperty(field) ? holderTypeObject.fieldObjectsByName[field].typeObject : null, typeObject);
268
+ return fieldTypeObject && expression;
269
+ },
270
+ visitOrderByExpression: orderByExpression => {
271
+ return orderByExpression.map(x => {
272
+ const [fieldExpression, orderDir] = x;
273
+ const fieldExpressionNew = visitor.visitExpression(fieldExpression);
274
+ return fieldExpressionNew && [fieldExpressionNew, orderDir];
275
+ }).filter(Boolean);
276
+ },
277
+ visitQueryExpression: subQueryExpression => {
278
+ const {
279
+ "q/from": fromExpression,
280
+ "q/select": selectExpression,
281
+ "q/where": whereExpression,
282
+ "q/order-by": orderByExpression
283
+ } = subQueryExpression;
284
+ const subQueryTypeObject = fromExpression.reduce((typeObject, field) => typeObject && typeObject.fieldObjectsByName.hasOwnProperty(field) ? typeObject.fieldObjectsByName[field].typeObject : null, typeObject);
285
+ if (subQueryTypeObject) {
286
+ const subQueryVisitor = deleteExpressionsWithNotFoundFieldsVisitor(subQueryTypeObject);
287
+ const subQueryExpressionNew = ___default["default"].pickBy(_extends({}, subQueryExpression, {
288
+ "q/from": visitor.visitFieldExpression(fromExpression),
289
+ "q/select": subQueryVisitor.visitExpression(selectExpression)
290
+ }, whereExpression ? {
291
+ "q/where": subQueryVisitor.visitExpression(whereExpression)
292
+ } : null, orderByExpression ? {
293
+ "q/order-by": subQueryVisitor.visitOrderByExpression(orderByExpression)
294
+ } : null));
295
+ const {
296
+ "q/select": selectExpressionNew
297
+ } = subQueryExpressionNew;
298
+ return selectExpressionNew ? subQueryExpressionNew : null;
299
+ } else {
300
+ return null;
301
+ }
302
+ }
303
+ });
304
+ return visitor;
305
+ };
306
+ const expressionContainsAggregation = expression => {
307
+ let result = false;
308
+ const visitor = createExpressionVisitor({
309
+ visitQueryExpression: queryExpression => {
310
+ const {
311
+ "q/select": selectExpression
312
+ } = queryExpression;
313
+ if (isCollectionFunctionExpression(selectExpression)) {
314
+ result = true;
315
+ }
316
+ },
317
+ visitFunctionCallExpression: (expression, visitorDefault) => {
318
+ if (firstLastFunctions.has(expression[0])) {
319
+ result = true;
320
+ } else {
321
+ visitorDefault.visitFunctionCallExpression(expression);
322
+ }
323
+ }
324
+ });
325
+ visitor.visitExpression(expression);
326
+ return result;
327
+ };
328
+ const defaultGetExpressionTypeOnFieldNotFound = () => {
329
+ return {
330
+ currentTypeObject: null
331
+ };
332
+ };
333
+ const getFieldAccessExpressionTypeObject = ({
334
+ expression,
335
+ typeObject,
336
+ onFieldNotFound,
337
+ returnRefTypeInsteadOfId
338
+ }) => {
339
+ const reduced = expression.reduce(({
340
+ currentTypeObject
341
+ }, fieldId, index) => {
342
+ const fieldObject = currentTypeObject && currentTypeObject.fieldObjects.find(f => f.id === fieldId);
343
+ if (fieldObject) {
344
+ if (returnRefTypeInsteadOfId && index === expression.length - 1 && fieldObject.isId) {
345
+ return {
346
+ currentTypeObject
347
+ };
348
+ }
349
+ return {
350
+ currentTypeObject: fieldObject.typeObject
351
+ };
352
+ } else {
353
+ return onFieldNotFound({
354
+ currentTypeObject,
355
+ fieldId
356
+ });
357
+ }
358
+ }, {
359
+ currentTypeObject: typeObject
360
+ });
361
+ return reduced.currentTypeObject;
362
+ };
363
+ const UNKNOWN_EXPRESSION_TYPE = "unknown";
364
+ const getExpressionTypeInternal = ({
365
+ expression,
366
+ typeObject,
367
+ functionsMeta,
368
+ onFieldNotFound,
369
+ returnRefTypeInsteadOfId
370
+ }) => {
371
+ let result = null;
372
+ const visitor = createExpressionVisitor({
373
+ visitVariableExpression: function () {
374
+ result = UNKNOWN_EXPRESSION_TYPE;
375
+ },
376
+ visitFunctionCallExpression: function ([fnName, ...args]) {
377
+ const fnMeta = functionsMeta[fnName];
378
+ if (!fnMeta) {
379
+ throw new Error(`Function meta for "${fnName}" was not provided`);
380
+ }
381
+ const argTypes = args.map(arg => getExpressionTypeInternal({
382
+ expression: arg,
383
+ typeObject,
384
+ functionsMeta,
385
+ onFieldNotFound,
386
+ returnRefTypeInsteadOfId: firstLastFunctions.has(fnName) ? returnRefTypeInsteadOfId : false
387
+ }));
388
+ if (firstLastFunctions.has(fnName)) {
389
+ //assuming q/first has one argument and result type equals arg type.
390
+ //we need this trick to support 'returnRefTypeInsteadOfId' behavior for q/first and q/last.
391
+ result = argTypes[0];
392
+ } else {
393
+ const overload = fnMeta.overloads.find(o => o["arg-types"].every((argType, index) => argTypes[index] === UNKNOWN_EXPRESSION_TYPE || argTypes[index] === argType));
394
+ if (!overload) {
395
+ throw new Error(`No overload with args ${argTypes.join(",")} found for "${fnName}" in meta`);
396
+ }
397
+ result = overload["result-type"];
398
+ }
399
+ },
400
+ visitQueryExpression: expression => {
401
+ const {
402
+ "q/from": fromExpression,
403
+ "q/select": selectExpression
404
+ } = expression;
405
+ const fromTypeObject = getFieldAccessExpressionTypeObject({
406
+ expression: fromExpression,
407
+ typeObject,
408
+ onFieldNotFound,
409
+ returnRefTypeInsteadOfId
410
+ });
411
+ if (!fromTypeObject) {
412
+ result = null;
413
+ }
414
+ if (___default["default"].isPlainObject(selectExpression)) {
415
+ if (Object.values(selectExpression).length !== 1) {
416
+ throw new Error(`Cannot determine type of query expression ${JSON.stringify(expression)}`);
417
+ }
418
+ result = getExpressionTypeInternal({
419
+ expression: Object.values(selectExpression)[0],
420
+ typeObject: fromTypeObject,
421
+ functionsMeta,
422
+ onFieldNotFound,
423
+ returnRefTypeInsteadOfId
424
+ });
425
+ } else {
426
+ result = getExpressionTypeInternal({
427
+ expression: selectExpression,
428
+ typeObject: fromTypeObject,
429
+ functionsMeta,
430
+ onFieldNotFound,
431
+ returnRefTypeInsteadOfId
432
+ });
433
+ }
434
+ },
435
+ visitFieldExpression(expression) {
436
+ const fieldAccessExpressionTypeObject = getFieldAccessExpressionTypeObject({
437
+ expression,
438
+ typeObject,
439
+ onFieldNotFound,
440
+ returnRefTypeInsteadOfId
441
+ });
442
+ result = fieldAccessExpressionTypeObject && fieldAccessExpressionTypeObject.name;
443
+ }
444
+ });
445
+ visitor.visitExpression(expression);
446
+ return result;
447
+ };
448
+ const getExpressionType = ({
449
+ expression,
450
+ typeObject,
451
+ functionsMeta,
452
+ onFieldNotFound = defaultGetExpressionTypeOnFieldNotFound,
453
+ returnRefTypeInsteadOfId = true
454
+ }) => {
455
+ return getExpressionTypeInternal({
456
+ expression,
457
+ typeObject,
458
+ functionsMeta,
459
+ onFieldNotFound,
460
+ returnRefTypeInsteadOfId
461
+ });
462
+ };
463
+
464
+ exports.UNKNOWN_EXPRESSION_TYPE = UNKNOWN_EXPRESSION_TYPE;
465
+ exports.deleteExpressionsWithNotFoundFieldsVisitor = deleteExpressionsWithNotFoundFieldsVisitor;
466
+ exports.expressionContainsAggregation = expressionContainsAggregation;
467
+ exports.getExpressionType = getExpressionType;
468
+ exports.replaceIdsWithNamesVisitor = replaceIdsWithNamesVisitor;
469
+ exports.replaceNamesWithIdsVisitor = replaceNamesWithIdsVisitor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fibery/expression-utils",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "utils for working with fibery api expressions",
5
5
  "exports": {
6
6
  ".": "./lib/expression-utils.js",