@tinycloud/sdk-services 3.1.0-beta.2 → 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/index.cjs CHANGED
@@ -116,6 +116,7 @@ __export(src_exports, {
116
116
  ok: () => ok,
117
117
  openWrappedKey: () => openWrappedKey,
118
118
  parseAuthError: () => parseAuthError,
119
+ parseCapabilityResource: () => parseCapabilityResource,
119
120
  parseNetworkId: () => parseNetworkId,
120
121
  parsePermissionHint: () => parsePermissionHint,
121
122
  parsePermissionHintFromErrorText: () => parsePermissionHintFromErrorText,
@@ -141,6 +142,7 @@ __export(src_exports, {
141
142
  validateServiceResponseEvent: () => validateServiceResponseEvent,
142
143
  validateServiceSession: () => validateServiceSession,
143
144
  validateTelemetrySpanEvent: () => validateTelemetrySpanEvent,
145
+ validatedCapabilityOf: () => validatedCapabilityOf,
144
146
  verifyDecryptResponse: () => verifyDecryptResponse,
145
147
  wrapError: () => wrapError
146
148
  });
@@ -1044,6 +1046,106 @@ var ServiceContext = class {
1044
1046
  }
1045
1047
  };
1046
1048
 
1049
+ // src/errors.ts
1050
+ var import_bootstrap = require("@tinycloud/bootstrap");
1051
+
1052
+ // src/encryption/networkId.ts
1053
+ var URN_PREFIX = "urn:tinycloud:encryption:";
1054
+ var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
1055
+ var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
1056
+ var NetworkIdError = class extends Error {
1057
+ constructor(message) {
1058
+ super(message);
1059
+ this.name = "NetworkIdError";
1060
+ }
1061
+ };
1062
+ function parseNetworkId(networkId) {
1063
+ if (typeof networkId !== "string" || networkId.length === 0) {
1064
+ throw new NetworkIdError("networkId must be a non-empty string");
1065
+ }
1066
+ if (!networkId.startsWith(URN_PREFIX)) {
1067
+ throw new NetworkIdError(
1068
+ `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
1069
+ );
1070
+ }
1071
+ const body = networkId.slice(URN_PREFIX.length);
1072
+ const lastColon = body.lastIndexOf(":");
1073
+ if (lastColon <= 0 || lastColon === body.length - 1) {
1074
+ throw new NetworkIdError(
1075
+ `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
1076
+ );
1077
+ }
1078
+ const ownerDid = body.slice(0, lastColon);
1079
+ const name = body.slice(lastColon + 1);
1080
+ if (!ownerDid.startsWith("did:")) {
1081
+ throw new NetworkIdError(
1082
+ `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
1083
+ );
1084
+ }
1085
+ const didParts = ownerDid.split(":");
1086
+ if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
1087
+ throw new NetworkIdError(
1088
+ `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
1089
+ );
1090
+ }
1091
+ if (!NETWORK_NAME_RE.test(name)) {
1092
+ throw new NetworkIdError(
1093
+ `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1094
+ );
1095
+ }
1096
+ return { networkId, ownerDid, name };
1097
+ }
1098
+ function buildNetworkId(ownerDid, name) {
1099
+ if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
1100
+ throw new NetworkIdError("ownerDid must be a DID");
1101
+ }
1102
+ if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
1103
+ throw new NetworkIdError(
1104
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1105
+ );
1106
+ }
1107
+ const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
1108
+ parseNetworkId(networkId);
1109
+ return networkId;
1110
+ }
1111
+ function isNetworkId(networkId) {
1112
+ if (typeof networkId !== "string") {
1113
+ return false;
1114
+ }
1115
+ try {
1116
+ parseNetworkId(networkId);
1117
+ return true;
1118
+ } catch {
1119
+ return false;
1120
+ }
1121
+ }
1122
+ function parsePkhOwnerDid(ownerDid) {
1123
+ const match = ownerDid.match(PKH_EIP155_DID_RE);
1124
+ if (!match) return null;
1125
+ return {
1126
+ chainId: match[1],
1127
+ address: match[2].toLowerCase()
1128
+ };
1129
+ }
1130
+ function ownerDidMatches(a, b) {
1131
+ const aPkh = parsePkhOwnerDid(a);
1132
+ const bPkh = parsePkhOwnerDid(b);
1133
+ if (aPkh && bPkh) {
1134
+ return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
1135
+ }
1136
+ return a === b;
1137
+ }
1138
+ function networkDiscoveryKey(name) {
1139
+ if (!NETWORK_NAME_RE.test(name)) {
1140
+ throw new NetworkIdError(
1141
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1142
+ );
1143
+ }
1144
+ return `.well-known/encryption/network/${name}`;
1145
+ }
1146
+ var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
1147
+ var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
1148
+
1047
1149
  // src/errors.ts
1048
1150
  function parsePermissionHint(value) {
1049
1151
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
@@ -1103,11 +1205,31 @@ function networkError(service, message, cause) {
1103
1205
  cause
1104
1206
  };
1105
1207
  }
1106
- function timeoutError(service) {
1208
+ var RequestTimeoutError = class extends Error {
1209
+ constructor(timeoutMs) {
1210
+ super(`Request timed out after ${timeoutMs}ms.`);
1211
+ this.name = "TimeoutError";
1212
+ this.timeoutMs = timeoutMs;
1213
+ }
1214
+ };
1215
+ function isRequestTimeoutError(error) {
1216
+ return error instanceof RequestTimeoutError || error instanceof Error && error.name === "TimeoutError" && typeof error.timeoutMs === "number";
1217
+ }
1218
+ function timeoutError(service, timeoutMs, cause) {
1219
+ if (timeoutMs === void 0) {
1220
+ return {
1221
+ code: ErrorCodes.TIMEOUT,
1222
+ message: "Request timed out.",
1223
+ service,
1224
+ ...cause === void 0 ? {} : { cause }
1225
+ };
1226
+ }
1107
1227
  return {
1108
1228
  code: ErrorCodes.TIMEOUT,
1109
- message: "Request timed out.",
1110
- service
1229
+ message: `Request timed out after ${timeoutMs}ms.`,
1230
+ service,
1231
+ ...cause === void 0 ? {} : { cause },
1232
+ meta: { timeoutMs }
1111
1233
  };
1112
1234
  }
1113
1235
  function abortedError(service) {
@@ -1141,6 +1263,70 @@ function parseAuthError(responseText) {
1141
1263
  function authUnauthorizedError(service, message, meta) {
1142
1264
  return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
1143
1265
  }
1266
+ var grantableActions = new Set(
1267
+ import_bootstrap.CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
1268
+ );
1269
+ var capabilityServices = new Set(
1270
+ import_bootstrap.CAPABILITY_REGISTRY.map(({ service }) => service.slice("tinycloud.".length))
1271
+ );
1272
+ function parseCapabilityResource(resource, service) {
1273
+ if (resource.length === 0 || resource.length > 1024 || !capabilityServices.has(service) || /[\s\p{C}*#]/u.test(resource) || resource.includes("://")) return void 0;
1274
+ const segments = resource.split("/");
1275
+ if (!/^tinycloud:[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(segments[0])) return void 0;
1276
+ const serviceIndex = segments.findIndex(
1277
+ (segment, index) => index > 0 && capabilityServices.has(segment)
1278
+ );
1279
+ if (serviceIndex < 1 || segments[serviceIndex] !== service) return void 0;
1280
+ if (segments.slice(1, serviceIndex).some(
1281
+ (segment) => segment.length === 0 || segment === "." || segment === ".."
1282
+ )) return void 0;
1283
+ const pathSegments = segments.slice(serviceIndex + 1);
1284
+ if (pathSegments.some((segment) => segment === "." || segment === "..")) return void 0;
1285
+ if (service !== "kv" && (pathSegments.length === 0 || pathSegments[pathSegments.length - 1] === "")) return void 0;
1286
+ return {
1287
+ space: segments.slice(0, serviceIndex).join("/"),
1288
+ path: pathSegments.join("/")
1289
+ };
1290
+ }
1291
+ function safeCapabilityResource(value, service, action) {
1292
+ if (typeof value !== "string" || value.length === 0 || value.length > 1024) return false;
1293
+ if (service === "encryption" && value.startsWith("urn:tinycloud:encryption:")) {
1294
+ if (!/^[A-Za-z0-9_][A-Za-z0-9_.:/-]*$/.test(value) || value.includes("//") || value.split("/").some((segment) => segment === "." || segment === "..")) return false;
1295
+ try {
1296
+ parseNetworkId(value);
1297
+ return true;
1298
+ } catch {
1299
+ return false;
1300
+ }
1301
+ }
1302
+ const parsed = parseCapabilityResource(value, service);
1303
+ return parsed !== void 0 && (!parsed.path.endsWith("/") || service === "kv" && action === "tinycloud.kv/list") && (parsed.path !== "" || service === "kv" && action === "tinycloud.kv/list");
1304
+ }
1305
+ function validatedCapabilityOf(error) {
1306
+ const verdict = authorizationVerdictOf(error);
1307
+ if (verdict !== "unauthenticated" && verdict !== "forbidden") return void 0;
1308
+ const seen = /* @__PURE__ */ new Set();
1309
+ let node = error;
1310
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
1311
+ seen.add(node);
1312
+ const record = node;
1313
+ const status = errorStatusOf(record);
1314
+ const meta = record.meta;
1315
+ if ((status === 401 || status === 403 || status === void 0 && record.code === ErrorCodes.AUTH_UNAUTHORIZED) && typeof meta === "object" && meta !== null && !Array.isArray(meta)) {
1316
+ const metadata = meta;
1317
+ const { resource, requiredAction } = metadata;
1318
+ if (typeof requiredAction === "string" && grantableActions.has(requiredAction)) {
1319
+ const actionService = requiredAction.slice("tinycloud.".length, requiredAction.indexOf("/"));
1320
+ const explicitService = record.service === void 0 ? metadata.service : record.service;
1321
+ if (safeCapabilityResource(resource, actionService, requiredAction) && (explicitService === void 0 || explicitService === actionService || explicitService === `tinycloud.${actionService}`)) {
1322
+ return { resource, requiredAction };
1323
+ }
1324
+ }
1325
+ }
1326
+ node = record.cause;
1327
+ }
1328
+ return void 0;
1329
+ }
1144
1330
  var MAX_CAUSE_DEPTH = 8;
1145
1331
  function errorStatusOf(node) {
1146
1332
  const meta = node.meta;
@@ -1207,6 +1393,9 @@ function isStorageFullError(error) {
1207
1393
  }
1208
1394
  function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1209
1395
  if (error instanceof Error) {
1396
+ if (isRequestTimeoutError(error)) {
1397
+ return timeoutError(service, error.timeoutMs, error);
1398
+ }
1210
1399
  if (error.name === "AbortError") {
1211
1400
  return abortedError(service);
1212
1401
  }
@@ -1231,6 +1420,7 @@ function errorResult(error) {
1231
1420
  }
1232
1421
 
1233
1422
  // src/base/BaseService.ts
1423
+ var MAX_TIMER_DELAY_MS = 2147483647;
1234
1424
  var BaseService = class {
1235
1425
  constructor() {
1236
1426
  /**
@@ -1411,6 +1601,64 @@ var BaseService = class {
1411
1601
  }
1412
1602
  return controller.signal;
1413
1603
  }
1604
+ /**
1605
+ * Create the abort signal for one request, bounded by the request timeout.
1606
+ *
1607
+ * The signal aborts when the service signs out, the context aborts, the
1608
+ * caller's `signal` aborts, or the timeout elapses, whichever comes first.
1609
+ * A timeout aborts with a {@link RequestTimeoutError} (`TimeoutError`), which
1610
+ * `wrapError` maps to `ErrorCodes.TIMEOUT`; the other sources keep their own
1611
+ * reason (normally `AbortError`, mapped to `ErrorCodes.ABORTED`).
1612
+ *
1613
+ * The timeout is `timeoutMs` when given, otherwise `config.timeout`. Only a
1614
+ * positive, finite value (at most 2^31-1 ms) applies; `undefined`, `0`, and
1615
+ * `Infinity` mean no timeout, so a per-call `0` opts out of a configured one.
1616
+ *
1617
+ * Callers must call `dispose()` once the response body has been consumed,
1618
+ * which clears the timer and removes the listeners this adds to the
1619
+ * long-lived service and context signals.
1620
+ *
1621
+ * @param signal - The caller's abort signal for this request
1622
+ * @param timeoutMs - Per-request timeout override in milliseconds
1623
+ */
1624
+ createRequestSignal(signal, timeoutMs) {
1625
+ const controller = new AbortController();
1626
+ const detachers = [];
1627
+ let timer;
1628
+ const dispose = () => {
1629
+ if (timer !== void 0) {
1630
+ clearTimeout(timer);
1631
+ timer = void 0;
1632
+ }
1633
+ for (const detach of detachers.splice(0)) {
1634
+ detach();
1635
+ }
1636
+ };
1637
+ const abort = (reason) => {
1638
+ dispose();
1639
+ controller.abort(reason);
1640
+ };
1641
+ const requestSignal = { signal: controller.signal, dispose };
1642
+ const parents = [
1643
+ this.abortController.signal,
1644
+ this.context?.abortSignal,
1645
+ signal
1646
+ ].filter((parent) => parent !== void 0);
1647
+ for (const parent of parents) {
1648
+ if (parent.aborted) {
1649
+ abort(parent.reason);
1650
+ return requestSignal;
1651
+ }
1652
+ const onAbort = () => abort(parent.reason);
1653
+ parent.addEventListener("abort", onAbort, { once: true });
1654
+ detachers.push(() => parent.removeEventListener("abort", onAbort));
1655
+ }
1656
+ const timeout = timeoutMs ?? this._config.timeout;
1657
+ if (typeof timeout === "number" && timeout > 0 && timeout <= MAX_TIMER_DELAY_MS) {
1658
+ timer = setTimeout(() => abort(new RequestTimeoutError(timeout)), timeout);
1659
+ }
1660
+ return requestSignal;
1661
+ }
1414
1662
  /**
1415
1663
  * Wrap an operation with error handling and telemetry.
1416
1664
  *
@@ -1671,7 +1919,7 @@ var KVService = class extends BaseService {
1671
1919
  * @param path - Resource path
1672
1920
  * @param action - KV action
1673
1921
  * @param body - Optional request body
1674
- * @param signal - Optional abort signal
1922
+ * @param signal - Request signal from createRequestSignal()
1675
1923
  * @returns Fetch response
1676
1924
  */
1677
1925
  async invokeOperation(path, action, body, signal, extraHeaders) {
@@ -1687,7 +1935,7 @@ var KVService = class extends BaseService {
1687
1935
  method: "POST",
1688
1936
  headers: requestHeaders,
1689
1937
  body,
1690
- signal: this.combineSignals(signal)
1938
+ signal
1691
1939
  });
1692
1940
  }
1693
1941
  /**
@@ -1864,6 +2112,7 @@ var KVService = class extends BaseService {
1864
2112
  "kv"
1865
2113
  ));
1866
2114
  }
2115
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
1867
2116
  try {
1868
2117
  const session = this.context.session;
1869
2118
  const invocationHeaders = this.context.invokeAny(
@@ -1885,15 +2134,17 @@ var KVService = class extends BaseService {
1885
2134
  const response = await this.context.fetch(`${this.host}/invoke`, {
1886
2135
  method: "POST",
1887
2136
  headers,
1888
- signal: this.combineSignals(options?.signal)
2137
+ signal: request.signal
1889
2138
  });
1890
2139
  if (!response.ok) {
1891
- const errorText = await response.text();
2140
+ const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
1892
2141
  if (response.status === 401 || response.status === 403) {
1893
2142
  return this.authorizationFailure(
1894
2143
  `Failed to batch read ${keys.length} key(s)`,
1895
2144
  response,
1896
- errorText
2145
+ errorText,
2146
+ paths,
2147
+ action
1897
2148
  );
1898
2149
  }
1899
2150
  if (response.status === 413) {
@@ -1948,6 +2199,8 @@ var KVService = class extends BaseService {
1948
2199
  return ok({ results, count: results.length });
1949
2200
  } catch (error) {
1950
2201
  return err(wrapError("kv", error));
2202
+ } finally {
2203
+ request.dispose();
1951
2204
  }
1952
2205
  }
1953
2206
  /**
@@ -1983,7 +2236,7 @@ var KVService = class extends BaseService {
1983
2236
  return text;
1984
2237
  }
1985
2238
  }
1986
- async createSignedReadUrlError(response, key) {
2239
+ async createSignedReadUrlError(response, key, path) {
1987
2240
  let errorText = response.statusText;
1988
2241
  try {
1989
2242
  const text = await response.text();
@@ -1996,7 +2249,9 @@ var KVService = class extends BaseService {
1996
2249
  return this.authorizationFailure(
1997
2250
  `Failed to create signed read URL for key ${JSON.stringify(key)}`,
1998
2251
  response,
1999
- errorText
2252
+ errorText,
2253
+ [path],
2254
+ KVAction.GET
2000
2255
  );
2001
2256
  }
2002
2257
  const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
@@ -2009,21 +2264,42 @@ var KVService = class extends BaseService {
2009
2264
  )
2010
2265
  );
2011
2266
  }
2267
+ /** A known 401/403 keeps its typed status even if reading its body fails. */
2268
+ async readAuthorizationText(response) {
2269
+ try {
2270
+ return await response.text();
2271
+ } catch {
2272
+ return "";
2273
+ }
2274
+ }
2012
2275
  /**
2013
2276
  * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
2014
2277
  * status and the server text (`<context>: <status> - <text>`, the same
2015
2278
  * shape as every other KV failure) so callers that rethrow only the message
2016
2279
  * still see the status; `meta.status` carries it for typed callers.
2017
- * Resource/ability hints are parsed from the raw body, never the message.
2280
+ * Server hints must match the requested path/action (whether the node sends
2281
+ * a relative path or full resource); metadata uses only the request-derived
2282
+ * canonical resource under the authenticated session's space.
2018
2283
  */
2019
- authorizationFailure(context, response, errorText, extraMeta = {}) {
2284
+ authorizationFailure(context, response, errorText, paths, expectedAction, extraMeta = {}) {
2020
2285
  const { resource, action } = parseAuthError(errorText);
2286
+ const requestedPath = paths.find(
2287
+ (path) => resource === path || resource === `${this.context.session.spaceId}/kv/${path}`
2288
+ );
2289
+ const capability = requestedPath !== void 0 && action === expectedAction ? validatedCapabilityOf({
2290
+ service: "kv",
2291
+ code: ErrorCodes.AUTH_UNAUTHORIZED,
2292
+ meta: {
2293
+ status: response.status,
2294
+ resource: `${this.context.session.spaceId}/kv/${requestedPath}`,
2295
+ requiredAction: action
2296
+ }
2297
+ }) : void 0;
2021
2298
  const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
2022
2299
  return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
2023
2300
  status: response.status,
2024
- ...action && { requiredAction: action },
2025
- ...resource && { resource },
2026
- ...extraMeta
2301
+ ...extraMeta,
2302
+ ...capability
2027
2303
  }));
2028
2304
  }
2029
2305
  normalizeSignedReadUrlResponse(data) {
@@ -2057,23 +2333,35 @@ var KVService = class extends BaseService {
2057
2333
  ));
2058
2334
  }
2059
2335
  const path = this.getFullPath(key, options?.prefix);
2336
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2060
2337
  try {
2061
2338
  const response = await this.invokeOperation(
2062
2339
  path,
2063
2340
  KVAction.GET,
2064
2341
  void 0,
2065
- options?.signal,
2342
+ request.signal,
2066
2343
  options?.maxResponseBytes === void 0 ? void 0 : { "x-tinycloud-max-response-bytes": String(options.maxResponseBytes) }
2067
2344
  );
2068
2345
  if (!response.ok) {
2069
2346
  if (response.status === 401 || response.status === 403) {
2070
- const errorText2 = await response.text();
2347
+ const errorText2 = await this.readAuthorizationText(response);
2071
2348
  const permissionHint = parsePermissionHintFromErrorText(errorText2);
2349
+ const structuredCapability = permissionHint?.service === "tinycloud.kv" && permissionHint.space === this.context.session.spaceId && permissionHint.path === path ? validatedCapabilityOf({
2350
+ service: "kv",
2351
+ code: ErrorCodes.AUTH_UNAUTHORIZED,
2352
+ meta: {
2353
+ status: response.status,
2354
+ resource: `${this.context.session.spaceId}/kv/${path}`,
2355
+ requiredAction: KVAction.GET
2356
+ }
2357
+ }) : void 0;
2072
2358
  return this.authorizationFailure(
2073
2359
  `Failed to get key ${JSON.stringify(key)}`,
2074
2360
  response,
2075
2361
  errorText2,
2076
- permissionHint === void 0 ? {} : { permissionHint }
2362
+ [path],
2363
+ KVAction.GET,
2364
+ structuredCapability ? { permissionHint, ...structuredCapability } : {}
2077
2365
  );
2078
2366
  }
2079
2367
  if (response.status === 404) {
@@ -2108,6 +2396,8 @@ var KVService = class extends BaseService {
2108
2396
  });
2109
2397
  } catch (error) {
2110
2398
  return err(wrapError("kv", error));
2399
+ } finally {
2400
+ request.dispose();
2111
2401
  }
2112
2402
  });
2113
2403
  }
@@ -2135,12 +2425,13 @@ var KVService = class extends BaseService {
2135
2425
  }
2136
2426
  const path = this.getFullPath(key, options?.prefix);
2137
2427
  const body = this.serializePutValue(value, options?.contentType);
2428
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2138
2429
  try {
2139
2430
  const response = await this.invokeOperation(
2140
2431
  path,
2141
2432
  KVAction.PUT,
2142
2433
  body,
2143
- options?.signal,
2434
+ request.signal,
2144
2435
  {
2145
2436
  ...options?.ifMatch === void 0 ? {} : { "if-match": options.ifMatch },
2146
2437
  ...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
@@ -2150,7 +2441,9 @@ var KVService = class extends BaseService {
2150
2441
  return this.authorizationFailure(
2151
2442
  `Failed to put key ${JSON.stringify(key)}`,
2152
2443
  response,
2153
- await response.text()
2444
+ await this.readAuthorizationText(response),
2445
+ [path],
2446
+ KVAction.PUT
2154
2447
  );
2155
2448
  }
2156
2449
  if (!response.ok) {
@@ -2194,6 +2487,8 @@ var KVService = class extends BaseService {
2194
2487
  });
2195
2488
  } catch (error) {
2196
2489
  return err(wrapError("kv", error));
2490
+ } finally {
2491
+ request.dispose();
2197
2492
  }
2198
2493
  });
2199
2494
  }
@@ -2233,6 +2528,7 @@ var KVService = class extends BaseService {
2233
2528
  seen.add(path);
2234
2529
  }
2235
2530
  let requestMayHaveDispatched = false;
2531
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2236
2532
  try {
2237
2533
  const body = new FormData();
2238
2534
  for (let index = 0; index < items.length; index++) {
@@ -2255,7 +2551,7 @@ var KVService = class extends BaseService {
2255
2551
  method: "POST",
2256
2552
  headers,
2257
2553
  body,
2258
- signal: this.combineSignals(options?.signal)
2554
+ signal: request.signal
2259
2555
  };
2260
2556
  const fetchFn = this.context.fetch;
2261
2557
  requestMayHaveDispatched = true;
@@ -2279,7 +2575,9 @@ var KVService = class extends BaseService {
2279
2575
  return this.authorizationFailure(
2280
2576
  `Failed to batch put ${items.length} key(s)`,
2281
2577
  response,
2282
- errorText
2578
+ errorText,
2579
+ paths,
2580
+ KVAction.PUT
2283
2581
  );
2284
2582
  }
2285
2583
  const quotaError = this.handleQuotaErrorResponse(
@@ -2304,14 +2602,16 @@ var KVService = class extends BaseService {
2304
2602
  rawBody = await response.json();
2305
2603
  } catch (jsonError) {
2306
2604
  const cause = jsonError instanceof Error ? jsonError : new Error(String(jsonError));
2605
+ const timedOut = isRequestTimeoutError(cause);
2307
2606
  return err(
2308
2607
  serviceError(
2309
- ErrorCodes.NETWORK_ERROR,
2310
- `KV batchPut response was not valid JSON: ${cause.message}`,
2608
+ timedOut ? ErrorCodes.TIMEOUT : ErrorCodes.NETWORK_ERROR,
2609
+ timedOut ? `KV batchPut response body did not arrive in time: ${cause.message}` : `KV batchPut response was not valid JSON: ${cause.message}`,
2311
2610
  "kv",
2312
2611
  {
2313
2612
  cause,
2314
2613
  meta: {
2614
+ ...timedOut ? { timeoutMs: cause.timeoutMs } : {},
2315
2615
  requestMayHaveDispatched: true,
2316
2616
  responseReceived: true,
2317
2617
  status: response.status,
@@ -2366,6 +2666,8 @@ var KVService = class extends BaseService {
2366
2666
  ...wrapped,
2367
2667
  meta: { ...wrapped.meta, requestMayHaveDispatched }
2368
2668
  });
2669
+ } finally {
2670
+ request.dispose();
2369
2671
  }
2370
2672
  });
2371
2673
  }
@@ -2388,23 +2690,26 @@ var KVService = class extends BaseService {
2388
2690
  if (options?.path) {
2389
2691
  listPath = listPath ? `${listPath}/${options.path}` : options.path;
2390
2692
  }
2693
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2391
2694
  try {
2392
2695
  const response = await this.invokeOperation(
2393
2696
  listPath,
2394
2697
  KVAction.LIST,
2395
2698
  void 0,
2396
- options?.signal,
2699
+ request.signal,
2397
2700
  options?.limit === void 0 && options?.cursor === void 0 ? void 0 : {
2398
2701
  ...options?.limit === void 0 ? {} : { "x-tinycloud-limit": String(options.limit) },
2399
2702
  ...options?.cursor === void 0 ? {} : { "x-tinycloud-cursor": options.cursor }
2400
2703
  }
2401
2704
  );
2402
2705
  if (!response.ok) {
2403
- if (response.status === 401) {
2706
+ if (response.status === 401 || response.status === 403) {
2404
2707
  return this.authorizationFailure(
2405
2708
  "Failed to list keys",
2406
2709
  response,
2407
- await response.text()
2710
+ await this.readAuthorizationText(response),
2711
+ [listPath],
2712
+ KVAction.LIST
2408
2713
  );
2409
2714
  }
2410
2715
  const errorText = await response.text();
@@ -2432,6 +2737,8 @@ var KVService = class extends BaseService {
2432
2737
  });
2433
2738
  } catch (error) {
2434
2739
  return err(wrapError("kv", error));
2740
+ } finally {
2741
+ request.dispose();
2435
2742
  }
2436
2743
  });
2437
2744
  }
@@ -2444,20 +2751,23 @@ var KVService = class extends BaseService {
2444
2751
  return err(authRequiredError("kv"));
2445
2752
  }
2446
2753
  const path = this.getFullPath(key, options?.prefix);
2754
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2447
2755
  try {
2448
2756
  const response = await this.invokeOperation(
2449
2757
  path,
2450
2758
  KVAction.DELETE,
2451
2759
  void 0,
2452
- options?.signal,
2760
+ request.signal,
2453
2761
  options?.ifMatch === void 0 ? void 0 : { "if-match": options.ifMatch }
2454
2762
  );
2455
2763
  if (!response.ok) {
2456
- if (response.status === 401) {
2764
+ if (response.status === 401 || response.status === 403) {
2457
2765
  return this.authorizationFailure(
2458
2766
  `Failed to delete key ${JSON.stringify(key)}`,
2459
2767
  response,
2460
- await response.text()
2768
+ await this.readAuthorizationText(response),
2769
+ [path],
2770
+ KVAction.DELETE
2461
2771
  );
2462
2772
  }
2463
2773
  if (response.status === 404) {
@@ -2495,6 +2805,8 @@ var KVService = class extends BaseService {
2495
2805
  });
2496
2806
  } catch (error) {
2497
2807
  return err(wrapError("kv", error));
2808
+ } finally {
2809
+ request.dispose();
2498
2810
  }
2499
2811
  });
2500
2812
  }
@@ -2507,19 +2819,22 @@ var KVService = class extends BaseService {
2507
2819
  return err(authRequiredError("kv"));
2508
2820
  }
2509
2821
  const path = this.getFullPath(key, options?.prefix);
2822
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2510
2823
  try {
2511
2824
  const response = await this.invokeOperation(
2512
2825
  path,
2513
2826
  KVAction.HEAD,
2514
2827
  void 0,
2515
- options?.signal
2828
+ request.signal
2516
2829
  );
2517
2830
  if (!response.ok) {
2518
- if (response.status === 401) {
2831
+ if (response.status === 401 || response.status === 403) {
2519
2832
  return this.authorizationFailure(
2520
2833
  `Failed to get metadata for key ${JSON.stringify(key)}`,
2521
2834
  response,
2522
- await response.text()
2835
+ await this.readAuthorizationText(response),
2836
+ [path],
2837
+ KVAction.HEAD
2523
2838
  );
2524
2839
  }
2525
2840
  if (response.status === 404) {
@@ -2541,6 +2856,8 @@ var KVService = class extends BaseService {
2541
2856
  });
2542
2857
  } catch (error) {
2543
2858
  return err(wrapError("kv", error));
2859
+ } finally {
2860
+ request.dispose();
2544
2861
  }
2545
2862
  });
2546
2863
  }
@@ -2578,15 +2895,16 @@ var KVService = class extends BaseService {
2578
2895
  if (options?.etag !== void 0) {
2579
2896
  body.etag = options.etag;
2580
2897
  }
2898
+ const request = this.createRequestSignal(options?.signal, options?.timeout);
2581
2899
  try {
2582
2900
  const response = await this.context.fetch(`${this.host}/signed/kv`, {
2583
2901
  method: "POST",
2584
2902
  headers: this.withJsonContentType(headers),
2585
2903
  body: JSON.stringify(body),
2586
- signal: this.combineSignals(options?.signal)
2904
+ signal: request.signal
2587
2905
  });
2588
2906
  if (!response.ok) {
2589
- return this.createSignedReadUrlError(response, key);
2907
+ return this.createSignedReadUrlError(response, key, path);
2590
2908
  }
2591
2909
  const signedUrl = this.normalizeSignedReadUrlResponse(
2592
2910
  await response.json()
@@ -2603,6 +2921,8 @@ var KVService = class extends BaseService {
2603
2921
  return ok(signedUrl);
2604
2922
  } catch (error) {
2605
2923
  return err(wrapError("kv", error));
2924
+ } finally {
2925
+ request.dispose();
2606
2926
  }
2607
2927
  });
2608
2928
  }
@@ -2729,16 +3049,16 @@ var DatabaseHandle = class {
2729
3049
  };
2730
3050
 
2731
3051
  // src/sql/types.ts
2732
- var import_bootstrap = require("@tinycloud/bootstrap");
3052
+ var import_bootstrap2 = require("@tinycloud/bootstrap");
2733
3053
  var SQLAction = {
2734
- READ: import_bootstrap.SQL.READ,
2735
- WRITE: import_bootstrap.SQL.WRITE,
2736
- SCHEMA: import_bootstrap.SQL.SCHEMA,
2737
- ADMIN: import_bootstrap.SQL.ADMIN,
2738
- SELECT: import_bootstrap.SQL.SELECT,
2739
- EXECUTE: import_bootstrap.SQL.EXECUTE,
2740
- EXPORT: import_bootstrap.SQL.EXPORT,
2741
- ALL: import_bootstrap.SQL.ALL
3054
+ READ: import_bootstrap2.SQL.READ,
3055
+ WRITE: import_bootstrap2.SQL.WRITE,
3056
+ SCHEMA: import_bootstrap2.SQL.SCHEMA,
3057
+ ADMIN: import_bootstrap2.SQL.ADMIN,
3058
+ SELECT: import_bootstrap2.SQL.SELECT,
3059
+ EXECUTE: import_bootstrap2.SQL.EXECUTE,
3060
+ EXPORT: import_bootstrap2.SQL.EXPORT,
3061
+ ALL: import_bootstrap2.SQL.ALL
2742
3062
  };
2743
3063
 
2744
3064
  // src/sql/SQLService.ts
@@ -2816,6 +3136,7 @@ var SQLService = class extends BaseService {
2816
3136
  if (!this.requireAuth()) {
2817
3137
  return err(authRequiredError("sql"));
2818
3138
  }
3139
+ const request = this.createRequestSignal(options?.signal);
2819
3140
  try {
2820
3141
  const body = {
2821
3142
  action: "query",
@@ -2824,19 +3145,22 @@ var SQLService = class extends BaseService {
2824
3145
  };
2825
3146
  if (options?.maxRows !== void 0) body.maxRows = options.maxRows;
2826
3147
  if (options?.maxBytes !== void 0) body.maxBytes = options.maxBytes;
3148
+ const action = this.actionForSql(sql, SQLAction.READ);
2827
3149
  const response = await this.invokeSQL(
2828
3150
  dbName,
2829
- this.actionForSql(sql, SQLAction.READ),
3151
+ action,
2830
3152
  body,
2831
- options?.signal
3153
+ request.signal
2832
3154
  );
2833
3155
  if (!response.ok) {
2834
- return this.handleErrorResponse(response, "query");
3156
+ return this.handleErrorResponse(response, "query", dbName, [action]);
2835
3157
  }
2836
3158
  const data = await response.json();
2837
3159
  return ok(data);
2838
3160
  } catch (error) {
2839
3161
  return err(wrapError("sql", error));
3162
+ } finally {
3163
+ request.dispose();
2840
3164
  }
2841
3165
  });
2842
3166
  }
@@ -2845,6 +3169,7 @@ var SQLService = class extends BaseService {
2845
3169
  if (!this.requireAuth()) {
2846
3170
  return err(authRequiredError("sql"));
2847
3171
  }
3172
+ const request = this.createRequestSignal(options?.signal);
2848
3173
  try {
2849
3174
  const body = {
2850
3175
  action: "execute",
@@ -2860,19 +3185,22 @@ var SQLService = class extends BaseService {
2860
3185
  (statement) => this.actionForSql(statement, SQLAction.SCHEMA)
2861
3186
  )
2862
3187
  ];
3188
+ const requestedActions = this.dedupeActions(actions);
2863
3189
  const response = await this.invokeSQL(
2864
3190
  dbName,
2865
- this.dedupeActions(actions),
3191
+ requestedActions,
2866
3192
  body,
2867
- options?.signal
3193
+ request.signal
2868
3194
  );
2869
3195
  if (!response.ok) {
2870
- return this.handleErrorResponse(response, "execute");
3196
+ return this.handleErrorResponse(response, "execute", dbName, requestedActions);
2871
3197
  }
2872
3198
  const data = await response.json();
2873
3199
  return ok(data);
2874
3200
  } catch (error) {
2875
3201
  return err(wrapError("sql", error));
3202
+ } finally {
3203
+ request.dispose();
2876
3204
  }
2877
3205
  });
2878
3206
  }
@@ -2881,20 +3209,24 @@ var SQLService = class extends BaseService {
2881
3209
  if (!this.requireAuth()) {
2882
3210
  return err(authRequiredError("sql"));
2883
3211
  }
3212
+ const request = this.createRequestSignal(options?.signal);
2884
3213
  try {
3214
+ const requestedActions = this.actionsForSqlBatch(statements);
2885
3215
  const response = await this.invokeSQL(
2886
3216
  dbName,
2887
- this.actionsForSqlBatch(statements),
3217
+ requestedActions,
2888
3218
  { action: "batch", statements },
2889
- options?.signal
3219
+ request.signal
2890
3220
  );
2891
3221
  if (!response.ok) {
2892
- return this.handleErrorResponse(response, "batch");
3222
+ return this.handleErrorResponse(response, "batch", dbName, requestedActions);
2893
3223
  }
2894
3224
  const data = await response.json();
2895
3225
  return ok(data);
2896
3226
  } catch (error) {
2897
3227
  return err(wrapError("sql", error));
3228
+ } finally {
3229
+ request.dispose();
2898
3230
  }
2899
3231
  });
2900
3232
  }
@@ -2903,20 +3235,23 @@ var SQLService = class extends BaseService {
2903
3235
  if (!this.requireAuth()) {
2904
3236
  return err(authRequiredError("sql"));
2905
3237
  }
3238
+ const request = this.createRequestSignal(options?.signal);
2906
3239
  try {
2907
3240
  const response = await this.invokeSQL(
2908
3241
  dbName,
2909
3242
  SQLAction.WRITE,
2910
3243
  { action: "execute_statement", name, params: params ?? [] },
2911
- options?.signal
3244
+ request.signal
2912
3245
  );
2913
3246
  if (!response.ok) {
2914
- return this.handleErrorResponse(response, "executeStatement");
3247
+ return this.handleErrorResponse(response, "executeStatement", dbName, [SQLAction.WRITE]);
2915
3248
  }
2916
3249
  const data = await response.json();
2917
3250
  return ok(data);
2918
3251
  } catch (error) {
2919
3252
  return err(wrapError("sql", error));
3253
+ } finally {
3254
+ request.dispose();
2920
3255
  }
2921
3256
  });
2922
3257
  }
@@ -2925,15 +3260,16 @@ var SQLService = class extends BaseService {
2925
3260
  if (!this.requireAuth()) {
2926
3261
  return err(authRequiredError("sql"));
2927
3262
  }
3263
+ const request = this.createRequestSignal(options?.signal);
2928
3264
  try {
2929
3265
  const response = await this.invokeSQL(
2930
3266
  dbName,
2931
3267
  SQLAction.READ,
2932
3268
  { action: "export" },
2933
- options?.signal
3269
+ request.signal
2934
3270
  );
2935
3271
  if (!response.ok) {
2936
- return this.handleErrorResponse(response, "export");
3272
+ return this.handleErrorResponse(response, "export", dbName, [SQLAction.READ]);
2937
3273
  }
2938
3274
  const resp = response;
2939
3275
  if (typeof resp.blob === "function") {
@@ -2944,6 +3280,8 @@ var SQLService = class extends BaseService {
2944
3280
  return ok(text);
2945
3281
  } catch (error) {
2946
3282
  return err(wrapError("sql", error));
3283
+ } finally {
3284
+ request.dispose();
2947
3285
  }
2948
3286
  });
2949
3287
  }
@@ -3034,7 +3372,7 @@ var SQLService = class extends BaseService {
3034
3372
  "Content-Type": "application/json"
3035
3373
  },
3036
3374
  body: JSON.stringify(body),
3037
- signal: this.combineSignals(signal)
3375
+ signal
3038
3376
  });
3039
3377
  }
3040
3378
  actionForSql(sql, fallback) {
@@ -3067,7 +3405,7 @@ var SQLService = class extends BaseService {
3067
3405
  }))
3068
3406
  );
3069
3407
  }
3070
- async handleErrorResponse(response, operation) {
3408
+ async handleErrorResponse(response, operation, dbName, requestedActions) {
3071
3409
  const errorText = await response.text();
3072
3410
  const meta = responseErrorMeta(response.status, response.statusText, errorText);
3073
3411
  if (response.status === 402) {
@@ -3086,9 +3424,11 @@ var SQLService = class extends BaseService {
3086
3424
  errorBody
3087
3425
  );
3088
3426
  if (response.status === 401) {
3089
- const { resource, action } = parseAuthError(errorText);
3090
- if (action) meta.requiredAction = action;
3091
- if (resource) meta.resource = resource;
3427
+ const { action } = parseAuthError(errorText);
3428
+ if (action && requestedActions.includes(action)) {
3429
+ meta.requiredAction = action;
3430
+ meta.resource = `${this.context.session.spaceId}/sql/${dbName}`;
3431
+ }
3092
3432
  }
3093
3433
  return err(
3094
3434
  serviceError(errorCode, message, "sql", { meta })
@@ -3233,16 +3573,16 @@ var DuckDbDatabaseHandle = class {
3233
3573
  };
3234
3574
 
3235
3575
  // src/duckdb/types.ts
3236
- var import_bootstrap2 = require("@tinycloud/bootstrap");
3576
+ var import_bootstrap3 = require("@tinycloud/bootstrap");
3237
3577
  var DuckDbAction = {
3238
- READ: import_bootstrap2.DUCKDB.READ,
3239
- WRITE: import_bootstrap2.DUCKDB.WRITE,
3240
- ADMIN: import_bootstrap2.DUCKDB.ADMIN,
3241
- DESCRIBE: import_bootstrap2.DUCKDB.DESCRIBE,
3242
- EXPORT: import_bootstrap2.DUCKDB.EXPORT,
3243
- IMPORT: import_bootstrap2.DUCKDB.IMPORT,
3244
- EXECUTE: import_bootstrap2.DUCKDB.EXECUTE,
3245
- ALL: import_bootstrap2.DUCKDB.ALL
3578
+ READ: import_bootstrap3.DUCKDB.READ,
3579
+ WRITE: import_bootstrap3.DUCKDB.WRITE,
3580
+ ADMIN: import_bootstrap3.DUCKDB.ADMIN,
3581
+ DESCRIBE: import_bootstrap3.DUCKDB.DESCRIBE,
3582
+ EXPORT: import_bootstrap3.DUCKDB.EXPORT,
3583
+ IMPORT: import_bootstrap3.DUCKDB.IMPORT,
3584
+ EXECUTE: import_bootstrap3.DUCKDB.EXECUTE,
3585
+ ALL: import_bootstrap3.DUCKDB.ALL
3246
3586
  };
3247
3587
 
3248
3588
  // src/duckdb/DuckDbService.ts
@@ -3296,12 +3636,13 @@ var DuckDbService = class extends BaseService {
3296
3636
  if (!this.requireAuth()) {
3297
3637
  return err(authRequiredError("duckdb"));
3298
3638
  }
3639
+ const request = this.createRequestSignal(options?.signal);
3299
3640
  try {
3300
3641
  const response = await this.invokeDuckDb(
3301
3642
  dbName,
3302
3643
  DuckDbAction.READ,
3303
3644
  { action: "query", sql, params: params ?? [] },
3304
- options?.signal
3645
+ request.signal
3305
3646
  );
3306
3647
  if (!response.ok) {
3307
3648
  return this.handleErrorResponse(response, "query");
@@ -3310,6 +3651,8 @@ var DuckDbService = class extends BaseService {
3310
3651
  return ok(data);
3311
3652
  } catch (error) {
3312
3653
  return err(wrapError("duckdb", error));
3654
+ } finally {
3655
+ request.dispose();
3313
3656
  }
3314
3657
  });
3315
3658
  }
@@ -3318,12 +3661,13 @@ var DuckDbService = class extends BaseService {
3318
3661
  if (!this.requireAuth()) {
3319
3662
  return err(authRequiredError("duckdb"));
3320
3663
  }
3664
+ const request = this.createRequestSignal(options?.signal);
3321
3665
  try {
3322
3666
  const response = await this.invokeDuckDb(
3323
3667
  dbName,
3324
3668
  DuckDbAction.READ,
3325
3669
  { action: "query", sql, params: params ?? [] },
3326
- options?.signal,
3670
+ request.signal,
3327
3671
  { Accept: "application/vnd.apache.arrow.stream" }
3328
3672
  );
3329
3673
  if (!response.ok) {
@@ -3333,6 +3677,8 @@ var DuckDbService = class extends BaseService {
3333
3677
  return ok(buffer);
3334
3678
  } catch (error) {
3335
3679
  return err(wrapError("duckdb", error));
3680
+ } finally {
3681
+ request.dispose();
3336
3682
  }
3337
3683
  });
3338
3684
  }
@@ -3341,6 +3687,7 @@ var DuckDbService = class extends BaseService {
3341
3687
  if (!this.requireAuth()) {
3342
3688
  return err(authRequiredError("duckdb"));
3343
3689
  }
3690
+ const request = this.createRequestSignal(options?.signal);
3344
3691
  try {
3345
3692
  const body = {
3346
3693
  action: "execute",
@@ -3354,7 +3701,7 @@ var DuckDbService = class extends BaseService {
3354
3701
  dbName,
3355
3702
  DuckDbAction.WRITE,
3356
3703
  body,
3357
- options?.signal
3704
+ request.signal
3358
3705
  );
3359
3706
  if (!response.ok) {
3360
3707
  return this.handleErrorResponse(response, "execute");
@@ -3363,6 +3710,8 @@ var DuckDbService = class extends BaseService {
3363
3710
  return ok(data);
3364
3711
  } catch (error) {
3365
3712
  return err(wrapError("duckdb", error));
3713
+ } finally {
3714
+ request.dispose();
3366
3715
  }
3367
3716
  });
3368
3717
  }
@@ -3371,6 +3720,7 @@ var DuckDbService = class extends BaseService {
3371
3720
  if (!this.requireAuth()) {
3372
3721
  return err(authRequiredError("duckdb"));
3373
3722
  }
3723
+ const request = this.createRequestSignal(options?.signal);
3374
3724
  try {
3375
3725
  const body = {
3376
3726
  action: "batch",
@@ -3383,7 +3733,7 @@ var DuckDbService = class extends BaseService {
3383
3733
  dbName,
3384
3734
  DuckDbAction.WRITE,
3385
3735
  body,
3386
- options?.signal
3736
+ request.signal
3387
3737
  );
3388
3738
  if (!response.ok) {
3389
3739
  return this.handleErrorResponse(response, "batch");
@@ -3392,6 +3742,8 @@ var DuckDbService = class extends BaseService {
3392
3742
  return ok(data);
3393
3743
  } catch (error) {
3394
3744
  return err(wrapError("duckdb", error));
3745
+ } finally {
3746
+ request.dispose();
3395
3747
  }
3396
3748
  });
3397
3749
  }
@@ -3400,12 +3752,13 @@ var DuckDbService = class extends BaseService {
3400
3752
  if (!this.requireAuth()) {
3401
3753
  return err(authRequiredError("duckdb"));
3402
3754
  }
3755
+ const request = this.createRequestSignal(options?.signal);
3403
3756
  try {
3404
3757
  const response = await this.invokeDuckDb(
3405
3758
  dbName,
3406
3759
  DuckDbAction.WRITE,
3407
3760
  { action: "executeStatement", name, params: params ?? [] },
3408
- options?.signal
3761
+ request.signal
3409
3762
  );
3410
3763
  if (!response.ok) {
3411
3764
  return this.handleErrorResponse(response, "executeStatement");
@@ -3414,6 +3767,8 @@ var DuckDbService = class extends BaseService {
3414
3767
  return ok(data);
3415
3768
  } catch (error) {
3416
3769
  return err(wrapError("duckdb", error));
3770
+ } finally {
3771
+ request.dispose();
3417
3772
  }
3418
3773
  });
3419
3774
  }
@@ -3422,12 +3777,13 @@ var DuckDbService = class extends BaseService {
3422
3777
  if (!this.requireAuth()) {
3423
3778
  return err(authRequiredError("duckdb"));
3424
3779
  }
3780
+ const request = this.createRequestSignal(options?.signal);
3425
3781
  try {
3426
3782
  const response = await this.invokeDuckDb(
3427
3783
  dbName,
3428
3784
  DuckDbAction.READ,
3429
3785
  { action: "describe" },
3430
- options?.signal
3786
+ request.signal
3431
3787
  );
3432
3788
  if (!response.ok) {
3433
3789
  return this.handleErrorResponse(response, "describe");
@@ -3436,6 +3792,8 @@ var DuckDbService = class extends BaseService {
3436
3792
  return ok(data);
3437
3793
  } catch (error) {
3438
3794
  return err(wrapError("duckdb", error));
3795
+ } finally {
3796
+ request.dispose();
3439
3797
  }
3440
3798
  });
3441
3799
  }
@@ -3444,12 +3802,13 @@ var DuckDbService = class extends BaseService {
3444
3802
  if (!this.requireAuth()) {
3445
3803
  return err(authRequiredError("duckdb"));
3446
3804
  }
3805
+ const request = this.createRequestSignal(options?.signal);
3447
3806
  try {
3448
3807
  const response = await this.invokeDuckDb(
3449
3808
  dbName,
3450
3809
  DuckDbAction.EXPORT,
3451
3810
  { action: "export" },
3452
- options?.signal
3811
+ request.signal
3453
3812
  );
3454
3813
  if (!response.ok) {
3455
3814
  return this.handleErrorResponse(response, "export");
@@ -3458,6 +3817,8 @@ var DuckDbService = class extends BaseService {
3458
3817
  return ok(blob);
3459
3818
  } catch (error) {
3460
3819
  return err(wrapError("duckdb", error));
3820
+ } finally {
3821
+ request.dispose();
3461
3822
  }
3462
3823
  });
3463
3824
  }
@@ -3466,6 +3827,7 @@ var DuckDbService = class extends BaseService {
3466
3827
  if (!this.requireAuth()) {
3467
3828
  return err(authRequiredError("duckdb"));
3468
3829
  }
3830
+ const request = this.createRequestSignal(options?.signal);
3469
3831
  try {
3470
3832
  const session = this.context.session;
3471
3833
  const headers = this.context.invoke(
@@ -3481,7 +3843,7 @@ var DuckDbService = class extends BaseService {
3481
3843
  "Content-Type": "application/x-duckdb"
3482
3844
  },
3483
3845
  body: new Blob([data]),
3484
- signal: this.combineSignals(options?.signal)
3846
+ signal: request.signal
3485
3847
  });
3486
3848
  if (!response.ok) {
3487
3849
  return this.handleErrorResponse(response, "import");
@@ -3489,6 +3851,8 @@ var DuckDbService = class extends BaseService {
3489
3851
  return ok(void 0);
3490
3852
  } catch (error) {
3491
3853
  return err(wrapError("duckdb", error));
3854
+ } finally {
3855
+ request.dispose();
3492
3856
  }
3493
3857
  });
3494
3858
  }
@@ -3504,7 +3868,7 @@ var DuckDbService = class extends BaseService {
3504
3868
  ...extraHeaders
3505
3869
  },
3506
3870
  body: JSON.stringify(body),
3507
- signal: this.combineSignals(signal)
3871
+ signal
3508
3872
  });
3509
3873
  }
3510
3874
  async handleErrorResponse(response, operation) {
@@ -6067,103 +6431,6 @@ function canonicalHashHex(sha256, value) {
6067
6431
  return hexEncode2(sha256(utf8Encode(canonical)));
6068
6432
  }
6069
6433
 
6070
- // src/encryption/networkId.ts
6071
- var URN_PREFIX = "urn:tinycloud:encryption:";
6072
- var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
6073
- var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
6074
- var NetworkIdError = class extends Error {
6075
- constructor(message) {
6076
- super(message);
6077
- this.name = "NetworkIdError";
6078
- }
6079
- };
6080
- function parseNetworkId(networkId) {
6081
- if (typeof networkId !== "string" || networkId.length === 0) {
6082
- throw new NetworkIdError("networkId must be a non-empty string");
6083
- }
6084
- if (!networkId.startsWith(URN_PREFIX)) {
6085
- throw new NetworkIdError(
6086
- `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
6087
- );
6088
- }
6089
- const body = networkId.slice(URN_PREFIX.length);
6090
- const lastColon = body.lastIndexOf(":");
6091
- if (lastColon <= 0 || lastColon === body.length - 1) {
6092
- throw new NetworkIdError(
6093
- `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
6094
- );
6095
- }
6096
- const ownerDid = body.slice(0, lastColon);
6097
- const name = body.slice(lastColon + 1);
6098
- if (!ownerDid.startsWith("did:")) {
6099
- throw new NetworkIdError(
6100
- `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
6101
- );
6102
- }
6103
- const didParts = ownerDid.split(":");
6104
- if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
6105
- throw new NetworkIdError(
6106
- `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
6107
- );
6108
- }
6109
- if (!NETWORK_NAME_RE.test(name)) {
6110
- throw new NetworkIdError(
6111
- `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6112
- );
6113
- }
6114
- return { networkId, ownerDid, name };
6115
- }
6116
- function buildNetworkId(ownerDid, name) {
6117
- if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
6118
- throw new NetworkIdError("ownerDid must be a DID");
6119
- }
6120
- if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
6121
- throw new NetworkIdError(
6122
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6123
- );
6124
- }
6125
- const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
6126
- parseNetworkId(networkId);
6127
- return networkId;
6128
- }
6129
- function isNetworkId(networkId) {
6130
- if (typeof networkId !== "string") {
6131
- return false;
6132
- }
6133
- try {
6134
- parseNetworkId(networkId);
6135
- return true;
6136
- } catch {
6137
- return false;
6138
- }
6139
- }
6140
- function parsePkhOwnerDid(ownerDid) {
6141
- const match = ownerDid.match(PKH_EIP155_DID_RE);
6142
- if (!match) return null;
6143
- return {
6144
- chainId: match[1],
6145
- address: match[2].toLowerCase()
6146
- };
6147
- }
6148
- function ownerDidMatches(a, b) {
6149
- const aPkh = parsePkhOwnerDid(a);
6150
- const bPkh = parsePkhOwnerDid(b);
6151
- if (aPkh && bPkh) {
6152
- return aPkh.chainId === bPkh.chainId && aPkh.address === bPkh.address;
6153
- }
6154
- return a === b;
6155
- }
6156
- function networkDiscoveryKey(name) {
6157
- if (!NETWORK_NAME_RE.test(name)) {
6158
- throw new NetworkIdError(
6159
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
6160
- );
6161
- }
6162
- return `.well-known/encryption/network/${name}`;
6163
- }
6164
- var ENCRYPTION_NETWORK_URN_PREFIX = URN_PREFIX;
6165
- var NETWORK_NAME_PATTERN = NETWORK_NAME_RE;
6166
-
6167
6434
  // src/encryption/types.ts
6168
6435
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
6169
6436
  var ENVELOPE_VERSION = 1;
@@ -7107,6 +7374,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7107
7374
  ok,
7108
7375
  openWrappedKey,
7109
7376
  parseAuthError,
7377
+ parseCapabilityResource,
7110
7378
  parseNetworkId,
7111
7379
  parsePermissionHint,
7112
7380
  parsePermissionHintFromErrorText,
@@ -7132,6 +7400,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7132
7400
  validateServiceResponseEvent,
7133
7401
  validateServiceSession,
7134
7402
  validateTelemetrySpanEvent,
7403
+ validatedCapabilityOf,
7135
7404
  verifyDecryptResponse,
7136
7405
  wrapError
7137
7406
  });