@tinycloud/sdk-services 3.1.0-beta.6 → 3.1.0-beta.7

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/kv/index.cjs CHANGED
@@ -192,11 +192,31 @@ function authRequiredError(service) {
192
192
  service
193
193
  };
194
194
  }
195
- function timeoutError(service) {
195
+ var RequestTimeoutError = class extends Error {
196
+ constructor(timeoutMs) {
197
+ super(`Request timed out after ${timeoutMs}ms.`);
198
+ this.name = "TimeoutError";
199
+ this.timeoutMs = timeoutMs;
200
+ }
201
+ };
202
+ function isRequestTimeoutError(error) {
203
+ return error instanceof RequestTimeoutError || error instanceof Error && error.name === "TimeoutError" && typeof error.timeoutMs === "number";
204
+ }
205
+ function timeoutError(service, timeoutMs, cause) {
206
+ if (timeoutMs === void 0) {
207
+ return {
208
+ code: ErrorCodes.TIMEOUT,
209
+ message: "Request timed out.",
210
+ service,
211
+ ...cause === void 0 ? {} : { cause }
212
+ };
213
+ }
196
214
  return {
197
215
  code: ErrorCodes.TIMEOUT,
198
- message: "Request timed out.",
199
- service
216
+ message: `Request timed out after ${timeoutMs}ms.`,
217
+ service,
218
+ ...cause === void 0 ? {} : { cause },
219
+ meta: { timeoutMs }
200
220
  };
201
221
  }
202
222
  function abortedError(service) {
@@ -343,6 +363,9 @@ function storageRejectionError(service, status, meta, responseText) {
343
363
  }
344
364
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
345
365
  if (error instanceof Error) {
366
+ if (isRequestTimeoutError(error)) {
367
+ return timeoutError(service, error.timeoutMs, error);
368
+ }
346
369
  if (error.name === "AbortError") {
347
370
  return abortedError(service);
348
371
  }
@@ -364,6 +387,7 @@ function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
364
387
  }
365
388
 
366
389
  // src/base/BaseService.ts
390
+ var MAX_TIMER_DELAY_MS = 2147483647;
367
391
  var BaseService = class {
368
392
  constructor() {
369
393
  /**
@@ -544,6 +568,64 @@ var BaseService = class {
544
568
  }
545
569
  return controller.signal;
546
570
  }
571
+ /**
572
+ * Create the abort signal for one request, bounded by the request timeout.
573
+ *
574
+ * The signal aborts when the service signs out, the context aborts, the
575
+ * caller's `signal` aborts, or the timeout elapses, whichever comes first.
576
+ * A timeout aborts with a {@link RequestTimeoutError} (`TimeoutError`), which
577
+ * `wrapError` maps to `ErrorCodes.TIMEOUT`; the other sources keep their own
578
+ * reason (normally `AbortError`, mapped to `ErrorCodes.ABORTED`).
579
+ *
580
+ * The timeout is `timeoutMs` when given, otherwise `config.timeout`. Only a
581
+ * positive, finite value (at most 2^31-1 ms) applies; `undefined`, `0`, and
582
+ * `Infinity` mean no timeout, so a per-call `0` opts out of a configured one.
583
+ *
584
+ * Callers must call `dispose()` once the response body has been consumed,
585
+ * which clears the timer and removes the listeners this adds to the
586
+ * long-lived service and context signals.
587
+ *
588
+ * @param signal - The caller's abort signal for this request
589
+ * @param timeoutMs - Per-request timeout override in milliseconds
590
+ */
591
+ createRequestSignal(signal, timeoutMs) {
592
+ const controller = new AbortController();
593
+ const detachers = [];
594
+ let timer;
595
+ const dispose = () => {
596
+ if (timer !== void 0) {
597
+ clearTimeout(timer);
598
+ timer = void 0;
599
+ }
600
+ for (const detach of detachers.splice(0)) {
601
+ detach();
602
+ }
603
+ };
604
+ const abort = (reason) => {
605
+ dispose();
606
+ controller.abort(reason);
607
+ };
608
+ const requestSignal = { signal: controller.signal, dispose };
609
+ const parents = [
610
+ this.abortController.signal,
611
+ this.context?.abortSignal,
612
+ signal
613
+ ].filter((parent) => parent !== void 0);
614
+ for (const parent of parents) {
615
+ if (parent.aborted) {
616
+ abort(parent.reason);
617
+ return requestSignal;
618
+ }
619
+ const onAbort = () => abort(parent.reason);
620
+ parent.addEventListener("abort", onAbort, { once: true });
621
+ detachers.push(() => parent.removeEventListener("abort", onAbort));
622
+ }
623
+ const timeout = timeoutMs ?? this._config.timeout;
624
+ if (typeof timeout === "number" && timeout > 0 && timeout <= MAX_TIMER_DELAY_MS) {
625
+ timer = setTimeout(() => abort(new RequestTimeoutError(timeout)), timeout);
626
+ }
627
+ return requestSignal;
628
+ }
547
629
  /**
548
630
  * Wrap an operation with error handling and telemetry.
549
631
  *
@@ -804,7 +886,7 @@ var KVService = class extends BaseService {
804
886
  * @param path - Resource path
805
887
  * @param action - KV action
806
888
  * @param body - Optional request body
807
- * @param signal - Optional abort signal
889
+ * @param signal - Request signal from createRequestSignal()
808
890
  * @returns Fetch response
809
891
  */
810
892
  async invokeOperation(path, action, body, signal, extraHeaders) {
@@ -820,7 +902,7 @@ var KVService = class extends BaseService {
820
902
  method: "POST",
821
903
  headers: requestHeaders,
822
904
  body,
823
- signal: this.combineSignals(signal)
905
+ signal
824
906
  });
825
907
  }
826
908
  /**
@@ -997,6 +1079,7 @@ var KVService = class extends BaseService {
997
1079
  "kv"
998
1080
  ));
999
1081
  }
1082
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1000
1083
  try {
1001
1084
  const session = this.context.session;
1002
1085
  const invocationHeaders = this.context.invokeAny(
@@ -1018,7 +1101,7 @@ var KVService = class extends BaseService {
1018
1101
  const response = await this.context.fetch(`${this.host}/invoke`, {
1019
1102
  method: "POST",
1020
1103
  headers,
1021
- signal: this.combineSignals(options?.signal)
1104
+ signal: request.signal
1022
1105
  });
1023
1106
  if (!response.ok) {
1024
1107
  const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
@@ -1083,6 +1166,8 @@ var KVService = class extends BaseService {
1083
1166
  return ok({ results, count: results.length });
1084
1167
  } catch (error) {
1085
1168
  return err(wrapError("kv", error));
1169
+ } finally {
1170
+ request.dispose();
1086
1171
  }
1087
1172
  }
1088
1173
  /**
@@ -1215,12 +1300,13 @@ var KVService = class extends BaseService {
1215
1300
  ));
1216
1301
  }
1217
1302
  const path = this.getFullPath(key, options?.prefix);
1303
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1218
1304
  try {
1219
1305
  const response = await this.invokeOperation(
1220
1306
  path,
1221
1307
  KVAction.GET,
1222
1308
  void 0,
1223
- options?.signal,
1309
+ request.signal,
1224
1310
  options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
1225
1311
  );
1226
1312
  if (!response.ok) {
@@ -1277,6 +1363,8 @@ var KVService = class extends BaseService {
1277
1363
  });
1278
1364
  } catch (error) {
1279
1365
  return err(wrapError("kv", error));
1366
+ } finally {
1367
+ request.dispose();
1280
1368
  }
1281
1369
  });
1282
1370
  }
@@ -1304,12 +1392,13 @@ var KVService = class extends BaseService {
1304
1392
  }
1305
1393
  const path = this.getFullPath(key, options?.prefix);
1306
1394
  const body = this.serializePutValue(value, options?.contentType);
1395
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1307
1396
  try {
1308
1397
  const response = await this.invokeOperation(
1309
1398
  path,
1310
1399
  KVAction.PUT,
1311
1400
  body,
1312
- options?.signal,
1401
+ request.signal,
1313
1402
  {
1314
1403
  ...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
1315
1404
  ...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
@@ -1365,6 +1454,8 @@ var KVService = class extends BaseService {
1365
1454
  });
1366
1455
  } catch (error) {
1367
1456
  return err(wrapError("kv", error));
1457
+ } finally {
1458
+ request.dispose();
1368
1459
  }
1369
1460
  });
1370
1461
  }
@@ -1404,6 +1495,7 @@ var KVService = class extends BaseService {
1404
1495
  seen.add(path);
1405
1496
  }
1406
1497
  let requestMayHaveDispatched = false;
1498
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1407
1499
  try {
1408
1500
  const body = new FormData();
1409
1501
  for (let index = 0; index < items.length; index++) {
@@ -1426,7 +1518,7 @@ var KVService = class extends BaseService {
1426
1518
  method: "POST",
1427
1519
  headers,
1428
1520
  body,
1429
- signal: this.combineSignals(options?.signal)
1521
+ signal: request.signal
1430
1522
  };
1431
1523
  const fetchFn = this.context.fetch;
1432
1524
  requestMayHaveDispatched = true;
@@ -1477,14 +1569,16 @@ var KVService = class extends BaseService {
1477
1569
  rawBody = await response.json();
1478
1570
  } catch (jsonError) {
1479
1571
  const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
1572
+ const timedOut = isRequestTimeoutError(cause);
1480
1573
  return err(
1481
1574
  serviceError(
1482
- ErrorCodes.NETWORK_ERROR,
1483
- `KV batchPut response was not valid JSON: ${cause.message}`,
1575
+ timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
1576
+ timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
1484
1577
  "kv",
1485
1578
  {
1486
1579
  cause,
1487
1580
  meta: {
1581
+ ...timedOut ? { timeoutMs: cause.timeoutMs } : {},
1488
1582
  requestMayHaveDispatched: true,
1489
1583
  responseReceived: true,
1490
1584
  status: response.status,
@@ -1539,6 +1633,8 @@ var KVService = class extends BaseService {
1539
1633
  ...wrapped,
1540
1634
  meta: { ...wrapped.meta, requestMayHaveDispatched }
1541
1635
  });
1636
+ } finally {
1637
+ request.dispose();
1542
1638
  }
1543
1639
  });
1544
1640
  }
@@ -1561,12 +1657,13 @@ var KVService = class extends BaseService {
1561
1657
  if (options?.path) {
1562
1658
  listPath = listPath ? `${listPath}/${options.path}` : options.path;
1563
1659
  }
1660
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1564
1661
  try {
1565
1662
  const response = await this.invokeOperation(
1566
1663
  listPath,
1567
1664
  KVAction.LIST,
1568
1665
  void 0,
1569
- options?.signal,
1666
+ request.signal,
1570
1667
  options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
1571
1668
  ...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
1572
1669
  ...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
@@ -1607,6 +1704,8 @@ var KVService = class extends BaseService {
1607
1704
  });
1608
1705
  } catch (error) {
1609
1706
  return err(wrapError("kv", error));
1707
+ } finally {
1708
+ request.dispose();
1610
1709
  }
1611
1710
  });
1612
1711
  }
@@ -1619,12 +1718,13 @@ var KVService = class extends BaseService {
1619
1718
  return err(authRequiredError("kv"));
1620
1719
  }
1621
1720
  const path = this.getFullPath(key, options?.prefix);
1721
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1622
1722
  try {
1623
1723
  const response = await this.invokeOperation(
1624
1724
  path,
1625
1725
  KVAction.DELETE,
1626
1726
  void 0,
1627
- options?.signal,
1727
+ request.signal,
1628
1728
  options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
1629
1729
  );
1630
1730
  if (!response.ok) {
@@ -1672,6 +1772,8 @@ var KVService = class extends BaseService {
1672
1772
  });
1673
1773
  } catch (error) {
1674
1774
  return err(wrapError("kv", error));
1775
+ } finally {
1776
+ request.dispose();
1675
1777
  }
1676
1778
  });
1677
1779
  }
@@ -1684,12 +1786,13 @@ var KVService = class extends BaseService {
1684
1786
  return err(authRequiredError("kv"));
1685
1787
  }
1686
1788
  const path = this.getFullPath(key, options?.prefix);
1789
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1687
1790
  try {
1688
1791
  const response = await this.invokeOperation(
1689
1792
  path,
1690
1793
  KVAction.HEAD,
1691
1794
  void 0,
1692
- options?.signal
1795
+ request.signal
1693
1796
  );
1694
1797
  if (!response.ok) {
1695
1798
  if (response.status === 401 || response.status === 403) {
@@ -1720,6 +1823,8 @@ var KVService = class extends BaseService {
1720
1823
  });
1721
1824
  } catch (error) {
1722
1825
  return err(wrapError("kv", error));
1826
+ } finally {
1827
+ request.dispose();
1723
1828
  }
1724
1829
  });
1725
1830
  }
@@ -1757,12 +1862,13 @@ var KVService = class extends BaseService {
1757
1862
  if (options?.etag !== void 0) {
1758
1863
  body.etag = options.etag;
1759
1864
  }
1865
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1760
1866
  try {
1761
1867
  const response = await this.context.fetch(`${this.host}/signed/kv`, {
1762
1868
  method: "POST",
1763
1869
  headers: this.withJsonContentType(headers),
1764
1870
  body: JSON.stringify(body),
1765
- signal: this.combineSignals(options?.signal)
1871
+ signal: request.signal
1766
1872
  });
1767
1873
  if (!response.ok) {
1768
1874
  return this.createSignedReadUrlError(response, key, path);
@@ -1782,6 +1888,8 @@ var KVService = class extends BaseService {
1782
1888
  return ok(signedUrl);
1783
1889
  } catch (error) {
1784
1890
  return err(wrapError("kv", error));
1891
+ } finally {
1892
+ request.dispose();
1785
1893
  }
1786
1894
  });
1787
1895
  }