@tinycloud/sdk-services 3.2.6-beta.2 → 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 (65) hide show
  1. package/dist/{BaseService-4wwJq1Gn.d.cts → BaseService-Cc6Ueicm.d.cts} +1 -1
  2. package/dist/{BaseService-BGr2slUz.d.ts → BaseService-DOdXd7sr.d.ts} +1 -1
  3. package/dist/encryption/index.cjs +10 -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 +10 -0
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +109 -64
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +38 -12
  12. package/dist/index.d.ts +38 -12
  13. package/dist/index.js +108 -64
  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 +68 -21
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +7 -4
  20. package/dist/kv/index.d.ts +7 -4
  21. package/dist/kv/index.js +68 -21
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/contractConstants.cjs +115 -0
  24. package/dist/kv/replication/contractConstants.cjs.map +1 -0
  25. package/dist/kv/replication/contractConstants.d.cts +77 -0
  26. package/dist/kv/replication/contractConstants.d.ts +77 -0
  27. package/dist/kv/replication/contractConstants.js +75 -0
  28. package/dist/kv/replication/contractConstants.js.map +1 -0
  29. package/dist/kv/replication/grantPolicy.cjs +82 -0
  30. package/dist/kv/replication/grantPolicy.cjs.map +1 -0
  31. package/dist/kv/replication/grantPolicy.d.cts +19 -0
  32. package/dist/kv/replication/grantPolicy.d.ts +19 -0
  33. package/dist/kv/replication/grantPolicy.js +50 -0
  34. package/dist/kv/replication/grantPolicy.js.map +1 -0
  35. package/dist/kv/replication/grantPolicyConstants.cjs +42 -0
  36. package/dist/kv/replication/grantPolicyConstants.cjs.map +1 -0
  37. package/dist/kv/replication/grantPolicyConstants.d.cts +10 -0
  38. package/dist/kv/replication/grantPolicyConstants.d.ts +10 -0
  39. package/dist/kv/replication/grantPolicyConstants.js +14 -0
  40. package/dist/kv/replication/grantPolicyConstants.js.map +1 -0
  41. package/dist/kv/replication/grantPolicyTypes.cjs +19 -0
  42. package/dist/kv/replication/grantPolicyTypes.cjs.map +1 -0
  43. package/dist/kv/replication/grantPolicyTypes.d.cts +87 -0
  44. package/dist/kv/replication/grantPolicyTypes.d.ts +87 -0
  45. package/dist/kv/replication/grantPolicyTypes.js +1 -0
  46. package/dist/kv/replication/grantPolicyTypes.js.map +1 -0
  47. package/dist/kv/replication/index.cjs +575 -131
  48. package/dist/kv/replication/index.cjs.map +1 -1
  49. package/dist/kv/replication/index.d.cts +6 -3
  50. package/dist/kv/replication/index.d.ts +6 -3
  51. package/dist/kv/replication/index.js +575 -131
  52. package/dist/kv/replication/index.js.map +1 -1
  53. package/dist/scope-B-m6cKH-.d.ts +1962 -0
  54. package/dist/scope-B7oeKdLM.d.cts +1962 -0
  55. package/dist/sql/index.cjs +72 -36
  56. package/dist/sql/index.cjs.map +1 -1
  57. package/dist/sql/index.d.cts +2 -2
  58. package/dist/sql/index.d.ts +2 -2
  59. package/dist/sql/index.js +72 -36
  60. package/dist/sql/index.js.map +1 -1
  61. package/dist/{types-D-9A3Sut.d.cts → types-BjH0ZtGy.d.cts} +8 -0
  62. package/dist/{types-D-9A3Sut.d.ts → types-BjH0ZtGy.d.ts} +8 -0
  63. package/package.json +55 -2
  64. package/dist/scope-CoKKmMiI.d.ts +0 -957
  65. package/dist/scope-D7wmjbMH.d.cts +0 -957
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",
@@ -233,6 +242,8 @@ var TelemetryEvents = {
233
242
  REPLICATION_SYNC: "replication.sync",
234
243
  REPLICATION_STATE: "replication.state",
235
244
  REPLICATION_DIVERGENCE: "replication.divergence",
245
+ REPLICATION_GRANT: "replication.grant",
246
+ REPLICATION_LIFECYCLE: "replication.lifecycle",
236
247
  /** Emitted once, on the first write rejected because storage is full. */
237
248
  STORAGE_FULL: "storage.full"
238
249
  };
@@ -269,21 +280,27 @@ var SAFE_NUMBER_FIELDS = /* @__PURE__ */ new Set([
269
280
  "fetched",
270
281
  "pages",
271
282
  "lagMs",
272
- "pendingCleared"
283
+ "pendingCleared",
284
+ "partitions",
285
+ "remaining",
286
+ "expiryMs"
273
287
  ]);
