UNPKG

angular-odata

Version:

Client side OData typescript library for Angular

14,261 lines 509 kB
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