@hkdigital/lib-sveltekit 0.1.68 → 0.1.70
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/dist/classes/cache/CacheStorage.js__ +45 -0
- package/dist/classes/cache/MemoryResponseCache.d.ts +19 -0
- package/dist/classes/cache/MemoryResponseCache.js +53 -0
- package/dist/classes/cache/PersistentResponseCache.d.ts +46 -0
- package/dist/classes/cache/PersistentResponseCache.js +165 -0
- package/dist/classes/cache/index.d.ts +3 -0
- package/dist/classes/cache/index.js +5 -0
- package/dist/classes/cache/typedef.d.ts +50 -0
- package/dist/classes/cache/typedef.js +25 -0
- package/dist/util/http/caching.d.ts +28 -0
- package/dist/util/http/caching.js +251 -0
- package/dist/util/http/headers.d.ts +33 -4
- package/dist/util/http/headers.js +57 -27
- package/dist/util/http/http-request.d.ts +93 -115
- package/dist/util/http/http-request.js +329 -262
- package/dist/util/http/json-request.d.ts +65 -45
- package/dist/util/http/json-request.js +188 -138
- package/dist/util/http/response.d.ts +91 -22
- package/dist/util/http/response.js +229 -176
- package/dist/util/http/typedef.d.ts +184 -0
- package/dist/util/http/typedef.js +93 -0
- package/dist/widgets/game-box/GameBox.svelte +2 -2
- package/package.json +1 -1
@@ -3,8 +3,8 @@ import * as expect from '../expect/index.js';
|
|
3
3
|
import { toURL } from './url.js';
|
4
4
|
|
5
5
|
import {
|
6
|
-
|
7
|
-
|
6
|
+
WWW_AUTHENTICATE,
|
7
|
+
CONTENT_LENGTH
|
8
8
|
} from '../../constants/http/headers.js';
|
9
9
|
|
10
10
|
import { href } from './url.js';
|
@@ -14,228 +14,281 @@ import { getErrorFromResponse } from './errors.js';
|
|
14
14
|
// > Types
|
15
15
|
|
16
16
|
/**
|
17
|
+
* Callback function that reports progress of data loading
|
18
|
+
*
|
17
19
|
* @callback progressCallback
|
20
|
+
*
|
18
21
|
* @param {object} _
|
19
|
-
* @param {number} _.bytesLoaded
|
20
|
-
* @param {number} _.size
|
22
|
+
* @param {number} _.bytesLoaded - Number of bytes loaded so far
|
23
|
+
* @param {number} _.size - Total size of the response in bytes (0 if unknown)
|
21
24
|
*/
|
22
25
|
|
23
26
|
// > Exports
|
24
27
|
|
25
28
|
/**
|
26
|
-
* Check if the response status is ok
|
29
|
+
* Check if the response status is ok (in 200-299 range)
|
30
|
+
* This function examines HTTP status codes and throws appropriate errors for
|
31
|
+
* non-successful responses, with special handling for 401 Unauthorized.
|
32
|
+
*
|
33
|
+
* @param {object} response - Fetch Response object to check
|
27
34
|
*
|
28
|
-
* @param {
|
29
|
-
* @param {string} url - used to produce useful error messages
|
35
|
+
* @param {string} url - The URL used for the request (for error messages)
|
30
36
|
*
|
31
|
-
* @throws {Error}
|
32
|
-
* @throws {
|
37
|
+
* @throws {Error} When response has 401 status with authorization details
|
38
|
+
* @throws {ResponseError} When response has other non-successful status codes
|
39
|
+
*
|
40
|
+
* @example
|
41
|
+
* // Check if response was successful
|
42
|
+
* try {
|
43
|
+
* await expectResponseOk(response, 'https://api.example.com/data');
|
44
|
+
* // Process successful response here
|
45
|
+
* } catch (error) {
|
46
|
+
* // Handle specific error types
|
47
|
+
* if (error.message.includes('401')) {
|
48
|
+
* // Handle unauthorized error
|
49
|
+
* }
|
50
|
+
* }
|
33
51
|
*/
|
34
52
|
export async function expectResponseOk(response, url) {
|
35
|
-
|
53
|
+
expect.object(response);
|
36
54
|
|
37
|
-
|
38
|
-
|
55
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200
|
56
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201
|
39
57
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
58
|
+
if (200 === response.status || 201 === response.status) {
|
59
|
+
if (!response.ok) {
|
60
|
+
throw new ResponseError(
|
61
|
+
`Server returned - ${response.status} ${response.statusText} ` +
|
62
|
+
`[response.ok=false] [url=${href(url)}]`
|
63
|
+
);
|
64
|
+
}
|
47
65
|
|
48
|
-
|
49
|
-
|
50
|
-
|
66
|
+
// All ok
|
67
|
+
return;
|
68
|
+
}
|
51
69
|
|
52
|
-
|
70
|
+
// > Handle 401 Unauthorized
|
53
71
|
|
54
|
-
|
55
|
-
|
72
|
+
if (401 === response.status) {
|
73
|
+
let errorMessage = 'Server returned [401] Unauthorized';
|
56
74
|
|
57
|
-
|
75
|
+
const authValue = response.headers.get(WWW_AUTHENTICATE);
|
58
76
|
|
59
|
-
|
60
|
-
|
77
|
+
if (authValue) {
|
78
|
+
// Add WWW_AUTHENTICATE response to error message
|
79
|
+
errorMessage += ` (${authValue})`;
|
80
|
+
}
|
61
81
|
|
62
|
-
|
63
|
-
}
|
82
|
+
errorMessage += ` [url=${href(url)}]`;
|
64
83
|
|
65
|
-
|
84
|
+
throw new Error(errorMessage);
|
85
|
+
}
|
66
86
|
|
67
|
-
|
68
|
-
}
|
87
|
+
// > Handle all other error responses
|
69
88
|
|
70
|
-
|
89
|
+
const error = await getErrorFromResponse(response);
|
71
90
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
{ cause: error }
|
78
|
-
);
|
91
|
+
throw new ResponseError(
|
92
|
+
`Server returned - ${response.status} ${response.statusText} ` +
|
93
|
+
`[url=${href(url)}]`,
|
94
|
+
{ cause: error }
|
95
|
+
);
|
79
96
|
}
|
80
97
|
|
81
98
|
/**
|
82
99
|
* Get the response size from the content-length response header
|
83
100
|
*
|
84
|
-
* @param {Response} response
|
101
|
+
* @param {Response} response - Fetch Response object
|
102
|
+
*
|
103
|
+
* @returns {number} Response size in bytes, or 0 if content-length is not set
|
85
104
|
*
|
86
|
-
* @
|
105
|
+
* @example
|
106
|
+
* const response = await fetch('https://example.com/large-file.zip');
|
107
|
+
* const size = getResponseSize(response);
|
108
|
+
* console.log(`Download size: ${size} bytes`);
|
87
109
|
*/
|
88
110
|
export function getResponseSize(response) {
|
89
|
-
|
111
|
+
const sizeStr = response.headers.get(CONTENT_LENGTH);
|
90
112
|
|
91
|
-
|
92
|
-
|
93
|
-
|
113
|
+
if (!sizeStr) {
|
114
|
+
return 0;
|
115
|
+
}
|
94
116
|
|
95
|
-
|
117
|
+
return parseInt(sizeStr, 10);
|
96
118
|
}
|
97
119
|
|
98
120
|
/**
|
99
121
|
* Wait for a response and check if the response is ok
|
122
|
+
* This function awaits a response promise and performs error checking,
|
123
|
+
* wrapping network errors in a standardized ResponseError format.
|
100
124
|
*
|
101
|
-
* @
|
102
|
-
* const response = await waitForAndCheckResponse( responsePromise );
|
125
|
+
* @param {Promise<Response>} responsePromise - Promise that resolves to a Response
|
103
126
|
*
|
104
|
-
* @param {
|
105
|
-
* @param {string|URL} url - An url that is used for error messages
|
127
|
+
* @param {string|URL} url - URL used for the request (for error messages)
|
106
128
|
*
|
107
|
-
* @throws ResponseError
|
129
|
+
* @throws {ResponseError} When a network error occurs or response is not ok
|
108
130
|
*
|
109
|
-
* @returns {Promise<Response>} response
|
131
|
+
* @returns {Promise<Response>} The response if successful
|
132
|
+
*
|
133
|
+
* @example
|
134
|
+
* // Handle a fetch promise with proper error handling
|
135
|
+
* const responsePromise = fetch('https://api.example.com/data');
|
136
|
+
* try {
|
137
|
+
* const response = await waitForAndCheckResponse(
|
138
|
+
* responsePromise,
|
139
|
+
* 'https://api.example.com/data'
|
140
|
+
* );
|
141
|
+
* // Process response
|
142
|
+
* } catch (error) {
|
143
|
+
* // Handle standardized ResponseError
|
144
|
+
* console.error(error.message);
|
145
|
+
* }
|
110
146
|
*/
|
111
147
|
export async function waitForAndCheckResponse(responsePromise, url) {
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
148
|
+
expect.promise(responsePromise);
|
149
|
+
|
150
|
+
url = toURL(url);
|
151
|
+
|
152
|
+
let response;
|
153
|
+
|
154
|
+
try {
|
155
|
+
response = await responsePromise;
|
156
|
+
|
157
|
+
if (response && false === response.ok) {
|
158
|
+
// if response.ok is false, it also indicates a network error
|
159
|
+
throw new Error(`Response failed [response.ok=false]`);
|
160
|
+
}
|
161
|
+
} catch (e) {
|
162
|
+
if (e instanceof TypeError || response?.ok === false) {
|
163
|
+
throw new ResponseError(
|
164
|
+
`A network error occurred for request [${href(url)}]`,
|
165
|
+
{
|
166
|
+
cause: e
|
167
|
+
}
|
168
|
+
);
|
169
|
+
} else {
|
170
|
+
throw e;
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
return response;
|
139
175
|
}
|
140
176
|
|
141
177
|
/**
|
142
|
-
* Load response body as ArrayBuffer
|
143
|
-
*
|
144
|
-
*
|
178
|
+
* Load response body as ArrayBuffer with progress monitoring and abort capability
|
179
|
+
*
|
180
|
+
* This function reads a response body stream chunk by chunk, with optional
|
181
|
+
* progress reporting. It provides an abort mechanism to cancel an in-progress
|
182
|
+
* download.
|
183
|
+
*
|
184
|
+
* @param {Response} response - Fetch Response object to read
|
185
|
+
*
|
186
|
+
* @param {progressCallback} [onProgress] - Optional callback for progress updates
|
187
|
+
*
|
188
|
+
* @returns {{
|
189
|
+
* bufferPromise: Promise<ArrayBuffer>,
|
190
|
+
* abort: () => void
|
191
|
+
* }} Object containing the buffer promise and abort function
|
192
|
+
*
|
193
|
+
* @example
|
194
|
+
* // Download a file with progress monitoring and abort capability
|
195
|
+
* const response = await fetch('https://example.com/large-file.zip');
|
196
|
+
*
|
197
|
+
* const { bufferPromise, abort } = loadResponseBuffer(
|
198
|
+
* response,
|
199
|
+
* ({ bytesLoaded, size }) => {
|
200
|
+
* // Update progress UI
|
201
|
+
* const percent = size ? Math.round((bytesLoaded / size) * 100) : 0;
|
202
|
+
* console.log(`Downloaded ${bytesLoaded} bytes (${percent}%)`);
|
203
|
+
* }
|
204
|
+
* );
|
145
205
|
*
|
146
|
-
*
|
147
|
-
*
|
206
|
+
* // To abort the download:
|
207
|
+
* // abort();
|
148
208
|
*
|
149
|
-
*
|
209
|
+
* try {
|
210
|
+
* const buffer = await bufferPromise;
|
211
|
+
* // Process the complete buffer
|
212
|
+
* } catch (error) {
|
213
|
+
* console.error('Download failed or was aborted', error);
|
214
|
+
* }
|
150
215
|
*/
|
151
216
|
export function loadResponseBuffer(response, onProgress) {
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
return buffer;
|
231
|
-
}
|
232
|
-
|
233
|
-
const bufferPromise = read();
|
234
|
-
|
235
|
-
return {
|
236
|
-
bufferPromise,
|
237
|
-
abort: () => {
|
238
|
-
aborted = true;
|
239
|
-
}
|
240
|
-
};
|
217
|
+
// @note size might be 0
|
218
|
+
// @note might not be send by server in dev mode
|
219
|
+
const size = getResponseSize(response);
|
220
|
+
|
221
|
+
let bytesLoaded = 0;
|
222
|
+
|
223
|
+
if (onProgress /*&& size*/) {
|
224
|
+
onProgress({ bytesLoaded, size });
|
225
|
+
}
|
226
|
+
|
227
|
+
if (!response.body) {
|
228
|
+
throw new Error('Missing [response.body]');
|
229
|
+
}
|
230
|
+
|
231
|
+
let reader;
|
232
|
+
let aborted = false;
|
233
|
+
|
234
|
+
/**
|
235
|
+
* Read chunks from response body using reader
|
236
|
+
*
|
237
|
+
* @returns {Promise<ArrayBuffer>}
|
238
|
+
*/
|
239
|
+
async function read() {
|
240
|
+
reader = response.body.getReader();
|
241
|
+
let chunks = [];
|
242
|
+
|
243
|
+
for (;;) {
|
244
|
+
const { done, value } = await reader.read();
|
245
|
+
|
246
|
+
if (value) {
|
247
|
+
// @note value is an ArrayBuffer
|
248
|
+
bytesLoaded += value.byteLength;
|
249
|
+
|
250
|
+
chunks.push(value);
|
251
|
+
|
252
|
+
if (onProgress /*&& size*/) {
|
253
|
+
onProgress({ bytesLoaded, size });
|
254
|
+
}
|
255
|
+
}
|
256
|
+
|
257
|
+
if (done || aborted) {
|
258
|
+
// Loading complete or aborted by user
|
259
|
+
break;
|
260
|
+
}
|
261
|
+
} // end for
|
262
|
+
|
263
|
+
if (size && bytesLoaded !== size) {
|
264
|
+
console.error(`Received [${bytesLoaded}], but expected [${size}] bytes`);
|
265
|
+
}
|
266
|
+
|
267
|
+
// Concat the chunks into a single array
|
268
|
+
let buffer = new ArrayBuffer(bytesLoaded);
|
269
|
+
let body = new Uint8Array(buffer);
|
270
|
+
|
271
|
+
let offset = 0;
|
272
|
+
|
273
|
+
// Place the chunks in the buffer
|
274
|
+
for (let chunk of chunks) {
|
275
|
+
body.set(chunk, offset);
|
276
|
+
offset += chunk.byteLength;
|
277
|
+
} // end for
|
278
|
+
|
279
|
+
return buffer;
|
280
|
+
}
|
281
|
+
|
282
|
+
const bufferPromise = read();
|
283
|
+
|
284
|
+
return {
|
285
|
+
bufferPromise,
|
286
|
+
abort: () => {
|
287
|
+
aborted = true;
|
288
|
+
|
289
|
+
if (reader) {
|
290
|
+
reader.cancel('Aborted by user');
|
291
|
+
}
|
292
|
+
}
|
293
|
+
};
|
241
294
|
} // end fn
|
@@ -0,0 +1,184 @@
|
|
1
|
+
export type HttpRequestOptions = {
|
2
|
+
/**
|
3
|
+
* URL string or URL object for the request
|
4
|
+
*/
|
5
|
+
url: string | URL;
|
6
|
+
/**
|
7
|
+
* HTTP method to use (GET, POST, etc.)
|
8
|
+
*/
|
9
|
+
method?: string;
|
10
|
+
/**
|
11
|
+
* Parameters to add to the URL
|
12
|
+
*/
|
13
|
+
urlSearchParams?: any | URLSearchParams;
|
14
|
+
/**
|
15
|
+
* Request body (for POST, PUT, etc.)
|
16
|
+
*/
|
17
|
+
body?: any;
|
18
|
+
/**
|
19
|
+
* HTTP headers as name-value pairs
|
20
|
+
*/
|
21
|
+
headers?: any;
|
22
|
+
/**
|
23
|
+
* Whether to include credentials
|
24
|
+
*/
|
25
|
+
withCredentials?: boolean;
|
26
|
+
/**
|
27
|
+
* Request timeout in milliseconds
|
28
|
+
*/
|
29
|
+
timeoutMs?: number;
|
30
|
+
/**
|
31
|
+
* Handler for abort/timeout control
|
32
|
+
*/
|
33
|
+
requestHandler?: Function;
|
34
|
+
/**
|
35
|
+
* CORS mode ('cors', 'no-cors', 'same-origin')
|
36
|
+
*/
|
37
|
+
mode?: string;
|
38
|
+
/**
|
39
|
+
* Cache mode ('default', 'no-cache', etc.)
|
40
|
+
*/
|
41
|
+
cache?: string;
|
42
|
+
/**
|
43
|
+
* Redirect mode ('follow', 'error', 'manual')
|
44
|
+
*/
|
45
|
+
redirect?: string;
|
46
|
+
/**
|
47
|
+
* Referrer policy
|
48
|
+
*/
|
49
|
+
referrerPolicy?: string;
|
50
|
+
/**
|
51
|
+
* Enable or disabled automatic caching
|
52
|
+
*/
|
53
|
+
cacheEnabled?: boolean;
|
54
|
+
};
|
55
|
+
export type RequestHandlerParams = {
|
56
|
+
/**
|
57
|
+
* The AbortController instance for this request
|
58
|
+
*/
|
59
|
+
controller: AbortController;
|
60
|
+
/**
|
61
|
+
* Function to abort the request
|
62
|
+
*/
|
63
|
+
abort: (reason?: Error) => void;
|
64
|
+
/**
|
65
|
+
* Function to set a timeout
|
66
|
+
*/
|
67
|
+
timeout: (delayMs: number) => void;
|
68
|
+
};
|
69
|
+
export type RequestHandler = (params: RequestHandlerParams) => void;
|
70
|
+
export type JsonGetOptions = {
|
71
|
+
/**
|
72
|
+
* URL string or URL object for the request
|
73
|
+
*/
|
74
|
+
url: string | URL;
|
75
|
+
/**
|
76
|
+
* Parameters to add to the URL
|
77
|
+
*/
|
78
|
+
urlSearchParams?: any | URLSearchParams;
|
79
|
+
/**
|
80
|
+
* HTTP headers as name-value pairs
|
81
|
+
*/
|
82
|
+
headers?: any;
|
83
|
+
/**
|
84
|
+
* Whether to include credentials
|
85
|
+
*/
|
86
|
+
withCredentials?: boolean;
|
87
|
+
/**
|
88
|
+
* Request timeout in milliseconds
|
89
|
+
*/
|
90
|
+
timeoutMs?: number;
|
91
|
+
/**
|
92
|
+
* Handler for abort/timeout control
|
93
|
+
*/
|
94
|
+
requestHandler?: RequestHandler;
|
95
|
+
/**
|
96
|
+
* CORS mode ('cors', 'no-cors', 'same-origin')
|
97
|
+
*/
|
98
|
+
mode?: string;
|
99
|
+
/**
|
100
|
+
* Cache mode ('default', 'no-cache', etc.)
|
101
|
+
*/
|
102
|
+
cache?: string;
|
103
|
+
/**
|
104
|
+
* Redirect mode ('follow', 'error', 'manual')
|
105
|
+
*/
|
106
|
+
redirect?: string;
|
107
|
+
/**
|
108
|
+
* Referrer policy
|
109
|
+
*/
|
110
|
+
referrerPolicy?: string;
|
111
|
+
/**
|
112
|
+
* Enable or disabled automatic caching
|
113
|
+
*/
|
114
|
+
cacheEnabled?: boolean;
|
115
|
+
};
|
116
|
+
export type JsonPostOptions = {
|
117
|
+
/**
|
118
|
+
* URL string or URL object for the request
|
119
|
+
*/
|
120
|
+
url: string | URL;
|
121
|
+
/**
|
122
|
+
* Request body (will be sent as JSON)
|
123
|
+
*/
|
124
|
+
body: any;
|
125
|
+
/**
|
126
|
+
* Parameters to add to the URL
|
127
|
+
*/
|
128
|
+
urlSearchParams?: any | URLSearchParams;
|
129
|
+
/**
|
130
|
+
* HTTP headers as name-value pairs
|
131
|
+
*/
|
132
|
+
headers?: any;
|
133
|
+
/**
|
134
|
+
* Whether to include credentials
|
135
|
+
*/
|
136
|
+
withCredentials?: boolean;
|
137
|
+
/**
|
138
|
+
* Request timeout in milliseconds
|
139
|
+
*/
|
140
|
+
timeoutMs?: number;
|
141
|
+
/**
|
142
|
+
* Handler for abort/timeout control
|
143
|
+
*/
|
144
|
+
requestHandler?: RequestHandler;
|
145
|
+
/**
|
146
|
+
* CORS mode ('cors', 'no-cors', 'same-origin')
|
147
|
+
*/
|
148
|
+
mode?: string;
|
149
|
+
/**
|
150
|
+
* Cache mode ('default', 'no-cache', etc.)
|
151
|
+
*/
|
152
|
+
cache?: string;
|
153
|
+
/**
|
154
|
+
* Redirect mode ('follow', 'error', 'manual')
|
155
|
+
*/
|
156
|
+
redirect?: string;
|
157
|
+
/**
|
158
|
+
* Referrer policy
|
159
|
+
*/
|
160
|
+
referrerPolicy?: string;
|
161
|
+
/**
|
162
|
+
* Enable or disabled automatic caching
|
163
|
+
*/
|
164
|
+
cacheEnabled?: boolean;
|
165
|
+
};
|
166
|
+
export type StaleInfo = {
|
167
|
+
/**
|
168
|
+
* Whether the response contains stale data
|
169
|
+
*/
|
170
|
+
isStale: boolean;
|
171
|
+
/**
|
172
|
+
* Promise that resolves to fresh data (if available)
|
173
|
+
*/
|
174
|
+
fresh: Promise<Response> | null;
|
175
|
+
/**
|
176
|
+
* When the response was originally cached
|
177
|
+
*/
|
178
|
+
timestamp: number;
|
179
|
+
/**
|
180
|
+
* When the response expires
|
181
|
+
*/
|
182
|
+
expires: number | null;
|
183
|
+
};
|
184
|
+
export type ResponseWithStale = Response;
|