angular-odata
Version:
Client side OData typescript library for Angular
1,343 lines (1,333 loc) • 509 kB
JavaScript
import * as i1 from '@angular/common/http';
import { HttpHeaders, HttpParams, HttpResponse, HttpErrorResponse, HttpEventType, HttpClientModule } from '@angular/common/http';
import { of, throwError, Subject, map as map$1, EMPTY, Observable, forkJoin, NEVER } from 'rxjs';
import { tap, startWith, map, expand, reduce, finalize, defaultIfEmpty, switchMap, catchError } 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$/;
class ODataCache {
timeout;
entries;
constructor({ timeout = DEFAULT_TIMEOUT }) {
this.timeout = timeout;
this.entries = new Map();
}
/**
* Using the resource on the request build an array of string to identify the scope of the request
* @param req The request with the resource to build the scope
* @returns Array of string to identify the scope of the request
*/
scope(req) {
const segments = req.resource.cloneSegments();
return segments.segments({ key: true }).reduce((acc, s) => {
if (s.name === PathSegment.entitySet)
acc = [...acc, s.path()];
return acc;
}, ['request']);
}
/**
* Using the odata context on the response build an array of string to identify the tags of the response
* @param res The response to build the tags
* @returns Array of string to identify the tags of the response
*/
tags(res) {
const tags = [];
const context = res.context;
if (context.entitySet) {
tags.push(context.key
? `${context.entitySet}(${context.key})`
: context.entitySet);
}
if (context.type)
tags.push(context.type);
return tags;
}
/**
* Build an entry from a payload and some options
* @param payload The payload to store in the cache
* @param timeout The timeout for the entry
* @param tags The tags for the entry
* @returns The entry to store in the cache
*/
buildEntry(payload, { timeout, tags }) {
return {
payload,
lastRead: Date.now(),
timeout: timeout || this.timeout,
tags: tags || [],
};
}
/**
* Build a key from store an entry in the cache
* @param names The names of the entry
* @returns The key for the entry
*/
buildKey(names) {
return names.join(CACHE_KEY_SEPARATOR);
}
/**
* Put some payload in the cache
* @param name The name for the entry
* @param payload The payload to store in the cache
* @param timeout The timeout for the entry
* @param scope The scope for the entry
* @param tags The tags for the entry
*/
put(name, payload, { timeout, scope, tags, } = {}) {
const entry = this.buildEntry(payload, { timeout, tags });
const key = this.buildKey([...(scope || []), name]);
this.entries.set(key, entry);
this.forget();
}
/**
* Return the payload from the cache if it exists and is not expired
* @param name The name of the entry
* @param scope The scope of the entry
* @returns The payload of the entry
*/
get(name, { scope } = {}) {
const key = this.buildKey([...(scope || []), name]);
const entry = this.entries.get(key);
return entry !== undefined && !this.isExpired(entry)
? entry.payload
: undefined;
}
/**
* Remove all cache entries that are matching with the given options
* @param options The options to forget
*/
forget({ name, scope = [], tags = [], } = {}) {
if (name !== undefined)
scope.push(name);
const key = scope.length > 0 ? this.buildKey(scope) : undefined;
this.entries.forEach((entry, k) => {
if (this.isExpired(entry) || // Expired
(key !== undefined && k.startsWith(key)) || // Key
(tags.length > 0 && tags.some((t) => entry.tags.indexOf(t) !== -1)) // Tags
) {
this.entries.delete(k);
}
});
}
/**
* Remove all cache entries
*/
flush() {
this.entries = new Map();
}
/**
* Check if the entry is expired
* @param entry The cache entry
* @returns Boolean indicating if the entry is expired
*/
isExpired(entry) {
return entry.lastRead < Date.now() - (entry.timeout || this.timeout) * 1000;
}
/**
* Using the request, handle the fetching of the response
* @param req The request to fetch
* @param res$ Observable of the response
* @returns
*/
handleRequest(req, res$) {
return req.isFetch()
? this.handleFetch(req, res$)
: req.isMutate()
? this.handleMutate(req, res$)
: res$;
}
handleFetch(req, res$) {
const policy = req.fetchPolicy;
const cached = this.getResponse(req);
if (policy === 'no-cache') {
return res$;
}
if (policy === 'cache-only') {
if (cached) {
return of(cached);
}
else {
return throwError(() => new Error('No Cached'));
}
}
if (policy === 'cache-first' ||
policy === 'cache-and-network' ||
policy === 'network-only') {
res$ = res$.pipe(tap((res) => {
if (res.options.cacheability !== 'no-store')
this.putResponse(req, res);
}));
}
return cached !== undefined && policy !== 'network-only'
? policy === 'cache-and-network'
? res$.pipe(startWith(cached))
: of(cached)
: res$;
}
handleMutate(req, res$) {
const requests = req.isBatch()
? req.resource
.requests()
.filter((r) => r.isMutate())
: [req];
for (var r of requests) {
const scope = this.scope(r);
this.forget({ scope });
}
return res$;
}
}
class ODataInMemoryCache extends ODataCache {
constructor({ timeout } = {}) {
super({ timeout });
}
/**
* Store the response in the cache
* @param req The request with the resource to store the response
* @param res The response to store in the cache
*/
putResponse(req, res) {
let scope = this.scope(req);
let tags = this.tags(res);
this.put(req.cacheKey, res, {
timeout: res.options.maxAge,
scope,
tags,
});
}
/**
* Restore the response from the cache
* @param req The request with the resource to get the response
* @returns The response from the cache
*/
getResponse(req) {
let scope = this.scope(req);
return this.get(req.cacheKey, { scope });
}
}
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+)\)/;
var StandardAggregateMethods;
(function (StandardAggregateMethods) {
StandardAggregateMethods["sum"] = "sum";
StandardAggregateMethods["min"] = "min";
StandardAggregateMethods["max"] = "max";
StandardAggregateMethods["average"] = "average";
StandardAggregateMethods["countdistinct"] = "countdistinct";
})(StandardAggregateMethods || (StandardAggregateMethods = {}));
var QueryCustomTypes;
(function (QueryCustomTypes) {
QueryCustomTypes[QueryCustomTypes["Raw"] = 0] = "Raw";
QueryCustomTypes[QueryCustomTypes["Alias"] = 1] = "Alias";
QueryCustomTypes[QueryCustomTypes["Duration"] = 2] = "Duration";
QueryCustomTypes[QueryCustomTypes["Binary"] = 3] = "Binary";
})(QueryCustomTypes || (QueryCustomTypes = {}));
//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) {
let clause = '';
const conditionOperator = collectionOperator == 'all' ? 'ne' : 'eq';
clause = `${propName}/${collectionOperator}(${lambdaParameter}: ${lambdaParameter} ${conditionOperator} '${value}')`;
return clause;
}
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) => {
const { aggregate, filter, groupBy, ...rest } = transform;
// TODO: support as many of the following:
// topcount, topsum, toppercent,
// bottomsum, bottomcount, bottompercent,
// identity, concat, expand, search, compute, isdefined
const unsupportedKeys = Object.keys(rest);
if (unsupportedKeys.length) {
throw new Error(`Unsupported transform(s): ${unsupportedKeys}`);
}
if (aggregate) {
result.push(`aggregate(${buildAggregate(aggregate)})`);
}
if (filter) {
const builtFilter = buildFilter(filter, { aliases, escape });
if (builtFilter) {
result.push(`filter(${buildFilter(builtFilter, { aliases, escape })})`);
}
}
if (groupBy) {
result.push(`groupby(${buildGroupBy(groupBy, { aliases, escape })})`);
}
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 === 'function';
},
isArray(value) {
return Array.isArray(value);
},
isMap(value) {
return this.rawType(value) === 'Map';
},
isEmpty(value) {
return (value === undefined ||
value === null ||
(typeof value === 'string' && !value.length) ||
(value instanceof Date && isNaN(value.valueOf())) ||
(Types.isMap(value) && !value.size) ||
(Types.isArray(value) && !value.length) ||
(Types.isFunction(value.isEmpty) && value.isEmpty()) ||
(Types.isArray(value) &&
value.every((v) => Types.isEmpty(v))) ||
(Types.isPlainObject(value) &&
!Object.keys(value).filter((k) => value.hasOwnProperty(k)).length));
},
isEqual(value1, value2) {
function getType(obj) {
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase();
}
function areDatesEqual() {
return value1.getTime() === value2.getTime();
}
function areArraysBufferEqual() {
if (value1.byteLength !== value2.byteLength) {
return false;
}
var view1 = new DataView(value1);
var view2 = new DataView(value2);
var i = value1.byteLength;
while (i--) {
if (view1.getUint8(i) !== view2.getUint8(i)) {
return false;
}
}
return true;
}
function areArraysEqual() {
// Check length
if (value1.length !== value2.length)
return false;
// Check each item in the array
for (let i = 0; i < value1.length; i++) {
if (!Types.isEqual(value1[i], value2[i]))
return false;
}
// If no errors, return true
return true;
}
function areObjectsEqual() {
if (Object.keys(value1).length !== Object.keys(value2).length)
return false;
// Check each item in the object
for (let key in value1) {
if (Object.prototype.hasOwnProperty.call(value1, key)) {
if (!Types.isEqual(value1[key], value2[key]))
return false;
}
}
// If no errors, return true
return true;
}
function areFunctionsEqual() {
return value1.toString() === value2.toString();
}
function arePrimativesEqual() {
return value1 === value2;
}
// Get the object type
let type = getType(value1);
// If the two items are not the same type, return false
if (type !== getType(value2))
return false;
// Compare based on type
if (type === 'date')
return areDatesEqual();
if (type === 'arraybuffer')
return areArraysBufferEqual();
if (type === 'array')
return areArraysEqual();
if (type === 'object')
return areObjectsEqual();
if (type === 'function')
return areFunctionsEqual();
return arePrimativesEqual();
},
clone(target) {
const constrFun = target.constructor;
switch (this.rawType(target)) {
case 'Boolean':
case 'Number':
case 'String':
case 'Error':
case 'Date':
return new constrFun(target);
case 'RegExp':
return cloneReg(target);
case 'Symbol':
return cloneSymbol(target);
case 'Function':
return target;
default:
return null;
}
},
};
const Http = {
//Merge Headers
mergeHttpHeaders(...values) {
let headers = new HttpHeaders();
values.forEach((value) => {
if (value instanceof HttpHeaders) {
value.keys().forEach((key) => {
headers = (value.getAll(key) || []).reduce((acc, v) => acc.append(key, v), headers);
});
}
else if (Types.isPlainObject(value)) {
Object.entries(value).forEach(([key, value]) => {
headers = (Array.isArray(value) ? value : [value]).reduce((acc, v) => acc.append(key, v), headers);
});
}
});
return headers;
},
// Merge Params
mergeHttpParams(