@mainframework/api-request-worker 1.0.2 → 1.0.4
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/README.md +114 -50
- package/dist/shared/output/react.d.ts +3 -3
- package/dist/shared/output/react.js +132 -67
- package/dist/shared/output/react.js.map +1 -1
- package/dist/shared/output/vanilla.d.ts +1 -1
- package/dist/shared/output/vanilla.js +1 -1
- package/dist/shared/output/vanilla.js.map +1 -1
- package/dist/{types-Bbz0QMYB.d.ts → types-C6nnvGO7.d.ts} +23 -49
- package/dist/workers/api/api.worker.js +289 -106
- package/dist/workers/api/api.worker.js.map +1 -1
- package/package.json +19 -16
|
@@ -1,44 +1,104 @@
|
|
|
1
1
|
const BINARY_MARKER = /* @__PURE__ */ Symbol.for("WorkerApiBinary");
|
|
2
2
|
const DEFAULT_FILES_FIELD = "Files";
|
|
3
|
+
const EMPTY_BUFFER = new ArrayBuffer(0);
|
|
3
4
|
const NO_ERROR = { message: "" };
|
|
4
|
-
const
|
|
5
|
+
const DEFAULT_ERROR = "An error occurred";
|
|
6
|
+
const callerResponse = (cacheName, data, hookId, httpStatus, error = NO_ERROR, type = "result", requestId) => {
|
|
5
7
|
self.postMessage({
|
|
8
|
+
type,
|
|
9
|
+
...requestId != null && requestId !== "" && { requestId },
|
|
6
10
|
cacheName,
|
|
7
|
-
data: error.message !== "" ? null : data ?? null,
|
|
11
|
+
data: type === "delete" ? null : error.message !== "" ? null : data ?? null,
|
|
8
12
|
hookId,
|
|
9
13
|
httpStatus,
|
|
10
14
|
error
|
|
11
15
|
});
|
|
12
16
|
};
|
|
13
|
-
const makeError = (message) =>
|
|
14
|
-
const callerResponseBinary = (cacheName, data, meta, hookId, httpStatus, error = NO_ERROR) => {
|
|
15
|
-
const buffer = data
|
|
16
|
-
const payload = {
|
|
17
|
+
const makeError = (message, code) => code !== void 0 ? { message, code } : { message };
|
|
18
|
+
const callerResponseBinary = (cacheName, data, meta, hookId, httpStatus, error = NO_ERROR, requestId) => {
|
|
19
|
+
const buffer = data.byteLength ? data : EMPTY_BUFFER;
|
|
20
|
+
const payload = {
|
|
21
|
+
type: "result",
|
|
22
|
+
...requestId != null && requestId !== "" && { requestId },
|
|
23
|
+
cacheName,
|
|
24
|
+
data: buffer,
|
|
25
|
+
meta,
|
|
26
|
+
hookId,
|
|
27
|
+
httpStatus,
|
|
28
|
+
error
|
|
29
|
+
};
|
|
17
30
|
self.postMessage(payload, buffer.byteLength > 0 ? [buffer] : []);
|
|
18
31
|
};
|
|
19
|
-
const callerResponseStreamStart = (cacheName, meta, hookId, httpStatus, error = NO_ERROR) => {
|
|
20
|
-
self.postMessage({
|
|
32
|
+
const callerResponseStreamStart = (cacheName, meta, hookId, httpStatus, error = NO_ERROR, requestId) => {
|
|
33
|
+
self.postMessage({
|
|
34
|
+
type: "stream",
|
|
35
|
+
...requestId != null && requestId !== "" && { requestId },
|
|
36
|
+
cacheName,
|
|
37
|
+
stream: "start",
|
|
38
|
+
meta,
|
|
39
|
+
hookId,
|
|
40
|
+
httpStatus,
|
|
41
|
+
error
|
|
42
|
+
});
|
|
21
43
|
};
|
|
22
|
-
const callerResponseStreamResume = (cacheName, meta, hookId, httpStatus, error = NO_ERROR) => {
|
|
23
|
-
self.postMessage({
|
|
44
|
+
const callerResponseStreamResume = (cacheName, meta, hookId, httpStatus, error = NO_ERROR, requestId) => {
|
|
45
|
+
self.postMessage({
|
|
46
|
+
type: "stream",
|
|
47
|
+
...requestId != null && requestId !== "" && { requestId },
|
|
48
|
+
cacheName,
|
|
49
|
+
stream: "resume",
|
|
50
|
+
meta,
|
|
51
|
+
hookId,
|
|
52
|
+
httpStatus,
|
|
53
|
+
error
|
|
54
|
+
});
|
|
24
55
|
};
|
|
25
|
-
const callerResponseStreamChunk = (cacheName, data, hookId, error = NO_ERROR) => {
|
|
26
|
-
const buffer = data
|
|
27
|
-
const payload = {
|
|
56
|
+
const callerResponseStreamChunk = (cacheName, data, hookId, error = NO_ERROR, requestId) => {
|
|
57
|
+
const buffer = data.byteLength ? data : EMPTY_BUFFER;
|
|
58
|
+
const payload = {
|
|
59
|
+
type: "stream",
|
|
60
|
+
...requestId != null && requestId !== "" && { requestId },
|
|
61
|
+
cacheName,
|
|
62
|
+
stream: "chunk",
|
|
63
|
+
data: buffer,
|
|
64
|
+
hookId,
|
|
65
|
+
error
|
|
66
|
+
};
|
|
28
67
|
self.postMessage(payload, buffer.byteLength > 0 ? [buffer] : []);
|
|
29
68
|
};
|
|
30
|
-
const callerResponseStreamEnd = (cacheName, hookId, error = NO_ERROR) => {
|
|
31
|
-
self.postMessage({
|
|
69
|
+
const callerResponseStreamEnd = (cacheName, hookId, error = NO_ERROR, requestId) => {
|
|
70
|
+
self.postMessage({
|
|
71
|
+
type: "stream",
|
|
72
|
+
...requestId != null && requestId !== "" && { requestId },
|
|
73
|
+
cacheName,
|
|
74
|
+
stream: "end",
|
|
75
|
+
hookId,
|
|
76
|
+
error
|
|
77
|
+
});
|
|
32
78
|
};
|
|
33
79
|
const transferableBuffer = (view) => view.byteOffset === 0 && view.byteLength === view.buffer.byteLength ? view.buffer : view.slice(0).buffer;
|
|
34
80
|
const store = /* @__PURE__ */ Object.create(null);
|
|
81
|
+
const storeActivity = /* @__PURE__ */ new Map();
|
|
82
|
+
const STALE_ENTRY_MS = 5e3;
|
|
83
|
+
const CLEANUP_INTERVAL_MS = 3e4;
|
|
35
84
|
const normalizeKey = (key) => key.toLocaleLowerCase();
|
|
36
|
-
const
|
|
85
|
+
const touchActivity = (key) => {
|
|
86
|
+
storeActivity.set(normalizeKey(key), Date.now());
|
|
87
|
+
};
|
|
88
|
+
const get = (key) => {
|
|
89
|
+
const nk = normalizeKey(key);
|
|
90
|
+
touchActivity(nk);
|
|
91
|
+
return store[nk];
|
|
92
|
+
};
|
|
37
93
|
const set = (key, value) => {
|
|
38
|
-
|
|
94
|
+
const nk = normalizeKey(key);
|
|
95
|
+
store[nk] = value;
|
|
96
|
+
touchActivity(nk);
|
|
39
97
|
};
|
|
40
98
|
const remove = (key) => {
|
|
41
|
-
|
|
99
|
+
const nk = normalizeKey(key);
|
|
100
|
+
delete store[nk];
|
|
101
|
+
storeActivity.delete(nk);
|
|
42
102
|
};
|
|
43
103
|
const isNonEmptyString = (v) => typeof v === "string" && v.trim() !== "";
|
|
44
104
|
const omitContentType = (headers) => {
|
|
@@ -48,13 +108,22 @@ const omitContentType = (headers) => {
|
|
|
48
108
|
return rest;
|
|
49
109
|
};
|
|
50
110
|
const isBinaryResponse = (r) => typeof r === "object" && r !== null && BINARY_MARKER in r && r.data instanceof ArrayBuffer;
|
|
51
|
-
const commit = (cacheName, data, hookId, httpStatus) => {
|
|
111
|
+
const commit = (cacheName, data, hookId, httpStatus, requestId) => {
|
|
52
112
|
if (!cacheName) {
|
|
53
|
-
callerResponse(
|
|
113
|
+
callerResponse(
|
|
114
|
+
"",
|
|
115
|
+
null,
|
|
116
|
+
hookId,
|
|
117
|
+
void 0,
|
|
118
|
+
makeError("Invalid commit: cacheName is required", "validation"),
|
|
119
|
+
"result",
|
|
120
|
+
requestId
|
|
121
|
+
);
|
|
54
122
|
return;
|
|
55
123
|
}
|
|
56
|
-
set(
|
|
57
|
-
|
|
124
|
+
set(cacheName, data);
|
|
125
|
+
touchActivity(cacheName);
|
|
126
|
+
callerResponse(cacheName, data, hookId, httpStatus, NO_ERROR, "result", requestId);
|
|
58
127
|
};
|
|
59
128
|
const parseResponseByContentType = async (response) => {
|
|
60
129
|
const contentType = response.headers.get("content-type")?.toLocaleLowerCase() || "";
|
|
@@ -63,7 +132,11 @@ const parseResponseByContentType = async (response) => {
|
|
|
63
132
|
return null;
|
|
64
133
|
}
|
|
65
134
|
if (contentType.includes("json")) {
|
|
66
|
-
|
|
135
|
+
const text = await response.text();
|
|
136
|
+
if (text.trim() === "") {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return JSON.parse(text);
|
|
67
140
|
}
|
|
68
141
|
if (contentType.startsWith("text/") || contentType.includes("xml") || contentType.includes("javascript") || contentType.includes("x-www-form-urlencoded")) {
|
|
69
142
|
return await response.text();
|
|
@@ -86,14 +159,14 @@ const appendToFormData = (formData, key, value, fileFieldName = DEFAULT_FILES_FI
|
|
|
86
159
|
return true;
|
|
87
160
|
}
|
|
88
161
|
if (value !== null && value !== void 0 && typeof value === "object") {
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
|
|
162
|
+
const visitedSet = visited ?? /* @__PURE__ */ new WeakSet();
|
|
163
|
+
if (visitedSet.has(value)) return false;
|
|
164
|
+
visitedSet.add(value);
|
|
92
165
|
if (Array.isArray(value)) {
|
|
93
166
|
let hasFile2 = false;
|
|
94
167
|
let i = 0;
|
|
95
168
|
while (i < value.length) {
|
|
96
|
-
hasFile2 = appendToFormData(formData, key ? `${key}.${i}` : String(i), value[i], fileFieldName,
|
|
169
|
+
hasFile2 = appendToFormData(formData, key ? `${key}.${i}` : String(i), value[i], fileFieldName, visitedSet) || hasFile2;
|
|
97
170
|
i++;
|
|
98
171
|
}
|
|
99
172
|
return hasFile2;
|
|
@@ -103,22 +176,20 @@ const appendToFormData = (formData, key, value, fileFieldName = DEFAULT_FILES_FI
|
|
|
103
176
|
let ki = 0;
|
|
104
177
|
while (ki < keys.length) {
|
|
105
178
|
const k = keys[ki];
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
) || hasFile;
|
|
114
|
-
}
|
|
179
|
+
hasFile = appendToFormData(
|
|
180
|
+
formData,
|
|
181
|
+
key ? `${key}.${k}` : k,
|
|
182
|
+
value[k],
|
|
183
|
+
fileFieldName,
|
|
184
|
+
visitedSet
|
|
185
|
+
) || hasFile;
|
|
115
186
|
ki++;
|
|
116
187
|
}
|
|
117
188
|
return hasFile;
|
|
118
189
|
}
|
|
119
190
|
if (value !== void 0 && value !== null) {
|
|
120
191
|
const primitive = value;
|
|
121
|
-
formData.append(key, primitive);
|
|
192
|
+
formData.append(key, String(primitive));
|
|
122
193
|
}
|
|
123
194
|
return false;
|
|
124
195
|
};
|
|
@@ -151,13 +222,32 @@ const getPayloadType = (payload) => {
|
|
|
151
222
|
}
|
|
152
223
|
};
|
|
153
224
|
const buildJsonBody = (payload) => JSON.stringify(payload);
|
|
154
|
-
const buildUrlEncodedBody = (payload) =>
|
|
225
|
+
const buildUrlEncodedBody = (payload) => {
|
|
226
|
+
const params = new URLSearchParams();
|
|
227
|
+
const keys = Object.keys(payload);
|
|
228
|
+
let i = 0;
|
|
229
|
+
while (i < keys.length) {
|
|
230
|
+
const key = keys[i];
|
|
231
|
+
const value = payload[key];
|
|
232
|
+
if (value !== null && value !== void 0) {
|
|
233
|
+
if (typeof value === "object") {
|
|
234
|
+
throw new Error(`Cannot url-encode non-primitive value for key "${key}"`);
|
|
235
|
+
}
|
|
236
|
+
params.append(key, value.toString());
|
|
237
|
+
}
|
|
238
|
+
i++;
|
|
239
|
+
}
|
|
240
|
+
return params.toString();
|
|
241
|
+
};
|
|
155
242
|
const buildTextBody = (payload) => String(payload);
|
|
156
243
|
const prepareRequestBody = (payload, headers, options) => {
|
|
157
244
|
const payloadType = getPayloadType(payload);
|
|
158
245
|
switch (payloadType) {
|
|
159
246
|
case "formdata":
|
|
160
|
-
return {
|
|
247
|
+
return {
|
|
248
|
+
body: payload,
|
|
249
|
+
headers: omitContentType({ ...headers })
|
|
250
|
+
};
|
|
161
251
|
case "blob":
|
|
162
252
|
return { body: payload, headers: { ...headers } };
|
|
163
253
|
case "arraybuffer":
|
|
@@ -165,28 +255,31 @@ const prepareRequestBody = (payload, headers, options) => {
|
|
|
165
255
|
case "arraybufferview":
|
|
166
256
|
return { body: payload, headers: { ...headers } };
|
|
167
257
|
case "stream":
|
|
168
|
-
return {
|
|
258
|
+
return {
|
|
259
|
+
body: payload,
|
|
260
|
+
headers: { ...headers }
|
|
261
|
+
};
|
|
169
262
|
case "string":
|
|
170
263
|
return { body: payload, headers: { ...headers } };
|
|
171
264
|
case "object": {
|
|
172
|
-
const h = { ...headers };
|
|
173
|
-
const contentType = getContentType(h);
|
|
174
265
|
const fileFieldName = options?.formDataFileFieldName ?? DEFAULT_FILES_FIELD;
|
|
175
266
|
const formDataKey = options?.formDataKey ?? DEFAULT_FILES_FIELD;
|
|
267
|
+
const formData = createFormDataIfBlobOrFile(payload, fileFieldName, formDataKey);
|
|
268
|
+
if (formData) {
|
|
269
|
+
return { body: formData, headers: omitContentType({ ...headers }) };
|
|
270
|
+
}
|
|
271
|
+
const h = { ...headers };
|
|
272
|
+
let contentType = getContentType(h);
|
|
273
|
+
if (contentType.includes("multipart/form-data")) {
|
|
274
|
+
contentType = "application/json";
|
|
275
|
+
delete h["Content-Type"];
|
|
276
|
+
delete h["content-type"];
|
|
277
|
+
}
|
|
176
278
|
let body;
|
|
177
|
-
if (contentType.includes("application/
|
|
178
|
-
body = buildJsonBody(payload);
|
|
179
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
279
|
+
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
180
280
|
body = buildUrlEncodedBody(payload);
|
|
181
281
|
} else if (contentType.startsWith("text/") || contentType.includes("xml")) {
|
|
182
282
|
body = buildTextBody(payload);
|
|
183
|
-
} else if (contentType.includes("multipart/form-data")) {
|
|
184
|
-
const formData = createFormDataIfBlobOrFile(payload, fileFieldName, formDataKey);
|
|
185
|
-
if (formData) {
|
|
186
|
-
return { body: formData, headers: omitContentType(h) };
|
|
187
|
-
}
|
|
188
|
-
body = buildJsonBody(payload);
|
|
189
|
-
h["Content-Type"] = "application/json";
|
|
190
283
|
} else {
|
|
191
284
|
body = buildJsonBody(payload);
|
|
192
285
|
}
|
|
@@ -199,8 +292,8 @@ const prepareRequestBody = (payload, headers, options) => {
|
|
|
199
292
|
return { headers: { ...headers } };
|
|
200
293
|
}
|
|
201
294
|
};
|
|
202
|
-
const inFlightControllers = /* @__PURE__ */ new Map();
|
|
203
295
|
const inFlightByCacheName = /* @__PURE__ */ new Map();
|
|
296
|
+
const requestIdToCacheName = /* @__PURE__ */ new Map();
|
|
204
297
|
const apiRequest = async (cacheName, payload, {
|
|
205
298
|
url,
|
|
206
299
|
method,
|
|
@@ -219,21 +312,32 @@ const apiRequest = async (cacheName, payload, {
|
|
|
219
312
|
if (!skipInFlightDedupe) {
|
|
220
313
|
const existing = inFlightByCacheName.get(cacheName);
|
|
221
314
|
if (existing) {
|
|
315
|
+
if (requestId) {
|
|
316
|
+
existing.requestIds.add(requestId);
|
|
317
|
+
requestIdToCacheName.set(requestId, cacheName);
|
|
318
|
+
}
|
|
222
319
|
const cached = get(cacheName);
|
|
223
|
-
if (cached !== void 0) callerResponse(cacheName, cached, hookId);
|
|
224
|
-
await existing;
|
|
320
|
+
if (cached !== void 0) callerResponse(cacheName, cached, hookId, void 0, NO_ERROR, "result", requestId);
|
|
321
|
+
await existing.promise;
|
|
225
322
|
const fresh = get(cacheName);
|
|
226
|
-
if (fresh !== void 0) callerResponse(cacheName, fresh, hookId);
|
|
323
|
+
if (fresh !== void 0) callerResponse(cacheName, fresh, hookId, void 0, NO_ERROR, "result", requestId);
|
|
227
324
|
return;
|
|
228
325
|
}
|
|
229
326
|
}
|
|
230
327
|
const controller = new AbortController();
|
|
328
|
+
const requestIds = /* @__PURE__ */ new Set();
|
|
329
|
+
const inflightKey = skipInFlightDedupe && requestId ? requestId : cacheName;
|
|
231
330
|
if (requestId) {
|
|
232
|
-
|
|
331
|
+
requestIds.add(requestId);
|
|
332
|
+
requestIdToCacheName.set(requestId, inflightKey);
|
|
233
333
|
}
|
|
234
334
|
let timeoutId;
|
|
335
|
+
let abortedByTimeout = false;
|
|
235
336
|
if (timeoutMs != null && timeoutMs > 0) {
|
|
236
|
-
timeoutId = setTimeout(() =>
|
|
337
|
+
timeoutId = setTimeout(() => {
|
|
338
|
+
abortedByTimeout = true;
|
|
339
|
+
controller.abort();
|
|
340
|
+
}, timeoutMs);
|
|
237
341
|
}
|
|
238
342
|
const promise = (async () => {
|
|
239
343
|
const fetchOptions = {
|
|
@@ -242,47 +346,57 @@ const apiRequest = async (cacheName, payload, {
|
|
|
242
346
|
credentials,
|
|
243
347
|
signal: controller.signal
|
|
244
348
|
};
|
|
245
|
-
if (methodLower !== "get" && payload != null) {
|
|
246
|
-
const prepareOptions = {};
|
|
247
|
-
if (formDataFileFieldName != null && formDataFileFieldName !== "")
|
|
248
|
-
prepareOptions.formDataFileFieldName = formDataFileFieldName;
|
|
249
|
-
if (formDataKey != null && formDataKey !== "") prepareOptions.formDataKey = formDataKey;
|
|
250
|
-
const { body, headers: processedHeaders } = prepareRequestBody(payload, headers, prepareOptions);
|
|
251
|
-
if (body !== void 0) fetchOptions.body = body;
|
|
252
|
-
fetchOptions.headers = processedHeaders;
|
|
253
|
-
} else {
|
|
254
|
-
fetchOptions.headers = omitContentType({ ...headers });
|
|
255
|
-
}
|
|
256
349
|
try {
|
|
350
|
+
if (methodLower !== "get" && payload != null) {
|
|
351
|
+
const prepareOptions = {};
|
|
352
|
+
if (formDataFileFieldName != null && formDataFileFieldName !== "")
|
|
353
|
+
prepareOptions.formDataFileFieldName = formDataFileFieldName;
|
|
354
|
+
if (formDataKey != null && formDataKey !== "") prepareOptions.formDataKey = formDataKey;
|
|
355
|
+
const { body, headers: processedHeaders } = prepareRequestBody(payload, headers, prepareOptions);
|
|
356
|
+
if (body !== void 0) fetchOptions.body = body;
|
|
357
|
+
fetchOptions.headers = processedHeaders;
|
|
358
|
+
} else {
|
|
359
|
+
fetchOptions.headers = omitContentType({ ...headers });
|
|
360
|
+
}
|
|
257
361
|
if (responseTypeLower === "stream") {
|
|
258
362
|
const maxRetries = Math.min(retries ?? 3, 5);
|
|
259
363
|
let bytesReceived = 0;
|
|
260
364
|
let streamError = NO_ERROR;
|
|
261
365
|
let attempt = 0;
|
|
366
|
+
let isPermanentError = false;
|
|
262
367
|
while (attempt <= maxRetries) {
|
|
263
368
|
try {
|
|
264
|
-
const reqHeaders = methodLower === "get" ? omitContentType({ ...fetchOptions.headers }) : fetchOptions.headers;
|
|
369
|
+
const reqHeaders = methodLower === "get" ? omitContentType({ ...fetchOptions.headers }) : { ...fetchOptions.headers };
|
|
265
370
|
if (bytesReceived > 0) reqHeaders["Range"] = `bytes=${bytesReceived}-`;
|
|
266
|
-
const streamResponse = await fetch(url, {
|
|
371
|
+
const streamResponse = await fetch(url, {
|
|
372
|
+
...fetchOptions,
|
|
373
|
+
headers: reqHeaders
|
|
374
|
+
});
|
|
267
375
|
if (streamResponse.status >= 400) {
|
|
268
|
-
streamError = makeError(streamResponse.statusText);
|
|
376
|
+
streamError = makeError(streamResponse.statusText || DEFAULT_ERROR, "http");
|
|
377
|
+
isPermanentError = true;
|
|
269
378
|
break;
|
|
270
379
|
}
|
|
271
380
|
if (streamResponse.status === 204) {
|
|
272
|
-
callerResponseStreamEnd(cacheName, hookId);
|
|
381
|
+
callerResponseStreamEnd(cacheName, hookId, NO_ERROR, requestId);
|
|
273
382
|
return;
|
|
274
383
|
}
|
|
275
|
-
if (streamResponse.status === 416)
|
|
384
|
+
if (streamResponse.status === 416) {
|
|
385
|
+
streamError = makeError("Range Not Satisfiable", "http");
|
|
386
|
+
isPermanentError = true;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
276
389
|
const contentType = streamResponse.headers.get("content-type") ?? void 0;
|
|
277
390
|
const meta = {
|
|
278
391
|
contentDisposition: streamResponse.headers.get("content-disposition") ?? null,
|
|
279
392
|
...contentType !== void 0 && { contentType }
|
|
280
393
|
};
|
|
281
|
-
if (bytesReceived === 0)
|
|
282
|
-
|
|
394
|
+
if (bytesReceived === 0)
|
|
395
|
+
callerResponseStreamStart(cacheName, meta, hookId, streamResponse.status, NO_ERROR, requestId);
|
|
396
|
+
else callerResponseStreamResume(cacheName, meta, hookId, streamResponse.status, NO_ERROR, requestId);
|
|
283
397
|
const body = streamResponse.body;
|
|
284
398
|
if (!body) {
|
|
285
|
-
callerResponseStreamEnd(cacheName, hookId);
|
|
399
|
+
callerResponseStreamEnd(cacheName, hookId, NO_ERROR, requestId);
|
|
286
400
|
return;
|
|
287
401
|
}
|
|
288
402
|
const reader = body.getReader();
|
|
@@ -299,31 +413,45 @@ const apiRequest = async (cacheName, payload, {
|
|
|
299
413
|
const offset = skipRemaining;
|
|
300
414
|
skipRemaining = 0;
|
|
301
415
|
const tail = value.subarray(offset);
|
|
302
|
-
callerResponseStreamChunk(cacheName, transferableBuffer(tail), hookId);
|
|
416
|
+
callerResponseStreamChunk(cacheName, transferableBuffer(tail), hookId, NO_ERROR, requestId);
|
|
303
417
|
bytesReceived += tail.byteLength;
|
|
304
418
|
} else {
|
|
305
|
-
callerResponseStreamChunk(cacheName, transferableBuffer(value), hookId);
|
|
419
|
+
callerResponseStreamChunk(cacheName, transferableBuffer(value), hookId, NO_ERROR, requestId);
|
|
306
420
|
bytesReceived += value.byteLength;
|
|
307
421
|
}
|
|
308
422
|
}
|
|
309
423
|
break;
|
|
310
424
|
} catch (err) {
|
|
311
|
-
|
|
425
|
+
if (err.name === "AbortError") {
|
|
426
|
+
streamError = makeError("Request aborted", abortedByTimeout ? "timeout" : "aborted");
|
|
427
|
+
isPermanentError = true;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
streamError = makeError(err.message, "network");
|
|
312
431
|
if (attempt === maxRetries) break;
|
|
313
432
|
}
|
|
433
|
+
if (isPermanentError) break;
|
|
314
434
|
attempt++;
|
|
315
435
|
}
|
|
316
|
-
callerResponseStreamEnd(cacheName, hookId, streamError);
|
|
436
|
+
callerResponseStreamEnd(cacheName, hookId, streamError, requestId);
|
|
317
437
|
return;
|
|
318
438
|
}
|
|
319
439
|
const response = await fetch(url, fetchOptions);
|
|
320
440
|
if (response.status >= 400) {
|
|
321
|
-
callerResponse(
|
|
441
|
+
callerResponse(
|
|
442
|
+
cacheName,
|
|
443
|
+
null,
|
|
444
|
+
hookId,
|
|
445
|
+
response.status,
|
|
446
|
+
makeError(response.statusText || DEFAULT_ERROR, "http"),
|
|
447
|
+
"result",
|
|
448
|
+
requestId
|
|
449
|
+
);
|
|
322
450
|
return;
|
|
323
451
|
}
|
|
324
452
|
if (response.status === 204) {
|
|
325
|
-
set(
|
|
326
|
-
callerResponse(cacheName, null, hookId, 204);
|
|
453
|
+
set(cacheName, null);
|
|
454
|
+
callerResponse(cacheName, null, hookId, 204, NO_ERROR, "result", requestId);
|
|
327
455
|
return;
|
|
328
456
|
}
|
|
329
457
|
const responseData = await parseResponseByContentType(response);
|
|
@@ -336,34 +464,52 @@ const apiRequest = async (cacheName, payload, {
|
|
|
336
464
|
contentDisposition: responseData.contentDisposition ?? null
|
|
337
465
|
},
|
|
338
466
|
hookId,
|
|
339
|
-
response.status
|
|
467
|
+
response.status,
|
|
468
|
+
NO_ERROR,
|
|
469
|
+
requestId
|
|
340
470
|
);
|
|
341
471
|
} else {
|
|
342
|
-
commit(cacheName, responseData, hookId, response.status);
|
|
472
|
+
commit(cacheName, responseData, hookId, response.status, requestId);
|
|
343
473
|
}
|
|
344
474
|
} catch (error) {
|
|
345
475
|
if (error.name === "AbortError") {
|
|
346
|
-
callerResponse(
|
|
476
|
+
callerResponse(
|
|
477
|
+
cacheName,
|
|
478
|
+
null,
|
|
479
|
+
hookId,
|
|
480
|
+
void 0,
|
|
481
|
+
makeError("Request aborted", abortedByTimeout ? "timeout" : "aborted"),
|
|
482
|
+
"result",
|
|
483
|
+
requestId
|
|
484
|
+
);
|
|
347
485
|
return;
|
|
348
486
|
}
|
|
349
487
|
const err = error;
|
|
350
|
-
callerResponse(cacheName, null, hookId, void 0, makeError(err.message));
|
|
488
|
+
callerResponse(cacheName, null, hookId, void 0, makeError(err.message, "network"), "result", requestId);
|
|
351
489
|
} finally {
|
|
352
490
|
if (timeoutId != null) clearTimeout(timeoutId);
|
|
353
|
-
|
|
354
|
-
|
|
491
|
+
for (const id of requestIds) {
|
|
492
|
+
requestIdToCacheName.delete(id);
|
|
355
493
|
}
|
|
356
|
-
inFlightByCacheName.delete(
|
|
494
|
+
inFlightByCacheName.delete(inflightKey);
|
|
357
495
|
}
|
|
358
496
|
})();
|
|
359
|
-
|
|
497
|
+
const entry = { promise, controller, requestIds };
|
|
498
|
+
inFlightByCacheName.set(inflightKey, entry);
|
|
360
499
|
await promise;
|
|
361
500
|
};
|
|
362
501
|
const onRequest = (dataRequest) => {
|
|
363
502
|
const { cacheName, type, payload, request, requestId, hookId } = dataRequest;
|
|
364
|
-
const responseCacheName = dataRequest.cacheName ?? "";
|
|
365
503
|
if (!isNonEmptyString(type)) {
|
|
366
|
-
callerResponse(
|
|
504
|
+
callerResponse(
|
|
505
|
+
cacheName ?? "",
|
|
506
|
+
null,
|
|
507
|
+
hookId,
|
|
508
|
+
void 0,
|
|
509
|
+
makeError("Invalid request: type is required", "validation"),
|
|
510
|
+
"result",
|
|
511
|
+
requestId
|
|
512
|
+
);
|
|
367
513
|
return;
|
|
368
514
|
}
|
|
369
515
|
const lowerType = normalizeKey(type);
|
|
@@ -372,26 +518,44 @@ const onRequest = (dataRequest) => {
|
|
|
372
518
|
return;
|
|
373
519
|
}
|
|
374
520
|
if (!isNonEmptyString(cacheName)) {
|
|
375
|
-
callerResponse(
|
|
521
|
+
callerResponse(
|
|
522
|
+
cacheName ?? "",
|
|
523
|
+
null,
|
|
524
|
+
hookId,
|
|
525
|
+
void 0,
|
|
526
|
+
makeError("Invalid request: cacheName is required", "validation"),
|
|
527
|
+
"result",
|
|
528
|
+
requestId
|
|
529
|
+
);
|
|
376
530
|
return;
|
|
377
531
|
}
|
|
378
532
|
const lowerCacheName = normalizeKey(cacheName);
|
|
379
533
|
if (lowerType === "get") {
|
|
380
534
|
const requestedData = get(lowerCacheName);
|
|
381
535
|
if (requestedData === void 0) {
|
|
382
|
-
callerResponse(
|
|
536
|
+
callerResponse(
|
|
537
|
+
lowerCacheName,
|
|
538
|
+
null,
|
|
539
|
+
hookId,
|
|
540
|
+
void 0,
|
|
541
|
+
makeError("Cache miss", "validation"),
|
|
542
|
+
"result",
|
|
543
|
+
requestId
|
|
544
|
+
);
|
|
383
545
|
} else {
|
|
384
|
-
callerResponse(lowerCacheName, requestedData, hookId);
|
|
546
|
+
callerResponse(lowerCacheName, requestedData, hookId, void 0, NO_ERROR, "result", requestId);
|
|
385
547
|
}
|
|
386
548
|
} else if (lowerType === "set") {
|
|
387
549
|
if (!request) {
|
|
388
550
|
if (payload == null) {
|
|
389
551
|
callerResponse(
|
|
390
|
-
|
|
552
|
+
cacheName ?? "",
|
|
391
553
|
null,
|
|
392
554
|
hookId,
|
|
393
555
|
void 0,
|
|
394
|
-
makeError("Invalid request: payload is required for set")
|
|
556
|
+
makeError("Invalid request: payload is required for set", "validation"),
|
|
557
|
+
"result",
|
|
558
|
+
requestId
|
|
395
559
|
);
|
|
396
560
|
return;
|
|
397
561
|
}
|
|
@@ -400,11 +564,13 @@ const onRequest = (dataRequest) => {
|
|
|
400
564
|
const methodLower = normalizeKey(request.method);
|
|
401
565
|
if (methodLower !== "get" && payload == null) {
|
|
402
566
|
callerResponse(
|
|
403
|
-
|
|
567
|
+
cacheName ?? "",
|
|
404
568
|
null,
|
|
405
569
|
hookId,
|
|
406
570
|
void 0,
|
|
407
|
-
makeError("Invalid request: payload is required for non-GET API request")
|
|
571
|
+
makeError("Invalid request: payload is required for non-GET API request", "validation"),
|
|
572
|
+
"result",
|
|
573
|
+
requestId
|
|
408
574
|
);
|
|
409
575
|
return;
|
|
410
576
|
}
|
|
@@ -412,16 +578,33 @@ const onRequest = (dataRequest) => {
|
|
|
412
578
|
}
|
|
413
579
|
} else if (lowerType === "delete") {
|
|
414
580
|
remove(lowerCacheName);
|
|
415
|
-
callerResponse(lowerCacheName,
|
|
581
|
+
callerResponse(lowerCacheName, null, hookId, void 0, NO_ERROR, "delete", requestId);
|
|
416
582
|
}
|
|
417
583
|
};
|
|
418
584
|
const onCancel = (requestId) => {
|
|
419
|
-
const
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
585
|
+
const inflightKey = requestIdToCacheName.get(requestId);
|
|
586
|
+
if (!inflightKey) return;
|
|
587
|
+
const entry = inFlightByCacheName.get(inflightKey);
|
|
588
|
+
if (!entry) {
|
|
589
|
+
requestIdToCacheName.delete(requestId);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
entry.requestIds.delete(requestId);
|
|
593
|
+
requestIdToCacheName.delete(requestId);
|
|
594
|
+
if (entry.requestIds.size === 0) {
|
|
595
|
+
entry.controller.abort();
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const runStaleStoreCleanup = () => {
|
|
599
|
+
const now = Date.now();
|
|
600
|
+
for (const [key, lastAccessAt] of storeActivity) {
|
|
601
|
+
if (now - lastAccessAt < STALE_ENTRY_MS) continue;
|
|
602
|
+
if (inFlightByCacheName.has(key)) continue;
|
|
603
|
+
delete store[key];
|
|
604
|
+
storeActivity.delete(key);
|
|
423
605
|
}
|
|
424
606
|
};
|
|
607
|
+
setInterval(runStaleStoreCleanup, CLEANUP_INTERVAL_MS);
|
|
425
608
|
onmessage = (event) => {
|
|
426
609
|
const payload = event.data;
|
|
427
610
|
if (payload === null || typeof payload !== "object") return;
|