274
- var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted"]);
288
+ var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted", "purgePending"]);
275
289
  var SAFE_REPLICATION_FIELDS = {
276
290
  source: ["replica", "network", "none"],
277
- reason: ["hit", "absent", "deleted", "content_missing", "coverage_incomplete", "not_covered", "pending_write", "grant_missing", "grant_expired", "grant_revoked", "grant_not_yet_valid", "stale", "replica_error", "runtime_unsupported", "replica_unavailable", "unsupported_option", "network_cursor", "cursor_restart", "aborted", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED"],
291
+ reason: ["hit", "absent", "deleted", "content_missing", "coverage_incomplete", "not_covered", "pending_write", "grant_missing", "grant_expired", "grant_revoked", "grant_not_yet_valid", "stale", "replica_error", "runtime_unsupported", "replica_unavailable", "unsupported_option", "network_cursor", "cursor_restart", "aborted", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED", "replica_gated", "sign_out", "account_switch", "consent_expired", "startup"],
278
292
  op: ["get", "list", "put", "delete", "batchPut"],
279
- trigger: ["start", "interval", "stale_read", "manual"],
293
+ trigger: ["start", "interval", "stale_read", "manual", "peer"],
294
+ origin: ["peer"],
280
295
  outcome: ["found", "not_found", "error", "committed", "failed", "ambiguous", "ok", "busy", "aborted"],
281
- class: ["offline", "authority", "storage", "node"],
282
- state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready"],
296
+ class: ["offline", "authority", "storage", "node", "lifecycle"],
297
+ state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready", "gated"],
298
+ action: ["minted", "renewed", "renewal_refused", "renewal_deferred", "capped", "expired", "consent_deadline_reached", "denied", "deny_stale", "deny_failed", "database_deleted", "marker_set", "marker_cleared", "unguarded", "drained", "erased", "erase_failed", "activation_blocked", "activated", "recovered"],
299
+ gate: ["not_established", "stamp_stale", "principal_mismatch", "purge_pending", "logout_pending", "gate_unavailable"],
283
300
  kind: ["value", "missing_local", "extra_local", "keys", "order"],
284
301
  coverage: ["empty", "bootstrapping", "complete"],
285
302
  authority: ["valid", "expired", "revoked", "not-yet-valid"],
286
- code: ["NETWORK_ERROR", "TIMEOUT", "ABORTED", "REPLICA_BUSY", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED", "INTEGRITY_ERROR", "STORAGE_ERROR", "REPLICA_CLOSED", "RUNTIME_UNSUPPORTED", "GRANT_EXPIRED", "GRANT_REVOKED", "GRANT_NOT_YET_VALID", "GRANT_MISSING", "GRANT_INVALID", "SOURCE_CHANGED", "PROTOCOL_ERROR", "SCOPE_VIOLATION", "NODE_ERROR", "CONTENT_MISMATCH", "STORAGE_FULL"],
303
+ code: ["NETWORK_ERROR", "TIMEOUT", "ABORTED", "REPLICA_BUSY", "REPLICA_BEHIND_OWN_WRITES", "REPLICA_UNPROVEN_SINCE_START", "NETWORK_REQUESTED", "INTEGRITY_ERROR", "STORAGE_ERROR", "REPLICA_CLOSED", "RUNTIME_UNSUPPORTED", "GRANT_EXPIRED", "GRANT_REVOKED", "GRANT_NOT_YET_VALID", "GRANT_MISSING", "GRANT_INVALID", "SOURCE_CHANGED", "PROTOCOL_ERROR", "SCOPE_VIOLATION", "NODE_ERROR", "CONTENT_MISMATCH", "STORAGE_FULL", "REPLICA_GATED", "RESET_REQUIRED", "STORAGE_BUDGET", "CONSENT_EXPIRED"],
287
304
  pendingState: ["in_flight", "committed", "ambiguous"],
288
305
  strategy: ["session", "minted", "installed"],
289
306
  verify: ["match", "diverged", "error", "aborted"]
@@ -1115,6 +1132,42 @@ var ServiceContext = class {
1115
1132
  }
1116
1133
  };
1117
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
+
1118
1171
  // src/errors.ts
1119
1172
  var import_bootstrap = require("@tinycloud/bootstrap");
1120
1173
 
@@ -1274,6 +1327,30 @@ function networkError(service, message, cause) {
1274
1327
  cause
1275
1328
  };
1276
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
+ }
1277
1354
  var RequestTimeoutError = class extends Error {
1278
1355
  constructor(timeoutMs) {
1279
1356
  super(`Request timed out after ${timeoutMs}ms.`);
@@ -2612,9 +2689,12 @@ var KVService = class extends BaseService {
2612
2689
  { meta: { status: response.status, statusText: response.statusText } }
2613
2690
  ));
2614
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);
2615
2695
  return err(serviceError(
2616
2696
  ErrorCodes.NETWORK_ERROR,
2617
- `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2697
+ message,
2618
2698
  "kv",
2619
2699
  { meta: { status: response.status, statusText: response.statusText } }
2620
2700
  ));
@@ -2696,19 +2776,14 @@ var KVService = class extends BaseService {
2696
2776
  { meta: { status: response.status, statusText: response.statusText } }
2697
2777
  ));
2698
2778
  }
2699
- if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
2700
- return err(serviceError(
2701
- ErrorCodes.KV_CONFLICT,
2702
- `Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
2703
- "kv",
2704
- { meta: { status: response.status, statusText: response.statusText } }
2705
- ));
2706
- }
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);
2707
2782
  const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
