ts-flex-query
Version:
Flexible and type-safe data queries
368 lines • 20.3 kB
JavaScript
"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