@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.
@@ -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 callerResponse = (cacheName, data, hookId, httpStatus, error = NO_ERROR) => {
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) => ({ message });
14
- const callerResponseBinary = (cacheName, data, meta, hookId, httpStatus, error = NO_ERROR) => {
15
- const buffer = data?.byteLength ? data : new ArrayBuffer(0);
16
- const payload = { cacheName, data: buffer, meta, hookId, httpStatus, error };
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({ cacheName, stream: "start", meta, hookId, httpStatus, error });
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({ cacheName, stream: "resume", meta, hookId, httpStatus, error });
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?.byteLength ? data : new ArrayBuffer(0);
27
- const payload = { cacheName, stream: "chunk", data: buffer, hookId, error };
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({ cacheName, stream: "end", hookId, error });
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 get = (key) => store[normalizeKey(key)];
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
- store[normalizeKey(key)] = value;
94
+ const nk = normalizeKey(key);
95
+ store[nk] = value;
96
+ touchActivity(nk);
39
97
  };
40
98
  const remove = (key) => {
41
- delete store[normalizeKey(key)];
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("", null, hookId, void 0, makeError("Invalid commit: cacheName is required"));
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(normalizeKey(cacheName), data);
57
- callerResponse(cacheName, data, hookId, httpStatus);
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
- return await response.json();
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 set2 = visited ?? /* @__PURE__ */ new WeakSet();
90
- if (set2.has(value)) return false;
91
- set2.add(value);
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, set2) || hasFile2;
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
- if (Object.prototype.hasOwnProperty.call(value, k)) {
107
- hasFile = appendToFormData(
108
- formData,
109
- key ? `${key}.${k}` : k,
110
- value[k],
111
- fileFieldName,
112
- set2
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) => new URLSearchParams(payload).toString();
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 { body: payload, headers: omitContentType({ ...headers }) };
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 { body: payload, headers: { ...headers } };
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/json")) {
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
- inFlightControllers.set(requestId, controller);
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(() => controller.abort(), timeoutMs);
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, { ...fetchOptions, headers: reqHeaders });
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) break;
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) callerResponseStreamStart(cacheName, meta, hookId, streamResponse.status);
282
- else callerResponseStreamResume(cacheName, meta, hookId, streamResponse.status);
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
- streamError = err.name === "AbortError" ? makeError("Request aborted") : makeError(err.message);
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(cacheName, null, hookId, response.status, makeError(response.statusText));
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(normalizeKey(cacheName), null);
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(cacheName, null, hookId, void 0, makeError("Request aborted"));
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
- if (requestId) {
354
- inFlightControllers.delete(requestId);
491
+ for (const id of requestIds) {
492
+ requestIdToCacheName.delete(id);
355
493
  }
356
- inFlightByCacheName.delete(cacheName);
494
+ inFlightByCacheName.delete(inflightKey);
357
495
  }
358
496
  })();
359
- inFlightByCacheName.set(cacheName, promise);
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(responseCacheName, null, hookId, void 0, makeError("Invalid request: type is required"));
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(responseCacheName, null, hookId, void 0, makeError("Invalid request: cacheName is required"));
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(lowerCacheName, null, hookId, void 0, makeError("Cache miss"));
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
- responseCacheName,
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
- responseCacheName,
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, { deleted: true }, hookId);
581
+ callerResponse(lowerCacheName, null, hookId, void 0, NO_ERROR, "delete", requestId);
416
582
  }
417
583
  };
418
584
  const onCancel = (requestId) => {
419
- const controller = inFlightControllers.get(requestId);
420
- if (controller) {
421
- controller.abort();
422
- inFlightControllers.delete(requestId);
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;