2708
2783
  if (quotaError) return quotaError;
2709
2784
  return err(serviceError(
2710
2785
  ErrorCodes.KV_WRITE_FAILED,
2711
- `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2786
+ message,
2712
2787
  "kv",
2713
2788
  { meta: { status: response.status, statusText: response.statusText } }
2714
2789
  ));
@@ -3008,9 +3083,12 @@ var KVService = class extends BaseService {
3008
3083
  );
3009
3084
  }
3010
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);
3011
3089
  return err(serviceError(
3012
3090
  ErrorCodes.NETWORK_ERROR,
3013
- `Failed to list keys: ${response.status} - ${errorText}`,
3091
+ message,
3014
3092
  "kv",
3015
3093
  { meta: { status: response.status, statusText: response.statusText } }
3016
3094
  ));
@@ -3261,17 +3339,12 @@ var KVService = class extends BaseService {
3261
3339
  { meta: { status: response.status, statusText: response.statusText } }
3262
3340
  ));
3263
3341
  }
3264
- if (response.status === 503 && options?.ifMatch !== void 0) {
3265
- return err(serviceError(
3266
- ErrorCodes.KV_CONFLICT,
3267
- `Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
3268
- "kv",
3269
- { meta: { status: response.status, statusText: response.statusText } }
3270
- ));
3271
- }
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);
3272
3345
  return err(serviceError(
3273
3346
  ErrorCodes.NETWORK_ERROR,
3274
- `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3347
+ message,
3275
3348
  "kv",
3276
3349
  { meta: { status: response.status, statusText: response.statusText } }
3277
3350
  ));
@@ -3312,10 +3385,13 @@ var KVService = class extends BaseService {
3312
3385
  return await this.classifyNotFound(response, key);
3313
3386
  }
3314
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);
3315
3391
  return err(
3316
3392
  serviceError(
3317
3393
  ErrorCodes.NETWORK_ERROR,
3318
- `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3394
+ message,
3319
3395
  "kv",
3320
3396
  { meta: { status: response.status, statusText: response.statusText } }
3321
3397
  )
@@ -3507,42 +3583,6 @@ function requiresSecretsOptIn(space, prefix) {
3507
3583
  return prefix.split("/", 1)[0] === "vault";
3508
3584
  }
3509
3585
 
3510
- // src/responseErrors.ts
3511
- function parseServiceErrorBody(errorText) {
3512
- try {
3513
- return JSON.parse(errorText);
3514
- } catch {
3515
- return {};
3516
- }
3517
- }
3518
- function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
3519
- if (parsed.message) {
3520
- return compactMessage(parsed.message);
3521
- }
3522
- if (looksLikeHtml(errorText)) {
3523
- if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
3524
- return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
3525
- }
3526
- return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
3527
- }
3528
- return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
3529
- }
3530
- function responseErrorMeta(status, statusText, errorText) {
3531
- const meta = { status, statusText };
3532
- const snippet = compactMessage(errorText);
3533
- if (snippet) {
3534
- meta.responseSnippet = snippet.slice(0, 300);
3535
- }
3536
- return meta;
3537
- }
3538
- function looksLikeHtml(text) {
3539
- return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
3540
- }
3541
- function compactMessage(message) {
3542
- const compact = message.replace(/\s+/g, " ").trim();
3543
- return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
3544
- }
3545
-
3546
3586
  // src/sql/SQLMigrations.ts
3547
3587
  var SQLMigrations = class {
3548
3588
  constructor(service, dbName) {
@@ -3958,6 +3998,8 @@ var SQLService = class extends BaseService {
3958
3998
  if (response.status === 413 && parseStorageRejection(errorText)) {
3959
3999
  return err(storageRejectionError("sql", 413, meta, errorText));
3960
4000
  }
4001
+ const retryError = nodeRetryError("sql", `SQL ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
4002
+ if (retryError) return err(retryError);
3961
4003
  const errorBody = parseServiceErrorBody(errorText);
3962
4004
  const errorCode = this.mapHttpStatusToErrorCode(
3963
4005
  response.status,
@@ -4427,6 +4469,8 @@ var DuckDbService = class extends BaseService {
4427
4469
  if (response.status === 413 && parseStorageRejection(errorText)) {
4428
4470
  return err(storageRejectionError("duckdb", 413, meta, errorText));
4429
4471
  }
4472
+ const retryError = nodeRetryError("duckdb", `DuckDB ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
4473
+ if (retryError) return err(retryError);
4430
4474
  const errorBody = parseServiceErrorBody(errorText);
4431
4475
  const errorCode = this.mapHttpStatusToErrorCode(
4432
4476
  response.status,
@@ -8468,6 +8512,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8468
8512
  kvPrefixCovers,
8469
8513
  networkDiscoveryKey,
8470
8514
  networkError,
8515
+ nodeRetryError,
8471
8516
  notFoundError,
8472
8517
  ok,
8473
8518
  openWrappedKey,