@khanacademy/wonder-blocks-data 3.1.2 → 5.0.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/CHANGELOG.md +41 -0
- package/dist/es/index.js +408 -349
- package/dist/index.js +568 -467
- package/docs.md +17 -35
- package/package.json +1 -1
- package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
- package/src/__tests__/generated-snapshot.test.js +60 -126
- package/src/components/__tests__/data.test.js +373 -313
- package/src/components/__tests__/intercept-requests.test.js +58 -0
- package/src/components/data.js +139 -21
- package/src/components/data.md +38 -69
- package/src/components/gql-router.js +1 -1
- package/src/components/intercept-context.js +6 -3
- package/src/components/intercept-requests.js +69 -0
- package/src/components/intercept-requests.md +54 -0
- package/src/components/track-data.md +9 -23
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
- package/src/hooks/__tests__/use-gql.test.js +1 -0
- package/src/hooks/__tests__/use-request-interception.test.js +255 -0
- package/src/hooks/__tests__/use-server-effect.test.js +217 -0
- package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
- package/src/hooks/use-gql.js +39 -31
- package/src/hooks/use-request-interception.js +54 -0
- package/src/hooks/use-server-effect.js +45 -0
- package/src/hooks/use-shared-cache.js +106 -0
- package/src/index.js +17 -20
- package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
- package/src/util/__tests__/request-fulfillment.test.js +42 -85
- package/src/util/__tests__/request-tracking.test.js +72 -191
- package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
- package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
- package/src/util/__tests__/ssr-cache.test.js +639 -0
- package/src/util/gql-types.js +5 -10
- package/src/util/request-fulfillment.js +36 -44
- package/src/util/request-tracking.js +62 -75
- package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
- package/src/util/scoped-in-memory-cache.js +149 -0
- package/src/util/ssr-cache.js +206 -0
- package/src/util/types.js +43 -108
- package/src/components/__tests__/intercept-data.test.js +0 -87
- package/src/components/intercept-data.js +0 -77
- package/src/components/intercept-data.md +0 -65
- package/src/hooks/__tests__/use-data.test.js +0 -826
- package/src/hooks/use-data.js +0 -143
- package/src/util/__tests__/memory-cache.test.js +0 -446
- package/src/util/__tests__/request-handler.test.js +0 -121
- package/src/util/__tests__/response-cache.test.js +0 -879
- package/src/util/memory-cache.js +0 -187
- package/src/util/request-handler.js +0 -42
- package/src/util/request-handler.md +0 -51
- package/src/util/response-cache.js +0 -213
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import {
|
|
2
|
+
import {SsrCache} from "./ssr-cache.js";
|
|
3
3
|
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
type Subcache = {
|
|
7
|
-
[key: string]: Promise<any>,
|
|
8
|
-
...
|
|
9
|
-
};
|
|
4
|
+
import type {ValidCacheData, CachedResponse} from "./types.js";
|
|
10
5
|
|
|
11
6
|
type RequestCache = {
|
|
12
|
-
[
|
|
7
|
+
[id: string]: Promise<any>,
|
|
13
8
|
...
|
|
14
9
|
};
|
|
15
10
|
|
|
@@ -23,44 +18,39 @@ export class RequestFulfillment {
|
|
|
23
18
|
return _default;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
_responseCache:
|
|
21
|
+
_responseCache: SsrCache;
|
|
27
22
|
_requests: RequestCache = {};
|
|
28
23
|
|
|
29
|
-
constructor(responseCache: ?
|
|
30
|
-
this._responseCache = responseCache ||
|
|
24
|
+
constructor(responseCache: ?SsrCache = undefined) {
|
|
25
|
+
this._responseCache = responseCache || SsrCache.Default;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
|
-
_getHandlerSubcache: <TOptions, TData: ValidData>(
|
|
34
|
-
handler: IRequestHandler<TOptions, TData>,
|
|
35
|
-
) => Subcache = <TOptions, TData: ValidData>(
|
|
36
|
-
handler: IRequestHandler<TOptions, TData>,
|
|
37
|
-
): Subcache => {
|
|
38
|
-
if (!this._requests[handler.type]) {
|
|
39
|
-
this._requests[handler.type] = {};
|
|
40
|
-
}
|
|
41
|
-
return this._requests[handler.type];
|
|
42
|
-
};
|
|
43
|
-
|
|
44
28
|
/**
|
|
45
29
|
* Get a promise of a request for a given handler and options.
|
|
46
30
|
*
|
|
47
31
|
* This will return an inflight request if one exists, otherwise it will
|
|
48
32
|
* make a new request. Inflight requests are deleted once they resolve.
|
|
49
33
|
*/
|
|
50
|
-
fulfill: <
|
|
51
|
-
|
|
52
|
-
options:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
fulfill: <TData: ValidCacheData>(
|
|
35
|
+
id: string,
|
|
36
|
+
options: {|
|
|
37
|
+
handler: () => Promise<?TData>,
|
|
38
|
+
hydrate?: boolean,
|
|
39
|
+
|},
|
|
40
|
+
) => Promise<?CachedResponse<TData>> = <TData: ValidCacheData>(
|
|
41
|
+
id: string,
|
|
42
|
+
{
|
|
43
|
+
handler,
|
|
44
|
+
hydrate = true,
|
|
45
|
+
}: {|
|
|
46
|
+
handler: () => Promise<?TData>,
|
|
47
|
+
hydrate?: boolean,
|
|
48
|
+
|},
|
|
49
|
+
): Promise<?CachedResponse<TData>> => {
|
|
60
50
|
/**
|
|
61
51
|
* If we have an inflight request, we'll provide that.
|
|
62
52
|
*/
|
|
63
|
-
const inflight =
|
|
53
|
+
const inflight = this._requests[id];
|
|
64
54
|
if (inflight) {
|
|
65
55
|
return inflight;
|
|
66
56
|
}
|
|
@@ -70,36 +60,38 @@ export class RequestFulfillment {
|
|
|
70
60
|
*/
|
|
71
61
|
const {cacheData, cacheError} = this._responseCache;
|
|
72
62
|
try {
|
|
73
|
-
const request = handler
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
63
|
+
const request = handler()
|
|
64
|
+
.then((data: ?TData) => {
|
|
65
|
+
delete this._requests[id];
|
|
66
|
+
if (data == null) {
|
|
67
|
+
// Request aborted. We won't cache this.
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
77
71
|
/**
|
|
78
72
|
* Let's cache the data!
|
|
79
73
|
*
|
|
80
74
|
* NOTE: This only caches when we're server side.
|
|
81
75
|
*/
|
|
82
|
-
return cacheData<
|
|
76
|
+
return cacheData<TData>(id, data, hydrate);
|
|
83
77
|
})
|
|
84
78
|
.catch((error: string | Error) => {
|
|
85
|
-
delete
|
|
79
|
+
delete this._requests[id];
|
|
86
80
|
/**
|
|
87
81
|
* Let's cache the error!
|
|
88
82
|
*
|
|
89
83
|
* NOTE: This only caches when we're server side.
|
|
90
84
|
*/
|
|
91
|
-
return cacheError<
|
|
85
|
+
return cacheError<TData>(id, error, hydrate);
|
|
92
86
|
});
|
|
93
|
-
|
|
87
|
+
this._requests[id] = request;
|
|
94
88
|
return request;
|
|
95
89
|
} catch (e) {
|
|
96
90
|
/**
|
|
97
91
|
* In this case, we don't cache an inflight request, because there
|
|
98
92
|
* really isn't one.
|
|
99
93
|
*/
|
|
100
|
-
return Promise.resolve(
|
|
101
|
-
cacheError<TOptions, TData>(handler, options, e),
|
|
102
|
-
);
|
|
94
|
+
return Promise.resolve(cacheError<TData>(id, e, hydrate));
|
|
103
95
|
}
|
|
104
96
|
};
|
|
105
97
|
}
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
3
|
+
import {SsrCache} from "./ssr-cache.js";
|
|
4
4
|
import {RequestFulfillment} from "./request-fulfillment.js";
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type {ResponseCache, ValidCacheData} from "./types.js";
|
|
7
7
|
|
|
8
|
-
type TrackerFn =
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
8
|
+
type TrackerFn = <TData: ValidCacheData>(
|
|
9
|
+
id: string,
|
|
10
|
+
handler: () => Promise<?TData>,
|
|
11
|
+
hydrate: boolean,
|
|
12
|
+
) => void;
|
|
14
13
|
|
|
15
14
|
type RequestCache = {
|
|
16
|
-
[
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
15
|
+
[id: string]: {|
|
|
16
|
+
hydrate?: boolean,
|
|
17
|
+
handler: () => Promise<?any>,
|
|
18
|
+
|},
|
|
20
19
|
...
|
|
21
20
|
};
|
|
22
21
|
|
|
@@ -50,13 +49,12 @@ export class RequestTracker {
|
|
|
50
49
|
/**
|
|
51
50
|
* These are the caches for tracked requests, their handlers, and responses.
|
|
52
51
|
*/
|
|
53
|
-
_trackedHandlers: HandlerCache = {};
|
|
54
52
|
_trackedRequests: RequestCache = {};
|
|
55
|
-
_responseCache:
|
|
53
|
+
_responseCache: SsrCache;
|
|
56
54
|
_requestFulfillment: RequestFulfillment;
|
|
57
55
|
|
|
58
|
-
constructor(responseCache: ?
|
|
59
|
-
this._responseCache = responseCache ||
|
|
56
|
+
constructor(responseCache: ?SsrCache = undefined) {
|
|
57
|
+
this._responseCache = responseCache || SsrCache.Default;
|
|
60
58
|
this._requestFulfillment = new RequestFulfillment(responseCache);
|
|
61
59
|
}
|
|
62
60
|
|
|
@@ -66,26 +64,23 @@ export class RequestTracker {
|
|
|
66
64
|
* This method caches a request and its handler for use during server-side
|
|
67
65
|
* rendering to allow us to fulfill requests before producing a final render.
|
|
68
66
|
*/
|
|
69
|
-
trackDataRequest: (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
*/
|
|
79
|
-
if (this._trackedHandlers[type] == null) {
|
|
80
|
-
this._trackedHandlers[type] = handler;
|
|
81
|
-
this._trackedRequests[type] = {};
|
|
82
|
-
}
|
|
83
|
-
|
|
67
|
+
trackDataRequest: <TData: ValidCacheData>(
|
|
68
|
+
id: string,
|
|
69
|
+
handler: () => Promise<?TData>,
|
|
70
|
+
hydrate: boolean,
|
|
71
|
+
) => void = <TData: ValidCacheData>(
|
|
72
|
+
id: string,
|
|
73
|
+
handler: () => Promise<?TData>,
|
|
74
|
+
hydrate: boolean,
|
|
75
|
+
): void => {
|
|
84
76
|
/**
|
|
85
77
|
* If we don't already have this tracked, then let's track it.
|
|
86
78
|
*/
|
|
87
|
-
if (this._trackedRequests[
|
|
88
|
-
this._trackedRequests[
|
|
79
|
+
if (this._trackedRequests[id] == null) {
|
|
80
|
+
this._trackedRequests[id] = {
|
|
81
|
+
handler,
|
|
82
|
+
hydrate,
|
|
83
|
+
};
|
|
89
84
|
}
|
|
90
85
|
};
|
|
91
86
|
|
|
@@ -93,7 +88,6 @@ export class RequestTracker {
|
|
|
93
88
|
* Reset our tracking info.
|
|
94
89
|
*/
|
|
95
90
|
reset: () => void = () => {
|
|
96
|
-
this._trackedHandlers = {};
|
|
97
91
|
this._trackedRequests = {};
|
|
98
92
|
};
|
|
99
93
|
|
|
@@ -114,52 +108,45 @@ export class RequestTracker {
|
|
|
114
108
|
* Calling this method marks tracked requests as fulfilled; requests are
|
|
115
109
|
* removed from the list of tracked requests by calling this method.
|
|
116
110
|
*
|
|
117
|
-
* @returns {Promise<
|
|
118
|
-
* as a result of fulfilling the tracked requests.
|
|
111
|
+
* @returns {Promise<ResponseCache>} The promise of the data that was
|
|
112
|
+
* cached as a result of fulfilling the tracked requests.
|
|
119
113
|
*/
|
|
120
|
-
fulfillTrackedRequests: () => Promise
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const promises = [];
|
|
114
|
+
fulfillTrackedRequests: () => Promise<ResponseCache> =
|
|
115
|
+
(): Promise<ResponseCache> => {
|
|
116
|
+
const promises = [];
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
const handler = this._trackedHandlers[handlerType];
|
|
127
|
-
|
|
128
|
-
// For each handler, we will perform the request fulfillments!
|
|
129
|
-
const requests = this._trackedRequests[handlerType];
|
|
130
|
-
for (const requestKey of Object.keys(requests)) {
|
|
118
|
+
for (const requestKey of Object.keys(this._trackedRequests)) {
|
|
131
119
|
const promise = this._requestFulfillment.fulfill(
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
requestKey,
|
|
121
|
+
this._trackedRequests[requestKey],
|
|
134
122
|
);
|
|
135
123
|
promises.push(promise);
|
|
136
124
|
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Clear out our tracked info.
|
|
141
|
-
*
|
|
142
|
-
* We call this now for a simpler API.
|
|
143
|
-
*
|
|
144
|
-
* If we reset the tracked calls after all promises resolve, any
|
|
145
|
-
* requst tracking done while promises are in flight would be lost.
|
|
146
|
-
*
|
|
147
|
-
* If we don't reset at all, then we have to expose the `reset` call
|
|
148
|
-
* for consumers to use, or they'll only ever be able to accumulate
|
|
149
|
-
* more and more tracked requests, having to fulfill them all every
|
|
150
|
-
* time.
|
|
151
|
-
*
|
|
152
|
-
* Calling it here means we can have multiple "track -> request" cycles
|
|
153
|
-
* in a row and in an easy to reason about manner.
|
|
154
|
-
*
|
|
155
|
-
*/
|
|
156
|
-
this.reset();
|
|
157
125
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Clear out our tracked info.
|
|
128
|
+
*
|
|
129
|
+
* We call this now for a simpler API.
|
|
130
|
+
*
|
|
131
|
+
* If we reset the tracked calls after all promises resolve, any
|
|
132
|
+
* requst tracking done while promises are in flight would be lost.
|
|
133
|
+
*
|
|
134
|
+
* If we don't reset at all, then we have to expose the `reset` call
|
|
135
|
+
* for consumers to use, or they'll only ever be able to accumulate
|
|
136
|
+
* more and more tracked requests, having to fulfill them all every
|
|
137
|
+
* time.
|
|
138
|
+
*
|
|
139
|
+
* Calling it here means we can have multiple "track -> request" cycles
|
|
140
|
+
* in a row and in an easy to reason about manner.
|
|
141
|
+
*
|
|
142
|
+
*/
|
|
143
|
+
this.reset();
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Let's wait for everything to fulfill, and then clone the cached data.
|
|
147
|
+
*/
|
|
148
|
+
return Promise.all(promises).then(() =>
|
|
149
|
+
this._responseCache.cloneHydratableData(),
|
|
150
|
+
);
|
|
151
|
+
};
|
|
165
152
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import type {
|
|
2
|
+
import type {ValidCacheData, CachedResponse, Result} from "./types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Turns a cache entry into a stateful result.
|
|
6
6
|
*/
|
|
7
|
-
export const
|
|
8
|
-
cacheEntry: ?
|
|
7
|
+
export const resultFromCachedResponse = <TData: ValidCacheData>(
|
|
8
|
+
cacheEntry: ?CachedResponse<TData>,
|
|
9
9
|
): Result<TData> => {
|
|
10
10
|
// No cache entry means we didn't load one yet.
|
|
11
11
|
if (cacheEntry == null) {
|
|
@@ -15,24 +15,21 @@ export const resultFromCacheEntry = <TData: ValidData>(
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const {data, error} = cacheEntry;
|
|
18
|
-
|
|
19
|
-
if (data != null) {
|
|
18
|
+
if (error != null) {
|
|
20
19
|
return {
|
|
21
|
-
status: "
|
|
22
|
-
|
|
20
|
+
status: "error",
|
|
21
|
+
error,
|
|
23
22
|
};
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
if (
|
|
27
|
-
// We should never get here ever.
|
|
25
|
+
if (data != null) {
|
|
28
26
|
return {
|
|
29
|
-
status: "
|
|
30
|
-
|
|
27
|
+
status: "success",
|
|
28
|
+
data,
|
|
31
29
|
};
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
return {
|
|
35
|
-
status: "
|
|
36
|
-
error,
|
|
33
|
+
status: "aborted",
|
|
37
34
|
};
|
|
38
35
|
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import {KindError, Errors, clone} from "@khanacademy/wonder-stuff-core";
|
|
3
|
+
import type {ValidCacheData, ScopedCache} from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Describe an in-memory cache.
|
|
7
|
+
*/
|
|
8
|
+
export class ScopedInMemoryCache {
|
|
9
|
+
_cache: ScopedCache;
|
|
10
|
+
|
|
11
|
+
constructor(initialCache: ScopedCache = Object.freeze({})) {
|
|
12
|
+
try {
|
|
13
|
+
this._cache = clone(initialCache);
|
|
14
|
+
} catch (e) {
|
|
15
|
+
throw new KindError(
|
|
16
|
+
`An error occurred trying to initialize from a response cache snapshot: ${e}`,
|
|
17
|
+
Errors.InvalidInput,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Indicate if this cache is being used or not.
|
|
24
|
+
*
|
|
25
|
+
* When the cache has entries, returns `true`; otherwise, returns `false`.
|
|
26
|
+
*/
|
|
27
|
+
get inUse(): boolean {
|
|
28
|
+
return Object.keys(this._cache).length > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Set a value in the cache.
|
|
33
|
+
*/
|
|
34
|
+
set: <TValue: ValidCacheData>(
|
|
35
|
+
scope: string,
|
|
36
|
+
id: string,
|
|
37
|
+
value: TValue,
|
|
38
|
+
) => void = <TValue: ValidCacheData>(scope, id, value: TValue): void => {
|
|
39
|
+
if (!id || typeof id !== "string") {
|
|
40
|
+
throw new KindError(
|
|
41
|
+
"id must be non-empty string",
|
|
42
|
+
Errors.InvalidInput,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!scope || typeof scope !== "string") {
|
|
47
|
+
throw new KindError(
|
|
48
|
+
"scope must be non-empty string",
|
|
49
|
+
Errors.InvalidInput,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (typeof value === "function") {
|
|
54
|
+
throw new KindError(
|
|
55
|
+
"value must be a non-function value",
|
|
56
|
+
Errors.InvalidInput,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this._cache[scope] = this._cache[scope] ?? {};
|
|
61
|
+
this._cache[scope][id] = Object.freeze(clone(value));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Retrieve a value from the cache.
|
|
66
|
+
*/
|
|
67
|
+
get: (scope: string, id: string) => ?ValidCacheData = (
|
|
68
|
+
scope,
|
|
69
|
+
id,
|
|
70
|
+
): ?ValidCacheData => {
|
|
71
|
+
return this._cache[scope]?.[id] ?? null;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Purge an item from the cache.
|
|
76
|
+
*/
|
|
77
|
+
purge: (scope: string, id: string) => void = (scope, id) => {
|
|
78
|
+
if (!this._cache[scope]?.[id]) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
delete this._cache[scope][id];
|
|
82
|
+
if (Object.keys(this._cache[scope]).length === 0) {
|
|
83
|
+
delete this._cache[scope];
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Purge a scope of items that match the given predicate.
|
|
89
|
+
*
|
|
90
|
+
* If the predicate is omitted, then all items in the scope are purged.
|
|
91
|
+
*/
|
|
92
|
+
purgeScope: (
|
|
93
|
+
scope: string,
|
|
94
|
+
predicate?: (id: string, value: ValidCacheData) => boolean,
|
|
95
|
+
) => void = (scope, predicate) => {
|
|
96
|
+
if (!this._cache[scope]) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (predicate == null) {
|
|
101
|
+
delete this._cache[scope];
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const key of Object.keys(this._cache[scope])) {
|
|
106
|
+
if (predicate(key, this._cache[scope][key])) {
|
|
107
|
+
delete this._cache[scope][key];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (Object.keys(this._cache[scope]).length === 0) {
|
|
111
|
+
delete this._cache[scope];
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Purge all items from the cache that match the given predicate.
|
|
117
|
+
*
|
|
118
|
+
* If the predicate is omitted, then all items in the cache are purged.
|
|
119
|
+
*/
|
|
120
|
+
purgeAll: (
|
|
121
|
+
predicate?: (
|
|
122
|
+
scope: string,
|
|
123
|
+
id: string,
|
|
124
|
+
value: ValidCacheData,
|
|
125
|
+
) => boolean,
|
|
126
|
+
) => void = (predicate) => {
|
|
127
|
+
if (predicate == null) {
|
|
128
|
+
this._cache = {};
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (const scope of Object.keys(this._cache)) {
|
|
133
|
+
this.purgeScope(scope, (id, value) => predicate(scope, id, value));
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Clone the cache.
|
|
139
|
+
*/
|
|
140
|
+
clone: () => ScopedCache = () => {
|
|
141
|
+
try {
|
|
142
|
+
return clone(this._cache);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`An error occurred while trying to clone the cache: ${e}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|