angular-odata
Version:
Client side OData typescript library for Angular
1,262 lines (1,254 loc) • 511 kB
JavaScript
import * as i1 from '@angular/common/http';
import { HttpHeaders, HttpParams, HttpResponse, HttpErrorResponse, HttpEventType } from '@angular/common/http';
import { Subject, map as map$1, EMPTY, throwError, Observable, forkJoin, of, NEVER } from 'rxjs';
import { map, expand, reduce, finalize, defaultIfEmpty, switchMap, catchError, tap, startWith } from 'rxjs/operators';
import * as i0 from '@angular/core';
import { EventEmitter, Injectable, InjectionToken, makeEnvironmentProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
var PathSegment;
(function (PathSegment) {
PathSegment["batch"] = "batch";
PathSegment["metadata"] = "metadata";
PathSegment["entitySet"] = "entitySet";
PathSegment["singleton"] = "singleton";
PathSegment["type"] = "type";
PathSegment["property"] = "property";
PathSegment["navigationProperty"] = "navigationProperty";
PathSegment["reference"] = "reference";
PathSegment["value"] = "value";
PathSegment["count"] = "count";
PathSegment["function"] = "function";
PathSegment["action"] = "action";
})(PathSegment || (PathSegment = {}));
var QueryOption;
(function (QueryOption) {
QueryOption["select"] = "select";
QueryOption["expand"] = "expand";
QueryOption["compute"] = "compute";
QueryOption["apply"] = "apply";
QueryOption["filter"] = "filter";
QueryOption["search"] = "search";
QueryOption["transform"] = "transform";
QueryOption["orderBy"] = "orderBy";
QueryOption["top"] = "top";
QueryOption["skip"] = "skip";
QueryOption["skiptoken"] = "skiptoken";
QueryOption["format"] = "format";
QueryOption["levels"] = "levels";
QueryOption["count"] = "count";
})(QueryOption || (QueryOption = {}));
var EdmType;
(function (EdmType) {
//Edm.Guid 16-byte (128-bit) unique identifier
EdmType["Guid"] = "Edm.Guid";
//Edm.Int16 Signed 16-bit integer
EdmType["Int16"] = "Edm.Int16";
//Edm.String Sequence of UTF-8 characters
EdmType["String"] = "Edm.String";
//Edm.Boolean Binary-valued logic
EdmType["Boolean"] = "Edm.Boolean";
//Edm.Byte Unsigned 8-bit integer
EdmType["Byte"] = "Edm.Byte";
//Edm.SByte Signed 8-bit integer
EdmType["SByte"] = "Edm.SByte";
//Edm.Int32 Signed 16-bit integer
EdmType["Int32"] = "Edm.Int32";
//Edm.Int64 Signed 16-bit integer
EdmType["Int64"] = "Edm.Int64";
//Edm.Date Date without a time-zone offset
EdmType["Date"] = "Edm.Date";
//Edm.TimeOfDay Clock time 00:00-23:59:59.999999999999
EdmType["TimeOfDay"] = "Edm.TimeOfDay";
//Edm.DateTimeOffset Date and time with a time-zone offset, no leap seconds
EdmType["DateTimeOffset"] = "Edm.DateTimeOffset";
//Edm.Duration Signed duration in days, hours, minutes, and (sub)seconds
EdmType["Duration"] = "Edm.Duration";
//Edm.Decimal Numeric values with fixed precision and scale
EdmType["Decimal"] = "Edm.Decimal";
//Edm.Double IEEE 754 binary64 floating-point number (15-17 decimal digits)
EdmType["Double"] = "Edm.Double";
//Edm.Single IEEE 754 binary32 floating-point number (6-9 decimal digits)
EdmType["Single"] = "Edm.Single";
//Edm.Binary Binary data
EdmType["Binary"] = "Edm.Binary";
//Edm.Stream Binary data stream
EdmType["Stream"] = "Edm.Stream";
//Edm.Geography Abstract base type for all Geography types
EdmType["Geography"] = "Edm.Geography";
//Edm.GeographyPoint A point in a round-earth coordinate system
EdmType["GeographyPoint"] = "Edm.GeographyPoint";
//Edm.GeographyLineString Line string in a round-earth coordinate system
EdmType["GeographyLineString"] = "Edm.GeographyLineString";
//Edm.GeographyPolygon Polygon in a round-earth coordinate system
EdmType["GeographyPolygon"] = "Edm.GeographyPolygon";
//Edm.GeographyMultiPoint Collection of points in a round-earth coordinate system
EdmType["GeographyMultiPoint"] = "Edm.GeographyMultiPoint";
//Edm.GeographyMultiLineString Collection of line strings in a round-earth coordinate system
EdmType["GeographyMultiLineString"] = "Edm.GeographyMultiLineString";
//Edm.GeographyMultiPolygon Collection of polygons in a round-earth coordinate system
EdmType["GeographyMultiPolygon"] = "Edm.GeographyMultiPolygon";
//Edm.GeographyCollection Collection of arbitrary Geography values
EdmType["GeographyCollection"] = "Edm.GeographyCollection";
//Edm.Geometry Abstract base type for all Geometry types
EdmType["Geometry"] = "Edm.Geometry";
//Edm.GeometryPoint Point in a flat-earth coordinate system
EdmType["GeometryPoint"] = "Edm.GeometryPoint";
//Edm.GeometryLineString Line string in a flat-earth coordinate system
EdmType["GeometryLineString"] = "Edm.GeometryLineString";
//Edm.GeometryPolygon Polygon in a flat-earth coordinate system
EdmType["GeometryPolygon"] = "Edm.GeometryPolygon";
//Edm.GeometryMultiPoint Collection of points in a flat-earth coordinate system
EdmType["GeometryMultiPoint"] = "Edm.GeometryMultiPoint";
//Edm.GeometryMultiLineString Collection of line strings in a flat-earth coordinate system
EdmType["GeometryMultiLineString"] = "Edm.GeometryMultiLineString";
//Edm.GeometryMultiPolygon Collection of polygons in a flat-earth coordinate system
EdmType["GeometryMultiPolygon"] = "Edm.GeometryMultiPolygon";
//Edm.GeometryCollection Collection of arbitrary Geometry values
EdmType["GeometryCollection"] = "Edm.GeometryCollection";
})(EdmType || (EdmType = {}));
var JsonType;
(function (JsonType) {
JsonType["string"] = "string";
JsonType["number"] = "number";
JsonType["integer"] = "integer";
JsonType["object"] = "object";
JsonType["array"] = "array";
JsonType["boolean"] = "boolean";
JsonType["null"] = "null";
})(JsonType || (JsonType = {}));
const NONE_PARSER = {
deserialize: (value) => value,
serialize: (value) => value,
encode: (value) => value,
};
//#endregion
const $ID = '$id';
const ODATA_ID = '@odata.id';
// SEGMENTS
const $METADATA = '$metadata';
const $BATCH = '$batch';
const $REF = '$ref';
const $VALUE = '$value';
const $COUNT = '$count';
const $QUERY = '$query';
const $INLINECOUNT = '$inlinecount';
// HTTP HEADERS
const IF_MATCH_HEADER = 'If-Match';
const IF_NONE_MATCH_HEADER = 'If-None-Match';
const CONTENT_TYPE = 'Content-Type';
const HTTP11 = 'HTTP/1.1';
const ACCEPT = 'Accept';
const PREFER = 'Prefer';
const CACHE_CONTROL = 'Cache-Control';
const CACHE_CONTROL_HEADERS = [CACHE_CONTROL, CACHE_CONTROL.toLowerCase()];
const ODATA_VERSION = 'OData-Version';
const ODATA_VERSION_HEADERS = [
ODATA_VERSION,
ODATA_VERSION.toLowerCase(),
'dataserviceversion',
];
const LOCATION_HEADER = 'Location';
const LOCATION_HEADERS = [LOCATION_HEADER, LOCATION_HEADER.toLowerCase()];
const ODATA_ENTITYID = 'OData-EntityId';
const ODATA_ENTITYID_HEADERS = [ODATA_ENTITYID, ODATA_ENTITYID.toLowerCase()];
const PREFERENCE_APPLIED = 'Preference-Applied';
const PREFERENCE_APPLIED_HEADERS = [PREFERENCE_APPLIED, PREFERENCE_APPLIED.toLowerCase()];
const ETAG_HEADER = 'ETag';
const ETAG_HEADERS = [ETAG_HEADER, ETAG_HEADER.toLowerCase()];
const RETRY_AFTER = 'Retry-After';
const RETRY_AFTER_HEADERS = [RETRY_AFTER, RETRY_AFTER.toLowerCase()];
// HTTP HEADER VALUES
const APPLICATION_JSON = 'application/json';
const APPLICATION_HTTP = 'application/http';
const APPLICATION_XHTML = 'application/xhtml+xml';
const APPLICATION_XML = 'application/xml';
const TEXT_PLAIN = 'text/plain';
const CONTENT_TYPE_ANY = '*/*';
const MULTIPART_MIXED = 'multipart/mixed';
const MULTIPART_MIXED_BOUNDARY = 'multipart/mixed;boundary=';
const CONTENT_TRANSFER_ENCODING = 'Content-Transfer-Encoding';
const CONTENT_ID = 'Content-ID';
const MAX_AGE = 'max-age';
// VERSIONS
const VERSION_4_0 = '4.0';
const VERSION_3_0 = '3.0';
const VERSION_2_0 = '2.0';
const DEFAULT_VERSION = VERSION_4_0;
const BINARY = 'binary';
const BOUNDARY_PREFIX_SUFFIX = '--';
const BATCH_PREFIX = 'batch_';
const CHANGESET_PREFIX = 'changeset_';
const DEFAULT_METADATA = 'minimal';
const DEFAULT_STRIP_METADATA = 'full';
const DEFAULT_FETCH_POLICY = 'network-only';
const DEFAULT_TIMEOUT = 60; // Time in seconds
const CALLABLE_BINDING_PARAMETER = 'bindingParameter';
const XSSI_PREFIX = /^\)\]\}',?\n/;
// URL PARTS
const QUERY_SEPARATOR = '?';
const PARAM_SEPARATOR = '&';
const VALUE_SEPARATOR = '=';
const PATH_SEPARATOR = '/';
const ODATA_PARAM_PREFIX = '$';
const ODATA_ALIAS_PREFIX = '@';
const NEWLINE = '\r\n';
const NEWLINE_REGEXP = /\r?\n/;
const CACHE_KEY_SEPARATOR = ':';
// Models
const CID_FIELD_NAME = '_cid';
const EVENT_SPLITTER = /\s+/;
// Standard vocabularies for annotating OData services
// https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md
const COMPUTED = /.*Computed$/;
const OPTIMISTIC_CONCURRENCY = /.*OptimisticConcurrency$/;
const DESCRIPTION = /.*Description$/;
const LONG_DESCRIPTION = /.*LongDescription$/;
const OPTIONARL_PARAMETER = /.*OptionalParameter$/;
const COLLECTION = /Collection\(([\w\.]+)\)/;
const PROPERTY = /([\w\d\-_]+)\(([\'\w\d\-_=]+)\)/;
const EXPAND = /([\w\d\-_]+)\(([\w\d\,\(\)]+)\)/;
const ODataVersionBaseHelper = {
entity(data) {
return data;
},
entities(data) {
return data[this.VALUE];
},
property(data) {
return this.VALUE in data ? data[this.VALUE] : data;
},
functions(annots) {
const funcs = new Map();
[...annots.keys()]
.filter((key) => key.startsWith(this.ODATA_FUNCTION_PREFIX))
.forEach((key) => funcs.set(key.substring(this.ODATA_FUNCTION_PREFIX.length), annots.get(key)));
return funcs;
},
properties(annots) {
const props = new Map();
[...annots.keys()]
.filter((key) => key.indexOf(this.ODATA_ANNOTATION_PREFIX) > 0)
.forEach((key) => {
let name = key.substring(0, key.indexOf(this.ODATA_ANNOTATION_PREFIX));
let prop = props.has(name) ? props.get(name) : new Map();
prop.set(key.substring(key.indexOf(this.ODATA_ANNOTATION_PREFIX)), annots.get(key));
props.set(name, prop);
});
return props;
},
id(annots) {
return annots instanceof Map ? annots.get(this.ODATA_ID) : annots[this.ODATA_ID];
},
etag(annots) {
return annots instanceof Map ? annots.get(this.ODATA_ETAG) : annots[this.ODATA_ETAG];
},
type(annots) {
let type = annots instanceof Map ? annots.get(this.ODATA_TYPE) : annots[this.ODATA_TYPE];
if (!type)
return undefined;
type = type.substring(1);
const matches = COLLECTION.exec(type);
if (matches)
return matches[1].indexOf('.') === -1 ? `Edm.${matches[1]}` : matches[1];
return type;
},
mediaEtag(annots) {
return annots.has(this.ODATA_MEDIA_ETAG)
? decodeURIComponent(annots.get(this.ODATA_MEDIA_ETAG))
: undefined;
},
metadataEtag(annots) {
return annots.has(this.ODATA_METADATA_ETAG)
? decodeURIComponent(annots.get(this.ODATA_METADATA_ETAG))
: undefined;
},
count(annots) {
return annots.has(this.ODATA_COUNT) ? Number(annots.get(this.ODATA_COUNT)) : undefined;
},
annotations(value) {
const annots = new Map();
Object.entries(value)
.filter(([key]) => key.indexOf(this.ODATA_ANNOTATION_PREFIX) !== -1 ||
key.startsWith(this.ODATA_FUNCTION_PREFIX))
.forEach(([key, value]) => annots.set(key, value));
return annots;
},
attributes(value, metadata) {
return Object.entries(value)
.filter(([k]) => metadata === 'none' ||
(metadata === 'minimal' &&
(k.indexOf(this.ODATA_ANNOTATION_PREFIX) === -1 ||
k.startsWith(this.ODATA_ANNOTATION_PREFIX)) &&
!k.startsWith(this.ODATA_FUNCTION_PREFIX)) ||
(metadata === 'full' &&
k.indexOf(this.ODATA_ANNOTATION_PREFIX) === -1 &&
!k.startsWith(this.ODATA_FUNCTION_PREFIX)))
.reduce((acc, e) => ({ ...acc, [e[0]]: e[1] }), {});
},
nextLink(annots) {
return annots.has(this.ODATA_NEXTLINK)
? decodeURIComponent(annots.get(this.ODATA_NEXTLINK))
: undefined;
},
readLink(annots) {
return annots.has(this.ODATA_READLINK)
? decodeURIComponent(annots.get(this.ODATA_READLINK))
: undefined;
},
mediaReadLink(annots) {
return annots.has(this.ODATA_MEDIA_READLINK)
? decodeURIComponent(annots.get(this.ODATA_MEDIA_READLINK))
: undefined;
},
editLink(annots) {
return annots.has(this.ODATA_EDITLINK)
? decodeURIComponent(annots.get(this.ODATA_EDITLINK))
: undefined;
},
mediaEditLink(annots) {
return annots.has(this.ODATA_MEDIA_EDITLINK)
? decodeURIComponent(annots.get(this.ODATA_MEDIA_EDITLINK))
: undefined;
},
deltaLink(annots) {
return annots.has(this.ODATA_DELTALINK)
? decodeURIComponent(annots.get(this.ODATA_DELTALINK))
: undefined;
},
mediaContentType(annots) {
return annots.has(this.ODATA_MEDIA_CONTENTTYPE)
? decodeURIComponent(annots.get(this.ODATA_MEDIA_CONTENTTYPE))
: undefined;
},
};
const ODataHelper = {
//#region Version 4.0
[VERSION_4_0]: {
...ODataVersionBaseHelper,
VALUE: 'value',
ODATA_ANNOTATION_PREFIX: '@odata',
ODATA_FUNCTION_PREFIX: '#',
//odata.id: the ID of the entity
ODATA_ID: '@odata.id',
//odata.count: the total count of a collection of entities or collection of entity references, if requested.
ODATA_COUNT: '@odata.count',
//odata.context: the context URL for a collection, entity, primitive value, or service document.
ODATA_CONTEXT: '@odata.context',
//odata.etag: the ETag of the entity
ODATA_ETAG: '@odata.etag',
ODATA_METADATA_ETAG: '@odata.metadataEtag',
//odata.type: the type of the containing {[name: string]: any} or targeted property if the type of the {[name: string]: any} or targeted property cannot be heuristically determined
ODATA_TYPE: '@odata.type',
//odata.delta
ODATA_DELTA: '@odata.delta',
//odata.remove
ODATA_REMOVE: '@odata.remove',
//odata.nextLink: the next link of a collection with partial results
ODATA_NEXTLINK: '@odata.nextLink',
//odata.deltaLink: the delta link for obtaining changes to the result, if requested
ODATA_DELTALINK: '@odata.deltaLink',
//odata.readLink: the link used to read the entity, if the edit link cannot be used to read the entity
ODATA_READLINK: '@odata.readLink',
//odata.editLink: the link used to edit/update the entity, if the entity is updatable and the odata.id does not represent a URL that can be used to edit the entity
ODATA_EDITLINK: '@odata.editLink',
//odata.associationLink: the link used to describe the relationship between this entity and related entities
ODATA_ASSOCIATIONLINK: '@odata.associationLink',
//odata.navigationLink: the link used to retrieve the values of a navigation property
ODATA_NAVIGATIONLINK: '@odata.navigationLink',
//Media entities and stream properties may in addition contain the following annotations:
//odata.mediaEtag: the ETag of the stream, as appropriate
ODATA_MEDIA_ETAG: '@odata.mediaEtag',
//odata.mediaContentType: the content type of the stream
ODATA_MEDIA_CONTENTTYPE: '@odata.mediaContentType',
//odata.mediaReadLink: the link used to read the stream
ODATA_MEDIA_READLINK: '@odata.mediaReadLink',
//odata.mediaEditLink: the link used to edit/update the stream
ODATA_MEDIA_EDITLINK: '@odata.mediaEditLink',
//http://nb-mdp-dev01:57970/$metadata#recursos/$entity
//http://nb-mdp-dev01:57970/$metadata#categorias
//http://nb-mdp-dev01:57970/$metadata#juzgados
//http://nb-mdp-dev01:57970/$metadata#Collection(SIU.Recursos.RecursoEntry)
//http://nb-mdp-dev01:57970/$metadata#categorias/$entity
//http://nb-mdp-dev01:57970/$metadata#categorias(children(children(children(children(children(children(children(children(children(children()))))))))))/$entity
//http://nb-mdp-dev01:57970/$metadata#recursos/SIU.Documentos.Documento/$entity
//http://nb-mdp-dev01:57970/$metadata#SIU.Api.Infrastructure.Storage.Backend.SiuUrls
context(annots) {
let ctx = {};
const str = annots instanceof Map ? annots.get(this.ODATA_CONTEXT) : annots[this.ODATA_CONTEXT];
if (typeof str === 'string') {
let index = str.indexOf('$metadata');
ctx.serviceRootUrl = str.substring(0, index);
index = str.indexOf('#');
ctx.metadataUrl = str.substring(0, index);
const parts = str.substring(index + 1).split('/');
const col = COLLECTION.exec(parts[0]);
if (col) {
ctx.type = col[1];
}
else if (parts[0].indexOf('.') !== -1) {
ctx.type = parts[0];
}
else {
const property = parts[0].match(PROPERTY);
const expand = parts[0].match(EXPAND);
ctx.entity = parts[1] === '$entity';
if (property) {
ctx.entitySet = property[1];
ctx.key = property[2];
ctx.property = parts[1];
}
else if (expand) {
ctx.entitySet = expand[1];
ctx.expand = expand[2];
}
else {
ctx.entitySet = parts[0];
}
}
}
return ctx;
},
countParam() {
return { [$COUNT]: 'true' };
},
},
//#endregion
//#region Version 3.0
[VERSION_3_0]: {
...ODataVersionBaseHelper,
ODATA_ANNOTATION_PREFIX: 'odata.',
ODATA_FUNCTION_PREFIX: '',
ODATA_ID: 'odata.id',
ODATA_DELTA: 'odata.delta',
ODATA_REMOVE: 'odata.remove',
ODATA_ETAG: 'odata.etag',
ODATA_CONTEXT: 'odata.metadata',
ODATA_NEXTLINK: 'odata.nextLink',
ODATA_TYPE: 'odata.type',
ODATA_COUNT: 'odata.count',
VALUE: 'value',
context(annots) {
let ctx = {};
const str = annots instanceof Map ? annots.get(this.ODATA_CONTEXT) : annots[this.ODATA_CONTEXT];
if (typeof str === 'string') {
let index = str.indexOf('$metadata');
ctx.serviceRootUrl = str.substring(0, index);
index = str.indexOf('#');
ctx.metadataUrl = str.substring(0, index);
const parts = str.substring(index + 1).split('/');
ctx.entitySet = parts[0];
}
return ctx;
},
countParam() {
return { [$INLINECOUNT]: 'allpages' };
},
},
//#endregion
//#region Version 2.0
[VERSION_2_0]: {
...ODataVersionBaseHelper,
ODATA_ID: 'id',
ODATA_DELTA: 'delta',
ODATA_REMOVE: 'remove',
ODATA_ETAG: 'etag',
ODATA_ANNOTATION: '__metadata',
ODATA_NEXTLINK: '__next',
ODATA_COUNT: '__count',
ODATA_DEFERRED: '__deferred',
ODATA_TYPE: 'type',
VALUE: 'results',
annotations(value) {
const annots = new Map();
if (this.ODATA_ANNOTATION in value) {
Object.entries(value[this.ODATA_ANNOTATION]).forEach(([key, value]) => annots.set(key, value));
}
return annots;
},
context(annots) {
let ctx = {};
return ctx;
},
attributes(value, metadata) {
return value;
},
countParam() {
return { [$INLINECOUNT]: 'allpages' };
},
},
//#endregion
};
const COMPARISON_OPERATORS = ['eq', 'ne', 'gt', 'ge', 'lt', 'le'];
const LOGICAL_OPERATORS = ['and', 'or', 'not'];
const COLLECTION_OPERATORS = ['any', 'all'];
const BOOLEAN_FUNCTIONS = ['startswith', 'endswith', 'contains'];
const SUPPORTED_EXPAND_PROPERTIES = [
'expand',
'levels',
'select',
'top',
'count',
'orderby',
'filter',
];
const FUNCTION_REGEX = /\((.*)\)/;
const INDEXOF_REGEX = /(?!indexof)\((\w+)\)/;
const StandardAggregateMethods = {
sum: 'sum',
min: 'min',
max: 'max',
average: 'average',
countdistinct: 'countdistinct'
};
const QueryCustomTypes = {
Raw: 'Raw',
Alias: 'Alias',
Duration: 'Duration',
Binary: 'Binary'
};
//https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_QueryOptions
const raw = (value) => ({
type: QueryCustomTypes.Raw,
value,
});
const alias = (value, name) => ({
type: QueryCustomTypes.Alias,
value,
name,
});
const duration = (value) => ({
type: QueryCustomTypes.Duration,
value,
});
const binary = (value) => ({
type: QueryCustomTypes.Binary,
value,
});
const isQueryCustomType = (value) => typeof value === 'object' && 'type' in value && value.type in QueryCustomTypes;
const isRawType = (value) => isQueryCustomType(value) && value.type === QueryCustomTypes.Raw;
const ITEM_ROOT = '';
function builder ({ select, search, skiptoken, format, top, skip, filter, transform, compute, orderBy, key, count, expand, action, func, aliases, escape, } = {}) {
const [path, params] = buildPathAndQuery({
select,
search,
skiptoken,
format,
top,
skip,
filter,
transform,
compute,
orderBy,
key,
count,
expand,
action,
func,
aliases,
escape,
});
return buildUrl(path, params);
}
function buildPathAndQuery({ select, search, skiptoken, format, top, skip, filter, apply, transform, compute, orderBy, key, count, expand, action, func, aliases, escape, } = {}) {
let path = '';
aliases = aliases || [];
const query = {};
// key is not (null, undefined)
if (key != undefined) {
path += `(${normalizeValue(key, { aliases, escape })})`;
}
// Select
if (select) {
query.$select = isRawType(select)
? select.value
: Array.isArray(select)
? select.join(',')
: select;
}
// Compute
if (compute) {
query.$compute = isRawType(compute)
? compute.value
: Array.isArray(compute)
? compute.join(',')
: compute;
}
// Search
if (search) {
query.$search = search;
}
// Skiptoken
if (skiptoken) {
query.$skiptoken = skiptoken;
}
// Format
if (format) {
query.$format = format;
}
// Filter
if (filter || typeof count === 'object') {
query.$filter = buildFilter(typeof count === 'object' ? count : filter, {
aliases,
escape,
});
}
// Transform
if (transform) {
query.$apply = buildTransforms(transform, { aliases, escape });
}
// Apply
if (apply) {
query.$apply = query.$apply
? query.$apply + '/' + buildApply(apply, { aliases, escape })
: buildApply(apply, { aliases, escape });
}
// Expand
if (expand) {
query.$expand = buildExpand(expand, { aliases, escape });
}
// OrderBy
if (orderBy) {
query.$orderby = buildOrderBy(orderBy);
}
// Count
if (isRawType(count)) {
query.$count = count.value;
}
else if (typeof count === 'boolean') {
query.$count = true;
}
else if (count) {
path += '/$count';
}
// Top
if (isRawType(top)) {
query.$top = top.value;
}
else if (typeof top === 'number') {
query.$top = top;
}
// Skip
if (isRawType(skip)) {
query.$top = skip.value;
}
else if (typeof skip === 'number') {
query.$skip = skip;
}
if (action) {
path += `/${action}`;
}
if (func) {
if (typeof func === 'string') {
path += `/${func}()`;
}
else if (typeof func === 'object') {
const [funcName] = Object.keys(func);
const funcArgs = normalizeValue(func[funcName], {
aliases,
escape,
});
path += `/${funcName}(${funcArgs})`;
}
}
if (aliases.length > 0) {
Object.assign(query, aliases.reduce((acc, alias) => Object.assign(acc, {
[`@${alias.name}`]: normalizeValue(alias.value, {
escape,
}),
}), {}));
}
// Filter empty values
const params = Object.entries(query)
.filter(([, value]) => value !== undefined && value !== '')
.reduce((acc, [key, value]) => Object.assign(acc, { [key]: value }), {});
return [path, params];
}
function renderPrimitiveValue(key, val, { aliases, escape, }) {
return `${key} eq ${normalizeValue(val, { aliases, escape })}`;
}
function buildFilter(filters = {}, { aliases, propPrefix, escape, }) {
return (Array.isArray(filters) ? filters : [filters]).reduce((acc, filter) => {
if (filter) {
const builtFilter = buildFilterCore(filter, {
aliases,
propPrefix,
escape,
});
if (builtFilter) {
acc.push(builtFilter);
}
}
return acc;
}, []).join(' and ');
function buildFilterCore(filter = {}, { aliases, propPrefix, escape, }) {
let filterExpr = '';
if (isRawType(filter)) {
// Use raw query custom filter string
filterExpr = filter.value;
}
else if (typeof filter === 'string') {
// Use raw filter string
filterExpr = filter;
}
else if (filter && typeof filter === 'object') {
const filtersArray = Object.keys(filter).reduce((result, filterKey) => {
const value = filter[filterKey];
let propName = '';
if (propPrefix) {
if (filterKey === ITEM_ROOT) {
propName = propPrefix;
}
else if (INDEXOF_REGEX.test(filterKey)) {
propName = filterKey.replace(INDEXOF_REGEX, (_, $1) => $1.trim() === ITEM_ROOT ? `(${propPrefix})` : `(${propPrefix}/${$1.trim()})`);
}
else if (FUNCTION_REGEX.test(filterKey)) {
propName = filterKey.replace(FUNCTION_REGEX, (_, $1) => $1.trim() === ITEM_ROOT ? `(${propPrefix})` : `(${propPrefix}/${$1.trim()})`);
}
else {
propName = `${propPrefix}/${filterKey}`;
}
}
else {
propName = filterKey;
}
if (filterKey === ITEM_ROOT && Array.isArray(value)) {
return result.concat(value.map((arrayValue) => renderPrimitiveValue(propName, arrayValue, { escape, aliases })));
}
if (['number', 'string', 'boolean'].indexOf(typeof value) !== -1 ||
value instanceof Date ||
value === null) {
// Simple key/value handled as equals operator
result.push(renderPrimitiveValue(propName, value, { aliases, escape }));
}
else if (Array.isArray(value)) {
const op = filterKey;
const builtFilters = value
.map((v) => buildFilter(v, { aliases, propPrefix, escape }))
.filter((f) => f)
.map((f) => (LOGICAL_OPERATORS.indexOf(op) !== -1 ? `(${f})` : f));
if (builtFilters.length) {
if (LOGICAL_OPERATORS.indexOf(op) !== -1) {
if (builtFilters.length) {
if (op === 'not') {
result.push(parseNot(builtFilters));
}
else {
result.push(`(${builtFilters.join(` ${op} `)})`);
}
}
}
else {
result.push(builtFilters.join(` ${op} `));
}
}
}
else if (LOGICAL_OPERATORS.indexOf(propName) !== -1) {
const op = propName;
const builtFilters = Object.keys(value).map((valueKey) => buildFilterCore({ [valueKey]: value[valueKey] }, { aliases, escape }));
if (builtFilters.length) {
if (op === 'not') {
result.push(parseNot(builtFilters));
}
else {
result.push(`${builtFilters.join(` ${op} `)}`);
}
}
}
else if (typeof value === 'object') {
if ('type' in value) {
result.push(renderPrimitiveValue(propName, value, { aliases, escape }));
}
else {
const operators = Object.keys(value);
operators.forEach((op) => {
if (COMPARISON_OPERATORS.indexOf(op) !== -1) {
result.push(`${propName} ${op} ${normalizeValue(value[op], {
aliases,
escape,
})}`);
}
else if (LOGICAL_OPERATORS.indexOf(op) !== -1) {
if (Array.isArray(value[op])) {
result.push(value[op]
.map((v) => '(' +
buildFilterCore(v, {
aliases,
propPrefix: propName,
escape,
}) +
')')
.join(` ${op} `));
}
else {
result.push('(' +
buildFilterCore(value[op], {
aliases,
propPrefix: propName,
escape,
}) +
')');
}
}
else if (COLLECTION_OPERATORS.indexOf(op) !== -1) {
const collectionClause = buildCollectionClause(filterKey.toLowerCase(), value[op], op, propName);
if (collectionClause) {
result.push(collectionClause);
}
}
else if (op === 'has') {
result.push(`${propName} ${op} ${normalizeValue(value[op], {
aliases,
escape,
})}`);
}
else if (op === 'in') {
const resultingValues = Array.isArray(value[op])
? value[op]
: value[op].value.map((typedValue) => ({
type: value[op].type,
value: typedValue,
}));
result.push(propName +
' in (' +
resultingValues
.map((v) => normalizeValue(v, { aliases, escape }))
.join(',') +
')');
}
else if (BOOLEAN_FUNCTIONS.indexOf(op) !== -1) {
// Simple boolean functions (startswith, endswith, contains)
result.push(`${op}(${propName},${normalizeValue(value[op], {
aliases,
escape,
})})`);
}
else {
// Nested property
const filter = buildFilterCore(value, {
aliases,
propPrefix: propName,
escape,
});
if (filter) {
result.push(filter);
}
}
});
}
}
else if (value === undefined) {
// Ignore/omit filter if value is `undefined`
}
else {
throw new Error(`Unexpected value type: ${value}`);
}
return result;
}, []);
filterExpr = filtersArray.join(' and ');
} /* else {
throw new Error(`Unexpected filters type: ${filter}`);
} */
return filterExpr;
}
function buildCollectionClause(lambdaParameter, value, op, propName) {
let clause = '';
if (typeof value === 'string' || value instanceof String) {
clause = getStringCollectionClause(lambdaParameter, value, op, propName);
}
else if (value) {
// normalize {any:[{prop1: 1}, {prop2: 1}]} --> {any:{prop1: 1, prop2: 1}}; same for 'all',
// simple values collection: {any:[{'': 'simpleVal1'}, {'': 'simpleVal2'}]} --> {any:{'': ['simpleVal1', 'simpleVal2']}}; same for 'all',
const filterValue = Array.isArray(value)
? value.reduce((acc, item) => {
if (item.hasOwnProperty(ITEM_ROOT)) {
if (!acc.hasOwnProperty(ITEM_ROOT)) {
acc[ITEM_ROOT] = [];
}
acc[ITEM_ROOT].push(item[ITEM_ROOT]);
return acc;
}
return { ...acc, ...item };
}, {})
: value;
const filter = buildFilterCore(filterValue, {
aliases,
propPrefix: lambdaParameter,
escape,
});
clause = `${propName}/${op}(${filter ? `${lambdaParameter}:${filter}` : ''})`;
}
return clause;
}
}
function getStringCollectionClause(lambdaParameter, value, collectionOperator, propName) {
const conditionOperator = collectionOperator == 'all' ? 'ne' : 'eq';
return `${propName}/${collectionOperator}(${lambdaParameter}: ${lambdaParameter} ${conditionOperator} '${value}')`;
}
function escapeIllegalChars(string) {
string = string.replace(/%/g, '%25');
string = string.replace(/\+/g, '%2B');
string = string.replace(/\//g, '%2F');
string = string.replace(/\?/g, '%3F');
string = string.replace(/#/g, '%23');
string = string.replace(/&/g, '%26');
string = string.replace(/'/g, "''");
return string;
}
function normalizeValue(value, { aliases, escape = false } = {}) {
if (typeof value === 'string') {
return escape ? `'${escapeIllegalChars(value)}'` : `'${value}'`;
}
else if (value instanceof Date) {
return value.toISOString();
}
else if (typeof value === 'number') {
return value;
}
else if (Array.isArray(value)) {
return `[${value.map((d) => normalizeValue(d, { aliases, escape })).join(',')}]`;
}
else if (value === null) {
return value;
}
else if (typeof value === 'object') {
switch (value.type) {
case QueryCustomTypes.Raw:
return value.value;
case QueryCustomTypes.Duration:
return `duration'${value.value}'`;
case QueryCustomTypes.Binary:
return `binary'${value.value}'`;
case QueryCustomTypes.Alias:
// Store
if (Array.isArray(aliases)) {
if (value.name === undefined) {
value.name = `a${aliases.length + 1}`;
}
aliases.push(value);
}
return `@${value.name}`;
default:
return Object.entries(value)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => `${k}=${normalizeValue(v, { aliases, escape })}`)
.join(',');
}
}
return value;
}
function buildExpand(expands, { aliases, escape = false }) {
if (isRawType(expands)) {
return expands.value;
}
else if (typeof expands === 'number') {
return expands;
}
else if (typeof expands === 'string') {
if (expands.indexOf('/') === -1) {
return expands;
}
// Change `Foo/Bar/Baz` to `Foo($expand=Bar($expand=Baz))`
return expands
.split('/')
.reverse()
.reduce((results, item, index, arr) => {
if (index === 0) {
// Inner-most item
return `$expand=${item}`;
}
else if (index === arr.length - 1) {
// Outer-most item, don't add `$expand=` prefix (added above)
return `${item}(${results})`;
}
else {
// Other items
return `$expand=${item}(${results})`;
}
}, '');
}
else if (Array.isArray(expands)) {
return `${expands
.map((e) => buildExpand(e, { aliases, escape }))
.join(',')}`;
}
else if (typeof expands === 'object') {
const expandKeys = Object.keys(expands);
if (expandKeys.some((key) => SUPPORTED_EXPAND_PROPERTIES.indexOf(key.toLowerCase()) !== -1)) {
return expandKeys
.map((key) => {
let value;
switch (key) {
case 'filter':
value = buildFilter(expands[key], {
aliases,
escape,
});
break;
case 'orderBy':
value = buildOrderBy(expands[key]);
break;
case 'levels':
case 'count':
case 'top':
case 'skip':
value = `${expands[key]}`;
if (isRawType(value))
value = value.value;
break;
default:
value = buildExpand(expands[key], {
aliases,
escape,
});
}
return `$${key.toLowerCase()}=${value}`;
})
.join(';');
}
else {
return expandKeys
.map((key) => {
const builtExpand = buildExpand(expands[key], { aliases, escape });
return builtExpand ? `${key}(${builtExpand})` : key;
})
.join(',');
}
}
return '';
}
function buildTransforms(transforms, { aliases, escape = false }) {
// Wrap single object an array for simplified processing
const transformsArray = Array.isArray(transforms) ? transforms : [transforms];
const transformsResult = transformsArray.reduce((result, transform) => {
for (const key of Object.keys(transform)) {
switch (key) {
// TODO: support as many of the following:
// topcount, topsum, toppercent,
// bottomsum, bottomcount, bottompercent,
// identity, concat, expand, search, compute, isdefined
case 'aggregate': {
const aggregate = transform[key];
if (aggregate) {
result.push(`aggregate(${buildAggregate(aggregate)})`);
}
break;
}
case 'filter': {
const filter = transform[key];
if (filter) {
const builtFilter = buildFilter(filter, { aliases, escape });
if (builtFilter) {
result.push(`filter(${builtFilter})`);
}
}
break;
}
case 'groupBy': {
const groupBy = transform[key];
if (groupBy) {
result.push(`groupby(${buildGroupBy(groupBy, { aliases, escape })})`);
}
break;
}
default:
throw new Error(`Unsupported transform(s): ${key}`);
}
}
return result;
}, []);
return transformsResult.join('/') || undefined;
}
function buildAggregate(aggregate) {
// Wrap single object in an array for simplified processing
const aggregateArray = Array.isArray(aggregate) ? aggregate : [aggregate];
return aggregateArray
.map((aggregateItem) => {
return typeof aggregateItem === 'string'
? aggregateItem
: Object.keys(aggregateItem).map((aggregateKey) => {
const aggregateValue = aggregateItem[aggregateKey];
// TODO: Are these always required? Can/should we default them if so?
if (!aggregateValue.with) {
throw new Error(`'with' property required for '${aggregateKey}'`);
}
if (!aggregateValue.as) {
throw new Error(`'as' property required for '${aggregateKey}'`);
}
return `${aggregateKey} with ${aggregateValue.with} as ${aggregateValue.as}`;
});
})
.join(',');
}
function buildGroupBy(groupBy, { aliases, escape = false }) {
if (!groupBy.properties) {
throw new Error(`'properties' property required for groupBy`);
}
let result = `(${groupBy.properties.join(',')})`;
if (groupBy.transform) {
result += `,${buildTransforms(groupBy.transform, { aliases, escape })}`;
}
return result;
}
function buildOrderBy(orderBy, prefix = '') {
if (isRawType(orderBy)) {
return orderBy.value;
}
else if (Array.isArray(orderBy)) {
return orderBy
.map((value) => Array.isArray(value) && value.length === 2 && ['asc', 'desc'].indexOf(value[1]) !== -1
? value.join(' ')
: value)
.map((v) => `${prefix}${v}`)
.join(',');
}
else if (typeof orderBy === 'object') {
return Object.entries(orderBy)
.map(([k, v]) => buildOrderBy(v, `${k}/`))
.map((v) => `${prefix}${v}`)
.join(',');
}
return `${prefix}${orderBy}`;
}
function buildApply(apply, { aliases, escape = false }) {
const applyArray = Array.isArray(apply) ? apply : [apply];
return applyArray.map((v) => normalizeValue(v, { aliases, escape })).join('/');
}
function buildUrl(path, params) {
// This can be refactored using URL API. But IE does not support it.
const queries = Object.entries(params).map(([key, value]) => `${key}=${value}`);
return queries.length ? `${path}?${queries.join('&')}` : path;
}
function parseNot(builtFilters) {
return `not (${builtFilters.join(' and ')})`;
}
const ISO_REGEX = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
const Dates = {
isoStringToDate(value) {
if (typeof value === 'string' && value.search(ISO_REGEX) === 0) {
return new Date(value);
}
else if (Array.isArray(value)) {
return value.map((v) => this.isoStringToDate(v));
}
else if (value && value.constructor === Object) {
return Object.keys(value)
.map((key) => [key, this.isoStringToDate(value[key])])
.reduce((acc, v) => Object.assign(acc, { [v[0]]: v[1] }), {});
}
return value;
},
};
const DURATION_REGEX = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
const Durations = {
toDuration(v) {
const matches = DURATION_REGEX.exec(v);
if (!matches || v.length < 3) {
throw new TypeError(`duration invalid: "${v}". Must be a ISO 8601 duration. See https://en.wikipedia.org/wiki/ISO_8601#Durations`);
}
let duration = {};
duration.sign = matches[1] === '-' ? -1 : 1;
return ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'].reduce((acc, name, index) => {
const v = parseFloat(matches[index + 2]);
if (!Number.isNaN(v))
acc[name] = v;
return acc;
}, duration);
},
toString(v) {
return [
v.sign === -1 ? '-' : '',
'P',
v.years ? v.years + 'Y' : '',
v.months ? v.months + 'M' : '',
v.weeks ? v.weeks + 'W' : '',
v.days ? v.days + 'D' : '',
'T',
v.hours ? v.hours + 'H' : '',
v.minutes ? v.minutes + 'M' : '',
v.seconds ? v.seconds + 'S' : '',
].join('');
},
};
const Enums = {
names(enums) {
return Object.values(enums).filter((v) => typeof v === 'string');
},
values(enums) {
return Object.values(enums).filter((v) => typeof v === 'number');
},
toValue(enums, value) {
if (value in enums)
return typeof value === 'string' ? enums[value] : value;
return undefined;
},
toValues(enums, value) {
if (typeof value === 'number') {
return this.values(enums).filter((v) => (value & v) === v);
}
if (typeof value === 'string') {
value = value.split(',').map((o) => o.trim());
}
if (Array.isArray(value) && value.every((v) => v in enums)) {
return value.map((o) => this.toValue(enums, o));
}
return [];
},
toName(enums, value) {
if (value in enums)
return typeof value === 'number' ? enums[value] : value;
return undefined;
},
toNames(enums, value) {
if (typeof value === 'number') {
return this.values(enums)
.filter((v) => (value & v) === v)
.map((v) => this.toName(enums, v));
}
if (typeof value === 'string') {
value = value.split(',').map((o) => o.trim());
}
if (Array.isArray(value) && value.every((v) => v in enums)) {
return value.map((o) => this.toName(enums, o));
}
return [];
},
toFlags(enums, value) {
if (typeof value === 'number') {
return this.values(enums)
.filter((v) => v !== 0 && (value & v) === v)
.map((v) => this.toName(enums, v));
}
if (typeof value === 'string') {
value = value.split(',').map((o) => o.trim());
}
if (Array.isArray(value) && value.every((v) => v in enums)) {
return value.filter((v) => enums[v]).map((v) => this.toName(enums, v));
}
return [];
},
};
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
const Types = {
rawType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
},
isObject(value) {
return typeof value === 'object' && value !== null;
},
isPlainObject(value) {
if (this.rawType(value) !== 'Object') {
return false;
}
const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.prototype;
},
isFunction(value) {
return typeof value