@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.js CHANGED
@@ -14,6 +14,14 @@ var ErrorCodes = {
14
14
  ABORTED: "ABORTED",
15
15
  INVALID_INPUT: "INVALID_INPUT",
16
16
  PERMISSION_DENIED: "PERMISSION_DENIED",
17
+ // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
18
+ // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
19
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
20
+ NODE_BUSY: "NODE_BUSY",
21
+ /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
22
+ REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
23
+ /** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
24
+ OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
17
25
  // KV-specific errors
18
26
  KV_NOT_FOUND: "KV_NOT_FOUND",
19
27
  KV_WRITE_FAILED: "KV_WRITE_FAILED",
@@ -68,6 +76,8 @@ var TelemetryEvents = {
68
76
  REPLICATION_SYNC: "replication.sync",
69
77
  REPLICATION_STATE: "replication.state",
70
78
  REPLICATION_DIVERGENCE: "replication.divergence",
79
+ REPLICATION_GRANT: "replication.grant",
80
+ REPLICATION_LIFECYCLE: "replication.lifecycle",
71
81
  /** Emitted once, on the first write rejected because storage is full. */
72
82
  STORAGE_FULL: "storage.full"
73
83
  };
@@ -104,21 +114,27 @@ var SAFE_NUMBER_FIELDS = /* @__PURE__ */ new Set([
104
114
  "fetched",
105
115
  "pages",
106
116
  "lagMs",
107
- "pendingCleared"
117
+ "pendingCleared",
118
+ "partitions",
119
+ "remaining",
120
+ "expiryMs"
108
121
  ]);
109
- var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted"]);
122
+ var SAFE_BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["authenticated", "ok", "persisted", "purgePending"]);
110
123
  var SAFE_REPLICATION_FIELDS = {
111
124
  source: ["replica", "network", "none"],
112
- 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"],
125
+ 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"],
113
126
  op: ["get", "list", "put", "delete", "batchPut"],
114
- trigger: ["start", "interval", "stale_read", "manual"],
127
+ trigger: ["start", "interval", "stale_read", "manual", "peer"],
128
+ origin: ["peer"],
115
129
  outcome: ["found", "not_found", "error", "committed", "failed", "ambiguous", "ok", "busy", "aborted"],
116
- class: ["offline", "authority", "storage", "node"],
117
- state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready"],
130
+ class: ["offline", "authority", "storage", "node", "lifecycle"],
131
+ state: ["opened", "grant_installed", "grant_missing", "runtime_unsupported", "unavailable", "revoked", "recreated", "purged", "purge_failed", "pending_store_error", "pinned", "pending_cleared", "closed", "idle", "ready", "gated"],
132
+ 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"],
133
+ gate: ["not_established", "stamp_stale", "principal_mismatch", "purge_pending", "logout_pending", "gate_unavailable"],
118
134
  kind: ["value", "missing_local", "extra_local", "keys", "order"],
119
135
  coverage: ["empty", "bootstrapping", "complete"],
120
136
  authority: ["valid", "expired", "revoked", "not-yet-valid"],
121
- 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"],
137
+ 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"],
122
138
  pendingState: ["in_flight", "committed", "ambiguous"],
123
139
  strategy: ["session", "minted", "installed"],
124
140
  verify: ["match", "diverged", "error", "aborted"]
@@ -950,6 +966,42 @@ var ServiceContext = class {
950
966
  }
951
967
  };
952
968
 
969
+ // src/responseErrors.ts
970
+ function parseServiceErrorBody(errorText) {
971
+ try {
972
+ return JSON.parse(errorText);
973
+ } catch {
974
+ return {};
975
+ }
976
+ }
977
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
978
+ if (parsed.message) {
979
+ return compactMessage(parsed.message);
980
+ }
981
+ if (looksLikeHtml(errorText)) {
982
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
983
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
984
+ }
985
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
986
+ }
987
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
988
+ }
989
+ function responseErrorMeta(status, statusText, errorText) {
990
+ const meta = { status, statusText };
991
+ const snippet = compactMessage(errorText);
992
+ if (snippet) {
993
+ meta.responseSnippet = snippet.slice(0, 300);
994
+ }
995
+ return meta;
996
+ }
997
+ function looksLikeHtml(text) {
998
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
999
+ }
1000
+ function compactMessage(message) {
1001
+ const compact = message.replace(/\s+/g, " ").trim();
1002
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
1003
+ }
1004
+
953
1005
  // src/errors.ts
954
1006
  import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
955
1007
 
@@ -1109,6 +1161,30 @@ function networkError(service, message, cause) {
1109
1161
  cause
1110
1162
  };
1111
1163
  }
