@tinycloud/sdk-services 3.2.6-beta.4 → 3.3.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.
Files changed (45) hide show
  1. package/dist/{BaseService-1Ug6y7DC.d.cts → BaseService-Cc6Ueicm.d.cts} +1 -1
  2. package/dist/{BaseService-Bn07Vudy.d.ts → BaseService-DOdXd7sr.d.ts} +1 -1
  3. package/dist/encryption/index.cjs +8 -0
  4. package/dist/encryption/index.cjs.map +1 -1
  5. package/dist/encryption/index.d.cts +2 -2
  6. package/dist/encryption/index.d.ts +2 -2
  7. package/dist/encryption/index.js +8 -0
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +94 -57
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +29 -6
  12. package/dist/index.d.ts +29 -6
  13. package/dist/index.js +93 -57
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  16. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  17. package/dist/kv/index.cjs +66 -21
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +4 -4
  20. package/dist/kv/index.d.ts +4 -4
  21. package/dist/kv/index.js +66 -21
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/grantPolicy.cjs +82 -0
  24. package/dist/kv/replication/grantPolicy.cjs.map +1 -0
  25. package/dist/kv/replication/grantPolicy.d.cts +19 -0
  26. package/dist/kv/replication/grantPolicy.d.ts +19 -0
  27. package/dist/kv/replication/grantPolicy.js +50 -0
  28. package/dist/kv/replication/grantPolicy.js.map +1 -0
  29. package/dist/kv/replication/index.cjs +571 -128
  30. package/dist/kv/replication/index.cjs.map +1 -1
  31. package/dist/kv/replication/index.d.cts +3 -3
  32. package/dist/kv/replication/index.d.ts +3 -3
  33. package/dist/kv/replication/index.js +571 -128
  34. package/dist/kv/replication/index.js.map +1 -1
  35. package/dist/{scope-BnyVPZ5E.d.ts → scope-B-m6cKH-.d.ts} +310 -7
  36. package/dist/{scope-CQ5kmHlt.d.cts → scope-B7oeKdLM.d.cts} +310 -7
  37. package/dist/sql/index.cjs +70 -36
  38. package/dist/sql/index.cjs.map +1 -1
  39. package/dist/sql/index.d.cts +2 -2
  40. package/dist/sql/index.d.ts +2 -2
  41. package/dist/sql/index.js +70 -36
  42. package/dist/sql/index.js.map +1 -1
  43. package/dist/{types-gmdUiorz.d.cts → types-BjH0ZtGy.d.cts} +6 -0
  44. package/dist/{types-gmdUiorz.d.ts → types-BjH0ZtGy.d.ts} +6 -0
  45. package/package.json +16 -2
