@khanacademy/wonder-blocks-data 13.0.10 → 13.0.12
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 +15 -0
- package/package.json +3 -3
- package/src/components/__tests__/data.test.tsx +0 -832
- package/src/components/__tests__/gql-router.test.tsx +0 -63
- package/src/components/__tests__/intercept-requests.test.tsx +0 -57
- package/src/components/__tests__/track-data.test.tsx +0 -56
- package/src/components/data.ts +0 -73
- package/src/components/gql-router.tsx +0 -63
- package/src/components/intercept-context.ts +0 -19
- package/src/components/intercept-requests.tsx +0 -67
- package/src/components/track-data.tsx +0 -28
- package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.ts.snap +0 -17
- package/src/hooks/__tests__/use-cached-effect.test.tsx +0 -789
- package/src/hooks/__tests__/use-gql-router-context.test.tsx +0 -132
- package/src/hooks/__tests__/use-gql.test.tsx +0 -204
- package/src/hooks/__tests__/use-hydratable-effect.test.ts +0 -708
- package/src/hooks/__tests__/use-request-interception.test.tsx +0 -254
- package/src/hooks/__tests__/use-server-effect.test.ts +0 -293
- package/src/hooks/__tests__/use-shared-cache.test.ts +0 -263
- package/src/hooks/use-cached-effect.ts +0 -297
- package/src/hooks/use-gql-router-context.ts +0 -49
- package/src/hooks/use-gql.ts +0 -58
- package/src/hooks/use-hydratable-effect.ts +0 -201
- package/src/hooks/use-request-interception.ts +0 -53
- package/src/hooks/use-server-effect.ts +0 -75
- package/src/hooks/use-shared-cache.ts +0 -107
- package/src/index.ts +0 -46
- package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.ts.snap +0 -19
- package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.ts.snap +0 -19
- package/src/util/__tests__/get-gql-data-from-response.test.ts +0 -186
- package/src/util/__tests__/get-gql-request-id.test.ts +0 -132
- package/src/util/__tests__/graphql-document-node-parser.test.ts +0 -535
- package/src/util/__tests__/hydration-cache-api.test.ts +0 -34
- package/src/util/__tests__/merge-gql-context.test.ts +0 -73
- package/src/util/__tests__/purge-caches.test.ts +0 -28
- package/src/util/__tests__/request-api.test.ts +0 -176
- package/src/util/__tests__/request-fulfillment.test.ts +0 -146
- package/src/util/__tests__/request-tracking.test.tsx +0 -321
- package/src/util/__tests__/result-from-cache-response.test.ts +0 -79
- package/src/util/__tests__/scoped-in-memory-cache.test.ts +0 -316
- package/src/util/__tests__/serializable-in-memory-cache.test.ts +0 -397
- package/src/util/__tests__/ssr-cache.test.ts +0 -636
- package/src/util/__tests__/to-gql-operation.test.ts +0 -41
- package/src/util/data-error.ts +0 -63
- package/src/util/get-gql-data-from-response.ts +0 -65
- package/src/util/get-gql-request-id.ts +0 -106
- package/src/util/gql-error.ts +0 -43
- package/src/util/gql-router-context.ts +0 -9
- package/src/util/gql-types.ts +0 -64
- package/src/util/graphql-document-node-parser.ts +0 -132
- package/src/util/graphql-types.ts +0 -28
- package/src/util/hydration-cache-api.ts +0 -30
- package/src/util/merge-gql-context.ts +0 -35
- package/src/util/purge-caches.ts +0 -14
- package/src/util/request-api.ts +0 -65
- package/src/util/request-fulfillment.ts +0 -121
- package/src/util/request-tracking.ts +0 -211
- package/src/util/result-from-cache-response.ts +0 -30
- package/src/util/scoped-in-memory-cache.ts +0 -121
- package/src/util/serializable-in-memory-cache.ts +0 -44
- package/src/util/ssr-cache.ts +0 -193
- package/src/util/status.ts +0 -35
- package/src/util/to-gql-operation.ts +0 -43
- package/src/util/types.ts +0 -145
- package/tsconfig-build.json +0 -12
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type {Result, ValidCacheData} from "./types";
|
|
2
|
-
|
|
3
|
-
import {DataError, DataErrors} from "./data-error";
|
|
4
|
-
|
|
5
|
-
type RequestCache = {
|
|
6
|
-
[id: string]: Promise<Result<any>>;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
type FulfillOptions<TData extends ValidCacheData> = {
|
|
10
|
-
handler: () => Promise<TData>;
|
|
11
|
-
hydrate?: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
let _default: RequestFulfillment;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* This fulfills a request, making sure that in-flight requests are shared.
|
|
18
|
-
*/
|
|
19
|
-
export class RequestFulfillment {
|
|
20
|
-
static get Default(): RequestFulfillment {
|
|
21
|
-
if (!_default) {
|
|
22
|
-
_default = new RequestFulfillment();
|
|
23
|
-
}
|
|
24
|
-
return _default;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
_requests: RequestCache = {};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get a promise of a request for a given handler and options.
|
|
31
|
-
*
|
|
32
|
-
* This will return an inflight request if one exists, otherwise it will
|
|
33
|
-
* make a new request. Inflight requests are deleted once they resolve.
|
|
34
|
-
*/
|
|
35
|
-
fulfill: <TData extends ValidCacheData>(
|
|
36
|
-
id: string,
|
|
37
|
-
options: FulfillOptions<TData>,
|
|
38
|
-
) => Promise<Result<TData>> = <TData extends ValidCacheData>(
|
|
39
|
-
id: string,
|
|
40
|
-
{handler, hydrate = true}: FulfillOptions<TData>,
|
|
41
|
-
): Promise<Result<TData>> => {
|
|
42
|
-
/**
|
|
43
|
-
* If we have an inflight request, we'll provide that.
|
|
44
|
-
*/
|
|
45
|
-
const inflight = this._requests[id];
|
|
46
|
-
if (inflight) {
|
|
47
|
-
return inflight;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* We don't have an inflight request, so let's set one up.
|
|
52
|
-
*/
|
|
53
|
-
const request = handler()
|
|
54
|
-
.then(
|
|
55
|
-
(data: TData): Result<TData> => ({
|
|
56
|
-
status: "success",
|
|
57
|
-
data,
|
|
58
|
-
}),
|
|
59
|
-
)
|
|
60
|
-
.catch((error: string | Error): Result<TData> => {
|
|
61
|
-
const actualError =
|
|
62
|
-
typeof error === "string"
|
|
63
|
-
? new DataError("Request failed", DataErrors.Unknown, {
|
|
64
|
-
metadata: {
|
|
65
|
-
unexpectedError: error,
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
: error;
|
|
69
|
-
|
|
70
|
-
// Return aborted result if the request was aborted.
|
|
71
|
-
// The only way to detect this reliably, it seems, is to
|
|
72
|
-
// check the error name and see if it's "AbortError" (this
|
|
73
|
-
// is also what Apollo does).
|
|
74
|
-
// Even then, it's reliant on the handler supporting aborts.
|
|
75
|
-
// TODO(somewhatabstract, FEI-4276): Add first class abort
|
|
76
|
-
// support to the handler API.
|
|
77
|
-
if (actualError.name === "AbortError") {
|
|
78
|
-
return {
|
|
79
|
-
status: "aborted",
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
return {
|
|
83
|
-
status: "error",
|
|
84
|
-
error: actualError,
|
|
85
|
-
};
|
|
86
|
-
})
|
|
87
|
-
.finally(() => {
|
|
88
|
-
delete this._requests[id];
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Store the request in our cache.
|
|
92
|
-
this._requests[id] = request;
|
|
93
|
-
|
|
94
|
-
return request;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Abort an inflight request.
|
|
99
|
-
*
|
|
100
|
-
* NOTE: Currently, this does not perform an actual abort. It merely
|
|
101
|
-
* removes the request from being tracked.
|
|
102
|
-
*/
|
|
103
|
-
abort: (id: string) => void = (id) => {
|
|
104
|
-
// TODO(somewhatabstract, FEI-4276): Add first class abort
|
|
105
|
-
// support to the handler API.
|
|
106
|
-
// For now, we will just clear the request out of the list.
|
|
107
|
-
// When abort is implemented, the `finally` in the `fulfill` method
|
|
108
|
-
// would handle the deletion.
|
|
109
|
-
delete this._requests[id];
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Abort all inflight requests.
|
|
114
|
-
*
|
|
115
|
-
* NOTE: Currently, this does not perform actual aborts. It merely
|
|
116
|
-
* removes the requests from our tracking.
|
|
117
|
-
*/
|
|
118
|
-
abortAll: () => void = (): void => {
|
|
119
|
-
Object.keys(this._requests).forEach((id) => this.abort(id));
|
|
120
|
-
};
|
|
121
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {SsrCache} from "./ssr-cache";
|
|
3
|
-
import {RequestFulfillment} from "./request-fulfillment";
|
|
4
|
-
|
|
5
|
-
import type {ResponseCache, ValidCacheData} from "./types";
|
|
6
|
-
|
|
7
|
-
type TrackerFn = <TData extends ValidCacheData>(
|
|
8
|
-
id: string,
|
|
9
|
-
handler: () => Promise<TData>,
|
|
10
|
-
hydrate: boolean,
|
|
11
|
-
) => void;
|
|
12
|
-
|
|
13
|
-
type RequestCache = {
|
|
14
|
-
[id: string]: {
|
|
15
|
-
hydrate: boolean;
|
|
16
|
-
handler: () => Promise<any>;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Used to inject our tracking function into the render framework.
|
|
22
|
-
*
|
|
23
|
-
* INTERNAL USE ONLY
|
|
24
|
-
*/
|
|
25
|
-
const TrackerContext: React.Context<TrackerFn | null | undefined> =
|
|
26
|
-
React.createContext<TrackerFn | null | undefined>(null);
|
|
27
|
-
TrackerContext.displayName = "TrackerContext";
|
|
28
|
-
export {TrackerContext};
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The default instance is stored here.
|
|
32
|
-
* It's created below in the Default() static property.
|
|
33
|
-
*/
|
|
34
|
-
let _default: RequestTracker;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Implements request tracking and fulfillment.
|
|
38
|
-
*
|
|
39
|
-
* INTERNAL USE ONLY
|
|
40
|
-
*/
|
|
41
|
-
export class RequestTracker {
|
|
42
|
-
static get Default(): RequestTracker {
|
|
43
|
-
if (!_default) {
|
|
44
|
-
_default = new RequestTracker();
|
|
45
|
-
}
|
|
46
|
-
return _default;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* These are the caches for tracked requests, their handlers, and responses.
|
|
51
|
-
*/
|
|
52
|
-
_trackedRequests: RequestCache = {};
|
|
53
|
-
_responseCache: SsrCache;
|
|
54
|
-
_requestFulfillment: RequestFulfillment;
|
|
55
|
-
|
|
56
|
-
constructor(responseCache?: SsrCache | null) {
|
|
57
|
-
this._responseCache = responseCache || SsrCache.Default;
|
|
58
|
-
this._requestFulfillment = new RequestFulfillment();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Track a request.
|
|
63
|
-
*
|
|
64
|
-
* This method caches a request and its handler for use during server-side
|
|
65
|
-
* rendering to allow us to fulfill requests before producing a final render.
|
|
66
|
-
*/
|
|
67
|
-
trackDataRequest: <TData extends ValidCacheData>(
|
|
68
|
-
id: string,
|
|
69
|
-
handler: () => Promise<TData>,
|
|
70
|
-
hydrate: boolean,
|
|
71
|
-
) => void = <TData extends ValidCacheData>(
|
|
72
|
-
id: string,
|
|
73
|
-
handler: () => Promise<TData>,
|
|
74
|
-
hydrate: boolean,
|
|
75
|
-
): void => {
|
|
76
|
-
/**
|
|
77
|
-
* If we don't already have this tracked, then let's track it.
|
|
78
|
-
*/
|
|
79
|
-
if (this._trackedRequests[id] == null) {
|
|
80
|
-
this._trackedRequests[id] = {
|
|
81
|
-
handler,
|
|
82
|
-
hydrate,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Reset our tracking info.
|
|
89
|
-
*/
|
|
90
|
-
reset: () => void = () => {
|
|
91
|
-
this._trackedRequests = {};
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Indicates if we have requests waiting to be fulfilled.
|
|
96
|
-
*/
|
|
97
|
-
get hasUnfulfilledRequests(): boolean {
|
|
98
|
-
return Object.keys(this._trackedRequests).length > 0;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Initiate fulfillment of all tracked requests.
|
|
103
|
-
*
|
|
104
|
-
* This loops over the requests that were tracked using TrackData, and asks
|
|
105
|
-
* the respective handlers to fulfill those requests in the order they were
|
|
106
|
-
* tracked.
|
|
107
|
-
*
|
|
108
|
-
* Calling this method marks tracked requests as fulfilled; requests are
|
|
109
|
-
* removed from the list of tracked requests by calling this method.
|
|
110
|
-
*
|
|
111
|
-
* @returns {Promise<ResponseCache>} The promise of the data that was
|
|
112
|
-
* cached as a result of fulfilling the tracked requests.
|
|
113
|
-
*/
|
|
114
|
-
fulfillTrackedRequests: () => Promise<ResponseCache> =
|
|
115
|
-
(): Promise<ResponseCache> => {
|
|
116
|
-
const promises = [];
|
|
117
|
-
const {cacheData, cacheError} = this._responseCache;
|
|
118
|
-
|
|
119
|
-
for (const requestKey of Object.keys(this._trackedRequests)) {
|
|
120
|
-
const options = this._trackedRequests[requestKey];
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
promises.push(
|
|
124
|
-
this._requestFulfillment
|
|
125
|
-
.fulfill(requestKey, {...options})
|
|
126
|
-
.then((result) => {
|
|
127
|
-
switch (result.status) {
|
|
128
|
-
case "success":
|
|
129
|
-
/**
|
|
130
|
-
* Let's cache the data!
|
|
131
|
-
*
|
|
132
|
-
* NOTE: This only caches when we're
|
|
133
|
-
* server side.
|
|
134
|
-
*/
|
|
135
|
-
cacheData(
|
|
136
|
-
requestKey,
|
|
137
|
-
result.data,
|
|
138
|
-
options.hydrate,
|
|
139
|
-
);
|
|
140
|
-
break;
|
|
141
|
-
|
|
142
|
-
case "error":
|
|
143
|
-
/**
|
|
144
|
-
* Let's cache the error!
|
|
145
|
-
*
|
|
146
|
-
* NOTE: This only caches when we're
|
|
147
|
-
* server side.
|
|
148
|
-
*/
|
|
149
|
-
cacheError(
|
|
150
|
-
requestKey,
|
|
151
|
-
result.error,
|
|
152
|
-
options.hydrate,
|
|
153
|
-
);
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// For status === "loading":
|
|
158
|
-
// Could never get here unless we wrote
|
|
159
|
-
// the code wrong. Rather than bloat
|
|
160
|
-
// code with useless error, just ignore.
|
|
161
|
-
|
|
162
|
-
// For status === "no-data":
|
|
163
|
-
// Could never get here unless we wrote
|
|
164
|
-
// the code wrong. Rather than bloat
|
|
165
|
-
// code with useless error, just ignore.
|
|
166
|
-
|
|
167
|
-
// For status === "aborted":
|
|
168
|
-
// We won't cache this.
|
|
169
|
-
// We don't hydrate aborted requests,
|
|
170
|
-
// so the client would just see them
|
|
171
|
-
// as unfulfilled data.
|
|
172
|
-
return;
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
|
-
} catch (e: any) {
|
|
176
|
-
// This captures if there are problems in the code that
|
|
177
|
-
// begins the requests.
|
|
178
|
-
promises.push(
|
|
179
|
-
Promise.resolve(
|
|
180
|
-
cacheError(requestKey, e, options.hydrate),
|
|
181
|
-
),
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Clear out our tracked info.
|
|
188
|
-
*
|
|
189
|
-
* We call this now for a simpler API.
|
|
190
|
-
*
|
|
191
|
-
* If we reset the tracked calls after all promises resolve, any
|
|
192
|
-
* request tracking done while promises are in flight would be lost.
|
|
193
|
-
*
|
|
194
|
-
* If we don't reset at all, then we have to expose the `reset` call
|
|
195
|
-
* for consumers to use, or they'll only ever be able to accumulate
|
|
196
|
-
* more and more tracked requests, having to fulfill them all every
|
|
197
|
-
* time.
|
|
198
|
-
*
|
|
199
|
-
* Calling it here means we can have multiple "track -> request"
|
|
200
|
-
* cycles in a row and in an easy to reason about manner.
|
|
201
|
-
*/
|
|
202
|
-
this.reset();
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Let's wait for everything to fulfill, and then clone the cached data.
|
|
206
|
-
*/
|
|
207
|
-
return Promise.all(promises).then(() =>
|
|
208
|
-
this._responseCache.cloneHydratableData(),
|
|
209
|
-
);
|
|
210
|
-
};
|
|
211
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import {Status} from "./status";
|
|
2
|
-
import {DataError, DataErrors} from "./data-error";
|
|
3
|
-
import type {ValidCacheData, CachedResponse, Result} from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Turns a cache entry into a stateful result.
|
|
7
|
-
*/
|
|
8
|
-
export const resultFromCachedResponse = <TData extends ValidCacheData>(
|
|
9
|
-
cacheEntry?: CachedResponse<TData> | null,
|
|
10
|
-
): Result<TData> | null | undefined => {
|
|
11
|
-
// No cache entry means no result to be hydrated.
|
|
12
|
-
if (cacheEntry == null) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const {data, error} = cacheEntry;
|
|
17
|
-
if (error != null) {
|
|
18
|
-
// Let's hydrate the error. We don't persist everything about the
|
|
19
|
-
// original error on the server, hence why we only superficially
|
|
20
|
-
// hydrate it to a GqlHydratedError.
|
|
21
|
-
return Status.error(new DataError(error, DataErrors.Hydrated));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (data != null) {
|
|
25
|
-
return Status.success(data);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// We shouldn't get here since we don't actually cache null data.
|
|
29
|
-
return Status.aborted();
|
|
30
|
-
};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import {DataError, DataErrors} from "./data-error";
|
|
2
|
-
import type {ScopedCache, RawScopedCache, ValidCacheData} from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Describe an in-memory cache.
|
|
6
|
-
*/
|
|
7
|
-
export class ScopedInMemoryCache implements ScopedCache {
|
|
8
|
-
_cache: RawScopedCache;
|
|
9
|
-
|
|
10
|
-
constructor(initialCache: RawScopedCache = {}) {
|
|
11
|
-
this._cache = initialCache;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Indicate if this cache is being used or not.
|
|
16
|
-
*
|
|
17
|
-
* When the cache has entries, returns `true`; otherwise, returns `false`.
|
|
18
|
-
*/
|
|
19
|
-
get inUse(): boolean {
|
|
20
|
-
return Object.keys(this._cache).length > 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Set a value in the cache.
|
|
25
|
-
*/
|
|
26
|
-
set(scope: string, id: string, value: ValidCacheData): void {
|
|
27
|
-
if (!id || typeof id !== "string") {
|
|
28
|
-
throw new DataError(
|
|
29
|
-
"id must be non-empty string",
|
|
30
|
-
DataErrors.InvalidInput,
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (!scope || typeof scope !== "string") {
|
|
35
|
-
throw new DataError(
|
|
36
|
-
"scope must be non-empty string",
|
|
37
|
-
DataErrors.InvalidInput,
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (typeof value === "function") {
|
|
42
|
-
throw new DataError(
|
|
43
|
-
"value must be a non-function value",
|
|
44
|
-
DataErrors.InvalidInput,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
this._cache[scope] = this._cache[scope] ?? {};
|
|
49
|
-
this._cache[scope][id] = value;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Retrieve a value from the cache.
|
|
54
|
-
*/
|
|
55
|
-
get(scope: string, id: string): ValidCacheData | null | undefined {
|
|
56
|
-
return this._cache[scope]?.[id] ?? null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Purge an item from the cache.
|
|
61
|
-
*/
|
|
62
|
-
purge(scope: string, id: string): void {
|
|
63
|
-
if (!this._cache[scope]?.[id]) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
delete this._cache[scope][id];
|
|
67
|
-
if (Object.keys(this._cache[scope]).length === 0) {
|
|
68
|
-
delete this._cache[scope];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Purge a scope of items that match the given predicate.
|
|
74
|
-
*
|
|
75
|
-
* If the predicate is omitted, then all items in the scope are purged.
|
|
76
|
-
*/
|
|
77
|
-
purgeScope(
|
|
78
|
-
scope: string,
|
|
79
|
-
predicate?: (id: string, value: ValidCacheData) => boolean,
|
|
80
|
-
): void {
|
|
81
|
-
if (!this._cache[scope]) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (predicate == null) {
|
|
86
|
-
delete this._cache[scope];
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (const key of Object.keys(this._cache[scope])) {
|
|
91
|
-
if (predicate(key, this._cache[scope][key])) {
|
|
92
|
-
delete this._cache[scope][key];
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (Object.keys(this._cache[scope]).length === 0) {
|
|
96
|
-
delete this._cache[scope];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Purge all items from the cache that match the given predicate.
|
|
102
|
-
*
|
|
103
|
-
* If the predicate is omitted, then all items in the cache are purged.
|
|
104
|
-
*/
|
|
105
|
-
purgeAll(
|
|
106
|
-
predicate?: (
|
|
107
|
-
scope: string,
|
|
108
|
-
id: string,
|
|
109
|
-
value: ValidCacheData,
|
|
110
|
-
) => boolean,
|
|
111
|
-
): void {
|
|
112
|
-
if (predicate == null) {
|
|
113
|
-
this._cache = {};
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
for (const scope of Object.keys(this._cache)) {
|
|
118
|
-
this.purgeScope(scope, (id, value) => predicate(scope, id, value));
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {clone} from "@khanacademy/wonder-stuff-core";
|
|
2
|
-
import {DataError, DataErrors} from "./data-error";
|
|
3
|
-
import {ScopedInMemoryCache} from "./scoped-in-memory-cache";
|
|
4
|
-
import type {ValidCacheData, RawScopedCache} from "./types";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Describe a serializable in-memory cache.
|
|
8
|
-
*/
|
|
9
|
-
export class SerializableInMemoryCache extends ScopedInMemoryCache {
|
|
10
|
-
constructor(initialCache: RawScopedCache = {}) {
|
|
11
|
-
try {
|
|
12
|
-
super(clone(initialCache));
|
|
13
|
-
} catch (e: any) {
|
|
14
|
-
throw new DataError(
|
|
15
|
-
`An error occurred trying to initialize from a response cache snapshot: ${e}`,
|
|
16
|
-
DataErrors.InvalidInput,
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Set a value in the cache.
|
|
23
|
-
*/
|
|
24
|
-
set(scope: string, id: string, value: ValidCacheData): void {
|
|
25
|
-
super.set(scope, id, Object.freeze(clone(value)));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Clone the cache.
|
|
30
|
-
*/
|
|
31
|
-
clone(): RawScopedCache {
|
|
32
|
-
try {
|
|
33
|
-
return clone(this._cache);
|
|
34
|
-
} catch (e: any) {
|
|
35
|
-
throw new DataError(
|
|
36
|
-
"An error occurred while trying to clone the cache",
|
|
37
|
-
DataErrors.Internal,
|
|
38
|
-
{
|
|
39
|
-
cause: e,
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|