angular-odata
Version:
Client side OData typescript library for Angular
14,261 lines • 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(...values) {
let params = new HttpParams();
values.forEach((value) => {
if (value instanceof HttpParams) {
value.keys().forEach((key) => {
params = (value.getAll(key) || []).reduce((acc, v) => acc.append(key, v), params);
});
}
else if (Types.isPlainObject(value)) {
Object.entries(value).forEach(([key, value]) => {
params = (Array.isArray(value) ? value : [value]).reduce((acc, v) => acc.append(key, v), params);
});
}
});
return params;
},
// Split Params
splitHttpParams(params, keys) {
let other = new HttpParams();
params.keys().forEach((key) => {
if (keys.includes(key)) {
other = (params.getAll(key) || []).reduce((acc, v) => acc.append(key, v), other);
params = params.delete(key);
}
});
return [params, other];
},
// Without Params
withoutHttpParams(params, keys) {
return keys.reduce((acc, key) => acc.delete(key), params);
},
resolveHeaderKey(headers, options) {
if (headers instanceof HttpHeaders) {
return headers.keys().find((k) => options.indexOf(k) !== -1);
}
else if (Types.isPlainObject(headers)) {
return Object.keys(headers).find((k) => options.indexOf(k) !== -1);
}
return undefined;
},
headerValue(header) {
let res = header.split(';')[0].trim();
res = res.split(':')[1].trim();
return res;
},
parseResponseStatus(line) {
const chunks = line.split(' ');
return {
status: chunks[0],
code: parseInt(chunks[1], 10),
message: chunks.slice(2).join(' '),
};
},
boundaryDelimiter(contentType) {
const contentTypeParts = contentType.split(';');
if (contentTypeParts.length === 2) {
const boundary = contentType.split(';')[1].trim();
const boundaryDelimiter = BOUNDARY_PREFIX_SUFFIX + boundary.split('=')[1];
return boundaryDelimiter;
}
else {
return '';
}
},
boundaryEnd(boundaryDelimiter) {
if (!boundaryDelimiter.length) {
return '';
}
const boundaryEnd = boundaryDelimiter + BOUNDARY_PREFIX_SUFFIX;
return boundaryEnd;
},
};
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
const Objects = {
set(obj, path, value) {
// Check if path is string or array. Regex : ensure that we do not have '.' and brackets.
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
pathArray.reduce((acc, key, i) => {
if (acc[key] === undefined)
acc[key] = {};
if (i === pathArray.length - 1)
acc[key] = value;
return acc[key];
}, obj);
},
get(obj, path, def) {
// Check if path is string or array. Regex : ensure that we do not have '.' and brackets.
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
// Find value if exist return otherwise return undefined value;
return (pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj) || def);
},
unset(obj, path) {
// Check if path is string or array. Regex : ensure that we do not have '.' and brackets.
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
pathArray.reduce((acc, key, i) => {
if (i === pathArray.length - 1)
delete acc[key];
return acc[key];
}, obj);
},
has(obj, path) {
// Check if path is string or array. Regex : ensure that we do not have '.' and brackets.
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
return !!pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj);
},
merge(target, source) {
const merge = (target, source) => {
for (let attr in source) {
let value = source[attr];
if (value !== null && Types.isPlainObject(value) && attr in target) {
merge(target[attr], value);
}
else if (target[attr] !== value) {
target[attr] = value;
}
}
};
merge(target, source);
return target;
},
equal(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = Types.isPlainObject(val1) && Types.isPlainObject(val2);
if ((areObjects && !Objects.equal(val1, val2)) ||
(!areObjects && val1 !== val2)) {
return false;
}
}
return true;
},
difference(object1, object2) {
if (!object2 || !Types.isPlainObject(object2)) {
return object1;
}
var diffs = {};
var key;
var arraysMatch = function (arr1, arr2) {
if (arr1.length !== arr2.length)
return false;
for (var i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i])
return false;
}
return true;
};
var compare = function (item1, item2, key) {
if (item2 === undefined) {
diffs[key] = null;
return;
}
if (typeof item1 !== typeof item2) {
diffs[key] = item2;
return;
}
if (Types.isPlainObject(item1)) {
var objDiff = Objects.difference(item1, item2);
if (Object.keys(objDiff).length > 0) {
diffs[key] = objDiff;
}
return;
}
if (Array.isArray(item1)) {
if (!arraysMatch(item1, item2)) {
diffs[key] = item2;
}
return;
}
if (item1 !== item2) {
diffs[key] = item2;
}
};
for (key in object1) {
if (object1.hasOwnProperty(key)) {
compare(object1[key], object2[key], key);
}
}
for (key in object2) {
if (object2.hasOwnProperty(key)) {
if (!object1[key] && object1[key] !== object2[key]) {
diffs[key] = object2[key];
}
}
}
return diffs;
},
resolveKey(key, { single = true } = {}) {
const type = Types.rawType(key);
if (['number', 'string'].indexOf(type) !== -1)
return key;
if (type !== 'Map' && type !== 'Object') {
return undefined;
}
const values = type === 'Map' ? Array.from(key.values()) : Object.values(key);
if (values.length === 1 && single) {
// Single primitive key value
key = values[0];
return !Types.isEmpty(key) ? key : undefined;
}
else if (values.some((v) => v === undefined)) {
// Compose key, needs all values
return undefined;
}
else {
const obj = type === 'Map' ? Object.fromEntries(key) : key;
return !Types.isEmpty(obj) ? obj : undefined;
}
},
clone(target, map) {
if (map === undefined)
map = new WeakMap();
// clone primitive types
if (typeof target != 'object' || target == null) {
return target;
}
if ('clone' in target) {
// target is a cloneable object
return target.clone();
}
const type = Types.rawType(target);
let cloneTarget = null;
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
if (type != 'Set' && type != 'Map' && type != 'Array' && type != 'Object') {
return Types.clone(target);
}
// clone Set
if (type == 'Set') {
cloneTarget = new Set();
target.forEach((value) => {
cloneTarget.add(this.clone(value, map));
});
return cloneTarget;
}
// clone Map
if (type == 'Map') {
cloneTarget = new Map();
target.forEach((value, key) => {
cloneTarget.set(key, this.clone(value, map));
});
return cloneTarget;
}
// clone Array
if (type == 'Array') {
cloneTarget = new Array();
forEach(target, (value, index) => {
cloneTarget[index] = this.clone(value, map);
});
}
// clone normal Object
if (type == 'Object') {
cloneTarget = new Object();
forEach(Object.keys(target), (key, index) => {
cloneTarget[key] = this.clone(target[key], map);
});
}
return cloneTarget;
},
};
const OData = {
// Merge callables parameters
mergeCallableParameters(callables) {
const areEqual = (a, b) => a.name === b.name &&
Objects.equal((a.parameters || {})[CALLABLE_BINDING_PARAMETER] || {}, (b.parameters || {})[CALLABLE_BINDING_PARAMETER] || {});
return callables.reduce((acc, config) => {
if (acc.every((c) => !areEqual(c, config))) {
config = callables
.filter((c) => areEqual(c, config))
.reduce((acc, c) => {
acc.parameters = Object.assign(acc.parameters || {}, c.parameters || {});
return acc;
}, config);
return [...acc, config];
}
return acc;
}, []);
},
};
// From https://github.com/adamhalasz/uniqid
var glast;
function now() {
let time = Date.now();
let last = glast || time;
return (glast = time > last ? time : last + 1);
}
const Strings = {
uniqueId({ prefix, suffix, } = {}) {
return (prefix ? prefix : '') + now().toString(36) + (suffix ? suffix : '');
},
titleCase(text) {
const result = text.replace(/([a-z])([A-Z])/g, '$1 $2');
return result
.split(' ')
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
.join(' ');
},
};
const Urls = {
parseQueryString(query) {
return query.split(PARAM_SEPARATOR).reduce((acc, param) => {
let index = param.indexOf(VALUE_SEPARATOR);
if (index !== -1)
Object.assign(acc, {
[param.substring(0, index)]: param.substring(index + 1),
});
return acc;
}, {});
},
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;
},
};
class Expression {
_children;
constructor({ children, } = {}) {
this._children = children || [];
}
get [Symbol.toStringTag]() {
return 'Expression';
}
children() {
return this._children;
}
length() {
return this._children.length;
}
toJson() {
return {
$type: Types.rawType(this),
children: this._children.map((c) => c.toJson()),
};
}
resolve(parser) {
return parser;
}
}
class ODataAnnotation {
term;
string;
bool;
int;
permissions;
properties;
constructor(annot) {
this.term = annot.term;
Object.assign(this, annot);
}
}
class ODataAnnotatable {
annotations;
constructor(config) {
this.annotations = (config.annotations || []).map((annot) => new ODataAnnotation(annot));
}
/**
* Find an annotation inside the annotatable.
* @param predicate Function that returns true if the annotation match.
* @returns The annotation that matches the predicate.
*/
findAnnotation(predicate) {
return this.annotations.find(predicate);
}
/**
* Find an annotation inside the annotatable and return its value.
* @param term The term of the annotation to find.
* @returns The value of the annotation.
*/
annotatedValue(term) {
const reg = term instanceof RegExp ? term : new RegExp(`^${term}$`);
const annot = this.findAnnotation((a) => reg.test(a.term));
if (!annot) {
return undefined;
}
return (annot.string ||
annot.bool ||
annot.int ||
annot.permissions ||
annot.properties);
}
}
class ODataSchemaElement extends ODataAnnotatable {
name;
schema;
constructor(config, schema) {
super(config);
this.schema = schema;
this.name = config.name;
}
get api() {
return this.schema.api;
}
/**
* Create a nicer looking title.
* Titleize is meant for creating pretty output.
* @param term The term of the annotation to find.
* @returns The titleized string.
*/
titleize(term) {
return (term && this.annotatedValue(term)) ?? Strings.titleCase(this.name);
}
/**
* Returns a full type of the structured type including the namespace/alias.
* @param alias Use the alias of the namespace instead of the namespace.
* @returns The string representation of the type.
*/
type({ alias = false } = {}) {
return `${alias ? this.schema.alias : this.schema.namespace}.${this.name}`;
}
/**
* Returns a boolean indicating if the structured type is of the given type.
* @param type String representation of the type
* @returns True if the callable is type of the given type
*/
isTypeOf(element) {
const names = [`${this.schema.namespace}.${this.name}`];
if (this.schema.alias)
names.push(`${this.schema.alias}.${this.name}`);
return names.includes(element.type());
}
/**
* Returns a boolean indicating if the structured type is a subtype of the given type.
* @param type String representation of the type
* @returns True if the callable is type of the given type
*/
isSubtypeOf(element) {
if (this.isTypeOf(element))
return true;
return false;
}
/**
* Returns a boolean indicating if the structured type is a supertype of the given type.
* @param type String representation of the type
* @returns True if the callable is type of the given type
*/
isSupertypeOf(element) {
if (this.isTypeOf(element))
return true;
return false;
}
}
class ODataParserSchemaElement extends ODataSchemaElement {
parser;
constructor(config, schema, parser) {
super(config, schema);
this.parser = parser;
}
}
//https://github.com/niklasvh/base64-arraybuffer
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
// Use a lookup table to find the index.
const lookup = new Uint8Array(256);
for (var i = 0; i < chars.length; i++) {
lookup[chars.charCodeAt(i)] = i;
}
const ArrayBuffers = {
toArrayBuffer(v) {
var bufferLength = v.length * 0.75, len = v.length, i, p = 0, encoded1, encoded2, encoded3, encoded4;
if (v[v.length - 1] === '=') {
bufferLength--;
if (v[v.length - 2] === '=') {
bufferLength--;
}
}
var arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i += 4) {
encoded1 = lookup[v.charCodeAt(i)];
encoded2 = lookup[v.charCodeAt(i + 1)];
encoded3 = lookup[v.charCodeAt(i + 2)];
encoded4 = lookup[v.charCodeAt(i + 3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
}
return arraybuffer;
},
toString(v) {
var bytes = new Uint8Array(v), i, len = bytes.length, base64 = '';
for (i = 0; i < len; i += 3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if (len % 3 === 2) {
base64 = base64.substring(0, base64.length - 1) + '=';
}
else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + '==';
}
return base64;
},
};
// Core EdmTypeParserBuilder
const EdmParser = (_d, _s, _e) => ({
deserialize(value, options) {
return Array.isArray(value)
? value.map((v) => _d(v, options))
: _d(value, options);
},
serialize(value, options) {
return Array.isArray(value)
? value.map((v) => _s(v, options))
: _s(value, options);
},
encode(value, options) {
return Array.isArray(value)
? value.map((v) => (!isRawType(v) ? _e(v, options) : v))
: !isRawType(value)
? _e(value, options)
: value;
},
});
const Identity = (v) => v;
const toNumber = (v) => Number(v);
const toString = (v) => v.toString();
const toBoolean = (v) => Boolean(v);
const toDate = (v) => new Date(v);
const EDM_PARSERS = {
//Edm.Guid 16-byte (128-bit) unique identifier
[EdmType.Guid]: EdmParser(Identity, Identity, (v) => raw(v)),
//Edm.Int16 Signed 16-bit integer
[EdmType.Int16]: EdmParser(toNumber, toNumber, toNumber),
//Edm.String Sequence of UTF-8 characters
[EdmType.String]: EdmParser(toString, toString, toString),
//Edm.Boolean Binary-valued logic
[EdmType.Boolean]: EdmParser(toBoolean, toBoolean, toBoolean),
//Edm.Byte Unsigned 8-bit integer
[EdmType.Byte]: EdmParser(toNumber, toNumber, toNumber),
//Edm.SByte Signed 8-bit integer
[EdmType.SByte]: EdmParser(toNumber, toNumber, toNumber),
//Edm.Int32 Signed 16-bit integer
[EdmType.Int32]: EdmParser(toNumber, toNumber, toNumber),
//Edm.Int64 Signed 16-bit integer
[EdmType.Int64]: EdmParser(toNumber, toNumber, toNumber),
//Edm.Date Date without a time-zone offset
[EdmType.Date]: EdmParser((v) => new Date(`${v}T00:00:00.000Z`), (v) => toDate(v).toISOString().substring(0, 10), (v) => raw(toDate(v).toISOString().substring(0, 10))),
//Edm.TimeOfDay Clock time 00:00-23:59:59.999999999999
[EdmType.TimeOfDay]: EdmParser((v) => new Date(`1970-01-01T${v}Z`), (v) => toDate(v).toISOString().substring(11, 23), (v) => raw(toDate(v).toISOString().substring(11, 23))),
//Edm.DateTimeOffset Date and time with a time-zone offset, no leap seconds
[EdmType.DateTimeOffset]: EdmParser(toDate, (v) => toDate(v).toISOString(), (v) => raw(toDate(v).toISOString())),
//Edm.Duration Signed duration in days, hours, minutes, and (sub)seconds
[EdmType.Duration]: EdmParser((v) => Durations.toDuration(v), (v) => Durations.toString(v), (v) => raw(Durations.toString(v))),
//Edm.Decimal Numeric values with fixed precision and scale
[EdmType.Decimal]: EdmParser(toNumber, (v, o) => {
if (o.ieee754Compatible) {
let vstr = v.toPrecision(o.field.precision);
if (typeof o.field.scale === 'number') {
vstr = parseFloat(vstr).toFixed(o.field.scale);
}
return vstr;
}
return v;
}, (v, o) => {
if (o.ieee754Compatible) {
let vstr = v.toPrecision(o.field.precision);
if (typeof o.field.scale === 'number') {
vstr = parseFloat(vstr).toFixed(o.field.scale);
}
return raw(vstr);
}
return v;
}),
//Edm.Double IEEE 754 binary64 floating-point number (15-17 decimal digits)
[EdmType.Double]: EdmParser((v) => (v === 'INF' ? Infinity : v), (v) => (v === Infinity ? 'INF' : v), (v) => raw(v === Infinity ? 'INF' : v.toString())),
//Edm.Single IEEE 754 binary32 floating-point number (6-9 decimal digits)
[EdmType.Single]: EdmParser((v) => (v === 'INF' ? Infinity : v), (v) => (v === Infinity ? 'INF' : v), (v) => raw(v === Infinity ? 'INF' : v.toString())),
//Edm.Binary Binary data
[EdmType.Binary]: EdmParser((v) => ArrayBuffers.toArrayBuffer(v), (v) => ArrayBuffers.toString(v), (v) => raw(ArrayBuffers.toString(v))),
};
/*
Edm.Stream Binary data stream
Edm.Geography Abstract base type for all Geography types
Edm.GeographyPoint A point in a round-earth coordinate system
Edm.GeographyLineString Line string in a round-earth coordinate system
Edm.GeographyPolygon Polygon in a round-earth coordinate system
Edm.GeographyMultiPoint Collection of points in a round-earth coordinate system
Edm.GeographyMultiLineString Collection of line strings in a round-earth coordinate system
Edm.GeographyMultiPolygon Collection of polygons in a round-earth coordinate system
Edm.GeographyCollection Collection of arbitrary Geography values
Edm.Geometry Abstract base type for all Geometry types
Edm.GeometryPoint Point in a flat-earth coordinate system
Edm.GeometryLineString Line string in a flat-earth coordinate system
Edm.GeometryPolygon Polygon in a flat-earth coordinate system
Edm.GeometryMultiPoint Collection of points in a flat-earth coordinate system
Edm.GeometryMultiLineString Collection of line strings in a flat-earth coordinate system
Edm.GeometryMultiPolygon Collection of polygons in a flat-earth coordinate system
Edm.GeometryCollection Collection of arbitrary Geometry values
*/
class ODataEnumTypeFieldParser extends ODataAnnotatable {
name;
value;
constructor(name, field) {
super(field);
this.name = name;
this.value = field.value;
}
titleize(term) {
return (term && this.annotatedValue(term)) || this.name;
}
}
class ODataEnumTypeParser extends ODataAnnotatable {
name;
namespace;
alias;
flags;
members;
_fields;
parserOptions;
constructor(config, namespace, alias) {
super(config);
this.name = config.name;
this.namespace = namespace;
this.alias = alias;
this.flags = config.flags;
this.members = config.members;
this._fields = Object.entries(config.fields).map(([name, f]) => new ODataEnumTypeFieldParser(name, f));
}
configure({ options }) {
this.parserOptions = options;
}
isTypeOf(type) {
var names = [`${this.namespace}.${this.name}`];
if (this.alias)
names.push(`${this.alias}.${this.name}`);
return names.indexOf(type) !== -1;
}
fields(namesValue) {
if (namesValue === undefined)
return [...this._fields];
if (Array.isArray(namesValue))
return [...this._fields.filter((f) => namesValue.includes(f.value))];
if (typeof namesValue === 'number') {
return [
...this._fields.filter((f) => (this.flags && Boolean(f.value & namesValue)) ||
f.value === namesValue),
];
}
if (typeof namesValue === 'string') {
const names = namesValue.split(',').map((o) => o.trim());
return this._fields.filter((f) => names.includes(f.name));
}
return [];
}
field(nameValue) {
const field = this.fields().find((f) => f.name === nameValue || f.value === nameValue);
//Throw error if not found
//if (field === undefined)
// throw new Error(`${this.name} has no field for ${nameValue}`);
return field;
}
/**
* Map the fields of the enum type.
* @param mapper Function that maps the value to the new value
* @returns The fields mapped by the mapper
*/
mapFields(mapper) {
return this.fields().map(mapper);
}
// Deserialize
deserialize(value, options) {
// string -> number
const parserOptions = { ...this.parserOptions, ...options };
if (this.flags) {
return this.fields(value).reduce((acc, f) => acc | f.value, 0);
}
else {
return this.field(value)?.value;
}
}
// Serialize
serialize(value, options) {
// Enum are string | number
// string | number -> string
const parserOptions = { ...this.parserOptions, ...options };
if (this.flags) {
let names = this.fields(value).map((f) => f.name);
if (names.length === 0)
names = [`${value}`];
return !parserOptions?.stringAsEnum
? `${this.namespace}.${this.name}'${names.join(', ')}'`
: names.join(', ');
}
else {
let name = this.field(value)?.name;
if (name === undefined)
name = `${value}`;
return !parserOptions?.stringAsEnum
? `${this.namespace}.${this.name}'${name}'`
: name;
}
}
//Encode
encode(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
const serialized = this.serialize(value, parserOptions);
if (serialized === undefined)
return undefined;
return parserOptions?.stringAsEnum
? raw(`'${serialized}'`)
: raw(serialized);
}
// Json Schema
toJsonSchema() {
return this.flags
? {
title: this.name,
type: JsonType.array,
items: {
type: JsonType.integer,
},
}
: {
type: JsonType.integer,
enum: this._fields.map((f) => f.value),
};
}
validate(value, { method, navigation = false, } = {}) {
if (this.flags) {
let fields = this.fields(value);
return value && fields.length === 0 ? ['mismatch'] : undefined;
}
else {
return this.fields(value).length !== 1 ? ['mismatch'] : undefined;
}
}
unpack(value) {
return this.fields(value).map((f) => f.value);
}
pack(value) {
return this.fields(value).reduce((acc, v) => acc | v.value, 0);
}
}
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
};
class ODataEntityTypeKey {
name;
alias;
constructor({ name, alias }) {
this.name = name;
this.alias = alias;
}
}
class ODataReferential {
property;
referencedProperty;
constructor({ property, referencedProperty, }) {
this.property = property;
this.referencedProperty = referencedProperty;
}
}
class ODataStructuredTypeFieldParser extends ODataAnnotatable {
name;
structured;
type;
parser;
collection;
navigation;
nullable;
default;
maxLength;
precision;
scale;
referentials;
parserOptions;
constructor(name, structured, field) {
super(field);
this.name = name;
this.structured = structured;
this.type = field.type;
this.parser = NONE_PARSER;
this.referentials = (field.referentials || []).map((referential) => new ODataReferential(referential));
this.default = field.default;
this.maxLength = field.maxLength;
this.nullable = field.nullable ?? true;
this.collection = Boolean(field.collection);
this.navigation = Boolean(field.navigation);
this.precision = field.precision;
this.scale = field.scale;
}
validate(value, { method, navigation = false, } = {}) {
let errors;
if (this.collection && Array.isArray(value)) {
errors = value.map((v) => this.validate(v, { method, navigation }));
}
else if ((this.isStructuredType() &&
typeof value === 'object' &&
value !== null) ||
(this.navigation && value !== undefined)) {
errors =
this.structuredType().validate(value, { method, navigation }) ||
{};
}
else if (this.isEnumType() &&
(typeof value === 'string' || typeof value === 'number')) {
errors = this.enumType().validate(value, { method, navigation });
}
else {
// IsEdmType
const computed = this.annotatedValue(COMPUTED);
errors = [];
if (!this.nullable &&
(value === null || (value === undefined && method !== 'modify')) && // Is null or undefined without patch?
!(computed && method === 'create') // Not (Is Computed field and create) ?
) {
errors.push(`required`);
}
if (this.maxLength !== undefined &&
typeof value === 'string' &&
value.length > this.maxLength) {
errors.push(`maxlength`);
}
}
return !Types.isEmpty(errors) ? errors : undefined;
}
//#region Deserialize
parse(parser, value, options) {
const type = options !== undefined && Types.isPlainObject(value)
? ODataHelper[options?.version || DEFAULT_VERSION].type(value)
: undefined;
if (type !== undefined) {
return parser
.childParser((c) => c.isTypeOf(type))
.deserialize(value, options);
}
return parser.deserialize(value, options);
}
deserialize(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
if (this.parser instanceof ODataStructuredTypeParser) {
const parser = this.parser;
return Array.isArray(value)
? value.map((v) => this.parse(parser, v, parserOptions))
: this.parse(parser, value, parserOptions);
}
return this.parser.deserialize(value, {
field: this,
...parserOptions,
});
}
//#endregion
//#region Serialize
toJson(parser, value, options) {
const type = options !== undefined && Types.isPlainObject(value)
? ODataHelper[options?.version || DEFAULT_VERSION].type(value)
: undefined;
if (type !== undefined) {
return parser
.childParser((c) => c.isTypeOf(type))
.serialize(value, options);
}
return parser.serialize(value, options);
}
serialize(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
if (this.parser instanceof ODataStructuredTypeParser) {
const parser = this.parser;
return Array.isArray(value)
? value.map((v) => this.toJson(parser, v, parserOptions))
: this.toJson(parser, value, parserOptions);
}
return this.parser.serialize(value, {
field: this,
...parserOptions,
});
}
//#endregion
//#region Encode
encode(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
return this.parser.encode(value, {
field: this,
...parserOptions,
});
}
//#endregion
configure({ options, parserForType, }) {
this.parserOptions = options;
this.parser = parserForType(this.type);
if (this.default !== undefined) {
this.default = this.deserialize(this.default, options);
}
}
//#region Json Schema
// https://json-schema.org/
toJsonSchema(options = {}) {
let schema = this.parser instanceof ODataStructuredTypeFieldParser ||
this.parser instanceof ODataStructuredTypeParser ||
this.parser instanceof ODataEnumTypeParser
? this.parser.toJsonSchema(options)
: { title: this.name, type: JsonType.object };
switch (this.type) {
case EdmType.String:
schema.type = JsonType.string;
break;
case EdmType.Guid:
schema.type = JsonType.string;
schema.pattern =
'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$';
break;
case EdmType.Binary:
schema.type = JsonType.string;
schema.contentEncoding = 'base64';
break;
case EdmType.Date:
schema.type = JsonType.string;
schema.format = 'date';
break;
case EdmType.TimeOfDay:
schema.type = JsonType.string;
schema.format = 'time';
break;
case EdmType.DateTimeOffset:
schema.type = JsonType.string;
schema.format = 'date-time';
break;
case EdmType.Duration:
schema.type = JsonType.string;
schema.format = 'duration';
break;
case EdmType.Byte:
case EdmType.SByte:
case EdmType.Int16:
case EdmType.Int32:
case EdmType.Int64:
//TODO: Range
schema.type = JsonType.integer;
break;
case EdmType.Single:
case EdmType.Double:
case EdmType.Decimal:
schema.type = JsonType.number;
break;
case EdmType.Boolean:
schema.type = JsonType.boolean;
break;
}
if (this.maxLength)
schema.maxLength = this.maxLength;
if (this.default)
schema.default = this.default;
if (this.nullable)
schema.type = [schema.type, JsonType.null];
if (this.collection)
schema = {
type: JsonType.array,
items: schema,
additionalItems: false,
};
return schema;
}
//#endregion
isKey() {
return this.structured
.keys({ include_parents: true })
.some((k) => k.name === this.name);
}
hasReferentials() {
return this.referentials.length !== 0;
}
isEdmType() {
return this.type.startsWith('Edm.');
}
isEnumType() {
return this.parser instanceof ODataEnumTypeParser;
}
enumType() {
if (!this.isEnumType())
throw new Error('Field are not EnumType');
return this.parser;
}
isStructuredType() {
return this.parser instanceof ODataStructuredTypeParser;
}
structuredType() {
if (!this.isStructuredType())
throw new Error('Field are not StrucuturedType');
return this.parser;
}
field(name) {
if (this.isStructuredType())
return this.parser.field(name);
throw new Error(`The field ${this.name} is not related to a StructuredType`);
}
}
class ODataStructuredTypeParser extends ODataAnnotatable {
name;
namespace;
open;
children = [];
alias;
base;
parent;
_keys;
_fields = [];
parserOptions;
constructor(config, namespace, alias) {
super(config);
this.name = config.name;
this.base = config.base;
this.open = config.open || false;
this.namespace = namespace;
this.alias = alias;
if (Array.isArray(config.keys))
this._keys = config.keys.map((key) => new ODataEntityTypeKey(key));
Object.entries((config.fields ?? {})).forEach(([name, config]) => this.addField(name, config));
}
addField(name, config) {
const field = new ODataStructuredTypeFieldParser(name, this, config);
this._fields.push(field);
return field;
}
/**
* Create a nicer looking title.
* Titleize is meant for creating pretty output.
* @param term The term of the annotation to find.
* @returns The titleized string.
*/
titleize(term) {
return (term && this.annotatedValue(term)) || Strings.titleCase(this.name);
}
isTypeOf(type) {
var names = [`${this.namespace}.${this.name}`];
if (this.alias)
names.push(`${this.alias}.${this.name}`);
return names.indexOf(type) !== -1;
}
isSubtypeOf(type) {
if (this.isTypeOf(type))
return true;
if (this.parent)
return this.parent.isSubtypeOf(type);
return false;
}
isSupertypeOf(type) {
return (this.isTypeOf(type) || this.children.some((c) => c.isSupertypeOf(type)));
}
isOpenType() {
return this.open;
}
findChildParser(predicate) {
if (predicate(this))
return this;
let match;
for (let ch of this.children) {
match = ch.findChildParser(predicate);
if (match !== undefined)
break;
}
return match;
}
childParser(predicate) {
return this.findChildParser(predicate) || NONE_PARSER;
}
// Deserialize
deserialize(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
const fields = this.fields({
include_navigation: true,
include_parents: true,
}).filter((f) => f.name in value &&
value[f.name] !== undefined &&
value[f.name] !== null);
return {
...value,
...fields.reduce((acc, f) => ({
...acc,
[f.name]: f.deserialize(value[f.name], parserOptions),
}), {}),
};
}
// Serialize
serialize(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
const fields = this.fields({
include_navigation: true,
include_parents: true,
}).filter((f) => f.name in value &&
value[f.name] !== undefined &&
value[f.name] !== null);
return {
...value,
...fields.reduce((acc, f) => ({
...acc,
[f.name]: f.serialize(value[f.name], parserOptions),
}), {}),
};
}
// Encode
encode(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
return raw(JSON.stringify(this.serialize(value, parserOptions)));
}
configure({ options, parserForType, }) {
this.parserOptions = options;
if (this.base) {
const parent = parserForType(this.base);
parent.children.push(this);
this.parent = parent;
}
this._fields.forEach((f) => f.configure({ options, parserForType }));
}
/**
* Returns all fields of the structured type.
* @param include_navigation Include navigation properties in the result.
* @param include_parents Include the parent types in the result.
* @returns All fields of the structured type.
*/
fields({ include_navigation, include_parents, }) {
return [
...(include_parents && this.parent !== undefined
? this.parent.fields({ include_parents, include_navigation })
: []),
...this._fields.filter((field) => include_navigation || !field.navigation),
];
}
/**
* Returns the keys of the structured type.
* @param include_parents Include the parent fields
* @returns The keys of the structured type
*/
keys({ include_parents, }) {
return [
...(include_parents && this.parent !== undefined
? this.parent.keys({ include_parents })
: []),
...(this._keys || []),
];
}
isEntityType() {
return (this._keys !== undefined ||
(this.parent !== undefined && this.parent.isEntityType()));
}
isComplexType() {
return !this.isEntityType();
}
/**
* Find the field parser for the given field name.
* @param name Name of the field
* @returns The field parser
*/
field(name) {
let field = this.fields({
include_parents: true,
include_navigation: true,
}).find((field) => field.name === name);
//Throw error if not found
if (field === undefined)
throw new Error(`${this.name} has no field named ${String(name)}`);
return field;
}
/**
* Picks the fields from attributes.
* @param attrs
* @param include_parents Include the parent fields
* @param include_navigation Include the navigation fields
* @param include_etag Include the etag field
* @returns The picked fields
*/
pick(attrs, { include_id = true, include_key = true, include_parents = true, include_navigation = false, include_computed = false, include_etag = true, options, } = {}) {
const parserOptions = options ?? this.parserOptions;
let fields = this.fields({ include_parents, include_navigation });
if (!include_key) {
fields = fields.filter((f) => !f.isKey());
}
if (!include_computed) {
fields = fields.filter((f) => !f.annotatedValue(COMPUTED) || (f.isKey() && include_key));
}
return Object.keys(attrs)
.filter((key) => fields.some((f) => f.name === key) ||
(key ==
ODataHelper[parserOptions?.version || DEFAULT_VERSION].ODATA_ETAG &&
include_etag) ||
(key ==
ODataHelper[parserOptions?.version || DEFAULT_VERSION].ODATA_ID &&
include_id))
.reduce((acc, key) => Object.assign(acc, { [key]: attrs[key] }), {});
}
resolveKey(value, { resolve = true, single = true, } = {}) {
const keyTypes = this.keys({ include_parents: true });
const key = new Map();
for (var kt of keyTypes) {
let v = value;
let structured = this;
let field;
for (let name of kt.name.split('/')) {
if (structured === undefined)
break;
field = structured
.fields({ include_navigation: false, include_parents: true })
.find((f) => f.name === name);
if (field !== undefined) {
v = Types.isPlainObject(v) ? v[field.name] : v;
structured = field.isStructuredType()
? field.structuredType()
: undefined;
}
}
if (field !== undefined && v !== undefined) {
key.set(kt.alias || field.name, field.encode(v));
}
}
if (key.size === 0)
return undefined;
return resolve
? Objects.resolveKey(key, { single })
: Object.fromEntries(key);
}
defaults() {
let fields = this.fields({
include_navigation: false,
include_parents: true,
}).filter((f) => f.default !== undefined || f.isStructuredType());
return {
...fields.reduce((acc, f) => {
let value = f.isStructuredType()
? f.structuredType().defaults()
: f.default;
return Types.isEmpty(value) ? acc : { ...acc, [f.name]: value };
}, {}),
};
}
// Json Schema
toJsonSchema(options = {}) {
let schema = {
$schema: 'http://json-schema.org/draft-07/schema#',
$id: `${this.namespace}.${this.name}`,
title: this.titleize(DESCRIPTION),
description: this.annotatedValue(LONG_DESCRIPTION),
type: JsonType.object,
properties: {},
required: [],
};
const fields = this.fields({
include_navigation: true,
include_parents: true,
}).filter((f) => (!f.navigation || (options.expand && f.name in options.expand)) &&
(!options.select || options.select.indexOf(f.name) !== -1));
schema.properties = Object.assign({}, schema.properties, fields
.map((f) => {
let expand = options.expand && f.name in options.expand
? options.expand[f.name]
: undefined;
let schema = f.toJsonSchema(expand);
if (options.custom && f.name in options.custom)
schema = options.custom[f.name](schema, f);
return { [f.name]: schema };
})
.reduce((acc, v) => Object.assign(acc, v), {}));
schema.required = [
...schema.required,
...fields
.filter((f) => options.required && f.name in options.required
? options.required[f.name]
: !f.nullable)
.map((f) => f.name),
];
return schema;
}
validate(attrs, { method, navigation = false, } = {}) {
const errors = {};
const fields = this.fields({
include_navigation: true,
include_parents: true,
}).filter((f) => !f.navigation || navigation);
for (var field of fields) {
const value = attrs[field.name];
const errs = field.validate(value, { method, navigation });
if (errs !== undefined) {
errors[field.name] = errs;
}
}
return !Types.isEmpty(errors) ? errors : undefined;
}
}
class ODataParameterParser {
name;
type;
parser;
collection;
nullable;
parserOptions;
constructor(name, parameter) {
this.name = name;
this.type = parameter.type;
this.parser = NONE_PARSER;
this.nullable = parameter.nullable;
this.collection = parameter.collection;
}
serialize(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
return Array.isArray(value)
? value.map((v) => this.parser.serialize(v, parserOptions))
: this.parser.serialize(value, parserOptions);
}
//Encode
encode(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
return Array.isArray(value)
? value.map((v) => this.parser.encode(v, parserOptions))
: this.parser.encode(value, parserOptions);
}
configure({ options, parserForType, }) {
this.parserOptions = options;
this.parser = parserForType(this.type);
}
isEdmType() {
return this.type.startsWith('Edm.');
}
isEnumType() {
return this.parser instanceof ODataEnumTypeParser;
}
enumType() {
if (!this.isEnumType())
throw new Error('Field are not EnumType');
return this.parser;
}
isStructuredType() {
return this.parser instanceof ODataStructuredTypeParser;
}
structuredType() {
if (!this.isStructuredType())
throw new Error('Field are not StrucuturedType');
return this.parser;
}
field(name) {
if (this.isStructuredType())
return this.parser.field(name);
throw new Error(`The field ${this.name} is not related to a StructuredType`);
}
}
class ODataCallableParser {
name;
namespace;
alias;
return;
parser;
parameters;
parserOptions;
constructor(config, namespace, alias) {
this.name = config.name;
this.namespace = namespace;
this.alias = alias;
this.return = config.return;
this.parser = NONE_PARSER;
this.parameters = Object.entries(config.parameters || []).map(([name, p]) => new ODataParameterParser(name, p));
}
isTypeOf(type) {
var names = [`${this.namespace}.${this.name}`];
if (this.alias)
names.push(`${this.alias}.${this.name}`);
return names.indexOf(type) !== -1;
}
// Deserialize
deserialize(value, options) {
const parserOptions = { ...this.parserOptions, ...options };
return this.parser.deserialize(value, parserOptions);
}
// Serialize
serialize(params, options) {
const parserOptions = { ...this.parserOptions, ...options };
const parameters = this.parameters
.filter((p) => p.name !== CALLABLE_BINDING_PARAMETER)
.filter((p) => p.name in params && params[p.name] !== undefined);
return parameters.reduce((acc, p) => ({
...acc,
[p.name]: p.serialize(params[p.name], parserOptions),
}), {});
}
//Encode
encode(params, options) {
const parserOptions = { ...this.parserOptions, ...options };
const parameters = this.parameters
.filter((p) => p.name !== CALLABLE_BINDING_PARAMETER)
.filter((p) => p.name in params && params[p.name] !== undefined);
return parameters.reduce((acc, p) => ({
...acc,
[p.name]: p.encode(params[p.name], parserOptions),
}), {});
}
configure({ options, parserForType, }) {
this.parserOptions = options;
if (this.return)
this.parser = parserForType(this.return.type) ?? NONE_PARSER;
this.parameters.forEach((p) => p.configure({ options, parserForType }));
}
binding() {
return this.parameters.find((p) => p.name === CALLABLE_BINDING_PARAMETER);
}
returnType() {
return this.return?.type;
}
}
class ODataCallable extends ODataParserSchemaElement {
entitySetPath;
bound;
composable;
constructor(config, schema) {
super(config, schema, new ODataCallableParser(config, schema.namespace, schema.alias));
this.entitySetPath = config.entitySetPath;
this.bound = config.bound;
this.composable = config.composable;
}
path() {
let path;
if (this.entitySetPath)
path = this.entitySetPath;
else if (this.bound)
path = `${this.schema.namespace}.${this.name}`;
else
path = this.parser.return
? this.api.findEntitySet(this.parser.return.type)?.name || this.name
: this.name;
return path;
}
configure({ options }) {
this.parser.configure({
options,
parserForType: (t) => this.api.parserForType(t),
});
}
/**
* Deseialize the given value from the callable.
* @param value Value to deserialize
* @param options Options for deserialization
* @returns Deserialized value
*/
deserialize(value, options) {
return this.parser.deserialize(value, options);
}
/**
* Serialize the given value for the callable.
* @param value Value to serialize
* @param options Options for serialization
* @returns Serialized value
*/
serialize(value, options) {
return this.parser.serialize(value, options);
}
/**
* Encode the given value for the callable.
* @param value Value to encode
* @param options Options for encoding
* @returns Encoded value
*/
encode(value, options) {
return this.parser.encode(value, options);
}
/**
* Returns the binding parameter of the callable.
* @returns The binding parameter of the callable.
*/
binding() {
return this.parser.binding();
}
returnType() {
return this.parser.returnType();
}
}
class ODataEntitySet extends ODataSchemaElement {
entityType;
service;
constructor(config, schema) {
super(config, schema);
this.entityType = config.entityType;
this.service = config.service;
}
}
class ODataSingleton extends ODataSchemaElement {
singletonType;
service;
constructor(config, schema) {
super(config, schema);
this.singletonType = config.type;
this.service = config.service;
}
}
class ODataEntityContainer extends ODataSchemaElement {
entitySets;
singletons;
constructor(config, schema) {
super(config, schema);
this.entitySets = (config.entitySets ?? []).map((config) => new ODataEntitySet(config, schema));
this.singletons = (config.singletons ?? []).map((config) => new ODataSingleton(config, schema));
}
}
class ODataEnumType extends ODataParserSchemaElement {
members;
constructor(config, schema) {
super(config, schema, new ODataEnumTypeParser(config, schema.namespace, schema.alias));
this.members = config.members;
}
configure({ options }) {
this.parser.configure({ options });
}
/**
* Returns the fields of the enum type.
* @returns The fields of the enum type.
*/
fields(namesValue) {
return this.parser.fields(namesValue);
}
/**
* Find a field by name or value.
* @param enu The name or value of the field
* @returns The field with the given name or value
*/
field(nameValue) {
return this.parser.field(nameValue);
}
/**
* Map the fields of the enum type.
* @param mapper Function that maps the value to the new value
* @returns The fields mapped by the mapper
*/
mapFields(mapper) {
return this.parser.mapFields(mapper);
}
/**
* Deseialize the given value from the enum type.
* @param value Value to deserialize
* @param options Options for deserialization
* @returns Deserialized value
*/
deserialize(value, options) {
return this.parser.deserialize(value, options);
}
/**
* Serialize the given value for the enum type.
* @param value Value to serialize
* @param options Options for serialization
* @returns Serialized value
*/
serialize(value, options) {
return this.parser.serialize(value, options);
}
/**
* Encode the given value for the enum type.
* @param value Value to encode
* @param options Options for encoding
* @returns Encoded value
*/
encode(value, options) {
return this.parser.encode(value, options);
}
unpack(value) {
return this.parser.unpack(value);
}
pack(value) {
return this.parser.pack(value);
}
}
class ODataStructuredType extends ODataParserSchemaElement {
base;
parent;
children = [];
model;
collection;
constructor(config, schema) {
super(config, schema, new ODataStructuredTypeParser(config, schema.namespace, schema.alias));
this.base = config.base;
this.model = config.model;
this.collection = config.collection;
}
configure({ options }) {
if (this.base) {
const parent = this.api.findStructuredType(this.base);
parent.children.push(this);
this.parent = parent;
}
this.parser.configure({
options,
parserForType: (t) => this.api.parserForType(t),
});
if (this.model !== undefined) {
this.model.meta = this.api.optionsForType(this.type(), {
config: this.model.options,
structuredType: this,
});
if (this.model.meta !== undefined) {
// Configure
this.model.meta.configure({ options });
}
if (this.collection !== undefined) {
this.collection.model = this.model;
}
}
}
/**
* Returns a boolean indicating if the structured type is a subtype of the given type.
* @param type String representation of the type
* @returns True if the callable is type of the given type
*/
isSubtypeOf(schema) {
return (super.isSubtypeOf(schema) ||
(this.parent !== undefined && this.parent.isSubtypeOf(schema)));
}
/**
* Returns a boolean indicating if the structured type is a supertype of the given type.
* @param type String representation of the type
* @returns True if the callable is type of the given type
*/
isSupertypeOf(schema) {
return (super.isSupertypeOf(schema) ||
this.children.some((c) => c.isSupertypeOf(schema)));
}
/**
* Returns a boolean indicating if the structured type has a simple key.
* @returns True if the structured type has a simple key
*/
isSimpleKey() {
return this.keys().length === 1;
}
/**
* Returns a boolean indicating if the structured type has a compound key.
* @returns True if the structured type has a compound key.
*/
isCompoundKey() {
return this.keys().length > 1;
}
isOpenType() {
return this.parser.isOpenType();
}
isEntityType() {
return this.parser.isEntityType();
}
isComplexType() {
return this.parser.isComplexType();
}
/**
* Find the field parser for the given field name.
* @param name Name of the field
* @returns The field parser
*/
field(name) {
return this.parser.field(name);
}
addField(name, config) {
return this.parser.addField(name, config);
}
/**
* Find a parent schema of the structured type.
* @param predicate Function for evaluate the schemas in the hierarchy.
* @returns The schema that matches the predicate.
*/
findParentSchema(predicate) {
if (predicate(this))
return this;
if (this.parent === undefined)
return undefined;
return this.parent.findParentSchema(predicate);
}
findChildSchema(predicate) {
if (predicate(this))
return this;
let match;
for (let ch of this.children) {
match = ch.findChildSchema(predicate);
if (match !== undefined)
break;
}
return match;
}
/**
* Find a parent schema of the structured type for the given field.
* @param field Field that belongs to the structured type
* @returns The schema of the field
*/
findParentSchemaForField(field) {
return this.findParentSchema((p) => p
.fields({ include_parents: false, include_navigation: true })
.find((f) => f === field) !== undefined);
}
/**
* Picks the fields from attributes.
* @param attrs
* @param include_parents Include the parent fields
* @param include_navigation Include the navigation fields
* @param include_etag Include the etag field
* @returns The picked fields
*/
pick(attrs, { include_id = true, include_key = true, include_parents = true, include_navigation = false, include_computed = false, include_etag = true, } = {}) {
return this.parser.pick(attrs, {
include_id,
include_key,
include_etag,
include_navigation,
include_computed,
include_parents,
options: this.api.options,
});
}
/**
* Deseialize the given value from the structured type.
* @param value Value to deserialize
* @param options Options for deserialization
* @returns Deserialized value
*/
deserialize(value, options) {
return this.parser.deserialize(value, options);
}
/**
* Serialize the given value for the structured type.
* @param value Value to serialize
* @param options Options for serialization
* @returns Serialized value
*/
serialize(value, options) {
return this.parser.serialize(value, options);
}
/**
* Encode the given value for the structured type.
* @param value Value to encode
* @param options Options for encoding
* @returns Encoded value
*/
encode(value, options) {
return this.parser.encode(value, options);
}
/**
* Returns all fields of the structured type.
* @param include_navigation Include navigation properties in the result.
* @param include_parents Include the parent types in the result.
* @returns All fields of the structured type.
*/
fields({ include_navigation, include_parents, }) {
return this.parser.fields({ include_navigation, include_parents });
}
/**
* Returns the keys of the structured type.
* @param include_parents Include the parent fields
* @returns The keys of the structured type
*/
keys({ include_parents = true, } = {}) {
return this.parser.keys({ include_parents });
}
/**
* Resolve the key of the structured type for the given value.
* @param attrs Attributes of the value
* @returns Resolved key
*/
resolveKey(attrs) {
return this.parser.resolveKey(attrs);
}
/**
* Returns the defaults values for the structured type.
* @returns Default values for the structured type
*/
defaults() {
return this.parser.defaults();
}
/**
* Convert the structured type to json schema
* @param options Options for json schema
* @returns Json Schema
*/
toJsonSchema(options = {}) {
return this.parser.toJsonSchema(options);
}
/**
* Validate the given value against the structured type.
* @param attrs Attributes of the value
* @param method Method to use for the process validation
* @returns Object with the errors
*/
validate(attrs, { method, navigation = false, } = {}) {
return this.parser.validate(attrs, { method, navigation });
}
}
class ODataSchema extends ODataAnnotatable {
api;
namespace;
alias;
enums;
entities;
callables;
containers;
constructor(config, api) {
super(config);
this.api = api;
this.namespace = config.namespace;
this.alias = config.alias;
this.enums = (config.enums ?? []).map((config) => new ODataEnumType(config, this));
this.entities = (config.entities ?? []).map((config) => new ODataStructuredType(config, this));
this.callables = OData.mergeCallableParameters(config.callables ?? []).map((config) => new ODataCallable(config, this));
this.containers = (config.containers ?? []).map((config) => new ODataEntityContainer(config, this));
}
isNamespaceOf(type) {
return (type.startsWith(this.namespace) ??
(this.alias && type.startsWith(this.alias)));
}
get entitySets() {
return this.containers.reduce((acc, container) => [...acc, ...container.entitySets], []);
}
get singletons() {
return this.containers.reduce((acc, container) => [...acc, ...container.singletons], []);
}
//#region Find for Type
createStructuredType(config) {
const entity = new ODataStructuredType(config, this);
entity.configure({ options: this.api.options.parserOptions });
this.entities.push(entity);
return entity;
}
//#endregion
configure({ options }) {
// Configure Enums
this.enums.forEach((enu) => enu.configure({ options }));
// Configure Entities
this.entities.forEach((structured) => structured.configure({ options }));
// Configure callables
this.callables.forEach((callable) => callable.configure({ options }));
}
}
class CountField {
field;
values;
constructor(field, values = {}) {
this.field = field;
this.values = values;
}
get [Symbol.toStringTag]() {
return 'CountField';
}
toJson() {
return {
field: this.field.toJson(),
};
}
render({ aliases, escape, prefix, parser, options, }) {
const params = [
QueryOption.filter,
QueryOption.search,
]
.filter((key) => !Types.isEmpty(this.values[key]))
.reduce((acc, key) => {
let value = this.values[key];
if (Types.rawType(value).endsWith('Expression')) {
value = value.render({
aliases,
prefix,
escape,
parser: resolve([this.field], parser),
options,
});
}
return Object.assign(acc, { [key]: value });
}, {});
let count = `${render(this.field, {
aliases,
escape,
prefix,
parser,
options,
})}/$count`;
if (!Types.isEmpty(params)) {
count = `${count}(${Object.keys(params)
.map((key) => `$${key}=${params[key]}`)
.join(';')})`;
}
return count;
}
filter(opts) {
return this.option(QueryOption.filter, FilterExpression.factory(opts, this.values[QueryOption.filter]));
}
clone() {
const values = Object.keys(this.values).reduce((acc, key) => Object.assign(acc, { [key]: Objects.clone(this.values[key]) }), {});
return new CountField(this.field.clone(), values);
}
resolve(parser) {
return this.field.resolve(parser);
}
// Option Handler
option(name, opts) {
if (opts !== undefined)
this.values[name] = opts;
return this.values[name];
}
}
class CountExpression extends Expression {
constructor({ children, } = {}) {
super({ children });
}
get [Symbol.toStringTag]() {
return 'CountExpression';
}
static factory(opts, current) {
return opts({
t: FieldFactory(),
e: () => new CountExpression(),
}, current);
}
_add(node) {
this._children.push(node);
return this;
}
toJson() {
const json = super.toJson();
return Object.assign(json, {});
}
static fromJson(json) {
return new CountExpression({
children: json['children'].map((c) => RenderableFactory(c)),
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
let content = this._children
.map((n) => n.render({ aliases, escape, prefix, parser, options }))
.join(`,`);
return content;
}
clone() {
return new CountExpression({
children: this._children.map((c) => c.clone()),
});
}
field(field, opts) {
let countField = new CountField(field);
if (opts !== undefined)
opts({
t: FieldFactory(),
f: countField,
});
return this._add(countField);
}
}
class FilterExpression extends Expression {
_connector;
_negated;
constructor({ children, connector, negated, } = {}) {
super({ children });
this._connector = connector || 'and';
this._negated = negated || false;
}
get [Symbol.toStringTag]() {
return 'FilterExpression';
}
static factory(opts, current) {
return opts({
e: (connector = 'and') => new FilterExpression({ connector }),
t: FieldFactory(),
o: operators,
f: functions,
}, current);
}
toJson() {
const json = super.toJson();
return Object.assign(json, {
connector: this._connector,
negated: this._negated,
});
}
static fromJson(json) {
return new FilterExpression({
children: json['children'].map((c) => RenderableFactory(c)),
connector: json['connector'],
negated: json['negated'],
});
}
connector() {
return this._connector;
}
negated() {
return this._negated;
}
render({ aliases, escape, prefix, parser, options, } = {}) {
let content = this._children
.map((n) => n.render({ aliases, escape, prefix, parser, options }))
.join(` ${this._connector} `);
if (this._negated) {
content = `not (${content})`;
}
return content;
}
clone() {
return new FilterExpression({
children: this._children.map((c) => c.clone()),
connector: this._connector,
negated: this._negated,
});
}
_add(node, connector) {
if (connector !== undefined && this._connector !== connector) {
let children = [];
if (this._children.length > 0) {
if (this._children.length === 1) {
children = [...this._children];
}
else {
let exp = new FilterExpression({
children: this._children,
connector: this._connector,
negated: this._negated,
});
if (exp.length() > 1) {
children.push(syntax.group(exp));
}
else {
children.push(exp);
}
}
}
if (node instanceof FilterExpression &&
(node.connector() === connector || node.length() === 1)) {
children = [...children, ...node.children()];
}
else {
children.push(syntax.group(node));
}
this._connector = connector;
this._children = children;
}
else if (node instanceof FilterExpression &&
!node.negated() &&
(node.connector() === connector || node.length() === 1)) {
this._children = [...this._children, ...node.children()];
}
else {
this._children.push(node instanceof FilterExpression && !node.negated()
? syntax.group(node)
: node);
}
return this;
}
or(exp) {
return this._add(exp, 'or');
}
and(exp) {
return this._add(exp, 'and');
}
not(exp) {
const notExp = new FilterExpression({
children: exp.children(),
connector: exp.connector(),
negated: true,
});
return this._add(notExp, this._connector);
}
eq(left, right, normalize = 'right') {
return this._add(operators.eq(left, right, normalize));
}
ne(left, right, normalize = 'right') {
return this._add(operators.ne(left, right, normalize));
}
gt(left, right, normalize = 'right') {
return this._add(operators.gt(left, right, normalize));
}
ge(left, right, normalize = 'right') {
return this._add(operators.ge(left, right, normalize));
}
lt(left, right, normalize = 'right') {
return this._add(operators.lt(left, right, normalize));
}
le(left, right, normalize = 'right') {
return this._add(operators.le(left, right, normalize));
}
has(left, right, normalize = 'right') {
return this._add(operators.has(left, right, normalize));
}
in(left, right, normalize = 'right') {
return this._add(operators.in(left, right, normalize));
}
contains(left, right, normalize = 'right') {
return this._add(functions.contains(left, right, normalize));
}
startsWith(left, right, normalize = 'right') {
return this._add(functions.startsWith(left, right, normalize));
}
endsWith(left, right, normalize = 'right') {
return this._add(functions.endsWith(left, right, normalize));
}
any(left, opts, alias) {
let exp = undefined;
if (opts !== undefined) {
exp = opts({
t: FieldFactory(),
o: operators,
f: functions,
e: (connector = 'and') => new FilterExpression({ connector }),
});
}
return this._add(syntax.any(left, exp, alias));
}
all(left, opts, alias) {
let exp = undefined;
if (opts !== undefined) {
exp = opts({
t: FieldFactory(),
o: operators,
f: functions,
e: (connector = 'and') => new FilterExpression({ connector }),
});
}
return this._add(syntax.all(left, exp, alias));
}
count(left, opts) {
return this._add(new CountExpression().field(left, opts));
}
isof(left, type) {
return this._add(syntax.isof(left, type));
}
}
class SearchTerm {
value;
constructor(value) {
this.value = value;
}
get [Symbol.toStringTag]() {
return 'SearchTerm';
}
toJson() {
return {
$type: Types.rawType(this),
value: this.value,
};
}
render({ aliases, escape, prefix, parser, options, }) {
return `${render(this.value, {
aliases,
escape,
prefix,
parser,
options,
})}`;
}
clone() {
return new SearchTerm(this.value);
}
resolve(parser) {
return parser;
}
}
class SearchExpression extends Expression {
_connector;
_negated;
constructor({ children, connector, negated, } = {}) {
super({ children });
this._connector = connector || 'AND';
this._negated = negated || false;
}
get [Symbol.toStringTag]() {
return 'SearchExpression';
}
static factory(opts, current) {
return opts({
e: (connector = 'AND') => new SearchExpression({ connector }),
}, current);
}
_add(node, connector) {
if (connector !== undefined && this._connector !== connector) {
let children = [];
if (this._children.length > 0) {
if (this._children.length === 1) {
children = [...this._children];
}
else {
let exp = new SearchExpression({
children: this._children,
connector: this._connector,
negated: this._negated,
});
if (exp.length() > 1) {
children.push(syntax.group(exp));
}
else {
children.push(exp);
}
}
}
if (node instanceof SearchExpression &&
(node.connector() === connector || node.length() === 1)) {
children = [...children, ...node.children()];
}
else {
children.push(syntax.group(node));
}
this._connector = connector;
this._children = children;
}
else if (node instanceof SearchExpression &&
!node.negated() &&
(node.connector() === connector || node.length() === 1)) {
this._children = [...this._children, ...node.children()];
}
else {
this._children.push(node instanceof SearchExpression && !node.negated()
? syntax.group(node)
: node);
}
return this;
}
render({ aliases, escape, prefix, parser, options, } = {}) {
let content = this._children
.map((n) => n.render({ aliases, escape, prefix, parser, options }))
.join(` ${this._connector} `);
return content;
}
clone() {
return new SearchExpression({
children: this._children.map((c) => c.clone()),
connector: this._connector,
negated: this._negated,
});
}
toJson() {
const json = super.toJson();
return Object.assign(json, {
connector: this._connector,
negated: this._negated,
});
}
static fromJson(json) {
return new SearchExpression({
children: json['children'].map((c) => RenderableFactory(c)),
connector: json['connector'],
negated: json['negated'],
});
}
connector() {
return this._connector;
}
negated() {
return this._negated;
}
or(exp) {
return this._add(exp, 'OR');
}
and(exp) {
return this._add(exp, 'AND');
}
not(exp) {
const notExp = new SearchExpression({
children: exp.children(),
connector: exp.connector(),
negated: true,
});
return this._add(notExp, this._connector);
}
term(value) {
return this._add(new SearchTerm(value));
}
}
class GroupByTransformations extends Expression {
methods;
aliases;
constructor({ children, methods, aliases, } = {}) {
super({ children });
this.methods = methods || [];
this.aliases = aliases || [];
}
get [Symbol.toStringTag]() {
return 'GroupByTransformations';
}
toJson() {
const json = super.toJson();
return Object.assign(json, {
methods: this.methods,
aliases: this.aliases,
});
}
static fromJson(json) {
return new GroupByTransformations({
children: json['children'].map((c) => typeof c !== 'string' ? RenderableFactory(c) : c),
methods: json['methods'],
aliases: json['aliases'],
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
const children = this._children.map((n) => typeof n !== 'string'
? n.render({ aliases, escape, prefix, parser, options })
: n);
return `aggregate(${children
.map((child, index) => !child
? `${this.methods[index]} as ${this.aliases[index]}`
: `${child} with ${this.methods[index]} as ${this.aliases[index]}`)
.join(',')})`;
}
clone() {
return new GroupByTransformations({
children: this._children.map((c) => typeof c !== 'string' ? c.clone() : c),
methods: this.methods,
aliases: this.aliases,
});
}
_add(node, method, alias) {
this._children.push(node);
this.methods.push(method);
this.aliases.push(alias);
return this;
}
aggregate(value, method, alias) {
return this._add(value, method, alias);
}
sum(value, alias) {
return this.aggregate(value, 'sum', alias);
}
min(value, alias) {
return this.aggregate(value, 'min', alias);
}
max(value, alias) {
return this.aggregate(value, 'max', alias);
}
average(value, alias) {
return this.aggregate(value, 'average', alias);
}
countdistinct(value, alias) {
return this.aggregate(value, 'countdistinct', alias);
}
count(alias) {
return this.aggregate('', '$count', alias);
}
}
class ApplyExpression extends Expression {
constructor({ children, } = {}) {
super({ children });
}
get [Symbol.toStringTag]() {
return 'ApplyExpression';
}
static factory(opts, current) {
return opts({
t: FieldFactory(),
e: () => new ApplyExpression(),
}, current);
}
toJson() {
const json = super.toJson();
return Object.assign(json, {});
}
static fromJson(json) {
return new ApplyExpression({
children: json['children'].map((c) => RenderableFactory(c)),
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
return this._children
.map((n) => n.render({ aliases, escape, prefix, parser, options }))
.join('/');
}
clone() {
return new ApplyExpression({
children: this._children.map((c) => c.clone()),
});
}
_add(node) {
this._children.push(node);
return this;
}
aggregate(value, method, alias) {
return this._add(syntax.aggregate(value, method, alias));
}
//topcount
topCount(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//topsum
topSum(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//toppercent
topPercent(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//bottomcount
bottomCount(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//bottomsum
bottomSum(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//bottompercent
bottomPercent(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
identity(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
concat(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//groupby
groupBy(props, opts) {
let properties = props({ rollup: (e) => syntax.rollup(e) });
properties = Array.isArray(properties) ? properties : [properties];
const transformations = opts !== undefined ? opts(new GroupByTransformations()) : undefined;
return this._add(syntax.groupby(properties, transformations));
}
//filter
filter(opts) {
const exp = opts({
t: FieldFactory(),
o: operators,
f: functions,
e: (connector = 'and') => new FilterExpression({ connector }),
});
return this._add(transformations.filter(exp));
}
/*
orderBy(
opts: (
builder: OrderByExpressionBuilder<T>,
current?: OrderByExpression<T>
) => OrderByExpression<T>,
current?: OrderByExpression<T>
) {
const exp = opts(
{
t: FieldFactory<Required<T>>(),
e: () => new OrderByExpression<T>(),
},
current
) as OrderByExpression<T>;
return this._add(transformations.orderby(exp));
}
*/
//expand
expand(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
//search
search(opts) {
const exp = opts({
t: FieldFactory(),
o: operators,
f: functions,
e: (connector = 'AND') => new SearchExpression({ connector }),
});
return this._add(transformations.search(exp));
}
//compute
compute(opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(node);
}
}
class OrderByField {
field;
order;
constructor(field, order) {
this.field = field;
this.order = order;
}
get [Symbol.toStringTag]() {
return 'OrderByField';
}
toJson() {
return {
$type: Types.rawType(this),
field: this.field.toJson(),
order: this.order,
};
}
render({ aliases, escape, prefix, parser, options, }) {
return `${render(this.field, {
aliases,
escape,
prefix,
parser,
options,
})} ${this.order}`;
}
clone() {
return new OrderByField(typeof this.field !== 'string' ? this.field.clone() : this.field, this.order);
}
resolve(parser) {
return parser;
}
}
class OrderByExpression extends Expression {
constructor({ children, } = {}) {
super({ children });
}
get [Symbol.toStringTag]() {
return 'OrderByExpression';
}
static factory(opts, current) {
return opts({
t: FieldFactory(),
e: () => new OrderByExpression(),
}, current);
}
_add(node) {
this._children.push(node);
return this;
}
toJson() {
const json = super.toJson();
return Object.assign(json, {});
}
static fromJson(json) {
return new OrderByExpression({
children: json['children'].map((c) => RenderableFactory(c)),
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
let content = this._children
.map((n) => n.render({ aliases, escape, prefix, parser, options }))
.join(`,`);
return content;
}
clone() {
return new OrderByExpression({
children: this._children.map((c) => c.clone()),
});
}
ascending(field) {
return this._add(new OrderByField(field, 'asc'));
}
descending(field) {
return this._add(new OrderByField(field, 'desc'));
}
}
class SelectExpression extends Expression {
constructor({ children, } = {}) {
super({ children });
}
get [Symbol.toStringTag]() {
return 'SelectExpression';
}
static factory(opts, current) {
return opts({
t: FieldFactory(),
e: () => new SelectExpression(),
}, current);
}
toJson() {
const json = super.toJson();
return Object.assign(json, {});
}
static fromJson(json) {
return new SelectExpression({
children: json['children'].map((c) => RenderableFactory(c)),
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
return this._children
.map((n) => typeof n === 'string'
? n
: n.render({ aliases, escape, prefix, parser, options }))
.join(',');
}
clone() {
return new SelectExpression({
children: this._children.map((c) => c.clone()),
});
}
_add(node) {
this._children.push(node);
return this;
}
field(field) {
return this._add(field);
}
fields(...fields) {
fields.forEach((f) => this._add(f));
return this;
}
}
class ExpandField {
field;
values;
constructor(field, values = {}) {
this.field = field;
this.values = values;
}
get [Symbol.toStringTag]() {
return 'ExpandField';
}
toJson() {
return {
field: this.field.toJson(),
};
}
render({ aliases, escape, prefix, parser, options, }) {
parser = resolve([this.field], parser);
const params = [
QueryOption.select,
QueryOption.expand,
QueryOption.filter,
QueryOption.search,
QueryOption.orderBy,
QueryOption.compute,
QueryOption.skip,
QueryOption.top,
QueryOption.count,
QueryOption.levels,
]
.filter((key) => !Types.isEmpty(this.values[key]))
.reduce((acc, key) => {
let value = this.values[key];
if (Types.rawType(value).endsWith('Expression')) {
value = value.render({
aliases,
prefix,
escape,
parser,
options,
});
}
return Object.assign(acc, { [key]: value });
}, {});
let expand = `${render(this.field, {
aliases,
escape,
prefix,
parser,
options,
})}`;
if (!Types.isEmpty(params)) {
expand = `${expand}(${Object.keys(params)
.map((key) => `$${key}=${params[key]}`)
.join(';')})`;
}
return expand;
}
clone() {
const values = Object.keys(this.values).reduce((acc, key) => Object.assign(acc, { [key]: Objects.clone(this.values[key]) }), {});
return new ExpandField(typeof this.field === 'string' ? this.field : this.field.clone(), values);
}
resolve(parser) {
return parser;
}
select(opts) {
return this.option(QueryOption.select, SelectExpression.factory(opts, this.values[QueryOption.select]));
}
expand(opts) {
return this.option(QueryOption.expand, ExpandExpression.factory(opts, this.values[QueryOption.expand]));
}
filter(opts) {
return this.option(QueryOption.filter, FilterExpression.factory(opts, this.values[QueryOption.filter]));
}
search(opts) {
return this.option(QueryOption.search, SearchExpression.factory(opts, this.values[QueryOption.search]));
}
orderBy(opts) {
return this.option(QueryOption.orderBy, OrderByExpression.factory(opts, this.values[QueryOption.orderBy]));
}
compute(opts) {
return this.option(QueryOption.compute, ComputeExpression.factory(opts, this.values[QueryOption.compute]));
}
skip(n) {
return this.option(QueryOption.skip, n);
}
top(n) {
return this.option(QueryOption.top, n);
}
levels(n) {
return this.option(QueryOption.levels, n);
}
count() {
return this.option(QueryOption.count, true);
}
// Option Handler
option(name, opts) {
if (opts !== undefined)
this.values[name] = opts;
return this.values[name];
}
}
class ExpandExpression extends Expression {
constructor({ children, } = {}) {
super({ children });
}
get [Symbol.toStringTag]() {
return 'ExpandExpression';
}
static factory(opts, current) {
return opts({
t: FieldFactory(),
e: () => new ExpandExpression(),
}, current);
}
toJson() {
const json = super.toJson();
return Object.assign(json, {});
}
static fromJson(json) {
return new ExpandExpression({
children: json['children'].map((c) => RenderableFactory(c)),
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
return this._children
.map((n) => n.render({ aliases, escape, prefix, parser, options }))
.join(',');
}
clone() {
return new ExpandExpression({
children: this._children.map((c) => c.clone()),
});
}
_add(node) {
this._children.push(node);
return this;
}
field(field, opts) {
let node = new ExpandField(field);
if (opts !== undefined)
opts(node);
return this._add(node);
}
}
const FieldFactory = (names = []) => new Proxy({ _names: names }, {
get(target, key) {
let names = target['_names'];
if (key === 'render') {
return ({ aliases, escape, prefix, parser, options, }) => {
let values = names.map((n) => render(n, { aliases, escape, prefix, parser, options }));
if (prefix && (names.length === 0 || typeof names[0] === 'string')) {
values = [prefix, ...values];
}
return values.join('/');
};
}
else if (key === 'clone') {
return () => FieldFactory([...names]);
}
else if (key === 'isField') {
return () => true;
}
else if (key === 'toJson') {
return () => ({
$type: 'Field',
names: names,
});
}
else if (key === 'resolve') {
return (parser) => names.reduce((acc, name) => typeof name === 'string'
? acc?.field(name)
: name?.resolve(parser), parser);
}
else {
return FieldFactory([...names, key]);
}
},
has(target, key) {
return (['toJson', 'isField', 'clone', 'render', 'resolve'].includes(key) ||
key in target);
},
});
const RenderableFactory = (value) => {
if (Types.isPlainObject(value) && '$type' in value) {
switch (value.$type) {
case 'SelectExpression':
return SelectExpression.fromJson(value);
case 'ExpandExpression':
return ExpandExpression.fromJson(value);
case 'ComputeExpression':
return ComputeExpression.fromJson(value);
case 'ApplyExpression':
return ApplyExpression.fromJson(value);
case 'FilterExpression':
return FilterExpression.fromJson(value);
case 'OrderByExpression':
return OrderByExpression.fromJson(value);
case 'SearchExpression':
return SearchExpression.fromJson(value);
case 'CountExpression':
return CountExpression.fromJson(value);
case 'GroupByTransformations':
return GroupByTransformations.fromJson(value);
case 'Function':
return Function.fromJson(value);
case 'Operator':
return Operator.fromJson(value);
case 'Grouping':
return Grouping.fromJson(value);
case 'Aggregate':
return Aggregate.fromJson(value);
case 'GroupBy':
return GroupBy.fromJson(value);
case 'Lambda':
return Lambda.fromJson(value);
case 'Type':
return Type.fromJson(value);
case 'Field':
return FieldFactory(value['names']);
default:
return value;
}
}
return value;
};
function applyMixins(derivedCtor, constructors) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null));
});
});
}
function render(value, { aliases, normalize, escape, prefix, parser, options, } = {}) {
if (Types.isFunction(value)) {
return render(value(syntax), {
aliases,
normalize,
prefix,
parser,
options,
});
}
if (Types.isObject(value) && 'render' in value) {
return render(value.render({ aliases, escape, prefix, parser, options }), {
aliases,
normalize,
escape,
prefix,
parser,
options,
});
}
return normalize ? normalizeValue(value, { aliases, escape }) : value;
}
function resolve(values, parser) {
if (parser !== undefined) {
let fields = values.filter((v) => Types.isObject(v) && 'isField' in v && v.isField());
if (fields.length === 1 && Types.isObject(parser) && 'field' in parser) {
return fields[0].resolve(parser);
}
}
return parser;
}
function encode(values, parser, options) {
if (parser !== undefined) {
return values.map((v) => {
if (Types.isArray(v))
return encode(v, parser, options);
if (Types.isObject(v) || v == null)
return v;
try {
return parser.encode(v, options);
}
catch {
return v;
}
});
}
return values;
}
class Function {
name;
values;
normalize;
escape;
constructor(name, values, normalize, escape = false) {
this.name = name;
this.values = values;
this.normalize = normalize;
this.escape = escape;
}
get [Symbol.toStringTag]() {
return 'Function';
}
toJson() {
return {
$type: Types.rawType(this),
name: this.name,
values: this.values.map((v) => Types.isObject(v) && 'toJson' in v ? v.toJson() : v),
normalize: this.normalize,
};
}
static fromJson(json) {
return new Function(json['name'], json['values'].map((v) => RenderableFactory(v)), json['normalize'], json['escape']);
}
render({ aliases, escape, prefix, parser, options, }) {
parser = resolve(this.values, parser);
let [left, ...values] = encode(this.values, parser, options);
left = render(left, {
aliases,
escape,
prefix,
parser,
normalize: this.normalize === 'all' || this.normalize === 'left',
options,
});
const params = [
left,
...values.map((v) => render(v, {
aliases,
escape,
prefix,
parser,
normalize: this.normalize === 'all' || this.normalize === 'right',
options,
})),
];
return `${this.name}(${params.join(', ')})`;
}
clone() {
return new Function(this.name, this.values.map((v) => Objects.clone(v)), this.normalize, this.escape);
}
resolve(parser) {
return parser;
}
}
class StringAndCollectionFunctions {
concat(left, right, normalize = 'right') {
return new Function('concat', [left, right], normalize);
}
contains(left, right, normalize = 'right') {
return new Function('contains', [left, right], normalize);
}
endsWith(left, right, normalize = 'right') {
return new Function('endswith', [left, right], normalize);
}
indexOf(left, right, normalize = 'right') {
return new Function('indexof', [left, right], normalize);
}
length(left, normalize = 'right') {
return new Function('length', [left], normalize);
}
startsWith(left, right, normalize = 'right') {
return new Function('startswith', [left, right], normalize);
}
subString(left, right, length, normalize = 'none') {
let values = [left, right];
if (length !== undefined) {
values.push(length);
}
return new Function('substring', values, normalize);
}
}
class CollectionFunctions {
hasSubset(left, right, normalize = 'none') {
return new Function('hassubset', [left, right], normalize);
}
hasSubsequence(left, right, normalize = 'none') {
return new Function('hassubsequence', [left, right], normalize);
}
}
class StringFunctions {
matchesPattern(left, pattern, normalize = 'none') {
return new Function('matchesPattern', [left, pattern], normalize);
}
toLower(left, normalize = 'none') {
return new Function('tolower', [left], normalize);
}
toUpper(left, normalize = 'none') {
return new Function('toupper', [left], normalize);
}
trim(left, normalize = 'none') {
return new Function('trim', [left], normalize);
}
}
class DateAndTimeFunctions {
date(left, normalize = 'none') {
return new Function('date', [left], normalize);
}
day(left, normalize = 'none') {
return new Function('day', [left], normalize);
}
fractionalseconds(left, normalize = 'none') {
return new Function('fractionalseconds', [left], normalize);
}
hour(left, normalize = 'none') {
return new Function('hour', [left], normalize);
}
maxdatetime(left, normalize = 'none') {
return new Function('maxdatetime', [left], normalize);
}
mindatetime(left, normalize = 'none') {
return new Function('mindatetime', [left], normalize);
}
minute(left, normalize = 'none') {
return new Function('minute', [left], normalize);
}
month(left, normalize = 'none') {
return new Function('month', [left], normalize);
}
now() {
return new Function('now', [], 'none');
}
second(left, normalize = 'none') {
return new Function('second', [left], normalize);
}
time(left, normalize = 'none') {
return new Function('time', [left], normalize);
}
totaloffsetminutes(left, normalize = 'none') {
return new Function('totaloffsetminutes', [left], normalize);
}
totalseconds(left, normalize = 'none') {
return new Function('totalseconds', [left], normalize);
}
year(left, normalize = 'none') {
return new Function('year', [left], normalize);
}
}
class ArithmeticFunctions {
ceiling(left, normalize = 'none') {
return new Function('ceiling', [left], normalize);
}
floor(left, normalize = 'none') {
return new Function('floor', [left], normalize);
}
round(left, normalize = 'none') {
return new Function('round', [left], normalize);
}
}
class TypeFunctions {
cast(left, type) {
return FieldFactory([
type !== undefined
? new Type('cast', type, left)
: new Type('cast', left),
]);
}
isof(left, type) {
return type !== undefined
? new Type('isof', type, left)
: new Type('isof', left);
}
}
class GeoFunctions {
geoDistance(left, right, normalize = 'right') {
return new Function('geo.distance', [left, right], normalize);
}
geoIntersects(left, right, normalize = 'right') {
return new Function('geo.intersects', [left, right], normalize);
}
geoLength(left, normalize = 'none') {
return new Function('geo.length', [left], normalize);
}
}
class ConditionalFunctions {
case(left, right, normalize = 'none') {
return new Function('case', [left, right], normalize);
}
}
class Operator {
op;
values;
normalize;
constructor(op, values, normalize) {
this.op = op;
this.values = values;
this.normalize = normalize;
}
get [Symbol.toStringTag]() {
return 'Operator';
}
toJson() {
return {
$type: Types.rawType(this),
op: this.op,
values: this.values.map((v) => Types.isObject(v) && 'toJson' in v ? v.toJson() : v),
normalize: this.normalize,
};
}
static fromJson(json) {
return new Operator(json['op'], json['values'].map((v) => RenderableFactory(v)), json['normalize']);
}
render({ aliases, escape, prefix, parser, options, }) {
parser = resolve(this.values, parser);
let [left, right] = encode(this.values, parser, options);
left = render(left, {
aliases,
escape,
prefix,
parser,
normalize: this.normalize === 'all' || this.normalize === 'left',
options,
});
if (right !== undefined) {
right = Array.isArray(right)
? `(${right
.map((v) => render(v, {
aliases,
escape,
prefix,
parser,
normalize: this.normalize === 'all' || this.normalize === 'right',
options,
}))
.join(',')})`
: render(right, {
aliases,
escape,
prefix,
parser,
normalize: this.normalize === 'all' || this.normalize === 'right',
options,
});
return `${left} ${this.op} ${right}`;
}
return `${this.op}(${left})`;
}
clone() {
return new Operator(this.op, this.values.map((v) => Objects.clone(v)), this.normalize);
}
resolve(parser) {
return parser;
}
}
class LogicalOperators {
eq(left, right, normalize = 'right') {
return new Operator('eq', [left, right], normalize);
}
ne(left, right, normalize = 'right') {
return new Operator('ne', [left, right], normalize);
}
gt(left, right, normalize = 'right') {
return new Operator('gt', [left, right], normalize);
}
ge(left, right, normalize = 'right') {
return new Operator('ge', [left, right], normalize);
}
lt(left, right, normalize = 'right') {
return new Operator('lt', [left, right], normalize);
}
le(left, right, normalize = 'right') {
return new Operator('le', [left, right], normalize);
}
/*
and(left: any, right: any, normalize: Normalize = 'right') {
return new Operator('and', [left, right], normalize);
}
or(left: any, right: any, normalize: Normalize = 'right') {
return new Operator('or', [left, right], normalize);
}
*/
not(left, normalize = 'none') {
return new Operator('not', [left], normalize);
}
has(left, right, normalize = 'right') {
return new Operator('has', [left, right], normalize);
}
in(left, right, normalize = 'right') {
return new Operator('in', [left, right], normalize);
}
}
class ArithmeticOperators {
add(left, right, normalize = 'right') {
return new Operator('add', [left, right], normalize);
}
sub(left, right, normalize = 'right') {
return new Operator('sub', [left, right], normalize);
}
mul(left, right, normalize = 'right') {
return new Operator('mul', [left, right], normalize);
}
div(left, right, normalize = 'right') {
return new Operator('div', [left, right], normalize);
}
mod(left, right, normalize = 'right') {
return new Operator('mod', [left, right], normalize);
}
neg(value, normalize = 'right') {
return new Operator('-', [value], normalize);
}
}
class Grouping {
group;
constructor(group) {
this.group = group;
}
get [Symbol.toStringTag]() {
return 'Grouping';
}
toJson() {
return {
$type: Types.rawType(this),
group: this.group.toJson(),
};
}
static fromJson(json) {
return new Grouping(json['group'].map((v) => RenderableFactory(v)));
}
render({ aliases, escape, prefix, parser, options, }) {
return `(${render(this.group, {
aliases,
escape,
prefix,
parser,
options,
})})`;
}
clone() {
return new Grouping(Objects.clone(this.group));
}
resolve(parser) {
return parser;
}
}
class GroupingOperators {
group(value) {
return new Grouping(value);
}
rollup(...values) {
return new Function('rollup', values, 'none');
}
}
class Aggregate {
value;
method;
alias;
constructor(value, method, alias) {
this.value = value;
this.method = method;
this.alias = alias;
}
get [Symbol.toStringTag]() {
return 'Aggregate';
}
toJson() {
return {
$type: Types.rawType(this),
value: this.value.toJson(),
method: this.method,
alias: this.alias,
};
}
static fromJson(json) {
return new Aggregate(RenderableFactory(json['value']), json['method'], json['alias']);
}
render({ aliases, escape, prefix, parser, options, }) {
return `aggregate(${render(this.value, {
aliases,
escape,
prefix,
parser,
options,
})} with ${this.method} as ${this.alias})`;
}
clone() {
return new Aggregate(Objects.clone(this.value), this.method, this.alias);
}
resolve(parser) {
return parser;
}
}
class GroupBy {
properties;
transformations;
constructor(properties, transformations) {
this.properties = properties;
this.transformations = transformations;
}
get [Symbol.toStringTag]() {
return 'GroupBy';
}
toJson() {
return {
$type: Types.rawType(this),
properties: this.properties.map((p) => p.toJson()),
transformations: this.transformations?.toJson(),
};
}
static fromJson(json) {
return new GroupBy(json['properties'].map((p) => RenderableFactory(p)), RenderableFactory(json['transformations']));
}
render({ aliases, escape, prefix, parser, options, }) {
const properties = this.properties
.map((p) => render(p, {
aliases,
escape,
prefix,
parser,
options,
}))
.join(',');
const transformations = this.transformations
? ', ' +
render(this.transformations, {
aliases,
escape,
prefix,
parser,
options,
})
: '';
return `groupby((${properties})${transformations})`;
}
clone() {
return new GroupBy(Objects.clone(this.properties), Objects.clone(this.transformations));
}
resolve(parser) {
return parser;
}
}
class Transformations {
aggregate(value, method, alias) {
return new Aggregate(value, method, alias);
}
groupby(properties, options) {
return new GroupBy(properties, options);
}
topCount(value, field, normalize = 'none') {
return new Function('topcount', [value, field], normalize);
}
topSum(value, field, normalize = 'none') {
return new Function('topsum', [value, field], normalize);
}
topPercent(value, field, normalize = 'none') {
return new Function('toppercent', [value, field], normalize);
}
bottomCount(value, field, normalize = 'none') {
return new Function('bottomcount', [value, field], normalize);
}
bottomSum(value, field, normalize = 'none') {
return new Function('bottomsum', [value, field], normalize);
}
bottomPercent(value, field, normalize = 'none') {
return new Function('bottompercent', [value, field], normalize);
}
identity() {
return new Function('identity', [], 'none');
}
search(value, normalize = 'none') {
return new Function('search', [value], normalize);
}
filter(value, normalize = 'none') {
return new Function('filter', [value], normalize);
}
skip(value, normalize = 'none') {
return new Function('top', [value], normalize);
}
top(value, normalize = 'none') {
return new Function('top', [value], normalize);
}
orderby(value, normalize = 'none') {
return new Function('filter', [value], normalize);
}
}
class Type {
name;
type;
value;
constructor(name, type, value) {
this.name = name;
this.type = type;
this.value = value;
}
get [Symbol.toStringTag]() {
return 'Type';
}
toJson() {
return {
$type: Types.rawType(this),
name: this.name,
type: this.type,
value: this.value,
};
}
static fromJson(json) {
return new Type(json['name'], json['type'], RenderableFactory(json['value']));
}
render({ aliases, escape, prefix, parser, options, }) {
let value;
if (this.value) {
parser = resolve([this.value], parser);
let [left, right] = encode([this.value], parser, options);
value = render(left, { aliases, escape, prefix, parser, options });
}
return value
? `${this.name}(${value}, '${this.type}')`
: `${this.name}('${this.type}')`;
}
clone() {
return new Type(this.name, this.type, Objects.clone(this.value));
}
resolve(parser) {
parser =
parser instanceof ODataStructuredTypeFieldParser &&
parser.isStructuredType()
? parser.structuredType()
: parser;
return parser?.findChildParser((p) => p.isTypeOf(this.type));
}
}
class Lambda {
op;
values;
alias;
constructor(op, values, alias) {
this.op = op;
this.values = values;
this.alias = alias;
}
get [Symbol.toStringTag]() {
return 'Lambda';
}
toJson() {
return {
$type: Types.rawType(this),
op: this.op,
values: this.values.map((v) => Types.isObject(v) && 'toJson' in v ? v.toJson() : v),
alias: this.alias,
};
}
static fromJson(json) {
return new Lambda(json['op'], json['values'].map((v) => RenderableFactory(v)), json['alias']);
}
render({ aliases, escape, prefix, parser, options, }) {
parser = resolve(this.values, parser);
let [left, right] = encode(this.values, parser, options);
left = render(left, { aliases, escape, prefix, parser });
if (right) {
let alias = this.alias || left.split('/').pop().toLowerCase()[0];
return `${left}/${this.op}(${alias}:${render(right, {
aliases,
escape,
prefix: alias,
options,
parser,
})})`;
}
else {
return `${left}/${this.op}()`;
}
}
clone() {
return new Lambda(this.op, this.values.map((v) => Objects.clone(v)), this.alias);
}
resolve(parser) {
return parser;
}
}
class LambdaOperators {
any(left, right, alias) {
return new Lambda('any', [left, right], alias);
}
all(left, right, alias) {
return new Lambda('all', [left, right], alias);
}
}
class ODataOperators {
}
applyMixins(ODataOperators, [
LogicalOperators,
ArithmeticOperators,
GroupingOperators,
LambdaOperators,
]);
const operators = new ODataOperators();
class ODataFunctions {
}
applyMixins(ODataFunctions, [
StringAndCollectionFunctions,
CollectionFunctions,
StringFunctions,
DateAndTimeFunctions,
ArithmeticFunctions,
TypeFunctions,
GeoFunctions,
ConditionalFunctions,
]);
const functions = new ODataFunctions();
class ODataTransformations {
}
applyMixins(ODataTransformations, [Transformations]);
const transformations = new ODataTransformations();
class ODataSyntax {
}
applyMixins(ODataSyntax, [
ODataOperators,
ODataFunctions,
ODataTransformations,
]);
const syntax = new ODataSyntax();
class ComputeExpression extends Expression {
names;
constructor({ children, names, } = {}) {
super({ children });
this.names = names || [];
}
get [Symbol.toStringTag]() {
return 'ComputeExpression';
}
static factory(opts, current) {
return opts({
t: FieldFactory(),
e: () => new ComputeExpression(),
}, current);
}
toJson() {
const json = super.toJson();
return Object.assign(json, {
names: this.names,
});
}
static fromJson(json) {
return new ComputeExpression({
children: json['children'].map((c) => RenderableFactory(c)),
names: json['names'],
});
}
render({ aliases, escape, prefix, parser, options, } = {}) {
const children = this._children.map((n) => n.render({ aliases, escape, prefix, parser, options }));
return this.names
.map((name, index) => `${children[index]} as ${name}`)
.join(',');
}
clone() {
return new ComputeExpression({
children: this._children.map((c) => c.clone()),
names: [...this.names],
});
}
_add(name, node) {
this.names.push(name);
this._children.push(node);
return this;
}
field(name, opts) {
const node = opts({
o: operators,
f: functions,
});
return this._add(name, node);
}
}
class ODataQueryOptionHandler {
o;
n;
constructor(o, n) {
this.o = o;
this.n = n;
}
/**
* The name of the managed odata query option.
*/
get name() {
return this.n;
}
/**
* Converts the managed odata query option to a json object.
* @returns {any}
*/
toJson() {
return this.o.get(this.n);
}
/**
* Returns a boolean indicating if the managed odata query option is empty.
* @returns True if the managed odata query option is empty.
*/
empty() {
return Types.isEmpty(this.o.get(this.n));
}
//#region Primitive Value
/**
* Get or Set the value of the managed odata query option.
* @param v The value to set.
* @returns
*/
value(v) {
if (v !== undefined)
this.o.set(this.n, v);
return this.o.get(this.n);
}
//#endregion
//#region Array Value
assertArray() {
if (!Types.isArray(this.o.get(this.n)))
this.o.set(this.n, this.o.has(this.n) ? [this.o.get(this.n)] : []);
return this.o.get(this.n);
}
/**
* Push value to the managed odata query option.
* @param value Value to push
*/
push(value) {
this.assertArray().push(value);
}
/**
* Remove value from the managed odata query option.
* @param value Value to remove
*/
remove(value) {
this.o.set(this.n, this.assertArray().filter((v) => v !== value));
// If only one and not is array... down to value
if (this.o.get(this.n).length === 1 &&
!Types.isArray(this.o.get(this.n)[0]))
this.o.set(this.n, this.o.get(this.n)[0]);
}
/**
* Return value at index of the managed odata query option.
* @param index Index of the value
* @returns The value
*/
at(index) {
return this.assertArray()[index];
}
some(predicate) {
return this.assertArray().some(predicate);
}
every(predicate) {
return this.assertArray().every(predicate);
}
find(predicate) {
return this.assertArray().find(predicate);
}
//#endregion
//#region HashMap Value
assertObject(create) {
if (!Types.isArray(this.o.get(this.n)) &&
Types.isPlainObject(this.o.get(this.n))) {
return this.o.get(this.n);
}
let arr = this.assertArray();
let obj = arr.find((v) => Types.isPlainObject(v));
if (!obj && create) {
obj = {};
arr.push(obj);
}
return obj;
}
/**
* Set the value for path in the managed odata query option.
* @param path Path for set the value
* @param value Value to set
*/
set(path, value) {
let obj = this.assertObject(true);
Objects.set(obj, path, value);
}
/**
* Get the value for path from the managed odata query option.
* @param path The path from get the value
* @param def Default if not found
* @returns
*/
get(path, def) {
let obj = this.assertObject(false) || {};
return Objects.get(obj, path, def);
}
/**
* Unset the value for path in the managed odata query option.
* @param path
*/
unset(path) {
let obj = this.assertObject(true);
Objects.unset(obj, path);
if (Types.isArray(this.o.get(this.n))) {
this.o.set(this.n, this.o.get(this.n).filter((v) => !Types.isEmpty(v)));
if (this.o.get(this.n).length === 1)
this.o.set(this.n, this.o.get(this.n)[0]);
}
}
/**
* Test if the managed odata query option has the value.
* @param path The path fot test if the value is set
* @returns Boolean indicating if the value is set
*/
has(path) {
let obj = this.assertObject(false) || {};
return Objects.has(obj, path);
}
/**
* Merge values from object into the managed odata query option.
* @param values Object to merge
* @returns
*/
assign(values) {
let obj = this.assertObject(true);
return Objects.merge(obj, values);
}
//#endregion
/**
* Clear the managed odata query option.
*/
clear() {
this.o.delete(this.n);
}
toString({ escape, parser, } = {}) {
const [_, params] = pathAndParamsFromQueryOptions(new Map([[this.n, this.o.get(this.n)]]), { escape, parser });
return params[`$${this.n}`];
}
}
class ODataQueryOptionsHandler {
options;
constructor(options) {
this.options = options;
}
/**
* Create a raw odata value
* @param value The value to raw
* @returns The raw value
*/
raw(value) {
return raw(value);
}
/**
* Create a new odata alias parameter
* @link https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_ParameterAliases
* @param value The value of the alias
* @param name The name of the alias
* @returns The alias
*/
alias(value, name) {
return alias(value, name);
}
/**
* Create a duration odata value
* @param value The value to duration
* @returns The duration value
*/
duration(value) {
return duration(value);
}
/**
* Create a binary odata value
* @param value The value to binary
* @returns The binary value
*/
binary(value) {
return binary(value);
}
/**
* Normalize the given value to a valid odata value
* @param value The value to normalize
* @returns The normalized value
*/
normalize(value) {
return normalizeValue(value);
}
select(opts) {
if (Types.isFunction(opts)) {
return this.options.expression(QueryOption.select, SelectExpression.factory(opts, this.options.expression(QueryOption.select)));
}
return this.options.option(QueryOption.select, opts);
}
expand(opts) {
if (Types.isFunction(opts)) {
return this.options.expression(QueryOption.expand, ExpandExpression.factory(opts, this.options.expression(QueryOption.expand)));
}
return this.options.option(QueryOption.expand, opts);
}
compute(opts) {
if (Types.isFunction(opts)) {
return this.options.expression(QueryOption.compute, ComputeExpression.factory(opts, this.options.expression(QueryOption.compute)));
}
return this.options.option(QueryOption.compute, opts);
}
apply(opts) {
if (Types.isFunction(opts)) {
return this.options.expression(QueryOption.apply, ApplyExpression.factory(opts, this.options.expression(QueryOption.apply)));
}
return this.options.option(QueryOption.apply, opts);
}
format(opts) {
return this.options.option(QueryOption.format, opts);
}
transform(opts) {
return this.options.option(QueryOption.transform, opts);
}
search(opts) {
if (Types.isFunction(opts)) {
return this.options.expression(QueryOption.search, SearchExpression.factory(opts, this.options.expression(QueryOption.search)));
}
return this.options.option(QueryOption.search, opts);
}
filter(opts) {
if (Types.isFunction(opts)) {
return this.options.expression(QueryOption.filter, FilterExpression.factory(opts, this.options.expression(QueryOption.filter)));
}
return this.options.option(QueryOption.filter, opts);
}
orderBy(opts) {
if (Types.isFunction(opts)) {
return this.options.option(QueryOption.orderBy, OrderByExpression.factory(opts, this.options.expression(QueryOption.orderBy)));
}
return this.options.option(QueryOption.orderBy, opts);
}
top(opts) {
return this.options.option(QueryOption.top, opts);
}
skip(opts) {
return this.options.option(QueryOption.skip, opts);
}
skiptoken(opts) {
return this.options.option(QueryOption.skiptoken, opts);
}
remove(...keys) {
this.options.remove(...keys);
}
keep(...keys) {
this.options.keep(...keys);
}
/**
* Shortcut for set $top, $skip, $skiptoken.
* @param param0 skip or top or skiptoken
*/
paging({ skip, skiptoken, top, } = {}) {
if (skiptoken !== undefined) {
if (skiptoken !== null) {
this.skiptoken(skiptoken);
}
else {
this.remove(QueryOption.skiptoken);
}
}
if (skip !== undefined) {
if (skip !== null) {
this.skip(skip);
}
else {
this.remove(QueryOption.skip);
}
}
if (top !== undefined) {
if (top !== null) {
this.top(top);
}
else {
this.remove(QueryOption.top);
}
}
}
/**
* Shortcut for clear pagination by unset $top, $skip, $skiptoken.
*/
removePaging() {
this.remove(QueryOption.skip, QueryOption.top, QueryOption.skiptoken);
}
/**
* Shortcut for clear query.
*/
clear() {
this.options.clear();
}
/**
* Store the query options from the current query.
*/
store() {
return this.options.toQueryArguments();
}
/**
* Restore the given query options to the current query.
* @param options The query to be applied.
*/
restore(options) {
if (options.select !== undefined) {
if (options.select instanceof SelectExpression) {
this.options.expression(QueryOption.select, options.select);
}
else if (options.select !== null) {
this.options.option(QueryOption.select, options.select);
}
else {
this.options.remove(QueryOption.select);
}
}
if (options.expand !== undefined) {
if (options.expand instanceof ExpandExpression) {
this.options.expression(QueryOption.expand, options.expand);
}
else if (options.expand !== null) {
this.options.option(QueryOption.expand, options.expand);
}
else {
this.options.remove(QueryOption.expand);
}
}
if (options.compute !== undefined) {
if (options.compute instanceof ComputeExpression) {
this.options.expression(QueryOption.compute, options.compute);
}
else if (options.compute !== null) {
this.options.option(QueryOption.compute, options.compute);
}
else {
this.options.remove(QueryOption.compute);
}
}
if (options.apply !== undefined) {
if (options.apply instanceof ApplyExpression) {
this.options.expression(QueryOption.apply, options.apply);
}
else if (options.apply !== null) {
this.options.option(QueryOption.apply, options.apply);
}
else {
this.options.remove(QueryOption.apply);
}
}
if (options.transform !== undefined) {
if (options.transform !== null) {
this.options.option(QueryOption.transform, options.transform);
}
else {
this.options.remove(QueryOption.transform);
}
}
if (options.search !== undefined) {
if (options.search instanceof SearchExpression) {
this.options.expression(QueryOption.search, options.search);
}
else if (options.search !== null) {
this.options.option(QueryOption.search, options.search);
}
else {
this.options.remove(QueryOption.search);
}
}
if (options.filter !== undefined) {
if (options.filter instanceof FilterExpression) {
this.options.expression(QueryOption.filter, options.filter);
}
else if (options.filter !== null) {
this.options.option(QueryOption.filter, options.filter);
}
else {
this.options.remove(QueryOption.filter);
}
}
if (options.orderBy !== undefined) {
if (options.orderBy instanceof OrderByExpression) {
this.options.expression(QueryOption.orderBy, options.orderBy);
}
else if (options.orderBy !== null) {
this.options.option(QueryOption.orderBy, options.orderBy);
}
else {
this.options.remove(QueryOption.orderBy);
}
}
this.paging(options);
}
toJson() {
return this.options.toJson();
}
fromJson(json) {
this.options.fromJson(json);
}
toString({ escape, parser, } = {}) {
return this.options.toString({ escape, parser });
}
}
// Create a path and params tuple from the query options
const pathAndParamsFromQueryOptions = (values, { escape, parser, options, } = {}) => {
const aliases = [];
const queryOptions = [
QueryOption.select,
QueryOption.filter,
QueryOption.search,
QueryOption.compute,
QueryOption.apply,
QueryOption.transform,
QueryOption.orderBy,
QueryOption.top,
QueryOption.skip,
QueryOption.skiptoken,
QueryOption.expand,
QueryOption.format,
QueryOption.levels,
QueryOption.count,
]
.filter((key) => values.has(key) && !Types.isEmpty(values.get(key)))
.reduce((acc, key) => {
let value = values.get(key);
if (Types.rawType(value).endsWith('Expression') ||
(Types.isArray(value) &&
value.some((v) => Types.rawType(v).endsWith('Expression')))) {
value = Types.isArray(value)
? value.map((v) => Types.rawType(v).endsWith('Expression')
? raw(v.render({ aliases, escape, parser, options }))
: v)
: raw(value.render({
aliases,
escape,
parser,
options,
}));
}
return Object.assign(acc, { [key]: value });
}, {});
return buildPathAndQuery({ ...queryOptions, aliases, escape });
};
class ODataQueryOptions {
_values;
constructor(values) {
this._values = values ?? new Map();
}
pathAndParams({ escape, parser, options, } = {}) {
return pathAndParamsFromQueryOptions(this._values, {
escape,
parser,
options,
});
}
toString({ escape, parser, } = {}) {
const [path, params] = this.pathAndParams({ escape, parser });
return (path +
Object.entries(params)
.filter(([, value]) => !Types.isEmpty(value))
.map(([key, value]) => `${key}=${value}`)
.join('&'));
}
toJson() {
return [...this._values.keys()].reduce((acc, key) => {
let value = this._values.get(key);
value = 'toJson' in value ? value.toJson() : value;
return Object.assign(acc, { [key]: value });
}, {});
}
fromJson(json) {
Object.entries(json || {}).forEach(([key, value]) => {
this._values.set(key, RenderableFactory(value));
});
return this;
}
static fromJson(json) {
const entries = Object.entries(json || {}).map(([key, value]) => [
key,
RenderableFactory(value),
]);
new Map(entries);
return new ODataQueryOptions(new Map(entries));
}
toQueryArguments() {
return {
select: this._values.get(QueryOption.select) ?? null,
expand: this._values.get(QueryOption.expand) ?? null,
transform: this._values.get(QueryOption.transform) ?? null,
compute: this._values.get(QueryOption.compute) ?? null,
apply: this._values.get(QueryOption.apply) ?? null,
search: this._values.get(QueryOption.search) ?? null,
filter: this._values.get(QueryOption.filter) ?? null,
orderBy: this._values.get(QueryOption.orderBy) ?? null,
top: this._values.get(QueryOption.top) ?? null,
skip: this._values.get(QueryOption.skip) ?? null,
skiptoken: this._values.get(QueryOption.skiptoken) ?? null,
levels: this._values.get(QueryOption.levels) ?? null,
count: this._values.get(QueryOption.count) ?? null,
};
}
clone() {
return new ODataQueryOptions(Objects.clone(this._values));
}
// Set Renderable
expression(key, exp) {
if (exp !== undefined)
this._values.set(key, exp);
return this._values.get(key);
}
// Option Handler
option(key, opts) {
if (opts !== undefined)
this._values.set(key, opts);
return new ODataQueryOptionHandler(this._values, key);
}
// Query Options tools
has(key) {
return this._values.has(key);
}
remove(...keys) {
for (let key of this._values.keys()) {
if (keys.includes(key))
this._values.delete(key);
}
}
keep(...keys) {
for (let key of this._values.keys()) {
if (!keys.includes(key))
this._values.delete(key);
}
}
// Clear
clear() {
this._values.clear();
}
}
class SegmentHandler {
segment;
constructor(segment) {
this.segment = segment;
}
get name() {
return this.segment.name;
}
outgoingType(value) {
if (value !== undefined)
this.segment.outgoingType = value;
return this.segment.outgoingType;
}
incomingType(value) {
if (value !== undefined)
this.segment.incomingType = value;
return this.segment.incomingType;
}
bindingType(value) {
if (value !== undefined)
this.segment.bindingType = value;
return this.segment.bindingType;
}
path(value) {
if (value !== undefined)
this.segment.path = value;
return this.segment.path;
}
key(value) {
if (value !== undefined)
this.segment.key = value;
return this.segment.key;
}
hasKey() {
return !Types.isEmpty(this.segment.key);
}
clearKey() {
delete this.segment.key;
}
parameters(value) {
if (value !== undefined)
this.segment.parameters = value;
return this.segment.parameters;
}
hasParameters() {
return !Types.isEmpty(this.segment.parameters);
}
clearParameters() {
delete this.segment.parameters;
}
}
class ODataPathSegmentsHandler {
segments;
constructor(segments) {
this.segments = segments;
}
entitySet() {
return this.segments.get(PathSegment.entitySet);
}
singleton() {
return this.segments.get(PathSegment.singleton);
}
action() {
return this.segments.get(PathSegment.action);
}
function() {
return this.segments.get(PathSegment.function);
}
keys(values) {
return this.segments.keys(values);
}
property() {
return this.segments.get(PathSegment.property);
}
navigationProperty() {
return this.segments.get(PathSegment.navigationProperty);
}
}
function pathSegmentsBuilder(segment, escape = false, parser, options) {
if (segment.name === PathSegment.function) {
let [path, params] = segment.parameters
? buildPathAndQuery({
func: { [segment.path]: segment.parameters },
escape,
})
: buildPathAndQuery({ func: segment.path, escape });
if (path.startsWith(PATH_SEPARATOR)) {
path = path.slice(1);
}
// HACK: Remove parenthesis
if (path.endsWith('()') &&
options?.nonParenthesisForEmptyParameterFunction) {
path = path.substring(0, path.length - 2);
}
return [path, params];
}
else {
//TODO: Parser key
let key = segment.key;
// HACK: Check guid string
if (typeof key === 'string' &&
/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(key)) {
key = raw(key);
}
let [path, params] = key ? buildPathAndQuery({ key, escape }) : ['', {}];
return [segment.path + path, params];
}
}
const pathAndParamsFromSegments = (segments, { escape, parser, options, } = {}) => {
const result = segments.reduce((acc, segment) => {
const [path, params] = pathSegmentsBuilder(segment, escape, parser, options);
acc.paths.push(path);
acc.params = Object.assign(acc.params, params);
return acc;
}, { paths: [], params: {} });
return [result.paths.join(PATH_SEPARATOR), result.params];
};
class ODataPathSegments {
_segments;
constructor(segments) {
this._segments = segments || [];
}
pathAndParams({ escape, parser, options, } = {}) {
return pathAndParamsFromSegments(this._segments, {
escape,
parser,
options,
});
}
types({ key = false } = {}) {
return this.segments({ key })
.map((s) => s.outgoingType())
.filter((t) => t !== undefined);
}
keys(values) {
const segments = this.segments({ key: true });
if (values !== undefined) {
segments.forEach((segment, index) => {
const key = values[index];
if (key === undefined) {
segment.clearKey();
}
else {
segment.key(key);
}
});
}
return segments.map((s) => s.key());
}
toString({ escape } = {}) {
const [path, params] = this.pathAndParams({ escape });
return (path +
Object.entries(params)
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join('&'));
}
toJson() {
return this._segments.map((s) => {
let json = {
name: s.name,
path: s.path,
};
if (s.incomingType !== undefined)
json.incomingType = s.incomingType;
if (s.outgoingType !== undefined)
json.outgoingType = s.outgoingType;
if (s.key !== undefined)
json.key = s.key;
if (s.parameters !== undefined)
json.parameters = s.parameters;
return json;
});
}
static fromJson(json) {
return new ODataPathSegments(json.map((s) => ({
name: s.name,
path: s.path,
type: s.type,
key: s.key,
parameters: s.parameters,
})));
}
clone() {
const segments = Objects.clone(this._segments);
return new ODataPathSegments(segments);
}
find(predicate) {
//Backward Find
return [...this._segments].reverse().find(predicate);
}
segments({ key = false } = {}) {
let segments = [...this._segments];
if (key)
segments = segments.filter((s) => [
PathSegment.entitySet,
PathSegment.navigationProperty,
PathSegment.property,
].indexOf(s.name) !== -1);
return segments.map((s) => new SegmentHandler(s));
}
first({ key = false } = {}) {
const segments = this.segments({ key });
return segments.length > 0 ? segments[0] : undefined;
}
last({ key = false } = {}) {
const segments = this.segments({ key });
return segments.length > 0 ? segments[segments.length - 1] : undefined;
}
add(name, path) {
const segment = { name, path };
this._segments.push(segment);
return new SegmentHandler(segment);
}
get(name) {
let segment = this.find((s) => s.name === name);
if (segment === undefined)
throw Error(`No Segment for name ${name} was found`);
return new SegmentHandler(segment);
}
}
class ODataRequest {
api;
observe;
context;
reportProgress;
withCredentials;
bodyQueryOptions;
fetchPolicy;
resource;
_responseType;
_method;
_body;
_headers;
_params;
_path;
constructor(init) {
this._method = init.method;
this.resource = init.resource;
this.api = init.api;
this.reportProgress = init.reportProgress;
this.observe = init.observe;
this.context = init.context;
// Response Type
this._responseType = init.responseType;
// The Body
this._body = init.body !== undefined ? init.body : null;
if (this._body !== null)
this._body = this.resource.serialize(this._body, init.parserOptions);
this.withCredentials =
init.withCredentials === undefined
? this.api.options.withCredentials
: init.withCredentials;
this.fetchPolicy = init.fetchPolicy || this.api.options.fetchPolicy;
this.bodyQueryOptions = [
...(this.api.options.bodyQueryOptions || []),
...(init.bodyQueryOptions || []),
];
// The Path and Params from resource
const [resourcePath, resourceParams] = this.resource.pathAndParams(init.parserOptions);
this._path = resourcePath;
//#region Headers
const customHeaders = {};
if (typeof init.etag === 'string') {
if (this.api.options.etag.ifMatch &&
['PUT', 'PATCH', 'DELETE'].indexOf(this._method) !== -1)
customHeaders[IF_MATCH_HEADER] = init.etag;
else if (this.api.options.etag.ifNoneMatch &&
['GET'].indexOf(this._method) !== -1)
customHeaders[IF_NONE_MATCH_HEADER] = init.etag;
}
const accept = [];
// Metadata
if (this.api.options.accept?.metadata !== undefined)
accept.push(`odata.metadata=${this.api.options.accept?.metadata}`);
// IEEE754
if (this.api.options.accept?.ieee754Compatible !== undefined)
accept.push(`IEEE754Compatible=${this.api.options.accept?.ieee754Compatible}`);
// streaming
if (this.api.options.accept?.streaming !== undefined)
accept.push(`streaming=${this.api.options.accept?.streaming}`);
// ExponentialDecimals
if (this.api.options.accept?.exponentialDecimals !== undefined)
accept.push(`ExponentialDecimals=${this.api.options.accept?.exponentialDecimals}`);
if (accept.length > 0)
customHeaders[ACCEPT] = [
`${APPLICATION_JSON};${accept.join(';')}`,
`${TEXT_PLAIN}`,
`${CONTENT_TYPE_ANY}`,
];
const prefer = [];
// Return
if (this.api.options.prefer?.return !== undefined &&
['POST', 'PUT', 'PATCH'].indexOf(this._method) !== -1)
prefer.push(`return=${this.api.options.prefer?.return}`);
// MaxPageSize
if (this.api.options.prefer?.maxPageSize !== undefined &&
['GET'].indexOf(this._method) !== -1)
prefer.push(`odata.maxpagesize=${this.api.options.prefer?.maxPageSize}`);
// Annotations
if (this.api.options.prefer?.includeAnnotations !== undefined &&
['GET'].indexOf(this._method) !== -1)
prefer.push(`odata.include-annotations=${this.api.options.prefer?.includeAnnotations}`);
// Omit Null Values
if (this.api.options.prefer?.omitNullValues === true &&
['GET'].indexOf(this._method) !== -1)
prefer.push(`omit-values=nulls`);
// Continue on Error
if (this.api.options.prefer?.continueOnError === true &&
['POST'].indexOf(this._method) !== -1)
prefer.push(`odata.continue-on-error`);
if (prefer.length > 0)
customHeaders[PREFER] = prefer;
this._headers = Http.mergeHttpHeaders(this.api.options.headers, customHeaders, init.headers || {});
//#endregion
//#region Params
const customParams = {};
if (['POST', 'PUT', 'PATCH'].indexOf(this._method) !== -1 &&
'$select' in resourceParams) {
customParams['$select'] = resourceParams['$select'];
}
if (['POST'].indexOf(this._method) !== -1 && '$expand' in resourceParams) {
customParams['$expand'] = resourceParams['$expand'];
}
if (['GET'].indexOf(this._method) !== -1) {
Object.assign(customParams, resourceParams);
}
const params = Http.mergeHttpParams(this.api.options.params, customParams, init.params || {});
this._params =
this._responseType === 'entity'
? Http.withoutHttpParams(params, [
'$filter',
'$orderby',
'$count',
'$skip',
'$top',
])
: params;
//#endregion
}
static factory(api, method, resource, options) {
const apiOptions = api.options;
let params = options.params || {};
if (options.withCount) {
params = Http.mergeHttpParams(params, apiOptions.helper.countParam());
}
let etag = options.etag;
if (etag === undefined && Types.isPlainObject(options.body)) {
etag = apiOptions.helper.etag(options.body);
}
return new ODataRequest({
method,
etag,
api,
resource,
params,
context: options.context,
body: options.body,
observe: options.observe,
headers: options.headers,
reportProgress: options.reportProgress,
responseType: options.responseType,
fetchPolicy: options.fetchPolicy,
parserOptions: options.parserOptions,
withCredentials: options.withCredentials,
bodyQueryOptions: options.bodyQueryOptions,
});
}
get responseType() {
return this._responseType &&
['property', 'entity', 'entities'].indexOf(this._responseType) !== -1
? 'json'
: this._responseType === 'value'
? 'text'
: this._responseType;
}
get path() {
return this.isQueryBody() ? `${this._path}/${$QUERY}` : this._path;
}
get method() {
return this.isQueryBody() ? 'POST' : this._method;
}
get body() {
return this.isQueryBody()
? Http.splitHttpParams(this._params, this.bodyQueryOptions.map((name) => `$${name}`))[1].toString()
: this._body;
}
get params() {
return this.isQueryBody()
? Http.splitHttpParams(this._params, this.bodyQueryOptions.map((name) => `$${name}`))[0]
: this._params;
}
get headers() {
return this.isQueryBody()
? Http.mergeHttpHeaders(this._headers, { CONTENT_TYPE: TEXT_PLAIN })
: this._headers;
}
get pathWithParams() {
return this.params.keys().length > 0
? `${this.path}?${this.params}`
: this.path;
}
get url() {
return `${this.api.serviceRootUrl}${this.path}`;
}
get urlWithParams() {
return `${this.api.serviceRootUrl}${this.pathWithParams}`;
}
get cacheKey() {
return this._params.keys().length > 0
? `${this._path}?${this._params}`
: this._path;
}
isQueryBody() {
return (this._method === 'GET' &&
this.bodyQueryOptions.length > 0 &&
this.bodyQueryOptions.some((name) => this._params.has(`$${name}`)));
}
isBatch() {
return this.path.endsWith($BATCH);
}
isFetch() {
return ['GET'].indexOf(this._method) !== -1;
}
isMutate() {
return ['PUT', 'PATCH', 'POST', 'DELETE'].indexOf(this._method) !== -1;
}
}
class ODataResource {
// VARIABLES
api;
pathSegments;
queryOptions;
constructor(api, { segments, query, } = {}) {
this.api = api;
this.pathSegments = segments ?? new ODataPathSegments();
this.queryOptions = query ?? new ODataQueryOptions();
}
/**
* @returns string The outgoing type of the resource
*/
outgoingType() {
return this.pathSegments.last()?.outgoingType();
}
/**
* @returns string The incoming type of the resource
*/
incomingType() {
return this.pathSegments.last()?.incomingType();
}
/**
* @returns string The binding type of the resource
*/
bindingType() {
return this.pathSegments.last()?.bindingType();
}
/**
* @returns string All covered types of the resource
*/
types() {
return this.pathSegments.types();
}
callable() {
const type = this.outgoingType() ?? this.incomingType();
return type !== undefined ? this.api.callable(type) : undefined;
}
enumType() {
const type = this.outgoingType() ?? this.incomingType();
return type !== undefined ? this.api.enumType(type) : undefined;
}
structuredType() {
const type = this.outgoingType() ?? this.incomingType();
return type !== undefined ? this.api.structuredType(type) : undefined;
}
/**
* @returns boolean The resource has key ?
*/
hasKey() {
return Boolean(this.pathSegments.last({ key: true })?.hasKey());
}
hasEntityKey() {
return Boolean(this.pathSegments.get(PathSegment.entitySet)?.hasKey());
}
clearKey() {
return this.pathSegments.last({ key: true })?.clearKey();
}
asModel(entity, { reset, annots, ModelType, } = {}) {
reset ??= annots !== undefined;
let resource = this;
const type = annots?.type ?? this.incomingType();
if (type === undefined)
throw Error(`No type for model`);
if (ModelType === undefined)
ModelType = this.api.modelForType(type);
let entitySet = annots?.entitySet;
if (entitySet !== undefined) {
resource = this.api.entitySet(entitySet).entity(entity);
resource.query((q) => q.restore(this.queryOptions.toQueryArguments()));
}
return new ModelType(entity, { resource, annots, reset });
}
asCollection(entities, { reset, annots, CollectionType, } = {}) {
reset ??= annots !== undefined;
let resource = this;
const type = annots?.type ?? this.incomingType();
if (type === undefined)
throw Error(`No type for collection`);
if (CollectionType === undefined)
CollectionType = this.api.collectionForType(type);
let entitySet = annots?.entitySet;
if (entitySet !== undefined) {
resource = this.api.entitySet(entitySet);
resource.query((q) => q.restore(this.queryOptions.toQueryArguments()));
}
return new CollectionType(entities, { resource, annots, reset });
}
//#endregion
isTypeOf(other) {
const thisStructured = this.structuredType();
const otherStructured = other.structuredType();
return (thisStructured !== undefined &&
otherStructured !== undefined &&
thisStructured.isTypeOf(otherStructured));
}
isSubtypeOf(other) {
const thisStructured = this.structuredType();
const otherStructured = other.structuredType();
return (thisStructured !== undefined &&
otherStructured !== undefined &&
thisStructured.isSubtypeOf(otherStructured));
}
isSupertypeOf(other) {
const thisStructured = this.structuredType();
const otherStructured = other.structuredType();
return (thisStructured !== undefined &&
otherStructured !== undefined &&
thisStructured.isSupertypeOf(otherStructured));
}
isEqualTo(other, test) {
const [selfPath, selfParams] = this.pathAndParams();
const [otherPath, otherParams] = other.pathAndParams();
return test === 'path'
? otherPath === selfPath
: test === 'params'
? Types.isEqual(selfParams, otherParams)
: otherPath === selfPath && Types.isEqual(selfParams, otherParams);
}
pathAndParams({ escape, ...options } = {
escape: false,
}) {
const type = this.outgoingType();
const parser = type !== undefined ? this.api.parserForType(type) : undefined;
const [spath, sparams] = this.pathSegments.pathAndParams({
escape,
parser,
options,
});
const [, qparams] = this.queryOptions.pathAndParams({
escape,
parser,
options,
});
return [spath, { ...sparams, ...qparams }];
}
endpointUrl({ escape = false, params = true, ...options } = {}) {
let [path, qparams] = this.pathAndParams({ escape, ...options });
if (params && !Types.isEmpty(qparams)) {
path = `${path}${QUERY_SEPARATOR}${Object.entries(qparams)
.map((e) => `${e[0]}${VALUE_SEPARATOR}${e[1]}`)
.join(PARAM_SEPARATOR)}`;
}
return `${this.api.serviceRootUrl}${path}`;
}
toString({ escape, ...options } = {
escape: false,
}) {
let [path, params] = this.pathAndParams({ escape, ...options });
let queryString = Object.entries(params)
.map((e) => `${e[0]}${VALUE_SEPARATOR}${e[1]}`)
.join(PARAM_SEPARATOR);
return queryString ? `${path}${QUERY_SEPARATOR}${queryString}` : path;
}
clone() {
const Ctor = this.constructor;
return new Ctor(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
}
__parser(value, options, resourceType, bindingType) {
const dataType = options !== undefined && Types.isPlainObject(value)
? ODataHelper[options.version ?? DEFAULT_VERSION].type(value)
: undefined;
if (dataType !== undefined) {
// Parser from data type
return this.api.parserForType(dataType);
}
else if (resourceType !== undefined) {
// Parser from resource type
return this.api.parserForType(resourceType, bindingType);
}
return undefined;
}
deserialize(value, options) {
const resourceType = this.incomingType();
const bindingType = this.bindingType();
const _d = (value, options) => {
const parser = this.__parser(value, options, resourceType, bindingType);
return parser !== undefined ? parser.deserialize(value, options) : value;
};
return Array.isArray(value)
? value.map((v) => _d(v, options))
: _d(value, options);
}
serialize(value, options) {
const resourceType = this.outgoingType();
const bindingType = this.bindingType();
const _s = (value, options) => {
const parser = this.__parser(value, options, resourceType, bindingType);
return parser !== undefined ? parser.serialize(value, options) : value;
};
return Array.isArray(value)
? value.map((v) => _s(v, options))
: _s(value, options);
}
encode(value, options) {
const resourceType = this.outgoingType();
const bindingType = this.bindingType();
const _e = (value, options) => {
const parser = this.__parser(value, options, resourceType, bindingType);
return parser !== undefined ? parser.encode(value, options) : value;
};
return Array.isArray(value)
? value.map((v) => _e(v, options))
: _e(value, options);
}
toJson() {
return {
segments: this.pathSegments.toJson(),
options: this.queryOptions.toJson(),
};
}
cloneSegments() {
return this.pathSegments.clone();
}
//#region Query Options
clearQuery() {
this.queryOptions.clear();
return this;
}
cloneQuery() {
return this.queryOptions.clone();
}
/**
* Handle the path segments of the resource
* Create an object handler for mutate the path segments of the resource
* @param f Function context for handle the segments
* @returns ODataActionResource
*/
segment(f) {
const type = this.outgoingType();
f(new ODataPathSegmentsHandler(this.pathSegments), type !== undefined ? this.api.structuredType(type) : undefined);
return this;
}
/**
* Handle the query options of the resource
* Create an object handler for mutate the query options of the resource
* @param f Function context for handle the query options
*/
query(f) {
const type = this.outgoingType();
f(new ODataQueryOptionsHandler(this.queryOptions), type !== undefined ? this.api.structuredType(type) : undefined);
return this;
}
transform(opts, { type, fields, } = {}) {
if (type === undefined) {
type = Strings.uniqueId({ prefix: 'Transformation', suffix: 'Type' });
}
// Resolve Structured Type
let structuredType = this.api.findStructuredType(type);
if (structuredType === undefined) {
// Resolve Schema
let schema = this.api.findSchema(type);
if (schema === undefined) {
const namespace = type.substring(0, type.lastIndexOf('.')) ?? this.api.name;
schema = this.api.createSchema({ namespace });
}
const name = type.substring(type.lastIndexOf('.'));
structuredType = schema.createStructuredType({ name, fields });
}
// Segments
const segments = this.cloneSegments();
segments.last()?.incomingType(structuredType.type());
// Query
const query = this.cloneQuery();
const handler = new ODataQueryOptionsHandler(query);
handler.apply(opts);
const Ctor = this.constructor;
return new Ctor(this.api, {
segments,
query,
});
}
static resolveKey(value, schema) {
if (isQueryCustomType(value)) {
return value;
}
else if (Types.isPlainObject(value)) {
return schema instanceof ODataStructuredType
? schema.resolveKey(value)
: Objects.resolveKey(value);
}
return value;
}
resolveKey(value) {
const type = this.outgoingType();
const structured = type !== undefined ? this.api.structuredType(type) : undefined;
return ODataResource.resolveKey(value, structured);
}
//#endregion
get(options = {}) {
return this.api.request('GET', this, options);
}
post(body, options = {}) {
return this.api.request('POST', this, { body, ...options });
}
put(body, options = {}) {
return this.api.request('PUT', this, { body, ...options });
}
patch(body, options = {}) {
return this.api.request('PATCH', this, { body, ...options });
}
delete(options = {}) {
return this.api.request('DELETE', this, options);
}
}
class ODataActionResource extends ODataResource {
//#region Factory
static factory(api, { path, outgoingType, incomingType, bindingType, segments, }) {
segments = segments ?? new ODataPathSegments();
const segment = segments.add(PathSegment.action, path);
if (outgoingType !== undefined) {
segment.outgoingType(outgoingType);
}
if (incomingType !== undefined) {
segment.incomingType(incomingType);
}
if (bindingType !== undefined) {
segment.bindingType(bindingType);
}
return new ODataActionResource(api, { segments });
}
static fromResource(resource, path) {
const baseType = resource.outgoingType();
const callable = resource.api.findCallable(path, baseType);
const outgoingType = callable?.type();
const bindingType = callable?.binding()?.type;
const incomingType = callable?.returnType();
const action = ODataActionResource.factory(resource.api, {
path,
outgoingType,
bindingType,
incomingType,
segments: resource.cloneSegments(),
});
// Switch entitySet to binding type if available
if (bindingType !== undefined && bindingType !== baseType) {
let entitySet = resource.api.findEntitySet(bindingType);
if (entitySet !== undefined) {
action.segment((s) => s.entitySet().path(entitySet.name));
}
}
return action;
}
clone() {
return super.clone();
}
//#endregion
//#region Requests
post(params, options) {
return super.post(params, options);
}
call(params, options = {}) {
return this.clone().post(params, options);
}
/**
* Execute the action and return the result as a property
* @param params Parameters for the action
* @param options Options for the request
* @returns Observable of the result of the action
*/
callProperty(params, options = {}) {
return this.call(params, { responseType: 'property', ...options }).pipe(map(({ property }) => property));
}
/**
* Execute the action and return the result as a entity
* @param params Parameters for the action
* @param options Options for the request
* @returns Observable of the result of the action
*/
callEntity(params, options = {}) {
return this.call(params, { responseType: 'entity', ...options }).pipe(map(({ entity }) => entity));
}
/**
* Execute the action and return the result as a model
* @param params Parameters for the action
* @param options Options for the request
* @returns Observable of the result of the action
*/
callModel(params, options = {}) {
return this.call(params, { responseType: 'entity', ...options }).pipe(map(({ entity, annots }) => entity
? this.asModel(entity, { annots, ModelType: options?.ModelType })
: null));
}
/**
* Execute the action and return the result as a entities
* @param params Parameters for the action
* @param options Options for the request
* @returns Observable of the result of the action
*/
callEntities(params, options = {}) {
return this.call(params, { responseType: 'entities', ...options }).pipe(map(({ entities }) => entities));
}
/**
* Execute the action and return the result as a collection
* @param params Parameters for the action
* @param options Options for the request
* @returns Observable of the result of the action
*/
callCollection(params, options = {}) {
return this.call(params, { responseType: 'entities', ...options }).pipe(map(({ entities, annots }) => entities
? this.asCollection(entities, {
annots,
CollectionType: options?.CollectionType,
})
: null));
}
//#endregion
callArraybuffer(params, { alias, ...options } = {}) {
return this.call(params, {
responseType: 'arraybuffer',
alias,
...options,
});
}
callBlob(params, { alias, ...options } = {}) {
return this.call(params, { responseType: 'blob', alias, ...options });
}
}
const Arrays = {
// Zip arrays
// Example
// Arrays.zip([1, 2, 3, 4, 5, 6], ['a', 'b', 'c', 'd', 'e', 'f'])
// => [[1, 'a'], [2, 'b'], [3, 'c'], [4, 'd'], [5, 'e'], [6, 'f']]
zip: (...arrays) => {
return arrays[0].map((_, i) => arrays.map((array) => array[i]));
},
};
class ODataBatchRequest extends Subject {
request;
id;
group;
constructor(request) {
super();
this.request = request;
this.id = Strings.uniqueId({ prefix: 'r' });
this.group = Strings.uniqueId({ prefix: 'g' });
}
toString() {
return this.toLegacy();
}
toLegacy({ relativeUrls } = {}) {
//TODO: Relative or Absolute url ?
let res = [
`${this.request.method} ${relativeUrls ? this.request.pathWithParams : this.request.urlWithParams} ${HTTP11}`,
];
if (this.request.method === 'POST' ||
this.request.method === 'PATCH' ||
this.request.method === 'PUT') {
res.push(`${CONTENT_TYPE}: ${APPLICATION_JSON}`);
}
if (this.request.headers instanceof HttpHeaders) {
let headers = this.request.headers;
res = [
...res,
...headers
.keys()
.map((key) => `${key}: ${(headers.getAll(key) || []).join(',')}`),
];
}
if (this.request.method === 'GET' || this.request.method === 'DELETE') {
res.push(NEWLINE);
}
else {
res.push(`${NEWLINE}${JSON.stringify(this.request.body)}`);
}
return res.join(NEWLINE);
}
toJson({ relativeUrls } = {}) {
let res = {
id: this.id,
method: this.request.method,
url: relativeUrls
? this.request.pathWithParams
: this.request.urlWithParams,
//'atomicityGroup': this.group
//"dependsOn": ["g1", "g2", "r2"]
};
if (this.request.headers instanceof HttpHeaders) {
let headers = this.request.headers;
res['headers'] = headers
.keys()
.map((key) => `${key}: ${(headers.getAll(key) || []).join(',')}`);
}
if (!(this.request.method === 'GET' || this.request.method === 'DELETE')) {
res['body'] = this.request.body;
}
return res;
}
onLoad(response) {
if (response.ok) {
this.next(response);
this.complete();
}
else {
// An unsuccessful request is delivered on the error channel.
this.error(response);
}
}
onError(response) {
this.error(response);
}
}
/**
* OData Batch Resource
* https://www.odata.org/getting-started/advanced-tutorial/#batch
*/
class ODataBatchResource extends ODataResource {
// VARIABLES
_requests = [];
requests() {
return this._requests.map((r) => r.request);
}
_responses = null;
responses() {
return this._responses;
}
//#region Factory
static factory(api) {
let segments = new ODataPathSegments();
segments.add(PathSegment.batch, $BATCH);
return new ODataBatchResource(api, { segments });
}
clone() {
const batch = super.clone();
batch._requests = [...this._requests];
return batch;
}
//#endregion
storeRequester() {
const current = this.api.requester;
// Switch to the batch requester
this.api.requester = (req) => {
if (req.api !== this.api)
throw new Error('Batch Request are for the same api.');
if (req.observe === 'events')
throw new Error("Batch Request does not allows observe == 'events'.");
this._requests.push(new ODataBatchRequest(req));
return this._requests[this._requests.length - 1];
};
return current;
}
restoreRequester(handler) {
this.api.requester = handler;
}
/**
* Add to batch request
* @param ctx The context for the request
* @returns The result of execute the context
*/
add(ctx) {
// Store original requester
var handler = this.storeRequester();
// Execute the context
const result = ctx(this);
// Restore original requester
this.restoreRequester(handler);
return result;
}
send(options) {
if (this.api.options.jsonBatchFormat) {
return this.sendJson(options);
}
else {
return this.sendLegacy(options);
}
}
sendJson(options) {
const headers = Http.mergeHttpHeaders((options && options.headers) || {}, {
[ODATA_VERSION]: VERSION_4_0,
});
return this.api
.request('POST', this, {
body: ODataBatchResource.buildJsonBody(this._requests, this.api.options),
responseType: 'json',
observe: 'response',
headers: headers,
params: options ? options.params : undefined,
withCredentials: options ? options.withCredentials : undefined,
})
.pipe(map$1((response) => {
if (this._responses == null) {
this._responses = [];
}
this._responses = [
...this._responses,
...ODataBatchResource.parseJsonResponse(this._requests, response),
];
//HACK: tuple[1] === undefined
Arrays.zip(this._requests, this._responses).forEach((tuple) => {
if (!tuple[0].isStopped && tuple[1])
tuple[0].onLoad(tuple[1]);
});
return response;
}));
}
sendLegacy(options) {
const bound = Strings.uniqueId({ prefix: BATCH_PREFIX });
const headers = Http.mergeHttpHeaders((options && options.headers) || {}, {
[ODATA_VERSION]: VERSION_4_0,
[CONTENT_TYPE]: MULTIPART_MIXED_BOUNDARY + bound,
[ACCEPT]: MULTIPART_MIXED,
});
return this.api
.request('POST', this, {
body: ODataBatchResource.buildLegacyBody(bound, this._requests, this.api.options),
responseType: 'text',
observe: 'response',
headers: headers,
params: options ? options.params : undefined,
withCredentials: options ? options.withCredentials : undefined,
})
.pipe(map$1((response) => {
if (this._responses == null) {
this._responses = [];
}
this._responses = [
...this._responses,
...ODataBatchResource.parseLegacyResponse(this._requests, response),
];
Arrays.zip(this._requests, this._responses).forEach((tuple) => {
if (!tuple[0].isStopped && tuple[1])
tuple[0].onLoad(tuple[1]);
});
return response;
}));
}
/**
* Execute the batch request
* @param ctx The context for the request
* @param options The options of the batch request
* @returns The result of execute the context
*/
exec(ctx, options) {
let result = this.add(ctx);
return this.send(options).pipe(map$1((response) => [result, response]));
}
body() {
return ODataBatchResource.buildLegacyBody(Strings.uniqueId({ prefix: BATCH_PREFIX }), this._requests, this.api.options);
}
json() {
return ODataBatchResource.buildJsonBody(this._requests, this.api.options);
}
static buildLegacyBody(batchBoundary, requests, options) {
let res = [];
let changesetBoundary = null;
let changesetId = 1;
for (const request of requests) {
// if method is GET and there is a changeset boundary open then close it
if (request.request.method === 'GET' && changesetBoundary !== null) {
res.push(`${BOUNDARY_PREFIX_SUFFIX}${changesetBoundary}${BOUNDARY_PREFIX_SUFFIX}`);
changesetBoundary = null;
}
// if there is no changeset boundary open then open a batch boundary
if (changesetBoundary === null) {
res.push(`${BOUNDARY_PREFIX_SUFFIX}${batchBoundary}`);
}
// if method is not GET and there is no changeset boundary open then open a changeset boundary
if (request.request.method !== 'GET') {
if (changesetBoundary === null) {
changesetBoundary = Strings.uniqueId({ prefix: CHANGESET_PREFIX });
res.push(`${CONTENT_TYPE}: ${MULTIPART_MIXED_BOUNDARY}${changesetBoundary}`);
res.push(NEWLINE);
}
res.push(`${BOUNDARY_PREFIX_SUFFIX}${changesetBoundary}`);
}
res.push(`${CONTENT_TYPE}: ${APPLICATION_HTTP}`);
res.push(`${CONTENT_TRANSFER_ENCODING}: ${BINARY}`);
if (request.request.method !== 'GET') {
res.push(`${CONTENT_ID}: ${changesetId++}`);
}
res.push(NEWLINE);
res.push(`${request.toLegacy(options)}`);
}
if (res.length) {
if (changesetBoundary !== null) {
res.push(`${BOUNDARY_PREFIX_SUFFIX}${changesetBoundary}${BOUNDARY_PREFIX_SUFFIX}`);
changesetBoundary = null;
}
res.push(`${BOUNDARY_PREFIX_SUFFIX}${batchBoundary}${BOUNDARY_PREFIX_SUFFIX}`);
}
return res.join(NEWLINE);
}
static buildJsonBody(requests, options) {
return {
requests: requests.map((request) => request.toJson(options)),
};
}
static parseLegacyResponse(requests, response) {
let chunks = [];
const contentType = response.headers.get(CONTENT_TYPE) || '';
const batchBoundary = Http.boundaryDelimiter(contentType);
const endLine = Http.boundaryEnd(batchBoundary);
const lines = (response.body || '').split(NEWLINE_REGEXP);
let changesetResponses = null;
let contentId = null;
let changesetBoundary = null;
let changesetEndLine = null;
let startIndex = null;
for (let index = 0; index < lines.length; index++) {
const line = lines[index];
if (line.startsWith(CONTENT_TYPE)) {
const contentTypeValue = Http.headerValue(line);
if (contentTypeValue === MULTIPART_MIXED) {
changesetResponses = [];
contentId = null;
changesetBoundary = Http.boundaryDelimiter(line);
changesetEndLine = Http.boundaryEnd(changesetBoundary);
startIndex = null;
}
continue;
}
else if (changesetResponses !== null && line.startsWith(CONTENT_ID)) {
contentId = Number(Http.headerValue(line));
}
else if (line.startsWith(HTTP11)) {
startIndex = index;
}
else if (line === batchBoundary ||
line === changesetBoundary ||
line === endLine ||
line === changesetEndLine) {
if (!startIndex) {
continue;
}
const chunk = lines.slice(startIndex, index);
if (changesetResponses !== null && contentId !== null) {
changesetResponses[contentId] = chunk;
}
else {
chunks.push(chunk);
}
if (line === batchBoundary || line === changesetBoundary) {
startIndex = index + 1;
}
else if (line === endLine || line === changesetEndLine) {
if (changesetResponses !== null) {
for (const response of changesetResponses) {
if (response) {
chunks.push(response);
}
}
}
changesetResponses = null;
changesetBoundary = null;
changesetEndLine = null;
startIndex = null;
}
}
}
return chunks.map((chunk, index) => {
let request = requests[index].request;
let { code, message } = Http.parseResponseStatus(chunk[0]);
chunk = chunk.slice(1);
let headers = new HttpHeaders();
var index = 1;
for (; index < chunk.length; index++) {
const batchBodyLine = chunk[index];
if (batchBodyLine === '') {
break;
}
const batchBodyLineParts = batchBodyLine.split(': ');
headers = headers.append(batchBodyLineParts[0].trim(), batchBodyLineParts[1].trim());
}
let body = '';
for (; index < chunk.length; index++) {
body += chunk[index];
}
if (code === 0) {
code = !!body ? 200 : 0;
}
let ok = code >= 200 && code < 300;
if (request.responseType === 'json' && typeof body === 'string') {
const originalBody = body;
body = body.replace(XSSI_PREFIX, '');
try {
body = body !== '' ? JSON.parse(body) : null;
}
catch (error) {
body = originalBody;
if (ok) {
ok = false;
body = { error, text: body };
}
}
}
return ok
? new HttpResponse({
body,
headers,
status: code,
statusText: message,
url: request.urlWithParams,
})
: new HttpErrorResponse({
// The error in this case is the response body (error from the server).
error: body,
headers,
status: code,
statusText: message,
url: request.urlWithParams,
});
});
}
static parseJsonResponse(requests, response) {
const responses = (response.body ? response.body : {})['responses'] ?? [];
return responses.map((response, index) => {
let request = requests[index].request;
let code = response['status'];
let headers = new HttpHeaders(response['headers']);
let body = response['body'];
if (code === 0) {
code = !!body ? 200 : 0;
}
let ok = code >= 200 && code < 300;
if (request.responseType === 'json' && typeof body === 'string') {
const originalBody = body;
body = body.replace(XSSI_PREFIX, '');
try {
body = body !== '' ? JSON.parse(body) : null;
}
catch (error) {
body = originalBody;
if (ok) {
ok = false;
body = { error, text: body };
}
}
}
return ok
? new HttpResponse({
body,
headers,
status: code,
url: request.urlWithParams,
})
: new HttpErrorResponse({
// The error in this case is the response body (error from the server).
error: body,
headers,
status: code,
url: request.urlWithParams,
});
});
}
}
class ODataCountResource extends ODataResource {
//#region Factory
static factory(api, { segments, query, }) {
const currentType = segments.last()?.outgoingType();
const segment = segments.add(PathSegment.count, $COUNT);
segment.outgoingType(currentType);
segment.incomingType(EdmType.Int32);
query?.keep(QueryOption.filter, QueryOption.search);
return new ODataCountResource(api, { segments, query });
}
clone() {
return super.clone();
}
//#endregion
//#region Requests
get(options) {
return super.get({ responseType: 'value', ...options });
}
//#endregion
//#region Shortcuts
/**
* Fetch the count of the set.
* @param options Options for the request
* @returns The count of the set
*/
fetch(options) {
return this.get(options);
}
}
class ODataFunctionResource extends ODataResource {
//#region Factory
static factory(api, { path, outgoingType, incomingType, bindingType, segments, }) {
segments = segments ?? new ODataPathSegments();
const segment = segments.add(PathSegment.function, path);
if (outgoingType !== undefined) {
segment.outgoingType(outgoingType);
}
if (incomingType !== undefined) {
segment.incomingType(incomingType);
}
if (bindingType !== undefined) {
segment.bindingType(bindingType);
}
return new ODataFunctionResource(api, { segments });
}
static fromResource(resource, path) {
const baseType = resource.outgoingType();
const callable = resource.api.findCallable(path, baseType);
const outgoingType = callable?.type();
const bindingType = callable?.binding()?.type;
const incomingType = callable?.returnType();
const func = ODataFunctionResource.factory(resource.api, {
path,
outgoingType,
bindingType,
incomingType,
segments: resource.cloneSegments(),
});
// Switch entitySet to binding type if available
if (bindingType !== undefined && bindingType !== baseType) {
let entitySet = resource.api.findEntitySet(bindingType);
if (entitySet !== undefined) {
func.segment((s) => s.entitySet().path(entitySet.name));
}
}
return func;
}
clone() {
return super.clone();
}
//#endregion
parameters(params, { alias } = {}) {
let parameters = params !== null ? this.encode(params) : null;
if (alias && parameters !== null) {
this.query((q) => {
parameters = Object.entries(parameters).reduce((acc, [name, param]) => {
return Object.assign(acc, { [name]: q.alias(param, name) });
}, {});
});
}
return this.clone().segment((s) => s.function().parameters(parameters));
}
//#region Requests
get(options) {
return super.get(options);
}
call(params, { alias, ...options } = {}) {
return this.parameters(params, { alias }).get(options);
}
/**
* Execute the function with the given parameters and return the result as a property
* @param params Parameters to be sent to the function
* @param alias If true, the parameters will be send using aliases
* @param options Options for the request
* @returns Observable of the result of the function
*/
callProperty(params, { alias, ...options } = {}) {
return this.call(params, {
responseType: 'property',
alias,
...options,
}).pipe(map(({ property }) => property));
}
/**
* Execute the function with the given parameters and return the result as a entity
* @param params Parameters to be sent to the function
* @param alias If true, the parameters will be send using aliases
* @param options Options for the request
* @returns Observable of the result of the function
*/
callEntity(params, { alias, ...options } = {}) {
return this.call(params, {
responseType: 'entity',
alias,
...options,
}).pipe(map(({ entity }) => entity));
}
/**
* Execute the function with the given parameters and return the result as a model
* @param params Parameters to be sent to the function
* @param alias If true, the parameters will be send using aliases
* @param options Options for the request
* @returns Observable of the result of the function
*/
callModel(params, { alias, ModelType, ...options } = {}) {
return this.call(params, {
responseType: 'entity',
alias,
...options,
}).pipe(map(({ entity, annots }) => entity ? this.asModel(entity, { annots, ModelType }) : null));
}
/**
* Execute the function with the given parameters and return the result as a entities
* @param params Parameters to be sent to the function
* @param alias If true, the parameters will be send using aliases
* @param options Options for the request
* @returns Observable of the result of the function
*/
callEntities(params, { alias, ...options } = {}) {
return this.call(params, {
responseType: 'entities',
alias,
...options,
}).pipe(map(({ entities }) => entities));
}
/**
* Execute the function with the given parameters and return the result as a collection
* @param params Parameters to be sent to the function
* @param alias If true, the parameters will be send using aliases
* @param options Options for the request
* @returns Observable of the result of the function
*/
callCollection(params, { alias, CollectionType, ...options } = {}) {
return this.call(params, {
responseType: 'entities',
alias,
...options,
}).pipe(map(({ entities, annots }) => entities
? this.asCollection(entities, { annots, CollectionType })
: null));
}
//#endregion
callArraybuffer(params, { alias, ...options } = {}) {
return this.call(params, {
responseType: 'arraybuffer',
alias,
...options,
});
}
callBlob(params, { alias, ...options } = {}) {
return this.call(params, { responseType: 'blob', alias, ...options });
}
}
class ODataMediaResource extends ODataResource {
//#region Factory
static factory(api, { segments, query, }) {
segments.add(PathSegment.value, $VALUE);
return new ODataMediaResource(api, { segments, query });
}
clone() {
return super.clone();
}
//#endregion
//#region Requests
get(options) {
return super.get(options);
}
put(data, options = {}) {
return super.put(data, options);
}
fetch(options) {
return this.get(options);
}
fetchArraybuffer(options = {}) {
return this.fetch({ responseType: 'arraybuffer', ...options });
}
fetchBlob(options = {}) {
return this.fetch({ responseType: 'blob', ...options });
}
upload(data, options = {}) {
return this.put(data, options);
}
uploadArrayBuffer(data, contentType, options = {}) {
options.headers = Http.mergeHttpHeaders(options.headers || {}, {
'Content-Type': contentType,
});
return this.upload(data, options);
}
uploadBlob(data, options = {}) {
return this.upload(data, options);
}
}
class ODataValueResource extends ODataResource {
//#region Factory
static factory(api, { segments, }) {
const currentType = segments.last()?.outgoingType();
const segment = segments.add(PathSegment.value, $VALUE);
segment.incomingType(currentType);
return new ODataValueResource(api, { segments });
}
static fromResource(resource) {
const baseType = resource.outgoingType();
let baseSchema = baseType !== undefined
? resource.api.structuredType(baseType)
: undefined;
const value = ODataValueResource.factory(resource.api, {
segments: resource.cloneSegments(),
});
// Switch entitySet to binding type if available
if (baseSchema !== undefined && baseSchema.type() !== baseType) {
let entitySet = resource.api.findEntitySet(baseSchema.type());
if (entitySet !== undefined) {
value.segment((s) => s.entitySet().path(entitySet.name));
}
}
return value;
}
clone() {
return super.clone();
}
//#endregion
//#region Requests
get(options = {}) {
return super.get({ responseType: 'value', ...options });
}
//#endregion
//#region Shortcuts
/**
* Fetch the value of the resource.
* @param options OData options.
* @returns Observable of the value.
*/
fetch(options) {
return this.get(options);
}
}
class ODataPropertyResource extends ODataResource {
//#region Factory
static factory(api, { path, type, segments, }) {
const segment = segments.add(PathSegment.property, path);
if (type !== undefined) {
segment.outgoingType(type);
segment.incomingType(type);
}
return new ODataPropertyResource(api, {
segments,
});
}
static fromResource(resource, path) {
const baseType = resource.outgoingType();
let baseSchema = baseType !== undefined
? resource.api.structuredType(baseType)
: undefined;
let fieldType;
if (baseSchema !== undefined) {
const field = baseSchema.field(path);
fieldType = field?.type;
baseSchema =
field !== undefined
? baseSchema.findParentSchemaForField(field)
: undefined;
}
const property = ODataPropertyResource.factory(resource.api, {
path,
type: fieldType,
segments: resource.cloneSegments(),
});
// Switch entitySet to binding type if available
if (baseSchema !== undefined && baseSchema.type() !== baseType) {
let entitySet = resource.api.findEntitySet(baseSchema.type());
if (entitySet !== undefined) {
property.segment((s) => s.entitySet().path(entitySet.name));
}
}
return property;
}
clone() {
return super.clone();
}
transform(opts, { type, fields, } = {}) {
return super.transform(opts, {
type,
fields,
});
}
//#endregion
key(value) {
const property = this.clone();
var key = this.resolveKey(value);
if (key !== undefined)
property.segment((s) => s.property().key(key));
return property;
}
keys(values) {
const property = this.clone();
const types = this.pathSegments.types({ key: true });
const keys = values.map((value, index) => ODataResource.resolveKey(value, this.api.findStructuredType(types[index])));
property.segment((s) => s.keys(keys));
return property;
}
value() {
return ODataValueResource.fromResource(this);
}
count() {
return ODataCountResource.factory(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
}
/*
navigationProperty<N>(path: string) {
let schema: ODataStructuredType<N> | undefined;
if (this.schema instanceof ODataStructuredType) {
const field = this.schema.field<any>(path as keyof T);
schema =
field !== undefined
? this.schema.findSchemaForField<N>(field)
: undefined;
}
return ODataNavigationPropertyResource.factory<N>(this.api, {
path,
schema,
segments: this.cloneSegments(),
query: this.cloneQuery<N>(),
});
}
*/
property(path) {
return ODataPropertyResource.fromResource(this, path);
}
//#region Requests
get(options) {
return super.get(options);
}
fetch(options = {}) {
return this.get(options);
}
/**
* Fetch the property value
* @param options Options for the request
* @returns The property value
*/
fetchProperty(options = {}) {
return this.fetch({ responseType: 'property', ...options }).pipe(map(({ property }) => property));
}
/**
* Fetch the entity
* @param options Options for the request
* @returns The entity
*/
fetchEntity(options = {}) {
return this.fetch({ responseType: 'entity', ...options }).pipe(map(({ entity }) => entity));
}
fetchModel(options = {}) {
return this.fetch({ responseType: 'entity', ...options }).pipe(map(({ entity, annots }) => entity
? this.asModel(entity, { annots, ModelType: options?.ModelType })
: null));
}
/**
* Fetch the entities
* @param options Options for the request
* @returns The entities
*/
fetchEntities(options = {}) {
return this.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities }) => entities));
}
fetchCollection(options = {}) {
return this.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities, annots }) => entities
? this.asCollection(entities, {
annots,
CollectionType: options?.CollectionType,
})
: null));
}
fetchOne(options) {
let res = this.clone();
res.query((q) => q.top(1));
return res.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities, annots }) => ({
entity: entities !== null && entities.length === 1 ? entities[0] : null,
annots,
})));
}
fetchMany(top, options) {
let res = this.clone();
let fetch = (opts) => {
if (opts) {
res.query((q) => q.paging(opts));
}
return res.fetch({ responseType: 'entities', ...options });
};
return fetch({ top }).pipe(expand(({ annots }) => annots.skip || annots.skiptoken ? fetch(annots) : EMPTY), map(({ entities, annots }) => ({ entities: entities || [], annots })), reduce((acc, { entities, annots }) => ({
entities: [...(acc.entities || []), ...(entities || [])],
annots: acc.annots.union(annots),
})));
}
/**
* Fetch all entities
* @param options Options for the request
* @returns All entities
*/
fetchAll(options = {}) {
let res = this.clone();
// Clean Paging
res.query((q) => q.removePaging());
let fetch = (opts) => {
if (opts) {
res.query((q) => q.paging(opts));
}
return res.fetch({ responseType: 'entities', ...options });
};
return fetch().pipe(expand(({ annots }) => annots.skip || annots.skiptoken ? fetch(annots) : EMPTY), map(({ entities, annots }) => ({ entities: entities || [], annots })), reduce((acc, { entities, annots }) => ({
entities: [...(acc.entities || []), ...(entities || [])],
annots: acc.annots.union(annots),
})));
}
}
class ODataReferenceResource extends ODataResource {
//#region Factory
static factory(api, { segments }) {
segments.add(PathSegment.reference, $REF);
return new ODataReferenceResource(api, { segments });
}
clone() {
return super.clone();
}
//#endregion
//#region Requests
post(target, options) {
return super.post({ [ODATA_ID]: target.endpointUrl({ params: false }) }, options);
}
put(target, options) {
return super.put({ [ODATA_ID]: target.endpointUrl({ params: false }) }, options);
}
delete({ etag, target, ...options } = {}) {
if (target) {
options.params = { [$ID]: target.endpointUrl({ params: false }) };
}
return super.delete({ etag, ...options });
}
//#endregion
//#region Shortcuts for collections
/**
* Add the given target to the collection.
* @param target The target resource
* @param options Options for the request
* @returns Observable of the response
*/
add(target, options) {
return this.post(target, options);
}
/**
* Remove the given target from the collection.
* @param target The target resource
* @param options Options for the request
* @returns Observable of the response
*/
remove(target, options) {
return this.delete({ target, ...options });
}
//#region
//#region Shortcuts for single
/**
* Set the reference to the given target.
* @param target The target resource
* @param options Options for the request
* @returns Observable of the response
*/
set(target, options) {
return this.put(target, options);
}
/**
* Unset the reference to the given target.
* @param options Options for the request.
* @returns Observable of the response
*/
unset(options) {
return this.delete(options);
}
fetch(options = {}) {
return this.get(options);
}
/**
* Fetch the entity
* @param options Options for the request
* @returns The entity
*/
fetchEntity(options = {}) {
return this.fetch({ responseType: 'entity', ...options }).pipe(map(({ entity }) => entity));
}
/**
* Fetch entities
* @param options Options for the request
* @returns The entities
*/
fetchEntities(options = {}) {
return this.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities }) => entities));
}
}
/**
* OData Navigation Property Resource
* https://www.odata.org/getting-started/advanced-tutorial/#containment
* https://www.odata.org/getting-started/advanced-tutorial/#derived
*/
class ODataNavigationPropertyResource extends ODataResource {
//#region Factory
static factory(api, { path, type, segments, }) {
const segment = segments.add(PathSegment.navigationProperty, path);
if (type !== undefined) {
segment.outgoingType(type);
segment.incomingType(type);
}
return new ODataNavigationPropertyResource(api, {
segments,
});
}
static fromResource(resource, path) {
const baseType = resource.outgoingType();
let baseSchema = baseType !== undefined
? resource.api.structuredType(baseType)
: undefined;
let fieldType;
if (baseSchema !== undefined) {
const field = baseSchema.field(path);
fieldType = field?.type;
baseSchema =
field !== undefined
? baseSchema.findParentSchemaForField(field)
: undefined;
}
const navigation = ODataNavigationPropertyResource.factory(resource.api, {
path,
type: fieldType,
segments: resource.cloneSegments(),
});
// Switch entitySet to binding type if available
if (baseSchema !== undefined && baseSchema.type() !== baseType) {
let entitySet = resource.api.findEntitySet(baseSchema.type());
if (entitySet !== undefined) {
navigation.segment((s) => s.entitySet().path(entitySet.name));
}
}
return navigation;
}
clone() {
return super.clone();
}
transform(opts, { type, fields, } = {}) {
return super.transform(opts, {
type,
fields,
});
}
//#endregion
key(value) {
const navigation = this.clone();
var key = this.resolveKey(value);
if (key !== undefined)
navigation.segment((s) => s.navigationProperty().key(key));
return navigation;
}
keys(values) {
const navigation = this.clone();
const types = this.pathSegments.types({ key: true });
const keys = values.map((value, index) => ODataResource.resolveKey(value, this.api.findStructuredType(types[index])));
navigation.segment((s) => s.keys(keys));
return navigation;
}
media() {
return ODataMediaResource.factory(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
}
reference() {
return ODataReferenceResource.factory(this.api, {
segments: this.cloneSegments(),
});
}
navigationProperty(path) {
return ODataNavigationPropertyResource.fromResource(this, path);
}
property(path) {
return ODataPropertyResource.fromResource(this, path);
}
count() {
return ODataCountResource.factory(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
}
cast(type) {
const thisType = this.incomingType();
const baseSchema = thisType !== undefined ? this.api.structuredType(thisType) : undefined;
const castSchema = this.api.findStructuredType(type);
if (castSchema !== undefined &&
baseSchema !== undefined &&
!castSchema.isSubtypeOf(baseSchema))
throw new Error(`Cannot cast to ${type}`);
const segments = this.cloneSegments();
segments.add(PathSegment.type, type).incomingType(type);
return new ODataNavigationPropertyResource(this.api, {
segments,
query: this.cloneQuery(),
});
}
//#region Requests
post(attrs, options = {}) {
return super.post(attrs, { responseType: 'entity', ...options });
}
put(attrs, options = {}) {
return super.put(attrs, { responseType: 'entity', ...options });
}
patch(attrs, options = {}) {
return super.patch(attrs, { responseType: 'entity', ...options });
}
delete(options = {}) {
return super.delete({ responseType: 'entity', ...options });
}
get(options = {}) {
return super.get(options);
}
//#endregion
//#region Shortcuts
/**
* Create a new entity
* @param attrs The entity attributes
* @param options Options for the request
* @returns The created entity with the annotations
*/
create(attrs, options) {
return this.post(attrs, options);
}
/**
* Update an existing entity
* @param attrs The entity attributes
* @param options Options for the request
* @returns The updated entity with the annotations
*/
update(attrs, options) {
return this.put(attrs, options);
}
/**
* Modify an existing entity
* @param attrs The entity attributes
* @param options Options for the request
* @returns The modified entity with the annotations
*/
modify(attrs, options) {
return this.patch(attrs, options);
}
/**
* Delete an existing entity
* @param options Options for the request
* @returns An observable of the destroy
*/
destroy(options) {
return this.delete(options);
}
fetch(options = {}) {
if (!this.hasEntityKey())
return throwError(() => new Error('fetch: Navigation resource without entity key'));
return this.get(options);
}
/**
* Fetch the entity
* @param options Options for the request
* @returns The entity
*/
fetchEntity(options = {}) {
return this.fetch({ responseType: 'entity', ...options }).pipe(map(({ entity }) => entity));
}
fetchModel(options = {}) {
return this.fetch({ responseType: 'entity', ...options }).pipe(map(({ entity, annots }) => entity
? this.asModel(entity, { annots, ModelType: options?.ModelType })
: null));
}
/**
* Fetch entities
* @param options Options for the request
* @returns The entities
*/
fetchEntities(options = {}) {
return this.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities }) => entities));
}
fetchCollection(options = {}) {
return this.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities, annots }) => entities
? this.asCollection(entities, {
annots,
CollectionType: options?.CollectionType,
})
: null));
}
/**
* Fetch all entities
* @param options Options for the request
* @returns All entities
*/
fetchAll(options = {}) {
let res = this.clone();
// Clean Paging
res.query((q) => q.removePaging());
let fetch = (opts) => {
if (opts) {
res.query((q) => q.paging(opts));
}
return res.fetch({ responseType: 'entities', ...options });
};
return fetch().pipe(expand(({ annots }) => annots.skip || annots.skiptoken ? fetch(annots) : EMPTY), map(({ entities, annots }) => ({ entities: entities || [], annots })), reduce((acc, { entities, annots }) => ({
entities: [...(acc.entities || []), ...(entities || [])],
annots: acc.annots.union(annots),
})));
}
//#endregion
fetchMany(top, options) {
let res = this.clone();
let fetch = (opts) => {
if (opts) {
res.query((q) => q.paging(opts));
}
return res.fetch({ responseType: 'entities', ...options });
};
return fetch({ top }).pipe(expand(({ annots }) => annots.skip || annots.skiptoken ? fetch(annots) : EMPTY), map(({ entities, annots }) => ({ entities: entities || [], annots })), reduce((acc, { entities, annots }) => ({
entities: [...(acc.entities || []), ...(entities || [])],
annots: acc.annots.union(annots),
})));
}
fetchOne(options) {
const res = this.clone();
res.query((q) => q.top(1));
return res.fetch({ responseType: 'entities', ...options }).pipe(map(({ entities, annots }) => ({
entity: entities !== null && entities.length === 1 ? entities[0] : null,
annots,
})));
}
}
class ODataEntityResource extends ODataResource {
//#region Factory
static factory(api, { segments, query, }) {
query?.keep(QueryOption.expand, QueryOption.select, QueryOption.format);
return new ODataEntityResource(api, { segments, query });
}
clone() {
return super.clone();
}
//#endregion
key(value) {
const entity = this.clone();
var key = this.resolveKey(value);
if (key !== undefined)
entity.segment((s) => s.entitySet().key(key));
return entity;
}
keys(values) {
const entity = this.clone();
const types = this.pathSegments.types({ key: true });
const keys = values.map((value, index) => ODataResource.resolveKey(value, this.api.findStructuredType(types[index])));
entity.segment((s) => s.keys(keys));
return entity;
}
media() {
return ODataMediaResource.factory(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
}
navigationProperty(path) {
return ODataNavigationPropertyResource.fromResource(this, path);
}
property(path) {
return ODataPropertyResource.fromResource(this, path);
}
action(path) {
return ODataActionResource.fromResource(this, path);
}
function(path) {
return ODataFunctionResource.fromResource(this, path);
}
cast(type) {
const thisType = this.incomingType();
const baseSchema = thisType !== undefined ? this.api.structuredType(thisType) : undefined;
// Downcast
const castSchema = baseSchema?.findChildSchema((s) => s.type() === type);
if (castSchema !== undefined &&
baseSchema !== undefined &&
!castSchema.isSubtypeOf(baseSchema))
throw new Error(`cast: Cannot cast to ${type}`);
const segments = this.cloneSegments();
segments.add(PathSegment.type, type).incomingType(type);
return new ODataEntityResource(this.api, {
segments,
query: this.cloneQuery(),
});
}
//#region Requests
post(attrs, options = {}) {
return super.post(attrs, { responseType: 'entity', ...options });
}
put(attrs, options = {}) {
return super.put(attrs, { responseType: 'entity', ...options });
}
patch(attrs, options = {}) {
return super.patch(attrs, { responseType: 'entity', ...options });
}
delete(options = {}) {
return super.delete({ responseType: 'entity', ...options });
}
get(options = {}) {
return super.get({ responseType: 'entity', ...options });
}
//#endregion
//#region Shortcuts
create(attrs, options) {
return this.post(attrs, options);
}
update(attrs, options) {
return this.put(attrs, options);
}
modify(attrs, options) {
return this.patch(attrs, options);
}
destroy(options) {
return this.delete(options);
}
fetch(options) {
if (!this.hasKey())
return throwError(() => new Error('fetch: Entity resource without key'));
return this.get(options);
}
fetchEntity(options) {
return this.fetch(options).pipe(map(({ entity }) => entity));
}
fetchModel(options) {
return this.fetch(options).pipe(map(({ entity, annots }) => entity
? this.asModel(entity, { annots, ModelType: options?.ModelType })
: null));
}
}
class ODataEntitySetResource extends ODataResource {
//#region Factory
static factory(api, { path, type, query, }) {
const segments = new ODataPathSegments();
const segment = segments.add(PathSegment.entitySet, path);
if (type !== undefined) {
segment.outgoingType(type);
segment.incomingType(type);
}
return new ODataEntitySetResource(api, { segments, query });
}
clone() {
return super.clone();
}
transform(opts, { type, fields, } = {}) {
return super.transform(opts, {
type,
fields,
});
}
//#endregion
entity(key) {
const entity = ODataEntityResource.factory(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
if (key !== undefined) {
return entity.key(key);
}
return entity;
}
action(path) {
return ODataActionResource.fromResource(this, path);
}
function(path) {
return ODataFunctionResource.fromResource(this, path);
}
count() {
return ODataCountResource.factory(this.api, {
segments: this.cloneSegments(),
query: this.cloneQuery(),
});
}
cast(type) {
const thisType = this.incomingType();
const baseSchema = thisType !== undefined ? this.api.structuredType(thisType) : undefined;
const castSchema = this.api.findStructuredType(type);
if (castSchema !== undefined &&
baseSchema !== undefined &&
!castSchema.isSubtypeOf(baseSchema))
throw new Error(`cast: Cannot cast to ${type}`);
const segments = this.cloneSegments();
segments.add(PathSegment.type, type).incomingType(type);
return new ODataEntitySetResource(this.api, {
segments,
query: this.cloneQuery(),
});
}
//#region Requests
post(attrs, options) {
return super.post(attrs, { responseType: 'entity', ...options });
}
get(options = {}) {
return super.get({ responseType: 'entities', ...options });
}
//#endregion
//#region Shortcuts
create(attrs, options) {
return this.post(attrs, options);
}
fetch(options) {
return this.get(options);
}
fetchAll(options) {
const res = this.clone();
// Clean Paging
res.query((q) => q.removePaging());
const fetch = (opts) => {
if (opts) {
res.query((q) => q.paging(opts));
}
return res.fetch(options);
};
return fetch().pipe(expand(({ annots }) => annots.skip || annots.skiptoken ? fetch(annots) : EMPTY), map(({ entities, annots }) => ({ entities: entities ?? [], annots })), reduce((acc, { entities, annots }) => ({
entities: [...(acc.entities ?? []), ...(entities ?? [])],
annots: acc.annots.union(annots),
})));
}
fetchMany(top, options) {
const res = this.clone();
const fetch = (opts) => {
if (opts) {
res.query((q) => q.paging(opts));
}
return res.fetch(options);
};
return fetch({ top }).pipe(expand(({ annots }) => annots.skip || annots.skiptoken ? fetch(annots) : EMPTY), map(({ entities, annots }) => ({ entities: entities ?? [], annots })), reduce((acc, { entities, annots }) => ({
entities: [...(acc.entities ?? []), ...(entities ?? [])],
annots: acc.annots.union(annots),
})));
}
fetchOne(options) {
const res = this.clone();
res.query((q) => q.top(1));
return res.fetch(options).pipe(map(({ entities, annots }) => ({
entity: entities !== null && entities.length === 1 ? entities[0] : null,
annots,
})));
}
fetchEntities(options) {
return this.fetch(options).pipe(map(({ entities }) => entities));
}
fetchCollection(options) {
return this.fetch(options).pipe(map(({ entities, annots }) => entities
? this.asCollection(entities, {
annots,
CollectionType: options?.CollectionType,
})
: null));
}
}
class CsdlAnnotable {
Annotation;
constructor({ Annotation }) {
this.Annotation = Annotation?.map((a) => new CsdlAnnotation(a));
}
toJson() {
const json = {};
if (Array.isArray(this.Annotation) && this.Annotation.length > 0) {
json['Annotation'] = this.Annotation.map((a) => a.toJson());
}
return json;
}
toConfig() {
const config = {};
if (this.Annotation) {
config['annotations'] = this.Annotation.map((a) => a.toConfig());
}
return config;
}
}
class CsdlAnnotations extends CsdlAnnotable {
schema;
Target;
Qualifier;
constructor(schema, { Target, Qualifier, Annotation, }) {
super({ Annotation });
this.schema = schema;
this.Target = Target;
this.Qualifier = Qualifier;
}
toJson() {
const json = {
...super.toJson(),
Target: this.Target,
};
if (this.Qualifier !== undefined) {
json['Qualifier'] = this.Qualifier;
}
return json;
}
toConfig() {
return (this.Annotation ?? []).map((a) => a.toConfig());
}
}
class CsdlAnnotation {
Term;
String;
Bool;
Int;
Collection;
Record;
EnumMember;
constructor({ Term, String, Bool, Int, Collection, Record, EnumMember, }) {
this.Term = Term;
this.String = String;
this.Bool = Bool;
this.Int = Int;
this.Collection = Collection?.map((a) => new CsdlCollection(a));
this.Record = Record?.map((a) => new CsdlRecord(a));
this.EnumMember = EnumMember?.map((a) => new CsdlEnumMember(a));
}
toJson() {
const json = { Term: this.Term };
if (this.String !== undefined) {
json['String'] = this.String;
}
if (this.Bool !== undefined) {
json['Bool'] = this.Bool;
}
if (this.Int !== undefined) {
json['Int'] = this.Int;
}
if (Array.isArray(this.Collection) && this.Collection.length > 0) {
json['Collection'] = this.Collection.map((m) => m.toJson());
}
if (Array.isArray(this.Record) && this.Record.length > 0) {
json['Record'] = this.Record.map((m) => m.toJson());
}
if (Array.isArray(this.EnumMember) && this.EnumMember.length > 0) {
json['EnumMember'] = this.EnumMember.map((m) => m.toJson());
}
return json;
}
toConfig() {
return {
term: this.Term,
string: this.String,
bool: this.Bool,
int: this.Int,
};
}
}
class CsdlTerm {
schema;
Name;
Type;
BaseTerm;
DefaultValue;
AppliesTo;
Nullable;
MaxLength;
Precision;
Scale;
SRID;
String;
Bool;
Int;
constructor(schema, { Name, Type, BaseTerm, DefaultValue, AppliesTo, Nullable, MaxLength, Precision, Scale, SRID, String, Bool, Int, }) {
this.schema = schema;
this.Name = Name;
this.Type = Type;
this.BaseTerm = BaseTerm;
this.DefaultValue = DefaultValue;
this.AppliesTo = AppliesTo;
this.Nullable = Nullable;
this.MaxLength = MaxLength;
this.Precision = Precision;
this.Scale = Scale;
this.SRID = SRID;
this.String = String;
this.Bool = Bool;
this.Int = Int;
}
toJson() {
const json = { Name: this.Name, Type: this.Type };
if (this.BaseTerm !== undefined) {
json['BaseTerm'] = this.BaseTerm;
}
if (this.DefaultValue !== undefined) {
json['DefaultValue'] = this.DefaultValue;
}
if (this.AppliesTo !== undefined) {
json['AppliesTo'] = this.AppliesTo;
}
if (this.Nullable !== undefined) {
json['Nullable'] = this.Nullable;
}
if (this.MaxLength !== undefined) {
json['MaxLength'] = this.MaxLength;
}
if (this.Precision !== undefined) {
json['Precision'] = this.Precision;
}
if (this.Scale !== undefined) {
json['Scale'] = this.Scale;
}
if (this.SRID !== undefined) {
json['SRID'] = this.SRID;
}
if (this.String !== undefined) {
json['String'] = this.String;
}
if (this.Bool !== undefined) {
json['Bool'] = this.Bool;
}
if (this.Int !== undefined) {
json['Int'] = this.Int;
}
return json;
}
}
class CsdlCollection {
String;
Record;
PropertyPath;
NavigationPropertyPath;
constructor({ String, Record, PropertyPath, NavigationPropertyPath, }) {
this.String = String?.map((a) => new CsdlString(a));
this.Record = Record?.map((a) => new CsdlRecord(a));
this.PropertyPath = PropertyPath?.map((a) => new CsdlPropertyPath(a));
this.NavigationPropertyPath = NavigationPropertyPath?.map((a) => new CsdlNavigationPropertyPath(a));
}
toJson() {
const json = {};
if (Array.isArray(this.String) && this.String.length > 0) {
json['String'] = this.String.map((s) => s.toJson());
}
if (Array.isArray(this.Record) && this.Record.length > 0) {
json['Record'] = this.Record.map((r) => r.toJson());
}
if (Array.isArray(this.PropertyPath) && this.PropertyPath.length > 0) {
json['PropertyPath'] = this.PropertyPath.map((p) => p.toJson());
}
if (Array.isArray(this.NavigationPropertyPath) &&
this.NavigationPropertyPath.length > 0) {
json['NavigationPropertyPath'] = this.NavigationPropertyPath.map((p) => p.toJson());
}
return json;
}
}
class CsdlRecord {
PropertyValue;
constructor({ PropertyValue }) {
this.PropertyValue = PropertyValue?.map((a) => new CsdlPropertyValue(a));
}
toJson() {
const json = {};
if (Array.isArray(this.PropertyValue) && this.PropertyValue.length > 0) {
json['PropertyValue'] = this.PropertyValue.map((p) => p.toJson());
}
return json;
}
}
class CsdlPropertyValue {
Name;
String;
Date;
EnumMember;
constructor({ Name, String, Date, EnumMember, }) {
this.Name = Name;
this.String = String;
this.Date = Date;
this.EnumMember = EnumMember?.map((a) => new CsdlEnumMember(a));
}
toJson() {
const json = { Name: this.Name };
if (this.String !== undefined) {
json['String'] = this.String;
}
if (this.Date !== undefined) {
json['Date'] = this.Date;
}
if (Array.isArray(this.EnumMember) && this.EnumMember.length > 0) {
json['EnumMember'] = this.EnumMember.map((m) => m.toJson());
}
return json;
}
}
class CsdlEnumMember {
TextContent;
constructor({ TextContent }) {
this.TextContent = TextContent;
}
toJson() {
return {
TextContent: this.TextContent,
};
}
}
class CsdlString {
TextContent;
constructor({ TextContent }) {
this.TextContent = TextContent;
}
toJson() {
return {
TextContent: this.TextContent,
};
}
}
class CsdlPropertyPath {
TextContent;
constructor({ TextContent }) {
this.TextContent = TextContent;
}
toJson() {
return {
TextContent: this.TextContent,
};
}
}
class CsdlNavigationPropertyPath {
TextContent;
constructor({ TextContent }) {
this.TextContent = TextContent;
}
toJson() {
return {
TextContent: this.TextContent,
};
}
}
class CsdlReference extends CsdlAnnotable {
Uri;
Include;
IncludeAnnotations;
constructor({ Uri, Include, IncludeAnnotations, Annotation, }) {
super({ Annotation });
this.Uri = Uri;
this.Include = Include?.map((i) => new CsdlInclude(i));
this.IncludeAnnotations = IncludeAnnotations?.map((i) => new CsdlIncludeAnnotations(i));
}
toJson() {
const json = { ...super.toJson(), Uri: this.Uri };
if (Array.isArray(this.Include) && this.Include.length > 0) {
json['Include'] = this.Include.map((i) => i.toJson());
}
if (Array.isArray(this.IncludeAnnotations) &&
this.IncludeAnnotations.length > 0) {
json['IncludeAnnotations'] = this.IncludeAnnotations.map((i) => i.toJson());
}
return json;
}
toConfig(base) {
return {
...super.toConfig(),
uri: this.Uri,
includes: this.Include?.map((i) => i.toConfig()),
includeAnnotations: this.IncludeAnnotations?.map((i) => i.toConfig()),
};
}
}
class CsdlInclude {
Namespace;
Alias;
constructor({ Namespace, Alias }) {
this.Namespace = Namespace;
this.Alias = Alias;
}
toJson() {
return {
Namespace: this.Namespace,
Alias: this.Alias,
};
}
toConfig() {
return {
namespace: this.Namespace,
alias: this.Alias,
};
}
}
class CsdlIncludeAnnotations {
TermNamespace;
Qualifier;
TargetNamespace;
constructor({ TermNamespace, Qualifier, TargetNamespace, }) {
this.TermNamespace = TermNamespace;
this.Qualifier = Qualifier;
this.TargetNamespace = TargetNamespace;
}
toJson() {
return {
TermNamespace: this.TermNamespace,
Qualifier: this.Qualifier,
TargetNamespace: this.TargetNamespace,
};
}
toConfig() {
return {
termNamespace: this.TermNamespace,
qualifier: this.Qualifier,
targetNamespace: this.TargetNamespace,
};
}
}
class CsdlTypeDefinition extends CsdlAnnotable {
schema;
Name;
UnderlayingType;
MaxLength;
Precision;
Scale;
Unicode;
SRID;
constructor(schema, { Name, UnderlayingType, MaxLength, Precision, Scale, Unicode, SRID, Annotation, }) {
super({ Annotation });
this.schema = schema;
this.Name = Name;
this.UnderlayingType = UnderlayingType;
this.MaxLength = MaxLength;
this.Precision = Precision;
this.Scale = Scale;
this.Unicode = Unicode;
this.SRID = SRID;
}
toJson() {
const json = {
...super.toJson(),
Name: this.Name,
UnderlayingType: this.UnderlayingType,
};
if (this.MaxLength !== undefined) {
json['MaxLength'] = this.MaxLength;
}
if (this.Precision !== undefined) {
json['Precision'] = this.Precision;
}
if (this.Scale !== undefined) {
json['Scale'] = this.Scale;
}
if (this.Unicode !== undefined) {
json['Unicode'] = this.Unicode;
}
if (this.SRID !== undefined) {
json['SRID'] = this.SRID;
}
return json;
}
}
class CsdlEnumType extends CsdlAnnotable {
schema;
Name;
Member;
UnderlyingType;
IsFlags;
constructor(schema, { Name, Member, UnderlyingType, IsFlags, Annotation, }) {
super({ Annotation });
this.schema = schema;
this.Name = Name;
this.Member = Member.map((m) => new CsdlMember(m));
this.UnderlyingType = UnderlyingType;
this.IsFlags = IsFlags;
}
toJson() {
const json = {
...super.toJson(),
Name: this.Name,
Member: this.Member.map((m) => m.toJson()),
};
if (this.UnderlyingType !== undefined) {
json['UnderlyingType'] = this.UnderlyingType;
}
if (this.IsFlags !== undefined) {
json['IsFlags'] = this.IsFlags;
}
return json;
}
name() {
return `${this.Name}`;
}
namespace() {
return `${this.schema.Namespace}`;
}
fullName() {
return `${this.schema.Namespace}.${this.Name}`;
}
toConfig(base) {
return {
...super.toConfig(),
name: this.Name,
fields: this.Member.reduce((acc, m) => ({
...acc,
[m.Name]: m.toConfig(),
}), {}),
flags: this.IsFlags,
};
}
}
class CsdlMember extends CsdlAnnotable {
Name;
Value;
constructor({ Name, Value, Annotation, }) {
super({ Annotation });
this.Name = Name;
this.Value = Value;
}
toJson() {
const json = { ...super.toJson(), Name: this.Name };
if (this.Value !== undefined) {
json['Value'] = this.Value;
}
return json;
}
toConfig(base) {
const config = {
...super.toConfig(),
value: this.Value,
};
return config;
}
}
class CsdlStructuralProperty extends CsdlAnnotable {
Name;
Type;
Collection;
Nullable;
constructor({ Name, Type, Nullable, Annotation, }) {
super({ Annotation });
this.Name = Name;
this.Nullable = Nullable;
this.Collection = Type.startsWith('Collection(');
this.Type = this.Collection ? Type.substring(11, Type.length - 1) : Type;
}
toJson() {
return {
...super.toJson(),
Name: this.Name,
Type: this.Collection ? `Collection(${this.Type})` : this.Type,
Nullable: this.Nullable,
};
}
}
class CsdlProperty extends CsdlStructuralProperty {
MaxLength;
Precision;
Scale;
Unicode;
SRID;
DefaultValue;
constructor({ Name, Type, Nullable, MaxLength, Precision, Scale, Unicode, SRID, DefaultValue, Annotation, }) {
super({ Name, Type, Nullable, Annotation });
this.MaxLength = MaxLength;
this.Precision = Precision;
this.Scale = Scale;
this.Unicode = Unicode;
this.SRID = SRID;
this.DefaultValue = DefaultValue;
}
toJson() {
const json = { ...super.toJson() };
if (this.MaxLength !== undefined) {
json['MaxLength'] = this.MaxLength;
}
if (this.Precision !== undefined) {
json['Precision'] = this.Precision;
}
if (this.Scale !== undefined) {
json['Scale'] = this.Scale;
}
if (this.Unicode !== undefined) {
json['Unicode'] = this.Unicode;
}
if (this.SRID !== undefined) {
json['SRID'] = this.SRID;
}
if (this.DefaultValue !== undefined) {
json['DefaultValue'] = this.DefaultValue;
}
return json;
}
toConfig() {
return {
...super.toConfig(),
name: this.Name,
type: this.Type,
default: this.DefaultValue,
maxLength: this.MaxLength,
collection: this.Collection,
nullable: this.Nullable,
navigation: false,
precision: this.Precision,
scale: this.Scale,
};
}
}
class CsdlNavigationProperty extends CsdlStructuralProperty {
Partner;
ContainsTarget;
ReferentialConstraints;
OnDelete;
constructor({ Name, Type, Nullable, Partner, ContainsTarget, ReferentialConstraints, OnDelete, Annotation, }) {
super({ Name, Type, Nullable, Annotation });
this.Partner = Partner;
this.ContainsTarget = ContainsTarget;
this.ReferentialConstraints = ReferentialConstraints?.map((r) => new CsdlReferentialConstraint(r));
this.OnDelete = OnDelete ? new CsdlOnDelete(OnDelete) : undefined;
}
toJson() {
const json = { ...super.toJson() };
if (this.Partner !== undefined) {
json['Partner'] = this.Partner;
}
if (this.ContainsTarget !== undefined) {
json['ContainsTarget'] = this.ContainsTarget;
}
if (Array.isArray(this.ReferentialConstraints) &&
this.ReferentialConstraints.length > 0) {
json['ReferentialConstraints'] = this.ReferentialConstraints.map((r) => r.toJson());
}
if (this.OnDelete !== undefined) {
json['OnDelete'] = this.OnDelete;
}
return json;
}
toConfig() {
return {
...super.toConfig(),
name: this.Name,
type: this.Type,
collection: this.Collection,
nullable: this.Nullable,
navigation: true,
referentials: this.ReferentialConstraints?.map((r) => ({
property: r.Property,
referencedProperty: r.ReferencedProperty,
})),
};
}
}
class CsdlReferentialConstraint {
Property;
ReferencedProperty;
constructor({ Property, ReferencedProperty, }) {
this.Property = Property;
this.ReferencedProperty = ReferencedProperty;
}
toJson() {
return {
Property: this.Property,
ReferencedProperty: this.ReferencedProperty,
};
}
}
class CsdlOnDelete {
Action;
constructor({ Action }) {
this.Action = Action;
}
toJson() {
return {
Action: this.Action,
};
}
}
class CsdlStructuredType extends CsdlAnnotable {
schema;
Name;
Property;
NavigationProperty;
BaseType;
OpenType;
Abstract;
constructor(schema, { Name, Property, NavigationProperty, BaseType, OpenType, Abstract, Annotation, }) {
super({ Annotation });
this.schema = schema;
this.Name = Name;
this.Property = Property?.map((p) => new CsdlProperty(p));
this.NavigationProperty = NavigationProperty?.map((n) => new CsdlNavigationProperty(n));
this.BaseType = BaseType;
this.OpenType = OpenType;
this.Abstract = Abstract;
}
toJson() {
const json = { ...super.toJson(), Name: this.Name };
if (Array.isArray(this.Property) && this.Property.length > 0) {
json['Property'] = this.Property.map((p) => p.toJson());
}
if (Array.isArray(this.NavigationProperty) &&
this.NavigationProperty.length > 0) {
json['NavigationProperty'] = this.NavigationProperty.map((n) => n.toJson());
}
if (this.BaseType !== undefined) {
json['BaseType'] = this.BaseType;
}
if (this.OpenType !== undefined) {
json['OpenType'] = this.OpenType;
}
if (this.Abstract !== undefined) {
json['Abstract'] = this.Abstract;
}
return json;
}
name() {
return `${this.Name}`;
}
namespace() {
return `${this.schema.Namespace}`;
}
fullName() {
return `${this.schema.Namespace}.${this.Name}`;
}
}
class CsdlComplexType extends CsdlStructuredType {
constructor(schema, { Name, Property, NavigationProperty, BaseType, OpenType, Abstract, Annotation, }) {
super(schema, {
Name,
Property,
NavigationProperty,
BaseType,
OpenType,
Abstract,
Annotation,
});
}
toJson() {
return {
...super.toJson(),
};
}
toConfig(base) {
return {
...super.toConfig(),
name: this.Name,
base: this.BaseType,
open: base?.open ?? this.OpenType,
model: base?.model,
collection: base?.collection,
fields: [
...(this.Property ?? []).map((t) => t.toConfig()),
...(this.NavigationProperty ?? []).map((t) => t.toConfig()),
].reduce((acc, p) => Object.assign(acc, { [p.name]: p }), {}),
};
}
}
class CsdlEntityType extends CsdlStructuredType {
Key;
HasStream;
constructor(schema, { Name, Key, Property, NavigationProperty, BaseType, OpenType, Abstract, HasStream, Annotation, }) {
super(schema, {
Name,
Property,
NavigationProperty,
BaseType,
OpenType,
Abstract,
Annotation,
});
this.Key = Key ? new CsdlKey(Key) : undefined;
this.HasStream = HasStream;
}
toJson() {
const json = { ...super.toJson() };
if (this.Key !== undefined) {
json['Key'] = this.Key.toJson();
}
if (this.HasStream !== undefined) {
json['HasStream'] = this.HasStream;
}
return json;
}
toConfig(base) {
return {
...super.toConfig(),
name: this.Name,
base: this.BaseType,
open: base?.open ?? this.OpenType,
model: base?.model,
collection: base?.collection,
keys: this.Key?.toConfig(),
fields: [
...(this.Property ?? []).map((t) => t.toConfig()),
...(this.NavigationProperty ?? []).map((t) => t.toConfig()),
].reduce((acc, p) => Object.assign(acc, { [p.name]: p }), {}),
};
}
}
class CsdlKey {
PropertyRef;
constructor({ PropertyRef }) {
this.PropertyRef = PropertyRef?.map((p) => new CsdlPropertyRef(p));
}
toJson() {
return {
PropertyRef: this.PropertyRef?.map((p) => p.toJson()),
};
}
toConfig() {
return this.PropertyRef?.map((t) => t.toConfig());
}
}
class CsdlPropertyRef {
Name;
Alias;
constructor({ Name, Alias }) {
this.Name = Name;
this.Alias = Alias;
}
toJson() {
return {
Name: this.Name,
Alias: this.Alias,
};
}
toConfig() {
return {
name: this.Name,
alias: this.Alias,
};
}
}
const BINDING_PARAMETER_NAME = 'bindingParameter';
class CsdlCallable {
schema;
Name;
ReturnType;
IsBound;
EntitySetPath;
Parameter;
constructor(schema, { Name, ReturnType, IsBound, EntitySetPath, Parameter, }) {
this.schema = schema;
this.Name = Name;
this.ReturnType = ReturnType ? new CsdlReturnType(ReturnType) : undefined;
this.IsBound = IsBound;
this.EntitySetPath = EntitySetPath;
this.Parameter = Parameter?.map((p) => new CsdlParameter(p));
}
toJson() {
const json = {
Name: this.Name,
};
if (this.ReturnType !== undefined) {
json['ReturnType'] = this.ReturnType.toJson();
}
if (this.IsBound !== undefined) {
json['IsBound'] = this.IsBound;
}
if (this.EntitySetPath !== undefined) {
json['EntitySetPath'] = this.EntitySetPath;
}
if (Array.isArray(this.Parameter) && this.Parameter.length > 0) {
json['Parameter'] = this.Parameter.map((p) => p.toJson());
}
return json;
}
name() {
return `${this.Name}`;
}
namespace() {
return `${this.schema.Namespace}`;
}
fullName() {
return `${this.namespace()}.${this.Name}`;
}
}
class CsdlFunction extends CsdlCallable {
IsComposable;
constructor(schema, { Name, ReturnType, IsBound, EntitySetPath, IsComposable, Parameter, }) {
super(schema, { Name, ReturnType, IsBound, EntitySetPath, Parameter });
this.IsComposable = IsComposable;
}
toJson() {
return {
...super.toJson(),
IsComposable: this.IsComposable,
};
}
toConfig(base) {
return {
name: this.Name,
entitySetPath: this.EntitySetPath,
bound: this.IsBound,
composable: this.IsComposable,
parameters: this.Parameter?.map((p) => p.toConfig()),
return: this.ReturnType?.toConfig(),
};
}
}
class CsdlAction extends CsdlCallable {
constructor(schema, { Name, ReturnType, IsBound, EntitySetPath, Parameter, }) {
super(schema, { Name, ReturnType, IsBound, EntitySetPath, Parameter });
}
toJson() {
return {
...super.toJson(),
};
}
toConfig(base) {
return {
name: this.Name,
entitySetPath: this.EntitySetPath,
bound: this.IsBound,
parameters: this.Parameter?.map((p) => p.toConfig()),
return: this.ReturnType?.toConfig(),
};
}
}
class CsdlFunctionImport {
container;
Name;
FunctionName;
EntitySet;
IncludeInServiceDocument;
constructor(container, { Name, FunctionName, EntitySet, IncludeInServiceDocument, }) {
this.container = container;
this.Name = Name;
this.FunctionName = FunctionName;
this.EntitySet = EntitySet;
this.IncludeInServiceDocument = IncludeInServiceDocument;
}
toJson() {
return {
Name: this.Name,
FunctionName: this.FunctionName,
EntitySet: this.EntitySet,
IncludeInServiceDocument: this.IncludeInServiceDocument,
};
}
}
class CsdlActionImport {
container;
Name;
Action;
EntitySet;
constructor(container, { Name, Action, EntitySet, }) {
this.container = container;
this.Name = Name;
this.Action = Action;
this.EntitySet = EntitySet;
}
toJson() {
return {
Name: this.Name,
Action: this.Action,
EntitySet: this.EntitySet,
};
}
}
class CsdlParameter extends CsdlAnnotable {
Name;
Type;
Collection;
Nullable;
MaxLength;
Precision;
Scale;
SRID;
constructor({ Name, Type, Nullable, MaxLength, Precision, Scale, SRID, Annotation, }) {
super({ Annotation });
this.Name = Name;
this.Collection = Type.startsWith('Collection(');
this.Type = this.Collection ? Type.substring(11, Type.length - 1) : Type;
this.Nullable = Nullable;
this.MaxLength = MaxLength;
this.Precision = Precision;
this.Scale = Scale;
this.SRID = SRID;
}
toJson() {
return {
...super.toJson(),
Name: this.Name,
Type: this.Collection ? `Collection(${this.Type})` : this.Type,
Nullable: this.Nullable,
MaxLength: this.MaxLength,
Precision: this.Precision,
Scale: this.Scale,
SRID: this.SRID,
};
}
toConfig() {
return {
...super.toConfig(),
type: this.Type,
nullable: this.Nullable,
collection: false,
};
}
}
class CsdlReturnType {
Type;
Collection;
Nullable;
MaxLength;
Precision;
Scale;
SRID;
constructor({ Type, Nullable, MaxLength, Precision, Scale, SRID, }) {
this.Collection = Type.startsWith('Collection(');
this.Type = this.Collection ? Type.substring(11, Type.length - 1) : Type;
this.Nullable = Nullable;
this.MaxLength = MaxLength;
this.Precision = Precision;
this.Scale = Scale;
this.SRID = SRID;
}
toJson() {
return {
Type: this.Collection ? `Collection(${this.Type})` : this.Type,
Nullable: this.Nullable,
MaxLength: this.MaxLength,
Precision: this.Precision,
Scale: this.Scale,
SRID: this.SRID,
};
}
toConfig() {
return {
type: this.Type,
collection: false,
};
}
}
class CsdlNavigationPropertyBinding {
Path;
Target;
constructor({ Path, Target }) {
this.Path = Path;
this.Target = Target;
}
toJson() {
return {
Path: this.Path,
Target: this.Target,
};
}
}
class CsdlEntitySet extends CsdlAnnotable {
container;
Name;
EntityType;
NavigationPropertyBinding;
IncludeInServiceDocument;
constructor(container, { Name, EntityType, NavigationPropertyBinding, IncludeInServiceDocument, Annotation, }) {
super({ Annotation });
this.container = container;
this.Name = Name;
this.EntityType = EntityType;
this.NavigationPropertyBinding = NavigationPropertyBinding?.map((n) => new CsdlNavigationPropertyBinding(n));
this.IncludeInServiceDocument = IncludeInServiceDocument;
}
toJson() {
const json = {
...super.toJson(),
Name: this.Name,
EntityType: this.EntityType,
};
if (Array.isArray(this.NavigationPropertyBinding) &&
this.NavigationPropertyBinding.length > 0) {
json['NavigationPropertyBinding'] = this.NavigationPropertyBinding.map((n) => n.toJson());
}
if (this.IncludeInServiceDocument !== undefined) {
json['IncludeInServiceDocument'] = this.IncludeInServiceDocument;
}
return json;
}
name() {
return `${this.Name}`;
}
namespace() {
return `${this.container.namespace()}`;
}
fullName() {
return `${this.container.namespace()}.${this.Name}`;
}
toConfig() {
return {
...super.toConfig(),
name: this.Name,
entityType: this.EntityType,
service: {},
};
}
}
class CsdlSingleton extends CsdlAnnotable {
container;
Name;
Type;
NavigationPropertyBindings;
constructor(container, { Name, Type, NavigationPropertyBindings, Annotation, }) {
super({ Annotation });
this.container = container;
this.Name = Name;
this.Type = Type;
this.NavigationPropertyBindings = NavigationPropertyBindings?.map((n) => new CsdlNavigationPropertyBinding(n));
}
toJson() {
const json = {
...super.toJson(),
Name: this.Name,
Type: this.Type,
};
if (Array.isArray(this.NavigationPropertyBindings) &&
this.NavigationPropertyBindings.length > 0) {
json['NavigationPropertyBindings'] = this.NavigationPropertyBindings.map((n) => n.toJson());
}
return json;
}
name() {
return `${this.Name}`;
}
namespace() {
return `${this.container.namespace()}`;
}
fullName() {
return `${this.container.namespace()}.${this.Name}`;
}
}
class CsdlEntityContainer extends CsdlAnnotable {
schema;
Name;
Extend;
EntitySet;
Singleton;
FunctionImport;
ActionImport;
constructor(schema, { Name, Extend, EntitySet, Singleton, FunctionImport, ActionImport, Annotation, }) {
super({ Annotation });
this.schema = schema;
this.Name = Name;
this.Extend = Extend;
this.EntitySet = EntitySet?.map((e) => new CsdlEntitySet(this, e));
this.Singleton = Singleton?.map((s) => new CsdlSingleton(this, s));
this.FunctionImport = FunctionImport?.map((f) => new CsdlFunctionImport(this, f));
this.ActionImport = ActionImport?.map((a) => new CsdlActionImport(this, a));
}
toJson() {
const json = { ...super.toJson() };
if (this.Extend !== undefined) {
json['Extend'] = this.Extend;
}
if (Array.isArray(this.EntitySet) && this.EntitySet.length > 0) {
json['EntitySet'] = this.EntitySet.map((a) => a.toJson());
}
if (Array.isArray(this.Singleton) && this.Singleton.length > 0) {
json['Singleton'] = this.Singleton.map((a) => a.toJson());
}
if (Array.isArray(this.FunctionImport) && this.FunctionImport.length > 0) {
json['FunctionImport'] = this.FunctionImport.map((a) => a.toJson());
}
if (Array.isArray(this.ActionImport) && this.ActionImport.length > 0) {
json['ActionImport'] = this.ActionImport.map((a) => a.toJson());
}
return json;
}
name() {
return `${this.Name}`;
}
namespace() {
return `${this.schema.Namespace}`;
}
fullName() {
return `${this.schema.Namespace}.${this.Name}`;
}
toConfig(base) {
return {
...super.toConfig(),
name: this.Name,
entitySets: this.EntitySet?.map((t) => t.toConfig()),
};
}
}
class CsdlSchema {
Namespace;
Alias;
EnumType;
ComplexType;
EntityType;
Function;
Action;
EntityContainer;
TypeDefinition;
Term;
Annotations;
constructor({ Namespace, Alias, EnumType, ComplexType, EntityType, Function, Action, EntityContainer, TypeDefinition, Term, Annotations, }) {
this.Namespace = Namespace;
this.Alias = Alias;
this.EnumType = EnumType?.map((e) => new CsdlEnumType(this, e));
this.ComplexType = ComplexType?.map((c) => new CsdlComplexType(this, c));
this.EntityType = EntityType?.map((e) => new CsdlEntityType(this, e));
this.Function = Function?.map((f) => new CsdlFunction(this, f));
this.Action = Action?.map((a) => new CsdlAction(this, a));
this.EntityContainer = EntityContainer?.map((e) => new CsdlEntityContainer(this, e));
this.TypeDefinition = TypeDefinition?.map((t) => new CsdlTypeDefinition(this, t));
this.Term = Term?.map((t) => new CsdlTerm(this, t));
this.Annotations = Annotations?.map((a) => new CsdlAnnotations(this, a));
}
toJson() {
const json = {
Namespace: this.Namespace,
};
if (this.Alias !== undefined) {
json['Alias'] = this.Alias;
}
if (Array.isArray(this.EntityContainer) &&
this.EntityContainer.length > 0) {
json['EntityContainer'] = this.EntityContainer.map((a) => a.toJson());
}
if (Array.isArray(this.EntityType) && this.EntityType.length > 0) {
json['EntityType'] = this.EntityType.map((a) => a.toJson());
}
if (Array.isArray(this.ComplexType) && this.ComplexType.length > 0) {
json['ComplexType'] = this.ComplexType.map((a) => a.toJson());
}
if (Array.isArray(this.EnumType) && this.EnumType.length > 0) {
json['EnumType'] = this.EnumType.map((a) => a.toJson());
}
if (Array.isArray(this.TypeDefinition) && this.TypeDefinition.length > 0) {
json['TypeDefinition'] = this.TypeDefinition.map((a) => a.toJson());
}
if (Array.isArray(this.Term) && this.Term.length > 0) {
json['Term'] = this.Term.map((a) => a.toJson());
}
if (Array.isArray(this.Annotations) && this.Annotations.length > 0) {
json['Annotations'] = this.Annotations.map((a) => a.toJson());
}
if (Array.isArray(this.Action) && this.Action.length > 0) {
json['Action'] = this.Action.map((a) => a.toJson());
}
if (Array.isArray(this.Function) && this.Function.length > 0) {
json['Function'] = this.Function.map((a) => a.toJson());
}
return json;
}
toConfig(base) {
return {
namespace: this.Namespace,
alias: base?.alias ?? this.Alias,
annotations: this.Annotations?.map((t) => t.toConfig()),
enums: this.EnumType?.map((t) => t.toConfig(base?.enums?.find((cs) => cs.name === t.Name))),
entities: [
...(this.ComplexType ?? []).map((t) => t.toConfig(base?.entities?.find((cs) => cs.name === t.Name))),
...(this.EntityType ?? []).map((t) => t.toConfig(base?.entities?.find((cs) => cs.name === t.Name))),
],
callables: [
...(this.Function ?? []).map((t) => t.toConfig(base?.callables?.find((cs) => cs.name === t.Name))),
...(this.Action ?? []).map((t) => t.toConfig(base?.callables?.find((cs) => cs.name === t.Name))),
],
containers: this.EntityContainer?.map((t) => t.toConfig(base?.containers?.find((cs) => cs.name === t.Name))),
};
}
}
class ODataMetadata {
Version;
References;
Schemas;
constructor(Version, References, Schemas) {
this.Version = Version;
this.References = References?.map((r) => new CsdlReference(r));
this.Schemas = Schemas?.map((s) => new CsdlSchema(s));
}
toConfig(base) {
return {
...base,
version: base?.version ?? this.Version,
schemas: (this.Schemas ?? []).map((ms) => ms.toConfig(base?.schemas?.find((cs) => cs.namespace === ms.Namespace))),
references: (this.References ?? []).map((mr) => mr.toConfig(base?.references?.find((cs) => cs.uri === mr.Uri))),
};
}
toJson() {
return {
Version: this.Version,
References: this.References.map((r) => r.toJson()),
Schemas: this.Schemas.map((s) => s.toJson()),
};
}
functions() {
return this.Schemas.reduce((acc, s) => {
return [...acc, ...(s.Function ?? [])];
}, []);
}
actions() {
return this.Schemas.reduce((acc, s) => {
return [...acc, ...(s.Action ?? [])];
}, []);
}
static fromJson(json) {
return new ODataMetadata(json.Version, json.References, json.Schemas);
}
}
var FieldType;
(function (FieldType) {
FieldType[FieldType["ATTRIBUTE"] = 0] = "ATTRIBUTE";
FieldType[FieldType["TAG"] = 1] = "TAG";
})(FieldType || (FieldType = {}));
class Field {
name;
fieldType;
constructor(name, fieldType) {
this.name = name;
this.fieldType = fieldType;
}
}
class ODataMetadataParser {
// TAGS
static TAG_EDMX = 'edmx:Edmx';
static TAG_DATA_SERVICES = 'edmx:DataServices';
static TAG_REFERENCE = 'edmx:Reference';
static TAG_INCLUDE = 'edmx:Include';
static TAG_INCLUDE_ANNOTATIONS = 'edmx:IncludeAnnotations';
static TAG_TERM = 'Term';
static TAG_ANNOTATIONS = 'Annotations';
static TAG_ANNOTATION = 'Annotation';
static TAG_SCHEMA = 'Schema';
static TAG_ENUM_TYPE = 'EnumType';
static TAG_MEMBER = 'Member';
static TAG_COMPLEX_TYPE = 'ComplexType';
static TAG_ENTITY_TYPE = 'EntityType';
static TAG_PROPERTY = 'Property';
static TAG_KEY = 'Key';
static TAG_PROPERTY_REF = 'PropertyRef';
static TAG_NAVIGATION_PROPERTY = 'NavigationProperty';
static TAG_REFERENTIAL_CONSTRAINT = 'ReferentialConstraint';
static TAG_ON_DELETE = 'OnDelete';
static TAG_FUNCTION = 'Function';
static TAG_RETURN_TYPE = 'ReturnType';
static TAG_PARAMETER = 'Parameter';
static TAG_ACTION = 'Action';
static TAG_ENTITY_CONTAINER = 'EntityContainer';
static TAG_ENTITY_SET = 'EntitySet';
static TAG_SINGLETON = 'Singleton';
static TAG_COLLECTION = 'Collection';
static TAG_RECORD = 'Record';
static TAG_STRING = 'String';
static TAG_ENUM_MEMBER = 'EnumMember';
static TAG_PROPERTY_VALUE = 'PropertyValue';
static TAG_PROPERTY_PATH = 'PropertyPath';
static TAG_NAVIGATION_PROPERTY_PATH = 'NavigationPropertyPath';
static TAG_FUNCTION_IMPORT = 'FunctionImport';
static TAG_ACTION_IMPORT = 'ActionImport';
static TAG_NAVIGATION_PROPERTY_BINDING = 'NavigationPropertyBinding';
static TAG_TYPE_DEFINITION = 'TypeDefinition';
// ATTRIBUTES
static ATTRIBUTE_VERSION = 'Version';
static ATTRIBUTE_URI = 'Uri';
static ATTRIBUTE_ALIAS = 'Alias';
static ATTRIBUTE_NAMESPACE = 'Namespace';
static ATTRIBUTE_TERM_NAMESPACE = 'TermNamespace';
static ATTRIBUTE_QUALIFIER = 'Qualifier';
static ATTRIBUTE_STRING = 'String';
static ATTRIBUTE_DATE = 'Date';
static ATTRIBUTE_BOOL = 'Bool';
static ATTRIBUTE_INT = 'Int';
static ATTRIBUTE_TARGET_NAMESPACE = 'TargetNamespace';
static ATTRIBUTE_TERM = 'Term';
static ATTRIBUTE_NAME = 'Name';
static ATTRIBUTE_VALUE = 'Value';
static ATTRIBUTE_BASE_TYPE = 'BaseType';
static ATTRIBUTE_OPEN_TYPE = 'OpenType';
static ATTRIBUTE_TYPE = 'Type';
static ATTRIBUTE_NULLABLE = 'Nullable';
static ATTRIBUTE_MAX_LENGTH = 'MaxLength';
static ATTRIBUTE_PRECISION = 'Precision';
static ATTRIBUTE_SCALE = 'Scale';
static ATTRIBUTE_UNICODE = 'Unicode';
static ATTRIBUTE_SRID = 'SRID';
static ATTRIBUTE_DEFAULT_VALUE = 'DefaultValue';
static ATTRIBUTE_PARTNER = 'Partner';
static ATTRIBUTE_PROPERTY = 'Property';
static ATTRIBUTE_REFERENCED_PROPERTY = 'ReferencedProperty';
static ATTRIBUTE_HAS_STREAM = 'HasStream';
static ATTRIBUTE_CONTAINS_TARGET = 'ContainsTarget';
static ATTRIBUTE_IS_BOUND = 'IsBound';
static ATTRIBUTE_ENTITY_SET_PATH = 'EntitySetPath';
static ATTRIBUTE_IS_COMPOSABLE = 'IsComposable';
static ATTRIBUTE_ENTITY_TYPE = 'EntityType';
static ATTRIBUTE_PATH = 'Path';
static ATTRIBUTE_TARGET = 'Target';
static ATTRIBUTE_FUNCTION = 'Function';
static ATTRIBUTE_ACTION = 'Action';
static ATTRIBUTE_ENTITY_SET = 'EntitySet';
static ATTRIBUTE_INCLUDE_IN_SERVICE_DOCUMENT = 'IncludeInServiceDocument';
static ATTRIBUTE_ABSTRACT = 'Abstract';
static ATTRIBUTE_UNDERLYING_TYPE = 'UnderlyingType';
static ATTRIBUTE_IS_FLAGS = 'IsFlags';
static ATTRIBUTE_EXTENDS = 'Extends';
static ATTRIBUTE_BASE_TERM = 'BaseTerm';
static ATTRIBUTE_APPLIES_TO = 'AppliesTo';
document;
constructor(xml) {
try {
const parser = new DOMParser();
this.document = parser.parseFromString(xml, 'text/xml');
this.checkVersion(document);
}
catch (error) {
throw new Error('Unable to parse metadata, ' + error);
}
}
metadata() {
const version = this.getFieldValueByAttribute(new Field(ODataMetadataParser.ATTRIBUTE_VERSION, FieldType.ATTRIBUTE), this.document.documentElement.attributes);
const references = this.getObjects(this.document.documentElement, ODataMetadataParser.TAG_REFERENCE, [
new Field(ODataMetadataParser.ATTRIBUTE_URI, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_INCLUDE, FieldType.TAG),
new Field(ODataMetadataParser.TAG_INCLUDE_ANNOTATIONS, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
const dataServices = this.document.documentElement.getElementsByTagName(ODataMetadataParser.TAG_DATA_SERVICES)[0];
const schemas = this.getObjects(dataServices, ODataMetadataParser.TAG_SCHEMA, [
new Field(ODataMetadataParser.ATTRIBUTE_NAMESPACE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ALIAS, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ENUM_TYPE, FieldType.TAG),
new Field(ODataMetadataParser.TAG_COMPLEX_TYPE, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ENTITY_TYPE, FieldType.TAG),
new Field(ODataMetadataParser.TAG_FUNCTION, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ACTION, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ENTITY_CONTAINER, FieldType.TAG),
new Field(ODataMetadataParser.TAG_TYPE_DEFINITION, FieldType.TAG),
new Field(ODataMetadataParser.TAG_TERM, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ANNOTATIONS, FieldType.TAG),
]);
return new ODataMetadata(version, references, schemas);
}
checkVersion(document) {
// check version
const attributes = document.documentElement.attributes;
if (!attributes) {
throw new Error('OData version is not specified in the metadata');
}
const attr = attributes.getNamedItem('Version');
if (attr === null) {
throw new Error('OData version is not specified in the metadata');
}
const odataVersion = attr?.nodeValue;
if (odataVersion !== VERSION_4_0) {
throw new Error('OData version "' + odataVersion + '" is not supported');
}
}
getObjects(parentElement, tag, fieldNames) {
let objects = [];
const children = parentElement.children;
for (let index = 0; index < children.length; index++) {
const element = children.item(index);
if (!element || element.nodeName !== tag) {
continue;
}
const attributes = element.attributes;
const fieldValues = this.getFieldValues(fieldNames, attributes, element);
fieldValues['TextContent'] = element.textContent;
switch (tag) {
case ODataMetadataParser.TAG_REFERENCE:
case ODataMetadataParser.TAG_INCLUDE:
case ODataMetadataParser.TAG_INCLUDE_ANNOTATIONS:
case ODataMetadataParser.TAG_TERM:
case ODataMetadataParser.TAG_ANNOTATIONS:
case ODataMetadataParser.TAG_COLLECTION:
case ODataMetadataParser.TAG_RECORD:
case ODataMetadataParser.TAG_STRING:
case ODataMetadataParser.TAG_ENUM_MEMBER:
case ODataMetadataParser.TAG_PROPERTY_VALUE:
case ODataMetadataParser.TAG_PROPERTY_PATH:
case ODataMetadataParser.TAG_NAVIGATION_PROPERTY_PATH:
case ODataMetadataParser.TAG_ANNOTATION:
case ODataMetadataParser.TAG_SCHEMA:
case ODataMetadataParser.TAG_ENUM_TYPE:
case ODataMetadataParser.TAG_COMPLEX_TYPE:
case ODataMetadataParser.TAG_ENTITY_TYPE:
case ODataMetadataParser.TAG_FUNCTION:
case ODataMetadataParser.TAG_MEMBER:
case ODataMetadataParser.TAG_PROPERTY:
case ODataMetadataParser.TAG_PROPERTY_REF:
case ODataMetadataParser.TAG_NAVIGATION_PROPERTY:
case ODataMetadataParser.TAG_REFERENTIAL_CONSTRAINT:
case ODataMetadataParser.TAG_PARAMETER:
case ODataMetadataParser.TAG_ACTION:
case ODataMetadataParser.TAG_ENTITY_SET:
case ODataMetadataParser.TAG_SINGLETON:
case ODataMetadataParser.TAG_FUNCTION_IMPORT:
case ODataMetadataParser.TAG_ACTION_IMPORT:
case ODataMetadataParser.TAG_NAVIGATION_PROPERTY_BINDING:
case ODataMetadataParser.TAG_TYPE_DEFINITION:
case ODataMetadataParser.TAG_ENTITY_CONTAINER:
objects.push(fieldValues);
break;
default:
throw new Error('Unknwon tag:' + tag);
}
}
return objects;
}
getObject(parentElement, tag, fieldNames) {
let object;
const children = parentElement.children;
let element;
for (let index = 0; index < children.length; index++) {
if (children && children.item(index)?.nodeName === tag) {
if (element !== undefined && element !== null) {
throw new Error('Expected one ' + tag);
}
element = children.item(index);
}
}
if (element !== undefined && element !== null) {
const attributes = element.attributes;
const fieldValues = this.getFieldValues(fieldNames, attributes, element);
fieldValues['TextContent'] = element.textContent;
switch (tag) {
case ODataMetadataParser.TAG_KEY:
case ODataMetadataParser.TAG_RETURN_TYPE:
case ODataMetadataParser.TAG_ON_DELETE:
return fieldValues;
default:
throw new Error('Unknwon tag:' + tag);
}
}
return object;
}
getFieldValues(fields, attributes, element) {
const fieldValues = {};
for (const field of fields) {
if (field.fieldType === FieldType.TAG) {
fieldValues[field.name] = this.getFieldValueByTag(field, element);
}
else if (field.fieldType === FieldType.ATTRIBUTE) {
fieldValues[field.name] = this.getFieldValueByAttribute(field, attributes);
}
else {
throw new Error('Unknown field type: ' + field.fieldType);
}
}
return fieldValues;
}
getFieldValueByAttribute(field, attributes) {
switch (field.name) {
case ODataMetadataParser.ATTRIBUTE_VERSION:
case ODataMetadataParser.ATTRIBUTE_URI:
case ODataMetadataParser.ATTRIBUTE_NAMESPACE:
case ODataMetadataParser.ATTRIBUTE_ALIAS:
case ODataMetadataParser.ATTRIBUTE_TERM_NAMESPACE:
case ODataMetadataParser.ATTRIBUTE_TERM:
case ODataMetadataParser.ATTRIBUTE_QUALIFIER:
case ODataMetadataParser.ATTRIBUTE_STRING:
case ODataMetadataParser.ATTRIBUTE_BOOL:
case ODataMetadataParser.ATTRIBUTE_INT:
case ODataMetadataParser.ATTRIBUTE_TARGET_NAMESPACE:
case ODataMetadataParser.ATTRIBUTE_NAME:
case ODataMetadataParser.ATTRIBUTE_TYPE:
case ODataMetadataParser.ATTRIBUTE_SRID:
case ODataMetadataParser.ATTRIBUTE_DEFAULT_VALUE:
case ODataMetadataParser.ATTRIBUTE_PARTNER:
case ODataMetadataParser.ATTRIBUTE_PROPERTY:
case ODataMetadataParser.ATTRIBUTE_REFERENCED_PROPERTY:
case ODataMetadataParser.ATTRIBUTE_BASE_TYPE:
case ODataMetadataParser.ATTRIBUTE_ENTITY_SET_PATH:
case ODataMetadataParser.ATTRIBUTE_ENTITY_TYPE:
case ODataMetadataParser.ATTRIBUTE_PATH:
case ODataMetadataParser.ATTRIBUTE_TARGET:
case ODataMetadataParser.ATTRIBUTE_FUNCTION:
case ODataMetadataParser.ATTRIBUTE_ACTION:
case ODataMetadataParser.ATTRIBUTE_ENTITY_SET:
case ODataMetadataParser.ATTRIBUTE_UNDERLYING_TYPE:
case ODataMetadataParser.ATTRIBUTE_EXTENDS:
case ODataMetadataParser.ATTRIBUTE_BASE_TERM:
case ODataMetadataParser.ATTRIBUTE_APPLIES_TO:
return this.getAttributeValue(attributes, field.name);
case ODataMetadataParser.ATTRIBUTE_NULLABLE:
case ODataMetadataParser.ATTRIBUTE_UNICODE:
case ODataMetadataParser.ATTRIBUTE_OPEN_TYPE:
case ODataMetadataParser.ATTRIBUTE_HAS_STREAM:
case ODataMetadataParser.ATTRIBUTE_IS_BOUND:
case ODataMetadataParser.ATTRIBUTE_IS_COMPOSABLE:
case ODataMetadataParser.ATTRIBUTE_CONTAINS_TARGET:
case ODataMetadataParser.ATTRIBUTE_INCLUDE_IN_SERVICE_DOCUMENT:
case ODataMetadataParser.ATTRIBUTE_ABSTRACT:
case ODataMetadataParser.ATTRIBUTE_IS_FLAGS:
return this.propertyValueToBoolean(this.getAttributeValue(attributes, field.name));
case ODataMetadataParser.ATTRIBUTE_VALUE:
case ODataMetadataParser.ATTRIBUTE_MAX_LENGTH:
case ODataMetadataParser.ATTRIBUTE_PRECISION:
case ODataMetadataParser.ATTRIBUTE_SCALE:
return this.propertyValueToNumber(this.getAttributeValue(attributes, field.name));
case ODataMetadataParser.ATTRIBUTE_DATE:
return this.propertyValueToDate(this.getAttributeValue(attributes, field.name));
default:
throw new Error('Unknwon attribute:' + field.name);
}
}
getFieldValueByTag(field, element) {
switch (field.name) {
case ODataMetadataParser.TAG_INCLUDE:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAMESPACE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ALIAS, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_INCLUDE_ANNOTATIONS:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_TERM_NAMESPACE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_QUALIFIER, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TARGET_NAMESPACE, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_TERM:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_BASE_TERM, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_DEFAULT_VALUE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_APPLIES_TO, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_NULLABLE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_MAX_LENGTH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_PRECISION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SCALE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SRID, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_STRING, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_BOOL, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_INT, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_ANNOTATIONS:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_TARGET, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_QUALIFIER, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_ANNOTATION:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_TERM, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_STRING, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_BOOL, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_INT, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_COLLECTION, FieldType.TAG),
new Field(ODataMetadataParser.TAG_RECORD, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ENUM_MEMBER, FieldType.TAG),
]);
case ODataMetadataParser.TAG_COLLECTION:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.TAG_STRING, FieldType.TAG),
new Field(ODataMetadataParser.TAG_RECORD, FieldType.TAG),
new Field(ODataMetadataParser.TAG_PROPERTY_PATH, FieldType.TAG),
new Field(ODataMetadataParser.TAG_NAVIGATION_PROPERTY_PATH, FieldType.TAG),
]);
case ODataMetadataParser.TAG_RECORD:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.TAG_PROPERTY_VALUE, FieldType.TAG),
]);
case ODataMetadataParser.TAG_STRING:
return this.getObjects(element, field.name, []);
case ODataMetadataParser.TAG_ENUM_MEMBER:
return this.getObjects(element, field.name, []);
case ODataMetadataParser.TAG_PROPERTY_VALUE:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_PROPERTY, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_STRING, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_DATE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ENUM_MEMBER, FieldType.TAG),
]);
case ODataMetadataParser.TAG_PROPERTY_PATH:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.TAG_PROPERTY_VALUE, FieldType.TAG),
]);
case ODataMetadataParser.TAG_NAVIGATION_PROPERTY_PATH:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.TAG_PROPERTY_VALUE, FieldType.TAG),
]);
case ODataMetadataParser.TAG_ENUM_TYPE:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_MEMBER, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_UNDERLYING_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_IS_FLAGS, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_COMPLEX_TYPE:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_PROPERTY, FieldType.TAG),
new Field(ODataMetadataParser.TAG_NAVIGATION_PROPERTY, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_BASE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_OPEN_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ABSTRACT, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_ENTITY_TYPE:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_KEY, FieldType.TAG),
new Field(ODataMetadataParser.TAG_PROPERTY, FieldType.TAG),
new Field(ODataMetadataParser.TAG_NAVIGATION_PROPERTY, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_BASE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_OPEN_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ABSTRACT, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_HAS_STREAM, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_FUNCTION:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_RETURN_TYPE, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_IS_BOUND, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ENTITY_SET_PATH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_IS_COMPOSABLE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_PARAMETER, FieldType.TAG),
]);
case ODataMetadataParser.TAG_MEMBER:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_VALUE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_PROPERTY:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_NULLABLE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_MAX_LENGTH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_PRECISION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SCALE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_UNICODE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SRID, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_DEFAULT_VALUE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_KEY:
return this.getObject(element, field.name, [
new Field(ODataMetadataParser.TAG_PROPERTY_REF, FieldType.TAG),
]);
case ODataMetadataParser.TAG_PROPERTY_REF:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ALIAS, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_NAVIGATION_PROPERTY:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_NULLABLE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_PARTNER, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_CONTAINS_TARGET, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_REFERENTIAL_CONSTRAINT, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ON_DELETE, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_REFERENTIAL_CONSTRAINT:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_PROPERTY, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_REFERENCED_PROPERTY, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_ON_DELETE:
return this.getObject(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_ACTION, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_PARAMETER:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_NULLABLE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_MAX_LENGTH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_PRECISION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SCALE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SRID, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_RETURN_TYPE:
return this.getObject(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_NULLABLE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_MAX_LENGTH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_PRECISION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SCALE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SRID, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_ACTION:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_RETURN_TYPE, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_IS_BOUND, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ENTITY_SET_PATH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_PARAMETER, FieldType.TAG),
]);
case ODataMetadataParser.TAG_ENTITY_CONTAINER:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_EXTENDS, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ENTITY_SET, FieldType.TAG),
new Field(ODataMetadataParser.TAG_SINGLETON, FieldType.TAG),
new Field(ODataMetadataParser.TAG_FUNCTION_IMPORT, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ACTION_IMPORT, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_ENTITY_SET:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ENTITY_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_NAVIGATION_PROPERTY_BINDING, FieldType.TAG),
new Field(ODataMetadataParser.ATTRIBUTE_INCLUDE_IN_SERVICE_DOCUMENT, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_SINGLETON:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_NAVIGATION_PROPERTY_BINDING, FieldType.TAG),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
case ODataMetadataParser.TAG_FUNCTION_IMPORT:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_FUNCTION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ENTITY_SET, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_INCLUDE_IN_SERVICE_DOCUMENT, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_ACTION_IMPORT:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ACTION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_ENTITY_SET, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_NAVIGATION_PROPERTY_BINDING:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_PATH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_TARGET, FieldType.ATTRIBUTE),
]);
case ODataMetadataParser.TAG_TYPE_DEFINITION:
return this.getObjects(element, field.name, [
new Field(ODataMetadataParser.ATTRIBUTE_NAME, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_UNDERLYING_TYPE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_MAX_LENGTH, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_PRECISION, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SCALE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_UNICODE, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.ATTRIBUTE_SRID, FieldType.ATTRIBUTE),
new Field(ODataMetadataParser.TAG_ANNOTATION, FieldType.TAG),
]);
default:
throw new Error('Unknwon tag:' + field.name);
}
}
getAttributeValue(attributes, attributeName) {
const attribute = attributes.getNamedItem(attributeName);
return attribute !== null && attribute.nodeValue
? attribute.nodeValue
: undefined;
}
propertyValueToNumber(attributeValue) {
return attributeValue !== undefined ? Number(attributeValue) : undefined;
}
propertyValueToBoolean(attributeValue) {
return attributeValue !== undefined ? attributeValue === 'true' : false;
}
propertyValueToDate(attributeValue) {
return attributeValue !== undefined ? new Date(attributeValue) : undefined;
}
}
class ODataMetadataResource extends ODataResource {
constructor(api, segments) {
super(api, { segments });
}
//#region Factory
static factory(api) {
let segments = new ODataPathSegments();
segments.add(PathSegment.metadata, $METADATA);
return new ODataMetadataResource(api, segments);
}
clone() {
return super.clone();
}
//#endregion
//#region Requests
get(options) {
return super.get({
responseType: 'text',
...options,
headers: { [ACCEPT]: APPLICATION_XML },
});
}
//#endregion
//#region Shortcuts
fetch(options) {
return this.get(options).pipe(map((body) => new ODataMetadataParser(body).metadata()));
}
}
class ODataSingletonResource extends ODataResource {
//#region Factory
static factory(api, { path, type, }) {
const segments = new ODataPathSegments();
const segment = segments.add(PathSegment.singleton, path);
if (type !== undefined) {
segment.outgoingType(type);
segment.incomingType(type);
}
return new ODataSingletonResource(api, { segments });
}
clone() {
return super.clone();
}
//#endregion
key(value) {
const singleton = this.clone();
var key = this.resolveKey(value);
if (key !== undefined)
singleton.segment((s) => s.singleton().key(key));
return singleton;
}
keys(values) {
const singleton = this.clone();
const types = this.pathSegments.types({ key: true });
const keys = values.map((value, index) => ODataResource.resolveKey(value, this.api.findStructuredType(types[index])));
singleton.segment((s) => s.keys(keys));
return singleton;
}
navigationProperty(path) {
return ODataNavigationPropertyResource.fromResource(this, path);
}
property(path) {
return ODataPropertyResource.fromResource(this, path);
}
action(path) {
return ODataActionResource.fromResource(this, path);
}
function(path) {
return ODataFunctionResource.fromResource(this, path);
}
//#region Requests
post(attrs, options = {}) {
return super.post(attrs, { responseType: 'entity', ...options });
}
put(attrs, options = {}) {
return super.put(attrs, { responseType: 'entity', ...options });
}
patch(attrs, options = {}) {
return super.patch(attrs, { responseType: 'entity', ...options });
}
delete(options = {}) {
return super.delete({ responseType: 'entity', ...options });
}
get(options = {}) {
return super.get({ responseType: 'entity', ...options });
}
//#endregion
//#region Shortcuts
/**
* Creates a new entity.
* @param attrs The entity attributes to create.
* @param options The options for the request.
* @returns The created entity with the annotations.
*/
create(attrs, options) {
return this.post(attrs, options);
}
/**
* Updates an existing entity.
* @param attrs The entity attributes to update.
* @param options The options for the request.
* @returns The updated entity with the annotations.
*/
update(attrs, options) {
return this.put(attrs, options);
}
/**
* Modifies an existing entity.
* @param attrs The entity attributes to modify.
* @param options The options for the request.
* @returns The modified entity with the annotations.
*/
modify(attrs, options) {
return this.patch(attrs, options);
}
/**
* Delete an existing entity.
* @param options The options for the request.
* @returns Observable of the deleted entity.
*/
destroy(options) {
return this.delete(options);
}
/**
* Fetch an existing entity.
* @param options The options for the request.
* @returns Observable of the entity with the annotations.
*/
fetch(options) {
return this.get(options);
}
/**
* Fetch an existing entity.
* @param options The options for the request.
* @returns Observable of the entity.
*/
fetchEntity(options) {
return this.fetch(options).pipe(map(({ entity }) => entity));
}
fetchModel(options) {
return this.fetch(options).pipe(map(({ entity, annots }) => entity
? this.asModel(entity, { annots, ModelType: options?.ModelType })
: null));
}
}
class ODataAnnotations {
helper;
annotations;
context;
constructor(helper, annotations = new Map(), context) {
this.helper = helper;
this.annotations = annotations;
this.context = context;
}
attributes(data, metadata) {
return this.helper.attributes(data, metadata);
}
update(data) {
this.annotations = new Map([
...this.annotations,
...this.helper.annotations(data),
]);
}
get entitySet() {
return this.context?.entitySet;
}
get type() {
return this.helper.type(this.annotations) || this.context?.type;
}
}
class ODataPropertyAnnotations extends ODataAnnotations {
union(other) {
return new ODataPropertyAnnotations(this.helper, new Map([...this.annotations, ...other.annotations]), Object.assign({}, this.context, other.context));
}
clone() {
return new ODataPropertyAnnotations(this.helper, new Map(this.annotations), this.context);
}
data(data) {
return this.helper.property(data);
}
}
class ODataEntityAnnotations extends ODataAnnotations {
union(other) {
return new ODataEntityAnnotations(this.helper, new Map([...this.annotations, ...other.annotations]), Object.assign({}, this.context, other.context));
}
clone() {
return new ODataEntityAnnotations(this.helper, new Map(this.annotations), this.context);
}
data(data) {
return this.helper.entity(data);
}
get id() {
return this.helper.id(this.annotations);
}
get etag() {
return this.helper.etag(this.annotations);
}
get mediaEtag() {
return this.helper.mediaEtag(this.annotations);
}
get metadataEtag() {
return this.helper.metadataEtag(this.annotations);
}
get readLink() {
return this.helper.readLink(this.annotations);
}
get editLink() {
return this.helper.editLink(this.annotations);
}
get mediaReadLink() {
return this.helper.mediaReadLink(this.annotations);
}
get mediaEditLink() {
return this.helper.mediaEditLink(this.annotations);
}
get mediaContentType() {
return this.helper.mediaContentType(this.annotations);
}
_properties;
get properties() {
if (this._properties === undefined) {
this._properties = this.helper.properties(this.annotations);
}
return this._properties;
}
property(name, type) {
const props = this.properties.get(name);
return type === 'collection'
? new ODataEntitiesAnnotations(this.helper, props)
: new ODataEntityAnnotations(this.helper, props);
}
_functions;
get functions() {
if (this._functions === undefined) {
this._functions = this.helper.functions(this.annotations);
}
return this._functions;
}
function(name) {
return this.functions[name];
}
}
class ODataEntitiesAnnotations extends ODataAnnotations {
union(other) {
return new ODataEntitiesAnnotations(this.helper, new Map([...this.annotations, ...other.annotations]), Object.assign({}, this.context, other.context));
}
clone() {
return new ODataEntitiesAnnotations(this.helper, new Map(this.annotations), this.context);
}
data(data) {
return this.helper.entities(data);
}
get readLink() {
return this.helper.readLink(this.annotations);
}
get count() {
return this.helper.count(this.annotations);
}
get nextLink() {
return this.helper.nextLink(this.annotations);
}
get deltaLink() {
return this.helper.deltaLink(this.annotations);
}
get top() {
let match = (this.nextLink || '').match(/[&?]{1}\$top=(\d+)/);
return match !== null ? Number(match[1]) : undefined;
}
get skip() {
let match = (this.nextLink || '').match(/[&?]{1}\$skip=(\d+)/);
return match !== null ? Number(match[1]) : undefined;
}
get skiptoken() {
let match = (this.nextLink || '').match(/[&?]{1}\$skiptoken=([\d\w\s'\-]+)/);
return match !== null ? match[1] : undefined;
}
_functions;
get functions() {
if (this._functions === undefined) {
this._functions = this.helper.functions(this.annotations);
}
return this._functions;
}
function(name) {
return this.functions[name];
}
entity() {
return new ODataEntityAnnotations(this.helper, this.annotations);
}
}
class ODataResponseOptions {
version;
streaming;
// OData
metadata;
ieee754Compatible;
// Location
location;
// Cache
cacheability;
maxAge;
constructor(config) {
this.version = config.version || DEFAULT_VERSION;
}
get helper() {
return ODataHelper[this.version];
}
clone() {
return new ODataResponseOptions(this);
}
setFeatures(features) {
features.split(';').forEach((o) => {
let [k, v] = o.split('=');
switch (k.trim()) {
case 'odata.metadata':
this.metadata = v;
break;
case 'odata.streaming':
this.streaming = v == 'true';
break;
case 'IEEE754Compatible':
this.ieee754Compatible = v == 'true';
break;
}
});
}
setVersion(version) {
const value = version.replace(/\;/g, '').trim();
if ([VERSION_2_0, VERSION_3_0, VERSION_4_0].indexOf(value) !== -1)
this.version = value;
}
setLocation(location) {
// TODO: resolve location?
this.location = location;
}
setPreferenceApplied(preference) {
preference.split(',').forEach((prefer) => {
// TODO: resolve preference
});
}
setCache(cacheControl) {
cacheControl.split(',').forEach((directive) => {
if (directive.startsWith(MAX_AGE)) {
let maxAge = Number(directive.split('=')[1]);
if (!Number.isNaN(maxAge))
this.maxAge = maxAge;
}
if (['public', 'private', 'no-cache', 'no-store'].indexOf(directive) !== -1) {
this.cacheability = directive;
}
});
}
}
/**
* OData Response
*/
class ODataResponse extends HttpResponse {
api;
resource;
constructor(init) {
super(init);
this.api = init.api;
this.resource = init.resource;
}
static fromHttpResponse(req, res) {
return new ODataResponse({
api: req.api,
resource: req.resource,
body: res.body,
headers: res.headers,
status: res.status,
statusText: res.statusText,
url: res.url || undefined,
});
}
static fromJson(req, json) {
return new ODataResponse({
api: req.api,
resource: req.resource,
body: json.body,
headers: new HttpHeaders(json.headers),
status: json.status,
statusText: json.statusText,
url: json.url || undefined,
});
}
toJson() {
return {
body: this.body,
headers: this.headers
.keys()
.map((name) => ({ [name]: this.headers.getAll(name) || [] }))
.reduce((acc, header) => Object.assign(acc, header), {}),
status: this.status,
statusText: this.statusText,
url: this.url,
};
}
_options;
get options() {
if (this._options === undefined) {
this._options = new ODataResponseOptions(this.api.options.parserOptions);
const contentType = this.headers.get(CONTENT_TYPE);
if (contentType && contentType.includes(APPLICATION_JSON)) {
const features = contentType
.split(',')
.find((p) => p.startsWith(APPLICATION_JSON));
this._options.setFeatures(features);
}
const headerKey = Http.resolveHeaderKey(this.headers, ODATA_VERSION_HEADERS);
if (headerKey) {
const version = (this.headers.get(headerKey) || '').replace(/\;/g, '');
this._options.setVersion(version);
}
const preferenceApplied = this.headers.get(PREFERENCE_APPLIED);
if (preferenceApplied) {
this._options.setPreferenceApplied(preferenceApplied);
}
const location = this.headers.get(LOCATION_HEADER);
if (location) {
this._options.setLocation(location);
}
const cacheControl = this.headers.get(CACHE_CONTROL);
if (cacheControl) {
this._options.setCache(cacheControl);
}
}
return this._options;
}
_payload;
get payload() {
if (this._payload === undefined) {
this._payload =
this.body && this.options.version === '2.0'
? this.body['d']
: this.body;
}
return this._payload;
}
_context;
get context() {
if (this._context === undefined) {
this._context = this.options.helper.context(Types.isPlainObject(this.payload) ? this.payload : {});
}
return this._context;
}
_annotations;
get annotations() {
if (this._annotations === undefined) {
const options = this.options;
this._annotations = options.helper.annotations(this.payload || {});
let key = Http.resolveHeaderKey(this.headers, ETAG_HEADERS);
if (key) {
const etag = this.headers.get(key);
if (etag)
this._annotations.set(options.helper.ODATA_ETAG, etag);
}
key = Http.resolveHeaderKey(this.headers, ODATA_ENTITYID_HEADERS);
if (key) {
const entityId = this.headers.get(key);
if (entityId)
this._annotations.set(options.helper.ODATA_ID, entityId);
}
}
return this._annotations;
}
/**
* Handle the response body as an entity
* @returns
*/
entity() {
const options = this.options;
const payload = this.payload;
const annots = new ODataEntityAnnotations(options.helper, this.annotations, this.context);
const data = payload ? annots.data(payload) : null;
let entity = (data !== null && Types.isPlainObject(data)
? options.helper.attributes(data, this.api.options.stripMetadata)
: data);
if (entity !== null)
entity = this.resource.deserialize(entity, options);
return { entity, annots };
}
/**
* Handle the response body as entities
* @returns
*/
entities() {
const options = this.options;
const payload = this.payload;
const annots = new ODataEntitiesAnnotations(options.helper, this.annotations, this.context);
let entities = payload ? annots.data(payload) : null;
if (entities !== null)
entities = this.resource.deserialize(entities, options);
return { entities, annots };
}
/**
* Handle the response body as a property
* @returns
*/
property() {
const options = this.options;
const payload = this.payload;
const annots = new ODataPropertyAnnotations(options.helper, this.annotations, this.context);
const data = payload ? annots.data(payload) : null;
let property = (data !== null && Types.isPlainObject(data)
? options.helper.attributes(data, this.api.options.stripMetadata)
: data);
if (property !== null)
property = this.resource.deserialize(property, options);
return { property, annots };
}
/**
* Handle the response body as a value
* @returns
*/
value() {
const options = this.options;
const payload = this.payload;
let value = (payload !== null && Types.isPlainObject(payload)
? options.helper.attributes(payload, this.api.options.stripMetadata)
: payload);
if (value !== null)
value = this.resource.deserialize(value, options);
return value;
}
}
class ODataInStorageCache extends ODataCache {
name;
storage;
constructor({ name, storage = sessionStorage, timeout, }) {
super({ timeout });
this.name = name;
this.storage = storage;
this.restore();
window.addEventListener('beforeunload', () => this.store());
}
/**
* Store the cache in the storage
*/
store() {
this.storage.setItem(this.name, JSON.stringify(Array.from(this.entries.entries())));
}
/**
* Restore the cache from the storage
*/
restore() {
this.entries = new Map(JSON.parse(this.storage.getItem(this.name) || '[]'));
}
/**
* Flush the cache and clean the storage
*/
flush() {
super.flush();
this.store();
}
/**
* 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) {
const scope = this.scope(req);
const tags = this.tags(res);
this.put(req.cacheKey, res.toJson(), {
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) {
const scope = this.scope(req);
const data = this.get(req.cacheKey, { scope });
return data !== undefined ? ODataResponse.fromJson(req, data) : undefined;
}
}
class ODataCollection {
static model = null;
_parent = null;
_resource = null;
_resources = [];
_annotations;
_entries = [];
_model;
models() {
return this._entries
.filter((e) => e.state !== ODataModelState.Removed)
.map((e) => e.model);
}
get length() {
return this.models().length;
}
//Events
events$;
constructor(entities = [], { parent, resource, annots, model, reset = false, } = {}) {
const Klass = this.constructor;
if (!model && Klass.model !== null)
model = Klass.model;
if (!model)
throw new Error('Collection: Collection need model');
this._model = model;
// Events
this.events$ = new ODataModelEventEmitter({ collection: this });
this.events$.subscribe((e) => model.meta.events$.emit(e));
// Parent
if (parent !== undefined) {
this._parent = parent;
}
// Resource
if (this._parent === null && !resource)
resource = this._model.meta.collectionResourceFactory();
if (resource) {
this.attach(resource);
}
// Annotations
this._annotations =
annots ||
new ODataEntitiesAnnotations(ODataHelper[resource?.api?.options.version || DEFAULT_VERSION]);
entities = entities || [];
this.assign(entities, { reset });
}
isParentOf(child) {
return (child !== this &&
ODataModelOptions.chain(child).some((p) => p[0] === this));
}
resource() {
return ODataModelOptions.resource(this);
}
pushResource(resource) {
// Push current parent and resource
this._resources.push({ parent: this._parent, resource: this._resource });
// Replace parent and resource
this._parent = null;
this._resource = resource;
}
popResource() {
// Pop parent and resource
const pop = this._resources.pop();
if (pop !== undefined) {
const current = { parent: this._parent, resource: this._resource };
this._parent = pop.parent;
this._resource = pop.resource;
return current;
}
return undefined;
}
attach(resource) {
if (this._resource !== null &&
this._resource.outgoingType() !== resource.outgoingType() &&
!this._resource.isSubtypeOf(resource))
throw new Error(`attach: Can't reattach ${this._resource.outgoingType()} to ${resource.outgoingType()}`);
this._entries.forEach(({ model }) => {
const modelResource = this._model.meta.modelResourceFactory(resource.cloneQuery());
if (modelResource !== undefined)
model.attach(modelResource);
});
const current = this._resource;
if (current === null || !current.isEqualTo(resource)) {
this._resource = resource;
this.events$.trigger(ODataModelEventType.Attach, {
previous: current,
value: resource,
});
}
}
withResource(resource, ctx) {
// Push
this.pushResource(resource);
// Execute
const result = ctx(this);
if (result instanceof Observable) {
return result.pipe(finalize(() => this.popResource()));
}
else {
// Pop
this.popResource();
return result;
}
}
asEntitySet(ctx) {
// Build new resource
const resource = this._model.meta.collectionResourceFactory(this._resource?.cloneQuery());
return this.withResource(resource, ctx);
}
annots() {
return this._annotations;
}
modelFactory(data, { reset = false } = {}) {
let Model = this._model;
const annots = new ODataEntityAnnotations(this._annotations.helper);
annots.update(data);
if (annots?.type !== undefined && Model.meta !== null) {
const schema = Model.meta.findChildOptions((o) => o.isTypeOf(annots.type))?.structuredType;
if (schema !== undefined && schema.model !== undefined)
// Change to child model
Model = schema.model;
}
return new Model(data, {
annots,
reset,
parent: [this, null],
});
}
toEntities({ client_id = false, include_navigation = false, include_concurrency = false, include_computed = false, include_key = true, include_id = false, include_non_field = false, changes_only = false, field_mapping = false, chain = [], } = {}) {
return this._entries
.filter(({ model, state }) => state !== ODataModelState.Removed && chain.every((c) => c !== model))
.map(({ model, state }) => {
var changesOnly = changes_only && state !== ODataModelState.Added;
return model.toEntity({
client_id,
include_navigation,
include_concurrency,
include_computed,
include_key,
include_id,
include_non_field,
field_mapping,
changes_only: changesOnly,
chain: [this, ...chain],
});
});
}
toJson() {
return this.toEntities(INCLUDE_DEEP);
}
hasChanged({ include_navigation } = {}) {
return (this._entries.some((e) => e.state !== ODataModelState.Unchanged) ||
this.models().some((m) => m.hasChanged({ include_navigation })));
}
clone() {
return new this.constructor(this.toEntities(INCLUDE_DEEP), {
resource: this.resource(),
annots: this.annots(),
});
}
_request(obs$, mapCallback) {
this.events$.trigger(ODataModelEventType.Request, {
options: { observable: obs$ },
});
return obs$.pipe(map((response) => mapCallback(response)), finalize(() => this.events$.trigger(ODataModelEventType.Sync)));
}
fetch({ add, merge, remove, withCount, ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('fetch: Resource is null'));
const obs$ = resource instanceof ODataEntitySetResource
? resource.fetch({ withCount, ...options })
: resource.fetch({
responseType: 'entities',
withCount,
...options,
});
return this._request(obs$, ({ entities, annots }) => {
this._annotations = annots;
return entities !== null
? this.assign(entities, {
reset: true,
add: add ?? true,
merge: merge ?? true,
remove: remove ?? true,
})
: [];
});
}
fetchAll({ add, merge, remove, withCount, ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('fetchAll: Resource is null'));
const obs$ = resource.fetchAll({ withCount, ...options });
return this._request(obs$, ({ entities, annots }) => {
this._annotations = annots;
return entities !== null
? this.assign(entities, {
reset: true,
add: add ?? true,
merge: merge ?? true,
remove: remove ?? true,
})
: [];
});
}
fetchMany(top, { add, merge, remove, withCount, ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('fetchMany: Resource is null'));
resource.query((q) => remove || this.length == 0 ? q.skip().clear() : q.skip(this.length));
const obs$ = resource.fetchMany(top, { withCount, ...options });
return this._request(obs$, ({ entities, annots }) => {
this._annotations = annots;
return entities !== null
? this.assign(entities, {
reset: true,
add: add ?? true,
merge: merge ?? true,
remove: remove ?? false,
})
: [];
});
}
fetchOne({ add, merge, remove, withCount, ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('fetchOne: Resource is null'));
resource.query((q) => remove || this.length == 0 ? q.skip().clear() : q.skip(this.length));
const obs$ = resource.fetchOne({ withCount, ...options });
return this._request(obs$, ({ entity, annots }) => {
this._annotations = annots;
return entity !== null
? this.assign([entity], {
reset: true,
add: add ?? true,
merge: merge ?? true,
remove: remove ?? false,
})[0]
: null;
});
}
/**
* Save all models in the collection
* @param relModel The model is relationship
* @param method The method to use
* @param options HttpOptions
*/
save({ relModel = false, method, ...options } = {}) {
const resource = this.resource();
if (resource instanceof ODataPropertyResource)
return throwError(() => new Error('save: Resource is ODataPropertyResource'));
const toDestroyEntity = [];
const toRemoveReference = [];
const toDestroyContained = [];
const toCreateEntity = [];
const toAddReference = [];
const toCreateContained = [];
const toUpdateEntity = [];
const toUpdateContained = [];
this._entries.forEach(({ model, state }) => {
if (state === ODataModelState.Removed) {
if (relModel) {
toDestroyEntity.push(model);
}
else if (!model.isNew()) {
toRemoveReference.push(model);
}
else {
toDestroyContained.push(model);
}
}
else if (state === ODataModelState.Added) {
if (relModel) {
toCreateEntity.push(model);
}
else if (!model.isNew()) {
toAddReference.push(model);
}
else {
toCreateContained.push(model);
}
}
else if (model.hasChanged()) {
toUpdateEntity.push(model);
}
});
const obs$ = forkJoin([
...toDestroyEntity.map((m) => m.asEntity((e) => e.destroy(options))),
...toRemoveReference.map((m) => (this._model.meta.api.options.deleteRefBy === 'path'
? resource.key(m.key())
: resource)
.reference()
.remove(this._model.meta.api.options.deleteRefBy === 'id'
? m.asEntity((e) => e.resource())
: undefined, options)),
...toDestroyContained.map((m) => m.destroy(options)),
...toCreateEntity.map((m) => m.asEntity((e) => e.save({ method: 'create', ...options }))),
...toAddReference.map((m) => resource
.reference()
.add(m.asEntity((e) => e.resource()), options)),
...toCreateContained.map((m) => m.save({ method: 'create', ...options })),
...toUpdateEntity.map((m) => m.asEntity((e) => e.save({ method, ...options }))),
...toUpdateContained.map((m) => m.save({ method, ...options })),
]).pipe(defaultIfEmpty(null));
return this._request(obs$, () => {
this._entries = this._entries
.filter((entry) => entry.state !== ODataModelState.Removed)
.map((entry) => ({ ...entry, state: ODataModelState.Unchanged }));
return this.models();
});
}
_addServer(model, options) {
const resource = this.resource();
if (resource instanceof ODataNavigationPropertyResource) {
if (!model.isNew()) {
// Add Reference
return resource
.reference()
.add(model.asEntity((e) => e.resource()), options)
.pipe(map(() => model));
}
else {
// Create Contained
return resource.create(model.toEntity(), options).pipe(map(({ entity }) => {
if (entity) {
model.assign(entity);
}
return model;
}));
}
}
else if (resource instanceof ODataEntitySetResource) {
return model.asEntity((e) => e.save({ method: 'create', ...options }));
}
else {
return of(model);
}
}
_addModel(model, { silent = false, reset = false, reparent = false, merge = false, position, } = {}) {
let entry = this._findEntry(model);
if (entry !== undefined && entry.state !== ODataModelState.Removed) {
if (merge) {
entry.model.assign(model.toEntity(INCLUDE_DEEP));
}
return entry.model;
}
if (entry !== undefined && entry.state === ODataModelState.Removed) {
const index = this._entries.indexOf(entry);
this._entries.splice(index, 1);
}
// Create Entry
entry = {
state: reset ? ODataModelState.Unchanged : ODataModelState.Added,
model,
key: model.key(),
};
// Set Parent
if (reparent)
model._parent = [this, null];
// Subscribe
this._link(entry);
// If position is undefined and the collection is sorted, find the right position
if (position === undefined && this._sortBy !== null) {
for (let index = 0; index < this._entries.length; index++) {
if (this._compare(model, this._entries[index], this._sortBy, 0) < 0) {
position = index;
break;
}
}
}
// Now add
if (position !== undefined)
this._entries.splice(position, 0, entry);
else
this._entries.push(entry);
if (!silent) {
model.events$.trigger(ODataModelEventType.Add, {
collection: this,
options: { index: position },
});
}
return entry.model;
}
add(model, { silent = false, reparent = false, server = true, merge = false, position, reset, } = {}) {
const _addModel = (m, reset) => this._addModel(m, { silent, position, merge, reparent, reset });
return server
? this._request(this._addServer(model), (model) => _addModel(model, reset ?? true))
: of(_addModel(model, reset ?? false));
}
_removeServer(model, options) {
let resource = this.resource();
if (resource instanceof ODataNavigationPropertyResource) {
if (!model.isNew()) {
// Remove Reference
const target = this._model.meta.api.options.deleteRefBy === 'id'
? model.asEntity((e) => e.resource())
: undefined;
if (this._model.meta.api.options.deleteRefBy === 'path') {
resource = resource.key(model.key());
}
return resource
.reference()
.remove(target, options)
.pipe(map(() => model));
}
else {
// Remove Contained
return resource.destroy(options).pipe(map(() => model));
}
}
else if (resource instanceof ODataEntitySetResource) {
return model.asEntity((e) => e.destroy(options));
}
else {
return of(model);
}
}
_removeModel(model, { silent = false, reset = false, } = {}) {
const entry = this._findEntry(model);
if (entry === undefined || entry.state === ODataModelState.Removed) {
return model;
}
// Now remove
const index = this._entries.indexOf(entry);
this._entries.splice(index, 1);
if (!(reset || entry.state === ODataModelState.Added)) {
// Move to end of array and mark as removed
entry.state = ODataModelState.Removed;
this._entries.push(entry);
}
// Trigger Event
if (!silent) {
model.events$.trigger(ODataModelEventType.Remove, {
collection: this,
options: { index: index },
});
}
this._unlink(entry);
return entry.model;
}
remove(model, { silent = false, server = true, reset, } = {}) {
const _removeModel = (m, reset) => this._removeModel(m, { silent, reset });
return server
? this._request(this._removeServer(model), (model) => _removeModel(model, reset ?? true))
: of(_removeModel(model, reset ?? false));
}
_moveModel(model, position) {
const entry = this._findEntry(model);
if (entry === undefined || entry.state === ODataModelState.Removed) {
return model;
}
// Now remove
const index = this._entries.indexOf(entry);
this._entries.splice(index, 1);
this._entries.splice(position, 0, entry);
return entry.model;
}
create(attrs = {}, { silent = false, server = true, } = {}) {
const model = this.modelFactory(attrs);
return (model.isValid() && server ? model.save() : of(model)).pipe(switchMap((model) => this.add(model, { silent, server })), map(() => model));
}
set(path, value, {}) {
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
if (pathArray.length === 0)
return undefined;
if (pathArray.length > 1) {
const model = this._entries[Number(pathArray[0])].model;
return model.set(pathArray.slice(1), value, {});
}
if (pathArray.length === 1 && ODataModelOptions.isModel(value)) {
const models = this.models();
const index = Number(pathArray[0]);
models[index] = value;
this.assign(models, { reparent: true });
return value;
}
}
get(path) {
const pathArray = (Types.isArray(path) ? path : `${path}`.match(/([^[.\]])+/g));
if (pathArray.length === 0)
return undefined;
const value = this.models()[Number(pathArray[0])];
if (pathArray.length > 1 && ODataModelOptions.isModel(value)) {
return value.get(pathArray.slice(1));
}
return value;
}
has(path) {
const pathArray = (Types.isArray(path) ? path : `${path}`.match(/([^[.\]])+/g));
if (pathArray.length === 0)
return false;
const value = this.models()[Number(pathArray[0])];
if (pathArray.length > 1 && ODataModelOptions.isModel(value)) {
return value.has(pathArray.slice(1));
}
return value !== undefined;
}
reset({ path, silent = false, } = {}) {
let toAdd = [];
let toChange = [];
let toRemove = [];
if (path !== undefined) {
// Reset by path
const pathArray = (Types.isArray(path) ? path : `${path}`.match(/([^[.\]])+/g));
const index = Number(pathArray[0]);
if (!Number.isNaN(index)) {
const model = this.models()[index];
if (ODataModelOptions.isModel(model)) {
const entry = this._findEntry(model);
if (entry.state === ODataModelState.Unchanged &&
entry.model.hasChanged()) {
toChange = [entry];
}
path = pathArray.slice(1);
}
}
}
else {
// Reset all
toAdd = this._entries.filter((e) => e.state === ODataModelState.Removed);
toChange = this._entries.filter((e) => e.state === ODataModelState.Unchanged && e.model.hasChanged());
toRemove = this._entries.filter((e) => e.state === ODataModelState.Added);
}
toRemove.forEach((entry) => {
this._removeModel(entry.model, { silent });
});
toAdd.forEach((entry) => {
this._addModel(entry.model, { silent });
});
toChange.forEach((entry) => {
entry.model.reset({ path, silent });
entry.state = ODataModelState.Unchanged;
});
if (!silent &&
(toAdd.length > 0 || toRemove.length > 0 || toChange.length > 0)) {
this.events$.trigger(ODataModelEventType.Reset, {
options: {
added: toAdd.map((e) => e.model),
removed: toRemove.map((e) => e.model),
changed: toChange.map((e) => e.model),
},
});
}
}
clear({ silent = false } = {}) {
const toRemove = this.models();
toRemove.forEach((m) => {
this._removeModel(m, { silent });
});
this._entries = [];
if (!silent) {
this.events$.trigger(ODataModelEventType.Update, {
options: { removed: toRemove },
});
}
}
assign(objects, { add = true, merge = true, remove = true, reset = false, reparent = false, silent = false, } = {}) {
const offset = remove ? 0 : this.length;
const models = [];
const toAdd = [];
const toMerge = [];
const toRemove = [];
objects.forEach((obj, index) => {
const model = ODataModelOptions.isModel(obj)
? obj
: this.modelFactory(obj, {
reset,
});
const position = index + offset;
// Try find entry
const entry = this._findEntry(model);
if (merge && entry !== undefined) {
if (entry.model !== model) {
entry.model.assign(model.toEntity({
client_id: true,
...INCLUDE_DEEP,
}), { add, merge, remove, reset, silent });
// Model Change?
if (entry.model.hasChanged())
toMerge.push(entry.model);
}
if (reset)
entry.state = ODataModelState.Unchanged;
if (!models.includes(entry.model)) {
models.push(entry.model);
}
}
else if (add) {
// Add
toAdd.push(model);
this._addModel(model, { silent, reset, reparent, position });
models.push(model);
}
});
if (remove) {
[...this._entries].forEach((entry) => {
const model = entry.model;
if (!models.includes(model)) {
this._removeModel(model, { silent, reset });
toRemove.push(model);
}
});
}
if (this.models()
.slice(offset)
.some((m, i) => m !== models[i])) {
models.forEach((m, i) => this._moveModel(m, i));
this.events$.trigger(ODataModelEventType.Sort);
}
if (!silent &&
(toAdd.length > 0 || toRemove.length > 0 || toMerge.length > 0)) {
this.events$.trigger(reset ? ODataModelEventType.Reset : ODataModelEventType.Update, {
options: {
added: toAdd,
removed: toRemove,
merged: toMerge,
},
});
}
return models;
}
query(ctx) {
const resource = this.resource();
if (resource) {
resource.query(ctx);
this.attach(resource);
}
return this;
}
callFunction(name, params, responseType, options = {}) {
const resource = this.resource();
if (!(resource instanceof ODataEntitySetResource))
return throwError(() => new Error("callFunction: Can't call function without ODataEntitySetResource"));
const func = resource.function(name).query((q) => q.restore(options));
switch (responseType) {
case 'property':
return this._request(func.callProperty(params, options), (resp) => resp);
case 'model':
return this._request(func.callModel(params, options), (resp) => resp);
case 'collection':
return this._request(func.callCollection(params, options), (resp) => resp);
default:
return this._request(func.call(params, { responseType, ...options }), (resp) => resp);
}
}
callAction(name, params, responseType, options = {}) {
const resource = this.resource();
if (!(resource instanceof ODataEntitySetResource)) {
return throwError(() => new Error(`callAction: Can't call action without ODataEntitySetResource`));
}
const action = resource.action(name).query((q) => q.restore(options));
switch (responseType) {
case 'property':
return this._request(action.callProperty(params, options), (resp) => resp);
case 'model':
return this._request(action.callModel(params, options), (resp) => resp);
case 'collection':
return this._request(action.callCollection(params, options), (resp) => resp);
default:
return this._request(action.call(params, { responseType, ...options }), (resp) => resp);
}
}
_unlink(entry) {
if (entry.subscription) {
entry.subscription.unsubscribe();
entry.subscription = undefined;
}
}
_link(entry) {
if (entry.subscription) {
throw new Error('Collection: Subscription already exists');
}
entry.subscription = entry.model.events$.subscribe((event) => {
if (event.canContinueWith(this)) {
if (event.model === entry.model) {
if (event.type === ODataModelEventType.Destroy) {
this._removeModel(entry.model, { reset: true });
}
else if (event.type === ODataModelEventType.Change &&
event.options?.key) {
entry.key = entry.model.key();
}
}
const index = event.options?.index ?? this.models().indexOf(entry.model);
this.events$.emit(event.push(this, index));
}
});
}
_findEntry(model) {
return this._entries.find((entry) => (entry.key !== undefined &&
model.key() !== undefined &&
Types.isEqual(entry.key, model.key())) ||
entry.model.equals(model));
}
// Collection functions
equals(other) {
return this === other;
}
get [Symbol.toStringTag]() {
return 'Collection';
}
[Symbol.iterator]() {
let pointer = 0;
const models = this.models();
return {
next() {
return {
done: pointer === models.length,
value: models[pointer++],
};
},
};
}
filter(predicate, thisArg) {
return this.models().filter(predicate, thisArg);
}
map(callbackfn, thisArg) {
return this.models().map(callbackfn, thisArg);
}
find(predicate) {
return this.models().find(predicate);
}
reduce(callbackfn, initialValue) {
return this.models().reduce(callbackfn, initialValue);
}
first() {
return this.models()[0];
}
last() {
const models = this.models();
return models[models.length - 1];
}
next(model) {
const index = this.indexOf(model);
if (index === -1 || index === this.length - 1) {
return undefined;
}
return this.get(index + 1);
}
prev(model) {
const index = this.indexOf(model);
if (index <= 0) {
return undefined;
}
return this.get(index - 1);
}
every(predicate) {
return this.models().every(predicate);
}
some(predicate) {
return this.models().some(predicate);
}
includes(model, start = 0) {
return this.some((m, i) => i >= start && m.equals(model));
}
indexOf(model) {
const models = this.models();
const m = models.find((m) => m.equals(model));
return !m ? -1 : models.indexOf(m);
}
forEach(predicate, thisArg) {
return this.models().forEach(predicate, thisArg);
}
isEmpty() {
// Local length == 0 and if exist remote count is 0 or undefined
return this.length === 0 && !this.annots().count;
}
//#region Sort
_compare(e1, e2, by, index) {
const m1 = ODataModelOptions.isModel(e1)
? e1
: e1.model;
const m2 = ODataModelOptions.isModel(e2)
? e2
: e2.model;
const value1 = m1.get(by[index].field);
const value2 = m2.get(by[index].field);
let result = 0;
if (value1 == null && value2 != null)
result = -1;
else if (value1 != null && value2 == null)
result = 1;
else if (value1 == null && value2 == null)
result = 0;
else if ((typeof value1 == 'string' || value1 instanceof String) && value1.localeCompare && value1 != value2)
result = value1.localeCompare(value2);
else if (value1 == value2)
return by.length - 1 > index ? this._compare(e1, e2, by, index + 1) : 0;
else if (by[index].comparator !== undefined)
result = by[index].comparator(value1, value2);
else {
result = value1 < value2 ? -1 : 1;
}
return (by[index].order ?? 1) * result;
}
_sortBy = null;
isSorted() {
return this._sortBy !== null;
}
sort(by, { silent } = {}) {
this._sortBy = by;
this._entries = this._entries.sort((e1, e2) => this._compare(e1, e2, by, 0));
if (!silent) {
this.events$.trigger(ODataModelEventType.Sort);
}
}
}
class ODataModel {
// Properties
static options;
static meta;
// Parent
_parent = null;
_resource = null;
_resources = [];
_attributes = new Map();
_annotations;
_meta;
// Events
events$;
static buildMetaOptions({ config, structuredType, }) {
if (config === undefined) {
const fields = structuredType
.fields({ include_navigation: true, include_parents: true })
.reduce((acc, field) => {
let name = field.name;
// Prevent collision with reserved keywords
while (RESERVED_FIELD_NAMES.includes(name)) {
name = name + '_';
}
return Object.assign(acc, {
[name]: {
field: field.name,
default: field.default,
required: !field.nullable,
},
});
}, {});
config = {
fields: new Map(Object.entries(fields)),
};
}
return new ODataModelOptions({ config, structuredType });
}
constructor(data = {}, { parent, resource, annots, reset = false, } = {}) {
const Klass = this.constructor;
if (Klass.meta === undefined)
throw new Error(`Model: Can't create model without metadata`);
this._meta = Klass.meta;
this.events$ = new ODataModelEventEmitter({ model: this });
this._meta.bind(this, { parent, resource, annots });
// Client Id
this[this._meta.cid] =
data[this._meta.cid] ||
Strings.uniqueId({
prefix: `${Klass.meta.structuredType.name.toLowerCase()}-`,
});
if (!reset)
data = Objects.merge(this.defaults(), data);
this.assign(data, { reset });
}
//#region Resources
resource() {
return ODataModelOptions.resource(this);
}
pushResource(resource) {
// Push current parent and resource
this._resources.push({ parent: this._parent, resource: this._resource });
// Replace parent and resource
this._parent = null;
this._resource = resource;
}
popResource() {
// Pop parent and resource
const pop = this._resources.pop();
if (pop !== undefined) {
const current = { parent: this._parent, resource: this._resource };
this._parent = pop.parent;
this._resource = pop.resource;
return current;
}
return undefined;
}
navigationProperty(name) {
const field = this._meta.findField(name);
if (!field || !field.navigation)
throw Error(`navigationProperty: Can't find navigation property ${name}`);
const resource = this.resource();
if (!(resource instanceof ODataEntityResource) || !resource.hasKey())
throw Error("navigationProperty: Can't get navigation without ODataEntityResource with key");
return field.resourceFactory(resource);
}
property(name) {
const field = this._meta.findField(name);
if (!field || field.navigation)
throw Error(`property: Can't find property ${name}`);
const resource = this.resource();
if (!(resource instanceof ODataEntityResource) || !resource.hasKey())
throw Error("property: Can't get property without ODataEntityResource with key");
return field.resourceFactory(resource);
}
attach(resource) {
this._meta.attach(this, resource);
}
//#endregion
schema() {
return this._meta.structuredType;
}
annots() {
return this._annotations;
}
key({ field_mapping = false, resolve = true, } = {}) {
return this._meta.resolveKey(this, { field_mapping, resolve });
}
isOpenModel() {
return this._meta.isOpenType();
}
isParentOf(child) {
return (child !== this &&
ODataModelOptions.chain(child).some((p) => p[0] === this));
}
referential(attr, { field_mapping = false, resolve = true, } = {}) {
return this._meta.resolveReferential(this, attr, {
field_mapping,
resolve,
});
}
referenced(attr, { field_mapping = false, resolve = true, } = {}) {
return this._meta.resolveReferenced(this, attr, {
field_mapping,
resolve,
});
}
// Validation
_errors;
validate({ method, navigation = false, } = {}) {
return this._meta.validate(this, { method, navigation });
}
isValid({ method, navigation = false, } = {}) {
this._errors = this.validate({ method, navigation });
if (this._errors !== undefined)
this.events$.trigger(ODataModelEventType.Invalid, {
value: this._errors,
options: { method },
});
return this._errors === undefined;
}
defaults() {
return this._meta.defaults() || {};
}
toEntity({ client_id = false, include_navigation = false, include_concurrency = false, include_computed = false, include_key = true, include_id = false, include_non_field = false, changes_only = false, field_mapping = false, chain = [], } = {}) {
return this._meta.toEntity(this, {
client_id,
include_navigation,
include_concurrency,
include_computed,
include_key,
include_id,
include_non_field,
changes_only,
field_mapping,
chain,
});
}
toJson() {
return this.toEntity(INCLUDE_DEEP);
}
set(path, value, { type } = {}) {
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
if (pathArray.length === 0)
return undefined;
if (pathArray.length > 1) {
const model = this[pathArray[0]];
return model.set(pathArray.slice(1), value, {});
}
if (pathArray.length === 1) {
return this._meta.set(this, pathArray[0], value, { type });
}
}
get(path) {
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
if (pathArray.length === 0)
return undefined;
const value = this._meta.get(this, pathArray[0]);
if (pathArray.length > 1 &&
(value instanceof ODataModel || value instanceof ODataCollection)) {
return value.get(pathArray.slice(1));
}
return value;
}
has(path) {
const pathArray = (Types.isArray(path) ? path : path.match(/([^[.\]])+/g));
if (pathArray.length === 0)
return false;
const value = this._meta.get(this, pathArray[0]);
if (pathArray.length > 1 &&
(value instanceof ODataModel || value instanceof ODataCollection)) {
return value.has(pathArray.slice(1));
}
return value !== undefined;
}
reset({ path, silent = false, } = {}) {
const pathArray = (path === undefined
? []
: Types.isArray(path)
? path
: path.match(/([^[.\]])+/g));
const name = pathArray[0];
const value = name !== undefined ? this[name] : undefined;
if (ODataModelOptions.isModel(value) ||
ODataModelOptions.isCollection(value)) {
value.reset({ path: pathArray.slice(1), silent });
}
else {
this._meta.reset(this, { name: pathArray[0], silent });
}
}
clear({ silent = false } = {}) {
this._attributes.clear();
if (!silent) {
this.events$.trigger(ODataModelEventType.Update);
}
}
assign(entity, { add = true, merge = true, remove = true, reset = false, reparent = false, silent = false, } = {}) {
return this._meta.assign(this, entity, {
add,
merge,
remove,
reset,
silent,
reparent,
});
}
clone() {
return new this.constructor(this.toEntity(INCLUDE_DEEP), {
resource: this.resource(),
annots: this.annots(),
});
}
_request(obs$, mapCallback) {
this.events$.trigger(ODataModelEventType.Request, {
options: { observable: obs$ },
});
return obs$.pipe(map((response) => mapCallback(response)), finalize(() => this.events$.trigger(ODataModelEventType.Sync)));
}
fetch({ ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('fetch: Resource is null'));
let obs$;
if (resource instanceof ODataEntityResource) {
obs$ = resource.fetch(options);
}
else if (resource instanceof ODataNavigationPropertyResource) {
obs$ = resource.fetch({ responseType: 'entity', ...options });
}
else {
obs$ = resource.fetch({
responseType: 'entity',
...options,
});
}
return this._request(obs$, ({ entity, annots }) => {
this._annotations = annots;
return this.assign(entity ?? {}, { reset: true });
});
}
save({ method, navigation = false, validate = true, ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('save: Resource is null'));
if (!(resource instanceof ODataEntityResource ||
resource instanceof ODataNavigationPropertyResource))
return throwError(() => new Error('save: Resource type ODataEntityResource/ODataNavigationPropertyResource needed'));
// Resolve method and resource key
if (method === undefined && this.schema().isCompoundKey())
return throwError(() => new Error('save: Composite key require a specific method, use create/update/modify'));
method = method || (!resource.hasKey() ? 'create' : 'update');
if (resource instanceof ODataEntityResource &&
(method === 'update' || method === 'modify') &&
!resource.hasKey())
return throwError(() => new Error('save: Update/Patch require entity key'));
if (resource instanceof ODataNavigationPropertyResource ||
method === 'create')
resource.clearKey();
if (validate && !this.isValid({ method, navigation })) {
return throwError(() => new Error('save: Validation errors'));
}
const _entity = this.toEntity({
changes_only: method === 'modify',
field_mapping: true,
include_concurrency: true,
include_navigation: navigation,
});
const obs$ = method === 'create'
? resource.create(_entity, options)
: method === 'modify'
? resource.modify(_entity, {
etag: this.annots().etag,
...options,
})
: resource.update(_entity, {
etag: this.annots().etag,
...options,
});
return this._request(obs$, ({ entity, annots }) => {
this._annotations = annots;
return this.assign(entity ?? _entity, {
reset: true,
});
});
}
destroy({ ...options } = {}) {
const resource = this.resource();
if (!resource)
return throwError(() => new Error('destroy: Resource is null'));
if (!(resource instanceof ODataEntityResource ||
resource instanceof ODataNavigationPropertyResource))
return throwError(() => new Error('destroy: Resource type ODataEntityResource/ODataNavigationPropertyResource needed'));
if (!resource.hasKey())
return throwError(() => new Error("destroy: Can't destroy model without key"));
const obs$ = resource.destroy({ etag: this.annots().etag, ...options });
return this._request(obs$, (resp) => {
this.events$.trigger(ODataModelEventType.Destroy);
return resp;
});
}
/**
* Create an execution context for change the internal query of a resource
* @param ctx Function to execute
*/
query(ctx) {
const resource = this.resource();
return resource ? this._meta.query(this, resource, ctx) : this;
}
/**
* Perform a check on the internal state of the model and return true if the model is changed.
* @param include_navigation Check in navigation properties
* @returns true if the model has changed, false otherwise
*/
hasChanged({ include_navigation = false, } = {}) {
return this._meta.hasChanged(this, { include_navigation });
}
encode(name, options) {
const value = this[name];
if (value === undefined)
return undefined;
const field = this._meta.findField(name);
return field ? field.encode(value, options) : value;
}
isNew() {
return !this._meta.hasKey(this);
}
withResource(resource, ctx) {
return this._meta.withResource(this, resource, ctx);
}
/**
* Create an execution context for a given function, where the model is bound to its entity endpoint
* @param ctx Context function
* @returns The result of the context
*/
asEntity(ctx) {
return this._meta.asEntity(this, ctx);
}
//#region Callables
callFunction(name, params, responseType, options = {}) {
const resource = this.resource();
if (!(resource instanceof ODataEntityResource) || !resource.hasKey())
return throwError(() => new Error("callFunction: Can't call function without ODataEntityResource with key"));
const func = resource.function(name).query((q) => q.restore(options));
switch (responseType) {
case 'property':
return this._request(func.callProperty(params, options), (resp) => resp);
case 'model':
return this._request(func.callModel(params, options), (resp) => resp);
case 'collection':
return this._request(func.callCollection(params, options), (resp) => resp);
case 'blob':
return this._request(func.callBlob(params, options), (resp) => resp);
case 'arraybuffer':
return this._request(func.callArraybuffer(params, options), (resp) => resp);
default:
return this._request(func.call(params, { responseType, ...options }), (resp) => resp);
}
}
callAction(name, params, responseType, { ...options } = {}) {
const resource = this.resource();
if (!(resource instanceof ODataEntityResource) || !resource.hasKey())
return throwError(() => new Error("callAction: Can't call action without ODataEntityResource with key"));
const action = resource.action(name).query((q) => q.restore(options));
switch (responseType) {
case 'property':
return this._request(action.callProperty(params, options), (resp) => resp);
case 'model':
return this._request(action.callModel(params, options), (resp) => resp);
case 'collection':
return this._request(action.callCollection(params, options), (resp) => resp);
case 'blob':
return this._request(action.callBlob(params, options), (resp) => resp);
case 'arraybuffer':
return this._request(action.callArraybuffer(params, options), (resp) => resp);
default:
return this._request(action.call(params, { responseType, ...options }), (resp) => resp);
}
}
cast(type, ModelType) {
//: ODataModel<S> {
const resource = this.resource();
if (!(resource instanceof ODataEntityResource))
throw new Error(`cast: Can't cast to derived model without ODataEntityResource`);
return resource
.cast(type)
.asModel(this.toEntity(INCLUDE_DEEP), {
annots: this.annots(),
ModelType,
});
}
fetchNavigationProperty(name, responseType, options = {}) {
const nav = this.navigationProperty(name);
nav.query((q) => q.restore(options));
switch (responseType) {
case 'model':
return nav.fetchModel(options);
case 'collection':
return nav.fetchCollection(options);
}
}
fetchAttribute(name, options = {}) {
const field = this._meta.findField(name);
if (!field)
throw Error(`fetchAttribute: Can't find attribute ${name}`);
if (field.isStructuredType() && field.collection) {
const collection = field.collectionFactory({ parent: this });
collection.query((q) => q.restore(options));
return this._request(collection.fetch(options), () => {
this.assign({ [name]: collection });
return collection;
});
}
else if (field.isStructuredType()) {
const model = field.modelFactory({ parent: this });
model.query((q) => q.restore(options));
return this._request(model.fetch(options), () => {
this.assign({ [name]: model });
return model;
});
}
else {
const prop = field.resourceFactory(this.resource());
prop.query((q) => q.restore(options));
return this._request(prop.fetchProperty(options), (resp) => {
this.assign({ [name]: resp });
return resp;
});
}
}
/*
getAttribute<P>(
name: keyof T,
): (P extends (infer U)[] ? ODataCollection<U, ODataModel<U> & ModelInterface<U>> :
P extends ArrayBufferLike ? ArrayBuffer :
P extends Date ? Date :
P extends object ? ODataModel<P> & ModelInterface<P> : P ) | null;
getAttribute<M extends ODataModel<keyof T>>(
name: keyof T,
): M | null;
getAttribute<C extends ODataCollection<keyof T, ODataModel<keyof T>>>(
name: keyof T,
): C | null;
getAttribute<P>(
name: keyof T,
) {
*/
getAttribute(name) {
const field = this._meta.findField(name);
if (!field)
throw Error(`getAttribute: Can't find attribute ${name}`);
let model = this[name];
if (field.isStructuredType() && model === undefined) {
if (field.collection) {
model = field.collectionFactory({ parent: this });
}
else {
const ref = field.navigation
? this.referenced(field)
: undefined;
model =
ref === null
? null
: field.modelFactory({ parent: this, value: ref });
}
this[name] = model;
}
return model;
}
setAttribute(name, model, options) {
const reference = this.navigationProperty(name).reference();
const etag = this.annots().etag;
let obs$ = NEVER;
if (model instanceof ODataModel) {
obs$ = reference.set(model.asEntity((e) => e.resource()), { etag, ...options });
}
else if (model instanceof ODataCollection) {
obs$ = forkJoin(model
.models()
.map((m) => reference.add(m.asEntity((e) => e.resource()), options)));
}
else if (model === null) {
obs$ = reference.unset({ etag, ...options });
}
return this._request(obs$, (model) => this.assign({ [name]: model }));
}
setReference(name, model, options) {
const reference = this.navigationProperty(name).reference();
const etag = this.annots().etag;
let obs$ = NEVER;
if (model instanceof ODataModel) {
obs$ = reference.set(model.asEntity((e) => e.resource()), { etag, ...options });
}
else if (model instanceof ODataCollection) {
obs$ = forkJoin(model
.models()
.map((m) => reference.add(m.asEntity((e) => e.resource()), options)));
}
else if (model === null) {
obs$ = reference.unset({ etag, ...options });
}
return this._request(obs$, (model) => this.assign({ [name]: model }));
}
//#region Model Identity
get [Symbol.toStringTag]() {
return 'Model';
}
equals(other) {
if (this === other)
return true;
if (typeof this !== typeof other)
return false;
const meta = this._meta;
const thisCid = this[meta.cid];
const otherCid = other[meta.cid];
if (thisCid !== undefined &&
otherCid !== undefined &&
Types.isEqual(thisCid, otherCid))
return true;
if (meta.isEntityType()) {
const thisKey = this.key();
const otherKey = other.key();
if (thisKey !== undefined &&
otherKey !== undefined &&
Types.isEqual(thisKey, otherKey))
return true;
}
else if (meta.isComplexType()) {
const thisJson = this.toJson();
const otherJson = other.toJson();
if (Types.isEqual(thisJson, otherJson))
return true;
}
return false;
}
//#endregion
//#region Collection Tools
collection() {
return this._parent !== null &&
ODataModelOptions.isCollection(this._parent[0])
? this._parent[0]
: undefined;
}
next() {
return this.collection()?.next(this);
}
prev() {
return this.collection()?.prev(this);
}
}
const RESERVED_FIELD_NAMES = Object.getOwnPropertyNames(ODataModel.prototype);
var ODataModelEventType;
(function (ODataModelEventType) {
ODataModelEventType["Change"] = "change";
ODataModelEventType["Reset"] = "reset";
ODataModelEventType["Update"] = "update";
ODataModelEventType["Sort"] = "sort";
ODataModelEventType["Destroy"] = "destroy";
ODataModelEventType["Add"] = "add";
ODataModelEventType["Remove"] = "remove";
ODataModelEventType["Invalid"] = "invalid";
ODataModelEventType["Request"] = "request";
ODataModelEventType["Sync"] = "sync";
ODataModelEventType["Attach"] = "attach";
})(ODataModelEventType || (ODataModelEventType = {}));
class ODataModelEvent {
type;
value;
previous;
options;
constructor(type, { model, collection, previous, value, attr, options, bubbles, chain, } = {}) {
this.type = type;
this.model = model;
this.collection = collection;
this.previous = previous;
this.value = value;
this.options = options;
this.chain = chain ?? [
[
(this.model || this.collection),
attr || null,
],
];
this.bubbles = bubbles ?? BUBBLES.indexOf(this.type) !== -1;
}
chain;
push(model, attr) {
return new ODataModelEvent(this.type, {
model: this.model ?? (model instanceof ODataModel ? model : undefined),
collection: this.collection ??
(model instanceof ODataCollection
? model
: undefined),
previous: this.previous,
value: this.value,
options: {
...this.options,
index: attr instanceof ODataModelAttribute ? attr.name : attr,
},
bubbles: this.bubbles,
chain: [[model, attr], ...this.chain],
});
}
bubbles;
stopPropagation() {
this.bubbles = false;
}
visited(model) {
return (this.chain.some((c) => c[0] === model) &&
this.chain[this.chain.length - 1][0] !== model);
}
canContinueWith(self) {
return this.bubbles && !this.visited(self);
}
get path() {
return this.chain
.map(([, attr], index) => typeof attr === 'number'
? `[${attr}]`
: attr instanceof ODataModelAttribute
? index === 0
? attr.name
: `.${attr.name}`
: '')
.join('');
}
//Reference to the model which the event was dispatched
model;
//Identifies the current model for the event
get currentModel() {
const link = this.chain.find((c) => ODataModelOptions.isModel(c[0]));
return link !== undefined ? link[0] : undefined;
}
//Reference to the collection which the event was dispatched
collection;
//Identifies the current collection for the event
get currentCollection() {
const link = this.chain.find((c) => ODataModelOptions.isCollection(c[0]));
return link !== undefined
? link[0]
: undefined;
}
}
class ODataModelEventEmitter extends EventEmitter {
model;
collection;
constructor({ model, collection, } = {}) {
super();
this.model = model;
this.collection = collection;
}
trigger(type, { collection, previous, value, attr, options, bubbles, } = {}) {
const _trigger = (name) => this.emit(new ODataModelEvent(name, {
model: this.model,
collection: collection ?? this.collection,
previous,
value,
attr,
options,
bubbles,
}));
if (type && EVENT_SPLITTER.test(type)) {
for (const name of type.split(EVENT_SPLITTER)) {
_trigger(name);
}
}
else {
_trigger(type);
}
}
}
const BUBBLES = [
ODataModelEventType.Change,
ODataModelEventType.Reset,
ODataModelEventType.Update,
ODataModelEventType.Destroy,
ODataModelEventType.Add,
ODataModelEventType.Remove,
];
const INCLUDE_SHALLOW = {
include_concurrency: true,
include_computed: true,
include_key: true,
include_id: false,
};
const INCLUDE_DEEP = {
include_navigation: true,
include_non_field: true,
...INCLUDE_SHALLOW,
};
var ODataModelState;
(function (ODataModelState) {
ODataModelState[ODataModelState["Added"] = 0] = "Added";
ODataModelState[ODataModelState["Removed"] = 1] = "Removed";
ODataModelState[ODataModelState["Changed"] = 2] = "Changed";
ODataModelState[ODataModelState["Unchanged"] = 3] = "Unchanged";
})(ODataModelState || (ODataModelState = {}));
function Model({ cid = CID_FIELD_NAME } = {}) {
return (constructor) => {
const Klass = constructor;
if (!Klass.hasOwnProperty('options'))
Klass.options = {
fields: new Map(),
};
Klass.options.cid = cid;
return constructor;
};
}
function ModelField({ name, ...options } = {}) {
return (target, key) => {
const Klass = target.constructor;
if (!Klass.hasOwnProperty('options'))
Klass.options = {
fields: new Map(),
};
options.field = name ?? key;
Klass.options.fields.set(key, options);
};
}
class ODataModelField {
name;
field;
parser;
options;
optionsForType;
modelForType;
collectionForType;
enumForType;
structuredForType;
default;
required;
concurrency;
maxLength;
minLength;
min;
max;
pattern;
parserOptions;
constructor(options, { name, field, parser, ...opts }) {
this.options = options;
this.name = name;
this.field = field;
this.parser = parser;
this.default = opts.default || parser.default;
this.required = Boolean(opts.required || !parser.nullable);
this.concurrency = Boolean(opts.concurrency);
this.maxLength = opts.maxLength || parser.maxLength;
this.minLength = opts.minLength;
this.min = opts.min;
this.max = opts.max;
this.pattern = opts.pattern;
}
get type() {
return this.parser.type;
}
get navigation() {
return this.parser.navigation;
}
get collection() {
return this.parser.collection;
}
annotatedValue(term) {
return this.parser.annotatedValue(term);
}
configure({ optionsForType, modelForType, collectionForType, enumForType, structuredForType, concurrency, options, }) {
this.optionsForType = optionsForType;
this.modelForType = modelForType;
this.collectionForType = collectionForType;
this.enumForType = enumForType;
this.structuredForType = structuredForType;
this.parserOptions = options;
if (concurrency)
this.concurrency = concurrency;
if (this.default !== undefined)
this.default = this.deserialize(this.default, options);
}
isKey() {
return this.parser.isKey();
}
hasReferentials() {
return this.parser.hasReferentials();
}
get referentials() {
return this.parser.referentials;
}
isStructuredType() {
return this.parser.isStructuredType();
}
structuredType() {
const structuredType = this.structuredForType
? this.structuredForType(this.type)
: undefined;
//Throw error if not found
if (!structuredType)
throw new Error(`Could not find structured type for ${this.parser.type}`);
return structuredType;
}
isEnumType() {
return this.parser.isEnumType();
}
enumType() {
const enumType = this.enumForType ? this.enumForType(this.type) : undefined;
//Throw error if not found
if (!enumType)
throw new Error(`Could not find enum type for ${this.parser.type}`);
return enumType;
}
validate(value, { method, navigation = false, } = {}) {
if (ODataModelOptions.isModel(value)) {
return !value.isValid({ method, navigation }) ? value._errors : undefined;
}
else if (ODataModelOptions.isCollection(value)) {
return value
.models()
.some((m) => !m.isValid({ method, navigation }))
? value.models().map((m) => m._errors)
: undefined;
}
else {
const computed = this.annotatedValue(COMPUTED);
const errors = this.parser?.validate(value, { method, navigation }) || [];
if (this.required &&
(value === null || (value === undefined && method !== 'modify')) && // Is null or undefined without patch?
!(computed && method === 'create') // Not (Is Computed field and create) ?
) {
errors['push'](`required`);
}
if (this.maxLength !== undefined &&
typeof value === 'string' &&
value.length > this.maxLength) {
errors['push'](`maxlength`);
}
if (this.minLength !== undefined &&
typeof value === 'string' &&
value.length < this.minLength) {
errors['push'](`minlength`);
}
if (this.min !== undefined &&
typeof value === 'number' &&
value < this.min) {
errors['push'](`min`);
}
if (this.max !== undefined &&
typeof value === 'number' &&
value > this.max) {
errors['push'](`max`);
}
if (this.pattern !== undefined &&
typeof value === 'string' &&
!this.pattern.test(value)) {
errors['push'](`pattern`);
}
return !Types.isEmpty(errors) ? errors : undefined;
}
}
defaults() {
const meta = this.optionsForType
? this.optionsForType(this.type)
: undefined;
return this.isStructuredType() && meta !== undefined
? meta.defaults()
: this.default;
}
deserialize(value, options) {
const parserOptions = options ?? this.parserOptions;
return this.parser.deserialize(value, parserOptions);
}
serialize(value, options) {
const parserOptions = options ?? this.parserOptions;
return this.parser.serialize(value, parserOptions);
}
encode(value, options) {
const parserOptions = options ?? this.parserOptions;
return this.parser.encode(value, parserOptions);
}
resourceFactory(base) {
if (!(base instanceof ODataEntityResource ||
base instanceof ODataNavigationPropertyResource ||
base instanceof ODataPropertyResource))
throw new Error("Can't build resource for non compatible base type");
return this.navigation
? base.navigationProperty(this.parser.name)
: base.property(this.parser.name);
}
annotationsFactory(base) {
return this.parser.collection
? base.property(this.parser.name, 'collection')
: base.property(this.parser.name, 'single');
}
modelFactory({ parent, value, reset, }) {
// Model
const annots = this.annotationsFactory(parent.annots());
let Model = this.modelForType ? this.modelForType(this.type) : undefined;
if (Model === undefined)
throw Error(`No Model type for ${this.name}`);
if (value !== undefined) {
annots.update(value);
}
if (annots?.type !== undefined && Model.meta !== null) {
const meta = Model.meta.findChildOptions((o) => o.isTypeOf(annots.type))?.structuredType;
if (meta !== undefined && meta.model !== undefined)
// Change to child model
Model = meta.model;
}
return new Model((value || {}), {
annots,
reset,
parent: [parent, this],
});
}
collectionFactory({ parent, value, reset, }) {
// Collection Factory
const annots = this.annotationsFactory(parent.annots());
const Collection = this.collectionForType
? this.collectionForType(this.type)
: undefined;
if (Collection === undefined)
throw Error(`No Collection type for ${this.name}`);
return new Collection((value || []), {
annots: annots,
reset,
parent: [parent, this],
});
}
}
class ODataModelAttribute {
_model;
_field;
state = ODataModelState.Unchanged;
value;
change;
subscription;
events$ = new ODataModelEventEmitter();
constructor(_model, _field) {
this._model = _model;
this._field = _field;
}
get type() {
return this._field.type;
}
get navigation() {
return this._field.navigation;
}
get computed() {
return this._field.annotatedValue(COMPUTED);
}
get concurrency() {
return Boolean(this._field.concurrency);
}
get referentials() {
return this._field.referentials;
}
get options() {
return this._field.options;
}
get name() {
return this._field.name;
}
get fieldName() {
return this._field.field;
}
get() {
return this.state === ODataModelState.Changed ? this.change : this.value;
}
set(value, reset = false, reparent = false) {
const current = this.get();
if (ODataModelOptions.isModel(current) ||
ODataModelOptions.isCollection(current))
this.unlink(current);
const changed = ODataModelOptions.isModel(current) && ODataModelOptions.isModel(value)
? !current.equals(value)
: ODataModelOptions.isCollection(current) &&
ODataModelOptions.isCollection(value)
? !current.equals(value)
: !Types.isEqual(current, value);
if (reset) {
this.value = value;
this.state = ODataModelState.Unchanged;
}
else if (Types.isEqual(value, this.value)) {
this.state = ODataModelState.Unchanged;
}
else if (changed) {
this.change = value;
this.state = ODataModelState.Changed;
}
if (ODataModelOptions.isModel(value) ||
ODataModelOptions.isCollection(value)) {
this.link(value, reparent);
}
return changed;
}
isChanged({ include_navigation = false, } = {}) {
const current = this.get();
return (this.state === ODataModelState.Changed ||
((ODataModelOptions.isModel(current) ||
ODataModelOptions.isCollection(current)) &&
current.hasChanged({ include_navigation })));
}
reset() {
if (ODataModelOptions.isModel(this.change) ||
ODataModelOptions.isCollection(this.change))
this.unlink(this.change);
this.state = ODataModelState.Unchanged;
if (ODataModelOptions.isModel(this.value) ||
ODataModelOptions.isCollection(this.value))
this.link(this.value);
}
link(value, reparent = false) {
this.subscription = value.events$.subscribe((e) => this.events$.emit(e));
if (reparent) {
value._parent = [this._model, this._field];
}
}
unlink(value, reparent = false) {
this.subscription?.unsubscribe();
this.subscription = undefined;
if (reparent) {
value._parent = null;
}
}
}
class ODataModelOptions {
name;
cid;
base;
_fields = [];
structuredType;
entitySet;
// Hierarchy
parent;
children = [];
events$ = new ODataModelEventEmitter();
constructor({ config, structuredType, }) {
this.name = structuredType.name;
this.base = structuredType.base;
this.structuredType = structuredType;
this.cid = config?.cid ?? CID_FIELD_NAME;
config.fields.forEach((value, key) => this.addField(key, value));
}
get api() {
return this.structuredType.api;
}
type({ alias = false } = {}) {
return this.structuredType.type({ alias });
}
isOpenType() {
return this.structuredType.isOpenType();
}
isEntityType() {
return this.structuredType.isEntityType();
}
isComplexType() {
return this.structuredType.isComplexType();
}
isTypeOf(type) {
return this.structuredType.type() === type;
}
isModelFor(entity) {
// Resolve By Type
const type = this.api.options.helper.type(entity);
if (type && this.isTypeOf(type))
return true;
// Resolve By fields
const keys = Object.keys(entity);
const names = this.fields({
include_navigation: true,
include_parents: true,
}).map((f) => f.name);
return keys.every((key) => names.includes(key));
}
findChildOptions(predicate) {
if (predicate(this))
return this;
let match;
for (const ch of this.children) {
match = ch.findChildOptions(predicate);
if (match !== undefined)
break;
}
return match;
}
configure({ options }) {
if (this.base) {
const parent = this.api.optionsForType(this.base);
parent.children.push(this);
this.parent = parent;
}
this.entitySet = this.api.findEntitySetForEntityType(this.type());
let concurrencyFields = [];
if (this.entitySet !== undefined) {
concurrencyFields =
this.entitySet.annotatedValue(OPTIMISTIC_CONCURRENCY) || [];
}
this._fields.forEach((field) => {
const concurrency = concurrencyFields.indexOf(field.field) !== -1;
field.configure({
optionsForType: (t) => this.api.optionsForType(t),
modelForType: (t) => this.api.modelForType(t),
collectionForType: (t) => this.api.collectionForType(t),
enumForType: (t) => this.api.findEnumType(t),
structuredForType: (t) => this.api.findStructuredType(t),
concurrency,
options,
});
});
}
fields({ include_navigation, include_parents, }) {
return [
...(include_parents && this.parent !== undefined
? this.parent.fields({ include_navigation, include_parents })
: []),
...this._fields.filter((field) => include_navigation || !field.navigation),
];
}
field(name) {
const field = this.findField(name);
//Throw error if not found
if (!field)
throw new Error(`No field with name ${name}`);
return field;
}
findField(name, { reset } = {}) {
return this.fields({
include_parents: true,
include_navigation: true,
}).find((modelField) => (reset && modelField.field === name) || modelField.name === name);
}
addField(name, options) {
const { field, parser, ...opts } = options;
if (field === undefined || name === undefined)
throw new Error('Model Properties need name and field');
const fieldParser = parser ?? this.structuredType.field(field);
if (fieldParser === undefined)
throw new Error(`No parser for ${field} with name = ${name}`);
const modelField = new ODataModelField(this, {
name,
field,
parser: fieldParser,
...opts,
});
this._fields.push(modelField);
return modelField;
}
tsToEdm = {
string: EdmType.String,
number: EdmType.Int32,
bigint: EdmType.Int64,
boolean: EdmType.Boolean,
};
modelFieldFactory(self, name, type) {
const structuredFieldParser = this.structuredType.addField(name, {
type,
});
structuredFieldParser.configure({
parserForType: (type) => this.api.parserForType(type),
options: this.api.options,
});
const modelField = this.addField(name, {
field: name,
parser: structuredFieldParser,
});
modelField.configure({
optionsForType: (t) => this.api.optionsForType(t),
modelForType: (t) => this.api.modelForType(t),
collectionForType: (t) => this.api.collectionForType(t),
enumForType: (t) => this.api.findEnumType(t),
structuredForType: (t) => this.api.findStructuredType(t),
options: this.api.options,
concurrency: false,
});
Object.defineProperty(self, modelField.name, {
configurable: true,
get: () => this.get(self, modelField),
set: (value) => this.set(self, modelField, value),
});
return modelField;
}
attach(self, resource) {
if (self._resource !== null &&
resource.outgoingType() !== self._resource.outgoingType() &&
!self._resource.isSubtypeOf(resource))
throw new Error(`Can't attach ${resource.outgoingType()} to ${self._resource.outgoingType()}`);
const current = self._resource;
if (current === null || !current.isEqualTo(resource)) {
self._resource = resource;
self.events$.trigger(ODataModelEventType.Attach, {
previous: current,
value: resource,
});
}
}
//# region Resource
static chain(child) {
const chain = [];
let tuple = [child, null];
while (tuple !== null) {
const parent = tuple;
if (chain.some((p) => p[0] === parent[0]))
break;
chain.splice(0, 0, parent);
tuple = tuple[0]._parent;
}
return chain;
}
static resource(child) {
let resource = null;
let prevField = null;
for (const [model, field] of ODataModelOptions.chain(child)) {
resource = resource || model._resource;
if (resource === null)
break;
if (ODataModelOptions.isModel(model) &&
(prevField === null || prevField.collection)) {
const m = model;
// Resolve subtype if collection not is from field
// FIXME
/*
if (field === null) {
const r = m._meta.modelResourceFactory(resource.cloneQuery<T>());
if (r !== null && !r.isTypeOf(resource) && r.isSubtypeOf(resource)) {
resource = r;
}
}
*/
// Resolve key
const mKey = m.key({ field_mapping: true });
if (mKey !== undefined) {
resource =
resource instanceof ODataEntitySetResource
? resource.entity(mKey)
: resource.key(mKey);
}
}
prevField = field;
if (field === null && model._resource !== null) {
// Apply the query from model to new resource
model._resource.query((qs) => resource?.query((qd) => qd.restore(qs.store())));
}
else if (field !== null) {
resource = field.resourceFactory(resource);
}
}
return resource;
}
collectionResourceFactory(query) {
if (this.entitySet === undefined)
return null;
return ODataEntitySetResource.factory(this.api, {
path: this.entitySet.name,
type: this.entitySet.entityType,
query,
});
}
modelResourceFactory(query) {
const resource = this.collectionResourceFactory(query);
if (resource instanceof ODataEntitySetResource)
return resource.entity();
return resource;
}
//#endregion
bind(self, { parent, resource, annots, } = {}) {
// Events
self.events$.subscribe((e) => this.events$.emit(e));
// Parent
if (parent !== undefined) {
self._parent = parent;
}
// Resource
if (self._parent === null && resource === undefined)
resource = this.modelResourceFactory();
if (resource) {
this.attach(self, resource);
}
// Annotations
self._annotations =
annots ?? new ODataEntityAnnotations(ODataHelper[DEFAULT_VERSION]);
// Fields
this.fields({
include_navigation: true,
include_parents: true,
}).forEach((field) => {
Object.defineProperty(self, field.name, {
configurable: true,
get: () => this.get(self, field),
set: (value) => this.set(self, field, value),
});
});
}
query(self, resource, func) {
resource.query(func);
this.attach(self, resource);
return self;
}
resolveKey(value, { field_mapping = false, resolve = true, single = true, } = {}) {
const keyTypes = this.structuredType.keys({ include_parents: true });
const key = new Map();
for (const kt of keyTypes) {
let v = value;
let options = this;
let field;
for (const name of kt.name.split('/')) {
if (options === undefined)
break;
field = options
.fields({ include_navigation: false, include_parents: true })
.find((field) => field.field === name);
if (field !== undefined) {
v =
Types.isPlainObject(v) || ODataModelOptions.isModel(v)
? v[field.name]
: v;
options = this.api.optionsForType(field.type);
}
}
if (field === undefined)
return undefined;
let name = field_mapping ? field.field : field.name;
if (kt.alias !== undefined)
name = kt.alias;
key.set(name, v);
}
if (key.size === 0)
return undefined;
return resolve
? Objects.resolveKey(key, { single })
: Object.fromEntries(key);
}
resolveReferential(value, attr, { field_mapping = false, resolve = true, single = false, } = {}) {
const referential = new Map();
for (const ref of attr.referentials) {
const from = this.fields({
include_navigation: false,
include_parents: true,
}).find((p) => p.field === ref.referencedProperty);
const to = attr.options
.fields({ include_navigation: false, include_parents: true })
.find((field) => field.field === ref.property);
if (from !== undefined && to !== undefined) {
const name = field_mapping ? to.field : to.name;
referential.set(name, value && value[from.name]);
}
}
if (referential.size === 0)
return undefined;
if (referential.size === 1 && Array.from(referential.values())[0] === null)
return null;
return resolve
? Objects.resolveKey(referential, { single })
: Object.fromEntries(referential);
}
resolveReferenced(value, attr, { field_mapping = false, resolve = true, single = false, } = {}) {
const referenced = new Map();
for (const ref of attr.referentials) {
const from = this.fields({
include_navigation: false,
include_parents: true,
}).find((field) => field.field === ref.property);
const meta = this.api.optionsForType(attr.type);
const to = meta
?.fields({ include_navigation: false, include_parents: true })
.find((field) => field.field === ref.referencedProperty);
if (from !== undefined && to !== undefined) {
const name = field_mapping ? to.field : to.name;
referenced.set(name, value && value[from.name]);
}
}
if (referenced.size === 0)
return undefined;
if (referenced.size === 1 && Array.from(referenced.values())[0] === null)
return null;
return resolve
? Objects.resolveKey(referenced, { single })
: Object.fromEntries(referenced);
}
validate(self, { method, navigation = false, } = {}) {
const errors = this.fields({
include_parents: true,
include_navigation: navigation,
}).reduce((acc, field) => {
const value = self[field.name];
const errs = field.validate(value, { method });
return errs !== undefined
? Object.assign(acc, { [field.name]: errs })
: acc;
}, {});
return !Types.isEmpty(errors) ? errors : undefined;
}
defaults() {
const defs = this.fields({
include_navigation: false,
include_parents: true,
}).reduce((acc, field) => {
const value = field.defaults();
return value !== undefined
? Object.assign(acc, { [field.name]: value })
: acc;
}, {});
return !Types.isEmpty(defs) ? defs : undefined;
}
hasChanged(self, { include_navigation = false } = {}) {
return [...self._attributes.values()]
.filter((attr) => !attr.navigation || include_navigation)
.some((attr) => attr.isChanged({ include_navigation }));
}
hasKey(self) {
return this.resolveKey(self) !== undefined;
}
withResource(self, resource, ctx) {
// Push
self.pushResource(resource);
// Execute function
const result = ctx(self);
if (result instanceof Observable) {
return result.pipe(finalize(() => {
// Pop
self.popResource();
}));
}
else {
// Pop
self.popResource();
return result;
}
}
asEntity(self, ctx) {
// Clone query from him or parent
let query = self._resource?.cloneQuery();
if (query === undefined &&
self._parent &&
self._parent[0] instanceof ODataCollection)
query = self._parent[0]._resource?.cloneQuery();
// Build new resource
const resource = this.modelResourceFactory(query);
return this.withResource(self, resource, ctx);
}
toEntity(self, { client_id = false, include_navigation = false, include_concurrency = false, include_computed = false, include_key = true, include_id = false, include_non_field = false, changes_only = false, field_mapping = false, chain = [], } = {}) {
let entity = [...self._attributes.values()]
.filter(
// Chain
(attr) => chain.every((c) => c !== attr.get()))
.filter(
// Changes only
(attr) => !changes_only ||
(changes_only && attr.isChanged({ include_navigation })))
.filter((attr) =>
// Navigation
(include_navigation && attr.navigation && attr.get() !== null) ||
!attr.navigation)
.reduce((acc, attr) => {
const name = field_mapping ? attr.fieldName : attr.name;
let value = attr.get();
const computed = attr.computed;
const navigation = attr.navigation;
const concurrency = attr.concurrency;
if (ODataModelOptions.isModel(value)) {
value = value.toEntity({
client_id,
include_navigation,
include_concurrency,
include_computed,
include_non_field,
field_mapping,
changes_only: changes_only && !!navigation,
include_key: include_key && !!navigation,
include_id: include_id && !!navigation,
chain: [self, ...chain],
});
}
else if (ODataModelOptions.isCollection(value)) {
value = value.toEntities({
client_id,
include_navigation,
include_concurrency,
include_computed,
include_non_field,
field_mapping,
changes_only: changes_only && !!navigation,
include_key: include_key && !!navigation,
include_id: include_id && !!navigation,
chain: [self, ...chain],
});
}
if (include_concurrency && concurrency) {
return Object.assign(acc, { [name]: value });
}
else if (include_computed && computed) {
return Object.assign(acc, { [name]: value });
}
else if (changes_only && attr.isChanged()) {
return Object.assign(acc, { [name]: value });
}
else if (!changes_only && !concurrency && !computed) {
return Object.assign(acc, { [name]: value });
}
return acc;
}, {});
if (include_non_field) {
const names = Object.keys(entity);
// Attributes from object (attributes for object)
const nonFieldAttrs = Object.entries(self)
.filter(([k]) => names.indexOf(k) === -1 && !k.startsWith('_') && !k.endsWith('$'))
.reduce((acc, [k, v]) => Object.assign(acc, { [k]: v }), {});
entity = { ...entity, ...nonFieldAttrs };
}
// Add client_id
if (client_id) {
entity[this.cid] = self[this.cid];
}
// Add key
if (include_key) {
entity = {
...entity,
...this.resolveKey(self, { field_mapping, resolve: false }),
};
}
// Add id
if (include_id) {
self.asEntity((e) => {
const resource = e.resource();
if (resource)
entity[this.api.options.helper.ODATA_ID] = `${resource.clearQuery()}`;
});
}
// Add type
if (self._parent !== null &&
((ODataModelOptions.isModel(self._parent[0]) &&
self._parent[1] !== null &&
this.api.optionsForType(self._parent[1].type) !== self._meta) ||
(ODataModelOptions.isCollection(self._parent[0]) &&
self._parent[0]._model
.meta !== self._meta))) {
entity[this.api.options.helper.ODATA_TYPE] =
`#${this.structuredType.type()}`;
}
return entity;
}
reset(self, { name, silent = false } = {}) {
let changes = [];
if (name !== undefined) {
// Reset value
const attribute = self._attributes.get(name);
if (attribute !== undefined &&
attribute.isChanged({ include_navigation: true })) {
attribute.reset();
changes = [name];
}
else if (attribute?.isChanged()) {
attribute.reset();
changes = [name];
}
}
else {
// reset all
changes = [...self._attributes.keys()];
//self._changes.clear();
self._attributes.forEach((attr, key) => {
if (attr.isChanged({ include_navigation: true })) {
attr.reset();
changes.push(key);
}
});
}
if (!silent && changes.length > 0) {
self.events$.trigger(ODataModelEventType.Reset, { options: { changes } });
}
}
assign(self, entity, { add = true, merge = true, remove = true, reset = false, reparent = false, silent = false, } = {}) {
const changes = [];
// Update annotations
self.annots().update(entity);
// Update attributes
const attrs = self.annots().attributes(entity, 'full');
Object.entries(attrs)
.filter(([, value]) => value !== undefined) // Filter undefined
.forEach(([key, value]) => {
const field = this.findField(key, { reset });
if (field !== undefined || this.isOpenType()) {
// Delegated to private setter
if (this.set(self, field ?? key, value, {
add,
merge,
remove,
reset,
reparent,
silent,
})) {
changes.push(field?.name ?? key);
}
}
else {
// Basic assignment
const current = self[key];
self[key] = value;
if (current !== value)
changes.push(key);
}
});
if (!silent && changes.length > 0) {
self.events$.trigger(reset ? ODataModelEventType.Reset : ODataModelEventType.Update, { options: { changes } });
}
return self;
}
static isModel(obj) {
return Types.rawType(obj) === 'Model';
}
static isCollection(obj) {
return Types.rawType(obj) === 'Collection';
}
get(self, field) {
const attr = self._attributes.get(field instanceof ODataModelField ? field.name : field);
if (attr !== undefined) {
const value = attr.get();
if ((attr.navigation && value === null) ||
ODataModelOptions.isModel(value)) {
// Check for reference
const referenced = this.resolveReferenced(self, attr, {
resolve: false,
});
if (value !== null && referenced !== null && referenced !== undefined) {
value.assign(referenced, {
silent: true,
});
}
else if (value !== null && referenced === null) {
// New value is null
attr.set(null);
}
else if (value === null && referenced !== null) {
// New value is undefined
attr.set(undefined);
}
}
return value;
}
else if (typeof field === 'string' &&
!field.startsWith('_') &&
!field.endsWith('$')) {
return self[field];
}
return undefined;
}
set(self, field, value, { add, merge, remove, reset, reparent, silent, type, } = {}) {
let modelField = field instanceof ODataModelField ? field : this.findField(field);
if (modelField === undefined &&
this.isOpenType() &&
typeof field === 'string') {
type = type ?? this.tsToEdm[typeof value] ?? EdmType.String;
modelField = this.modelFieldFactory(self, field, type);
}
if (modelField === undefined)
throw new Error(`No field with name ${field}`);
let changed = false;
let attr = self._attributes.get(modelField.name);
// Ensures that the attribute exists
if (attr === undefined) {
attr = new ODataModelAttribute(self, modelField);
this._link(self, attr);
self._attributes.set(modelField.name, attr);
}
const current = attr.get();
if (modelField.isStructuredType()) {
if (value === null) {
// New value is null
changed = attr.set(value, reset, reparent);
}
else if (ODataModelOptions.isCollection(current)) {
// Current is collection
const currentCollection = current;
if (ODataModelOptions.isCollection(value)) {
// New value is collection
changed = attr.set(value, reset, reparent);
}
else if (Types.isArray(value)) {
// New value is array
currentCollection._annotations = modelField.annotationsFactory(self.annots());
currentCollection.assign(value, {
add,
merge,
remove,
reset,
reparent,
silent,
});
changed = currentCollection.hasChanged();
}
}
else if (ODataModelOptions.isModel(current)) {
// Current is model
const currentModel = current;
if (ODataModelOptions.isModel(value)) {
// New value is model
changed = attr.set(value, reset, reparent);
}
else if (Types.isPlainObject(value)) {
currentModel._annotations = modelField.annotationsFactory(self.annots());
currentModel.assign(value, {
add,
merge,
remove,
reset,
reparent,
silent,
});
changed = currentModel.hasChanged();
}
}
else {
// Current is null or undefined
// create new model/collection for given value
changed = attr.set(ODataModelOptions.isCollection(value) ||
ODataModelOptions.isModel(value)
? value
: modelField.collection
? modelField.collectionFactory({
parent: self,
value: value,
reset: reset,
})
: modelField.modelFactory({
parent: self,
value: value,
reset: reset,
}), reset, reparent);
}
// Resolve referentials
if (!ODataModelOptions.isCollection(attr.get())) {
const meta = this.api.optionsForType(modelField.type);
const ref = meta?.resolveReferential(attr.get(), attr, {
resolve: false,
});
if (ref !== null && ref !== undefined) {
Object.assign(self, ref);
}
}
}
else {
changed = attr.set(value, reset, reparent);
}
if (!silent && changed) {
self.events$.trigger(ODataModelEventType.Change, {
attr,
value,
previous: current,
options: { key: modelField.isKey() },
});
}
return changed;
}
_link(self, attr) {
attr.events$.subscribe((event) => {
if (event.canContinueWith(self)) {
if (event.model === attr.get()) {
if (event.type === ODataModelEventType.Change &&
attr.navigation &&
event.options?.key) {
const ref = attr.get().referential(attr);
if (ref !== null && ref !== undefined) {
Object.assign(self, ref);
}
}
}
self.events$.emit(event.push(self, attr));
}
});
}
}
class ODataApiOptions {
/**
* Default OData version
*/
version;
/**
* Send enum as string in the request
*/
stringAsEnum;
/**
* Delete reference by path or by id
*/
deleteRefBy;
/**
* No use parenthesis for empty parameters functions
*/
nonParenthesisForEmptyParameterFunction;
/**
* Strip metadata from the response
*/
stripMetadata;
/**
* Use JSON Batch Format
*/
jsonBatchFormat;
/**
* Relative urls
* http://docs.oasis-open.org/odata/odata-json-format/v4.0/cs01/odata-json-format-v4.0-cs01.html#_Toc365464682
*/
relativeUrls;
/**
* Cache fetch policy
*/
fetchPolicy;
/**
* Extra params to be sent in the request
*/
params;
/**
* Extra headers to be sent in the request
*/
headers;
/**
* Http request with credentials
*/
withCredentials;
/**
* Send query options in the request body
*/
bodyQueryOptions;
/**
* Customize accept header with OData options
* @link http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html#sec_RequestingtheJSONFormat
*/
accept;
etag = { ifMatch: true, ifNoneMatch: false };
prefer;
constructor(config) {
this.version = config.version || DEFAULT_VERSION;
this.stringAsEnum = config.stringAsEnum || false;
this.params = config.params || {};
this.headers = config.headers || {};
this.withCredentials = config.withCredentials;
this.stripMetadata = config.stripMetadata || DEFAULT_STRIP_METADATA;
this.fetchPolicy = config.fetchPolicy || DEFAULT_FETCH_POLICY;
this.bodyQueryOptions = config.bodyQueryOptions || [];
this.accept = config.accept;
Object.assign(this.etag, config.etag || {});
this.prefer = config.prefer;
this.deleteRefBy = config.deleteRefBy ?? 'path';
this.nonParenthesisForEmptyParameterFunction =
config.nonParenthesisForEmptyParameterFunction ?? false;
this.jsonBatchFormat = config.jsonBatchFormat ?? false;
this.relativeUrls = config.relativeUrls ?? true;
}
get parserOptions() {
return {
version: this.version,
stringAsEnum: this.stringAsEnum,
deleteRefBy: this.deleteRefBy,
nonParenthesisForEmptyParameterFunction: this.nonParenthesisForEmptyParameterFunction,
...this.accept,
};
}
get helper() {
return ODataHelper[this.version];
}
}
/**
* Api abstraction for consuming OData services.
*/
class ODataApi {
requester;
serviceRootUrl;
metadataUrl;
name;
version;
default;
creation;
// Options
options;
// Cache
cache;
// Error Handler
errorHandler;
// Base Parsers
parsers;
// Schemas
schemas;
constructor(config) {
this.serviceRootUrl = config.serviceRootUrl;
if (this.serviceRootUrl.includes('?'))
throw new Error("The 'serviceRootUrl' should not contain query string. Please use 'params' to add extra parameters");
if (!this.serviceRootUrl.endsWith('/'))
this.serviceRootUrl += '/';
this.metadataUrl = config.metadataUrl ?? `${this.serviceRootUrl}$metadata`;
this.name = config.name;
this.version = config.version ?? DEFAULT_VERSION;
this.default = config.default ?? false;
this.creation = config.creation ?? new Date();
this.options = new ODataApiOptions({
...config.options,
version: this.version,
});
this.cache = config.cache ?? new ODataInMemoryCache();
this.errorHandler = config.errorHandler;
this.parsers = new Map(Object.entries(config.parsers ?? EDM_PARSERS));
this.schemas = (config.schemas ?? []).map((schema) => new ODataSchema(schema, this));
}
configure(settings = {}) {
this.requester = settings.requester;
this.schemas.forEach((schema) => {
schema.configure({
options: this.options.parserOptions,
});
});
}
populate(metadata) {
const config = metadata.toConfig();
this.version = config.version ?? DEFAULT_VERSION;
const schemas = (config.schemas ?? []).map((schema) => new ODataSchema(schema, this));
this.schemas = [...this.schemas, ...schemas];
schemas.forEach((schema) => {
schema.configure({
options: this.options.parserOptions,
});
});
}
fromJson(json) {
const segments = ODataPathSegments.fromJson(json.segments);
const query = ODataQueryOptions.fromJson(json.options);
switch (segments.last()?.name) {
case PathSegment.entitySet:
if (segments.last()?.hasKey()) {
return new ODataEntityResource(this, { segments, query });
}
else {
return new ODataEntitySetResource(this, { segments, query });
}
case PathSegment.navigationProperty:
return new ODataNavigationPropertyResource(this, { segments, query });
case PathSegment.singleton:
return new ODataSingletonResource(this, { segments, query });
case PathSegment.action:
return new ODataActionResource(this, { segments, query });
case PathSegment.function:
return new ODataFunctionResource(this, { segments, query });
}
throw new Error('No Resource for json');
}
/**
* Build a metadata resource.
* @returns ODataMetadataResource
*/
metadata() {
return ODataMetadataResource.factory(this);
}
/**
* Build a batch resource.
* @returns ODataBatchResource
*/
batch() {
return ODataBatchResource.factory(this);
}
/**
* Build a singleton resource.
* @param path Name of the singleton
* @returns
*/
singleton(name) {
const singleton = this.findSingleton(name);
return ODataSingletonResource.factory(this, {
path: singleton?.name ?? name,
type: singleton?.singletonType,
});
}
/**
* Build an entity set resource.
* @param path Name of the entity set
* @returns
*/
entitySet(name) {
const entitySet = this.findEntitySet(name);
return ODataEntitySetResource.factory(this, {
path: entitySet?.name ?? name,
type: entitySet?.entityType,
});
}
/**
* Unbound Action
* @param {string} path?
* @returns ODataActionResource
*/
action(path) {
const callable = this.findCallable(path);
return ODataActionResource.factory(this, {
path,
outgoingType: callable?.type(),
incomingType: callable?.returnType(),
});
}
/**
* Unbound Function
* @param {string} path?
* @returns ODataFunctionResource
*/
function(path) {
const callable = this.findCallable(path);
return ODataFunctionResource.factory(this, {
path,
outgoingType: callable?.type(),
incomingType: callable?.returnType(),
});
}
callable(type) {
return this.findCallable(type);
}
enumType(type) {
return this.findEnumType(type);
}
structuredType(type) {
return this.findStructuredType(type);
}
//request(req: ODataRequest<any>): Observable<any> {
request(method, resource, options) {
let req = ODataRequest.factory(this, method, resource, {
body: options.body,
etag: options.etag,
context: options.context,
headers: options.headers,
params: options.params,
responseType: options.responseType,
observe: (options.observe === 'events' ? 'events' : 'response'),
withCount: options.withCount,
bodyQueryOptions: options.bodyQueryOptions,
reportProgress: options.reportProgress,
fetchPolicy: options.fetchPolicy,
parserOptions: options.parserOptions,
withCredentials: options.withCredentials,
});
let res$ = this.requester !== undefined ? this.requester(req) : NEVER;
res$ = res$.pipe(map((res) => res.type === HttpEventType.Response
? ODataResponse.fromHttpResponse(req, res)
: res));
if (this.errorHandler !== undefined)
res$ = res$.pipe(catchError(this.errorHandler));
if (options.observe === 'events') {
return res$;
}
res$ = this.cache.handleRequest(req, res$);
switch (options.observe || 'body') {
case 'body':
switch (options.responseType) {
case 'entities':
return res$.pipe(map((res) => res.entities()));
case 'entity':
return res$.pipe(map((res) => res.entity()));
case 'property':
return res$.pipe(map((res) => res.property()));
case 'value':
return res$.pipe(map((res) => res.value()));
default:
// Other responseTypes (arraybuffer, blob, json, text) return body
return res$.pipe(map((res) => res.body));
}
case 'response':
// The response stream was requested directly, so return it.
return res$;
default:
// Guard against new future observe types being added.
throw new Error(`Unreachable: unhandled observe type ${options.observe}}`);
}
}
//# region Find by Type
// Memoize
memo = {
enumTypes: new Map(),
structuredTypes: new Map(),
callables: new Map(),
entitySets: new Map(),
singletons: new Map(),
parsers: new Map(),
options: new Map(),
};
createSchema(config) {
const schema = new ODataSchema(config, this);
schema.configure({
options: this.options.parserOptions,
});
this.schemas.push(schema);
return schema;
}
findSchema(type) {
const schemas = this.schemas.filter((s) => s.isNamespaceOf(type));
if (schemas.length === 0)
return undefined;
if (schemas.length === 1)
return schemas[0];
return schemas
.sort((s1, s2) => s1.namespace.length - s2.namespace.length)
.pop();
}
//#region EnumTypes
findEnumType(value) {
if (this.memo.enumTypes.has(value)) {
return this.memo.enumTypes.get(value);
}
const enumTypes = this.schemas.reduce((acc, schema) => [...acc, ...schema.enums], []);
let enumType = enumTypes.find((e) => e.type() === value);
enumType = enumType ?? enumTypes.find((e) => e.name === value);
this.memo.enumTypes.set(value, enumType);
return enumType;
}
//#endregion
//#region StructuredTypes
findStructuredType(value) {
if (this.memo.structuredTypes.has(value)) {
return this.memo.structuredTypes.get(value);
}
const structuredTypes = this.schemas.reduce((acc, schema) => [...acc, ...schema.entities], []);
let structuredType = structuredTypes.find((e) => e.type() === value);
structuredType =
structuredType ?? structuredTypes.find((e) => e.name === value);
this.memo.structuredTypes.set(value, structuredType);
return structuredType;
}
//#endregion
//#region Callables
findCallable(value, bindingType) {
const key = bindingType !== undefined ? `${bindingType}/${value}` : value;
if (this.memo.callables.has(key)) {
return this.memo.callables.get(key);
}
const bindingStructuredType = bindingType !== undefined
? this.findStructuredType(bindingType)
: undefined;
const callables = this.schemas.reduce((acc, schema) => [...acc, ...schema.callables], []);
let callable = callables.find((c) => {
const isCallableType = c.type() == value;
const callableBindingType = c.binding()?.type;
const callableBindingStructuredType = callableBindingType !== undefined
? this.findStructuredType(callableBindingType)
: undefined;
return (isCallableType &&
(!bindingStructuredType ||
(callableBindingStructuredType &&
bindingStructuredType.isSubtypeOf(callableBindingStructuredType))));
});
callable =
callable ??
callables.find((c) => {
const isCallableType = c.name == value;
const callableBindingType = c.binding()?.type;
const callableBindingStructuredType = callableBindingType !== undefined
? this.findStructuredType(callableBindingType)
: undefined;
return (isCallableType &&
(!bindingStructuredType ||
(callableBindingStructuredType &&
bindingStructuredType.isSubtypeOf(callableBindingStructuredType))));
});
this.memo.callables.set(key, callable);
return callable;
}
//#endregion
//#region EntitySets
findEntitySet(value) {
if (this.memo.entitySets.has(value)) {
return this.memo.entitySets.get(value);
}
const entitySets = this.schemas.reduce((acc, schema) => [...acc, ...schema.entitySets], []);
let entitySet = entitySets.find((e) => e.type() === value);
entitySet = entitySet ?? entitySets.find((e) => e.name === value);
this.memo.entitySets.set(value, entitySet);
return entitySet;
}
//#endregion
//#region Singletons
findSingleton(value) {
if (this.memo.singletons.has(value)) {
return this.memo.singletons.get(value);
}
const singletons = this.schemas.reduce((acc, schema) => [...acc, ...schema.singletons], []);
let singleton = singletons.find((e) => e.type() === value);
singleton = singleton ?? singletons.find((e) => e.name === value);
this.memo.singletons.set(value, singleton);
return singleton;
}
//#endregion
findModel(type) {
return this.findStructuredType(type)?.model;
}
createModel(structured) {
if (structured.model !== undefined)
return structured.model;
// Build Ad-hoc model
const Model = class extends ODataModel {
};
// Build Meta
Model.meta = this.optionsForType(structured.type(), {
structuredType: structured,
});
if (Model.meta !== undefined) {
// Configure
Model.meta.configure({
options: this.options.parserOptions,
});
}
// Store New Model for next time
structured.model = Model;
return Model;
}
modelForType(type) {
let Model = this.findModel(type);
if (Model === undefined) {
const structured = this.findStructuredType(type);
if (structured === undefined)
throw Error(`No structured type for ${type}`);
Model = this.createModel(structured);
}
return Model;
}
findCollection(type) {
return this.findStructuredType(type)?.collection;
}
createCollection(structured, model) {
if (structured.collection !== undefined)
return structured.collection;
if (model === undefined)
model = this.createModel(structured);
const Collection = class extends ODataCollection {
static model = model;
};
structured.collection = Collection;
return Collection;
}
collectionForType(type) {
let Collection = this.findCollection(type);
if (Collection === undefined) {
const structured = this.findStructuredType(type);
if (structured === undefined)
throw Error(`No structured type for ${type}`);
const Model = this.modelForType(type);
Collection = this.createCollection(structured, Model);
}
return Collection;
}
findEntitySetForEntityType(entityType) {
if (this.memo.entitySets.has(entityType)) {
return this.memo.entitySets.get(entityType);
}
const entitySet = this.schemas
.reduce((acc, schema) => [...acc, ...schema.entitySets], [])
.find((e) => e.entityType === entityType);
this.memo.entitySets.set(entityType, entitySet);
return entitySet;
}
parserForType(type, bindingType) {
const key = bindingType !== undefined ? `${bindingType}/${type}` : type;
if (this.memo.parsers.has(key)) {
return this.memo.parsers.get(key);
}
// None Parser by default
let parser = NONE_PARSER;
if (this.parsers.has(type)) {
// Edm, Base Parsers
parser = this.parsers.get(type);
}
else if (!type.startsWith('Edm.')) {
// Callable, EnumType, StructuredType (ComplexType and EntityType) Parsers
let value = this.findCallable(type, bindingType) ??
this.findEnumType(type) ??
this.findStructuredType(type);
parser = value?.parser;
}
// Set Parser for next time
this.memo.parsers.set(key, parser);
return parser;
}
optionsForType(type, { structuredType, config, } = {}) {
// Strucutred Options
if (this.memo.options.has(type)) {
return this.memo.options.get(type);
}
let meta = undefined;
if (!type.startsWith('Edm.')) {
structuredType = this.findStructuredType(type) ?? structuredType;
if (structuredType !== undefined) {
meta = ODataModel.buildMetaOptions({ config, structuredType });
}
}
// Set Options for next time
this.memo.options.set(type, meta);
return meta;
}
}
class ODataSettings {
apis;
constructor(configs) {
this.apis = configs.map((config) => new ODataApi(config));
if (this.apis.length > 1) {
if (this.apis.some((c) => c.name === undefined))
throw new Error('Multiple APIs: Needs configuration names');
if (this.apis.filter((c) => c.default).length > 1)
throw new Error('Multiple APIs: Needs only one default api');
}
// If not default setup first config as default api
if (this.apis.every((c) => !c.default))
this.apis[0].default = true;
}
configure(settings) {
this.apis.forEach((api) => api.configure(settings));
}
defaultApi() {
return this.apis.find((c) => c.default);
}
findApiByName(name) {
return this.apis.find((c) => c.name === name);
}
apiByName(name) {
const api = this.findApiByName(name);
if (api === undefined)
throw new Error(`No API for name: ${name}`);
return api;
}
findApiForTypes(types) {
return this.apis.find((c) => c.schemas.some((s) => types.some((type) => s.isNamespaceOf(type))));
}
findApiForType(type) {
return this.findApiForTypes([type]);
}
apiForType(type) {
const api = this.findApiForType(type);
if (api === undefined)
throw new Error(`No API for type: ${type}`);
return api;
}
//#region Configs shortcuts
enumTypeForType(type) {
let values = this.apis
.map((api) => api.findEnumType(type))
.filter((e) => e);
if (values.length === 0)
throw Error(`No Enum for type ${type} was found`);
if (values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
structuredTypeForType(type) {
let values = this.apis
.map((api) => api.findStructuredType(type))
.filter((e) => e);
if (values.length === 0)
throw Error(`No Structured for type ${type} was found`);
if (values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
callableForType(type, bindingType) {
let values = this.apis
.map((api) => api.findCallable(type, bindingType))
.filter((e) => e);
if (values.length === 0)
throw Error(`No Callable for type ${type} was found`);
if (values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
entitySetForType(type) {
let values = this.apis
.map((api) => api.findEntitySet(type))
.filter((e) => e);
if (values.length === 0)
throw Error(`No EntitySet for type ${type} was found`);
if (values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
parserForType(type) {
let values = this.apis
.map((api) => api.parserForType(type))
.filter((e) => e);
if (values.length === 0)
throw Error(`No Parser for type ${type} was found`);
if (!type.startsWith('Edm.') && values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
modelForType(type) {
let values = this.apis.map((api) => api.findModel(type)).filter((e) => e);
if (values.length === 0)
throw Error(`No Model for type ${type} was found`);
if (values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
collectionForType(type) {
let values = this.apis
.map((api) => api.findCollection(type))
.filter((e) => e);
if (values.length === 0)
throw Error(`No Collection for type ${type} was found`);
if (values.length > 1)
throw Error('Multiple APIs: More than one value was found');
return values[0];
}
}
class ODataConfigLoader {
}
class ODataConfigSyncLoader {
passedConfigs;
constructor(passedConfigs) {
this.passedConfigs = passedConfigs;
}
loadConfigs() {
return Array.isArray(this.passedConfigs)
? of(this.passedConfigs)
: of([this.passedConfigs]);
}
}
class ODataConfigAsyncLoader {
configs$;
constructor(configs$) {
this.configs$ = configs$;
}
loadConfigs() {
return Array.isArray(this.configs$)
? forkJoin(this.configs$)
: this.configs$.pipe(map$1((value) => Array.isArray(value)
? value
: [value]));
}
}
class ODataMetadataLoader {
sources$;
baseConfigs;
constructor(sources$, baseConfigs) {
this.sources$ = sources$;
this.baseConfigs = baseConfigs;
}
loadConfigs() {
const configs = Array.isArray(this.baseConfigs)
? this.baseConfigs
: [this.baseConfigs];
return this.sources$.pipe(map$1((source) => (Array.isArray(source) ? source : [source]).map((m, i) => new ODataMetadataParser(m).metadata().toConfig(configs[i] ?? {}))));
}
}
function addBody(options, body) {
return {
body,
etag: options.etag,
fetchPolicy: options.fetchPolicy,
headers: options.headers,
observe: options.observe,
params: options.params,
reportProgress: options.reportProgress,
responseType: options.responseType,
withCredentials: options.withCredentials,
};
}
class ODataClient {
http;
loader;
settings;
constructor(http, loader) {
this.http = http;
this.loader = loader;
this.loader.loadConfigs().subscribe((configs) => {
this.settings = new ODataSettings(configs);
this.settings.configure({
requester: (req) => this.http.request(req.method, `${req.url}`, {
body: req.body,
context: req.context,
headers: req.headers,
observe: req.observe,
params: req.params,
reportProgress: req.reportProgress,
responseType: req.responseType,
withCredentials: req.withCredentials,
}),
});
});
}
//#region Resolve Building Blocks
/**
* Resolve the api for the given value.
* Where value is: string type or an string name or an instance of resource.
* @param value The value to resolve.
* @returns The api for the value.
*/
apiFor(value) {
let api = undefined;
if (value instanceof ODataResource)
api = this.settings.findApiForTypes(value.types());
else if (typeof value === 'string')
api =
this.settings.findApiByName(value) ||
this.settings.findApiForType(value);
return api ?? this.settings.defaultApi();
}
defaultApi() {
return this.settings.defaultApi();
}
/**
* Resolve the parser for the given string type.
* @param type The string type of the parser.
* @returns The parser for the given type.
*/
parserForType(type) {
return this.settings.parserForType(type);
}
/**
* Resolve the enum type for the given string type.
* @param type The string type of the enum type.
* @returns The enum type for the given type.
*/
enumTypeForType(type) {
return this.settings.enumTypeForType(type);
}
/**
* Resolve the structured type for the given string type.
* @param type The string type of the structured type.
* @returns The structured type for the given type.
*/
structuredTypeForType(type) {
return this.settings.structuredTypeForType(type);
}
/**
* Resolve the callable for the given string type.
* @param type The string type of the callable.
* @returns The callable for the given type.
*/
callableForType(type) {
return this.settings.callableForType(type);
}
/**
* Resolve the entity set for the given string type.
* @param type The string type of the entity set.
* @returns The entity set for the given type.
*/
entitySetForType(type) {
return this.settings.entitySetForType(type);
}
/**
* Resolve the model for the given string type.
* @param type The string type of the model.
* @returns The model for the given type.
*/
modelForType(type) {
return this.settings.modelForType(type);
}
/**
* Resolve the collection for the given string type.
* @param type The string type of the collection.
* @returns The collection for the given type.
*/
collectionForType(type) {
return this.settings.collectionForType(type);
}
fromJson(json, apiNameOrType) {
return this.apiFor(apiNameOrType).fromJson(json);
}
// Requests
/**
* Build a resource for the metadata.
* @param apiName The name of the API.
* @returns The metadata resource.
*/
metadata(apiName) {
return this.apiFor(apiName).metadata();
}
/**
* Build a resource for the batch.
* @param apiName The name of the API.
* @returns The batch resource.
*/
batch(apiName) {
return this.apiFor(apiName).batch();
}
/**
* Build a resource for the singleton.
* @param path The full path to the singleton.
* @param apiNameOrType The name of the API or the type of the singleton.
* @returns The singleton resource.
*/
singleton(path, apiNameOrType) {
return this.apiFor(apiNameOrType).singleton(path);
}
/**
* Build a resource for the entity set.
* @param path The full path to the entity set.
* @param apiNameOrType The name of the API or the type of the entity set.
* @returns The entity set resource.
*/
entitySet(path, apiNameOrType) {
return this.apiFor(apiNameOrType).entitySet(path);
}
/**
* Build a resource for unbound action.
* @param path The full path to the action.
* @param apiNameOrType The name of the API or the type of the entity.
* @returns The unbound action resource.
*/
action(path, apiNameOrType) {
return this.apiFor(apiNameOrType).action(path);
}
/**
* Build a resource for unbound function.
* @param path The full path to the function.
* @param apiNameOrType The name of the API or the type of the callable.
* @returns The unbound function resource.
*/
function(path, apiNameOrType) {
return this.apiFor(apiNameOrType).function(path);
}
request(method, resource, options) {
let api = this.apiFor(resource);
return api.request(method, resource, options);
}
delete(resource, options = {}) {
return this.request('DELETE', resource, addBody(options, null));
}
get(resource, options = {}) {
return this.request('GET', resource, options);
}
head(resource, options = {}) {
return this.request('HEAD', resource, options);
}
jsonp(resource, callbackParam) {
return this.request('JSONP', resource, {
body: null,
params: new HttpParams().append(callbackParam, 'JSONP_CALLBACK'),
observe: 'body',
responseType: 'json',
});
}
options(resource, options = {}) {
return this.request('OPTIONS', resource, options);
}
patch(resource, body, options = {}) {
return this.request('PATCH', resource, addBody(options, body));
}
post(resource, body, options = {}) {
return this.request('POST', resource, addBody(options, body));
}
put(resource, body, options = {}) {
return this.request('PUT', resource, addBody(options, body));
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataClient, deps: [{ token: i1.HttpClient }, { token: ODataConfigLoader }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataClient });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataClient, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: ODataConfigLoader }] });
class ODataBaseService {
client;
name;
apiNameOrEntityType;
constructor(client, name, apiNameOrEntityType) {
this.client = client;
this.name = name;
this.apiNameOrEntityType = apiNameOrEntityType;
}
get api() {
return this.client.apiFor(this.apiNameOrEntityType);
}
callFunction(params, resource, responseType, options = {}) {
resource.query((q) => q.restore(options));
return resource.call(params, {
responseType: responseType,
...options,
});
}
callAction(params, resource, responseType, options = {}) {
resource.query((q) => q.restore(options));
return resource.call(params, {
responseType: responseType,
...options,
});
}
fetchNavigationProperty(resource, responseType, options = {}) {
resource.query((q) => q.restore(options));
return resource.fetch({ responseType: responseType, ...options });
}
}
class ODataEntityService extends ODataBaseService {
/**
* The schema for the structured type.
*/
get structuredTypeSchema() {
return this.apiNameOrEntityType !== undefined
? this.api.findStructuredType(this.apiNameOrEntityType)
: undefined;
}
}
class ODataEntitySetService extends ODataEntityService {
static Model;
static Collection;
model(entity, reset) {
const Service = this.constructor;
return this.entity().asModel((entity ?? {}), {
reset,
ModelType: Service.Model,
});
}
collection(entities, reset) {
const Service = this.constructor;
return this.entities().asCollection((entities ?? []), {
reset,
CollectionType: Service.Collection,
});
}
/**
* Get the entity set resource for this service.
*/
entities() {
return this.client.entitySet(this.name, this.apiNameOrEntityType);
}
/**
* Get the entity resource for this service.
* @param key The entity key.
*/
entity(key) {
return this.entities().entity(key);
}
attach(model) {
if (model instanceof ODataModel) {
model.attach(this.entities().entity());
}
else if (model instanceof ODataCollection) {
model.attach(this.entities());
}
}
/**
* The schema for the entity set.
*/
get entitySetSchema() {
return this.api.findEntitySet(this.name);
}
/**
* Get all entities from the entity set.
* @param options The options for the request.
*/
fetchAll(options) {
return this.entities().fetchAll(options);
}
/**
* Get entities from the entity set.
* @param withCount Get the count of the entities.
* @param options The options for the request.
*/
fetchMany(top, options) {
return this.entities().fetchMany(top, options);
}
/**
* Get an entity from the entity set.
* @param key The entity key.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
fetchOne(options) {
return this.entities().fetchOne(options);
}
/**
* Create an entity in the entity set.
* @param attrs The attributes for the entity.
* @param options The options for the request.
*/
create(attrs, options) {
return this.entities().create(attrs, options);
}
/**
* Update an entity in the entity set.
* @param key The entity key.
* @param attrs The attributes for the entity.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
update(key, attrs, options) {
const res = this.entity(key);
if (!res.hasKey())
return throwError(() => new Error('update: Resource without key'));
return res.update(attrs, options);
}
/**
* Patch an entity in the entity set.
* @param key The entity key.
* @param attrs The attributes for the entity.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
modify(key, attrs, options) {
const res = this.entity(key);
if (!res.hasKey())
return throwError(() => new Error('modify: Resource without key'));
return res.modify(attrs, options);
}
/**
* Delete an entity in the entity set.
* @param key The entity key.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
destroy(key, options) {
const res = this.entity(key);
if (!res.hasKey())
return throwError(() => new Error('destroy: Resource without key'));
return res.destroy(options);
}
//#region Shortcuts
/**
* Get or create an entity in the entity set.
* @param key The entity key.
* @param attrs The attributes for the entity.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
fetchOrCreate(key, attrs, { etag, ...options } = {}) {
return this.entity(key)
.fetch({ etag, ...options })
.pipe(catchError((error) => {
if (error.status === 404)
return this.create(attrs, options);
else
return throwError(() => error);
}));
}
/**
* Save an entity in the entity set.
* @param attrs The attributes for the entity.
* @param method The method to use.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
save(attrs, { etag, method, ...options } = {}) {
let schema = this.structuredTypeSchema;
if (method === undefined && schema !== undefined && schema.isCompoundKey())
return throwError(() => new Error('save: Composite key require a specific method, use create/update/patch'));
let key = schema && schema.resolveKey(attrs);
if (method === undefined)
method = key !== undefined ? 'update' : 'create';
if ((method === 'update' || method === 'modify') && key === undefined)
return throwError(() => new Error("save: Can't update/patch entity without key"));
return method === 'create'
? this.create(attrs, options)
: method === 'modify'
? this.modify(key, attrs, { etag, ...options })
: this.update(key, attrs, { etag, ...options });
}
}
/**
* OData Singleton Service
* www.odata.org/getting-started/advanced-tutorial/#singleton
*/
class ODataSingletonService extends ODataEntityService {
static Model;
model(entity) {
const Service = this.constructor;
return this.entity().asModel((entity ?? {}), {
ModelType: Service.Model,
});
}
/**
* Get the entity resource for this service.
* @param key The entity key.
*/
entity() {
return this.client.singleton(this.name, this.apiNameOrEntityType);
}
/**
* Attach an existing model to this service.
* @param model The model to attach.
*/
attach(model) {
model.attach(this.entity());
}
/**
* The schema for the singleton.
*/
get singletonSchema() {
return this.api.findEntitySet(this.name);
}
/**
* Update the singleton entity
* @param attrs The attributes for the entity.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
update(attrs, options) {
const res = this.entity();
return res.update(attrs, options);
}
/**
* Patch the singleton entity
* @param attrs The attributes for the entity.
* @param etag The etag for the entity.
* @param options The options for the request.
*/
patch(attrs, options) {
const res = this.entity();
return res.modify(attrs, options);
}
}
class ODataServiceFactory {
client;
constructor(client) {
this.client = client;
}
/**
* Factory method to create an entity set service.
* @param entitySetName Name of the entity set.
* @param apiNameOrEntityType Name of the API or the type of the entity.
*/
entitySet(entitySetName, apiNameOrEntityType, options = {}) {
const Service = class extends ODataEntitySetService {
Model = options?.Model;
Collection = options?.Collection;
};
return new Service(this.client, entitySetName, apiNameOrEntityType);
}
/** Factory method to create a singleton service.
* @param singletonName Name of the singleton.
* @param apiNameOrEntityType Name of the API or the type of the entity.
*/
singleton(singletonName, apiNameOrEntityType, options = {}) {
const Service = class extends ODataSingletonService {
Model = options?.Model;
};
return new Service(this.client, singletonName, apiNameOrEntityType);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataServiceFactory, deps: [{ token: ODataClient }], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataServiceFactory });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataServiceFactory, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: ODataClient }] });
const ODATA_CONFIG = new InjectionToken('odata.config');
function createSyncLoader(passedConfig) {
return new ODataConfigSyncLoader(passedConfig.config);
}
// Standalone version
function provideODataClient(passedConfig) {
return makeEnvironmentProviders([
{ provide: ODATA_CONFIG, useValue: passedConfig },
passedConfig?.loader ?? {
provide: ODataConfigLoader,
useFactory: createSyncLoader,
deps: [ODATA_CONFIG],
},
ODataClient,
ODataServiceFactory,
]);
}
// Module version
class ODataModule {
static forRoot(passedConfig) {
return {
ngModule: ODataModule,
providers: [
// Make the ODATA_CONFIG available through injection
{ provide: ODATA_CONFIG, useValue: passedConfig },
// Create the loader: Either the one getting passed or a sync one
passedConfig?.loader ?? {
provide: ODataConfigLoader,
useFactory: createSyncLoader,
deps: [ODATA_CONFIG],
},
ODataClient,
ODataServiceFactory,
],
};
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.1.0", ngImport: i0, type: ODataModule, imports: [CommonModule, HttpClientModule] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataModule, providers: [ODataClient, ODataServiceFactory], imports: [CommonModule, HttpClientModule] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.0", ngImport: i0, type: ODataModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule, HttpClientModule],
providers: [ODataClient, ODataServiceFactory],
}]
}] });
/*
* Public API Surface of angular-odata
*/
/**
* Generated bundle index. Do not edit.
*/
export { Aggregate, ApplyExpression, ArithmeticFunctions, ArithmeticOperators, BUBBLES, CollectionFunctions, ComputeExpression, ConditionalFunctions, DateAndTimeFunctions, Dates, Durations, EDM_PARSERS, EdmType, Enums, ExpandExpression, ExpandField, Expression, FieldFactory, FilterExpression, Function, GeoFunctions, GroupBy, GroupByTransformations, Grouping, GroupingOperators, Http, INCLUDE_DEEP, INCLUDE_SHALLOW, ITEM_ROOT, JsonType, Lambda, LambdaOperators, LogicalOperators, Model, ModelField, NONE_PARSER, ODATA_CONFIG, OData, ODataActionResource, ODataAnnotations, ODataApi, ODataBaseService, ODataBatchRequest, ODataBatchResource, ODataCache, ODataCallable, ODataCallableParser, ODataClient, ODataCollection, ODataConfigAsyncLoader, ODataConfigLoader, ODataConfigSyncLoader, ODataCountResource, ODataEntitiesAnnotations, ODataEntityAnnotations, ODataEntityContainer, ODataEntityResource, ODataEntitySet, ODataEntitySetResource, ODataEntitySetService, ODataEntityTypeKey, ODataEnumType, ODataEnumTypeFieldParser, ODataEnumTypeParser, ODataFunctionResource, ODataFunctions, ODataInMemoryCache, ODataInStorageCache, ODataMediaResource, ODataMetadata, ODataMetadataLoader, ODataMetadataParser, ODataMetadataResource, ODataModel, ODataModelAttribute, ODataModelEvent, ODataModelEventEmitter, ODataModelEventType, ODataModelField, ODataModelOptions, ODataModelState, ODataModule, ODataNavigationPropertyResource, ODataOperators, ODataParameterParser, ODataPathSegments, ODataPathSegmentsHandler, ODataPropertyAnnotations, ODataPropertyResource, ODataQueryOptionHandler, ODataQueryOptions, ODataQueryOptionsHandler, ODataReferenceResource, ODataReferential, ODataRequest, ODataResource, ODataResponse, ODataSchema, ODataServiceFactory, ODataSettings, ODataSingleton, ODataSingletonResource, ODataSingletonService, ODataStructuredType, ODataStructuredTypeFieldParser, ODataStructuredTypeParser, ODataSyntax, ODataTransformations, ODataValueResource, Objects, Operator, OrderByExpression, OrderByField, PathSegment, QueryCustomTypes, QueryOption, RenderableFactory, SearchExpression, SearchTerm, SegmentHandler, SelectExpression, StandardAggregateMethods, StringAndCollectionFunctions, StringFunctions, Strings, Transformations, Type, TypeFunctions, Types, Urls, alias, binary, buildPathAndQuery, createSyncLoader, duration, encode, functions, isQueryCustomType, isRawType, normalizeValue, operators, pathAndParamsFromQueryOptions, pathAndParamsFromSegments, provideODataClient, raw, render, resolve, syntax, transformations };
//# sourceMappingURL=angular-odata.mjs.map