1164
+ function nodeRetryError(service, message, response, errorText) {
1165
+ const body = errorText.trim();
1166
+ const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
1167
+ let code;
1168
+ if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
1169
+ code = ErrorCodes.NODE_BUSY;
1170
+ } else if (response.status === 503 && token === "request_timeout") {
1171
+ code = ErrorCodes.REQUEST_TIMEOUT;
1172
+ } else if (response.status === 500 && token === "outcome_unknown") {
1173
+ code = ErrorCodes.OUTCOME_UNKNOWN;
1174
+ } else {
1175
+ return void 0;
1176
+ }
1177
+ const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;
1178
+ const retryAfter = response.headers.get("retry-after")?.trim();
1179
+ return serviceError(code, message, service, {
1180
+ meta: {
1181
+ ...responseErrorMeta(response.status, response.statusText, errorText),
1182
+ retryable,
1183
+ ...token === "sql_busy" && stage ? { stage } : {},
1184
+ ...retryable && retryAfter && /^\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1e3 } : {}
1185
+ }
1186
+ });
1187
+ }
1112
1188
  var RequestTimeoutError = class extends Error {
1113
1189
  constructor(timeoutMs) {
1114
1190
  super(`Request timed out after ${timeoutMs}ms.`);
@@ -2447,9 +2523,12 @@ var KVService = class extends BaseService {
2447
2523
  { meta: { status: response.status, statusText: response.statusText } }
2448
2524
  ));
2449
2525
  }
2526
+ const message = `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
2527
+ const retryError = nodeRetryError("kv", message, response, errorText);
2528
+ if (retryError) return err(retryError);
2450
2529
  return err(serviceError(
2451
2530
  ErrorCodes.NETWORK_ERROR,
2452
- `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2531
+ message,
2453
2532
  "kv",
2454
2533
  { meta: { status: response.status, statusText: response.statusText } }
2455
2534
  ));
@@ -2531,19 +2610,14 @@ var KVService = class extends BaseService {
2531
2610
  { meta: { status: response.status, statusText: response.statusText } }
2532
2611
  ));
2533
2612
  }
2534
- if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
2535
- return err(serviceError(
2536
- ErrorCodes.KV_CONFLICT,
2537
- `Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
2538
- "kv",
2539
- { meta: { status: response.status, statusText: response.statusText } }
2540
- ));
2541
- }
2613
+ const message = `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
2614
+ const retryError = nodeRetryError("kv", message, response, errorText);
2615
+ if (retryError) return err(retryError);
2542
2616
  const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
2543
2617
  if (quotaError) return quotaError;
2544
2618
  return err(serviceError(
2545
2619
  ErrorCodes.KV_WRITE_FAILED,
2546
- `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2620
+ message,
2547
2621
  "kv",
2548
2622
  { meta: { status: response.status, statusText: response.statusText } }
2549
2623
  ));
@@ -2843,9 +2917,12 @@ var KVService = class extends BaseService {
2843
2917
  );
2844
2918
  }
2845
2919
  const errorText = await response.text();
2920
+ const message = `Failed to list keys: ${response.status} - ${errorText}`;
2921
+ const retryError = nodeRetryError("kv", message, response, errorText);
2922
+ if (retryError) return err(retryError);
2846
2923
  return err(serviceError(
2847
2924
  ErrorCodes.NETWORK_ERROR,
2848
- `Failed to list keys: ${response.status} - ${errorText}`,
2925
+ message,
2849
2926
  "kv",
2850
2927
  { meta: { status: response.status, statusText: response.statusText } }
2851
2928
  ));
@@ -3096,17 +3173,12 @@ var KVService = class extends BaseService {
3096
3173
  { meta: { status: response.status, statusText: response.statusText } }
3097
3174
  ));
3098
3175
  }
3099
- if (response.status === 503 && options?.ifMatch !== void 0) {
3100
- return err(serviceError(
3101
- ErrorCodes.KV_CONFLICT,
3102
- `Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
3103
- "kv",
3104
- { meta: { status: response.status, statusText: response.statusText } }
3105
- ));
3106
- }
3176
+ const message = `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
3177
+ const retryError = nodeRetryError("kv", message, response, errorText);
3178
+ if (retryError) return err(retryError);
3107
3179
  return err(serviceError(
3108
3180
  ErrorCodes.NETWORK_ERROR,
3109
- `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3181
+ message,
3110
3182
  "kv",
3111
3183
  { meta: { status: response.status, statusText: response.statusText } }
3112
3184
  ));
@@ -3147,10 +3219,13 @@ var KVService = class extends BaseService {
3147
3219
  return await this.classifyNotFound(response, key);
3148
3220
  }
3149
3221
  const errorText = await response.text();
3222
+ const message = `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
3223
+ const retryError = nodeRetryError("kv", message, response, errorText);
3224
+ if (retryError) return err(retryError);
3150
3225
  return err(
3151
3226
  serviceError(
3152
3227
  ErrorCodes.NETWORK_ERROR,
3153
- `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
3228
+ message,
3154
3229
  "kv",
3155
3230
  { meta: { status: response.status, statusText: response.statusText } }
3156
3231
  )
@@ -3342,42 +3417,6 @@ function requiresSecretsOptIn(space, prefix) {
3342
3417
  return prefix.split("/", 1)[0] === "vault";
3343
3418
  }
3344
3419
 
3345
- // src/responseErrors.ts
3346
- function parseServiceErrorBody(errorText) {
3347
- try {
3348
- return JSON.parse(errorText);
3349
- } catch {
3350
- return {};
3351
- }
3352
- }
3353
- function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
3354
- if (parsed.message) {
3355
- return compactMessage(parsed.message);
3356
- }
3357
- if (looksLikeHtml(errorText)) {
3358
- if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
3359
- return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
3360
- }
3361
- return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
3362
- }
3363
- return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
3364
- }
3365
- function responseErrorMeta(status, statusText, errorText) {
3366
- const meta = { status, statusText };
3367
- const snippet = compactMessage(errorText);
3368
- if (snippet) {
3369
- meta.responseSnippet = snippet.slice(0, 300);
3370
- }
3371
- return meta;
3372
- }
3373
- function looksLikeHtml(text) {
3374
- return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
3375
- }
3376
- function compactMessage(message) {
3377
- const compact = message.replace(/\s+/g, " ").trim();
3378
- return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
3379
- }
3380
-
3381
3420
  // src/sql/SQLMigrations.ts
