UNPKG

angular-odata

Version:

Client side OData typescript library for Angular

1 lines 1.17 MB
{"version":3,"file":"angular-odata.mjs","sources":["../../../projects/angular-odata/src/lib/types.ts","../../../projects/angular-odata/src/lib/constants.ts","../../../projects/angular-odata/src/lib/cache/cache.ts","../../../projects/angular-odata/src/lib/cache/memory.ts","../../../projects/angular-odata/src/lib/resources/query/builder.ts","../../../projects/angular-odata/src/lib/utils/dates.ts","../../../projects/angular-odata/src/lib/utils/durations.ts","../../../projects/angular-odata/src/lib/utils/enums.ts","../../../projects/angular-odata/src/lib/utils/types.ts","../../../projects/angular-odata/src/lib/utils/http.ts","../../../projects/angular-odata/src/lib/utils/objects.ts","../../../projects/angular-odata/src/lib/utils/odata.ts","../../../projects/angular-odata/src/lib/utils/strings.ts","../../../projects/angular-odata/src/lib/utils/urls.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/base.ts","../../../projects/angular-odata/src/lib/schema/annotation.ts","../../../projects/angular-odata/src/lib/schema/element.ts","../../../projects/angular-odata/src/lib/utils/arraybuffers.ts","../../../projects/angular-odata/src/lib/schema/parsers/edm.ts","../../../projects/angular-odata/src/lib/schema/parsers/enum-type.ts","../../../projects/angular-odata/src/lib/helper.ts","../../../projects/angular-odata/src/lib/schema/parsers/structured-type.ts","../../../projects/angular-odata/src/lib/schema/parsers/callable.ts","../../../projects/angular-odata/src/lib/schema/callable.ts","../../../projects/angular-odata/src/lib/schema/entity-set.ts","../../../projects/angular-odata/src/lib/schema/singleton.ts","../../../projects/angular-odata/src/lib/schema/entity-container.ts","../../../projects/angular-odata/src/lib/schema/enum-type.ts","../../../projects/angular-odata/src/lib/schema/structured-type.ts","../../../projects/angular-odata/src/lib/schema/schema.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/count.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/filter.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/search.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/apply.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/orderby.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/select.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/expand.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/syntax.ts","../../../projects/angular-odata/src/lib/resources/query/expressions/compute.ts","../../../projects/angular-odata/src/lib/resources/query/handlers.ts","../../../projects/angular-odata/src/lib/resources/query/options.ts","../../../projects/angular-odata/src/lib/resources/path/handlers.ts","../../../projects/angular-odata/src/lib/resources/path/segments.ts","../../../projects/angular-odata/src/lib/resources/request.ts","../../../projects/angular-odata/src/lib/resources/resource.ts","../../../projects/angular-odata/src/lib/resources/types/action.ts","../../../projects/angular-odata/src/lib/utils/arrays.ts","../../../projects/angular-odata/src/lib/resources/types/batch.ts","../../../projects/angular-odata/src/lib/resources/types/count.ts","../../../projects/angular-odata/src/lib/resources/types/function.ts","../../../projects/angular-odata/src/lib/resources/types/media.ts","../../../projects/angular-odata/src/lib/resources/types/value.ts","../../../projects/angular-odata/src/lib/resources/types/property.ts","../../../projects/angular-odata/src/lib/resources/types/reference.ts","../../../projects/angular-odata/src/lib/resources/types/navigation-property.ts","../../../projects/angular-odata/src/lib/resources/types/entity.ts","../../../projects/angular-odata/src/lib/resources/types/entity-set.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-annotation.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-reference.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-type-definition.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-enum-type.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-structural-property.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-structured-type.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-function-action.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-navigation-property-binding.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-entity-set.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-singleton.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-entity-container.ts","../../../projects/angular-odata/src/lib/metadata/csdl/csdl-schema.ts","../../../projects/angular-odata/src/lib/metadata/metadata.ts","../../../projects/angular-odata/src/lib/metadata/parser.ts","../../../projects/angular-odata/src/lib/resources/types/metadata.ts","../../../projects/angular-odata/src/lib/resources/types/singleton.ts","../../../projects/angular-odata/src/lib/annotations.ts","../../../projects/angular-odata/src/lib/resources/options.ts","../../../projects/angular-odata/src/lib/resources/response.ts","../../../projects/angular-odata/src/lib/cache/storage.ts","../../../projects/angular-odata/src/lib/models/collection.ts","../../../projects/angular-odata/src/lib/models/model.ts","../../../projects/angular-odata/src/lib/models/options.ts","../../../projects/angular-odata/src/lib/options.ts","../../../projects/angular-odata/src/lib/api.ts","../../../projects/angular-odata/src/lib/settings.ts","../../../projects/angular-odata/src/lib/loaders.ts","../../../projects/angular-odata/src/lib/client.ts","../../../projects/angular-odata/src/lib/services/base.ts","../../../projects/angular-odata/src/lib/services/entity.ts","../../../projects/angular-odata/src/lib/services/entity-set.ts","../../../projects/angular-odata/src/lib/services/singleton.ts","../../../projects/angular-odata/src/lib/services/factory.ts","../../../projects/angular-odata/src/lib/module.ts","../../../projects/angular-odata/src/public-api.ts","../../../projects/angular-odata/src/angular-odata.ts"],"sourcesContent":["import { Observable } from 'rxjs';\nimport { ODataMetadata } from './metadata';\n\nexport type ODataVersion = '2.0' | '3.0' | '4.0';\nexport type FetchPolicy =\n | 'cache-first'\n | 'cache-and-network'\n | 'network-only'\n | 'no-cache'\n | 'cache-only';\nexport type ODataMetadataType = 'minimal' | 'full' | 'none';\nexport type CacheCacheability = 'public' | 'private' | 'no-cache' | 'no-store';\n\nexport enum PathSegment {\n batch = 'batch',\n metadata = 'metadata',\n entitySet = 'entitySet',\n singleton = 'singleton',\n type = 'type',\n property = 'property',\n navigationProperty = 'navigationProperty',\n reference = 'reference',\n value = 'value',\n count = 'count',\n function = 'function',\n action = 'action',\n}\n\nexport enum QueryOption {\n select = 'select',\n expand = 'expand',\n compute = 'compute',\n apply = 'apply',\n filter = 'filter',\n search = 'search',\n transform = 'transform',\n orderBy = 'orderBy',\n top = 'top',\n skip = 'skip',\n skiptoken = 'skiptoken',\n format = 'format',\n levels = 'levels',\n count = 'count',\n}\nexport enum EdmType {\n //Edm.Guid 16-byte (128-bit) unique identifier\n Guid = 'Edm.Guid',\n //Edm.Int16 Signed 16-bit integer\n Int16 = 'Edm.Int16',\n //Edm.String Sequence of UTF-8 characters\n String = 'Edm.String',\n //Edm.Boolean Binary-valued logic\n Boolean = 'Edm.Boolean',\n //Edm.Byte Unsigned 8-bit integer\n Byte = 'Edm.Byte',\n //Edm.SByte Signed 8-bit integer\n SByte = 'Edm.SByte',\n //Edm.Int32 Signed 16-bit integer\n Int32 = 'Edm.Int32',\n //Edm.Int64 Signed 16-bit integer\n Int64 = 'Edm.Int64',\n //Edm.Date Date without a time-zone offset\n Date = 'Edm.Date',\n //Edm.TimeOfDay Clock time 00:00-23:59:59.999999999999\n TimeOfDay = 'Edm.TimeOfDay',\n //Edm.DateTimeOffset Date and time with a time-zone offset, no leap seconds\n DateTimeOffset = 'Edm.DateTimeOffset',\n //Edm.Duration Signed duration in days, hours, minutes, and (sub)seconds\n Duration = 'Edm.Duration',\n //Edm.Decimal Numeric values with fixed precision and scale\n Decimal = 'Edm.Decimal',\n //Edm.Double IEEE 754 binary64 floating-point number (15-17 decimal digits)\n Double = 'Edm.Double',\n //Edm.Single IEEE 754 binary32 floating-point number (6-9 decimal digits)\n Single = 'Edm.Single',\n //Edm.Binary Binary data\n Binary = 'Edm.Binary',\n //Edm.Stream Binary data stream\n Stream = 'Edm.Stream',\n //Edm.Geography Abstract base type for all Geography types\n Geography = 'Edm.Geography',\n //Edm.GeographyPoint A point in a round-earth coordinate system\n GeographyPoint = 'Edm.GeographyPoint',\n //Edm.GeographyLineString Line string in a round-earth coordinate system\n GeographyLineString = 'Edm.GeographyLineString',\n //Edm.GeographyPolygon Polygon in a round-earth coordinate system\n GeographyPolygon = 'Edm.GeographyPolygon',\n //Edm.GeographyMultiPoint Collection of points in a round-earth coordinate system\n GeographyMultiPoint = 'Edm.GeographyMultiPoint',\n //Edm.GeographyMultiLineString Collection of line strings in a round-earth coordinate system\n GeographyMultiLineString = 'Edm.GeographyMultiLineString',\n //Edm.GeographyMultiPolygon Collection of polygons in a round-earth coordinate system\n GeographyMultiPolygon = 'Edm.GeographyMultiPolygon',\n //Edm.GeographyCollection Collection of arbitrary Geography values\n GeographyCollection = 'Edm.GeographyCollection',\n //Edm.Geometry Abstract base type for all Geometry types\n Geometry = 'Edm.Geometry',\n //Edm.GeometryPoint Point in a flat-earth coordinate system\n GeometryPoint = 'Edm.GeometryPoint',\n //Edm.GeometryLineString Line string in a flat-earth coordinate system\n GeometryLineString = 'Edm.GeometryLineString',\n //Edm.GeometryPolygon Polygon in a flat-earth coordinate system\n GeometryPolygon = 'Edm.GeometryPolygon',\n //Edm.GeometryMultiPoint Collection of points in a flat-earth coordinate system\n GeometryMultiPoint = 'Edm.GeometryMultiPoint',\n //Edm.GeometryMultiLineString Collection of line strings in a flat-earth coordinate system\n GeometryMultiLineString = 'Edm.GeometryMultiLineString',\n //Edm.GeometryMultiPolygon Collection of polygons in a flat-earth coordinate system\n GeometryMultiPolygon = 'Edm.GeometryMultiPolygon',\n //Edm.GeometryCollection Collection of arbitrary Geometry values\n GeometryCollection = 'Edm.GeometryCollection',\n}\n\nexport enum JsonType {\n string = 'string',\n number = 'number',\n integer = 'integer',\n object = 'object',\n array = 'array',\n boolean = 'boolean',\n null = 'null',\n}\n\nexport interface ApiOptions {\n version?: ODataVersion;\n params?: { [param: string]: string | string[] };\n headers?: { [param: string]: string | string[] };\n withCredentials?: boolean;\n //Headers\n accept?: {\n exponentialDecimals?: boolean;\n metadata?: ODataMetadataType;\n ieee754Compatible?: boolean;\n streaming?: boolean;\n };\n etag?: {\n ifMatch?: boolean;\n ifNoneMatch?: boolean;\n };\n prefer?: {\n maxPageSize?: number;\n return?: 'representation' | 'minimal';\n continueOnError?: boolean;\n includeAnnotations?: string;\n };\n stripMetadata?: ODataMetadataType;\n fetchPolicy?: FetchPolicy;\n bodyQueryOptions?: QueryOption[];\n stringAsEnum?: boolean;\n //https://github.com/OData/WebApi/issues/1974\n //https://github.com/OData/WebApi/issues/1647\n deleteRefBy?: 'path' | 'id';\n //https://github.com/OData/AspNetCoreOData/issues/171\n nonParenthesisForEmptyParameterFunction?: boolean;\n jsonBatchFormat?: boolean;\n relativeUrls?: boolean;\n}\n\nexport interface ParserOptions {\n version?: ODataVersion;\n exponentialDecimals?: boolean;\n metadata?: ODataMetadataType;\n ieee754Compatible?: boolean;\n streaming?: boolean;\n stringAsEnum?: boolean;\n deleteRefBy?: 'path' | 'id';\n nonParenthesisForEmptyParameterFunction?: boolean;\n}\n\nexport interface ResponseOptions extends ParserOptions {\n cacheability?: CacheCacheability;\n maxAge?: number;\n}\n\nexport interface StructuredTypeFieldOptions extends ParserOptions {\n field: StructuredTypeFieldConfig;\n}\n\nexport interface Parser<T> {\n // Deserialize value/s from request body.\n deserialize(\n value: any,\n options?: ParserOptions | StructuredTypeFieldOptions,\n ): T;\n // Serialize value/s for request body.\n serialize(\n value: any,\n options?: ParserOptions | StructuredTypeFieldOptions,\n ): any;\n //Encode value/s for URL parameter or query-string.\n encode(value: any, options?: ParserOptions | StructuredTypeFieldOptions): any;\n}\n\nexport interface FieldParser<T> extends Parser<T> {\n nullable?: boolean;\n default?: any;\n maxLength?: number;\n precision?: number;\n scale?: number | 'variable';\n}\n\nexport const NONE_PARSER = {\n deserialize: (value: any) => value,\n serialize: (value: any) => value,\n encode: (value: any) => value,\n} as Parser<any>;\n\nexport interface Cache {\n put<T>(key: string, payload: T, ...opts: any[]): void;\n get<T>(key: string, ...opts: any[]): T | undefined;\n}\n\n//#region Configs\nexport type ApiConfig = {\n serviceRootUrl: string;\n metadataUrl?: string;\n name?: string;\n version?: ODataVersion;\n default?: boolean;\n creation?: Date;\n cache?: Cache;\n errorHandler?: (error: any, caught: Observable<any>) => Observable<never>;\n options?: ApiOptions;\n parsers?: { [type: string]: Parser<any> };\n schemas?: SchemaConfig[];\n references?: ReferenceConfig[];\n};\nexport type AnnotationConfig = {\n term: string;\n string?: string;\n bool?: boolean;\n int?: number;\n permissions?: string[];\n properties?: string[];\n};\nexport type ReferenceConfig = {\n uri: string;\n includes?: string;\n annotations?: AnnotationConfig[];\n enums?: EnumTypeConfig[];\n entities?: StructuredTypeConfig[];\n callables?: CallableConfig[];\n containers?: EntityContainerConfig[];\n};\nexport type SchemaConfig = {\n namespace: string;\n alias?: string;\n annotations?: AnnotationConfig[];\n enums?: EnumTypeConfig[];\n entities?: StructuredTypeConfig[];\n callables?: CallableConfig[];\n containers?: EntityContainerConfig[];\n};\n\nexport type EntityContainerConfig = {\n name: string;\n annotations?: AnnotationConfig[];\n entitySets?: EntitySetConfig[];\n singletons?: SingletonConfig[];\n};\n\nexport type EnumTypeFieldConfig = {\n value: number;\n annotations?: AnnotationConfig[];\n};\n\nexport type EnumTypeConfig = {\n name: string;\n flags?: boolean;\n annotations?: AnnotationConfig[];\n members: { [name: string]: number } | { [value: number]: string };\n fields: { [member: string]: EnumTypeFieldConfig };\n};\n\nexport type StructuredTypeFieldConfig = {\n type: string;\n default?: any;\n maxLength?: number;\n key?: boolean;\n collection?: boolean;\n nullable?: boolean;\n navigation?: boolean;\n precision?: number;\n annotations?: AnnotationConfig[];\n scale?: number | 'variable';\n referentials?: { property: string; referencedProperty: string }[];\n referential?: string;\n referenced?: string;\n};\n\nexport type StructuredTypeConfig = {\n name: string;\n base?: string;\n open?: boolean;\n model?: { new (...params: any[]): any };\n collection?: { new (...params: any[]): any };\n annotations?: AnnotationConfig[];\n keys?: { name: string; alias?: string }[];\n fields?: { [name: string]: StructuredTypeFieldConfig };\n};\n\nexport type ParameterConfig = {\n type: string;\n nullable?: boolean;\n collection?: boolean;\n};\n\nexport type CallableConfig = {\n name: string;\n entitySetPath?: string;\n bound?: boolean;\n composable?: boolean;\n parameters?: { [name: string]: ParameterConfig };\n return?: { type: string; collection?: boolean };\n};\n\nexport type EntitySetConfig = {\n name: string;\n entityType: string;\n service: { new (...params: any[]): any };\n annotations?: AnnotationConfig[];\n};\n\nexport type SingletonConfig = {\n name: string;\n type: string;\n service: { new (...params: any[]): any };\n annotations?: AnnotationConfig[];\n};\n//#endregion\n","export const $ID = '$id';\nexport const ODATA_ID = '@odata.id';\n\n// SEGMENTS\nexport const $METADATA = '$metadata';\nexport const $BATCH = '$batch';\nexport const $REF = '$ref';\nexport const $VALUE = '$value';\nexport const $COUNT = '$count';\nexport const $QUERY = '$query';\nexport const $INLINECOUNT = '$inlinecount';\n\n// HTTP HEADERS\nexport const IF_MATCH_HEADER = 'If-Match';\nexport const IF_NONE_MATCH_HEADER = 'If-None-Match';\nexport const CONTENT_TYPE = 'Content-Type';\nexport const HTTP11 = 'HTTP/1.1';\nexport const ACCEPT = 'Accept';\nexport const PREFER = 'Prefer';\nexport const CACHE_CONTROL = 'Cache-Control';\nexport const CACHE_CONTROL_HEADERS = [\n CACHE_CONTROL,\n CACHE_CONTROL.toLowerCase(),\n];\nexport const ODATA_VERSION = 'OData-Version';\nexport const ODATA_VERSION_HEADERS = [\n ODATA_VERSION,\n ODATA_VERSION.toLowerCase(),\n 'dataserviceversion',\n];\nexport const LOCATION_HEADER = 'Location';\nexport const LOCATION_HEADERS = [\n LOCATION_HEADER,\n LOCATION_HEADER.toLowerCase(),\n];\nexport const ODATA_ENTITYID = 'OData-EntityId';\nexport const ODATA_ENTITYID_HEADERS = [\n ODATA_ENTITYID,\n ODATA_ENTITYID.toLowerCase(),\n];\nexport const PREFERENCE_APPLIED = 'Preference-Applied';\nexport const PREFERENCE_APPLIED_HEADERS = [\n PREFERENCE_APPLIED,\n PREFERENCE_APPLIED.toLowerCase(),\n];\nexport const ETAG_HEADER = 'ETag';\nexport const ETAG_HEADERS = [ETAG_HEADER, ETAG_HEADER.toLowerCase()];\n\nexport const RETRY_AFTER = 'Retry-After';\nexport const RETRY_AFTER_HEADERS = [RETRY_AFTER, RETRY_AFTER.toLowerCase()];\n\n// HTTP HEADER VALUES\nexport const APPLICATION_JSON = 'application/json';\nexport const APPLICATION_HTTP = 'application/http';\nexport const APPLICATION_XHTML = 'application/xhtml+xml';\nexport const APPLICATION_XML = 'application/xml';\nexport const TEXT_PLAIN = 'text/plain';\nexport const CONTENT_TYPE_ANY = '*/*';\nexport const MULTIPART_MIXED = 'multipart/mixed';\nexport const MULTIPART_MIXED_BOUNDARY = 'multipart/mixed;boundary=';\nexport const CONTENT_TRANSFER_ENCODING = 'Content-Transfer-Encoding';\nexport const CONTENT_ID = 'Content-ID';\nexport const MAX_AGE = 'max-age';\n\n// VERSIONS\nexport const VERSION_4_0 = '4.0';\nexport const VERSION_3_0 = '3.0';\nexport const VERSION_2_0 = '2.0';\nexport const DEFAULT_VERSION = VERSION_4_0;\n\nexport const BINARY = 'binary';\nexport const BOUNDARY_PREFIX_SUFFIX = '--';\nexport const BATCH_PREFIX = 'batch_';\nexport const CHANGESET_PREFIX = 'changeset_';\nexport const DEFAULT_METADATA = 'minimal';\nexport const DEFAULT_STRIP_METADATA = 'full';\nexport const DEFAULT_FETCH_POLICY = 'network-only';\nexport const DEFAULT_TIMEOUT = 60; // Time in seconds\nexport const CALLABLE_BINDING_PARAMETER = 'bindingParameter';\nexport const XSSI_PREFIX = /^\\)\\]\\}',?\\n/;\n\n// URL PARTS\nexport const QUERY_SEPARATOR = '?';\nexport const PARAM_SEPARATOR = '&';\nexport const VALUE_SEPARATOR = '=';\nexport const PATH_SEPARATOR = '/';\nexport const ODATA_PARAM_PREFIX = '$';\nexport const ODATA_ALIAS_PREFIX = '@';\n\nexport const NEWLINE = '\\r\\n';\nexport const NEWLINE_REGEXP = /\\r?\\n/;\nexport const CACHE_KEY_SEPARATOR = ':';\n\n// Models\nexport const CID_FIELD_NAME = '_cid';\nexport const EVENT_SPLITTER = /\\s+/;\n\n// Standard vocabularies for annotating OData services\n// https://github.com/oasis-tcs/odata-vocabularies/blob/main/vocabularies/Org.OData.Core.V1.md\n\nexport const COMPUTED = /.*Computed$/;\nexport const OPTIMISTIC_CONCURRENCY = /.*OptimisticConcurrency$/;\nexport const DESCRIPTION = /.*Description$/;\nexport const LONG_DESCRIPTION = /.*LongDescription$/;\nexport const OPTIONARL_PARAMETER = /.*OptionalParameter$/;\n","import { Observable, of, throwError } from 'rxjs';\nimport { startWith, tap } from 'rxjs/operators';\nimport { CACHE_KEY_SEPARATOR, DEFAULT_TIMEOUT } from '../constants';\nimport { ODataBatchResource, ODataRequest, ODataResponse } from '../resources';\nimport { Cache, PathSegment } from '../types';\n\n/**\n * A cache entry that holds a payload, a last read time, and a timeout for the entry.\n * @param payload The payload to cache.\n * @param lastRead The last read time.\n * @param timeout The timeout.\n * @param tags Some tags to identify the entry.\n */\nexport interface ODataCacheEntry<T> {\n payload: T;\n lastRead: number;\n timeout: number;\n tags: string[];\n}\n\nexport abstract class ODataCache implements Cache {\n timeout: number;\n entries: Map<string, ODataCacheEntry<any>>;\n\n constructor({ timeout = DEFAULT_TIMEOUT }: { timeout?: number }) {\n this.timeout = timeout;\n this.entries = new Map<string, ODataCacheEntry<any>>();\n }\n\n abstract getResponse(req: ODataRequest<any>): ODataResponse<any> | undefined;\n abstract putResponse(req: ODataRequest<any>, res: ODataResponse<any>): void;\n\n /**\n * Using the resource on the request build an array of string to identify the scope of the request\n * @param req The request with the resource to build the scope\n * @returns Array of string to identify the scope of the request\n */\n scope(req: ODataRequest<any>): string[] {\n const segments = req.resource.cloneSegments();\n return segments.segments({ key: true }).reduce(\n (acc, s) => {\n if (s.name === PathSegment.entitySet)\n acc = [...acc, s.path() as string];\n return acc;\n },\n ['request'],\n );\n }\n\n /**\n * Using the odata context on the response build an array of string to identify the tags of the response\n * @param res The response to build the tags\n * @returns Array of string to identify the tags of the response\n */\n tags(res: ODataResponse<any>): string[] {\n const tags = [];\n const context = res.context;\n if (context.entitySet) {\n tags.push(\n context.key\n ? `${context.entitySet}(${context.key})`\n : context.entitySet,\n );\n }\n if (context.type) tags.push(context.type);\n return tags;\n }\n\n /**\n * Build an entry from a payload and some options\n * @param payload The payload to store in the cache\n * @param timeout The timeout for the entry\n * @param tags The tags for the entry\n * @returns The entry to store in the cache\n */\n buildEntry<T>(\n payload: T,\n { timeout, tags }: { timeout?: number; tags?: string[] },\n ): ODataCacheEntry<T> {\n return {\n payload,\n lastRead: Date.now(),\n timeout: timeout || this.timeout,\n tags: tags || [],\n };\n }\n\n /**\n * Build a key from store an entry in the cache\n * @param names The names of the entry\n * @returns The key for the entry\n */\n buildKey(names: string[]): string {\n return names.join(CACHE_KEY_SEPARATOR);\n }\n\n /**\n * Put some payload in the cache\n * @param name The name for the entry\n * @param payload The payload to store in the cache\n * @param timeout The timeout for the entry\n * @param scope The scope for the entry\n * @param tags The tags for the entry\n */\n put<T>(\n name: string,\n payload: T,\n {\n timeout,\n scope,\n tags,\n }: { timeout?: number; scope?: string[]; tags?: string[] } = {},\n ) {\n const entry = this.buildEntry<T>(payload, { timeout, tags });\n const key = this.buildKey([...(scope || []), name]);\n this.entries.set(key, entry);\n this.forget();\n }\n\n /**\n * Return the payload from the cache if it exists and is not expired\n * @param name The name of the entry\n * @param scope The scope of the entry\n * @returns The payload of the entry\n */\n get<T>(name: string, { scope }: { scope?: string[] } = {}): T {\n const key = this.buildKey([...(scope || []), name]);\n const entry = this.entries.get(key);\n return entry !== undefined && !this.isExpired(entry)\n ? entry.payload\n : undefined;\n }\n\n /**\n * Remove all cache entries that are matching with the given options\n * @param options The options to forget\n */\n forget({\n name,\n scope = [],\n tags = [],\n }: { name?: string; scope?: string[]; tags?: string[] } = {}) {\n if (name !== undefined) scope.push(name);\n const key = scope.length > 0 ? this.buildKey(scope) : undefined;\n this.entries.forEach((entry, k) => {\n if (\n this.isExpired(entry) || // Expired\n (key !== undefined && k.startsWith(key)) || // Key\n (tags.length > 0 && tags.some((t) => entry.tags.indexOf(t) !== -1)) // Tags\n ) {\n this.entries.delete(k);\n }\n });\n }\n\n /**\n * Remove all cache entries\n */\n flush() {\n this.entries = new Map<string, ODataCacheEntry<any>>();\n }\n\n /**\n * Check if the entry is expired\n * @param entry The cache entry\n * @returns Boolean indicating if the entry is expired\n */\n isExpired(entry: ODataCacheEntry<any>) {\n return entry.lastRead < Date.now() - (entry.timeout || this.timeout) * 1000;\n }\n\n /**\n * Using the request, handle the fetching of the response\n * @param req The request to fetch\n * @param res$ Observable of the response\n * @returns\n */\n handleRequest(\n req: ODataRequest<any>,\n res$: Observable<ODataResponse<any>>,\n ): Observable<ODataResponse<any>> {\n return req.isFetch()\n ? this.handleFetch(req, res$)\n : req.isMutate()\n ? this.handleMutate(req, res$)\n : res$;\n }\n\n private handleFetch(\n req: ODataRequest<any>,\n res$: Observable<ODataResponse<any>>,\n ): Observable<ODataResponse<any>> {\n const policy = req.fetchPolicy;\n const cached = this.getResponse(req);\n if (policy === 'no-cache') {\n return res$;\n }\n if (policy === 'cache-only') {\n if (cached) {\n return of(cached);\n } else {\n return throwError(() => new Error('No Cached'));\n }\n }\n if (\n policy === 'cache-first' ||\n policy === 'cache-and-network' ||\n policy === 'network-only'\n ) {\n res$ = res$.pipe(\n tap((res: ODataResponse<any>) => {\n if (res.options.cacheability !== 'no-store')\n this.putResponse(req, res);\n }),\n );\n }\n return cached !== undefined && policy !== 'network-only'\n ? policy === 'cache-and-network'\n ? res$.pipe(startWith(cached))\n : of(cached)\n : res$;\n }\n\n private handleMutate(\n req: ODataRequest<any>,\n res$: Observable<ODataResponse<any>>,\n ): Observable<ODataResponse<any>> {\n const requests = req.isBatch()\n ? (req.resource as ODataBatchResource)\n .requests()\n .filter((r) => r.isMutate())\n : [req];\n for (var r of requests) {\n const scope = this.scope(r);\n this.forget({ scope });\n }\n return res$;\n }\n}\n","import { ODataRequest, ODataResponse } from '../resources';\nimport { ODataCache } from './cache';\n\nexport class ODataInMemoryCache extends ODataCache {\n constructor({ timeout }: { timeout?: number } = {}) {\n super({ timeout });\n }\n\n /**\n * Store the response in the cache\n * @param req The request with the resource to store the response\n * @param res The response to store in the cache\n */\n putResponse(req: ODataRequest<any>, res: ODataResponse<any>) {\n let scope = this.scope(req);\n let tags = this.tags(res);\n this.put(req.cacheKey, res, {\n timeout: res.options.maxAge,\n scope,\n tags,\n });\n }\n\n /**\n * Restore the response from the cache\n * @param req The request with the resource to get the response\n * @returns The response from the cache\n */\n getResponse(req: ODataRequest<any>): ODataResponse<any> | undefined {\n let scope = this.scope(req);\n return this.get(req.cacheKey, { scope });\n }\n}\n","const COMPARISON_OPERATORS = ['eq', 'ne', 'gt', 'ge', 'lt', 'le'];\nconst LOGICAL_OPERATORS = ['and', 'or', 'not'];\nconst COLLECTION_OPERATORS = ['any', 'all'];\nconst BOOLEAN_FUNCTIONS = ['startswith', 'endswith', 'contains'];\nconst SUPPORTED_EXPAND_PROPERTIES = [\n 'expand',\n 'levels',\n 'select',\n 'top',\n 'count',\n 'orderby',\n 'filter',\n];\n\nconst FUNCTION_REGEX = /\\((.*)\\)/;\nconst INDEXOF_REGEX = /(?!indexof)\\((\\w+)\\)/;\n\nexport type Unpacked<T> = T extends (infer U)[] ? U : T;\nexport type Select<T> = SelectType<T> | SelectType<T>[];\nexport type SelectType<T> = string | keyof T;\nexport type Filter<T> = FilterType | FilterType[];\nexport type FilterType = string | { [name: string]: any };\n\nexport enum StandardAggregateMethods {\n sum = 'sum',\n min = 'min',\n max = 'max',\n average = 'average',\n countdistinct = 'countdistinct',\n}\nexport type AggregateType =\n | string\n | { [propertyName: string]: { with: StandardAggregateMethods; as: string } };\n\n// OrderBy\n\nexport type OrderBy<T> = OrderByType<T> | OrderByType<T>[];\nexport type OrderByType<T> = string | OrderByObject<T>;\nexport type OrderByObject<T> =\n | keyof T\n | [keyof T | string, 'asc' | 'desc']\n | NestedOrderBy<T>;\nexport type NestedOrderBy<T> = {\n [P in keyof T]?: T[P] extends Array<infer E> ? OrderBy<E> : OrderBy<T[P]>;\n};\n\n// Expand\nexport type Expand<T> = ExpandType<T> | ExpandType<T>[];\nexport type ExpandType<T> = string | ExpandObject<T>;\nexport type ExpandObject<T> = keyof T | NestedExpandOptions<T>;\nexport type NestedExpandOptions<T> = {\n [P in keyof T]?: ExpandOptions<Unpacked<T[P]>>;\n};\nexport type ExpandOptions<T> = {\n select?: Select<T>;\n filter?: Filter<T>;\n orderBy?: OrderBy<T>;\n top?: number;\n skip?: number;\n levels?: number | 'max';\n count?: boolean | Filter<T>;\n expand?: Expand<T>;\n};\n\nexport type Transform<T> = {\n aggregate?: AggregateType | Array<AggregateType>;\n filter?: Filter<T>;\n groupBy?: GroupByType<T>;\n};\nexport type GroupByType<T> = {\n properties: Array<keyof T>;\n transform?: Transform<T>;\n};\n\nexport enum QueryCustomTypes {\n Raw,\n Alias,\n Duration,\n Binary,\n}\n\nexport type QueryCustomType = {\n type: QueryCustomTypes;\n value: any;\n name?: string;\n};\nexport type Value = string | Date | number | boolean | QueryCustomType;\n\n//https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_QueryOptions\nexport const raw = (value: string): QueryCustomType => ({\n type: QueryCustomTypes.Raw,\n value,\n});\nexport const alias = (value: any, name?: string): QueryCustomType => ({\n type: QueryCustomTypes.Alias,\n value,\n name,\n});\nexport const duration = (value: string): QueryCustomType => ({\n type: QueryCustomTypes.Duration,\n value,\n});\nexport const binary = (value: string): QueryCustomType => ({\n type: QueryCustomTypes.Binary,\n value,\n});\nexport const isQueryCustomType = (value: any) =>\n typeof value === 'object' &&\n 'type' in value &&\n value.type in QueryCustomTypes;\n\nexport const isRawType = (value: any) =>\n isQueryCustomType(value) &&\n (value as QueryCustomType).type === QueryCustomTypes.Raw;\n\nexport type QueryOptions<T> = ExpandOptions<T> & {\n search: string;\n apply: string;\n transform: { [name: string]: any } | { [name: string]: any }[];\n compute: string;\n skip: number;\n skiptoken: string;\n key: string | number | { [name: string]: any };\n count: boolean | Filter<T>;\n action: string;\n func: string | { [functionName: string]: { [parameterName: string]: any } };\n format: string;\n aliases: QueryCustomType[];\n escape: boolean;\n};\n\nexport const ITEM_ROOT = '';\n\nexport default function <T>({\n select,\n search,\n skiptoken,\n format,\n top,\n skip,\n filter,\n transform,\n compute,\n orderBy,\n key,\n count,\n expand,\n action,\n func,\n aliases,\n escape,\n}: Partial<QueryOptions<T>> = {}) {\n const [path, params] = buildPathAndQuery({\n select,\n search,\n skiptoken,\n format,\n top,\n skip,\n filter,\n transform,\n compute,\n orderBy,\n key,\n count,\n expand,\n action,\n func,\n aliases,\n escape,\n });\n\n return buildUrl(path, params);\n}\n\nexport function buildPathAndQuery<T>({\n select,\n search,\n skiptoken,\n format,\n top,\n skip,\n filter,\n apply,\n transform,\n compute,\n orderBy,\n key,\n count,\n expand,\n action,\n func,\n aliases,\n escape,\n}: Partial<QueryOptions<T>> = {}): [string, { [name: string]: any }] {\n let path: string = '';\n aliases = aliases || [];\n\n const query: any = {};\n\n // key is not (null, undefined)\n if (key != undefined) {\n path += `(${normalizeValue(key as Value, { aliases, escape })})`;\n }\n\n // Select\n if (select) {\n query.$select = isRawType(select)\n ? (select as unknown as QueryCustomType).value\n : Array.isArray(select)\n ? select.join(',')\n : select;\n }\n\n // Compute\n if (compute) {\n query.$compute = isRawType(compute)\n ? (compute as unknown as QueryCustomType).value\n : Array.isArray(compute)\n ? compute.join(',')\n : compute;\n }\n\n // Search\n if (search) {\n query.$search = search;\n }\n\n // Skiptoken\n if (skiptoken) {\n query.$skiptoken = skiptoken;\n }\n\n // Format\n if (format) {\n query.$format = format;\n }\n\n // Filter\n if (filter || typeof count === 'object') {\n query.$filter = buildFilter(typeof count === 'object' ? count : filter, {\n aliases,\n escape,\n });\n }\n\n // Transform\n if (transform) {\n query.$apply = buildTransforms(transform, { aliases, escape });\n }\n\n // Apply\n if (apply) {\n query.$apply = query.$apply\n ? query.$apply + '/' + buildApply(apply, { aliases, escape })\n : buildApply(apply, { aliases, escape });\n }\n\n // Expand\n if (expand) {\n query.$expand = buildExpand(expand, { aliases, escape });\n }\n\n // OrderBy\n if (orderBy) {\n query.$orderby = buildOrderBy(orderBy);\n }\n\n // Count\n if (isRawType(count)) {\n query.$count = (count as QueryCustomType).value;\n } else if (typeof count === 'boolean') {\n query.$count = true;\n } else if (count) {\n path += '/$count';\n }\n\n // Top\n if (isRawType(top)) {\n query.$top = (top as unknown as QueryCustomType).value;\n } else if (typeof top === 'number') {\n query.$top = top;\n }\n\n // Skip\n if (isRawType(skip)) {\n query.$top = (skip as unknown as QueryCustomType).value;\n } else if (typeof skip === 'number') {\n query.$skip = skip;\n }\n\n if (action) {\n path += `/${action}`;\n }\n\n if (func) {\n if (typeof func === 'string') {\n path += `/${func}()`;\n } else if (typeof func === 'object') {\n const [funcName] = Object.keys(func);\n const funcArgs = normalizeValue(func[funcName] as Value, {\n aliases,\n escape,\n });\n\n path += `/${funcName}(${funcArgs})`;\n }\n }\n\n if (aliases.length > 0) {\n Object.assign(\n query,\n aliases.reduce(\n (acc, alias) =>\n Object.assign(acc, {\n [`@${alias.name}`]: normalizeValue(alias.value, {\n escape,\n }),\n }),\n {},\n ),\n );\n }\n\n // Filter empty values\n const params = Object.entries(query)\n .filter(([, value]) => value !== undefined && value !== '')\n .reduce((acc, [key, value]) => Object.assign(acc, { [key]: value }), {});\n\n return [path, params];\n}\n\nfunction renderPrimitiveValue(\n key: string,\n val: any,\n {\n aliases,\n escape,\n }: {\n aliases?: QueryCustomType[];\n escape?: boolean;\n },\n) {\n return `${key} eq ${normalizeValue(val, { aliases, escape })}`;\n}\n\nfunction buildFilter(\n filters: Filter<any> = {},\n {\n aliases,\n propPrefix,\n escape,\n }: { aliases?: QueryCustomType[]; propPrefix?: string; escape?: boolean },\n): string {\n return (\n (Array.isArray(filters) ? filters : [filters]).reduce(\n (acc: string[], filter) => {\n if (filter) {\n const builtFilter = buildFilterCore(filter, {\n aliases,\n propPrefix,\n escape,\n });\n if (builtFilter) {\n acc.push(builtFilter);\n }\n }\n return acc;\n },\n [],\n ) as string[]\n ).join(' and ');\n\n function buildFilterCore(\n filter: Filter<any> = {},\n {\n aliases,\n propPrefix,\n escape,\n }: { aliases?: QueryCustomType[]; propPrefix?: string; escape?: boolean },\n ) {\n let filterExpr = '';\n if (isRawType(filter)) {\n // Use raw query custom filter string\n filterExpr = (filter as QueryCustomType).value;\n } else if (typeof filter === 'string') {\n // Use raw filter string\n filterExpr = filter;\n } else if (filter && typeof filter === 'object') {\n const filtersArray = Object.keys(filter).reduce(\n (result: any[], filterKey) => {\n const value = (filter as any)[filterKey];\n let propName = '';\n if (propPrefix) {\n if (filterKey === ITEM_ROOT) {\n propName = propPrefix;\n } else if (INDEXOF_REGEX.test(filterKey)) {\n propName = filterKey.replace(INDEXOF_REGEX, (_, $1) =>\n $1.trim() === ITEM_ROOT\n ? `(${propPrefix})`\n : `(${propPrefix}/${$1.trim()})`,\n );\n } else if (FUNCTION_REGEX.test(filterKey)) {\n propName = filterKey.replace(FUNCTION_REGEX, (_, $1) =>\n $1.trim() === ITEM_ROOT\n ? `(${propPrefix})`\n : `(${propPrefix}/${$1.trim()})`,\n );\n } else {\n propName = `${propPrefix}/${filterKey}`;\n }\n } else {\n propName = filterKey;\n }\n\n if (filterKey === ITEM_ROOT && Array.isArray(value)) {\n return result.concat(\n value.map((arrayValue: any) =>\n renderPrimitiveValue(propName, arrayValue, { escape, aliases }),\n ),\n );\n }\n\n if (\n ['number', 'string', 'boolean'].indexOf(typeof value) !== -1 ||\n value instanceof Date ||\n value === null\n ) {\n // Simple key/value handled as equals operator\n result.push(\n renderPrimitiveValue(propName, value, { aliases, escape }),\n );\n } else if (Array.isArray(value)) {\n const op = filterKey;\n const builtFilters = value\n .map((v) => buildFilter(v, { aliases, propPrefix, escape }))\n .filter((f) => f)\n .map((f) =>\n LOGICAL_OPERATORS.indexOf(op) !== -1 ? `(${f})` : f,\n );\n if (builtFilters.length) {\n if (LOGICAL_OPERATORS.indexOf(op) !== -1) {\n if (builtFilters.length) {\n if (op === 'not') {\n result.push(parseNot(builtFilters as string[]));\n } else {\n result.push(`(${builtFilters.join(` ${op} `)})`);\n }\n }\n } else {\n result.push(builtFilters.join(` ${op} `));\n }\n }\n } else if (LOGICAL_OPERATORS.indexOf(propName) !== -1) {\n const op = propName;\n const builtFilters = Object.keys(value).map((valueKey) =>\n buildFilterCore(\n { [valueKey]: value[valueKey] },\n { aliases, escape },\n ),\n );\n if (builtFilters.length) {\n if (op === 'not') {\n result.push(parseNot(builtFilters as string[]));\n } else {\n result.push(`${builtFilters.join(` ${op} `)}`);\n }\n }\n } else if (typeof value === 'object') {\n if ('type' in value) {\n result.push(\n renderPrimitiveValue(propName, value, { aliases, escape }),\n );\n } else {\n const operators = Object.keys(value);\n operators.forEach((op) => {\n if (COMPARISON_OPERATORS.indexOf(op) !== -1) {\n result.push(\n `${propName} ${op} ${normalizeValue(value[op], {\n aliases,\n escape,\n })}`,\n );\n } else if (LOGICAL_OPERATORS.indexOf(op) !== -1) {\n if (Array.isArray(value[op])) {\n result.push(\n value[op]\n .map(\n (v: any) =>\n '(' +\n buildFilterCore(v, {\n aliases,\n propPrefix: propName,\n escape,\n }) +\n ')',\n )\n .join(` ${op} `),\n );\n } else {\n result.push(\n '(' +\n buildFilterCore(value[op], {\n aliases,\n propPrefix: propName,\n escape,\n }) +\n ')',\n );\n }\n } else if (COLLECTION_OPERATORS.indexOf(op) !== -1) {\n const collectionClause = buildCollectionClause(\n filterKey.toLowerCase(),\n value[op],\n op,\n propName,\n );\n if (collectionClause) {\n result.push(collectionClause);\n }\n } else if (op === 'has') {\n result.push(\n `${propName} ${op} ${normalizeValue(value[op], {\n aliases,\n escape,\n })}`,\n );\n } else if (op === 'in') {\n const resultingValues = Array.isArray(value[op])\n ? value[op]\n : value[op].value.map((typedValue: any) => ({\n type: value[op].type,\n value: typedValue,\n }));\n\n result.push(\n propName +\n ' in (' +\n resultingValues\n .map((v: any) => normalizeValue(v, { aliases, escape }))\n .join(',') +\n ')',\n );\n } else if (BOOLEAN_FUNCTIONS.indexOf(op) !== -1) {\n // Simple boolean functions (startswith, endswith, contains)\n result.push(\n `${op}(${propName},${normalizeValue(value[op], {\n aliases,\n escape,\n })})`,\n );\n } else {\n // Nested property\n const filter = buildFilterCore(value, {\n aliases,\n propPrefix: propName,\n escape,\n });\n if (filter) {\n result.push(filter);\n }\n }\n });\n }\n } else if (value === undefined) {\n // Ignore/omit filter if value is `undefined`\n } else {\n throw new Error(`Unexpected value type: ${value}`);\n }\n\n return result;\n },\n [],\n );\n\n filterExpr = filtersArray.join(' and ');\n } /* else {\n throw new Error(`Unexpected filters type: ${filter}`);\n } */\n return filterExpr;\n }\n\n function buildCollectionClause(\n lambdaParameter: string,\n value: any,\n op: string,\n propName: string,\n ) {\n let clause = '';\n\n if (typeof value === 'string' || value instanceof String) {\n clause = getStringCollectionClause(lambdaParameter, value, op, propName);\n } else if (value) {\n // normalize {any:[{prop1: 1}, {prop2: 1}]} --> {any:{prop1: 1, prop2: 1}}; same for 'all',\n // simple values collection: {any:[{'': 'simpleVal1'}, {'': 'simpleVal2'}]} --> {any:{'': ['simpleVal1', 'simpleVal2']}}; same for 'all',\n const filterValue = Array.isArray(value)\n ? value.reduce((acc, item) => {\n if (item.hasOwnProperty(ITEM_ROOT)) {\n if (!acc.hasOwnProperty(ITEM_ROOT)) {\n acc[ITEM_ROOT] = [];\n }\n acc[ITEM_ROOT].push(item[ITEM_ROOT]);\n return acc;\n }\n return { ...acc, ...item };\n }, {})\n : value;\n\n const filter = buildFilterCore(filterValue, {\n aliases,\n propPrefix: lambdaParameter,\n escape,\n });\n clause = `${propName}/${op}(${\n filter ? `${lambdaParameter}:${filter}` : ''\n })`;\n }\n return clause;\n }\n}\n\nfunction getStringCollectionClause(\n lambdaParameter: string,\n value: any,\n collectionOperator: string,\n propName: string,\n) {\n let clause = '';\n const conditionOperator = collectionOperator == 'all' ? 'ne' : 'eq';\n clause = `${propName}/${collectionOperator}(${lambdaParameter}: ${lambdaParameter} ${conditionOperator} '${value}')`;\n\n return clause;\n}\n\nfunction escapeIllegalChars(string: string) {\n string = string.replace(/%/g, '%25');\n string = string.replace(/\\+/g, '%2B');\n string = string.replace(/\\//g, '%2F');\n string = string.replace(/\\?/g, '%3F');\n string = string.replace(/#/g, '%23');\n string = string.replace(/&/g, '%26');\n string = string.replace(/'/g, \"''\");\n return string;\n}\n\nexport function normalizeValue(\n value: Value,\n {\n aliases,\n escape = false,\n }: { aliases?: QueryCustomType[]; escape?: boolean } = {},\n): any {\n if (typeof value === 'string') {\n return escape ? `'${escapeIllegalChars(value)}'` : `'${value}'`;\n } else if (value instanceof Date) {\n return value.toISOString();\n } else if (typeof value === 'number') {\n return value;\n } else if (Array.isArray(value)) {\n return `[${value\n .map((d) => normalizeValue(d, { aliases, escape }))\n .join(',')}]`;\n } else if (value === null) {\n return value;\n } else if (typeof value === 'object') {\n switch (value.type) {\n case QueryCustomTypes.Raw:\n return value.value;\n case QueryCustomTypes.Duration:\n return `duration'${value.value}'`;\n case QueryCustomTypes.Binary:\n return `binary'${value.value}'`;\n case QueryCustomTypes.Alias:\n // Store\n if (Array.isArray(aliases)) {\n if (value.name === undefined) {\n value.name = `a${aliases.length + 1}`;\n }\n aliases.push(value);\n }\n return `@${value.name}`;\n default:\n return Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(\n ([k, v]) =>\n `${k}=${normalizeValue(v as Value, { aliases, escape })}`,\n )\n .join(',');\n }\n }\n return value;\n}\n\nfunction buildExpand<T>(\n expands: Expand<T>,\n {\n aliases,\n escape = false,\n }: { aliases?: QueryCustomType[]; escape?: boolean },\n): string {\n if (isRawType(expands)) {\n return (expands as QueryCustomType).value;\n } else if (typeof expands === 'number') {\n return expands as any;\n } else if (typeof expands === 'string') {\n if (expands.indexOf('/') === -1) {\n return expands;\n }\n\n // Change `Foo/Bar/Baz` to `Foo($expand=Bar($expand=Baz))`\n return expands\n .split('/')\n .reverse()\n .reduce((results, item, index, arr) => {\n if (index === 0) {\n // Inner-most item\n return `$expand=${item}`;\n } else if (index === arr.length - 1) {\n // Outer-most item, don't add `$expand=` prefix (added above)\n return `${item}(${results})`;\n } else {\n // Other items\n return `$expand=${item}(${results})`;\n }\n }, '');\n } else if (Array.isArray(expands)) {\n return `${(expands as Array<NestedExpandOptions<any>>)\n .map((e) => buildExpand(e, { aliases, escape }))\n .join(',')}`;\n } else if (typeof expands === 'object') {\n const expandKeys = Object.keys(expands);\n\n if (\n expandKeys.some(\n (key) => SUPPORTED_EXPAND_PROPERTIES.indexOf(key.toLowerCase()) !== -1,\n )\n ) {\n return expandKeys\n .map((key) => {\n let value;\n switch (key) {\n case 'filter':\n value = buildFilter((expands as NestedExpandOptions<any>)[key], {\n aliases,\n escape,\n });\n break;\n case 'orderBy':\n value = buildOrderBy(\n (expands as NestedExpandOptions<any>)[key] as OrderBy<T>,\n );\n break;\n case 'levels':\n case 'count':\n case 'top':\n case 'skip':\n value = `${(expands as NestedExpandOptions<any>)[key]}`;\n if (isRawType(value))\n value = (value as unknown as QueryCustomType).value;\n break;\n default:\n value = buildExpand(\n (expands