UNPKG

ts-flex-query

Version:
368 lines 20.3 kB
"use strict"; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var _RequestBuilder_rootExpression; Object.defineProperty(exports, "__esModule", { value: true }); exports.RequestBuilder = void 0; const lodash_1 = require("lodash"); const field_1 = require("../../expressions/field"); const filter_1 = require("../../expressions/filter"); const function_application_1 = require("../../expressions/function-application"); const group_1 = require("../../expressions/group"); const let_1 = require("../../expressions/let"); const map_1 = require("../../expressions/map"); const record_1 = require("../../expressions/record"); const slice_1 = require("../../expressions/slice"); const sort_1 = require("../../expressions/sort"); const specify_type_1 = require("../../expressions/specify-type"); const variable_1 = require("../../expressions/variable"); const aggregation_1 = require("../../functions/aggregation"); const internal_1 = require("../../functions/internal"); const main_1 = require("../../functions/main"); const utils_1 = require("../../helpers/utils"); const operators_1 = require("../../operators"); const group_2 = require("../../operators/basic/group"); const types_1 = require("../../types"); const odata_root_expression_1 = require("../expressions/odata-root-expression"); const definitions_1 = require("../helpers/definitions"); const expression_serializer_1 = require("../serialization/expression-serializer"); class RequestBuilder { get rootExpression() { return __classPrivateFieldGet(this, _RequestBuilder_rootExpression, "f"); } constructor(params) { this.params = params; this.result = {}; _RequestBuilder_rootExpression.set(this, void 0); } buildWithPossibleIncludeCount(expression) { var _a, _b; // Detect "include count" pattern: if (expression instanceof let_1.LetExpression && expression.body instanceof record_1.RecordExpression && Object.keys(expression.body.fields).length === 2) { const entries = Object.entries(expression.body.fields); const countFieldIndex = entries.findIndex(([_, value]) => value instanceof function_application_1.FunctionApplicationExpression && value.container === aggregation_1.aggregation && value.member === (0, utils_1.nameOf)()('count')); if (countFieldIndex >= 0) { const elementsFieldIndex = 1 - countFieldIndex; const countedExpression = entries[countFieldIndex][1].args[0]; if (!(countedExpression instanceof variable_1.VariableExpression) || countedExpression.symbol !== expression.variableSymbol) { throw new Error(`Expected expression in count to refer to let variable, but was ${countedExpression.constructor.name}`); } this.build(entries[elementsFieldIndex][1]); (0, utils_1.assertIsDefined)(this.rootExpression, 'rootExpression is not defined.'); if (!(this.rootExpression instanceof variable_1.VariableExpression) || this.rootExpression.symbol !== expression.variableSymbol) { throw new Error(`Expected element selector to be based on Let variable, but was ${this.rootExpression.constructor.name}`); } if (((_a = this.result.apply) === null || _a === void 0 ? void 0 : _a.length) || this.result.count || this.result.filter || ((_b = this.result.orderBy) === null || _b === void 0 ? void 0 : _b.length)) { throw new Error('Only the following operations are permitted after count: select, expand, skip, top'); } this.result.count = true; this.build(expression.input); return { countFieldName: entries[countFieldIndex][0], elementsFieldName: entries[elementsFieldIndex][0] }; } } this.build(expression); return undefined; } /** Applies the given expression to the request. Throws an error if the expression is not OData-compatible. */ build(expression) { var _a, _b; let currentExpression = expression; while (!(0, odata_root_expression_1.isODataRootExpression)(currentExpression)) { if (currentExpression instanceof field_1.FieldExpression) { __classPrivateFieldSet(this, _RequestBuilder_rootExpression, currentExpression, "f"); return; } if (currentExpression instanceof filter_1.FilterExpression) { this.applyFilter(currentExpression); currentExpression = currentExpression.input; } else if (currentExpression instanceof let_1.LetExpression) { currentExpression = this.applyLet(currentExpression); } else if (currentExpression instanceof map_1.MapExpression) { currentExpression = this.applyMap(currentExpression); } else if (currentExpression instanceof slice_1.SliceExpression) { this.applySlice(currentExpression); currentExpression = currentExpression.input; } else if (currentExpression instanceof sort_1.SortExpression) { this.applySort(currentExpression); currentExpression = currentExpression.input; } else if (currentExpression instanceof specify_type_1.SpecifyTypeExpression) { currentExpression = currentExpression.input; } else { const customResult = (_b = (_a = this.params).expressionHandler) === null || _b === void 0 ? void 0 : _b.call(_a, { expression: currentExpression, currentRequest: this.result }); if (customResult) { currentExpression = customResult.innerExpression; this.result = customResult.newRequest; } else { throw new Error(`Unsupported expression: ${currentExpression.constructor.name}`); } } } __classPrivateFieldSet(this, _RequestBuilder_rootExpression, currentExpression, "f"); } applyFilter(expression) { var _a, _b; const value = expression_serializer_1.ExpressionSerializer.serializeExpression(expression.body, { [expression.variableSymbol]: null }); if (!value) { throw new Error('No filter value was provided.'); } const filter = { value }; if (this.result.filter || ((_a = this.result.apply) === null || _a === void 0 ? void 0 : _a.length)) { this.result.apply = [ Object.assign({ type: 'filter' }, filter), ...(_b = this.result.apply) !== null && _b !== void 0 ? _b : [] ]; } else { this.result.filter = filter; } } applyLet(expression) { this.build(expression.body); if (!(this.rootExpression instanceof variable_1.VariableExpression) || this.rootExpression.symbol !== expression.variableSymbol) { throw new Error('Root of a let expression body must be the let variable.'); } return expression.input; } applyMap(expression) { if (expression.input instanceof group_1.GroupExpression) { return this.applyGroup(expression.input, expression.body, expression.variableSymbol); } if (!(expression.body instanceof record_1.RecordExpression)) { throw new Error('MapExpression body must be a record.'); } const request = this.createRequestFromRecord(expression.body, expression.variableSymbol); Object.assign(this.result, request); return expression.input; } applyGroup(expression, mapBody, mapVariable) { return this.applyGroupForValue(expression, expression.groupValue, expression.variableSymbol, mapBody, mapVariable); } applyGroupForValue(expression, groupValue, groupVariableSymbol, mapBody, mapVariable) { var _a, _b; const unwrapLetIfDefinedResult = (0, operators_1.unwrapLetIfDefined)(groupValue); if (unwrapLetIfDefinedResult) { RequestBuilder.assertExpectedFieldChain(unwrapLetIfDefinedResult.baseExpression, groupVariableSymbol); return this.applyGroupForValue(expression, unwrapLetIfDefinedResult.body, unwrapLetIfDefinedResult.baseExpressionVariableSymbol, mapBody, mapVariable); } if (!(groupValue instanceof record_1.RecordExpression)) { throw new Error('Only records are allowed as group values.'); } if (mapBody instanceof field_1.FieldExpression && mapBody.field === expression.groupValueField) { RequestBuilder.assertExpectedFieldChain(mapBody.input, mapVariable); const apply = this.createODataApplyForGroupValue(groupValue, groupVariableSymbol); if (apply.fields.length) { this.result.apply = [ apply, ...(_a = this.result.apply) !== null && _a !== void 0 ? _a : [] ]; } } else if ((0, function_application_1.isFunctionApplication)(mapBody, internal_1.internal, 'mergeObjects')) { RequestBuilder.assertExpectedFieldChain(mapBody.args[0], mapVariable, group_2.GroupOperator.groupValueField); const mergeArgument = mapBody.args[1]; if (!(mergeArgument instanceof record_1.RecordExpression)) { throw new Error('GroupExpression is mapped to a mergeObjects application with unsupported arguments. Use the groupAndAggregate operator to support OData.'); } const groupBy = this.createODataApplyForGroupValue(groupValue, groupVariableSymbol); const aggregate = RequestBuilder.createODataApplyForAggregate(mergeArgument, mapVariable); let consolidatedApply; if (groupBy.fields.length) { groupBy.groupApply = [aggregate]; consolidatedApply = groupBy; } else if (aggregate.elements.length) { consolidatedApply = aggregate; } if (consolidatedApply) { this.result.apply = [ consolidatedApply, ...(_b = this.result.apply) !== null && _b !== void 0 ? _b : [] ]; } } else { throw new Error(`GroupExpression is mapped to unsupported expression ${mapBody.constructor.name}.`); } return expression.input; } createODataApplyForGroupValue(groupValue, groupVariable) { const recordRequest = this.createRequestFromRecord(groupValue, groupVariable); return { type: 'groupby', fields: RequestBuilder.selectAndExpandRequestToFieldArray(recordRequest, []), groupApply: [] }; } static createODataApplyForAggregate(record, groupMapVariable) { const elements = Object.entries(record.fields) .map(([name, value]) => { (0, utils_1.assertIsDefined)(value, 'value is not defined.'); if (!(value instanceof function_application_1.FunctionApplicationExpression)) { throw new Error(`Aggregation values must be built using a FunctionApplicationExpression, but was ${value.constructor.name}`); } const containerName = (0, main_1.getFunctionContainerName)(value.container); if (!containerName) { throw new Error(`Unsupported aggregation function container: ${containerName}`); } const dataSetAggregationFunction = definitions_1.oDataDataSetAggregationFunctions[containerName][value.member]; if (dataSetAggregationFunction) { RequestBuilder.assertExpectedFieldChain(value.args[0], groupMapVariable, group_2.GroupOperator.elementsField); return { name, aggregationFunction: dataSetAggregationFunction, field: null }; } const aggregationFunction = definitions_1.oDataFieldAggregationFunctions[containerName][value.member]; if (!aggregationFunction) { throw new Error(`Unsupported aggregation function: ${containerName}.${value.member}`); } const aggregationFunctionArg = value.args[0]; if (!(aggregationFunctionArg instanceof map_1.MapExpression)) { throw new Error(`Expected a MapExpression as argument for aggregation function ${containerName}.${value.member}.`); } RequestBuilder.assertExpectedFieldChain(aggregationFunctionArg.input, groupMapVariable, group_2.GroupOperator.elementsField); const { chain, terminatingExpression } = RequestBuilder.getFieldChain(aggregationFunctionArg.body); if (!(terminatingExpression instanceof variable_1.VariableExpression) || terminatingExpression.symbol !== aggregationFunctionArg.variableSymbol) { throw new Error(`Unsupported terminating expression for map in aggregation function argument: ${terminatingExpression.constructor.name}`); } return { name, aggregationFunction, field: chain.join('/') }; }); return { type: 'aggregate', elements }; } static selectAndExpandRequestToFieldArray(request, baseChain) { return [ ...request.select.map((field) => [...baseChain, field].join('/')), ...(0, lodash_1.flatten)(Object.entries(request.expand) .map(([field, subRequest]) => RequestBuilder.selectAndExpandRequestToFieldArray(Object.assign({ select: [], expand: {} }, subRequest), [...baseChain, field]))) ]; } applySlice(expression) { if (this.result.skip) { this.result.skip += expression.skip; } else { this.result.skip = expression.skip; } this.result.top = expression.take; } applySort(expression) { const oDataSpecs = expression.specs.map((spec) => { const serializedValue = expression_serializer_1.ExpressionSerializer.serializeExpression(spec.value, { [expression.variableSymbol]: null }); if (serializedValue === null) { throw new Error('No sort value was provided.'); } return { field: serializedValue, mode: spec.isAscending ? 'asc' : 'desc' }; }); this.result.orderBy = oDataSpecs; } createRequestFromRecord(record, baseObjectVariableSymbol, ...expectedFieldChain) { const result = { select: [], expand: {} }; Object.entries(record.fields).forEach(([field, subExpression]) => { if (!subExpression) { return; } const fieldChain = [...expectedFieldChain, field]; const fieldResult = this.createFieldRequestFromExpression(subExpression, baseObjectVariableSymbol, ...fieldChain); if (fieldResult === 'select') { result.select.push(field); } else { result.expand[field] = fieldResult === 'expand' ? null : fieldResult; } }); return result; } createFieldRequestFromExpression(expression, baseObjectVariableSymbol, ...expectedFieldChain) { var _a; const underlyingExpression = RequestBuilder.getUnderlyingExpression(expression); if (underlyingExpression instanceof field_1.FieldExpression || underlyingExpression instanceof variable_1.VariableExpression) { RequestBuilder.assertExpectedFieldChain(underlyingExpression, baseObjectVariableSymbol, ...expectedFieldChain); return (0, types_1.isExpansionDataType)(expression.dataType) && ((_a = expression.dataType.isExpandable) !== null && _a !== void 0 ? _a : true) ? 'expand' : 'select'; } const unwrapLetIfDefinedResult = (0, operators_1.unwrapLetIfDefined)(underlyingExpression); if (unwrapLetIfDefinedResult) { RequestBuilder.assertExpectedFieldChain(unwrapLetIfDefinedResult.baseExpression, baseObjectVariableSymbol, ...expectedFieldChain); return this.createFieldRequestFromExpression(unwrapLetIfDefinedResult.body, unwrapLetIfDefinedResult.baseExpressionVariableSymbol); } if (underlyingExpression instanceof let_1.LetExpression) { return RequestBuilder.applyLet(underlyingExpression, baseObjectVariableSymbol, expectedFieldChain, this.createFieldRequestFromExpression.bind(this)); } if (underlyingExpression instanceof record_1.RecordExpression) { return this.createRequestFromRecord(underlyingExpression, baseObjectVariableSymbol, ...expectedFieldChain); } const fieldRequestBuilder = new RequestBuilder(this.params); fieldRequestBuilder.build(underlyingExpression); if (!fieldRequestBuilder.rootExpression) { throw new Error(`Root expression for ${underlyingExpression.constructor.name} was expected.`); } RequestBuilder.assertExpectedFieldChain(fieldRequestBuilder.rootExpression, baseObjectVariableSymbol, ...expectedFieldChain); return fieldRequestBuilder.result; } static assertExpectedFieldChain(expression, baseObjectVariableSymbol, ...expectedFieldChain) { const { chain, terminatingExpression } = RequestBuilder.getFieldChain(expression); if (!(0, lodash_1.isEqual)(chain, expectedFieldChain)) { throw new Error(`Expected access to field chain ${expectedFieldChain.join('.')}, but was ${chain.join('.')}`); } if (!(terminatingExpression instanceof variable_1.VariableExpression) || terminatingExpression.symbol !== baseObjectVariableSymbol) { throw new Error(`Expected access to base object variable ${baseObjectVariableSymbol.toString()}, but was ${terminatingExpression.constructor.name}.`); } } static getFieldChain(expression) { const chain = []; let expr; for (expr = expression; expr instanceof field_1.FieldExpression; expr = expr.input) { chain.push(expr.field); } chain.reverse(); return { chain, terminatingExpression: expr }; } static getUnderlyingExpression(expression) { return (expression instanceof specify_type_1.SpecifyTypeExpression ? RequestBuilder.getUnderlyingExpression(expression.input) : expression); } static applyLet(expression, baseObjectVariableSymbol, currentExpectedFieldChain, continuation) { RequestBuilder.assertExpectedFieldChain(expression.input, baseObjectVariableSymbol, ...currentExpectedFieldChain); return continuation(expression.body, expression.variableSymbol); } } exports.RequestBuilder = RequestBuilder; _RequestBuilder_rootExpression = new WeakMap(); //# sourceMappingURL=request-builder.js.map