@tinycloud/sdk-services 3.2.6-beta.2 → 3.3.0-beta.11

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 +360 -210
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +116 -13
  12. package/dist/index.d.ts +116 -13
  13. package/dist/index.js +359 -210
  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
@@ -84,8 +84,8 @@ __export(src_exports, {
84
84
  authRequiredError: () => authRequiredError,
85
85
  authUnauthorizedError: () => authUnauthorizedError,
86
86
  authorizationVerdictOf: () => authorizationVerdictOf,
87
- base64Decode: () => base64Decode2,
88
- base64Encode: () => base64Encode2,
87
+ base64Decode: () => base64Decode,
88
+ base64Encode: () => base64Encode,
89
89
  buildCanonicalDecryptRequest: () => buildCanonicalDecryptRequest,
90
90
  buildDecryptAttenuation: () => buildDecryptAttenuation,
91
91
  buildDecryptFacts: () => buildDecryptFacts,
@@ -115,13 +115,14 @@ __export(src_exports, {
115
115
  generateRandomReceiverKey: () => generateRandomReceiverKey,
116
116
  getTinyCloudDebugLogs: () => getTinyCloudDebugLogs,
117
117
  hexDecode: () => hexDecode,
118
- hexEncode: () => hexEncode2,
118
+ hexEncode: () => hexEncode,
119
119
  installTinyCloudDebugGlobals: () => installTinyCloudDebugGlobals,
120
120
  isNetworkId: () => isNetworkId,
121
121
  isStorageFullError: () => isStorageFullError,
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,
@@ -5634,6 +5678,116 @@ var TinyCloudQuota = class {
5634
5678
  }
5635
5679
  };
5636
5680
 
5681
+ // src/encryption/canonical.ts
5682
+ function canonicalize(value) {
5683
+ if (value === void 0) {
5684
+ return "";
5685
+ }
5686
+ return stringify(value);
5687
+ }
5688
+ function stringify(value) {
5689
+ if (value === null) return "null";
5690
+ switch (typeof value) {
5691
+ case "boolean":
5692
+ case "number":
5693
+ return JSON.stringify(value);
5694
+ case "string":
5695
+ return JSON.stringify(value);
5696
+ case "object": {
5697
+ if (Array.isArray(value)) {
5698
+ return `[${value.map(stringify).join(",")}]`;
5699
+ }
5700
+ const keys = Object.keys(value).sort();
5701
+ const parts = [];
5702
+ for (const k of keys) {
5703
+ const v = value[k];
5704
+ if (v === void 0) continue;
5705
+ parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
5706
+ }
5707
+ return `{${parts.join(",")}}`;
5708
+ }
5709
+ default:
5710
+ throw new TypeError(
5711
+ `canonicalize: unsupported value type ${typeof value}`
5712
+ );
5713
+ }
5714
+ }
5715
+ var HEX = "0123456789abcdef";
5716
+ function hexEncode(bytes) {
5717
+ let out = "";
5718
+ for (let i = 0; i < bytes.length; i++) {
5719
+ const b = bytes[i];
5720
+ out += HEX[b >> 4 & 15] + HEX[b & 15];
5721
+ }
5722
+ return out;
5723
+ }
5724
+ function hexDecode(hex) {
5725
+ if (hex.length % 2 !== 0) {
5726
+ throw new Error("hex string must have even length");
5727
+ }
5728
+ const out = new Uint8Array(hex.length / 2);
5729
+ for (let i = 0; i < out.length; i++) {
5730
+ const hi = parseInt(hex[i * 2], 16);
5731
+ const lo = parseInt(hex[i * 2 + 1], 16);
5732
+ if (Number.isNaN(hi) || Number.isNaN(lo)) {
5733
+ throw new Error("invalid hex character");
5734
+ }
5735
+ out[i] = hi << 4 | lo;
5736
+ }
5737
+ return out;
5738
+ }
5739
+ function base64Encode(bytes) {
5740
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5741
+ let out = "";
5742
+ for (let i = 0; i < bytes.length; i += 3) {
5743
+ const b0 = bytes[i];
5744
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
5745
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
5746
+ out += chars[b0 >> 2 & 63];
5747
+ out += chars[(b0 << 4 | b1 >> 4) & 63];
5748
+ out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
5749
+ out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
5750
+ }
5751
+ return out;
5752
+ }
5753
+ function base64Decode(s) {
5754
+ const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
5755
+ const len = clean.length;
5756
+ if (len % 4 !== 0) {
5757
+ throw new Error("invalid base64 input");
5758
+ }
5759
+ const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
5760
+ const outLen = len / 4 * 3 - padding;
5761
+ const out = new Uint8Array(outLen);
5762
+ const lookup = {};
5763
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5764
+ for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
5765
+ let outIdx = 0;
5766
+ for (let i = 0; i < len; i += 4) {
5767
+ const v0 = lookup[clean[i]] ?? 0;
5768
+ const v1 = lookup[clean[i + 1]] ?? 0;
5769
+ const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
5770
+ const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
5771
+ const b0 = v0 << 2 | v1 >> 4;
5772
+ const b1 = (v1 & 15) << 4 | v2 >> 2;
5773
+ const b2 = (v2 & 3) << 6 | v3;
5774
+ if (outIdx < outLen) out[outIdx++] = b0;
5775
+ if (outIdx < outLen) out[outIdx++] = b1;
5776
+ if (outIdx < outLen) out[outIdx++] = b2;
5777
+ }
5778
+ return out;
5779
+ }
5780
+ function utf8Encode(s) {
5781
+ return new TextEncoder().encode(s);
5782
+ }
5783
+ function utf8Decode(b) {
5784
+ return new TextDecoder().decode(b);
5785
+ }
5786
+ function canonicalHashHex(sha256, value) {
5787
+ const canonical = canonicalize(value);
5788
+ return hexEncode(sha256(utf8Encode(canonical)));
5789
+ }
5790
+
5637
5791
  // src/vault/types.ts
5638
5792
  var VaultPublicSpaceKVActions = [
5639
5793
  "tinycloud.kv/get",
@@ -5803,7 +5957,7 @@ function toBytes(str) {
5803
5957
  function fromBytes(bytes) {
5804
5958
  return new TextDecoder().decode(bytes);
5805
5959
  }
5806
- function hexEncode(bytes) {
5960
+ function hexEncode2(bytes) {
5807
5961
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5808
5962
  }
5809
5963
  function concatBytes(...arrays) {
@@ -5816,14 +5970,14 @@ function concatBytes(...arrays) {
5816
5970
  }
5817
5971
  return result;
5818
5972
  }
5819
- function base64Encode(bytes) {
5973
+ function base64Encode2(bytes) {
5820
5974
  let binary = "";
5821
5975
  for (let i = 0; i < bytes.length; i++) {
5822
5976
  binary += String.fromCharCode(bytes[i]);
5823
5977
  }
5824
5978
  return btoa(binary);
5825
5979
  }
5826
- function base64Decode(str) {
5980
+ function base64Decode2(str) {
5827
5981
  const binary = atob(str);
5828
5982
  const bytes = new Uint8Array(binary.length);
5829
5983
  for (let i = 0; i < binary.length; i++) {
@@ -5865,6 +6019,11 @@ function defaultVaultMessage(input) {
5865
6019
  return input.message;
5866
6020
  case "PERMISSION_DENIED":
5867
6021
  return input.message;
6022
+ case "AAD_MISMATCH":
6023
+ case "ETAG_MISMATCH":
6024
+ case "INVALID_ETAG":
6025
+ case "OUTCOME_UNKNOWN":
6026
+ case "NODE_BUSY":
5868
6027
  case "STORAGE_QUOTA_EXCEEDED":
5869
6028
  case "STORAGE_LIMIT_REACHED":
5870
6029
  return input.message;
@@ -5887,6 +6046,37 @@ function typedKvCause(error) {
5887
6046
  ...error.cause !== void 0 ? { cause: error.cause } : {}
5888
6047
  });
5889
6048
  }
6049
+ function canonicalAadSpaceId(spaceId) {
6050
+ return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
6051
+ }
6052
+ function checkEnvelopeAad(value, spaceId, key) {
6053
+ const e = value;
6054
+ if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
6055
+ (field) => typeof e[field] === "string" && e[field] !== ""
6056
+ ) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
6057
+ return "corrupt";
6058
+ }
6059
+ let aad = "";
6060
+ try {
6061
+ if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
6062
+ } catch {
6063
+ }
6064
+ const suffix = `:${key}`;
6065
+ return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
6066
+ }
6067
+ var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
6068
+ function ifMatchWriteError(key, error) {
6069
+ const status = error.meta?.status;
6070
+ const code = error.code === ErrorCodes.KV_PRECONDITION_FAILED ? "ETAG_MISMATCH" : error.code === ErrorCodes.KV_WRITE_FAILED && status === 400 ? "INVALID_ETAG" : error.code === ErrorCodes.OUTCOME_UNKNOWN || error.code === ErrorCodes.KV_WRITE_FAILED && typeof status === "number" && AMBIGUOUS_WRITE_STATUSES.includes(status) || (error.code === ErrorCodes.NETWORK_ERROR || error.code === ErrorCodes.TIMEOUT) && status === void 0 && error.meta?.requestMayHaveDispatched !== false ? "OUTCOME_UNKNOWN" : error.code === ErrorCodes.NODE_BUSY || error.code === ErrorCodes.REQUEST_TIMEOUT ? "NODE_BUSY" : void 0;
6071
+ if (code === void 0) return void 0;
6072
+ return vaultError({
6073
+ code,
6074
+ key,
6075
+ message: error.message,
6076
+ ...error.meta !== void 0 ? { meta: error.meta } : {},
6077
+ cause: typedKvCause(error)
6078
+ });
6079
+ }
5890
6080
  function kvWriteError(context, error) {
5891
6081
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
5892
6082
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
@@ -6073,7 +6263,7 @@ var DataVaultService = class extends BaseService {
6073
6263
  }
6074
6264
  if (existingPubKey) {
6075
6265
  this.encryptionIdentity = {
6076
- publicKey: base64Decode(existingPubKey),
6266
+ publicKey: base64Decode2(existingPubKey),
6077
6267
  privateKey: new Uint8Array(0)
6078
6268
  // private key not available without signing
6079
6269
  };
@@ -6098,7 +6288,7 @@ var DataVaultService = class extends BaseService {
6098
6288
  );
6099
6289
  this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
6100
6290
  try {
6101
- const pubKeyB64 = base64Encode(this.encryptionIdentity.publicKey);
6291
+ const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
6102
6292
  await this.tc.ensurePublicSpace();
6103
6293
  await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
6104
6294
  await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
@@ -6164,6 +6354,21 @@ var DataVaultService = class extends BaseService {
6164
6354
  message: "Authentication required"
6165
6355
  });
6166
6356
  }
6357
+ if (options?.ifMatch !== void 0) {
6358
+ if (options.ifNoneMatch !== void 0) {
6359
+ return vaultError({
6360
+ code: "STORAGE_ERROR",
6361
+ cause: new Error("ifMatch cannot be combined with ifNoneMatch")
6362
+ });
6363
+ }
6364
+ if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
6365
+ return vaultError({
6366
+ code: "INVALID_ETAG",
6367
+ key,
6368
+ message: options.ifMatch.startsWith("W/") ? 'ifMatch is a weak etag (W/"..."): the node only issues strong etags, so the etag was weakened in transit (a proxy or CDN rewrote it). Read it from a connection that preserves strong ETags' : "ifMatch must be the strong, quoted ETag returned by a network read (VaultEntry.etag), passed exactly as returned"
6369
+ });
6370
+ }
6371
+ }
6167
6372
  try {
6168
6373
  const { plaintext, contentType } = this.serializeValue(value, options);
6169
6374
  const metadata = {
@@ -6186,7 +6391,8 @@ var DataVaultService = class extends BaseService {
6186
6391
  }
6187
6392
  const valuePath = `vault/${key}`;
6188
6393
  const valueBody = JSON.stringify(envelopeResult.data);
6189
- const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
6394
+ const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
6395
+ const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
6190
6396
  if (!valuePutResult.ok) {
6191
6397
  if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
6192
6398
  return vaultError({
@@ -6196,6 +6402,10 @@ var DataVaultService = class extends BaseService {
6196
6402
  cause: typedKvCause(valuePutResult.error)
6197
6403
  });
6198
6404
  }
6405
+ if (options?.ifMatch !== void 0) {
6406
+ const classified = ifMatchWriteError(key, valuePutResult.error);
6407
+ if (classified !== void 0) return classified;
6408
+ }
6199
6409
  return kvWriteError("Failed to store encrypted value", valuePutResult.error);
6200
6410
  }
6201
6411
  return { ok: true, data: void 0 };
@@ -6230,6 +6440,19 @@ var DataVaultService = class extends BaseService {
6230
6440
  }
6231
6441
  const rawEnvelope = unwrapKVData(valueResult.data);
6232
6442
  const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
6443
+ if (options?.expectAad === true) {
6444
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6445
+ if (verdict === "corrupt") {
6446
+ return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
6447
+ }
6448
+ if (verdict === "mismatch") {
6449
+ return vaultError({
6450
+ code: "AAD_MISMATCH",
6451
+ key,
6452
+ message: `Envelope at ${key} is not bound to this space and key`
6453
+ });
6454
+ }
6455
+ }
6233
6456
  const proof = await this.decryptCapabilityProof();
6234
6457
  const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6235
6458
  if (!plaintextResult.ok) {
@@ -6246,7 +6469,11 @@ var DataVaultService = class extends BaseService {
6246
6469
  contentType,
6247
6470
  options
6248
6471
  );
6249
- return { ok: true, data: { value, metadata, keyId } };
6472
+ const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
6473
+ return {
6474
+ ok: true,
6475
+ data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6476
+ };
6250
6477
  } catch (error) {
6251
6478
  return vaultError({
6252
6479
  code: "STORAGE_ERROR",
@@ -6292,6 +6519,12 @@ var DataVaultService = class extends BaseService {
6292
6519
  } catch {
6293
6520
  return { status: "corrupt_envelope" };
6294
6521
  }
6522
+ if (options?.expectAad === true) {
6523
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6524
+ if (verdict !== "ok") {
6525
+ return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
6526
+ }
6527
+ }
6295
6528
  let plaintextResult;
6296
6529
  try {
6297
6530
  const proof = await this.decryptCapabilityProof();
@@ -6323,7 +6556,11 @@ var DataVaultService = class extends BaseService {
6323
6556
  contentType,
6324
6557
  options
6325
6558
  );
6326
- return { status: "ok", entry: { value, metadata, keyId } };
6559
+ const etag = valueResult.data.headers?.etag;
6560
+ return {
6561
+ status: "ok",
6562
+ entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6563
+ };
6327
6564
  } catch {
6328
6565
  return { status: "invalid_payload" };
6329
6566
  }
@@ -6373,6 +6610,14 @@ var DataVaultService = class extends BaseService {
6373
6610
  )
6374
6611
  });
6375
6612
  }
6613
+ if (options?.ifMatch !== void 0) {
6614
+ return vaultError({
6615
+ code: "STORAGE_ERROR",
6616
+ cause: new Error(
6617
+ "ifMatch is only supported by network-encrypted vaults"
6618
+ )
6619
+ });
6620
+ }
6376
6621
  if (!this._isUnlocked || !this.masterKey) {
6377
6622
  return vaultError({
6378
6623
  code: "VAULT_LOCKED",
@@ -6398,7 +6643,7 @@ var DataVaultService = class extends BaseService {
6398
6643
  }
6399
6644
  const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
6400
6645
  const entryKey = this.crypto.randomBytes(32);
6401
- const keyId = hexEncode(this.crypto.sha256(entryKey)).slice(0, 16);
6646
+ const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
6402
6647
  const encrypted = this.crypto.encrypt(entryKey, plaintext);
6403
6648
  const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
6404
6649
  const metadata = {
@@ -6416,7 +6661,7 @@ var DataVaultService = class extends BaseService {
6416
6661
  ...metadata
6417
6662
  });
6418
6663
  const keyPayload = JSON.stringify({
6419
- key: base64Encode(keyBlob),
6664
+ key: base64Encode2(keyBlob),
6420
6665
  metadata: keyMetadata
6421
6666
  });
6422
6667
  const keyPutResult = await this.tc.kv.put(
@@ -6427,7 +6672,7 @@ var DataVaultService = class extends BaseService {
6427
6672
  return kvWriteError("Failed to store key blob", keyPutResult.error);
6428
6673
  }
6429
6674
  const valuePayload = JSON.stringify({
6430
- data: base64Encode(encrypted),
6675
+ data: base64Encode2(encrypted),
6431
6676
  metadata
6432
6677
  });
6433
6678
  const valuePutResult = await this.tc.kv.put(
@@ -6458,6 +6703,12 @@ var DataVaultService = class extends BaseService {
6458
6703
  if (this.usesNetworkEncryption) {
6459
6704
  return this.getNetworkEncrypted(key, options);
6460
6705
  }
6706
+ if (options?.expectAad === true) {
6707
+ return vaultError({
6708
+ code: "STORAGE_ERROR",
6709
+ cause: new Error("expectAad is only supported by network-encrypted vaults")
6710
+ });
6711
+ }
6461
6712
  if (!this._isUnlocked || !this.masterKey) {
6462
6713
  return vaultError({
6463
6714
  code: "VAULT_LOCKED",
@@ -6479,7 +6730,7 @@ var DataVaultService = class extends BaseService {
6479
6730
  return vaultError({ code: "KEY_NOT_FOUND", key });
6480
6731
  }
6481
6732
  const keyEnvelope = JSON.parse(keyResult.data.data);
6482
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6733
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6483
6734
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6484
6735
  const valueResult = await this.tc.kv.get(`vault/${key}`, {
6485
6736
  raw: true,
@@ -6489,7 +6740,7 @@ var DataVaultService = class extends BaseService {
6489
6740
  return vaultError({ code: "KEY_NOT_FOUND", key });
6490
6741
  }
6491
6742
  const valueEnvelope = JSON.parse(valueResult.data.data);
6492
- const encryptedBytes = base64Decode(valueEnvelope.data);
6743
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6493
6744
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6494
6745
  const metadata = valueEnvelope.metadata ?? {};
6495
6746
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6737,7 +6988,7 @@ var DataVaultService = class extends BaseService {
6737
6988
  return vaultError({ code: "KEY_NOT_FOUND", key });
6738
6989
  }
6739
6990
  const keyEnvelope = JSON.parse(keyResult.data.data);
6740
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6991
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6741
6992
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6742
6993
  const ephemeralSeed = this.crypto.randomBytes(32);
6743
6994
  const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
@@ -6756,7 +7007,7 @@ var DataVaultService = class extends BaseService {
6756
7007
  encryptedGrant
6757
7008
  );
6758
7009
  const grantPayload = JSON.stringify({
6759
- grant: base64Encode(grantBlob),
7010
+ grant: base64Encode2(grantBlob),
6760
7011
  spaceId: this.vaultConfig.spaceId,
6761
7012
  metadata: {
6762
7013
  [VaultHeaders.GRANT_VERSION]: "1",
@@ -6796,6 +7047,14 @@ var DataVaultService = class extends BaseService {
6796
7047
  */
6797
7048
  async getShared(grantorDID, key, options) {
6798
7049
  return this.withTelemetry("getShared", key, async () => {
7050
+ if (options?.expectAad === true) {
7051
+ return vaultError({
7052
+ code: "STORAGE_ERROR",
7053
+ cause: new Error(
7054
+ "expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
7055
+ )
7056
+ });
7057
+ }
6799
7058
  if (this.usesNetworkEncryption) {
6800
7059
  const grantorKV = options?.kv;
6801
7060
  if (!grantorKV) {
@@ -6890,7 +7149,7 @@ var DataVaultService = class extends BaseService {
6890
7149
  });
6891
7150
  }
6892
7151
  const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
6893
- const grantBlobBytes = base64Decode(grantEnvelope.grant);
7152
+ const grantBlobBytes = base64Decode2(grantEnvelope.grant);
6894
7153
  const ephemeralPubKey = grantBlobBytes.slice(0, 32);
6895
7154
  const encryptedGrant = grantBlobBytes.slice(32);
6896
7155
  const sharedSecret = this.crypto.x25519Dh(
@@ -6913,7 +7172,7 @@ var DataVaultService = class extends BaseService {
6913
7172
  });
6914
7173
  }
6915
7174
  const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
6916
- const encryptedBytes = base64Decode(valueEnvelope.data);
7175
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6917
7176
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6918
7177
  const metadata = valueEnvelope.metadata ?? {};
6919
7178
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6967,7 +7226,7 @@ var DataVaultService = class extends BaseService {
6967
7226
  if (!result.ok) {
6968
7227
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
6969
7228
  }
6970
- const pubKeyBytes = base64Decode(result.data);
7229
+ const pubKeyBytes = base64Decode2(result.data);
6971
7230
  return { ok: true, data: pubKeyBytes };
6972
7231
  } catch (error) {
6973
7232
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
@@ -7086,7 +7345,7 @@ var DataVaultService = class extends BaseService {
7086
7345
  }
7087
7346
  const currentEntry = getResult.data;
7088
7347
  const newEntryKey = this.crypto.randomBytes(32);
7089
- const newKeyId = hexEncode(this.crypto.sha256(newEntryKey)).slice(
7348
+ const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
7090
7349
  0,
7091
7350
  16
7092
7351
  );
@@ -7103,7 +7362,7 @@ var DataVaultService = class extends BaseService {
7103
7362
  [VaultHeaders.KEY_ID]: newKeyId
7104
7363
  };
7105
7364
  const keyPayload = JSON.stringify({
7106
- key: base64Encode(newKeyBlob),
7365
+ key: base64Encode2(newKeyBlob),
7107
7366
  metadata: JSON.stringify({
7108
7367
  keyId: newKeyId,
7109
7368
  ...metadata
@@ -7117,7 +7376,7 @@ var DataVaultService = class extends BaseService {
7117
7376
  return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
7118
7377
  }
7119
7378
  const valuePayload = JSON.stringify({
7120
- data: base64Encode(encrypted),
7379
+ data: base64Encode2(encrypted),
7121
7380
  metadata
7122
7381
  });
7123
7382
  const valuePutResult = await this.tc.kv.put(
@@ -7409,116 +7668,6 @@ var SecretsService = class {
7409
7668
  }
7410
7669
  };
7411
7670
 
7412
- // src/encryption/canonical.ts
7413
- function canonicalize(value) {
7414
- if (value === void 0) {
7415
- return "";
7416
- }
7417
- return stringify(value);
7418
- }
7419
- function stringify(value) {
7420
- if (value === null) return "null";
7421
- switch (typeof value) {
7422
- case "boolean":
7423
- case "number":
7424
- return JSON.stringify(value);
7425
- case "string":
7426
- return JSON.stringify(value);
7427
- case "object": {
7428
- if (Array.isArray(value)) {
7429
- return `[${value.map(stringify).join(",")}]`;
7430
- }
7431
- const keys = Object.keys(value).sort();
7432
- const parts = [];
7433
- for (const k of keys) {
7434
- const v = value[k];
7435
- if (v === void 0) continue;
7436
- parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
7437
- }
7438
- return `{${parts.join(",")}}`;
7439
- }
7440
- default:
7441
- throw new TypeError(
7442
- `canonicalize: unsupported value type ${typeof value}`
7443
- );
7444
- }
7445
- }
7446
- var HEX = "0123456789abcdef";
7447
- function hexEncode2(bytes) {
7448
- let out = "";
7449
- for (let i = 0; i < bytes.length; i++) {
7450
- const b = bytes[i];
7451
- out += HEX[b >> 4 & 15] + HEX[b & 15];
7452
- }
7453
- return out;
7454
- }
7455
- function hexDecode(hex) {
7456
- if (hex.length % 2 !== 0) {
7457
- throw new Error("hex string must have even length");
7458
- }
7459
- const out = new Uint8Array(hex.length / 2);
7460
- for (let i = 0; i < out.length; i++) {
7461
- const hi = parseInt(hex[i * 2], 16);
7462
- const lo = parseInt(hex[i * 2 + 1], 16);
7463
- if (Number.isNaN(hi) || Number.isNaN(lo)) {
7464
- throw new Error("invalid hex character");
7465
- }
7466
- out[i] = hi << 4 | lo;
7467
- }
7468
- return out;
7469
- }
7470
- function base64Encode2(bytes) {
7471
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7472
- let out = "";
7473
- for (let i = 0; i < bytes.length; i += 3) {
7474
- const b0 = bytes[i];
7475
- const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
7476
- const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
7477
- out += chars[b0 >> 2 & 63];
7478
- out += chars[(b0 << 4 | b1 >> 4) & 63];
7479
- out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
7480
- out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
7481
- }
7482
- return out;
7483
- }
7484
- function base64Decode2(s) {
7485
- const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
7486
- const len = clean.length;
7487
- if (len % 4 !== 0) {
7488
- throw new Error("invalid base64 input");
7489
- }
7490
- const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
7491
- const outLen = len / 4 * 3 - padding;
7492
- const out = new Uint8Array(outLen);
7493
- const lookup = {};
7494
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7495
- for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
7496
- let outIdx = 0;
7497
- for (let i = 0; i < len; i += 4) {
7498
- const v0 = lookup[clean[i]] ?? 0;
7499
- const v1 = lookup[clean[i + 1]] ?? 0;
7500
- const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
7501
- const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
7502
- const b0 = v0 << 2 | v1 >> 4;
7503
- const b1 = (v1 & 15) << 4 | v2 >> 2;
7504
- const b2 = (v2 & 3) << 6 | v3;
7505
- if (outIdx < outLen) out[outIdx++] = b0;
7506
- if (outIdx < outLen) out[outIdx++] = b1;
7507
- if (outIdx < outLen) out[outIdx++] = b2;
7508
- }
7509
- return out;
7510
- }
7511
- function utf8Encode(s) {
7512
- return new TextEncoder().encode(s);
7513
- }
7514
- function utf8Decode(b) {
7515
- return new TextDecoder().decode(b);
7516
- }
7517
- function canonicalHashHex(sha256, value) {
7518
- const canonical = canonicalize(value);
7519
- return hexEncode2(sha256(utf8Encode(canonical)));
7520
- }
7521
-
7522
7671
  // src/encryption/types.ts
7523
7672
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
7524
7673
  var ENVELOPE_VERSION = 1;
@@ -7728,7 +7877,7 @@ function encryptToNetwork(crypto2, input) {
7728
7877
  const symmetricKey = crypto2.randomBytes(32);
7729
7878
  const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
7730
7879
  const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
7731
- const encryptedSymmetricKey = base64Encode2(wrapped);
7880
+ const encryptedSymmetricKey = base64Encode(wrapped);
7732
7881
  const encryptedSymmetricKeyHash = canonicalHashHex(
7733
7882
  crypto2.sha256,
7734
7883
  encryptedSymmetricKey
@@ -7740,8 +7889,8 @@ function encryptToNetwork(crypto2, input) {
7740
7889
  keyVersion,
7741
7890
  encryptedSymmetricKey,
7742
7891
  encryptedSymmetricKeyHash,
7743
- ciphertext: base64Encode2(ciphertext),
7744
- ...input.aad !== void 0 ? { aad: base64Encode2(input.aad) } : {},
7892
+ ciphertext: base64Encode(ciphertext),
7893
+ ...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
7745
7894
  ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
7746
7895
  };
7747
7896
  return { envelope, symmetricKey };
@@ -7815,8 +7964,8 @@ function validateEnvelope(crypto2, envelope) {
7815
7964
  return { ok: true, data: e };
7816
7965
  }
7817
7966
  function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
7818
- const ciphertext = base64Decode2(envelope.ciphertext);
7819
- const aad = envelope.aad !== void 0 ? base64Decode2(envelope.aad) : void 0;
7967
+ const ciphertext = base64Decode(envelope.ciphertext);
7968
+ const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
7820
7969
  return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
7821
7970
  }
7822
7971
 
@@ -7834,7 +7983,7 @@ function buildCanonicalDecryptRequest(input) {
7834
7983
  return { canonicalBody, bodyHash, receiverPublicKeyHash };
7835
7984
  }
7836
7985
  function buildDecryptFacts(input) {
7837
- const bodyHash = input.canonicalBody !== void 0 ? hexEncode2(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7986
+ const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7838
7987
  input.crypto.sha256,
7839
7988
  input.body
7840
7989
  );
@@ -8117,7 +8266,7 @@ function verifyDecryptResponse(input) {
8117
8266
  })
8118
8267
  };
8119
8268
  }
8120
- const expectedRequestHash = hexEncode2(
8269
+ const expectedRequestHash = hexEncode(
8121
8270
  crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
8122
8271
  );
8123
8272
  if (response.requestHash !== expectedRequestHash) {
@@ -8141,7 +8290,7 @@ function verifyDecryptResponse(input) {
8141
8290
  const signedBytes = new TextEncoder().encode(
8142
8291
  canonicalSignedResponse(response)
8143
8292
  );
8144
- const signatureBytes = base64Decode2(response.nodeSignature);
8293
+ const signatureBytes = base64Decode(response.nodeSignature);
8145
8294
  if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
8146
8295
  return {
8147
8296
  ok: false,
@@ -8153,7 +8302,7 @@ function verifyDecryptResponse(input) {
8153
8302
  return { ok: true, data: response };
8154
8303
  }
8155
8304
  function openWrappedKey(crypto2, receiverPrivateKey, response) {
8156
- const wrapped = base64Decode2(response.wrappedKey);
8305
+ const wrapped = base64Decode(response.wrappedKey);
8157
8306
  return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
8158
8307
  }
8159
8308
 
@@ -8197,7 +8346,7 @@ var EncryptionService = class extends BaseService {
8197
8346
  const usable = ensureNetworkUsableForDecrypt(discovered.data);
8198
8347
  if (!usable.ok) return usable;
8199
8348
  const descriptor = usable.data;
8200
- const networkPublicKey = base64Decode2(descriptor.publicEncryptionKey);
8349
+ const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
8201
8350
  const result = encryptToNetwork(this.crypto, {
8202
8351
  // Discovery returns the node's canonical network identifier. Persist
8203
8352
  // that spelling in the envelope so a later response can be compared
@@ -8225,7 +8374,7 @@ var EncryptionService = class extends BaseService {
8225
8374
  this.assertActive();
8226
8375
  const validated = validateEnvelope(this.crypto, envelope);
8227
8376
  if (!validated.ok) return validated;
8228
- if (options?.aad !== void 0 && validated.data.aad !== base64Encode2(options.aad)) {
8377
+ if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
8229
8378
  return encErr(
8230
8379
  encryptionError({
8231
8380
  code: "INVALID_INPUT",
@@ -8253,7 +8402,7 @@ var EncryptionService = class extends BaseService {
8253
8402
  );
8254
8403
  }
8255
8404
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
8256
- const receiverPublicKey = base64Encode2(receiverKey.publicKey);
8405
+ const receiverPublicKey = base64Encode(receiverKey.publicKey);
8257
8406
  const receiverPublicKeyHash = canonicalHashHex(
8258
8407
  this.crypto.sha256,
8259
8408
  receiverPublicKey
@@ -8468,6 +8617,7 @@ var import_decrypt_transport_response_error3 = require("@tinycloud/sdk-services/
8468
8617
  kvPrefixCovers,
8469
8618
  networkDiscoveryKey,
8470
8619
  networkError,
8620
+ nodeRetryError,
8471
8621
  notFoundError,
8472
8622
  ok,
8473
8623
  openWrappedKey,