3382
3421
  var SQLMigrations = class {
3383
3422
  constructor(service, dbName) {
@@ -3793,6 +3832,8 @@ var SQLService = class extends BaseService {
3793
3832
  if (response.status === 413 && parseStorageRejection(errorText)) {
3794
3833
  return err(storageRejectionError("sql", 413, meta, errorText));
3795
3834
  }
3835
+ const retryError = nodeRetryError("sql", `SQL ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
3836
+ if (retryError) return err(retryError);
3796
3837
  const errorBody = parseServiceErrorBody(errorText);
3797
3838
  const errorCode = this.mapHttpStatusToErrorCode(
3798
3839
  response.status,
@@ -4262,6 +4303,8 @@ var DuckDbService = class extends BaseService {
4262
4303
  if (response.status === 413 && parseStorageRejection(errorText)) {
4263
4304
  return err(storageRejectionError("duckdb", 413, meta, errorText));
4264
4305
  }
4306
+ const retryError = nodeRetryError("duckdb", `DuckDB ${operation} failed: ${response.status} - ${errorText}`, response, errorText);
4307
+ if (retryError) return err(retryError);
4265
4308
  const errorBody = parseServiceErrorBody(errorText);
4266
4309
  const errorCode = this.mapHttpStatusToErrorCode(
4267
4310
  response.status,
@@ -5469,6 +5512,116 @@ var TinyCloudQuota = class {
5469
5512
  }
5470
5513
  };
5471
5514
 
5515
+ // src/encryption/canonical.ts
5516
+ function canonicalize(value) {
5517
+ if (value === void 0) {
5518
+ return "";
5519
+ }
5520
+ return stringify(value);
5521
+ }
5522
+ function stringify(value) {
5523
+ if (value === null) return "null";
5524
+ switch (typeof value) {
5525
+ case "boolean":
5526
+ case "number":
5527
+ return JSON.stringify(value);
5528
+ case "string":
5529
+ return JSON.stringify(value);
5530
+ case "object": {
5531
+ if (Array.isArray(value)) {
5532
+ return `[${value.map(stringify).join(",")}]`;
5533
+ }
5534
+ const keys = Object.keys(value).sort();
5535
+ const parts = [];
5536
+ for (const k of keys) {
5537
+ const v = value[k];
5538
+ if (v === void 0) continue;
5539
+ parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
5540
+ }
5541
+ return `{${parts.join(",")}}`;
5542
+ }
5543
+ default:
5544
+ throw new TypeError(
5545
+ `canonicalize: unsupported value type ${typeof value}`
5546
+ );
5547
+ }
5548
+ }
5549
+ var HEX = "0123456789abcdef";
5550
+ function hexEncode(bytes) {
5551
+ let out = "";
5552
+ for (let i = 0; i < bytes.length; i++) {
5553
+ const b = bytes[i];
5554
+ out += HEX[b >> 4 & 15] + HEX[b & 15];
5555
+ }
5556
+ return out;
5557
+ }
5558
+ function hexDecode(hex) {
5559
+ if (hex.length % 2 !== 0) {
5560
+ throw new Error("hex string must have even length");
5561
+ }
5562
+ const out = new Uint8Array(hex.length / 2);
5563
+ for (let i = 0; i < out.length; i++) {
5564
+ const hi = parseInt(hex[i * 2], 16);
5565
+ const lo = parseInt(hex[i * 2 + 1], 16);
5566
+ if (Number.isNaN(hi) || Number.isNaN(lo)) {
5567
+ throw new Error("invalid hex character");
5568
+ }
5569
+ out[i] = hi << 4 | lo;
5570
+ }
5571
+ return out;
5572
+ }
5573
+ function base64Encode(bytes) {
5574
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5575
+ let out = "";
5576
+ for (let i = 0; i < bytes.length; i += 3) {
5577
+ const b0 = bytes[i];
5578
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
5579
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
5580
+ out += chars[b0 >> 2 & 63];
5581
+ out += chars[(b0 << 4 | b1 >> 4) & 63];
5582
+ out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
5583
+ out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
5584
+ }
5585
+ return out;
5586
+ }
5587
+ function base64Decode(s) {
5588
+ const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
5589
+ const len = clean.length;
5590
+ if (len % 4 !== 0) {
5591
+ throw new Error("invalid base64 input");
5592
+ }
5593
+ const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
5594
+ const outLen = len / 4 * 3 - padding;
5595
+ const out = new Uint8Array(outLen);
5596
+ const lookup = {};
5597
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5598
+ for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
5599
+ let outIdx = 0;
5600
+ for (let i = 0; i < len; i += 4) {
5601
+ const v0 = lookup[clean[i]] ?? 0;
5602
+ const v1 = lookup[clean[i + 1]] ?? 0;
5603
+ const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
5604
+ const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
5605
+ const b0 = v0 << 2 | v1 >> 4;
5606
+ const b1 = (v1 & 15) << 4 | v2 >> 2;
5607
+ const b2 = (v2 & 3) << 6 | v3;
5608
+ if (outIdx < outLen) out[outIdx++] = b0;
5609
+ if (outIdx < outLen) out[outIdx++] = b1;
5610
+ if (outIdx < outLen) out[outIdx++] = b2;
5611
+ }
5612
+ return out;
5613
+ }
5614
+ function utf8Encode(s) {
5615
+ return new TextEncoder().encode(s);
5616
+ }
5617
+ function utf8Decode(b) {
5618
+ return new TextDecoder().decode(b);
5619
+ }
5620
+ function canonicalHashHex(sha256, value) {
5621
+ const canonical = canonicalize(value);
5622
+ return hexEncode(sha256(utf8Encode(canonical)));
5623
+ }
5624
+
5472
5625
  // src/vault/types.ts
5473
5626
  var VaultPublicSpaceKVActions = [
5474
5627
  "tinycloud.kv/get",
@@ -5638,7 +5791,7 @@ function toBytes(str) {
5638
5791
  function fromBytes(bytes) {
5639
5792
  return new TextDecoder().decode(bytes);
5640
5793
  }
5641
- function hexEncode(bytes) {
5794
+ function hexEncode2(bytes) {
5642
5795
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5643
5796
  }
5644
5797
  function concatBytes(...arrays) {
@@ -5651,14 +5804,14 @@ function concatBytes(...arrays) {
5651
5804
  }
5652
5805
  return result;
5653
5806
  }
5654
- function base64Encode(bytes) {
5807
+ function base64Encode2(bytes) {
5655
5808
  let binary = "";
5656
5809
  for (let i = 0; i < bytes.length; i++) {
5657
5810
  binary += String.fromCharCode(bytes[i]);
5658
5811
  }
5659
5812
  return btoa(binary);
5660
5813
  }
5661
- function base64Decode(str) {
5814
+ function base64Decode2(str) {
5662
5815
  const binary = atob(str);
5663
5816
  const bytes = new Uint8Array(binary.length);
5664
5817
  for (let i = 0; i < binary.length; i++) {
@@ -5700,6 +5853,11 @@ function defaultVaultMessage(input) {
5700
5853
  return input.message;
5701
5854
  case "PERMISSION_DENIED":
5702
5855
  return input.message;
5856
+ case "AAD_MISMATCH":
5857
+ case "ETAG_MISMATCH":
5858
+ case "INVALID_ETAG":
5859
+ case "OUTCOME_UNKNOWN":
5860
+ case "NODE_BUSY":
5703
5861
  case "STORAGE_QUOTA_EXCEEDED":
5704
5862
  case "STORAGE_LIMIT_REACHED":
5705
5863
  return input.message;
@@ -5722,6 +5880,37 @@ function typedKvCause(error) {
5722
5880
  ...error.cause !== void 0 ? { cause: error.cause } : {}
5723
5881
  });
5724
5882
  }
5883
+ function canonicalAadSpaceId(spaceId) {
5884
+ return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
5885
+ }
5886
+ function checkEnvelopeAad(value, spaceId, key) {
5887
+ const e = value;
5888
+ if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
5889
+ (field) => typeof e[field] === "string" && e[field] !== ""
5890
+ ) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
5891
+ return "corrupt";
5892
+ }
5893
+ let aad = "";
5894
+ try {
5895
+ if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
5896
+ } catch {
5897
+ }
5898
+ const suffix = `:${key}`;
5899
+ return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
5900
+ }
5901
+ var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
5902
+ function ifMatchWriteError(key, error) {
5903
+ const status = error.meta?.status;
5904
+ 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;
5905
+ if (code === void 0) return void 0;
5906
+ return vaultError({
5907
+ code,
5908
+ key,
5909
+ message: error.message,
5910
+ ...error.meta !== void 0 ? { meta: error.meta } : {},
5911
+ cause: typedKvCause(error)
5912
+ });
5913
+ }
5725
5914
  function kvWriteError(context, error) {
5726
5915
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
5727
5916
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
@@ -5908,7 +6097,7 @@ var DataVaultService = class extends BaseService {
5908
6097
  }
5909
6098
  if (existingPubKey) {
5910
6099
  this.encryptionIdentity = {
5911
- publicKey: base64Decode(existingPubKey),
6100
+ publicKey: base64Decode2(existingPubKey),
5912
6101
  privateKey: new Uint8Array(0)
5913
6102
  // private key not available without signing
5914
6103
  };
@@ -5933,7 +6122,7 @@ var DataVaultService = class extends BaseService {
5933
6122
  );
5934
6123
  this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
5935
6124
  try {
5936
- const pubKeyB64 = base64Encode(this.encryptionIdentity.publicKey);
6125
+ const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
5937
6126
  await this.tc.ensurePublicSpace();
5938
6127
  await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
5939
6128
  await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
@@ -5999,6 +6188,21 @@ var DataVaultService = class extends BaseService {
5999
6188
  message: "Authentication required"
6000
6189
  });
6001
6190
  }
6191
+ if (options?.ifMatch !== void 0) {
6192
+ if (options.ifNoneMatch !== void 0) {
6193
+ return vaultError({
6194
+ code: "STORAGE_ERROR",
6195
+ cause: new Error("ifMatch cannot be combined with ifNoneMatch")
6196
+ });
6197
+ }
6198
+ if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
6199
+ return vaultError({
6200
+ code: "INVALID_ETAG",
6201
+ key,
6202
+ 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"
6203
+ });
6204
+ }
6205
+ }
6002
6206
  try {
6003
6207
  const { plaintext, contentType } = this.serializeValue(value, options);
6004
6208
  const metadata = {
@@ -6021,7 +6225,8 @@ var DataVaultService = class extends BaseService {
6021
6225
  }
6022
6226
  const valuePath = `vault/${key}`;
6023
6227
  const valueBody = JSON.stringify(envelopeResult.data);
6024
- const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
6228
+ const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
6229
+ const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
6025
6230
  if (!valuePutResult.ok) {
6026
6231
  if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
6027
6232
  return vaultError({
@@ -6031,6 +6236,10 @@ var DataVaultService = class extends BaseService {
6031
6236
  cause: typedKvCause(valuePutResult.error)
6032
6237
  });
6033
6238
  }
6239
+ if (options?.ifMatch !== void 0) {
6240
+ const classified = ifMatchWriteError(key, valuePutResult.error);
6241
+ if (classified !== void 0) return classified;
6242
+ }
6034
6243
  return kvWriteError("Failed to store encrypted value", valuePutResult.error);
6035
6244
  }
6036
6245
  return { ok: true, data: void 0 };
@@ -6065,6 +6274,19 @@ var DataVaultService = class extends BaseService {
6065
6274
  }
6066
6275
  const rawEnvelope = unwrapKVData(valueResult.data);
6067
6276
  const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
6277
+ if (options?.expectAad === true) {
6278
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6279
+ if (verdict === "corrupt") {
6280
+ return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
6281
+ }
6282
+ if (verdict === "mismatch") {
6283
+ return vaultError({
6284
+ code: "AAD_MISMATCH",
6285
+ key,
6286
+ message: `Envelope at ${key} is not bound to this space and key`
6287
+ });
6288
+ }
6289
+ }
6068
6290
  const proof = await this.decryptCapabilityProof();
6069
6291
  const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6070
6292
  if (!plaintextResult.ok) {
@@ -6081,7 +6303,11 @@ var DataVaultService = class extends BaseService {
6081
6303
  contentType,
6082
6304
  options
6083
6305
  );
6084
- return { ok: true, data: { value, metadata, keyId } };
6306
+ const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
6307
+ return {
6308
+ ok: true,
6309
+ data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6310
+ };
6085
6311
  } catch (error) {
6086
6312
  return vaultError({
6087
6313
  code: "STORAGE_ERROR",
@@ -6127,6 +6353,12 @@ var DataVaultService = class extends BaseService {
6127
6353
  } catch {
6128
6354
  return { status: "corrupt_envelope" };
6129
6355
  }
6356
+ if (options?.expectAad === true) {
6357
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6358
+ if (verdict !== "ok") {
6359
+ return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
6360
+ }
6361
+ }
6130
6362
  let plaintextResult;
6131
6363
  try {
6132
6364
  const proof = await this.decryptCapabilityProof();
@@ -6158,7 +6390,11 @@ var DataVaultService = class extends BaseService {
6158
6390
  contentType,
6159
6391
  options
6160
6392
  );
6161
- return { status: "ok", entry: { value, metadata, keyId } };
6393
+ const etag = valueResult.data.headers?.etag;
6394
+ return {
6395
+ status: "ok",
6396
+ entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6397
+ };
6162
6398
  } catch {
6163
6399
  return { status: "invalid_payload" };
6164
6400
  }
@@ -6208,6 +6444,14 @@ var DataVaultService = class extends BaseService {
6208
6444
  )
6209
6445
  });
6210
6446
  }
6447
+ if (options?.ifMatch !== void 0) {
6448
+ return vaultError({
6449
+ code: "STORAGE_ERROR",
6450
+ cause: new Error(
6451
+ "ifMatch is only supported by network-encrypted vaults"
6452
+ )
6453
+ });
6454
+ }
6211
6455
  if (!this._isUnlocked || !this.masterKey) {
6212
6456
  return vaultError({
6213
6457
  code: "VAULT_LOCKED",
@@ -6233,7 +6477,7 @@ var DataVaultService = class extends BaseService {
6233
6477
  }
6234
6478
  const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
6235
6479
  const entryKey = this.crypto.randomBytes(32);
6236
- const keyId = hexEncode(this.crypto.sha256(entryKey)).slice(0, 16);
6480
+ const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
6237
6481
  const encrypted = this.crypto.encrypt(entryKey, plaintext);
6238
6482
  const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
6239
6483
  const metadata = {
@@ -6251,7 +6495,7 @@ var DataVaultService = class extends BaseService {
6251
6495
  ...metadata
6252
6496
  });
6253
6497
  const keyPayload = JSON.stringify({
6254
- key: base64Encode(keyBlob),
6498
+ key: base64Encode2(keyBlob),
6255
6499
  metadata: keyMetadata
6256
6500
  });
6257
6501
  const keyPutResult = await this.tc.kv.put(
@@ -6262,7 +6506,7 @@ var DataVaultService = class extends BaseService {
6262
6506
  return kvWriteError("Failed to store key blob", keyPutResult.error);
6263
6507
  }
6264
6508
  const valuePayload = JSON.stringify({
6265
- data: base64Encode(encrypted),
6509
+ data: base64Encode2(encrypted),
6266
6510
  metadata
6267
6511
  });
6268
6512
  const valuePutResult = await this.tc.kv.put(
@@ -6293,6 +6537,12 @@ var DataVaultService = class extends BaseService {
6293
6537
  if (this.usesNetworkEncryption) {
6294
6538
  return this.getNetworkEncrypted(key, options);
6295
6539
  }
6540
+ if (options?.expectAad === true) {
6541
+ return vaultError({
6542
+ code: "STORAGE_ERROR",
6543
+ cause: new Error("expectAad is only supported by network-encrypted vaults")
6544
+ });
6545
+ }
6296
6546
  if (!this._isUnlocked || !this.masterKey) {
6297
6547
  return vaultError({
6298
6548
  code: "VAULT_LOCKED",
@@ -6314,7 +6564,7 @@ var DataVaultService = class extends BaseService {
6314
6564
  return vaultError({ code: "KEY_NOT_FOUND", key });
6315
6565
  }
6316
6566
  const keyEnvelope = JSON.parse(keyResult.data.data);
6317
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6567
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6318
6568
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6319
6569
  const valueResult = await this.tc.kv.get(`vault/${key}`, {
6320
6570
  raw: true,
@@ -6324,7 +6574,7 @@ var DataVaultService = class extends BaseService {
6324
6574
  return vaultError({ code: "KEY_NOT_FOUND", key });
6325
6575
  }
6326
6576
  const valueEnvelope = JSON.parse(valueResult.data.data);
6327
- const encryptedBytes = base64Decode(valueEnvelope.data);
6577
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6328
6578
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6329
6579
  const metadata = valueEnvelope.metadata ?? {};
6330
6580
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6572,7 +6822,7 @@ var DataVaultService = class extends BaseService {
6572
6822
  return vaultError({ code: "KEY_NOT_FOUND", key });
6573
6823
  }
6574
6824
  const keyEnvelope = JSON.parse(keyResult.data.data);
6575
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6825
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6576
6826
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6577
6827
  const ephemeralSeed = this.crypto.randomBytes(32);
6578
6828
  const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
@@ -6591,7 +6841,7 @@ var DataVaultService = class extends BaseService {
6591
6841
  encryptedGrant
6592
6842
  );
6593
6843
  const grantPayload = JSON.stringify({
6594
- grant: base64Encode(grantBlob),
6844
+ grant: base64Encode2(grantBlob),
6595
6845
  spaceId: this.vaultConfig.spaceId,
6596
6846
  metadata: {
6597
6847
  [VaultHeaders.GRANT_VERSION]: "1",
@@ -6631,6 +6881,14 @@ var DataVaultService = class extends BaseService {
6631
6881
  */
6632
6882
  async getShared(grantorDID, key, options) {
6633
6883
  return this.withTelemetry("getShared", key, async () => {
6884
+ if (options?.expectAad === true) {
6885
+ return vaultError({
6886
+ code: "STORAGE_ERROR",
6887
+ cause: new Error(
6888
+ "expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
6889
+ )
6890
+ });
6891
+ }
6634
6892
  if (this.usesNetworkEncryption) {
6635
6893
  const grantorKV = options?.kv;
6636
6894
  if (!grantorKV) {
@@ -6725,7 +6983,7 @@ var DataVaultService = class extends BaseService {
6725
6983
  });
6726
6984
  }
6727
6985
  const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
6728
- const grantBlobBytes = base64Decode(grantEnvelope.grant);
6986
+ const grantBlobBytes = base64Decode2(grantEnvelope.grant);
6729
6987
  const ephemeralPubKey = grantBlobBytes.slice(0, 32);
6730
6988
  const encryptedGrant = grantBlobBytes.slice(32);
6731
6989
  const sharedSecret = this.crypto.x25519Dh(
@@ -6748,7 +7006,7 @@ var DataVaultService = class extends BaseService {
6748
7006
  });
6749
7007
  }
6750
7008
  const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
6751
- const encryptedBytes = base64Decode(valueEnvelope.data);
7009
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6752
7010
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6753
7011
  const metadata = valueEnvelope.metadata ?? {};
6754
7012
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6802,7 +7060,7 @@ var DataVaultService = class extends BaseService {
6802
7060
  if (!result.ok) {
6803
7061
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
6804
7062
  }
6805
- const pubKeyBytes = base64Decode(result.data);
7063
+ const pubKeyBytes = base64Decode2(result.data);
6806
7064
  return { ok: true, data: pubKeyBytes };
6807
7065
  } catch (error) {
6808
7066
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
@@ -6921,7 +7179,7 @@ var DataVaultService = class extends BaseService {
6921
7179
  }
6922
7180
  const currentEntry = getResult.data;
6923
7181
  const newEntryKey = this.crypto.randomBytes(32);
6924
- const newKeyId = hexEncode(this.crypto.sha256(newEntryKey)).slice(
7182
+ const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
6925
7183
  0,
6926
7184
  16
6927
7185
  );
@@ -6938,7 +7196,7 @@ var DataVaultService = class extends BaseService {
6938
7196
  [VaultHeaders.KEY_ID]: newKeyId
6939
7197
  };
6940
7198
  const keyPayload = JSON.stringify({
6941
- key: base64Encode(newKeyBlob),
7199
+ key: base64Encode2(newKeyBlob),
6942
7200
  metadata: JSON.stringify({
6943
7201
  keyId: newKeyId,
6944
7202
  ...metadata
@@ -6952,7 +7210,7 @@ var DataVaultService = class extends BaseService {
6952
7210
  return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
6953
7211
  }
6954
7212
  const valuePayload = JSON.stringify({
6955
- data: base64Encode(encrypted),
7213
+ data: base64Encode2(encrypted),
6956
7214
  metadata
6957
7215
  });
6958
7216
  const valuePutResult = await this.tc.kv.put(
@@ -7244,116 +7502,6 @@ var SecretsService = class {
7244
7502
  }
7245
7503
  };
7246
7504
 
7247
- // src/encryption/canonical.ts
7248
- function canonicalize(value) {
7249
- if (value === void 0) {
7250
- return "";
7251
- }
7252
- return stringify(value);
7253
- }
7254
- function stringify(value) {
7255
- if (value === null) return "null";
7256
- switch (typeof value) {
7257
- case "boolean":
7258
- case "number":
7259
- return JSON.stringify(value);
7260
- case "string":
7261
- return JSON.stringify(value);
7262
- case "object": {
7263
- if (Array.isArray(value)) {
7264
- return `[${value.map(stringify).join(",")}]`;
7265
- }
7266
- const keys = Object.keys(value).sort();
7267
- const parts = [];
7268
- for (const k of keys) {
7269
- const v = value[k];
7270
- if (v === void 0) continue;
7271
- parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
7272
- }
7273
- return `{${parts.join(",")}}`;
7274
- }
7275
- default:
7276
- throw new TypeError(
7277
- `canonicalize: unsupported value type ${typeof value}`
7278
- );
7279
- }
7280
- }
7281
- var HEX = "0123456789abcdef";
7282
- function hexEncode2(bytes) {
7283
- let out = "";
7284
- for (let i = 0; i < bytes.length; i++) {
7285
- const b = bytes[i];
7286
- out += HEX[b >> 4 & 15] + HEX[b & 15];
7287
- }
7288
- return out;
7289
- }
7290
- function hexDecode(hex) {
7291
- if (hex.length % 2 !== 0) {
7292
- throw new Error("hex string must have even length");
7293
- }
7294
- const out = new Uint8Array(hex.length / 2);
7295
- for (let i = 0; i < out.length; i++) {
7296
- const hi = parseInt(hex[i * 2], 16);
7297
- const lo = parseInt(hex[i * 2 + 1], 16);
7298
- if (Number.isNaN(hi) || Number.isNaN(lo)) {
7299
- throw new Error("invalid hex character");
7300
- }
7301
- out[i] = hi << 4 | lo;
7302
- }
7303
- return out;
7304
- }
7305
- function base64Encode2(bytes) {
7306
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7307
- let out = "";
7308
- for (let i = 0; i < bytes.length; i += 3) {
7309
- const b0 = bytes[i];
7310
- const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
7311
- const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
7312
- out += chars[b0 >> 2 & 63];
7313
- out += chars[(b0 << 4 | b1 >> 4) & 63];
7314
- out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
7315
- out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
7316
- }
7317
- return out;
7318
- }
7319
- function base64Decode2(s) {
7320
- const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
7321
- const len = clean.length;
7322
- if (len % 4 !== 0) {
7323
- throw new Error("invalid base64 input");
7324
- }
7325
- const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
7326
- const outLen = len / 4 * 3 - padding;
7327
- const out = new Uint8Array(outLen);
7328
- const lookup = {};
7329
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7330
- for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
7331
- let outIdx = 0;
7332
- for (let i = 0; i < len; i += 4) {
7333
- const v0 = lookup[clean[i]] ?? 0;
7334
- const v1 = lookup[clean[i + 1]] ?? 0;
7335
- const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
7336
- const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
7337
- const b0 = v0 << 2 | v1 >> 4;
7338
- const b1 = (v1 & 15) << 4 | v2 >> 2;
7339
- const b2 = (v2 & 3) << 6 | v3;
7340
- if (outIdx < outLen) out[outIdx++] = b0;
7341
- if (outIdx < outLen) out[outIdx++] = b1;
7342
- if (outIdx < outLen) out[outIdx++] = b2;
7343
- }
7344
- return out;
7345
- }
7346
- function utf8Encode(s) {
7347
- return new TextEncoder().encode(s);
7348
- }
7349
- function utf8Decode(b) {
7350
- return new TextDecoder().decode(b);
7351
- }
7352
- function canonicalHashHex(sha256, value) {
7353
- const canonical = canonicalize(value);
7354
- return hexEncode2(sha256(utf8Encode(canonical)));
7355
- }
7356
-
7357
7505
  // src/encryption/types.ts
7358
7506
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
7359
7507
  var ENVELOPE_VERSION = 1;
@@ -7563,7 +7711,7 @@ function encryptToNetwork(crypto2, input) {
7563
7711
  const symmetricKey = crypto2.randomBytes(32);
7564
7712
  const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
7565
7713
  const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
7566
- const encryptedSymmetricKey = base64Encode2(wrapped);
7714
+ const encryptedSymmetricKey = base64Encode(wrapped);
7567
7715
  const encryptedSymmetricKeyHash = canonicalHashHex(
7568
7716
  crypto2.sha256,
7569
7717
  encryptedSymmetricKey
@@ -7575,8 +7723,8 @@ function encryptToNetwork(crypto2, input) {
7575
7723
  keyVersion,
7576
7724
  encryptedSymmetricKey,
7577
7725
  encryptedSymmetricKeyHash,
7578
- ciphertext: base64Encode2(ciphertext),
7579
- ...input.aad !== void 0 ? { aad: base64Encode2(input.aad) } : {},
7726
+ ciphertext: base64Encode(ciphertext),
7727
+ ...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
7580
7728
  ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
7581
7729
  };
7582
7730
  return { envelope, symmetricKey };
@@ -7650,8 +7798,8 @@ function validateEnvelope(crypto2, envelope) {
7650
7798
  return { ok: true, data: e };
7651
7799
  }
7652
7800
  function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
7653
- const ciphertext = base64Decode2(envelope.ciphertext);
7654
- const aad = envelope.aad !== void 0 ? base64Decode2(envelope.aad) : void 0;
7801
+ const ciphertext = base64Decode(envelope.ciphertext);
7802
+ const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
7655
7803
  return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
7656
7804
  }
7657
7805
 
@@ -7669,7 +7817,7 @@ function buildCanonicalDecryptRequest(input) {
7669
7817
  return { canonicalBody, bodyHash, receiverPublicKeyHash };
7670
7818
  }
7671
7819
  function buildDecryptFacts(input) {
7672
- const bodyHash = input.canonicalBody !== void 0 ? hexEncode2(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7820
+ const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7673
7821
  input.crypto.sha256,
7674
7822
  input.body
7675
7823
  );
@@ -7952,7 +8100,7 @@ function verifyDecryptResponse(input) {
7952
8100
  })
7953
8101
  };
7954
8102
  }
7955
- const expectedRequestHash = hexEncode2(
8103
+ const expectedRequestHash = hexEncode(
7956
8104
  crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
7957
8105
  );
7958
8106
  if (response.requestHash !== expectedRequestHash) {
@@ -7976,7 +8124,7 @@ function verifyDecryptResponse(input) {
7976
8124
  const signedBytes = new TextEncoder().encode(
7977
8125
  canonicalSignedResponse(response)
7978
8126
  );
7979
- const signatureBytes = base64Decode2(response.nodeSignature);
8127
+ const signatureBytes = base64Decode(response.nodeSignature);
7980
8128
  if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
7981
8129
  return {
7982
8130
  ok: false,
@@ -7988,7 +8136,7 @@ function verifyDecryptResponse(input) {
7988
8136
  return { ok: true, data: response };
7989
8137
  }
7990
8138
  function openWrappedKey(crypto2, receiverPrivateKey, response) {
7991
- const wrapped = base64Decode2(response.wrappedKey);
8139
+ const wrapped = base64Decode(response.wrappedKey);
7992
8140
  return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
7993
8141
  }
7994
8142
 
@@ -8032,7 +8180,7 @@ var EncryptionService = class extends BaseService {
8032
8180
  const usable = ensureNetworkUsableForDecrypt(discovered.data);
8033
8181
  if (!usable.ok) return usable;
8034
8182
  const descriptor = usable.data;
8035
- const networkPublicKey = base64Decode2(descriptor.publicEncryptionKey);
8183
+ const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
8036
8184
  const result = encryptToNetwork(this.crypto, {
8037
8185
  // Discovery returns the node's canonical network identifier. Persist
8038
8186
  // that spelling in the envelope so a later response can be compared
@@ -8060,7 +8208,7 @@ var EncryptionService = class extends BaseService {
8060
8208
  this.assertActive();
8061
8209
  const validated = validateEnvelope(this.crypto, envelope);
8062
8210
  if (!validated.ok) return validated;
8063
- if (options?.aad !== void 0 && validated.data.aad !== base64Encode2(options.aad)) {
8211
+ if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
8064
8212
  return encErr(
8065
8213
  encryptionError({
8066
8214
  code: "INVALID_INPUT",
@@ -8088,7 +8236,7 @@ var EncryptionService = class extends BaseService {
8088
8236
  );
8089
8237
  }
8090
8238
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
8091
- const receiverPublicKey = base64Encode2(receiverKey.publicKey);
8239
+ const receiverPublicKey = base64Encode(receiverKey.publicKey);
8092
8240
  const receiverPublicKeyHash = canonicalHashHex(
8093
8241
  this.crypto.sha256,
8094
8242
  receiverPublicKey
@@ -8264,8 +8412,8 @@ export {
8264
8412
  authRequiredError,
8265
8413
  authUnauthorizedError,
8266
8414
  authorizationVerdictOf,
8267
- base64Decode2 as base64Decode,
8268
- base64Encode2 as base64Encode,
8415
+ base64Decode,
8416
+ base64Encode,
8269
8417
  buildCanonicalDecryptRequest,
8270
8418
  buildDecryptAttenuation,
8271
8419
  buildDecryptFacts,
@@ -8295,13 +8443,14 @@ export {
8295
8443
  generateRandomReceiverKey,
8296
8444
  getTinyCloudDebugLogs,
8297
8445
  hexDecode,
8298
- hexEncode2 as hexEncode,
8446
+ hexEncode,
8299
8447
  installTinyCloudDebugGlobals,
8300
8448
  isNetworkId,
8301
8449
  isStorageFullError,
8302
8450
  kvPrefixCovers,
8303
8451
  networkDiscoveryKey,
8304
8452
  networkError,
8453
+ nodeRetryError,
8305
8454
  notFoundError,
8306
8455
  ok,
8307
8456
  openWrappedKey,