angular-odata 0.162.0 → 0.164.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/angular-odata.mjs +895 -380
- package/fesm2022/angular-odata.mjs.map +1 -1
- package/package.json +1 -1
- package/schematics/apigen/angular/base.js +4 -4
- package/schematics/apigen/angular/base.js.map +1 -1
- package/schematics/metadata/parser/csdl/csdl-annotation.d.ts +7 -3
- package/schematics/metadata/parser/csdl/csdl-annotation.js +11 -3
- package/schematics/metadata/parser/csdl/csdl-annotation.js.map +1 -1
- package/schematics/metadata/parser/csdl/csdl-function-action.d.ts +2 -13
- package/schematics/metadata/parser/csdl/csdl-function-action.js +35 -8
- package/schematics/metadata/parser/csdl/csdl-function-action.js.map +1 -1
- package/schematics/metadata/parser/csdl/csdl-structural-property.js +1 -1
- package/schematics/metadata/parser/csdl/csdl-structural-property.js.map +1 -1
- package/schematics/metadata/parser/index.js +2 -2
- package/schematics/metadata/parser/index.js.map +1 -1
- package/types/angular-odata.d.ts +139 -108
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpHeaders, HttpParams, HttpResponse, HttpErrorResponse, HttpEventType, provideHttpClient, HttpClient } from '@angular/common/http';
|
|
2
|
-
import { Subject,
|
|
3
|
-
import { map,
|
|
2
|
+
import { Subject, defer, of, from, ReplaySubject, Subscription, race, EMPTY, throwError, Observable, forkJoin, NEVER, firstValueFrom, map as map$1 } from 'rxjs';
|
|
3
|
+
import { map, tap, catchError, switchMap, finalize, expand, reduce, defaultIfEmpty, startWith } from 'rxjs/operators';
|
|
4
4
|
import * as i0 from '@angular/core';
|
|
5
5
|
import { EventEmitter, Injectable, InjectionToken, provideAppInitializer, inject, makeEnvironmentProviders, NgModule } from '@angular/core';
|
|
6
6
|
import { CommonModule } from '@angular/common';
|
|
@@ -195,6 +195,7 @@ const ODATA_ALIAS_PREFIX = '@';
|
|
|
195
195
|
const NEWLINE = '\r\n';
|
|
196
196
|
const NEWLINE_REGEXP = /\r?\n/;
|
|
197
197
|
const CACHE_KEY_SEPARATOR = ':';
|
|
198
|
+
const CACHE_REPRESENTATION_HEADERS = ['accept', 'accept-language', 'prefer'];
|
|
198
199
|
// Models
|
|
199
200
|
const CID_FIELD_NAME = '_cid';
|
|
200
201
|
const EVENT_SPLITTER = /\s+/;
|
|
@@ -5928,6 +5929,8 @@ class ODataRequest {
|
|
|
5928
5929
|
withCredentials;
|
|
5929
5930
|
bodyQueryOptions;
|
|
5930
5931
|
fetchPolicy;
|
|
5932
|
+
ignoreCacheControl;
|
|
5933
|
+
cacheInvalidation;
|
|
5931
5934
|
maxAge;
|
|
5932
5935
|
resource;
|
|
5933
5936
|
_responseType;
|
|
@@ -5954,6 +5957,8 @@ class ODataRequest {
|
|
|
5954
5957
|
this._body = this.resource.serialize(this._body, parserOptions);
|
|
5955
5958
|
this.withCredentials = init.withCredentials ?? apiOptions.withCredentials;
|
|
5956
5959
|
this.fetchPolicy = init.fetchPolicy ?? apiOptions.fetchPolicy;
|
|
5960
|
+
this.ignoreCacheControl = init.ignoreCacheControl ?? apiOptions.ignoreCacheControl;
|
|
5961
|
+
this.cacheInvalidation = init.cacheInvalidation ?? apiOptions.cacheInvalidation;
|
|
5957
5962
|
this.maxAge = init.maxAge;
|
|
5958
5963
|
this.bodyQueryOptions = [
|
|
5959
5964
|
...(apiOptions.bodyQueryOptions ?? []),
|
|
@@ -6052,6 +6057,8 @@ class ODataRequest {
|
|
|
6052
6057
|
reportProgress: options.reportProgress,
|
|
6053
6058
|
responseType: options.responseType,
|
|
6054
6059
|
fetchPolicy: options.fetchPolicy,
|
|
6060
|
+
ignoreCacheControl: options.ignoreCacheControl,
|
|
6061
|
+
cacheInvalidation: options.cacheInvalidation,
|
|
6055
6062
|
maxAge: options.maxAge,
|
|
6056
6063
|
parserOptions: options.parserOptions,
|
|
6057
6064
|
withCredentials: options.withCredentials,
|
|
@@ -6096,8 +6103,29 @@ class ODataRequest {
|
|
|
6096
6103
|
get urlWithParams() {
|
|
6097
6104
|
return `${this.api.serviceRootUrl}${this.pathWithParams}`;
|
|
6098
6105
|
}
|
|
6106
|
+
/** A versioned response identity that includes the API and transport representation. */
|
|
6099
6107
|
get cacheKey() {
|
|
6100
|
-
return
|
|
6108
|
+
return [
|
|
6109
|
+
'v2',
|
|
6110
|
+
this.api.serviceRootUrl,
|
|
6111
|
+
JSON.stringify(this.api.name ?? null),
|
|
6112
|
+
this._path,
|
|
6113
|
+
this._params.toString(),
|
|
6114
|
+
this.responseType ?? 'json',
|
|
6115
|
+
String(this.withCredentials ?? false),
|
|
6116
|
+
String(this.ignoreCacheControl),
|
|
6117
|
+
...CACHE_REPRESENTATION_HEADERS.map((name) => JSON.stringify(this.headers.getAll(name))),
|
|
6118
|
+
]
|
|
6119
|
+
.map((part) => part.replace(/%/g, '%25').replace(/\|/g, '%7C'))
|
|
6120
|
+
.join('|');
|
|
6121
|
+
}
|
|
6122
|
+
/** The versioned response-cache partition belonging to this API. */
|
|
6123
|
+
get cacheScope() {
|
|
6124
|
+
return [
|
|
6125
|
+
'request',
|
|
6126
|
+
'v2',
|
|
6127
|
+
encodeURIComponent(JSON.stringify([this.api.serviceRootUrl, this.api.name ?? null])),
|
|
6128
|
+
];
|
|
6101
6129
|
}
|
|
6102
6130
|
isQueryBody() {
|
|
6103
6131
|
return (this._method === 'GET' &&
|
|
@@ -6557,16 +6585,6 @@ class ODataActionResource extends ODataResource {
|
|
|
6557
6585
|
}
|
|
6558
6586
|
}
|
|
6559
6587
|
|
|
6560
|
-
const Arrays = {
|
|
6561
|
-
// Zip arrays
|
|
6562
|
-
// Example
|
|
6563
|
-
// Arrays.zip([1, 2, 3, 4, 5, 6], ['a', 'b', 'c', 'd', 'e', 'f'])
|
|
6564
|
-
// => [[1, 'a'], [2, 'b'], [3, 'c'], [4, 'd'], [5, 'e'], [6, 'f']]
|
|
6565
|
-
zip: (...arrays) => {
|
|
6566
|
-
return arrays[0].map((_, i) => arrays.map((array) => array[i]));
|
|
6567
|
-
},
|
|
6568
|
-
};
|
|
6569
|
-
|
|
6570
6588
|
class ODataBatchRequest extends Subject {
|
|
6571
6589
|
request;
|
|
6572
6590
|
id;
|
|
@@ -6664,103 +6682,144 @@ class ODataBatchResource extends ODataResource {
|
|
|
6664
6682
|
return batch;
|
|
6665
6683
|
}
|
|
6666
6684
|
//#endregion
|
|
6667
|
-
storeRequester() {
|
|
6668
|
-
const current = this.api.requester;
|
|
6669
|
-
// Switch to the batch requester
|
|
6670
|
-
this.api.requester = (req) => {
|
|
6671
|
-
if (req.api !== this.api)
|
|
6672
|
-
throw new Error('Batch Request are for the same api.');
|
|
6673
|
-
if (req.observe === 'events')
|
|
6674
|
-
throw new Error("Batch Request does not allows observe == 'events'.");
|
|
6675
|
-
this._requests.push(new ODataBatchRequest(req));
|
|
6676
|
-
return this._requests[this._requests.length - 1];
|
|
6677
|
-
};
|
|
6678
|
-
return current;
|
|
6679
|
-
}
|
|
6680
|
-
restoreRequester(handler) {
|
|
6681
|
-
this.api.requester = handler;
|
|
6682
|
-
}
|
|
6683
6685
|
/**
|
|
6684
6686
|
* Add to batch request
|
|
6685
6687
|
* @param ctx The context for the request
|
|
6686
6688
|
* @returns The result of execute the context
|
|
6687
6689
|
*/
|
|
6688
6690
|
add(ctx) {
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6691
|
+
return this.api.captureRequests((req) => {
|
|
6692
|
+
if (req.api !== this.api)
|
|
6693
|
+
throw new Error('Batch Request are for the same api.');
|
|
6694
|
+
if (req.observe === 'events')
|
|
6695
|
+
throw new Error("Batch Request does not allows observe == 'events'.");
|
|
6696
|
+
const request = new ODataBatchRequest(req);
|
|
6697
|
+
this._requests.push(request);
|
|
6698
|
+
return request;
|
|
6699
|
+
}, () => ctx(this));
|
|
6696
6700
|
}
|
|
6701
|
+
/** Send collected requests, evaluating their cache policies when subscribed. */
|
|
6697
6702
|
send(options) {
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
+
return defer(() => {
|
|
6704
|
+
const readsCache = this._requests.some(({ request }) => request.isFetch() &&
|
|
6705
|
+
request.fetchPolicy !== 'network-only' &&
|
|
6706
|
+
request.fetchPolicy !== 'no-cache');
|
|
6707
|
+
let readiness = defer(() => {
|
|
6708
|
+
const ready = readsCache ? this.api.cache?.ready?.() : undefined;
|
|
6709
|
+
return ready === undefined ? of(undefined) : from(ready);
|
|
6710
|
+
}).pipe(tap({
|
|
6711
|
+
error: (error) => this._requests.forEach((request) => request.error(error)),
|
|
6712
|
+
}));
|
|
6713
|
+
if (this.api.errorHandler !== undefined) {
|
|
6714
|
+
readiness = readiness.pipe(catchError(this.api.errorHandler));
|
|
6715
|
+
}
|
|
6716
|
+
return readiness.pipe(switchMap(() => this.sendRequests(options)));
|
|
6717
|
+
});
|
|
6718
|
+
}
|
|
6719
|
+
sendRequests(options) {
|
|
6720
|
+
const outgoing = [];
|
|
6721
|
+
const responses = new Map();
|
|
6722
|
+
const responseErrors = new Set();
|
|
6723
|
+
const cacheErrors = new ReplaySubject(1);
|
|
6724
|
+
let dispatchingResponses = false;
|
|
6725
|
+
let pendingCacheError;
|
|
6726
|
+
const subscriptions = new Subscription();
|
|
6727
|
+
for (const request of this._requests) {
|
|
6728
|
+
subscriptions.add(this.api
|
|
6729
|
+
.executeRequest(request.request, () => {
|
|
6730
|
+
const response = new Subject();
|
|
6731
|
+
responses.set(request, response);
|
|
6732
|
+
outgoing.push(request);
|
|
6733
|
+
return response;
|
|
6734
|
+
}, true)
|
|
6735
|
+
.subscribe({
|
|
6736
|
+
next: (response) => request.next(response),
|
|
6737
|
+
error: (error) => {
|
|
6738
|
+
request.error(error);
|
|
6739
|
+
if (responseErrors.has(error) || this.api.isCacheMiss(error))
|
|
6740
|
+
return;
|
|
6741
|
+
if (dispatchingResponses)
|
|
6742
|
+
pendingCacheError ??= { error };
|
|
6743
|
+
else
|
|
6744
|
+
cacheErrors.error(error);
|
|
6745
|
+
},
|
|
6746
|
+
complete: () => request.complete(),
|
|
6747
|
+
}));
|
|
6703
6748
|
}
|
|
6749
|
+
const result = defer(() => this.api.options.jsonBatchFormat
|
|
6750
|
+
? this.sendJson(outgoing, options).pipe(map((response) => ({
|
|
6751
|
+
response,
|
|
6752
|
+
parsed: ODataBatchResource.parseJsonResponse(outgoing, response),
|
|
6753
|
+
})))
|
|
6754
|
+
: this.sendLegacy(outgoing, options).pipe(map((response) => ({
|
|
6755
|
+
response,
|
|
6756
|
+
parsed: ODataBatchResource.parseLegacyResponse(outgoing, response),
|
|
6757
|
+
}))));
|
|
6758
|
+
const resultWithResponses = result.pipe(map(({ response, parsed }) => {
|
|
6759
|
+
this._responses = [...(this._responses ?? []), ...parsed];
|
|
6760
|
+
dispatchingResponses = true;
|
|
6761
|
+
outgoing.forEach((request, index) => {
|
|
6762
|
+
const result = parsed[index];
|
|
6763
|
+
const stream = responses.get(request);
|
|
6764
|
+
if (result === undefined) {
|
|
6765
|
+
const error = new Error('Missing batch response');
|
|
6766
|
+
responseErrors.add(error);
|
|
6767
|
+
stream.error(error);
|
|
6768
|
+
}
|
|
6769
|
+
else if (result.ok) {
|
|
6770
|
+
stream.next(result);
|
|
6771
|
+
stream.complete();
|
|
6772
|
+
}
|
|
6773
|
+
else {
|
|
6774
|
+
responseErrors.add(result);
|
|
6775
|
+
stream.error(result);
|
|
6776
|
+
}
|
|
6777
|
+
});
|
|
6778
|
+
dispatchingResponses = false;
|
|
6779
|
+
if (pendingCacheError !== undefined)
|
|
6780
|
+
cacheErrors.error(pendingCacheError.error);
|
|
6781
|
+
return response;
|
|
6782
|
+
}));
|
|
6783
|
+
const failures = this.api.errorHandler === undefined
|
|
6784
|
+
? cacheErrors
|
|
6785
|
+
: cacheErrors.pipe(catchError(this.api.errorHandler));
|
|
6786
|
+
return race(failures, resultWithResponses).pipe(tap({
|
|
6787
|
+
error: (error) => {
|
|
6788
|
+
responseErrors.add(error);
|
|
6789
|
+
responses.forEach((response) => response.error(error));
|
|
6790
|
+
this._requests.forEach((request) => request.error(error));
|
|
6791
|
+
},
|
|
6792
|
+
complete: () => this._requests.forEach((request) => request.complete()),
|
|
6793
|
+
}), finalize(() => subscriptions.unsubscribe()));
|
|
6704
6794
|
}
|
|
6705
|
-
sendJson(options) {
|
|
6795
|
+
sendJson(requests, options) {
|
|
6706
6796
|
const headers = Http.mergeHttpHeaders((options && options.headers) || {}, {
|
|
6707
6797
|
[ODATA_VERSION]: VERSION_4_0,
|
|
6708
6798
|
});
|
|
6709
|
-
return this.api
|
|
6710
|
-
.
|
|
6711
|
-
body: ODataBatchResource.buildJsonBody(this._requests, this.api.options),
|
|
6799
|
+
return this.api.request('POST', this, {
|
|
6800
|
+
body: ODataBatchResource.buildJsonBody(requests, this.api.options),
|
|
6712
6801
|
responseType: 'json',
|
|
6713
6802
|
observe: 'response',
|
|
6714
6803
|
headers: headers,
|
|
6715
6804
|
params: options ? options.params : undefined,
|
|
6716
6805
|
withCredentials: options ? options.withCredentials : undefined,
|
|
6717
|
-
})
|
|
6718
|
-
.pipe(map$1((response) => {
|
|
6719
|
-
if (this._responses == null) {
|
|
6720
|
-
this._responses = [];
|
|
6721
|
-
}
|
|
6722
|
-
this._responses = [
|
|
6723
|
-
...this._responses,
|
|
6724
|
-
...ODataBatchResource.parseJsonResponse(this._requests, response),
|
|
6725
|
-
];
|
|
6726
|
-
//HACK: tuple[1] === undefined
|
|
6727
|
-
Arrays.zip(this._requests, this._responses).forEach((tuple) => {
|
|
6728
|
-
if (!tuple[0].isStopped && tuple[1])
|
|
6729
|
-
tuple[0].onLoad(tuple[1]);
|
|
6730
|
-
});
|
|
6731
|
-
return response;
|
|
6732
|
-
}));
|
|
6806
|
+
});
|
|
6733
6807
|
}
|
|
6734
|
-
sendLegacy(options) {
|
|
6808
|
+
sendLegacy(requests, options) {
|
|
6735
6809
|
const bound = Strings.uniqueId({ prefix: BATCH_PREFIX });
|
|
6736
6810
|
const headers = Http.mergeHttpHeaders((options && options.headers) || {}, {
|
|
6737
6811
|
[ODATA_VERSION]: VERSION_4_0,
|
|
6738
6812
|
[CONTENT_TYPE]: MULTIPART_MIXED_BOUNDARY + bound,
|
|
6739
6813
|
[ACCEPT]: MULTIPART_MIXED,
|
|
6740
6814
|
});
|
|
6741
|
-
return this.api
|
|
6742
|
-
.
|
|
6743
|
-
body: ODataBatchResource.buildLegacyBody(bound, this._requests, this.api.options),
|
|
6815
|
+
return this.api.request('POST', this, {
|
|
6816
|
+
body: ODataBatchResource.buildLegacyBody(bound, requests, this.api.options),
|
|
6744
6817
|
responseType: 'text',
|
|
6745
6818
|
observe: 'response',
|
|
6746
6819
|
headers: headers,
|
|
6747
6820
|
params: options ? options.params : undefined,
|
|
6748
6821
|
withCredentials: options ? options.withCredentials : undefined,
|
|
6749
|
-
})
|
|
6750
|
-
.pipe(map$1((response) => {
|
|
6751
|
-
if (this._responses == null) {
|
|
6752
|
-
this._responses = [];
|
|
6753
|
-
}
|
|
6754
|
-
this._responses = [
|
|
6755
|
-
...this._responses,
|
|
6756
|
-
...ODataBatchResource.parseLegacyResponse(this._requests, response),
|
|
6757
|
-
];
|
|
6758
|
-
Arrays.zip(this._requests, this._responses).forEach((tuple) => {
|
|
6759
|
-
if (!tuple[0].isStopped && tuple[1])
|
|
6760
|
-
tuple[0].onLoad(tuple[1]);
|
|
6761
|
-
});
|
|
6762
|
-
return response;
|
|
6763
|
-
}));
|
|
6822
|
+
});
|
|
6764
6823
|
}
|
|
6765
6824
|
/**
|
|
6766
6825
|
* Execute the batch request
|
|
@@ -6770,7 +6829,7 @@ class ODataBatchResource extends ODataResource {
|
|
|
6770
6829
|
*/
|
|
6771
6830
|
exec(ctx, options) {
|
|
6772
6831
|
let result = this.add(ctx);
|
|
6773
|
-
return this.send(options).pipe(map
|
|
6832
|
+
return this.send(options).pipe(map((response) => [result, response]));
|
|
6774
6833
|
}
|
|
6775
6834
|
body() {
|
|
6776
6835
|
return ODataBatchResource.buildLegacyBody(Strings.uniqueId({ prefix: BATCH_PREFIX }), this._requests, this.api.options);
|
|
@@ -6940,21 +6999,29 @@ class ODataBatchResource extends ODataResource {
|
|
|
6940
6999
|
});
|
|
6941
7000
|
}
|
|
6942
7001
|
static parseJsonResponse(requests, response) {
|
|
6943
|
-
const responses =
|
|
6944
|
-
return
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
7002
|
+
const responses = response.body?.['responses'] ?? [];
|
|
7003
|
+
return requests.map(({ request, id }, index) => {
|
|
7004
|
+
const response = responses.find((response) => response.id === id) ??
|
|
7005
|
+
(responses[index]?.id ? undefined : responses[index]);
|
|
7006
|
+
if (response === undefined) {
|
|
7007
|
+
return new HttpErrorResponse({
|
|
7008
|
+
error: new Error('Missing batch response'),
|
|
7009
|
+
status: 0,
|
|
7010
|
+
url: request.urlWithParams,
|
|
7011
|
+
});
|
|
7012
|
+
}
|
|
7013
|
+
let code = response.status;
|
|
7014
|
+
const headers = new HttpHeaders(response.headers);
|
|
7015
|
+
let body = response.body;
|
|
6949
7016
|
if (code === 0) {
|
|
6950
7017
|
code = !!body ? 200 : 0;
|
|
6951
7018
|
}
|
|
6952
7019
|
let ok = code >= 200 && code < 300;
|
|
6953
7020
|
if (request.responseType === 'json' && typeof body === 'string') {
|
|
6954
7021
|
const originalBody = body;
|
|
6955
|
-
|
|
7022
|
+
const text = body.replace(XSSI_PREFIX, '');
|
|
6956
7023
|
try {
|
|
6957
|
-
body =
|
|
7024
|
+
body = text !== '' ? JSON.parse(text) : null;
|
|
6958
7025
|
}
|
|
6959
7026
|
catch (error) {
|
|
6960
7027
|
body = originalBody;
|
|
@@ -8147,18 +8214,25 @@ class CsdlRecord {
|
|
|
8147
8214
|
}
|
|
8148
8215
|
}
|
|
8149
8216
|
class CsdlPropertyValue {
|
|
8150
|
-
|
|
8217
|
+
Property;
|
|
8218
|
+
Bool;
|
|
8151
8219
|
String;
|
|
8152
8220
|
Date;
|
|
8153
8221
|
EnumMember;
|
|
8154
|
-
|
|
8155
|
-
|
|
8222
|
+
TextContent;
|
|
8223
|
+
constructor({ Property, Bool, String, Date, EnumMember, TextContent, }) {
|
|
8224
|
+
this.Property = Property;
|
|
8225
|
+
this.Bool = Bool;
|
|
8156
8226
|
this.String = String;
|
|
8157
8227
|
this.Date = Date;
|
|
8158
8228
|
this.EnumMember = EnumMember?.map((a) => new CsdlEnumMember(a));
|
|
8229
|
+
this.TextContent = TextContent;
|
|
8159
8230
|
}
|
|
8160
8231
|
toJson() {
|
|
8161
|
-
const json = {
|
|
8232
|
+
const json = { Property: this.Property };
|
|
8233
|
+
if (this.Bool !== undefined) {
|
|
8234
|
+
json['Bool'] = this.Bool;
|
|
8235
|
+
}
|
|
8162
8236
|
if (this.String !== undefined) {
|
|
8163
8237
|
json['String'] = this.String;
|
|
8164
8238
|
}
|
|
@@ -8168,10 +8242,16 @@ class CsdlPropertyValue {
|
|
|
8168
8242
|
if (Array.isArray(this.EnumMember) && this.EnumMember.length > 0) {
|
|
8169
8243
|
json['EnumMember'] = this.EnumMember.map((m) => m.toJson());
|
|
8170
8244
|
}
|
|
8245
|
+
if (this.TextContent !== undefined) {
|
|
8246
|
+
json['TextContent'] = this.TextContent;
|
|
8247
|
+
}
|
|
8171
8248
|
return json;
|
|
8172
8249
|
}
|
|
8173
8250
|
toConfig() {
|
|
8174
|
-
const config = {
|
|
8251
|
+
const config = { property: this.Property };
|
|
8252
|
+
if (this.Bool !== undefined) {
|
|
8253
|
+
config['bool'] = this.Bool;
|
|
8254
|
+
}
|
|
8175
8255
|
if (this.String !== undefined) {
|
|
8176
8256
|
config['string'] = this.String;
|
|
8177
8257
|
}
|
|
@@ -8181,6 +8261,9 @@ class CsdlPropertyValue {
|
|
|
8181
8261
|
if (Array.isArray(this.EnumMember) && this.EnumMember.length > 0) {
|
|
8182
8262
|
config['enumMembers'] = this.EnumMember.map((m) => m.toConfig());
|
|
8183
8263
|
}
|
|
8264
|
+
if (this.TextContent !== undefined) {
|
|
8265
|
+
config['textContent'] = this.TextContent;
|
|
8266
|
+
}
|
|
8184
8267
|
return config;
|
|
8185
8268
|
}
|
|
8186
8269
|
}
|
|
@@ -8401,10 +8484,8 @@ class CsdlEnumType extends CsdlAnnotable {
|
|
|
8401
8484
|
return {
|
|
8402
8485
|
...super.toConfig(),
|
|
8403
8486
|
name: this.Name,
|
|
8404
|
-
fields: this.Member.reduce((acc, m) => ({
|
|
8405
|
-
|
|
8406
|
-
[m.Name]: m.toConfig(),
|
|
8407
|
-
}), {}),
|
|
8487
|
+
fields: this.Member.reduce((acc, m) => ({ ...acc, [m.Name]: m.toConfig(), }), {}),
|
|
8488
|
+
members: this.Member.reduce((acc, m) => ({ ...acc, [m.Name]: m.Value, [m.Value]: m.Name }), {}),
|
|
8408
8489
|
flags: this.IsFlags,
|
|
8409
8490
|
};
|
|
8410
8491
|
}
|
|
@@ -8425,11 +8506,11 @@ class CsdlMember extends CsdlAnnotable {
|
|
|
8425
8506
|
return json;
|
|
8426
8507
|
}
|
|
8427
8508
|
toConfig(base) {
|
|
8428
|
-
|
|
8509
|
+
return {
|
|
8429
8510
|
...super.toConfig(),
|
|
8511
|
+
name: this.Name,
|
|
8430
8512
|
value: this.Value,
|
|
8431
8513
|
};
|
|
8432
|
-
return config;
|
|
8433
8514
|
}
|
|
8434
8515
|
}
|
|
8435
8516
|
|
|
@@ -8446,12 +8527,15 @@ class CsdlStructuralProperty extends CsdlAnnotable {
|
|
|
8446
8527
|
this.Type = this.Collection ? Type.substring(11, Type.length - 1) : Type;
|
|
8447
8528
|
}
|
|
8448
8529
|
toJson() {
|
|
8449
|
-
|
|
8530
|
+
const json = {
|
|
8450
8531
|
...super.toJson(),
|
|
8451
8532
|
Name: this.Name,
|
|
8452
|
-
Type: this.Collection ? `Collection(${this.Type})` : this.Type
|
|
8453
|
-
Nullable: this.Nullable,
|
|
8533
|
+
Type: this.Collection ? `Collection(${this.Type})` : this.Type
|
|
8454
8534
|
};
|
|
8535
|
+
if (this.Nullable !== undefined) {
|
|
8536
|
+
json['Nullable'] = this.Nullable;
|
|
8537
|
+
}
|
|
8538
|
+
return json;
|
|
8455
8539
|
}
|
|
8456
8540
|
}
|
|
8457
8541
|
class CsdlProperty extends CsdlStructuralProperty {
|
|
@@ -8794,7 +8878,7 @@ class CsdlFunction extends CsdlCallable {
|
|
|
8794
8878
|
entitySetPath: this.EntitySetPath,
|
|
8795
8879
|
bound: this.IsBound,
|
|
8796
8880
|
composable: this.IsComposable,
|
|
8797
|
-
parameters: this.Parameter?.
|
|
8881
|
+
parameters: this.Parameter?.reduce((acc, p) => ({ ...acc, [p.Name]: p.toConfig() }), {}),
|
|
8798
8882
|
return: this.ReturnType?.toConfig(),
|
|
8799
8883
|
};
|
|
8800
8884
|
}
|
|
@@ -8813,7 +8897,7 @@ class CsdlAction extends CsdlCallable {
|
|
|
8813
8897
|
name: this.Name,
|
|
8814
8898
|
entitySetPath: this.EntitySetPath,
|
|
8815
8899
|
bound: this.IsBound,
|
|
8816
|
-
parameters: this.Parameter?.
|
|
8900
|
+
parameters: this.Parameter?.reduce((acc, p) => ({ ...acc, [p.Name]: p.toConfig() }), {}),
|
|
8817
8901
|
return: this.ReturnType?.toConfig(),
|
|
8818
8902
|
};
|
|
8819
8903
|
}
|
|
@@ -8880,24 +8964,51 @@ class CsdlParameter extends CsdlAnnotable {
|
|
|
8880
8964
|
this.SRID = SRID;
|
|
8881
8965
|
}
|
|
8882
8966
|
toJson() {
|
|
8883
|
-
|
|
8967
|
+
const json = {
|
|
8884
8968
|
...super.toJson(),
|
|
8885
8969
|
Name: this.Name,
|
|
8886
|
-
Type: this.Collection ? `Collection(${this.Type})` : this.Type
|
|
8887
|
-
Nullable: this.Nullable,
|
|
8888
|
-
MaxLength: this.MaxLength,
|
|
8889
|
-
Precision: this.Precision,
|
|
8890
|
-
Scale: this.Scale,
|
|
8891
|
-
SRID: this.SRID,
|
|
8970
|
+
Type: this.Collection ? `Collection(${this.Type})` : this.Type
|
|
8892
8971
|
};
|
|
8972
|
+
if (this.Nullable !== undefined) {
|
|
8973
|
+
json['Nullable'] = this.Nullable;
|
|
8974
|
+
}
|
|
8975
|
+
if (this.MaxLength !== undefined) {
|
|
8976
|
+
json['MaxLength'] = this.MaxLength;
|
|
8977
|
+
}
|
|
8978
|
+
if (this.Precision !== undefined) {
|
|
8979
|
+
json['Precision'] = this.Precision;
|
|
8980
|
+
}
|
|
8981
|
+
if (this.Scale !== undefined) {
|
|
8982
|
+
json['Scale'] = this.Scale;
|
|
8983
|
+
}
|
|
8984
|
+
if (this.SRID !== undefined) {
|
|
8985
|
+
json['SRID'] = this.SRID;
|
|
8986
|
+
}
|
|
8987
|
+
return json;
|
|
8893
8988
|
}
|
|
8894
8989
|
toConfig() {
|
|
8895
|
-
|
|
8990
|
+
const config = {
|
|
8896
8991
|
...super.toConfig(),
|
|
8992
|
+
name: this.Name,
|
|
8897
8993
|
type: this.Type,
|
|
8898
|
-
|
|
8899
|
-
collection: false,
|
|
8994
|
+
collection: this.Collection,
|
|
8900
8995
|
};
|
|
8996
|
+
if (this.Nullable !== undefined) {
|
|
8997
|
+
config.nullable = this.Nullable;
|
|
8998
|
+
}
|
|
8999
|
+
if (this.MaxLength !== undefined) {
|
|
9000
|
+
config.maxLength = this.MaxLength;
|
|
9001
|
+
}
|
|
9002
|
+
if (this.Precision !== undefined) {
|
|
9003
|
+
config.precision = this.Precision;
|
|
9004
|
+
}
|
|
9005
|
+
if (this.Scale !== undefined) {
|
|
9006
|
+
config.scale = this.Scale;
|
|
9007
|
+
}
|
|
9008
|
+
if (this.SRID !== undefined) {
|
|
9009
|
+
config.srid = this.SRID;
|
|
9010
|
+
}
|
|
9011
|
+
return config;
|
|
8901
9012
|
}
|
|
8902
9013
|
}
|
|
8903
9014
|
class CsdlReturnType {
|
|
@@ -8918,20 +9029,47 @@ class CsdlReturnType {
|
|
|
8918
9029
|
this.SRID = SRID;
|
|
8919
9030
|
}
|
|
8920
9031
|
toJson() {
|
|
8921
|
-
|
|
8922
|
-
Type: this.Collection ? `Collection(${this.Type})` : this.Type
|
|
8923
|
-
Nullable: this.Nullable,
|
|
8924
|
-
MaxLength: this.MaxLength,
|
|
8925
|
-
Precision: this.Precision,
|
|
8926
|
-
Scale: this.Scale,
|
|
8927
|
-
SRID: this.SRID,
|
|
9032
|
+
const json = {
|
|
9033
|
+
Type: this.Collection ? `Collection(${this.Type})` : this.Type
|
|
8928
9034
|
};
|
|
9035
|
+
if (this.Nullable !== undefined) {
|
|
9036
|
+
json['Nullable'] = this.Nullable;
|
|
9037
|
+
}
|
|
9038
|
+
if (this.MaxLength !== undefined) {
|
|
9039
|
+
json['MaxLength'] = this.MaxLength;
|
|
9040
|
+
}
|
|
9041
|
+
if (this.Precision !== undefined) {
|
|
9042
|
+
json['Precision'] = this.Precision;
|
|
9043
|
+
}
|
|
9044
|
+
if (this.Scale !== undefined) {
|
|
9045
|
+
json['Scale'] = this.Scale;
|
|
9046
|
+
}
|
|
9047
|
+
if (this.SRID !== undefined) {
|
|
9048
|
+
json['SRID'] = this.SRID;
|
|
9049
|
+
}
|
|
9050
|
+
return json;
|
|
8929
9051
|
}
|
|
8930
9052
|
toConfig() {
|
|
8931
|
-
|
|
9053
|
+
const config = {
|
|
8932
9054
|
type: this.Type,
|
|
8933
|
-
collection:
|
|
9055
|
+
collection: this.Collection
|
|
8934
9056
|
};
|
|
9057
|
+
if (this.Nullable !== undefined) {
|
|
9058
|
+
config.nullable = this.Nullable;
|
|
9059
|
+
}
|
|
9060
|
+
if (this.MaxLength !== undefined) {
|
|
9061
|
+
config.maxLength = this.MaxLength;
|
|
9062
|
+
}
|
|
9063
|
+
if (this.Precision !== undefined) {
|
|
9064
|
+
config.precision = this.Precision;
|
|
9065
|
+
}
|
|
9066
|
+
if (this.Scale !== undefined) {
|
|
9067
|
+
config.scale = this.Scale;
|
|
9068
|
+
}
|
|
9069
|
+
if (this.SRID !== undefined) {
|
|
9070
|
+
config.srid = this.SRID;
|
|
9071
|
+
}
|
|
9072
|
+
return config;
|
|
8935
9073
|
}
|
|
8936
9074
|
}
|
|
8937
9075
|
|
|
@@ -9459,8 +9597,6 @@ class ODataMetadataParser {
|
|
|
9459
9597
|
case ODataMetadataParser.ATTRIBUTE_TERM:
|
|
9460
9598
|
case ODataMetadataParser.ATTRIBUTE_QUALIFIER:
|
|
9461
9599
|
case ODataMetadataParser.ATTRIBUTE_STRING:
|
|
9462
|
-
case ODataMetadataParser.ATTRIBUTE_BOOL:
|
|
9463
|
-
case ODataMetadataParser.ATTRIBUTE_INT:
|
|
9464
9600
|
case ODataMetadataParser.ATTRIBUTE_TARGET_NAMESPACE:
|
|
9465
9601
|
case ODataMetadataParser.ATTRIBUTE_NAME:
|
|
9466
9602
|
case ODataMetadataParser.ATTRIBUTE_TYPE:
|
|
@@ -9482,6 +9618,7 @@ class ODataMetadataParser {
|
|
|
9482
9618
|
case ODataMetadataParser.ATTRIBUTE_BASE_TERM:
|
|
9483
9619
|
case ODataMetadataParser.ATTRIBUTE_APPLIES_TO:
|
|
9484
9620
|
return this.getAttributeValue(attributes, field.name);
|
|
9621
|
+
case ODataMetadataParser.ATTRIBUTE_BOOL:
|
|
9485
9622
|
case ODataMetadataParser.ATTRIBUTE_NULLABLE:
|
|
9486
9623
|
case ODataMetadataParser.ATTRIBUTE_UNICODE:
|
|
9487
9624
|
case ODataMetadataParser.ATTRIBUTE_OPEN_TYPE:
|
|
@@ -9493,6 +9630,7 @@ class ODataMetadataParser {
|
|
|
9493
9630
|
case ODataMetadataParser.ATTRIBUTE_ABSTRACT:
|
|
9494
9631
|
case ODataMetadataParser.ATTRIBUTE_IS_FLAGS:
|
|
9495
9632
|
return this.propertyValueToBoolean(this.getAttributeValue(attributes, field.name));
|
|
9633
|
+
case ODataMetadataParser.ATTRIBUTE_INT:
|
|
9496
9634
|
case ODataMetadataParser.ATTRIBUTE_VALUE:
|
|
9497
9635
|
case ODataMetadataParser.ATTRIBUTE_MAX_LENGTH:
|
|
9498
9636
|
case ODataMetadataParser.ATTRIBUTE_PRECISION:
|
|
@@ -10112,16 +10250,30 @@ class ODataResponseOptions {
|
|
|
10112
10250
|
});
|
|
10113
10251
|
}
|
|
10114
10252
|
setCache(cacheControl) {
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10253
|
+
const precedence = ['public', 'private', 'no-cache', 'no-store'];
|
|
10254
|
+
this.cacheability = undefined;
|
|
10255
|
+
this.maxAge = undefined;
|
|
10256
|
+
for (const directive of cacheControl.split(',')) {
|
|
10257
|
+
const separator = directive.indexOf('=');
|
|
10258
|
+
const name = (separator < 0 ? directive : directive.slice(0, separator)).trim().toLowerCase();
|
|
10259
|
+
const value = separator < 0
|
|
10260
|
+
? ''
|
|
10261
|
+
: directive
|
|
10262
|
+
.slice(separator + 1)
|
|
10263
|
+
.trim()
|
|
10264
|
+
.replace(/^"(.*)"$/, '$1');
|
|
10265
|
+
if (name === MAX_AGE) {
|
|
10266
|
+
const maxAge = /^\d+$/.test(value) ? Number(value) : NaN;
|
|
10267
|
+
const seconds = Number.isFinite(maxAge * 1000) ? maxAge : 0;
|
|
10268
|
+
this.maxAge = Math.min(this.maxAge ?? Infinity, seconds);
|
|
10269
|
+
}
|
|
10270
|
+
const cacheability = precedence.find((candidate) => candidate === name);
|
|
10271
|
+
if (cacheability !== undefined &&
|
|
10272
|
+
(this.cacheability === undefined ||
|
|
10273
|
+
precedence.indexOf(cacheability) > precedence.indexOf(this.cacheability))) {
|
|
10274
|
+
this.cacheability = cacheability;
|
|
10123
10275
|
}
|
|
10124
|
-
}
|
|
10276
|
+
}
|
|
10125
10277
|
}
|
|
10126
10278
|
}
|
|
10127
10279
|
|
|
@@ -10170,6 +10322,17 @@ class ODataResponse extends HttpResponse {
|
|
|
10170
10322
|
url: this.url,
|
|
10171
10323
|
};
|
|
10172
10324
|
}
|
|
10325
|
+
/** Whether the response can be reused without server validation. */
|
|
10326
|
+
isCacheable(ignoreCacheControl = false) {
|
|
10327
|
+
const vary = (this.headers.getAll('Vary') ?? [])
|
|
10328
|
+
.flatMap((value) => value.split(','))
|
|
10329
|
+
.map((name) => name.trim().toLowerCase())
|
|
10330
|
+
.filter((name) => name.length > 0);
|
|
10331
|
+
return (this.ok &&
|
|
10332
|
+
(ignoreCacheControl ||
|
|
10333
|
+
(this.options.cacheability !== 'no-store' && this.options.cacheability !== 'no-cache')) &&
|
|
10334
|
+
vary.every((name) => CACHE_REPRESENTATION_HEADERS.includes(name)));
|
|
10335
|
+
}
|
|
10173
10336
|
_options;
|
|
10174
10337
|
get options() {
|
|
10175
10338
|
if (this._options === undefined) {
|
|
@@ -12845,6 +13008,10 @@ class ODataApiOptions {
|
|
|
12845
13008
|
* Cache fetch policy
|
|
12846
13009
|
*/
|
|
12847
13010
|
fetchPolicy;
|
|
13011
|
+
/** Ignore response Cache-Control directives. Defaults to false. */
|
|
13012
|
+
ignoreCacheControl;
|
|
13013
|
+
/** Scope invalidated by successful writes. Defaults to entity-set. */
|
|
13014
|
+
cacheInvalidation;
|
|
12848
13015
|
/**
|
|
12849
13016
|
* Extra params to be sent in the request
|
|
12850
13017
|
*/
|
|
@@ -12876,6 +13043,8 @@ class ODataApiOptions {
|
|
|
12876
13043
|
this.withCredentials = config.withCredentials;
|
|
12877
13044
|
this.stripMetadata = config.stripMetadata || DEFAULT_STRIP_METADATA;
|
|
12878
13045
|
this.fetchPolicy = config.fetchPolicy || DEFAULT_FETCH_POLICY;
|
|
13046
|
+
this.ignoreCacheControl = config.ignoreCacheControl ?? false;
|
|
13047
|
+
this.cacheInvalidation = config.cacheInvalidation ?? 'entity-set';
|
|
12879
13048
|
this.bodyQueryOptions = config.bodyQueryOptions || [];
|
|
12880
13049
|
this.accept = config.accept;
|
|
12881
13050
|
Object.assign(this.etag, config.etag || {});
|
|
@@ -12945,11 +13114,15 @@ class ODataReference extends ODataAnnotatable {
|
|
|
12945
13114
|
}
|
|
12946
13115
|
|
|
12947
13116
|
const RESERVED_FIELD_NAMES = Object.getOwnPropertyNames(ODataModel.prototype);
|
|
13117
|
+
// Share invalidation generations across API instances using the same cache.
|
|
13118
|
+
const CACHE_GENERATIONS = new WeakMap();
|
|
13119
|
+
const CACHE_MISSES = new WeakSet();
|
|
12948
13120
|
/**
|
|
12949
13121
|
* Api abstraction for consuming OData services.
|
|
12950
13122
|
*/
|
|
12951
13123
|
class ODataApi {
|
|
12952
13124
|
requester;
|
|
13125
|
+
_requestCapture;
|
|
12953
13126
|
serviceRootUrl;
|
|
12954
13127
|
metadataUrl;
|
|
12955
13128
|
name;
|
|
@@ -13141,6 +13314,8 @@ class ODataApi {
|
|
|
13141
13314
|
bodyQueryOptions: options.bodyQueryOptions,
|
|
13142
13315
|
reportProgress: options.reportProgress,
|
|
13143
13316
|
fetchPolicy: options.fetchPolicy,
|
|
13317
|
+
ignoreCacheControl: options.ignoreCacheControl,
|
|
13318
|
+
cacheInvalidation: options.cacheInvalidation,
|
|
13144
13319
|
maxAge: options.maxAge,
|
|
13145
13320
|
parserOptions: options.parserOptions,
|
|
13146
13321
|
withCredentials: options.withCredentials,
|
|
@@ -13180,53 +13355,100 @@ class ODataApi {
|
|
|
13180
13355
|
* @returns
|
|
13181
13356
|
*/
|
|
13182
13357
|
handleRequest(req) {
|
|
13183
|
-
return this.
|
|
13184
|
-
? this.
|
|
13185
|
-
: this.
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13358
|
+
return this._requestCapture !== undefined
|
|
13359
|
+
? this.handleRequester(req, this._requestCapture)
|
|
13360
|
+
: this.executeRequest(req);
|
|
13361
|
+
}
|
|
13362
|
+
/** @internal Collect batch requests synchronously without executing their cache effects. */
|
|
13363
|
+
captureRequests(capture, callback) {
|
|
13364
|
+
const previous = this._requestCapture;
|
|
13365
|
+
this._requestCapture = capture;
|
|
13366
|
+
try {
|
|
13367
|
+
return callback();
|
|
13368
|
+
}
|
|
13369
|
+
finally {
|
|
13370
|
+
this._requestCapture = previous;
|
|
13371
|
+
}
|
|
13372
|
+
}
|
|
13373
|
+
/** @internal Execute a collected or ordinary request with subscription-time cache semantics. */
|
|
13374
|
+
executeRequest(req, requester = this.requester, cacheReady = false) {
|
|
13375
|
+
return defer(() => {
|
|
13376
|
+
const cache = this.cache;
|
|
13377
|
+
const network = () => this.handleRequester(req, requester);
|
|
13378
|
+
if (cache === undefined)
|
|
13379
|
+
return network();
|
|
13380
|
+
if (req.isFetch()) {
|
|
13381
|
+
if (req.fetchPolicy === 'no-cache')
|
|
13382
|
+
return network();
|
|
13383
|
+
const fetch = () => this.handleCacheFetch(req, cache, network);
|
|
13384
|
+
return !cacheReady && req.fetchPolicy !== 'network-only' && cache.ready !== undefined
|
|
13385
|
+
? from(cache.ready()).pipe(switchMap(fetch))
|
|
13386
|
+
: fetch();
|
|
13387
|
+
}
|
|
13388
|
+
return req.isMutate() && !req.isBatch()
|
|
13389
|
+
? network().pipe(tap((res) => {
|
|
13390
|
+
if (res.type === HttpEventType.Response && res.ok)
|
|
13391
|
+
this.invalidateCache(req, cache);
|
|
13392
|
+
}))
|
|
13393
|
+
: network();
|
|
13394
|
+
});
|
|
13395
|
+
}
|
|
13396
|
+
/** @internal Whether the error is a `cache-only` miss rather than a cache failure. */
|
|
13397
|
+
isCacheMiss(error) {
|
|
13398
|
+
return error instanceof Error && CACHE_MISSES.has(error);
|
|
13399
|
+
}
|
|
13400
|
+
handleRequester(req, requester) {
|
|
13401
|
+
return (requester !== undefined ? requester(req) : NEVER).pipe(map((res) => res.type === HttpEventType.Response ? ODataResponse.fromHttpResponse(req, res) : res));
|
|
13402
|
+
}
|
|
13403
|
+
cacheGeneration(req, cache) {
|
|
13404
|
+
const generations = CACHE_GENERATIONS.get(cache);
|
|
13405
|
+
const apiKey = JSON.stringify(req.cacheScope);
|
|
13406
|
+
const scopeKey = JSON.stringify(cache.scope(req));
|
|
13407
|
+
return ((generations?.get(apiKey) ?? 0) +
|
|
13408
|
+
(scopeKey !== apiKey ? generations?.get(scopeKey) ?? 0 : 0));
|
|
13409
|
+
}
|
|
13410
|
+
invalidateCache(req, cache) {
|
|
13411
|
+
let generations = CACHE_GENERATIONS.get(cache);
|
|
13412
|
+
if (generations === undefined) {
|
|
13413
|
+
generations = new Map();
|
|
13414
|
+
CACHE_GENERATIONS.set(cache, generations);
|
|
13415
|
+
}
|
|
13416
|
+
const scope = cache.scope(req);
|
|
13417
|
+
// Custom scopes outside the API partition use scoped invalidation.
|
|
13418
|
+
const target = req.cacheInvalidation === 'api' &&
|
|
13419
|
+
req.cacheScope.every((part, index) => scope[index] === part) ? req.cacheScope : scope;
|
|
13420
|
+
const key = JSON.stringify(target);
|
|
13421
|
+
generations.set(key, (generations.get(key) ?? 0) + 1);
|
|
13422
|
+
cache.forget({ scope: target });
|
|
13423
|
+
}
|
|
13424
|
+
handleCacheFetch(req, cache, network) {
|
|
13193
13425
|
const policy = req.fetchPolicy;
|
|
13194
|
-
const cached =
|
|
13195
|
-
if (policy === 'no-cache') {
|
|
13196
|
-
return this.handleRequester(req);
|
|
13197
|
-
}
|
|
13426
|
+
const cached = policy === 'network-only' ? undefined : cache.getResponse(req);
|
|
13198
13427
|
if (policy === 'cache-only') {
|
|
13199
|
-
if (cached !== undefined)
|
|
13428
|
+
if (cached !== undefined)
|
|
13200
13429
|
return of(cached);
|
|
13430
|
+
const miss = new Error('No Cached');
|
|
13431
|
+
CACHE_MISSES.add(miss);
|
|
13432
|
+
return throwError(() => miss);
|
|
13433
|
+
}
|
|
13434
|
+
if (cached !== undefined && policy === 'cache-first')
|
|
13435
|
+
return of(cached);
|
|
13436
|
+
const generation = this.cacheGeneration(req, cache);
|
|
13437
|
+
const response = defer(network).pipe(tap((res) => {
|
|
13438
|
+
if (res.type !== HttpEventType.Response ||
|
|
13439
|
+
!res.ok ||
|
|
13440
|
+
generation !== this.cacheGeneration(req, cache)) {
|
|
13441
|
+
return;
|
|
13201
13442
|
}
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
:
|
|
13211
|
-
if (cached === undefined &&
|
|
13212
|
-
(policy === 'cache-first' || policy === 'cache-and-network' || policy === 'network-only')) {
|
|
13213
|
-
res$ = res$.pipe(tap((res) => {
|
|
13214
|
-
if (res.options.cacheability !== 'no-store') {
|
|
13215
|
-
this.cache.putResponse(req, res);
|
|
13216
|
-
}
|
|
13217
|
-
}));
|
|
13218
|
-
}
|
|
13219
|
-
return res$;
|
|
13220
|
-
}
|
|
13221
|
-
handleCacheMutate(req) {
|
|
13222
|
-
const requests = req.isBatch()
|
|
13223
|
-
? req.resource.requests().filter((r) => r.isMutate())
|
|
13224
|
-
: [req];
|
|
13225
|
-
for (var r of requests) {
|
|
13226
|
-
const scope = this.cache.scope(r);
|
|
13227
|
-
this.cache.forget({ scope });
|
|
13228
|
-
}
|
|
13229
|
-
return this.handleRequester(req);
|
|
13443
|
+
const response = res instanceof ODataResponse ? res : ODataResponse.fromHttpResponse(req, res);
|
|
13444
|
+
if (response.isCacheable(req.ignoreCacheControl))
|
|
13445
|
+
cache.putResponse(req, response);
|
|
13446
|
+
else
|
|
13447
|
+
cache.forget({ name: req.cacheKey, scope: cache.scope(req) });
|
|
13448
|
+
}));
|
|
13449
|
+
return cached !== undefined && policy === 'cache-and-network'
|
|
13450
|
+
? response.pipe(startWith(cached))
|
|
13451
|
+
: response;
|
|
13230
13452
|
}
|
|
13231
13453
|
//# region Find by Type
|
|
13232
13454
|
// Memoize
|
|
@@ -13642,6 +13864,8 @@ function addBody(options, body) {
|
|
|
13642
13864
|
body,
|
|
13643
13865
|
etag: options.etag,
|
|
13644
13866
|
fetchPolicy: options.fetchPolicy,
|
|
13867
|
+
ignoreCacheControl: options.ignoreCacheControl,
|
|
13868
|
+
cacheInvalidation: options.cacheInvalidation,
|
|
13645
13869
|
headers: options.headers,
|
|
13646
13870
|
observe: options.observe,
|
|
13647
13871
|
params: options.params,
|
|
@@ -14209,8 +14433,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.5", ngImpor
|
|
|
14209
14433
|
|
|
14210
14434
|
class ODataBaseCache {
|
|
14211
14435
|
maxAge;
|
|
14212
|
-
|
|
14436
|
+
_onError;
|
|
14437
|
+
_error;
|
|
14438
|
+
constructor({ maxAge = DEFAULT_MAXAGE, onError, }) {
|
|
14439
|
+
this.validateMaxAge(maxAge);
|
|
14213
14440
|
this.maxAge = maxAge;
|
|
14441
|
+
this._onError = onError;
|
|
14214
14442
|
}
|
|
14215
14443
|
/**
|
|
14216
14444
|
* Check if the entry is expired
|
|
@@ -14218,7 +14446,7 @@ class ODataBaseCache {
|
|
|
14218
14446
|
* @returns Boolean indicating if the entry is expired
|
|
14219
14447
|
*/
|
|
14220
14448
|
isExpired(entry) {
|
|
14221
|
-
return entry.date
|
|
14449
|
+
return entry.date <= Date.now() - entry.maxAge;
|
|
14222
14450
|
}
|
|
14223
14451
|
/**
|
|
14224
14452
|
* Using the resource on the request build an array of string to identify the scope of the request
|
|
@@ -14227,11 +14455,11 @@ class ODataBaseCache {
|
|
|
14227
14455
|
*/
|
|
14228
14456
|
scope(req) {
|
|
14229
14457
|
const segments = req.resource.cloneSegments();
|
|
14230
|
-
return segments.segments(
|
|
14231
|
-
if (s.name === PathSegment.entitySet)
|
|
14458
|
+
return segments.segments().reduce((acc, s) => {
|
|
14459
|
+
if (s.name === PathSegment.entitySet || s.name === PathSegment.singleton)
|
|
14232
14460
|
acc = [...acc, s.path()];
|
|
14233
14461
|
return acc;
|
|
14234
|
-
},
|
|
14462
|
+
}, req.cacheScope);
|
|
14235
14463
|
}
|
|
14236
14464
|
/**
|
|
14237
14465
|
* Using the odata context on the response build an array of string to identify the tags of the response
|
|
@@ -14256,11 +14484,13 @@ class ODataBaseCache {
|
|
|
14256
14484
|
* @returns The entry to store in the cache
|
|
14257
14485
|
*/
|
|
14258
14486
|
buildEntry(payload, { maxAge, tags }) {
|
|
14487
|
+
const seconds = maxAge ?? this.maxAge;
|
|
14488
|
+
this.validateMaxAge(seconds);
|
|
14259
14489
|
return {
|
|
14260
14490
|
payload,
|
|
14261
14491
|
date: Date.now(),
|
|
14262
|
-
maxAge:
|
|
14263
|
-
tags: tags ?? [],
|
|
14492
|
+
maxAge: seconds * 1000,
|
|
14493
|
+
tags: [...(tags ?? [])],
|
|
14264
14494
|
};
|
|
14265
14495
|
}
|
|
14266
14496
|
/**
|
|
@@ -14271,14 +14501,147 @@ class ODataBaseCache {
|
|
|
14271
14501
|
buildKey(names) {
|
|
14272
14502
|
return names.join(CACHE_KEY_SEPARATOR);
|
|
14273
14503
|
}
|
|
14504
|
+
validateMaxAge(maxAge) {
|
|
14505
|
+
if (!Number.isFinite(maxAge * 1000) || maxAge < 0) {
|
|
14506
|
+
throw new RangeError('Cache maxAge must be a finite, non-negative number of seconds');
|
|
14507
|
+
}
|
|
14508
|
+
}
|
|
14509
|
+
reportError(message, cause) {
|
|
14510
|
+
const error = new Error(message, { cause });
|
|
14511
|
+
if (this._onError !== undefined)
|
|
14512
|
+
this._onError(error);
|
|
14513
|
+
else {
|
|
14514
|
+
this._error = error;
|
|
14515
|
+
throw error;
|
|
14516
|
+
}
|
|
14517
|
+
}
|
|
14518
|
+
/** Report a discarded entry; corrupt entries are treated as cache misses. */
|
|
14519
|
+
reportCorruption(message, cause) {
|
|
14520
|
+
this._onError?.(new Error(message, { cause }));
|
|
14521
|
+
}
|
|
14522
|
+
checkError() {
|
|
14523
|
+
if (this._error !== undefined)
|
|
14524
|
+
throw this._error;
|
|
14525
|
+
}
|
|
14526
|
+
isEntry(value) {
|
|
14527
|
+
return (typeof value === 'object' &&
|
|
14528
|
+
value !== null &&
|
|
14529
|
+
'payload' in value &&
|
|
14530
|
+
'date' in value &&
|
|
14531
|
+
typeof value.date === 'number' &&
|
|
14532
|
+
Number.isFinite(value.date) &&
|
|
14533
|
+
'maxAge' in value &&
|
|
14534
|
+
typeof value.maxAge === 'number' &&
|
|
14535
|
+
Number.isFinite(value.maxAge) &&
|
|
14536
|
+
value.maxAge >= 0 &&
|
|
14537
|
+
'tags' in value &&
|
|
14538
|
+
Array.isArray(value.tags) &&
|
|
14539
|
+
value.tags.every((tag) => typeof tag === 'string'));
|
|
14540
|
+
}
|
|
14541
|
+
matches(key, entry, { name, scope = [], tags = [] }) {
|
|
14542
|
+
const scopedKey = this.buildKey(name !== undefined ? [...scope, name] : scope);
|
|
14543
|
+
return (this.isExpired(entry) ||
|
|
14544
|
+
(name !== undefined
|
|
14545
|
+
? key === scopedKey
|
|
14546
|
+
: scope.length > 0 &&
|
|
14547
|
+
(key === scopedKey || key.startsWith(`${scopedKey}${CACHE_KEY_SEPARATOR}`))) ||
|
|
14548
|
+
tags.some((tag) => entry.tags.includes(tag)));
|
|
14549
|
+
}
|
|
14550
|
+
isResponseJson(value) {
|
|
14551
|
+
return (typeof value === 'object' &&
|
|
14552
|
+
value !== null &&
|
|
14553
|
+
'body' in value &&
|
|
14554
|
+
'status' in value &&
|
|
14555
|
+
typeof value.status === 'number' &&
|
|
14556
|
+
Number.isInteger(value.status) &&
|
|
14557
|
+
'statusText' in value &&
|
|
14558
|
+
typeof value.statusText === 'string' &&
|
|
14559
|
+
'url' in value &&
|
|
14560
|
+
(value.url === null || typeof value.url === 'string') &&
|
|
14561
|
+
'headers' in value &&
|
|
14562
|
+
typeof value.headers === 'object' &&
|
|
14563
|
+
value.headers !== null &&
|
|
14564
|
+
!Array.isArray(value.headers) &&
|
|
14565
|
+
Object.values(value.headers).every((header) => typeof header === 'string' ||
|
|
14566
|
+
(Array.isArray(header) && header.every((part) => typeof part === 'string'))));
|
|
14567
|
+
}
|
|
14568
|
+
snapshot(response) {
|
|
14569
|
+
const body = response.body;
|
|
14570
|
+
return {
|
|
14571
|
+
...response,
|
|
14572
|
+
body: typeof Blob !== 'undefined' && body instanceof Blob
|
|
14573
|
+
? body.slice(0, body.size, body.type)
|
|
14574
|
+
: structuredClone(body),
|
|
14575
|
+
headers: structuredClone(response.headers),
|
|
14576
|
+
};
|
|
14577
|
+
}
|
|
14578
|
+
/** Store a detached response snapshot, respecting server reuse restrictions. */
|
|
14579
|
+
putResponse(req, res) {
|
|
14580
|
+
const scope = this.scope(req);
|
|
14581
|
+
if (!res.isCacheable(req.ignoreCacheControl)) {
|
|
14582
|
+
this.forget({ name: req.cacheKey, scope });
|
|
14583
|
+
return;
|
|
14584
|
+
}
|
|
14585
|
+
this.put(req.cacheKey, this.snapshot(res.toJson()), {
|
|
14586
|
+
maxAge: req.maxAge ?? (req.ignoreCacheControl ? undefined : res.options.maxAge),
|
|
14587
|
+
scope,
|
|
14588
|
+
tags: this.tags(res),
|
|
14589
|
+
});
|
|
14590
|
+
}
|
|
14591
|
+
/** Restore a detached response using the current request's resource and parser. */
|
|
14592
|
+
getResponse(req) {
|
|
14593
|
+
const scope = this.scope(req);
|
|
14594
|
+
const data = this.get(req.cacheKey, { scope });
|
|
14595
|
+
if (data === undefined)
|
|
14596
|
+
return undefined;
|
|
14597
|
+
if (!this.isResponseJson(data)) {
|
|
14598
|
+
this.forget({ name: req.cacheKey, scope });
|
|
14599
|
+
this.reportCorruption('Discarding an invalid cached OData response');
|
|
14600
|
+
return undefined;
|
|
14601
|
+
}
|
|
14602
|
+
const response = ODataResponse.fromJson(req, this.snapshot(data));
|
|
14603
|
+
if (!response.isCacheable(req.ignoreCacheControl)) {
|
|
14604
|
+
this.forget({ name: req.cacheKey, scope });
|
|
14605
|
+
return undefined;
|
|
14606
|
+
}
|
|
14607
|
+
return response;
|
|
14608
|
+
}
|
|
14274
14609
|
}
|
|
14275
14610
|
|
|
14276
14611
|
class ODataInMemoryCache extends ODataBaseCache {
|
|
14277
14612
|
entries;
|
|
14278
|
-
|
|
14279
|
-
|
|
14613
|
+
_nextExpiry = Infinity;
|
|
14614
|
+
constructor(options = {}) {
|
|
14615
|
+
super(options);
|
|
14280
14616
|
this.entries = new Map();
|
|
14281
14617
|
}
|
|
14618
|
+
setEntry(key, entry) {
|
|
14619
|
+
this.entries.set(key, entry);
|
|
14620
|
+
this._nextExpiry = Math.min(this._nextExpiry, entry.date + entry.maxAge);
|
|
14621
|
+
}
|
|
14622
|
+
deleteEntry(key) {
|
|
14623
|
+
this.entries.delete(key);
|
|
14624
|
+
}
|
|
14625
|
+
expireEntry(key) {
|
|
14626
|
+
this.deleteEntry(key);
|
|
14627
|
+
}
|
|
14628
|
+
clearEntries() {
|
|
14629
|
+
this.entries.clear();
|
|
14630
|
+
this._nextExpiry = Infinity;
|
|
14631
|
+
}
|
|
14632
|
+
prune() {
|
|
14633
|
+
if (Date.now() < this._nextExpiry)
|
|
14634
|
+
return;
|
|
14635
|
+
this._nextExpiry = Infinity;
|
|
14636
|
+
this.entries.forEach((entry, key) => {
|
|
14637
|
+
if (this.isExpired(entry))
|
|
14638
|
+
this.expireEntry(key);
|
|
14639
|
+
const current = this.entries.get(key);
|
|
14640
|
+
if (current !== undefined) {
|
|
14641
|
+
this._nextExpiry = Math.min(this._nextExpiry, current.date + current.maxAge);
|
|
14642
|
+
}
|
|
14643
|
+
});
|
|
14644
|
+
}
|
|
14282
14645
|
/**
|
|
14283
14646
|
* Put some payload in the cache
|
|
14284
14647
|
* @param name The name for the entry
|
|
@@ -14288,9 +14651,11 @@ class ODataInMemoryCache extends ODataBaseCache {
|
|
|
14288
14651
|
* @param tags The tags for the entry
|
|
14289
14652
|
*/
|
|
14290
14653
|
put(name, payload, { maxAge, scope, tags } = {}) {
|
|
14654
|
+
this.checkError();
|
|
14291
14655
|
const entry = this.buildEntry(payload, { maxAge, tags });
|
|
14292
14656
|
const key = this.buildKey([...(scope ?? []), name]);
|
|
14293
|
-
this.
|
|
14657
|
+
this.prune();
|
|
14658
|
+
this.setEntry(key, entry);
|
|
14294
14659
|
}
|
|
14295
14660
|
/**
|
|
14296
14661
|
* Return the payload from the cache if it exists and is not expired
|
|
@@ -14299,266 +14664,416 @@ class ODataInMemoryCache extends ODataBaseCache {
|
|
|
14299
14664
|
* @returns The payload of the entry
|
|
14300
14665
|
*/
|
|
14301
14666
|
get(name, { scope } = {}) {
|
|
14667
|
+
this.checkError();
|
|
14668
|
+
this.prune();
|
|
14302
14669
|
const key = this.buildKey([...(scope || []), name]);
|
|
14303
14670
|
const entry = this.entries.get(key);
|
|
14304
14671
|
return entry !== undefined && !this.isExpired(entry) ? entry.payload : undefined;
|
|
14305
14672
|
}
|
|
14306
|
-
/**
|
|
14307
|
-
* Store the response in the cache
|
|
14308
|
-
* @param req The request with the resource to store the response
|
|
14309
|
-
* @param res The response to store in the cache
|
|
14310
|
-
*/
|
|
14311
|
-
putResponse(req, res) {
|
|
14312
|
-
let scope = this.scope(req);
|
|
14313
|
-
let tags = this.tags(res);
|
|
14314
|
-
this.put(req.cacheKey, res, {
|
|
14315
|
-
maxAge: req.maxAge ?? res.options.maxAge,
|
|
14316
|
-
scope,
|
|
14317
|
-
tags,
|
|
14318
|
-
});
|
|
14319
|
-
}
|
|
14320
|
-
/**
|
|
14321
|
-
* Restore the response from the cache
|
|
14322
|
-
* @param req The request with the resource to get the response
|
|
14323
|
-
* @returns The response from the cache
|
|
14324
|
-
*/
|
|
14325
|
-
getResponse(req) {
|
|
14326
|
-
let scope = this.scope(req);
|
|
14327
|
-
return this.get(req.cacheKey, { scope });
|
|
14328
|
-
}
|
|
14329
14673
|
/**
|
|
14330
14674
|
* Remove all cache entries that are matching with the given options
|
|
14331
14675
|
* @param options The options to forget
|
|
14332
14676
|
*/
|
|
14333
|
-
forget(
|
|
14334
|
-
|
|
14335
|
-
scope.push(name);
|
|
14336
|
-
const key = scope.length > 0 ? this.buildKey(scope) : undefined;
|
|
14677
|
+
forget(options = {}) {
|
|
14678
|
+
this.checkError();
|
|
14337
14679
|
this.entries.forEach((entry, k) => {
|
|
14338
|
-
if (this.
|
|
14339
|
-
(
|
|
14340
|
-
(tags.length > 0 && tags.some((t) => entry.tags.indexOf(t) !== -1)) // Tags
|
|
14341
|
-
) {
|
|
14342
|
-
this.entries.delete(k);
|
|
14343
|
-
}
|
|
14680
|
+
if (this.matches(k, entry, options))
|
|
14681
|
+
this.deleteEntry(k);
|
|
14344
14682
|
});
|
|
14345
14683
|
}
|
|
14346
14684
|
/**
|
|
14347
14685
|
* Remove all cache entries
|
|
14348
14686
|
*/
|
|
14349
14687
|
flush() {
|
|
14350
|
-
this.
|
|
14688
|
+
this.checkError();
|
|
14689
|
+
this.clearEntries();
|
|
14351
14690
|
}
|
|
14691
|
+
/** Return the number of unexpired entries. */
|
|
14352
14692
|
size() {
|
|
14693
|
+
this.checkError();
|
|
14694
|
+
this.prune();
|
|
14353
14695
|
return this.entries.size;
|
|
14354
14696
|
}
|
|
14355
14697
|
}
|
|
14356
14698
|
|
|
14357
|
-
class ODataInStorageCache extends
|
|
14699
|
+
class ODataInStorageCache extends ODataInMemoryCache {
|
|
14358
14700
|
prefix;
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14701
|
+
_storage;
|
|
14702
|
+
_persistent = true;
|
|
14703
|
+
_memoryOnly = new Set();
|
|
14704
|
+
constructor({ prefix, storage, ...options }) {
|
|
14705
|
+
super(options);
|
|
14362
14706
|
this.prefix = prefix;
|
|
14363
|
-
|
|
14707
|
+
try {
|
|
14708
|
+
this._storage = storage ?? globalThis.sessionStorage;
|
|
14709
|
+
if (this._storage === undefined)
|
|
14710
|
+
throw new Error('Web Storage is unavailable');
|
|
14711
|
+
}
|
|
14712
|
+
catch (error) {
|
|
14713
|
+
this.disablePersistence(error);
|
|
14714
|
+
}
|
|
14715
|
+
this.loadFromStorage();
|
|
14716
|
+
}
|
|
14717
|
+
/** The backing storage. Throws if the platform has no Web Storage implementation. */
|
|
14718
|
+
get storage() {
|
|
14719
|
+
if (this._storage === undefined)
|
|
14720
|
+
throw new Error('Web Storage is unavailable');
|
|
14721
|
+
return this._storage;
|
|
14722
|
+
}
|
|
14723
|
+
set storage(storage) {
|
|
14724
|
+
this._storage = storage;
|
|
14725
|
+
this._persistent = true;
|
|
14726
|
+
this.loadFromStorage();
|
|
14364
14727
|
}
|
|
14365
14728
|
buildKey(names) {
|
|
14366
14729
|
return super.buildKey([this.prefix, ...names]);
|
|
14367
14730
|
}
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
this.
|
|
14731
|
+
disablePersistence(cause) {
|
|
14732
|
+
if (!this._persistent)
|
|
14733
|
+
return;
|
|
14734
|
+
this._persistent = false;
|
|
14735
|
+
this.reportError('Web Storage cache persistence failed; using memory only', cause);
|
|
14372
14736
|
}
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14737
|
+
storageKeys() {
|
|
14738
|
+
if (this._storage === undefined)
|
|
14739
|
+
return [];
|
|
14740
|
+
try {
|
|
14741
|
+
const keys = [];
|
|
14742
|
+
const prefix = `${this.prefix}${CACHE_KEY_SEPARATOR}`;
|
|
14743
|
+
for (let index = 0; index < this.storage.length; index++) {
|
|
14744
|
+
const key = this.storage.key(index);
|
|
14745
|
+
if (key !== null && key.startsWith(prefix))
|
|
14746
|
+
keys.push(key);
|
|
14747
|
+
}
|
|
14748
|
+
return keys;
|
|
14749
|
+
}
|
|
14750
|
+
catch (error) {
|
|
14751
|
+
this.disablePersistence(error);
|
|
14752
|
+
return [];
|
|
14753
|
+
}
|
|
14377
14754
|
}
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
.
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14755
|
+
readEntry(key) {
|
|
14756
|
+
let raw;
|
|
14757
|
+
try {
|
|
14758
|
+
raw = this.storage.getItem(key);
|
|
14759
|
+
}
|
|
14760
|
+
catch (error) {
|
|
14761
|
+
this.disablePersistence(error);
|
|
14762
|
+
return this.entries.get(key);
|
|
14763
|
+
}
|
|
14764
|
+
if (raw === null)
|
|
14765
|
+
return undefined;
|
|
14766
|
+
let value;
|
|
14767
|
+
try {
|
|
14768
|
+
value = JSON.parse(raw);
|
|
14769
|
+
}
|
|
14770
|
+
catch (error) {
|
|
14771
|
+
this.deleteEntry(key);
|
|
14772
|
+
this.reportCorruption('Discarding invalid JSON from the Web Storage cache', error);
|
|
14773
|
+
return undefined;
|
|
14774
|
+
}
|
|
14775
|
+
if (!this.isEntry(value)) {
|
|
14776
|
+
this.deleteEntry(key);
|
|
14777
|
+
this.reportCorruption('Discarding an invalid Web Storage cache entry');
|
|
14778
|
+
return undefined;
|
|
14779
|
+
}
|
|
14780
|
+
if (this.isExpired(value)) {
|
|
14781
|
+
this.deleteEntry(key);
|
|
14782
|
+
return undefined;
|
|
14783
|
+
}
|
|
14784
|
+
return value;
|
|
14785
|
+
}
|
|
14786
|
+
loadFromStorage() {
|
|
14787
|
+
if (!this._persistent)
|
|
14788
|
+
return;
|
|
14789
|
+
const keys = this.storageKeys();
|
|
14790
|
+
if (!this._persistent)
|
|
14791
|
+
return;
|
|
14792
|
+
const stored = new Set(keys);
|
|
14793
|
+
this.entries.forEach((_, key) => {
|
|
14794
|
+
if (!stored.has(key) && !this._memoryOnly.has(key))
|
|
14795
|
+
super.deleteEntry(key);
|
|
14796
|
+
});
|
|
14797
|
+
for (const key of keys) {
|
|
14798
|
+
if (!this._persistent)
|
|
14799
|
+
break;
|
|
14800
|
+
if (this._memoryOnly.has(key))
|
|
14801
|
+
continue;
|
|
14802
|
+
const entry = this.readEntry(key);
|
|
14803
|
+
if (entry !== undefined)
|
|
14804
|
+
super.setEntry(key, entry);
|
|
14805
|
+
}
|
|
14806
|
+
this.prune();
|
|
14807
|
+
}
|
|
14808
|
+
// Removal is attempted after a memory fallback so flush/forget still clear persisted data.
|
|
14809
|
+
removeStoredEntry(key) {
|
|
14810
|
+
if (this._storage === undefined)
|
|
14811
|
+
return;
|
|
14812
|
+
try {
|
|
14813
|
+
this.storage.removeItem(key);
|
|
14814
|
+
}
|
|
14815
|
+
catch (error) {
|
|
14816
|
+
this.disablePersistence(error);
|
|
14817
|
+
}
|
|
14818
|
+
}
|
|
14819
|
+
setEntry(key, entry) {
|
|
14820
|
+
super.setEntry(key, entry);
|
|
14821
|
+
const payload = this.isResponseJson(entry.payload) ? entry.payload.body : entry.payload;
|
|
14822
|
+
if (['ArrayBuffer', 'Blob', 'File'].includes(Types.rawType(payload))) {
|
|
14823
|
+
this._memoryOnly.add(key);
|
|
14824
|
+
this.removeStoredEntry(key);
|
|
14825
|
+
return;
|
|
14826
|
+
}
|
|
14827
|
+
this._memoryOnly.delete(key);
|
|
14828
|
+
if (!this._persistent) {
|
|
14829
|
+
this.removeStoredEntry(key);
|
|
14830
|
+
return;
|
|
14831
|
+
}
|
|
14832
|
+
try {
|
|
14833
|
+
const serialized = JSON.stringify(entry);
|
|
14834
|
+
if (!this.isEntry(JSON.parse(serialized))) {
|
|
14835
|
+
throw new TypeError('Cache payload is not JSON serializable');
|
|
14391
14836
|
}
|
|
14392
|
-
|
|
14837
|
+
this.storage.setItem(key, serialized);
|
|
14838
|
+
}
|
|
14839
|
+
catch (error) {
|
|
14840
|
+
this.disablePersistence(error);
|
|
14841
|
+
}
|
|
14393
14842
|
}
|
|
14394
|
-
|
|
14395
|
-
|
|
14396
|
-
|
|
14397
|
-
|
|
14398
|
-
Object.keys(this.storage)
|
|
14399
|
-
.filter((k) => k.startsWith(this.prefix))
|
|
14400
|
-
.forEach((k) => {
|
|
14401
|
-
this.storage.removeItem(k);
|
|
14402
|
-
});
|
|
14843
|
+
deleteEntry(key) {
|
|
14844
|
+
super.deleteEntry(key);
|
|
14845
|
+
this._memoryOnly.delete(key);
|
|
14846
|
+
this.removeStoredEntry(key);
|
|
14403
14847
|
}
|
|
14404
|
-
|
|
14405
|
-
|
|
14406
|
-
|
|
14407
|
-
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
maxAge: req.maxAge ?? res.options.maxAge,
|
|
14414
|
-
scope,
|
|
14415
|
-
tags,
|
|
14416
|
-
});
|
|
14848
|
+
expireEntry(key) {
|
|
14849
|
+
if (this._persistent && !this._memoryOnly.has(key)) {
|
|
14850
|
+
const latest = this.readEntry(key);
|
|
14851
|
+
if (latest !== undefined && !this.isExpired(latest)) {
|
|
14852
|
+
super.setEntry(key, latest);
|
|
14853
|
+
return;
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14856
|
+
super.expireEntry(key);
|
|
14417
14857
|
}
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14426
|
-
|
|
14858
|
+
clearEntries() {
|
|
14859
|
+
const keys = this.storageKeys();
|
|
14860
|
+
super.clearEntries();
|
|
14861
|
+
this._memoryOnly.clear();
|
|
14862
|
+
keys.forEach((key) => this.removeStoredEntry(key));
|
|
14863
|
+
}
|
|
14864
|
+
/** Read a persisted entry, or its in-memory fallback, without renewing its age. */
|
|
14865
|
+
get(name, { scope } = {}) {
|
|
14866
|
+
const key = this.buildKey([...(scope ?? []), name]);
|
|
14867
|
+
if (this._persistent && !this._memoryOnly.has(key)) {
|
|
14868
|
+
const entry = this.readEntry(key);
|
|
14869
|
+
if (entry === undefined)
|
|
14870
|
+
super.deleteEntry(key);
|
|
14871
|
+
else
|
|
14872
|
+
super.setEntry(key, entry);
|
|
14873
|
+
}
|
|
14874
|
+
return super.get(name, { scope });
|
|
14427
14875
|
}
|
|
14876
|
+
/** Remove matching entries from both persistent storage and the memory fallback. */
|
|
14877
|
+
forget(options = {}) {
|
|
14878
|
+
this.loadFromStorage();
|
|
14879
|
+
super.forget(options);
|
|
14880
|
+
}
|
|
14881
|
+
/** Count unexpired entries, including memory-only binary responses. */
|
|
14428
14882
|
size() {
|
|
14429
|
-
|
|
14883
|
+
this.loadFromStorage();
|
|
14884
|
+
return super.size();
|
|
14430
14885
|
}
|
|
14431
14886
|
}
|
|
14432
14887
|
|
|
14433
|
-
class ODataIndexedDBCache extends
|
|
14888
|
+
class ODataIndexedDBCache extends ODataInMemoryCache {
|
|
14434
14889
|
name;
|
|
14435
14890
|
version;
|
|
14436
14891
|
store;
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
|
|
14440
|
-
|
|
14892
|
+
_database;
|
|
14893
|
+
_persistent = true;
|
|
14894
|
+
_hydrated = false;
|
|
14895
|
+
_pending = [];
|
|
14896
|
+
_writes = Promise.resolve();
|
|
14897
|
+
_ready;
|
|
14898
|
+
constructor({ name = 'ODataCache', store = 'cache', version = 1, ...options } = {}) {
|
|
14899
|
+
super(options);
|
|
14441
14900
|
this.name = name;
|
|
14442
14901
|
this.store = store;
|
|
14443
14902
|
this.version = version;
|
|
14444
|
-
this.
|
|
14445
|
-
|
|
14903
|
+
this._ready = this.initialize().catch((error) => {
|
|
14904
|
+
const flushed = this._pending.some((operation) => operation.kind === 'flush');
|
|
14905
|
+
this.disablePersistence(error);
|
|
14906
|
+
this._hydrated = true;
|
|
14907
|
+
this._pending = [];
|
|
14908
|
+
if (flushed)
|
|
14909
|
+
this.persist((store) => store.clear(), true);
|
|
14910
|
+
});
|
|
14911
|
+
}
|
|
14912
|
+
/** Wait for hydration and queued writes, or an explicitly reported memory-only fallback. */
|
|
14913
|
+
ready() {
|
|
14914
|
+
return this._ready.then(() => this._writes).then(() => this.checkError());
|
|
14915
|
+
}
|
|
14916
|
+
reportError(message, cause) {
|
|
14917
|
+
// IndexedDB event callbacks cannot throw into the caller's observable.
|
|
14918
|
+
try {
|
|
14919
|
+
super.reportError(message, cause);
|
|
14920
|
+
}
|
|
14921
|
+
catch (error) {
|
|
14922
|
+
if (!(error instanceof Error))
|
|
14923
|
+
throw error;
|
|
14924
|
+
this._error = error;
|
|
14925
|
+
}
|
|
14926
|
+
}
|
|
14927
|
+
// The database stays open after a fallback so flush/forget can still remove persisted data.
|
|
14928
|
+
disablePersistence(cause) {
|
|
14929
|
+
if (!this._persistent)
|
|
14930
|
+
return;
|
|
14931
|
+
this._persistent = false;
|
|
14932
|
+
this.reportError('IndexedDB cache persistence failed; using memory only', cause);
|
|
14446
14933
|
}
|
|
14447
|
-
|
|
14934
|
+
openDb() {
|
|
14448
14935
|
return new Promise((resolve, reject) => {
|
|
14449
14936
|
const request = indexedDB.open(this.name, this.version);
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14937
|
+
let failed = false;
|
|
14938
|
+
const fail = (error) => {
|
|
14939
|
+
failed = true;
|
|
14940
|
+
reject(error);
|
|
14941
|
+
};
|
|
14942
|
+
request.onupgradeneeded = () => {
|
|
14943
|
+
if (failed) {
|
|
14944
|
+
request.transaction?.abort();
|
|
14945
|
+
return;
|
|
14454
14946
|
}
|
|
14947
|
+
const db = request.result;
|
|
14948
|
+
if (!db.objectStoreNames.contains(this.store))
|
|
14949
|
+
db.createObjectStore(this.store);
|
|
14455
14950
|
};
|
|
14456
|
-
request.onsuccess =
|
|
14457
|
-
const db =
|
|
14458
|
-
|
|
14951
|
+
request.onsuccess = () => {
|
|
14952
|
+
const db = request.result;
|
|
14953
|
+
if (failed) {
|
|
14954
|
+
db.close();
|
|
14955
|
+
return;
|
|
14956
|
+
}
|
|
14957
|
+
db.onversionchange = () => {
|
|
14958
|
+
db.close();
|
|
14959
|
+
this._database = undefined;
|
|
14960
|
+
this.disablePersistence(new Error('IndexedDB cache version changed'));
|
|
14961
|
+
};
|
|
14459
14962
|
resolve(db);
|
|
14460
14963
|
};
|
|
14461
|
-
request.onerror = (
|
|
14462
|
-
|
|
14463
|
-
};
|
|
14964
|
+
request.onerror = () => fail(request.error);
|
|
14965
|
+
request.onblocked = () => fail(new Error('IndexedDB cache opening was blocked'));
|
|
14464
14966
|
});
|
|
14465
14967
|
}
|
|
14466
14968
|
loadFromDb(db) {
|
|
14467
14969
|
return new Promise((resolve, reject) => {
|
|
14468
|
-
const
|
|
14469
|
-
const
|
|
14470
|
-
const request = store.openCursor();
|
|
14471
|
-
request.onsuccess = (
|
|
14472
|
-
const cursor =
|
|
14473
|
-
if (cursor)
|
|
14474
|
-
|
|
14475
|
-
|
|
14970
|
+
const entries = new Map();
|
|
14971
|
+
const transaction = db.transaction(this.store, 'readwrite');
|
|
14972
|
+
const request = transaction.objectStore(this.store).openCursor();
|
|
14973
|
+
request.onsuccess = () => {
|
|
14974
|
+
const cursor = request.result;
|
|
14975
|
+
if (cursor === null)
|
|
14976
|
+
return;
|
|
14977
|
+
const value = cursor.value;
|
|
14978
|
+
if (typeof cursor.key !== 'string' || !this.isEntry(value)) {
|
|
14979
|
+
cursor.delete();
|
|
14980
|
+
this.reportCorruption('Discarding an invalid IndexedDB cache entry');
|
|
14981
|
+
}
|
|
14982
|
+
else if (this.isExpired(value)) {
|
|
14983
|
+
cursor.delete();
|
|
14476
14984
|
}
|
|
14477
14985
|
else {
|
|
14478
|
-
|
|
14986
|
+
entries.set(cursor.key, value);
|
|
14479
14987
|
}
|
|
14988
|
+
cursor.continue();
|
|
14480
14989
|
};
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
});
|
|
14485
|
-
}
|
|
14486
|
-
async
|
|
14487
|
-
const db = await this.
|
|
14488
|
-
|
|
14990
|
+
transaction.oncomplete = () => resolve(entries);
|
|
14991
|
+
transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB load failed'));
|
|
14992
|
+
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB load aborted'));
|
|
14993
|
+
});
|
|
14994
|
+
}
|
|
14995
|
+
async initialize() {
|
|
14996
|
+
const db = await this.openDb();
|
|
14997
|
+
this._database = db;
|
|
14998
|
+
const entries = await this.loadFromDb(db);
|
|
14999
|
+
if (!this._persistent) {
|
|
15000
|
+
this._hydrated = true;
|
|
15001
|
+
this._pending = [];
|
|
15002
|
+
return;
|
|
15003
|
+
}
|
|
15004
|
+
const pending = this._pending;
|
|
15005
|
+
this._pending = [];
|
|
15006
|
+
super.clearEntries();
|
|
15007
|
+
entries.forEach((entry, key) => super.setEntry(key, entry));
|
|
15008
|
+
this._hydrated = true;
|
|
15009
|
+
for (const operation of pending) {
|
|
15010
|
+
switch (operation.kind) {
|
|
15011
|
+
case 'put':
|
|
15012
|
+
this.setEntry(operation.key, operation.entry);
|
|
15013
|
+
break;
|
|
15014
|
+
case 'forget':
|
|
15015
|
+
this.forget(operation.options);
|
|
15016
|
+
break;
|
|
15017
|
+
case 'flush':
|
|
15018
|
+
this.flush();
|
|
15019
|
+
break;
|
|
15020
|
+
}
|
|
15021
|
+
}
|
|
15022
|
+
this.prune();
|
|
15023
|
+
await this._writes;
|
|
14489
15024
|
}
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
15025
|
+
persist(operation, removal = false) {
|
|
15026
|
+
this._writes = this._writes
|
|
15027
|
+
.then(() => {
|
|
15028
|
+
const db = this._database;
|
|
15029
|
+
if (db === undefined || (!this._persistent && !removal))
|
|
15030
|
+
return;
|
|
14495
15031
|
return new Promise((resolve, reject) => {
|
|
14496
15032
|
const transaction = db.transaction(this.store, 'readwrite');
|
|
14497
|
-
const store = transaction.objectStore(this.store);
|
|
14498
|
-
store.put(entry, key);
|
|
14499
15033
|
transaction.oncomplete = () => resolve();
|
|
14500
|
-
transaction.onerror = (
|
|
15034
|
+
transaction.onerror = () => reject(transaction.error ?? new Error('IndexedDB write failed'));
|
|
15035
|
+
transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB write aborted'));
|
|
15036
|
+
try {
|
|
15037
|
+
operation(transaction.objectStore(this.store));
|
|
15038
|
+
}
|
|
15039
|
+
catch (error) {
|
|
15040
|
+
transaction.abort();
|
|
15041
|
+
reject(error);
|
|
15042
|
+
}
|
|
14501
15043
|
});
|
|
14502
|
-
})
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14506
|
-
|
|
14507
|
-
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
maxAge: req.maxAge ?? res.options.maxAge,
|
|
14514
|
-
scope,
|
|
14515
|
-
tags,
|
|
14516
|
-
});
|
|
15044
|
+
})
|
|
15045
|
+
.catch((error) => this.disablePersistence(error));
|
|
15046
|
+
}
|
|
15047
|
+
setEntry(key, entry) {
|
|
15048
|
+
super.setEntry(key, entry);
|
|
15049
|
+
if (!this._hydrated)
|
|
15050
|
+
this._pending.push({ kind: 'put', key, entry });
|
|
15051
|
+
else if (this._persistent)
|
|
15052
|
+
this.persist((store) => store.put(entry, key));
|
|
15053
|
+
else
|
|
15054
|
+
this.persist((store) => store.delete(key), true);
|
|
14517
15055
|
}
|
|
14518
|
-
|
|
14519
|
-
|
|
14520
|
-
|
|
14521
|
-
|
|
15056
|
+
deleteEntry(key) {
|
|
15057
|
+
super.deleteEntry(key);
|
|
15058
|
+
if (this._hydrated)
|
|
15059
|
+
this.persist((store) => store.delete(key), true);
|
|
14522
15060
|
}
|
|
14523
|
-
|
|
14524
|
-
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
if (keysToDelete.length > 0) {
|
|
14537
|
-
this.withDb((db) => {
|
|
14538
|
-
return new Promise((resolve, reject) => {
|
|
14539
|
-
const transaction = db.transaction(this.store, 'readwrite');
|
|
14540
|
-
const store = transaction.objectStore(this.store);
|
|
14541
|
-
keysToDelete.forEach((k) => store.delete(k));
|
|
14542
|
-
transaction.oncomplete = () => resolve();
|
|
14543
|
-
transaction.onerror = (event) => reject(event.target.error);
|
|
14544
|
-
});
|
|
15061
|
+
clearEntries() {
|
|
15062
|
+
super.clearEntries();
|
|
15063
|
+
if (!this._hydrated)
|
|
15064
|
+
this._pending.push({ kind: 'flush' });
|
|
15065
|
+
else
|
|
15066
|
+
this.persist((store) => store.clear(), true);
|
|
15067
|
+
}
|
|
15068
|
+
/** Invalidate loaded entries and record filters that must also apply during hydration. */
|
|
15069
|
+
forget(options = {}) {
|
|
15070
|
+
if (!this._hydrated) {
|
|
15071
|
+
this._pending.push({
|
|
15072
|
+
kind: 'forget',
|
|
15073
|
+
options: { ...options, scope: [...(options.scope ?? [])], tags: [...(options.tags ?? [])] },
|
|
14545
15074
|
});
|
|
14546
15075
|
}
|
|
14547
|
-
|
|
14548
|
-
flush() {
|
|
14549
|
-
this.entries = new Map();
|
|
14550
|
-
this.withDb((db) => {
|
|
14551
|
-
return new Promise((resolve, reject) => {
|
|
14552
|
-
const transaction = db.transaction(this.store, 'readwrite');
|
|
14553
|
-
const store = transaction.objectStore(this.store);
|
|
14554
|
-
store.clear();
|
|
14555
|
-
transaction.oncomplete = () => resolve();
|
|
14556
|
-
transaction.onerror = (event) => reject(event.target.error);
|
|
14557
|
-
});
|
|
14558
|
-
});
|
|
14559
|
-
}
|
|
14560
|
-
size() {
|
|
14561
|
-
return this.entries.size;
|
|
15076
|
+
super.forget(options);
|
|
14562
15077
|
}
|
|
14563
15078
|
}
|
|
14564
15079
|
|