@tinycloud/sdk-services 3.0.0-beta.8 → 3.1.0-beta.1

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
@@ -73,6 +73,7 @@ __export(src_exports, {
73
73
  authExpiredError: () => authExpiredError,
74
74
  authRequiredError: () => authRequiredError,
75
75
  authUnauthorizedError: () => authUnauthorizedError,
76
+ authorizationVerdictOf: () => authorizationVerdictOf,
76
77
  base64Decode: () => base64Decode2,
77
78
  base64Encode: () => base64Encode2,
78
79
  buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
@@ -1135,6 +1136,37 @@ function parseAuthError(responseText) {
1135
1136
  function authUnauthorizedError(service, message, meta) {
1136
1137
  return serviceError(ErrorCodes.AUTH_UNAUTHORIZED, message, service, { meta });
1137
1138
  }
1139
+ var MAX_CAUSE_DEPTH = 8;
1140
+ function errorStatusOf(node) {
1141
+ const meta = node.meta;
1142
+ const candidates = [
1143
+ node.status,
1144
+ node.statusCode,
1145
+ typeof meta === "object" && meta !== null ? meta.status : void 0
1146
+ ];
1147
+ for (const candidate of candidates) {
1148
+ if (typeof candidate === "number" && Number.isInteger(candidate) && candidate >= 400 && candidate <= 599) {
1149
+ return candidate;
1150
+ }
1151
+ }
1152
+ return void 0;
1153
+ }
1154
+ function authorizationVerdictOf(error) {
1155
+ const seen = /* @__PURE__ */ new Set();
1156
+ let sawUnauthorizedCode = false;
1157
+ let node = error;
1158
+ for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === "object" && node !== null && !seen.has(node); depth += 1) {
1159
+ seen.add(node);
1160
+ const record = node;
1161
+ const status = errorStatusOf(record);
1162
+ if (status === 401) return "unauthenticated";
1163
+ if (status === 403) return "forbidden";
1164
+ if (status !== void 0) return "other";
1165
+ if (record.code === ErrorCodes.AUTH_UNAUTHORIZED) sawUnauthorizedCode = true;
1166
+ node = record.cause;
1167
+ }
1168
+ return sawUnauthorizedCode ? "forbidden" : void 0;
1169
+ }
1138
1170
  function storageQuotaExceededError(service, message, meta) {
1139
1171
  return {
1140
1172
  code: ErrorCodes.STORAGE_QUOTA_EXCEEDED,
@@ -1563,7 +1595,7 @@ var KVService = class extends BaseService {
1563
1595
  return err(
1564
1596
  storageQuotaExceededError(
1565
1597
  "kv",
1566
- `Storage quota exceeded for key "${key}": ${errorText}`,
1598
+ `Storage quota exceeded for key ${JSON.stringify(key)}: ${errorText}`,
1567
1599
  {
1568
1600
  status: response.status,
1569
1601
  ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
@@ -1576,7 +1608,7 @@ var KVService = class extends BaseService {
1576
1608
  return err(
1577
1609
  storageLimitReachedError(
1578
1610
  "kv",
1579
- `Storage limit reached for key "${key}": ${errorText}`,
1611
+ `Storage limit reached for key ${JSON.stringify(key)}: ${errorText}`,
1580
1612
  {
1581
1613
  status: response.status,
1582
1614
  ...quotaInfo ? { usedBytes: quotaInfo.usedBytes, limitBytes: quotaInfo.limitBytes } : {}
@@ -1620,7 +1652,7 @@ var KVService = class extends BaseService {
1620
1652
  */
1621
1653
  getFullPath(key, prefixOverride) {
1622
1654
  const prefix = prefixOverride ?? this._config.prefix ?? "";
1623
- return prefix ? `${prefix}/${key}` : key;
1655
+ return prefix ? key === "" ? prefix : `${prefix}/${key}` : key;
1624
1656
  }
1625
1657
  /**
1626
1658
  * Get the host URL.
@@ -1862,12 +1894,11 @@ var KVService = class extends BaseService {
1862
1894
  if (!response.ok) {
1863
1895
  const errorText = await response.text();
1864
1896
  if (response.status === 401 || response.status === 403) {
1865
- const { resource, action: requiredAction } = parseAuthError(errorText);
1866
- return err(authUnauthorizedError("kv", errorText, {
1867
- status: response.status,
1868
- ...requiredAction && { requiredAction },
1869
- ...resource && { resource }
1870
- }));
1897
+ return this.authorizationFailure(
1898
+ `Failed to batch read ${keys.length} key(s)`,
1899
+ response,
1900
+ errorText
1901
+ );
1871
1902
  }
1872
1903
  if (response.status === 413) {
1873
1904
  return err(serviceError(
@@ -1966,23 +1997,39 @@ var KVService = class extends BaseService {
1966
1997
  } catch {
1967
1998
  }
1968
1999
  if (response.status === 401 || response.status === 403) {
1969
- const { resource, action } = parseAuthError(errorText);
1970
- return err(authUnauthorizedError("kv", errorText, {
1971
- status: response.status,
1972
- ...action && { requiredAction: action },
1973
- ...resource && { resource }
1974
- }));
2000
+ return this.authorizationFailure(
2001
+ `Failed to create signed read URL for key ${JSON.stringify(key)}`,
2002
+ response,
2003
+ errorText
2004
+ );
1975
2005
  }
1976
2006
  const code = response.status === 400 ? ErrorCodes.INVALID_INPUT : ErrorCodes.NETWORK_ERROR;
1977
2007
  return err(
1978
2008
  serviceError(
1979
2009
  code,
1980
- `Failed to create signed read URL for key "${key}": ${response.status} - ${errorText}`,
2010
+ `Failed to create signed read URL for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
1981
2011
  "kv",
1982
2012
  { meta: { status: response.status, statusText: response.statusText } }
1983
2013
  )
1984
2014
  );
1985
2015
  }
2016
+ /**
2017
+ * AUTH_UNAUTHORIZED for a 401/403 response. The message keeps the HTTP
2018
+ * status and the server text (`<context>: <status> - <text>`, the same
2019
+ * shape as every other KV failure) so callers that rethrow only the message
2020
+ * still see the status; `meta.status` carries it for typed callers.
2021
+ * Resource/ability hints are parsed from the raw body, never the message.
2022
+ */
2023
+ authorizationFailure(context, response, errorText, extraMeta = {}) {
2024
+ const { resource, action } = parseAuthError(errorText);
2025
+ const detail = errorText.trim().length > 0 ? errorText : response.statusText || "authorization failed";
2026
+ return err(authUnauthorizedError("kv", `${context}: ${response.status} - ${detail}`, {
2027
+ status: response.status,
2028
+ ...action && { requiredAction: action },
2029
+ ...resource && { resource },
2030
+ ...extraMeta
2031
+ }));
2032
+ }
1986
2033
  normalizeSignedReadUrlResponse(data) {
1987
2034
  if (!data || typeof data !== "object") {
1988
2035
  return void 0;
@@ -2025,14 +2072,13 @@ var KVService = class extends BaseService {
2025
2072
  if (!response.ok) {
2026
2073
  if (response.status === 401 || response.status === 403) {
2027
2074
  const errorText2 = await response.text();
2028
- const { resource, action } = parseAuthError(errorText2);
2029
2075
  const permissionHint = parsePermissionHintFromErrorText(errorText2);
2030
- return err(authUnauthorizedError("kv", errorText2, {
2031
- status: response.status,
2032
- ...action && { requiredAction: action },
2033
- ...resource && { resource },
2034
- ...permissionHint === void 0 ? {} : { permissionHint }
2035
- }));
2076
+ return this.authorizationFailure(
2077
+ `Failed to get key ${JSON.stringify(key)}`,
2078
+ response,
2079
+ errorText2,
2080
+ permissionHint === void 0 ? {} : { permissionHint }
2081
+ );
2036
2082
  }
2037
2083
  if (response.status === 404) {
2038
2084
  return this.classifyNotFound(response, key);
@@ -2041,7 +2087,7 @@ var KVService = class extends BaseService {
2041
2087
  if (response.status === 413) {
2042
2088
  return err(serviceError(
2043
2089
  ErrorCodes.KV_RESPONSE_TOO_LARGE,
2044
- `KV value at key "${key}" exceeds the requested response limit`,
2090
+ `KV value at key ${JSON.stringify(key)} exceeds the requested response limit`,
2045
2091
  "kv",
2046
2092
  { meta: { status: response.status, statusText: response.statusText } }
2047
2093
  ));
@@ -2049,7 +2095,7 @@ var KVService = class extends BaseService {
2049
2095
  return err(
2050
2096
  serviceError(
2051
2097
  ErrorCodes.NETWORK_ERROR,
2052
- `Failed to get key "${key}": ${response.status} - ${errorText}`,
2098
+ `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2053
2099
  "kv",
2054
2100
  { meta: { status: response.status, statusText: response.statusText } }
2055
2101
  )
@@ -2104,21 +2150,19 @@ var KVService = class extends BaseService {
2104
2150
  ...options?.ifNoneMatch === void 0 ? {} : { "if-none-match": options.ifNoneMatch }
2105
2151
  }
2106
2152
  );
2153
+ if (response.status === 401 || response.status === 403) {
2154
+ return this.authorizationFailure(
2155
+ `Failed to put key ${JSON.stringify(key)}`,
2156
+ response,
2157
+ await response.text()
2158
+ );
2159
+ }
2107
2160
  if (!response.ok) {
2108
- if (response.status === 401) {
2109
- const errorText2 = await response.text();
2110
- const { resource, action } = parseAuthError(errorText2);
2111
- return err(authUnauthorizedError("kv", errorText2, {
2112
- status: response.status,
2113
- ...action && { requiredAction: action },
2114
- ...resource && { resource }
2115
- }));
2116
- }
2117
2161
  const errorText = await response.text();
2118
2162
  if (response.status === 412) {
2119
2163
  return err(serviceError(
2120
2164
  ErrorCodes.KV_PRECONDITION_FAILED,
2121
- `KV precondition failed for key "${key}"`,
2165
+ `KV precondition failed for key ${JSON.stringify(key)}`,
2122
2166
  "kv",
2123
2167
  { meta: { status: response.status, statusText: response.statusText } }
2124
2168
  ));
@@ -2126,7 +2170,7 @@ var KVService = class extends BaseService {
2126
2170
  if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
2127
2171
  return err(serviceError(
2128
2172
  ErrorCodes.KV_CONFLICT,
2129
- `Concurrent KV update conflicted for key "${key}"`,
2173
+ `Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
2130
2174
  "kv",
2131
2175
  { meta: { status: response.status, statusText: response.statusText } }
2132
2176
  ));
@@ -2142,7 +2186,7 @@ var KVService = class extends BaseService {
2142
2186
  return err(
2143
2187
  serviceError(
2144
2188
  ErrorCodes.KV_WRITE_FAILED,
2145
- `Failed to put key "${key}": ${response.status} - ${errorText}`,
2189
+ `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2146
2190
  "kv",
2147
2191
  { meta: { status: response.status, statusText: response.statusText } }
2148
2192
  )
@@ -2236,12 +2280,11 @@ var KVService = class extends BaseService {
2236
2280
  );
2237
2281
  }
2238
2282
  if (response.status === 401 || response.status === 403) {
2239
- const { resource, action } = parseAuthError(errorText);
2240
- return err(authUnauthorizedError("kv", errorText, {
2241
- status: response.status,
2242
- ...action && { requiredAction: action },
2243
- ...resource && { resource }
2244
- }));
2283
+ return this.authorizationFailure(
2284
+ `Failed to batch put ${items.length} key(s)`,
2285
+ response,
2286
+ errorText
2287
+ );
2245
2288
  }
2246
2289
  const quotaError = this.handleQuotaErrorResponse(
2247
2290
  response,
@@ -2362,13 +2405,11 @@ var KVService = class extends BaseService {
2362
2405
  );
2363
2406
  if (!response.ok) {
2364
2407
  if (response.status === 401) {
2365
- const errorText2 = await response.text();
2366
- const { resource, action } = parseAuthError(errorText2);
2367
- return err(authUnauthorizedError("kv", errorText2, {
2368
- status: response.status,
2369
- ...action && { requiredAction: action },
2370
- ...resource && { resource }
2371
- }));
2408
+ return this.authorizationFailure(
2409
+ "Failed to list keys",
2410
+ response,
2411
+ await response.text()
2412
+ );
2372
2413
  }
2373
2414
  const errorText = await response.text();
2374
2415
  return err(
@@ -2417,13 +2458,11 @@ var KVService = class extends BaseService {
2417
2458
  );
2418
2459
  if (!response.ok) {
2419
2460
  if (response.status === 401) {
2420
- const errorText2 = await response.text();
2421
- const { resource, action } = parseAuthError(errorText2);
2422
- return err(authUnauthorizedError("kv", errorText2, {
2423
- status: response.status,
2424
- ...action && { requiredAction: action },
2425
- ...resource && { resource }
2426
- }));
2461
+ return this.authorizationFailure(
2462
+ `Failed to delete key ${JSON.stringify(key)}`,
2463
+ response,
2464
+ await response.text()
2465
+ );
2427
2466
  }
2428
2467
  if (response.status === 404) {
2429
2468
  return this.classifyNotFound(response, key);
@@ -2432,7 +2471,7 @@ var KVService = class extends BaseService {
2432
2471
  if (response.status === 412) {
2433
2472
  return err(serviceError(
2434
2473
  ErrorCodes.KV_PRECONDITION_FAILED,
2435
- `KV precondition failed for key "${key}"`,
2474
+ `KV precondition failed for key ${JSON.stringify(key)}`,
2436
2475
  "kv",
2437
2476
  { meta: { status: response.status, statusText: response.statusText } }
2438
2477
  ));
@@ -2440,7 +2479,7 @@ var KVService = class extends BaseService {
2440
2479
  if (response.status === 503 && options?.ifMatch !== void 0) {
2441
2480
  return err(serviceError(
2442
2481
  ErrorCodes.KV_CONFLICT,
2443
- `Concurrent KV delete conflicted for key "${key}"`,
2482
+ `Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
2444
2483
  "kv",
2445
2484
  { meta: { status: response.status, statusText: response.statusText } }
2446
2485
  ));
@@ -2448,7 +2487,7 @@ var KVService = class extends BaseService {
2448
2487
  return err(
2449
2488
  serviceError(
2450
2489
  ErrorCodes.NETWORK_ERROR,
2451
- `Failed to delete key "${key}": ${response.status} - ${errorText}`,
2490
+ `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2452
2491
  "kv",
2453
2492
  { meta: { status: response.status, statusText: response.statusText } }
2454
2493
  )
@@ -2481,13 +2520,11 @@ var KVService = class extends BaseService {
2481
2520
  );
2482
2521
  if (!response.ok) {
2483
2522
  if (response.status === 401) {
2484
- const errorText2 = await response.text();
2485
- const { resource, action } = parseAuthError(errorText2);
2486
- return err(authUnauthorizedError("kv", errorText2, {
2487
- status: response.status,
2488
- ...action && { requiredAction: action },
2489
- ...resource && { resource }
2490
- }));
2523
+ return this.authorizationFailure(
2524
+ `Failed to get metadata for key ${JSON.stringify(key)}`,
2525
+ response,
2526
+ await response.text()
2527
+ );
2491
2528
  }
2492
2529
  if (response.status === 404) {
2493
2530
  return this.classifyNotFound(response, key);
@@ -2496,7 +2533,7 @@ var KVService = class extends BaseService {
2496
2533
  return err(
2497
2534
  serviceError(
2498
2535
  ErrorCodes.NETWORK_ERROR,
2499
- `Failed to get metadata for key "${key}": ${response.status} - ${errorText}`,
2536
+ `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2500
2537
  "kv",
2501
2538
  { meta: { status: response.status, statusText: response.statusText } }
2502
2539
  )
@@ -3978,10 +4015,14 @@ async function responseError(service, message, response) {
3978
4015
  }
3979
4016
  } catch {
3980
4017
  }
3981
- return wrapError(
4018
+ const error = wrapError(
3982
4019
  service,
3983
4020
  new Error(`${message}: ${response.status} ${detail}`)
3984
4021
  );
4022
+ return {
4023
+ ...error,
4024
+ meta: { ...error.meta, status: response.status, statusText: response.statusText }
4025
+ };
3985
4026
  }
3986
4027
  function isAbortError(error) {
3987
4028
  return error instanceof DOMException && error.name === "AbortError";
@@ -7033,6 +7074,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
7033
7074
  authExpiredError,
7034
7075
  authRequiredError,
7035
7076
  authUnauthorizedError,
7077
+ authorizationVerdictOf,
7036
7078
  base64Decode,
7037
7079
  base64Encode,
7038
7080
  buildCanonicalDecryptRequest,