@fibery/expression-utils 9.2.1 → 9.4.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.
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fieldAccessVisitorTypeAware = exports.getExpressionType = exports.UNKNOWN_EXPRESSION_TYPE = exports.expressionContainsAggregation = exports.deleteExpressionsWithNotFoundFieldsVisitor = exports.replaceNamesWithIdsVisitor = exports.replaceIdsWithNamesVisitor = void 0;
7
+ /* eslint-disable max-lines */
8
+ const lodash_1 = __importDefault(require("lodash"));
9
+ const fieldExpressionVisitorTypeAware_1 = require("./fieldExpressionVisitorTypeAware");
10
+ Object.defineProperty(exports, "fieldAccessVisitorTypeAware", { enumerable: true, get: function () { return fieldExpressionVisitorTypeAware_1.fieldAccessVisitorTypeAware; } });
11
+ const utils_1 = require("./utils");
12
+ const defaultIdsWithNamesOnFieldNotFound = ({ fieldExpressionInNamesTerms, fieldId, }) => {
13
+ return {
14
+ currentTypeObject: null,
15
+ fieldExpressionInNamesTerms: [...fieldExpressionInNamesTerms, fieldId],
16
+ };
17
+ };
18
+ const visitFieldExpressionForReplaceIdsWithNamesVisitor = ({ expression, typeObject, onFieldNotFound, }) => expression.reduce(({ currentTypeObject, fieldExpressionInNamesTerms }, fieldId) => {
19
+ if (currentTypeObject &&
20
+ (0, utils_1.isMultiFieldAccess)(fieldId) &&
21
+ Object.hasOwn(currentTypeObject.fieldObjectsById, fieldId[0])) {
22
+ const typeId = fieldId[1];
23
+ const fieldObject = currentTypeObject.fieldObjectsById[fieldId[0]];
24
+ const nextTypeObject = fieldObject.multiRelatedFieldObjects.find((f) => f.holderTypeObject.id === typeId)?.holderTypeObject;
25
+ if (!nextTypeObject) {
26
+ return onFieldNotFound({
27
+ currentTypeObject,
28
+ fieldExpressionInNamesTerms,
29
+ fieldId: fieldId,
30
+ expression,
31
+ });
32
+ }
33
+ return {
34
+ currentTypeObject: nextTypeObject,
35
+ fieldExpressionInNamesTerms: [
36
+ ...fieldExpressionInNamesTerms,
37
+ [fieldObject.name, nextTypeObject.name],
38
+ ],
39
+ };
40
+ }
41
+ else if (currentTypeObject && Object.hasOwn(currentTypeObject.fieldObjectsById, fieldId)) {
42
+ const fieldObject = currentTypeObject.fieldObjectsById[fieldId];
43
+ return {
44
+ currentTypeObject: fieldObject.typeObject,
45
+ fieldExpressionInNamesTerms: [...fieldExpressionInNamesTerms, fieldObject.name],
46
+ };
47
+ }
48
+ else {
49
+ return onFieldNotFound({
50
+ currentTypeObject,
51
+ fieldExpressionInNamesTerms,
52
+ fieldId: fieldId,
53
+ expression,
54
+ });
55
+ }
56
+ }, {
57
+ currentTypeObject: typeObject,
58
+ fieldExpressionInNamesTerms: [],
59
+ });
60
+ const fieldExpressionVisitor = (typeObject, visitFieldExpression, replacedExpressionKey = "replacedExpression") => {
61
+ const visitor = (0, utils_1.createExpressionVisitor)({
62
+ visitFieldExpression: (expression) => visitFieldExpression(typeObject, expression)[replacedExpressionKey],
63
+ visitQueryExpression: (subQueryExpression) => {
64
+ const { "q/from": fromExpression, "q/select": selectExpression, "q/where": whereExpression, "q/order-by": orderByExpression, } = subQueryExpression;
65
+ const subQueryTypeObject = visitFieldExpression(typeObject, fromExpression).currentTypeObject;
66
+ if (subQueryTypeObject) {
67
+ const subQueryVisitor = fieldExpressionVisitor(subQueryTypeObject, visitFieldExpression, replacedExpressionKey);
68
+ return {
69
+ ...subQueryExpression,
70
+ ...{
71
+ "q/from": visitor.visitFieldExpression(fromExpression),
72
+ // "q/select" can be in vector shape, it's not reflected in types right now.
73
+ "q/select": lodash_1.default.isPlainObject(selectExpression)
74
+ ? lodash_1.default.mapValues(selectExpression, (val) => subQueryVisitor.visitExpression(val))
75
+ : subQueryVisitor.visitExpression(selectExpression),
76
+ },
77
+ ...(whereExpression ? { "q/where": subQueryVisitor.visitExpression(whereExpression) } : null),
78
+ ...(orderByExpression
79
+ ? {
80
+ "q/order-by": subQueryVisitor.visitOrderByExpression(orderByExpression),
81
+ }
82
+ : null),
83
+ };
84
+ }
85
+ return subQueryExpression;
86
+ },
87
+ });
88
+ return visitor;
89
+ };
90
+ const replaceIdsWithNamesVisitor = (typeObject, onFieldNotFound = defaultIdsWithNamesOnFieldNotFound) => {
91
+ return fieldExpressionVisitor(typeObject, (typeObject, expression) => visitFieldExpressionForReplaceIdsWithNamesVisitor({
92
+ expression,
93
+ typeObject,
94
+ onFieldNotFound,
95
+ }), "fieldExpressionInNamesTerms");
96
+ };
97
+ exports.replaceIdsWithNamesVisitor = replaceIdsWithNamesVisitor;
98
+ const defaultNamesWithIdsOnFieldNotFound = ({ fieldExpressionInIdsTerms, field, }) => {
99
+ return {
100
+ currentTypeObject: null,
101
+ fieldExpressionInIdsTerms: [...fieldExpressionInIdsTerms, field],
102
+ };
103
+ };
104
+ const visitFieldExpressionForReplaceNamesWithIdsVisitor = ({ expression, typeObject, onFieldNotFound, }) => expression.reduce(({ currentTypeObject, fieldExpressionInIdsTerms }, field) => {
105
+ if (currentTypeObject &&
106
+ (0, utils_1.isMultiFieldAccess)(field) &&
107
+ Object.hasOwn(currentTypeObject.fieldObjectsByName, field[0])) {
108
+ const type = field[1];
109
+ const fieldObject = currentTypeObject.fieldObjectsByName[field[0]];
110
+ const nextTypeObject = fieldObject.multiRelatedFieldObjects.find((f) => f.holderType === type)?.holderTypeObject;
111
+ if (!nextTypeObject) {
112
+ return onFieldNotFound({
113
+ currentTypeObject,
114
+ fieldExpressionInIdsTerms,
115
+ field: field,
116
+ expression,
117
+ });
118
+ }
119
+ return {
120
+ currentTypeObject: nextTypeObject,
121
+ fieldExpressionInIdsTerms: [...fieldExpressionInIdsTerms, [fieldObject.id, nextTypeObject.id]],
122
+ };
123
+ }
124
+ else if (currentTypeObject && Object.hasOwn(currentTypeObject.fieldObjectsByName, field)) {
125
+ const fieldObject = currentTypeObject.fieldObjectsByName[field];
126
+ return {
127
+ currentTypeObject: fieldObject.typeObject,
128
+ fieldExpressionInIdsTerms: [...fieldExpressionInIdsTerms, fieldObject.id],
129
+ };
130
+ }
131
+ else {
132
+ return onFieldNotFound({ currentTypeObject, fieldExpressionInIdsTerms, field: field, expression });
133
+ }
134
+ }, {
135
+ currentTypeObject: typeObject,
136
+ fieldExpressionInIdsTerms: [],
137
+ });
138
+ const replaceNamesWithIdsVisitor = (typeObject, onFieldNotFound = defaultNamesWithIdsOnFieldNotFound) => {
139
+ return fieldExpressionVisitor(typeObject, (typeObject, expression) => visitFieldExpressionForReplaceNamesWithIdsVisitor({
140
+ expression,
141
+ typeObject,
142
+ onFieldNotFound,
143
+ }), "fieldExpressionInIdsTerms");
144
+ };
145
+ exports.replaceNamesWithIdsVisitor = replaceNamesWithIdsVisitor;
146
+ const deleteExpressionsWithNotFoundFieldsVisitor = (typeObject) => {
147
+ const visitor = (0, utils_1.createExpressionVisitor)({
148
+ visitFunctionCallExpression: ([fnName, ...args]) => {
149
+ const argsNew = args.map((x) => visitor.visitExpression(x)).filter(Boolean);
150
+ if (utils_1.logicalOperators.has(fnName)) {
151
+ if (argsNew.length > 0) {
152
+ return argsNew.length === 1 ? argsNew[0] : [fnName, ...argsNew];
153
+ }
154
+ return null;
155
+ }
156
+ else {
157
+ return argsNew.length === args.length ? [fnName, ...argsNew] : null;
158
+ }
159
+ },
160
+ visitFieldExpression: (expression) => {
161
+ const fieldTypeObject = expression.reduce((holderTypeObject, field) => {
162
+ if (holderTypeObject &&
163
+ (0, utils_1.isMultiFieldAccess)(field) &&
164
+ Object.hasOwn(holderTypeObject.fieldObjectsByName, field[0])) {
165
+ const type = field[1];
166
+ const fieldObject = holderTypeObject.fieldObjectsByName[field[0]];
167
+ const nextTypeObject = fieldObject.multiRelatedFieldObjects.find((f) => f.holderType === type)?.holderTypeObject;
168
+ if (!nextTypeObject) {
169
+ return null;
170
+ }
171
+ return nextTypeObject;
172
+ }
173
+ else if (holderTypeObject && Object.hasOwn(holderTypeObject.fieldObjectsByName, field)) {
174
+ return holderTypeObject.fieldObjectsByName[field].typeObject;
175
+ }
176
+ else {
177
+ return null;
178
+ }
179
+ }, typeObject);
180
+ return fieldTypeObject && expression;
181
+ },
182
+ visitOrderByExpression: (orderByExpression) => {
183
+ return orderByExpression
184
+ .map((x) => {
185
+ const [fieldExpression, orderDir] = x;
186
+ const fieldExpressionNew = visitor.visitExpression(fieldExpression);
187
+ return fieldExpressionNew && [fieldExpressionNew, orderDir];
188
+ })
189
+ .filter(Boolean);
190
+ },
191
+ visitQueryExpression: (subQueryExpression) => {
192
+ const { "q/from": fromExpression, "q/select": selectExpression, "q/where": whereExpression, "q/order-by": orderByExpression, } = subQueryExpression;
193
+ const subQueryTypeObject = fromExpression.reduce((typeObject, field) => typeObject && Object.hasOwn(typeObject.fieldObjectsByName, field)
194
+ ? typeObject.fieldObjectsByName[field].typeObject
195
+ : null, typeObject);
196
+ if (subQueryTypeObject) {
197
+ const subQueryVisitor = (0, exports.deleteExpressionsWithNotFoundFieldsVisitor)(subQueryTypeObject);
198
+ const subQueryExpressionNew = lodash_1.default.pickBy({
199
+ ...subQueryExpression,
200
+ ...{
201
+ "q/from": visitor.visitFieldExpression(fromExpression),
202
+ "q/select": subQueryVisitor.visitExpression(selectExpression),
203
+ },
204
+ ...(whereExpression ? { "q/where": subQueryVisitor.visitExpression(whereExpression) } : null),
205
+ ...(orderByExpression
206
+ ? {
207
+ "q/order-by": subQueryVisitor.visitOrderByExpression(orderByExpression),
208
+ }
209
+ : null),
210
+ });
211
+ const { "q/select": selectExpressionNew } = subQueryExpressionNew;
212
+ return selectExpressionNew ? subQueryExpressionNew : null;
213
+ }
214
+ else {
215
+ return null;
216
+ }
217
+ },
218
+ });
219
+ return visitor;
220
+ };
221
+ exports.deleteExpressionsWithNotFoundFieldsVisitor = deleteExpressionsWithNotFoundFieldsVisitor;
222
+ const expressionContainsAggregation = (expression) => {
223
+ let result = false;
224
+ const visitor = (0, utils_1.createExpressionVisitor)({
225
+ visitQueryExpression: (queryExpression) => {
226
+ const { "q/select": selectExpression } = queryExpression;
227
+ //"select" can have a non-object form, which is not reflected in types right now.
228
+ if ((0, utils_1.isCollectionFunctionExpression)(selectExpression)) {
229
+ result = true;
230
+ }
231
+ },
232
+ visitFunctionCallExpression: (expression, visitorDefault) => {
233
+ if (utils_1.firstLastFunctions.has(expression[0])) {
234
+ result = true;
235
+ }
236
+ else {
237
+ visitorDefault.visitFunctionCallExpression(expression);
238
+ }
239
+ },
240
+ });
241
+ visitor.visitExpression(expression);
242
+ return result;
243
+ };
244
+ exports.expressionContainsAggregation = expressionContainsAggregation;
245
+ const defaultGetExpressionTypeOnFieldNotFound = () => {
246
+ return { currentTypeObject: null };
247
+ };
248
+ const getFieldAccessExpressionTypeObject = ({ expression, typeObject, onFieldNotFound, returnRefTypeInsteadOfId, }) => {
249
+ const reduced = expression.reduce(({ currentTypeObject }, fieldId, index) => {
250
+ if (currentTypeObject &&
251
+ (0, utils_1.isMultiFieldAccess)(fieldId) &&
252
+ Object.hasOwn(currentTypeObject.fieldObjectsById, fieldId[0])) {
253
+ const typeId = fieldId[1];
254
+ const fieldObject = currentTypeObject.fieldObjectsById[fieldId[0]];
255
+ const nextTypeObject = fieldObject.multiRelatedFieldObjects.find((f) => f.holderTypeObject.id === typeId)?.holderTypeObject;
256
+ if (!nextTypeObject) {
257
+ return onFieldNotFound({ currentTypeObject, fieldId: fieldId });
258
+ }
259
+ return {
260
+ currentTypeObject: nextTypeObject,
261
+ };
262
+ }
263
+ else if (currentTypeObject && Object.hasOwn(currentTypeObject.fieldObjectsById, fieldId)) {
264
+ const fieldObject = currentTypeObject.fieldObjectsById[fieldId];
265
+ if (returnRefTypeInsteadOfId && index === expression.length - 1 && fieldObject.isId) {
266
+ return { currentTypeObject };
267
+ }
268
+ return {
269
+ currentTypeObject: fieldObject.typeObject,
270
+ };
271
+ }
272
+ else {
273
+ return onFieldNotFound({ currentTypeObject, fieldId: fieldId });
274
+ }
275
+ }, { currentTypeObject: typeObject });
276
+ return reduced.currentTypeObject;
277
+ };
278
+ exports.UNKNOWN_EXPRESSION_TYPE = "unknown";
279
+ const getExpressionTypeInternal = ({ expression, typeObject, functionsMeta, onFieldNotFound, returnRefTypeInsteadOfId, }) => {
280
+ let result = null;
281
+ const visitor = (0, utils_1.createExpressionVisitor)({
282
+ visitVariableExpression: function () {
283
+ result = exports.UNKNOWN_EXPRESSION_TYPE;
284
+ },
285
+ visitFunctionCallExpression: function ([fnName, ...args]) {
286
+ const fnMeta = functionsMeta[fnName];
287
+ if (!fnMeta) {
288
+ throw new Error(`Function meta for "${fnName}" was not provided`);
289
+ }
290
+ const argTypes = args.map((arg) => getExpressionTypeInternal({
291
+ expression: arg,
292
+ typeObject,
293
+ functionsMeta,
294
+ onFieldNotFound,
295
+ returnRefTypeInsteadOfId: utils_1.firstLastFunctions.has(fnName) || fnName === "q/if" || fnName === "q/if-null"
296
+ ? returnRefTypeInsteadOfId
297
+ : false,
298
+ }));
299
+ if (utils_1.firstLastFunctions.has(fnName)) {
300
+ //assuming q/first has one argument and result type equals arg type.
301
+ //we need this trick to support 'returnRefTypeInsteadOfId' behavior for q/first and q/last.
302
+ result = argTypes[0];
303
+ }
304
+ else if (fnName === "q/if") {
305
+ //we need this trick to support 'returnRefTypeInsteadOfId' behavior for q/if.
306
+ result = argTypes[1];
307
+ }
308
+ else if (fnName === "q/if-null" &&
309
+ argTypes.some((x) => {
310
+ // We want to check that the type is not primitive here. Should be improved!
311
+ return !x?.startsWith("fibery");
312
+ })) {
313
+ //we need this trick to support 'returnRefTypeInsteadOfId' behavior for q/if.
314
+ result = argTypes[0];
315
+ }
316
+ else {
317
+ const overload = fnMeta.overloads.find((o) => o["arg-types"].every((argType, index) => argTypes[index] === exports.UNKNOWN_EXPRESSION_TYPE ||
318
+ argTypes[index] === argType ||
319
+ (["fibery/email", "fibery/url", "fibery/emoji", "fibery/color"].includes(argTypes[index]) &&
320
+ argType === "fibery/text")));
321
+ if (!overload) {
322
+ throw new Error(`No overload with args ${argTypes.join(",")} found for "${fnName}" in meta`);
323
+ }
324
+ result = overload["result-type"];
325
+ }
326
+ },
327
+ visitQueryExpression: (expression) => {
328
+ const { "q/from": fromExpression, "q/select": selectExpression } = expression;
329
+ const fromTypeObject = getFieldAccessExpressionTypeObject({
330
+ expression: fromExpression,
331
+ typeObject,
332
+ onFieldNotFound,
333
+ returnRefTypeInsteadOfId,
334
+ });
335
+ if (!fromTypeObject) {
336
+ result = null;
337
+ }
338
+ if (lodash_1.default.isPlainObject(selectExpression)) {
339
+ if (Object.values(selectExpression).length !== 1) {
340
+ throw new Error(`Can't determine type of query expression ${JSON.stringify(expression)}`);
341
+ }
342
+ result = getExpressionTypeInternal({
343
+ expression: Object.values(selectExpression)[0],
344
+ typeObject: fromTypeObject,
345
+ functionsMeta,
346
+ onFieldNotFound,
347
+ returnRefTypeInsteadOfId,
348
+ });
349
+ }
350
+ else {
351
+ result = getExpressionTypeInternal({
352
+ expression: selectExpression,
353
+ typeObject: fromTypeObject,
354
+ functionsMeta,
355
+ onFieldNotFound,
356
+ returnRefTypeInsteadOfId,
357
+ });
358
+ }
359
+ },
360
+ visitFieldExpression(expression) {
361
+ const fieldAccessExpressionTypeObject = getFieldAccessExpressionTypeObject({
362
+ expression,
363
+ typeObject,
364
+ onFieldNotFound,
365
+ returnRefTypeInsteadOfId,
366
+ });
367
+ result = fieldAccessExpressionTypeObject && fieldAccessExpressionTypeObject.name;
368
+ },
369
+ });
370
+ visitor.visitExpression(expression);
371
+ return result;
372
+ };
373
+ const getExpressionType = ({ expression, typeObject, functionsMeta, onFieldNotFound = defaultGetExpressionTypeOnFieldNotFound, returnRefTypeInsteadOfId = true, }) => {
374
+ return getExpressionTypeInternal({
375
+ expression,
376
+ typeObject,
377
+ functionsMeta,
378
+ onFieldNotFound,
379
+ returnRefTypeInsteadOfId,
380
+ });
381
+ };
382
+ exports.getExpressionType = getExpressionType;