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

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/index.js CHANGED
@@ -5,6 +5,10 @@ var ErrorCodes = {
5
5
  AUTH_EXPIRED: "AUTH_EXPIRED",
6
6
  AUTH_REQUIRED: "AUTH_REQUIRED",
7
7
  AUTH_UNAUTHORIZED: "AUTH_UNAUTHORIZED",
8
+ /** HTTP 401: the delegation the invocation cites was revoked. */
9
+ AUTH_DELEGATION_REVOKED: "AUTH_DELEGATION_REVOKED",
10
+ /** HTTP 401: a delegation the cited one rests on was revoked. */
11
+ AUTH_DELEGATION_ANCESTOR_REVOKED: "AUTH_DELEGATION_ANCESTOR_REVOKED",
8
12
  NETWORK_ERROR: "NETWORK_ERROR",
9
13
  TIMEOUT: "TIMEOUT",
10
14
  ABORTED: "ABORTED",
@@ -16,6 +20,10 @@ var ErrorCodes = {
16
20
  KV_PRECONDITION_FAILED: "KV_PRECONDITION_FAILED",
17
21
  KV_CONFLICT: "KV_CONFLICT",
18
22
  KV_RESPONSE_TOO_LARGE: "KV_RESPONSE_TOO_LARGE",
23
+ /** HTTP 410 from the change feed: discard sync state and restart without a cursor; `meta.reason` says why. */
24
+ KV_SYNC_RESET_REQUIRED: "KV_SYNC_RESET_REQUIRED",
25
+ /** HTTP 403 from the change feed: the retention grant was refused; `meta.reason` says why. */
26
+ KV_RETENTION_GRANT_REFUSED: "KV_RETENTION_GRANT_REFUSED",
19
27
  // SQL-specific errors
20
28
  SQL_ERROR: "SQL_ERROR",
21
29
  SQL_PERMISSION_DENIED: "SQL_PERMISSION_DENIED",
@@ -1055,11 +1063,31 @@ function networkError(service, message, cause) {
1055
1063
  cause
1056
1064
  };
1057
1065
  }
1058
- function timeoutError(service) {
1066
+ var RequestTimeoutError = class extends Error {
1067
+ constructor(timeoutMs) {
1068
+ super(`Request timed out after ${timeoutMs}ms.`);
1069
+ this.name = "TimeoutError";
1070
+ this.timeoutMs = timeoutMs;
1071
+ }
1072
+ };
1073
+ function isRequestTimeoutError(error) {
1074
+ return error instanceof RequestTimeoutError || error instanceof Error && error.name === "TimeoutError" && typeof error.timeoutMs === "number";
1075
+ }
1076
+ function timeoutError(service, timeoutMs, cause) {
1077
+ if (timeoutMs === void 0) {
1078
+ return {
1079
+ code: ErrorCodes.TIMEOUT,
1080
+ message: "Request timed out.",
1081
+ service,
1082
+ ...cause === void 0 ? {} : { cause }
1083
+ };
1084
+ }
1059
1085
  return {
1060
1086
  code: ErrorCodes.TIMEOUT,
1061
- message: "Request timed out.",
1062
- service
1087
+ message: `Request timed out after ${timeoutMs}ms.`,
1088
+ service,
1089
+ ...cause === void 0 ? {} : { cause },
1090
+ meta: { timeoutMs }
1063
1091
  };
1064
1092
  }
1065
1093
  function abortedError(service) {
@@ -1223,6 +1251,9 @@ function isStorageFullError(error) {
1223
1251
  }
1224
1252
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1225
1253
  if (error instanceof Error) {
1254
+ if (isRequestTimeoutError(error)) {
1255
+ return timeoutError(service, error.timeoutMs, error);
1256
+ }
1226
1257
  if (error.name === "AbortError") {
1227
1258
  return abortedError(service);
1228
1259
  }
@@ -1247,6 +1278,7 @@ function errorResult(error) {
1247
1278
  }
1248
1279
 
1249
1280
  // src/base/BaseService.ts
1281
+ var MAX_TIMER_DELAY_MS = 2147483647;
1250
1282
  var BaseService = class {
1251
1283
  constructor() {
1252
1284
  /**
@@ -1427,6 +1459,64 @@ var BaseService = class {
1427
1459
  }
1428
1460
  return controller.signal;
1429
1461
  }
1462
+ /**
1463
+ * Create the abort signal for one request, bounded by the request timeout.
1464
+ *
1465
+ * The signal aborts when the service signs out, the context aborts, the
1466
+ * caller's `signal` aborts, or the timeout elapses, whichever comes first.
1467
+ * A timeout aborts with a {@link RequestTimeoutError} (`TimeoutError`), which
1468
+ * `wrapError` maps to `ErrorCodes.TIMEOUT`; the other sources keep their own
1469
+ * reason (normally `AbortError`, mapped to `ErrorCodes.ABORTED`).
1470
+ *
1471
+ * The timeout is `timeoutMs` when given, otherwise `config.timeout`. Only a
1472
+ * positive, finite value (at most 2^31-1 ms) applies; `undefined`, `0`, and
1473
+ * `Infinity` mean no timeout, so a per-call `0` opts out of a configured one.
1474
+ *
1475
+ * Callers must call `dispose()` once the response body has been consumed,
1476
+ * which clears the timer and removes the listeners this adds to the
1477
+ * long-lived service and context signals.
1478
+ *
1479
+ * @param signal - The caller's abort signal for this request
1480
+ * @param timeoutMs - Per-request timeout override in milliseconds
1481
+ */
1482
+ createRequestSignal(signal, timeoutMs) {
1483
+ const controller = new AbortController();
1484
+ const detachers = [];
1485
+ let timer;
1486
+ const dispose = () => {
1487
+ if (timer !== void 0) {
1488
+ clearTimeout(timer);
1489
+ timer = void 0;
1490
+ }
1491
+ for (const detach of detachers.splice(0)) {
1492
+ detach();
1493
+ }
1494
+ };
1495
+ const abort = (reason) => {
1496
+ dispose();
1497
+ controller.abort(reason);
1498
+ };
1499
+ const requestSignal = { signal: controller.signal, dispose };
1500
+ const parents = [
1501
+ this.abortController.signal,
1502
+ this.context?.abortSignal,
1503
+ signal
1504
+ ].filter((parent) => parent !== void 0);
1505
+ for (const parent of parents) {
1506
+ if (parent.aborted) {
1507
+ abort(parent.reason);
1508
+ return requestSignal;
1509
+ }
1510
+ const onAbort = () => abort(parent.reason);
1511
+ parent.addEventListener("abort", onAbort, { once: true });
1512
+ detachers.push(() => parent.removeEventListener("abort", onAbort));
1513
+ }
1514
+ const timeout = timeoutMs ?? this._config.timeout;
1515
+ if (typeof timeout === "number" && timeout > 0 && timeout <= MAX_TIMER_DELAY_MS) {
1516
+ timer = setTimeout(() => abort(new RequestTimeoutError(timeout)), timeout);
1517
+ }
1518
+ return requestSignal;
1519
+ }
1430
1520
  /**
1431
1521
  * Wrap an operation with error handling and telemetry.
1432
1522
  *
@@ -1535,6 +1625,30 @@ var PrefixedKVService = class _PrefixedKVService {
1535
1625
  removePrefix
1536
1626
  });
1537
1627
  }
1628
+ /**
1629
+ * Read the change feed for everything under this prefix. A view with an
1630
+ * empty prefix (`""` or `"/"`) is refused: the node does not serve a
1631
+ * whole-space feed.
1632
+ */
1633
+ async changes(options) {
1634
+ if (this._prefix.replace(/\/+$/, "") === "") {
1635
+ return err(serviceError(
1636
+ ErrorCodes.INVALID_INPUT,
1637
+ "KV changes needs a non-empty prefix; this view has none",
1638
+ "kv"
1639
+ ));
1640
+ }
1641
+ const syncPrefix = `${this._prefix}/`;
1642
+ const response = await this._kv.changes({ ...options, prefix: syncPrefix });
1643
+ if (!response.ok) return response;
1644
+ return ok({
1645
+ ...response.data,
1646
+ changes: response.data.changes.map((change) => ({
1647
+ ...change,
1648
+ key: change.key.startsWith(syncPrefix) ? change.key.slice(syncPrefix.length) : change.key
1649
+ }))
1650
+ });
1651
+ }
1538
1652
  /**
1539
1653
  * Delete a key.
1540
1654
  */
@@ -1587,11 +1701,18 @@ var KVAction = {
1587
1701
  PUT: "tinycloud.kv/put",
1588
1702
  LIST: "tinycloud.kv/list",
1589
1703
  DELETE: "tinycloud.kv/del",
1590
- HEAD: "tinycloud.kv/metadata"
1704
+ HEAD: "tinycloud.kv/metadata",
1705
+ /** The change feed; never implied by `*` or `tinycloud.kv/*`. */
1706
+ SYNC: "tinycloud.kv/sync",
1707
+ /** Retention attestation; presented through `retentionGrant`, never invoked or implied. */
1708
+ RETAIN: "tinycloud.kv/retain"
1591
1709
  };
1592
1710
 
1593
1711
  // src/kv/KVService.ts
1594
1712
  var MAX_KV_BATCH_READ_ITEMS = 100;
1713
+ function isJsonObject(value) {
1714
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1715
+ }
1595
1716
  function encodeKvBatchPartName(path) {
1596
1717
  return encodeURIComponent(path).replace(
1597
1718
  /[!'()*]/g,
@@ -1687,7 +1808,7 @@ var KVService = class extends BaseService {
1687
1808
  * @param path - Resource path
1688
1809
  * @param action - KV action
1689
1810
  * @param body - Optional request body
1690
- * @param signal - Optional abort signal
1811
+ * @param signal - Request signal from createRequestSignal()
1691
1812
  * @returns Fetch response
1692
1813
  */
1693
1814
  async invokeOperation(path, action, body, signal, extraHeaders) {
@@ -1703,7 +1824,7 @@ var KVService = class extends BaseService {
1703
1824
  method: "POST",
1704
1825
  headers: requestHeaders,
1705
1826
  body,
1706
- signal: this.combineSignals(signal)
1827
+ signal
1707
1828
  });
1708
1829
  }
1709
1830
  /**
@@ -1880,6 +2001,7 @@ var KVService = class extends BaseService {
1880
2001
  "kv"
1881
2002
  ));
1882
2003
  }
2004
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1883
2005
  try {
1884
2006
  const session = this.context.session;
1885
2007
  const invocationHeaders = this.context.invokeAny(
@@ -1901,7 +2023,7 @@ var KVService = class extends BaseService {
1901
2023
  const response = await this.context.fetch(`${this.host}/invoke`, {
1902
2024
  method: "POST",
1903
2025
  headers,
1904
- signal: this.combineSignals(options?.signal)
2026
+ signal: request.signal
1905
2027
  });
1906
2028
  if (!response.ok) {
1907
2029
  const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
@@ -1966,6 +2088,8 @@ var KVService = class extends BaseService {
1966
2088
  return ok({ results, count: results.length });
1967
2089
  } catch (error) {
1968
2090
  return err(wrapError("kv", error));
2091
+ } finally {
2092
+ request.dispose();
1969
2093
  }
1970
2094
  }
1971
2095
  /**
@@ -2098,12 +2222,13 @@ var KVService = class extends BaseService {
2098
2222
  ));
2099
2223
  }
2100
2224
  const path = this.getFullPath(key, options?.prefix);
2225
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2101
2226
  try {
2102
2227
  const response = await this.invokeOperation(
2103
2228
  path,
2104
2229
  KVAction.GET,
2105
2230
  void 0,
2106
- options?.signal,
2231
+ request.signal,
2107
2232
  options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
2108
2233
  );
2109
2234
  if (!response.ok) {
@@ -2160,6 +2285,8 @@ var KVService = class extends BaseService {
2160
2285
  });
2161
2286
  } catch (error) {
2162
2287
  return err(wrapError("kv", error));
2288
+ } finally {
2289
+ request.dispose();
2163
2290
  }
2164
2291
  });
2165
2292
  }
@@ -2187,12 +2314,13 @@ var KVService = class extends BaseService {
2187
2314
  }
2188
2315
  const path = this.getFullPath(key, options?.prefix);
2189
2316
  const body = this.serializePutValue(value, options?.contentType);
2317
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2190
2318
  try {
2191
2319
  const response = await this.invokeOperation(
2192
2320
  path,
2193
2321
  KVAction.PUT,
2194
2322
  body,
2195
- options?.signal,
2323
+ request.signal,
2196
2324
  {
2197
2325
  ...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
2198
2326
  ...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
@@ -2248,6 +2376,8 @@ var KVService = class extends BaseService {
2248
2376
  });
2249
2377
  } catch (error) {
2250
2378
  return err(wrapError("kv", error));
2379
+ } finally {
2380
+ request.dispose();
2251
2381
  }
2252
2382
  });
2253
2383
  }
@@ -2287,6 +2417,7 @@ var KVService = class extends BaseService {
2287
2417
  seen.add(path);
2288
2418
  }
2289
2419
  let requestMayHaveDispatched = false;
2420
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2290
2421
  try {
2291
2422
  const body = new FormData();
2292
2423
  for (let index = 0; index < items.length; index++) {
@@ -2309,7 +2440,7 @@ var KVService = class extends BaseService {
2309
2440
  method: "POST",
2310
2441
  headers,
2311
2442
  body,
2312
- signal: this.combineSignals(options?.signal)
2443
+ signal: request.signal
2313
2444
  };
2314
2445
  const fetchFn = this.context.fetch;
2315
2446
  requestMayHaveDispatched = true;
@@ -2360,14 +2491,16 @@ var KVService = class extends BaseService {
2360
2491
  rawBody = await response.json();
2361
2492
  } catch (jsonError) {
2362
2493
  const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
2494
+ const timedOut = isRequestTimeoutError(cause);
2363
2495
  return err(
2364
2496
  serviceError(
2365
- ErrorCodes.NETWORK_ERROR,
2366
- `KV batchPut response was not valid JSON: ${cause.message}`,
2497
+ timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
2498
+ timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
2367
2499
  "kv",
2368
2500
  {
2369
2501
  cause,
2370
2502
  meta: {
2503
+ ...timedOut ? { timeoutMs: cause.timeoutMs } : {},
2371
2504
  requestMayHaveDispatched: true,
2372
2505
  responseReceived: true,
2373
2506
  status: response.status,
@@ -2422,6 +2555,8 @@ var KVService = class extends BaseService {
2422
2555
  ...wrapped,
2423
2556
  meta: { ...wrapped.meta, requestMayHaveDispatched }
2424
2557
  });
2558
+ } finally {
2559
+ request.dispose();
2425
2560
  }
2426
2561
  });
2427
2562
  }
@@ -2444,12 +2579,13 @@ var KVService = class extends BaseService {
2444
2579
  if (options?.path) {
2445
2580
  listPath = listPath ? `${listPath}/${options.path}` : options.path;
2446
2581
  }
2582
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2447
2583
  try {
2448
2584
  const response = await this.invokeOperation(
2449
2585
  listPath,
2450
2586
  KVAction.LIST,
2451
2587
  void 0,
2452
- options?.signal,
2588
+ request.signal,
2453
2589
  options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
2454
2590
  ...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
2455
2591
  ...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
@@ -2490,9 +2626,174 @@ var KVService = class extends BaseService {
2490
2626
  });
2491
2627
  } catch (error) {
2492
2628
  return err(wrapError("kv", error));
2629
+ } finally {
2630
+ request.dispose();
2631
+ }
2632
+ });
2633
+ }
2634
+ /**
2635
+ * Read one page of the `tinycloud.kv/sync` change feed for a prefix.
2636
+ */
2637
+ async changes(options) {
2638
+ return this.withTelemetry("changes", options?.prefix, async () => {
2639
+ if (!this.requireAuth()) {
2640
+ return err(authRequiredError("kv"));
2641
+ }
2642
+ if (typeof options?.prefix !== "string" || options.prefix.length === 0) {
2643
+ return err(serviceError(
2644
+ ErrorCodes.INVALID_INPUT,
2645
+ "KV changes prefix must be a non-empty string",
2646
+ "kv"
2647
+ ));
2648
+ }
2649
+ if (options.limit !== void 0 && (!Number.isInteger(options.limit) || options.limit < 1 || options.limit > 1e3)) {
2650
+ return err(serviceError(
2651
+ ErrorCodes.INVALID_INPUT,
2652
+ "KV changes limit must be an integer from 1 through 1000",
2653
+ "kv"
2654
+ ));
2655
+ }
2656
+ const headers = {};
2657
+ if (options.limit !== void 0) headers["x-tinycloud-limit"] = String(options.limit);
2658
+ if (options.cursor !== void 0) headers["x-tinycloud-cursor"] = options.cursor;
2659
+ if (options.retentionGrant !== void 0) {
2660
+ headers["x-tinycloud-retention-grant"] = options.retentionGrant;
2661
+ }
2662
+ const request = this.createRequestSignal(options.signal, options.timeout);
2663
+ try {
2664
+ const response = await this.invokeOperation(
2665
+ options.prefix,
2666
+ KVAction.SYNC,
2667
+ void 0,
2668
+ request.signal,
2669
+ headers
2670
+ );
2671
+ if (!response.ok) {
2672
+ return await this.changesFailure(response, options.prefix, request.signal);
2673
+ }
2674
+ const text = await response.text();
2675
+ let body;
2676
+ try {
2677
+ body = JSON.parse(text);
2678
+ } catch {
2679
+ body = void 0;
2680
+ }
2681
+ const page = this.normalizeChangesResponse(body);
2682
+ if (!page) {
2683
+ return err(serviceError(
2684
+ ErrorCodes.NETWORK_ERROR,
2685
+ "KV changes response was not a tinycloud.kv/sync page",
2686
+ "kv",
2687
+ { meta: { status: response.status, statusText: response.statusText } }
2688
+ ));
2689
+ }
2690
+ return ok(page);
2691
+ } catch (error) {
2692
+ return err(wrapError("kv", error));
2693
+ } finally {
2694
+ request.dispose();
2493
2695
  }
2494
2696
  });
2495
2697
  }
2698
+ /**
2699
+ * Map a failed `kv/sync` response. 410 and the retention 403 carry
2700
+ * `{"error":{"code","reason"}}`; a revoked grant is a 401 whose text names
2701
+ * `delegation-revoked` or `delegation-ancestor-revoked`. A body read that
2702
+ * fails because the request was cancelled or timed out rethrows, so the
2703
+ * caller sees `ABORTED`/`TIMEOUT` rather than a mapped status.
2704
+ */
2705
+ async changesFailure(response, prefix, signal) {
2706
+ if (response.status === 404) {
2707
+ return this.classifyNotFound(response, prefix);
2708
+ }
2709
+ let errorText;
2710
+ try {
2711
+ errorText = await response.text();
2712
+ } catch (error) {
2713
+ if (signal.aborted) throw error;
2714
+ errorText = "";
2715
+ }
2716
+ const meta = { status: response.status, statusText: response.statusText };
2717
+ let reason;
2718
+ let bodyCode;
2719
+ try {
2720
+ const body = JSON.parse(errorText);
2721
+ const error = isJsonObject(body) ? body.error : void 0;
2722
+ if (isJsonObject(error)) {
2723
+ if (typeof error.code === "string") bodyCode = error.code;
2724
+ if (typeof error.reason === "string") reason = error.reason;
2725
+ }
2726
+ } catch {
2727
+ }
2728
+ const context = `Failed to read KV changes for ${JSON.stringify(prefix)}`;
2729
+ if (response.status === 410) {
2730
+ return err(serviceError(
2731
+ ErrorCodes.KV_SYNC_RESET_REQUIRED,
2732
+ `${context}: the node requires a reset (${reason ?? "reason not given"}); discard sync state and restart without a cursor`,
2733
+ "kv",
2734
+ { meta: { ...meta, reason } }
2735
+ ));
2736
+ }
2737
+ if (response.status === 403 && bodyCode === "RETENTION_GRANT_REFUSED") {
2738
+ return err(serviceError(
2739
+ ErrorCodes.KV_RETENTION_GRANT_REFUSED,
2740
+ `${context}: the retention grant was refused (${reason ?? "reason not given"})`,
2741
+ "kv",
2742
+ { meta: { ...meta, reason } }
2743
+ ));
2744
+ }
2745
+ if (response.status === 401) {
2746
+ const revoked = /\bdelegation-ancestor-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_ANCESTOR_REVOKED : /\bdelegation-revoked\b/.test(errorText) ? ErrorCodes.AUTH_DELEGATION_REVOKED : void 0;
2747
+ if (revoked !== void 0) {
2748
+ return err(serviceError(revoked, `${context}: 401 - ${errorText}`, "kv", { meta }));
2749
+ }
2750
+ }
2751
+ if (response.status === 401 || response.status === 403) {
2752
+ return this.authorizationFailure(context, response, errorText, [prefix], KVAction.SYNC);
2753
+ }
2754
+ return err(serviceError(
2755
+ response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR,
2756
+ `${context}: ${response.status} - ${errorText}`,
2757
+ "kv",
2758
+ { meta }
2759
+ ));
2760
+ }
2761
+ normalizeChangesResponse(data) {
2762
+ if (!isJsonObject(data)) return void 0;
2763
+ const { changes, more, cursor, source, authority } = data;
2764
+ if (!Array.isArray(changes) || typeof more !== "boolean" || typeof cursor !== "string" || !isJsonObject(source) || typeof source.nodeDid !== "string" || typeof source.space !== "string" || typeof source.prefix !== "string" || !isJsonObject(authority)) {
2765
+ return void 0;
2766
+ }
2767
+ const { notBefore, expiresAt, retainUntil } = authority;
2768
+ if (notBefore !== null && typeof notBefore !== "string" || expiresAt !== null && typeof expiresAt !== "string" || retainUntil !== null && typeof retainUntil !== "string") {
2769
+ return void 0;
2770
+ }
2771
+ const parsed = [];
2772
+ for (const change of changes) {
2773
+ if (!isJsonObject(change) || typeof change.key !== "string") return void 0;
2774
+ if (change.deleted === true) {
2775
+ parsed.push({ key: change.key, deleted: true });
2776
+ continue;
2777
+ }
2778
+ const { metadata } = change;
2779
+ if (change.deleted !== false || typeof change.etag !== "string" || !isJsonObject(metadata) || !Object.values(metadata).every((value) => typeof value === "string")) {
2780
+ return void 0;
2781
+ }
2782
+ parsed.push({
2783
+ key: change.key,
2784
+ deleted: false,
2785
+ etag: change.etag,
2786
+ metadata
2787
+ });
2788
+ }
2789
+ return {
2790
+ changes: parsed,
2791
+ more,
2792
+ cursor,
2793
+ source: { nodeDid: source.nodeDid, space: source.space, prefix: source.prefix },
2794
+ authority: { notBefore, expiresAt, retainUntil }
2795
+ };
2796
+ }
2496
2797
  /**
2497
2798
  * Delete a key.
2498
2799
  */
@@ -2502,12 +2803,13 @@ var KVService = class extends BaseService {
2502
2803
  return err(authRequiredError("kv"));
2503
2804
  }
2504
2805
  const path = this.getFullPath(key, options?.prefix);
2806
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2505
2807
  try {
2506
2808
  const response = await this.invokeOperation(
2507
2809
  path,
2508
2810
  KVAction.DELETE,
2509
2811
  void 0,
2510
- options?.signal,
2812
+ request.signal,
2511
2813
  options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
2512
2814
  );
2513
2815
  if (!response.ok) {
@@ -2555,6 +2857,8 @@ var KVService = class extends BaseService {
2555
2857
  });
2556
2858
  } catch (error) {
2557
2859
  return err(wrapError("kv", error));
2860
+ } finally {
2861
+ request.dispose();
2558
2862
  }
2559
2863
  });
2560
2864
  }
@@ -2567,12 +2871,13 @@ var KVService = class extends BaseService {
2567
2871
  return err(authRequiredError("kv"));
2568
2872
  }
2569
2873
  const path = this.getFullPath(key, options?.prefix);
2874
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2570
2875
  try {
2571
2876
  const response = await this.invokeOperation(
2572
2877
  path,
2573
2878
  KVAction.HEAD,
2574
2879
  void 0,
2575
- options?.signal
2880
+ request.signal
2576
2881
  );
2577
2882
  if (!response.ok) {
2578
2883
  if (response.status === 401 || response.status === 403) {
@@ -2603,6 +2908,8 @@ var KVService = class extends BaseService {
2603
2908
  });
2604
2909
  } catch (error) {
2605
2910
  return err(wrapError("kv", error));
2911
+ } finally {
2912
+ request.dispose();
2606
2913
  }
2607
2914
  });
2608
2915
  }
@@ -2640,12 +2947,13 @@ var KVService = class extends BaseService {
2640
2947
  if (options?.etag !== void 0) {
2641
2948
  body.etag = options.etag;
2642
2949
  }
2950
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2643
2951
  try {
2644
2952
  const response = await this.context.fetch(`${this.host}/signed/kv`, {
2645
2953
  method: "POST",
2646
2954
  headers: this.withJsonContentType(headers),
2647
2955
  body: JSON.stringify(body),
2648
- signal: this.combineSignals(options?.signal)
2956
+ signal: request.signal
2649
2957
  });
2650
2958
  if (!response.ok) {
2651
2959
  return this.createSignedReadUrlError(response, key, path);
@@ -2665,6 +2973,8 @@ var KVService = class extends BaseService {
2665
2973
  return ok(signedUrl);
2666
2974
  } catch (error) {
2667
2975
  return err(wrapError("kv", error));
2976
+ } finally {
2977
+ request.dispose();
2668
2978
  }
2669
2979
  });
2670
2980
  }
@@ -2878,6 +3188,7 @@ var SQLService = class extends BaseService {
2878
3188
  if (!this.requireAuth()) {
2879
3189
  return err(authRequiredError("sql"));
2880
3190
  }
3191
+ const request = this.createRequestSignal(options?.signal);
2881
3192
  try {
2882
3193
  const body = {
2883
3194
  action: "query",
@@ -2891,7 +3202,7 @@ var SQLService = class extends BaseService {
2891
3202
  dbName,
2892
3203
  action,
2893
3204
  body,
2894
- options?.signal
3205
+ request.signal
2895
3206
  );
2896
3207
  if (!response.ok) {
2897
3208
  return this.handleErrorResponse(response, "query", dbName, [action]);
@@ -2900,6 +3211,8 @@ var SQLService = class extends BaseService {
2900
3211
  return ok(data);
2901
3212
  } catch (error) {
2902
3213
  return err(wrapError("sql", error));
3214
+ } finally {
3215
+ request.dispose();
2903
3216
  }
2904
3217
  });
2905
3218
  }
@@ -2908,6 +3221,7 @@ var SQLService = class extends BaseService {
2908
3221
  if (!this.requireAuth()) {
2909
3222
  return err(authRequiredError("sql"));
2910
3223
  }
3224
+ const request = this.createRequestSignal(options?.signal);
2911
3225
  try {
2912
3226
  const body = {
2913
3227
  action: "execute",
@@ -2928,7 +3242,7 @@ var SQLService = class extends BaseService {
2928
3242
  dbName,
2929
3243
  requestedActions,
2930
3244
  body,
2931
- options?.signal
3245
+ request.signal
2932
3246
  );
2933
3247
  if (!response.ok) {
2934
3248
  return this.handleErrorResponse(response, "execute", dbName, requestedActions);
@@ -2937,6 +3251,8 @@ var SQLService = class extends BaseService {
2937
3251
  return ok(data);
2938
3252
  } catch (error) {
2939
3253
  return err(wrapError("sql", error));
3254
+ } finally {
3255
+ request.dispose();
2940
3256
  }
2941
3257
  });
2942
3258
  }
@@ -2945,13 +3261,14 @@ var SQLService = class extends BaseService {
2945
3261
  if (!this.requireAuth()) {
2946
3262
  return err(authRequiredError("sql"));
2947
3263
  }
3264
+ const request = this.createRequestSignal(options?.signal);
2948
3265
  try {
2949
3266
  const requestedActions = this.actionsForSqlBatch(statements);
2950
3267
  const response = await this.invokeSQL(
2951
3268
  dbName,
2952
3269
  requestedActions,
2953
3270
  { action: "batch", statements },
2954
- options?.signal
3271
+ request.signal
2955
3272
  );
2956
3273
  if (!response.ok) {
2957
3274
  return this.handleErrorResponse(response, "batch", dbName, requestedActions);
@@ -2960,6 +3277,8 @@ var SQLService = class extends BaseService {
2960
3277
  return ok(data);
2961
3278
  } catch (error) {
2962
3279
  return err(wrapError("sql", error));
3280
+ } finally {
3281
+ request.dispose();
2963
3282
  }
2964
3283
  });
2965
3284
  }
@@ -2968,12 +3287,13 @@ var SQLService = class extends BaseService {
2968
3287
  if (!this.requireAuth()) {
2969
3288
  return err(authRequiredError("sql"));
2970
3289
  }
3290
+ const request = this.createRequestSignal(options?.signal);
2971
3291
  try {
2972
3292
  const response = await this.invokeSQL(
2973
3293
  dbName,
2974
3294
  SQLAction.WRITE,
2975
3295
  { action: "execute_statement", name, params: params ?? [] },
2976
- options?.signal
3296
+ request.signal
2977
3297
  );
2978
3298
  if (!response.ok) {
2979
3299
  return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
@@ -2982,6 +3302,8 @@ var SQLService = class extends BaseService {
2982
3302
  return ok(data);
2983
3303
  } catch (error) {
2984
3304
  return err(wrapError("sql", error));
3305
+ } finally {
3306
+ request.dispose();
2985
3307
  }
2986
3308
  });
2987
3309
  }
@@ -2990,12 +3312,13 @@ var SQLService = class extends BaseService {
2990
3312
  if (!this.requireAuth()) {
2991
3313
  return err(authRequiredError("sql"));
2992
3314
  }
3315
+ const request = this.createRequestSignal(options?.signal);
2993
3316
  try {
2994
3317
  const response = await this.invokeSQL(
2995
3318
  dbName,
2996
3319
  SQLAction.READ,
2997
3320
  { action: "export" },
2998
- options?.signal
3321
+ request.signal
2999
3322
  );
3000
3323
  if (!response.ok) {
3001
3324
  return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
@@ -3009,6 +3332,8 @@ var SQLService = class extends BaseService {
3009
3332
  return ok(text);
3010
3333
  } catch (error) {
3011
3334
  return err(wrapError("sql", error));
3335
+ } finally {
3336
+ request.dispose();
3012
3337
  }
3013
3338
  });
3014
3339
  }
@@ -3099,7 +3424,7 @@ var SQLService = class extends BaseService {
3099
3424
  "Content-Type": "application/json"
3100
3425
  },
3101
3426
  body: JSON.stringify(body),
3102
- signal: this.combineSignals(signal)
3427
+ signal
3103
3428
  });
3104
3429
  }
3105
3430
  actionForSql(sql, fallback) {
@@ -3363,12 +3688,13 @@ var DuckDbService = class extends BaseService {
3363
3688
  if (!this.requireAuth()) {
3364
3689
  return err(authRequiredError("duckdb"));
3365
3690
  }
3691
+ const request = this.createRequestSignal(options?.signal);
3366
3692
  try {
3367
3693
  const response = await this.invokeDuckDb(
3368
3694
  dbName,
3369
3695
  DuckDbAction.READ,
3370
3696
  { action: "query", sql, params: params ?? [] },
3371
- options?.signal
3697
+ request.signal
3372
3698
  );
3373
3699
  if (!response.ok) {
3374
3700
  return this.handleErrorResponse(response, "query");
@@ -3377,6 +3703,8 @@ var DuckDbService = class extends BaseService {
3377
3703
  return ok(data);
3378
3704
  } catch (error) {
3379
3705
  return err(wrapError("duckdb", error));
3706
+ } finally {
3707
+ request.dispose();
3380
3708
  }
3381
3709
  });
3382
3710
  }
@@ -3385,12 +3713,13 @@ var DuckDbService = class extends BaseService {
3385
3713
  if (!this.requireAuth()) {
3386
3714
  return err(authRequiredError("duckdb"));
3387
3715
  }
3716
+ const request = this.createRequestSignal(options?.signal);
3388
3717
  try {
3389
3718
  const response = await this.invokeDuckDb(
3390
3719
  dbName,
3391
3720
  DuckDbAction.READ,
3392
3721
  { action: "query", sql, params: params ?? [] },
3393
- options?.signal,
3722
+ request.signal,
3394
3723
  { Accept: "application/vnd.apache.arrow.stream" }
3395
3724
  );
3396
3725
  if (!response.ok) {
@@ -3400,6 +3729,8 @@ var DuckDbService = class extends BaseService {
3400
3729
  return ok(buffer);
3401
3730
  } catch (error) {
3402
3731
  return err(wrapError("duckdb", error));
3732
+ } finally {
3733
+ request.dispose();
3403
3734
  }
3404
3735
  });
3405
3736
  }
@@ -3408,6 +3739,7 @@ var DuckDbService = class extends BaseService {
3408
3739
  if (!this.requireAuth()) {
3409
3740
  return err(authRequiredError("duckdb"));
3410
3741
  }
3742
+ const request = this.createRequestSignal(options?.signal);
3411
3743
  try {
3412
3744
  const body = {
3413
3745
  action: "execute",
@@ -3421,7 +3753,7 @@ var DuckDbService = class extends BaseService {
3421
3753
  dbName,
3422
3754
  DuckDbAction.WRITE,
3423
3755
  body,
3424
- options?.signal
3756
+ request.signal
3425
3757
  );
3426
3758
  if (!response.ok) {
3427
3759
  return this.handleErrorResponse(response, "execute");
@@ -3430,6 +3762,8 @@ var DuckDbService = class extends BaseService {
3430
3762
  return ok(data);
3431
3763
  } catch (error) {
3432
3764
  return err(wrapError("duckdb", error));
3765
+ } finally {
3766
+ request.dispose();
3433
3767
  }
3434
3768
  });
3435
3769
  }
@@ -3438,6 +3772,7 @@ var DuckDbService = class extends BaseService {
3438
3772
  if (!this.requireAuth()) {
3439
3773
  return err(authRequiredError("duckdb"));
3440
3774
  }
3775
+ const request = this.createRequestSignal(options?.signal);
3441
3776
  try {
3442
3777
  const body = {
3443
3778
  action: "batch",
@@ -3450,7 +3785,7 @@ var DuckDbService = class extends BaseService {
3450
3785
  dbName,
3451
3786
  DuckDbAction.WRITE,
3452
3787
  body,
3453
- options?.signal
3788
+ request.signal
3454
3789
  );
3455
3790
  if (!response.ok) {
3456
3791
  return this.handleErrorResponse(response, "batch");
@@ -3459,6 +3794,8 @@ var DuckDbService = class extends BaseService {
3459
3794
  return ok(data);
3460
3795
  } catch (error) {
3461
3796
  return err(wrapError("duckdb", error));
3797
+ } finally {
3798
+ request.dispose();
3462
3799
  }
3463
3800
  });
3464
3801
  }
@@ -3467,12 +3804,13 @@ var DuckDbService = class extends BaseService {
3467
3804
  if (!this.requireAuth()) {
3468
3805
  return err(authRequiredError("duckdb"));
3469
3806
  }
3807
+ const request = this.createRequestSignal(options?.signal);
3470
3808
  try {
3471
3809
  const response = await this.invokeDuckDb(
3472
3810
  dbName,
3473
3811
  DuckDbAction.WRITE,
3474
3812
  { action: "executeStatement", name, params: params ?? [] },
3475
- options?.signal
3813
+ request.signal
3476
3814
  );
3477
3815
  if (!response.ok) {
3478
3816
  return this.handleErrorResponse(response, "executeStatement");
@@ -3481,6 +3819,8 @@ var DuckDbService = class extends BaseService {
3481
3819
  return ok(data);
3482
3820
  } catch (error) {
3483
3821
  return err(wrapError("duckdb", error));
3822
+ } finally {
3823
+ request.dispose();
3484
3824
  }
3485
3825
  });
3486
3826
  }
@@ -3489,12 +3829,13 @@ var DuckDbService = class extends BaseService {
3489
3829
  if (!this.requireAuth()) {
3490
3830
  return err(authRequiredError("duckdb"));
3491
3831
  }
3832
+ const request = this.createRequestSignal(options?.signal);
3492
3833
  try {
3493
3834
  const response = await this.invokeDuckDb(
3494
3835
  dbName,
3495
3836
  DuckDbAction.READ,
3496
3837
  { action: "describe" },
3497
- options?.signal
3838
+ request.signal
3498
3839
  );
3499
3840
  if (!response.ok) {
3500
3841
  return this.handleErrorResponse(response, "describe");
@@ -3503,6 +3844,8 @@ var DuckDbService = class extends BaseService {
3503
3844
  return ok(data);
3504
3845
  } catch (error) {
3505
3846
  return err(wrapError("duckdb", error));
3847
+ } finally {
3848
+ request.dispose();
3506
3849
  }
3507
3850
  });
3508
3851
  }
@@ -3511,12 +3854,13 @@ var DuckDbService = class extends BaseService {
3511
3854
  if (!this.requireAuth()) {
3512
3855
  return err(authRequiredError("duckdb"));
3513
3856
  }
3857
+ const request = this.createRequestSignal(options?.signal);
3514
3858
  try {
3515
3859
  const response = await this.invokeDuckDb(
3516
3860
  dbName,
3517
3861
  DuckDbAction.EXPORT,
3518
3862
  { action: "export" },
3519
- options?.signal
3863
+ request.signal
3520
3864
  );
3521
3865
  if (!response.ok) {
3522
3866
  return this.handleErrorResponse(response, "export");
@@ -3525,6 +3869,8 @@ var DuckDbService = class extends BaseService {
3525
3869
  return ok(blob);
3526
3870
  } catch (error) {
3527
3871
  return err(wrapError("duckdb", error));
3872
+ } finally {
3873
+ request.dispose();
3528
3874
  }
3529
3875
  });
3530
3876
  }
@@ -3533,6 +3879,7 @@ var DuckDbService = class extends BaseService {
3533
3879
  if (!this.requireAuth()) {
3534
3880
  return err(authRequiredError("duckdb"));
3535
3881
  }
3882
+ const request = this.createRequestSignal(options?.signal);
3536
3883
  try {
3537
3884
  const session = this.context.session;
3538
3885
  const headers = this.context.invoke(
@@ -3548,7 +3895,7 @@ var DuckDbService = class extends BaseService {
3548
3895
  "Content-Type": "application/x-duckdb"
3549
3896
  },
3550
3897
  body: new Blob([data]),
3551
- signal: this.combineSignals(options?.signal)
3898
+ signal: request.signal
3552
3899
  });
3553
3900
  if (!response.ok) {
3554
3901
  return this.handleErrorResponse(response, "import");
@@ -3556,6 +3903,8 @@ var DuckDbService = class extends BaseService {
3556
3903
  return ok(void 0);
3557
3904
  } catch (error) {
3558
3905
  return err(wrapError("duckdb", error));
3906
+ } finally {
3907
+ request.dispose();
3559
3908
  }
3560
3909
  });
3561
3910
  }
@@ -3571,7 +3920,7 @@ var DuckDbService = class extends BaseService {
3571
3920
  ...extraHeaders
3572
3921
  },
3573
3922
  body: JSON.stringify(body),
3574
- signal: this.combineSignals(signal)
3923
+ signal
3575
3924
  });
3576
3925
  }
3577
3926
  async handleErrorResponse(response, operation) {
@@ -5992,6 +6341,7 @@ var SecretsService = class {
5992
6341
  }
5993
6342
  async listAll() {
5994
6343
  const entries = /* @__PURE__ */ new Map();
6344
+ const seenCursors = /* @__PURE__ */ new Set();
5995
6345
  let cursor;
5996
6346
  do {
5997
6347
  const result = await this.vault.listPage({
@@ -6014,6 +6364,14 @@ var SecretsService = class {
6014
6364
  "Secret catalog listing was truncated without a continuation cursor."
6015
6365
  );
6016
6366
  }
6367
+ if (cursor !== void 0) {
6368
+ if (seenCursors.has(cursor)) {
6369
+ return invalidSecretInput(
6370
+ "Secret catalog listing repeated a continuation cursor; the node does not support paged KV lists."
6371
+ );
6372
+ }
6373
+ seenCursors.add(cursor);
6374
+ }
6017
6375
  } while (cursor !== void 0);
6018
6376
  return {
6019
6377
  ok: true,