package/dist/index.cjs CHANGED
@@ -122,6 +122,7 @@ __export(src_exports, {
122
122
  kvPrefixCovers: () => kvPrefixCovers,
123
123
  networkDiscoveryKey: () => networkDiscoveryKey,
124
124
  networkError: () => networkError,
125
+ nodeRetryError: () => nodeRetryError,
125
126
  notFoundError: () => notFoundError,
126
127
  ok: () => ok,
127
128
  openWrappedKey: () => openWrappedKey,
@@ -179,6 +180,14 @@ var ErrorCodes = {
179
180
  ABORTED: "ABORTED",
180
181
  INVALID_INPUT: "INVALID_INPUT",
181
182
  PERMISSION_DENIED: "PERMISSION_DENIED",
183
+ // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
184
+ // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
185
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
186
+ NODE_BUSY: "NODE_BUSY",
187
+ /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
188
+ REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
189
+ /** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
190
+ OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
182
191
  // KV-specific errors
183
192
  KV_NOT_FOUND: "KV_NOT_FOUND",
184
193
  KV_WRITE_FAILED: "KV_WRITE_FAILED",
@@ -1123,6 +1132,42 @@ var ServiceContext = class {
1123
1132
  }
1124
1133
  };
1125
1134
 
1135
+ // src/responseErrors.ts
1136
+ function parseServiceErrorBody(errorText) {
1137
+ try {
1138
+ return JSON.parse(errorText);
1139
+ } catch {
1140
+ return {};
1141
+ }
1142
+ }
1143
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
1144
+ if (parsed.message) {
1145
+ return compactMessage(parsed.message);
1146
+ }
1147
+ if (looksLikeHtml(errorText)) {
1148
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
1149
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
1150
+ }
1151
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
1152
+ }
1153
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
1154
+ }
1155
+ function responseErrorMeta(status, statusText, errorText) {
1156
+ const meta = { status, statusText };
1157
+ const snippet = compactMessage(errorText);
1158
+ if (snippet) {
1159
+ meta.responseSnippet = snippet.slice(0, 300);
1160
+ }
1161
+ return meta;
1162
+ }
1163
+ function looksLikeHtml(text) {
1164
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
1165
+ }
1166
+ function compactMessage(message) {
1167
+ const compact = message.replace(/\s+/g, " ").trim();
1168
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
1169
+ }
1170
+
1126
1171
  // src/errors.ts
1127
1172
  var import_bootstrap = require("@tinycloud/bootstrap");
1128
1173
 
@@ -1282,6 +1327,30 @@ function networkError(service, message, cause) {
1282
1327
  cause
1283
1328
  };
1284
1329
  }
1330
+ function nodeRetryError(service, message, response, errorText) {
1331
+ const body = errorText.trim();
1332
+ const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
1333
+ let code;
1334
+ if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
1335
+ code = ErrorCodes.NODE_BUSY;
1336
+ } else if (response.status === 503 && token === "request_timeout") {
1337
+ code = ErrorCodes.REQUEST_TIMEOUT;
1338
+ } else if (response.status === 500 && token === "outcome_unknown") {
1339
+ code = ErrorCodes.OUTCOME_UNKNOWN;
1340
+ } else {
1341
+ return void 0;
1342
+ }
1343
+ const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;
1344
+ const retryAfter = response.headers.get("retry-after")?.trim();
1345
+ return serviceError(code, message, service, {
1346
+ meta: {
1347
+ ...responseErrorMeta(response.status, response.statusText, errorText),
1348
+ retryable,
1349
+ ...token === "sql_busy" && stage ? { stage } : {},
1350
+ ...retryable && retryAfter && /^\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1e3 } : {}
1351
+ }
1352
+ });
1353
+ }
1285
1354
  var RequestTimeoutError = class extends Error {
1286
1355
  constructor(timeoutMs) {
1287
1356
  super(`Request timed out after ${timeoutMs}ms.`);
@@ -2620,9 +2689,12 @@ var KVService = class extends BaseService {
2620
2689
  { meta: { status: response.status, statusText: response.statusText } }
2621
2690
  ));
2622
2691
  }
2692
+ const message = `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
2693
+ const retryError = nodeRetryError("kv", message, response, errorText);
2694
+ if (retryError) return err(retryError);
2623
2695
  return err(serviceError(
2624
2696
  ErrorCodes.NETWORK_ERROR,
2625
- `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2697
+ message,
2626
2698
  "kv",
2627
2699
  { meta: { status: response.status, statusText: response.statusText } }
2628
2700
  ));
@@ -2704,19 +2776,14 @@ var KVService = class extends BaseService {
2704
2776
  { meta: { status: response.status, statusText: response.statusText } }
2705
2777
  ));
2706
2778
  }
2707
- if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
2708
- return err(serviceError(
2709
- ErrorCodes.KV_CONFLICT,
2710
- `Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
2711
- "kv",
2712
- { meta: { status: response.status, statusText: response.statusText } }
2713
- ));
2714
- }
2779
+ const message = `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
2780
+ const retryError = nodeRetryError("kv", message, response, errorText);
2781
+ if (retryError) return err(retryError);
2715
2782
  const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
2716
2783
  if (quotaError) return quotaError;
2717
2784
  return err(serviceError(
2718
2785
  ErrorCodes.KV_WRITE_FAILED,
2719
- `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2786
+ message,
2720
2787
  "kv",
2721
2788
  { meta: { status: response.status, statusText: response.statusText } }
2722
2789
  ));
@@ -3016,9 +3083,12 @@ var KVService = class extends BaseService {
3016
3083
  );
3017
3084
  }
3018
3085
  const errorText = await response.text();
3086
+ const message = `Failed to list keys: ${response.status} - ${errorText}`;
3087
+ const retryError = nodeRetryError("kv", message, response, errorText);
3088
+ if (retryError) return err(retryError);
3019
3089
  return err(serviceError(
3020
3090
  ErrorCodes.NETWORK_ERROR,
3021
- `Failed to list keys: ${response.status} - ${errorText}`,
3091
+ message,
3022
3092
  "kv",
3023
3093
  { meta: { status: response.status, statusText: response.statusText } }
3024
3094
  ));
@@ -3269,17 +3339,12 @@ var KVService = class extends BaseService {
3269
3339
  { meta: { status: response.status, statusText: response.statusText } }
3270
3340
  ));
3271
3341
  }
3272
- if (response.status === 503 && options?.ifMatch !== void 0) {
3273
- return err(serviceError(
3274
- ErrorCodes.KV_CONFLICT,
3275
- `Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
3276
- "kv",
3277
- { meta: { status: response.status, statusText: response.statusText } }
3278
- ));
3279
- }
3342
+ const message = `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
3343
+ const retryError = nodeRetryError("kv", message, response, errorText);
3344
+ if (retryError) return err(retryError);
3280
3345
  return err(serviceError(
3281
3346
  ErrorCodes.NETWORK_ERROR,
3282
- `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3347
+ message,
3283
3348
  "kv",
3284
3349
  { meta: { status: response.status, statusText: response.statusText } }
3285
3350
  ));
@@ -3320,10 +3385,13 @@ var KVService = class extends BaseService {
3320
3385
  return await this.classifyNotFound(response, key);
3321
3386
  }
3322
3387
  const errorText = await response.text();
3388
+ const message = `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
3389
+ const retryError = nodeRetryError("kv", message, response, errorText);
3390
+ if (retryError) return err(retryError);
3323
3391
  return err(
3324
3392
  serviceError(
3325
3393
  ErrorCodes.NETWORK_ERROR,
3326
- `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3394
+ message,
3327
3395
  "kv",
3328
3396
  { meta: { status: response.status, statusText: response.statusText } }
3329
3397
  )
@@ -3515,42 +3583,6 @@ function requiresSecretsOptIn(space, prefix) {
3515
3583
  return prefix.split("/", 1)[0] === "vault";
3516
3584
  }
3517
3585
 
3518
- // src/responseErrors.ts
3519
- function parseServiceErrorBody(errorText) {
3520
- try {
3521
- return JSON.parse(errorText);
3522
- } catch {
3523
- return {};
3524
- }
3525
- }
3526
- function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
3527
- if (parsed.message) {
3528
- return compactMessage(parsed.message);
3529
- }
3530
- if (looksLikeHtml(errorText)) {
3531
- if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
3532
- return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
3533
- }
3534
- return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
3535
- }
3536
- return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
3537
- }
3538
- function responseErrorMeta(status, statusText, errorText) {
3539
- const meta = { status, statusText };
3540
- const snippet = compactMessage(errorText);
3541
- if (snippet) {
3542
- meta.responseSnippet = snippet.slice(0, 300);
3543
- }
3544
- return meta;
3545
- }
3546
- function looksLikeHtml(text) {
3547
- return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
3548
- }
3549
- function compactMessage(message) {
3550
- const compact = message.replace(/\s+/g, " ").trim();
3551
- return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
3552
- }
3553
-
3554
3586
  // src/sql/SQLMigrations.ts
3555
3587
  var SQLMigrations = class {
3556
3588
  constructor(service, dbName) {
@@ -3966,6 +3998,8 @@ var SQLService = class extends BaseService {
3966
3998
  if (response.status === 413 && parseStorageRejection(errorText)) {
3967
3999
  return err(storageRejectionError("sql", 413, meta, errorText));
3968
4000
  }
4001
+ const retryError = nodeRetryError("sql", `SQL ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
4002
+ if (retryError) return err(retryError);
3969
4003
  const errorBody = parseServiceErrorBody(errorText);
3970
4004
  const errorCode = this.mapHttpStatusToErrorCode(
3971
4005
  response.status,
@@ -4435,6 +4469,8 @@ var DuckDbService = class extends BaseService {
4435
4469
  if (response.status === 413 && parseStorageRejection(errorText)) {
4436
4470
  return err(storageRejectionError("duckdb", 413, meta, errorText));
4437
4471
  }
4472
+ const retryError = nodeRetryError("duckdb", `DuckDB ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
4473
+ if (retryError) return err(retryError);
4438
4474
  const errorBody = parseServiceErrorBody(errorText);
4439
4475
  const errorCode = this.mapHttpStatusToErrorCode(
4440
4476
  response.status,
@@ -8476,6 +8512,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8476
8512
  kvPrefixCovers,
8477
8513
  networkDiscoveryKey,
8478
8514
  networkError,
8515
+ nodeRetryError,
8479
8516
  notFoundError,
8480
8517
  ok,
8481
8518
  openWrappedKey,