@tinycloud/sdk-services 3.3.0-beta.7 → 3.3.0

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 (39) hide show
  1. package/dist/{BaseService-Cc6Ueicm.d.cts → BaseService-BhfKzKke.d.cts} +5 -1
  2. package/dist/{BaseService-DOdXd7sr.d.ts → BaseService-x4AFZiEa.d.ts} +5 -1
  3. package/dist/encryption/index.cjs +18 -5
  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 +18 -5
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +911 -797
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +95 -11
  12. package/dist/index.d.ts +95 -11
  13. package/dist/index.js +911 -797
  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 +72 -80
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +4 -5
  20. package/dist/kv/index.d.ts +4 -5
  21. package/dist/kv/index.js +72 -80
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/index.cjs +6 -5
  24. package/dist/kv/replication/index.cjs.map +1 -1
  25. package/dist/kv/replication/index.d.cts +3 -3
  26. package/dist/kv/replication/index.d.ts +3 -3
  27. package/dist/kv/replication/index.js +6 -5
  28. package/dist/kv/replication/index.js.map +1 -1
  29. package/dist/{scope-B-m6cKH-.d.ts → scope-BHTkk5Yk.d.ts} +13 -2
  30. package/dist/{scope-B7oeKdLM.d.cts → scope-DMhalRFn.d.cts} +13 -2
  31. package/dist/sql/index.cjs +30 -16
  32. package/dist/sql/index.cjs.map +1 -1
  33. package/dist/sql/index.d.cts +2 -2
  34. package/dist/sql/index.d.ts +2 -2
  35. package/dist/sql/index.js +30 -16
  36. package/dist/sql/index.js.map +1 -1
  37. package/dist/{types-BjH0ZtGy.d.cts → types-eVS1Whuf.d.cts} +24 -4
  38. package/dist/{types-BjH0ZtGy.d.ts → types-eVS1Whuf.d.ts} +24 -4
  39. package/package.json +2 -2
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,7 +115,7 @@ __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,
@@ -182,7 +182,7 @@ var ErrorCodes = {
182
182
  PERMISSION_DENIED: "PERMISSION_DENIED",
183
183
  // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
184
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`. */
185
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
186
186
  NODE_BUSY: "NODE_BUSY",
187
187
  /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
188
188
  REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
@@ -223,11 +223,12 @@ var ErrorCodes = {
223
223
  DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
224
224
  };
225
225
  var defaultRetryPolicy = {
226
- maxAttempts: 3,
226
+ maxAttempts: 4,
227
227
  backoff: "exponential",
228
- baseDelayMs: 1e3,
229
- maxDelayMs: 1e4,
230
- retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
228
+ baseDelayMs: 250,
229
+ maxDelayMs: 4e3,
230
+ retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],
231
+ maxTotalMs: 12e3
231
232
  };
232
233
  var TelemetryEvents = {
233
234
  SPAN: "telemetry.span",
@@ -654,7 +655,9 @@ var RetryPolicySchema = import_zod.z.object({
654
655
  /** Maximum delay in milliseconds between retries */
655
656
  maxDelayMs: import_zod.z.number().nonnegative(),
656
657
  /** Error codes that should trigger a retry */
657
- retryableErrors: import_zod.z.array(import_zod.z.string())
658
+ retryableErrors: import_zod.z.array(import_zod.z.string()),
659
+ /** Total time budget for all attempts and waits, in milliseconds */
660
+ maxTotalMs: import_zod.z.number().positive().optional()
658
661
  });
659
662
  var ServiceSessionSchema = import_zod.z.object({
660
663
  /** The delegation header containing the UCAN */
@@ -791,448 +794,107 @@ function validateTelemetrySpanEvent(data) {
791
794
  return { ok: true, data: result.data };
792
795
  }
793
796
 
794
- // src/context.ts
795
- var ServiceContext = class {
796
- constructor(config) {
797
- this._session = null;
798
- this._services = /* @__PURE__ */ new Map();
799
- this._eventHandlers = /* @__PURE__ */ new Map();
800
- this._abortController = new AbortController();
801
- this._retired = false;
802
- this._invoke = this.wrapInvoke(config.invoke);
803
- this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
804
- this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
805
- this._hosts = config.hosts;
806
- this._session = config.session ?? null;
807
- this._retryPolicy = {
808
- ...defaultRetryPolicy,
809
- ...config.retryPolicy
810
- };
811
- this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
812
- this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
813
- }
814
- // ============================================================
815
- // Session Management
816
- // ============================================================
817
- /**
818
- * Get the current session.
819
- */
820
- get session() {
821
- return this._retired ? null : this._session;
797
+ // src/responseErrors.ts
798
+ function parseServiceErrorBody(errorText) {
799
+ try {
800
+ return JSON.parse(errorText);
801
+ } catch {
802
+ return {};
822
803
  }
823
- /**
824
- * Check if the context has an authenticated session.
825
- */
826
- get isAuthenticated() {
827
- return !this._retired && this._session !== null;
804
+ }
805
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
806
+ if (parsed.message) {
807
+ return compactMessage(parsed.message);
828
808
  }
829
- /**
830
- * Update the session and notify all registered services.
831
- *
832
- * @param session - New session or null to clear
833
- */
834
- setSession(session) {
835
- this.assertActive();
836
- this._session = session;
837
- this.emit("session.changed", { authenticated: session !== null });
838
- for (const service of this._services.values()) {
839
- service.onSessionChange(session);
809
+ if (looksLikeHtml(errorText)) {
810
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
811
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
840
812
  }
813
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
841
814
  }
842
- // ============================================================
843
- // Platform Dependencies
844
- // ============================================================
845
- /**
846
- * Get the invoke function for WASM operations.
847
- */
848
- get invoke() {
849
- this.assertActive();
850
- return this._invoke;
851
- }
852
- /**
853
- * Get the multi-resource invoke function when available.
854
- */
855
- get invokeAny() {
856
- this.assertActive();
857
- return this._invokeAny;
815
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
816
+ }
817
+ function responseErrorMeta(status, statusText, errorText) {
818
+ const meta = { status, statusText };
819
+ const snippet = compactMessage(errorText);
820
+ if (snippet) {
821
+ meta.responseSnippet = snippet.slice(0, 300);
858
822
  }
859
- /**
860
- * Get the fetch function for HTTP requests.
861
- */
862
- get fetch() {
863
- this.assertActive();
864
- return this._fetch;
823
+ return meta;
824
+ }
825
+ function looksLikeHtml(text) {
826
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
827
+ }
828
+ function compactMessage(message) {
829
+ const compact = message.replace(/\s+/g, " ").trim();
830
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
831
+ }
832
+
833
+ // src/errors.ts
834
+ var import_bootstrap = require("@tinycloud/bootstrap");
835
+
836
+ // src/encryption/networkId.ts
837
+ var URN_PREFIX = "urn:tinycloud:encryption:";
838
+ var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
839
+ var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
840
+ var NetworkIdError = class extends Error {
841
+ constructor(message) {
842
+ super(message);
843
+ this.name = "NetworkIdError";
865
844
  }
866
- /**
867
- * Get the list of TinyCloud host URLs.
868
- */
869
- get hosts() {
870
- this.assertActive();
871
- return this._hosts;
845
+ };
846
+ function parseNetworkId(networkId) {
847
+ if (typeof networkId !== "string" || networkId.length === 0) {
848
+ throw new NetworkIdError("networkId must be a non-empty string");
872
849
  }
873
- // ============================================================
874
- // Service Registry
875
- // ============================================================
876
- /**
877
- * Register a service with the context.
878
- *
879
- * @param name - Service name (e.g., 'kv')
880
- * @param service - Service instance
881
- */
882
- registerService(name, service) {
883
- this._services.set(name, service);
850
+ if (!networkId.startsWith(URN_PREFIX)) {
851
+ throw new NetworkIdError(
852
+ `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
853
+ );
884
854
  }
885
- /**
886
- * Unregister a service from the context.
887
- *
888
- * @param name - Service name to remove
889
- */
890
- unregisterService(name) {
891
- this._services.delete(name);
855
+ const body = networkId.slice(URN_PREFIX.length);
856
+ const lastColon = body.lastIndexOf(":");
857
+ if (lastColon <= 0 || lastColon === body.length - 1) {
858
+ throw new NetworkIdError(
859
+ `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
860
+ );
892
861
  }
893
- /**
894
- * Get a registered service by name.
895
- *
896
- * @param name - Service name
897
- * @returns The service instance or undefined if not registered
898
- */
899
- getService(name) {
900
- return this._services.get(name);
862
+ const ownerDid = body.slice(0, lastColon);
863
+ const name = body.slice(lastColon + 1);
864
+ if (!ownerDid.startsWith("did:")) {
865
+ throw new NetworkIdError(
866
+ `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
867
+ );
901
868
  }
902
- // ============================================================
903
- // Event System (Telemetry)
904
- // ============================================================
905
- /**
906
- * Emit a telemetry event.
907
- *
908
- * @param event - Event name
909
- * @param data - Event data
910
- */
911
- emit(event, data) {
912
- tinyCloudDebugLogger.log(event, data);
913
- if (this._telemetryEnabled && this._telemetryHandler) {
914
- try {
915
- this._telemetryHandler(event, projectDiagnosticData(data));
916
- } catch (error) {
917
- console.error(`Error in telemetry handler for "${event}":`, error);
918
- }
919
- }
920
- const handlers = this._eventHandlers.get(event);
921
- if (handlers) {
922
- for (const handler of handlers) {
923
- try {
924
- handler(data);
925
- } catch (error) {
926
- console.error(`Error in event handler for "${event}":`, error);
927
- }
928
- }
929
- }
869
+ const didParts = ownerDid.split(":");
870
+ if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
871
+ throw new NetworkIdError(
872
+ `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
873
+ );
930
874
  }
931
- /**
932
- * Subscribe to telemetry events.
933
- *
934
- * @param event - Event name to subscribe to
935
- * @param handler - Handler function
936
- * @returns Unsubscribe function
937
- */
938
- on(event, handler) {
939
- if (!this._eventHandlers.has(event)) {
940
- this._eventHandlers.set(event, /* @__PURE__ */ new Set());
941
- }
942
- this._eventHandlers.get(event).add(handler);
943
- return () => {
944
- const handlers = this._eventHandlers.get(event);
945
- if (handlers) {
946
- handlers.delete(handler);
947
- if (handlers.size === 0) {
948
- this._eventHandlers.delete(event);
949
- }
950
- }
951
- };
875
+ if (!NETWORK_NAME_RE.test(name)) {
876
+ throw new NetworkIdError(
877
+ `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
878
+ );
952
879
  }
953
- /**
954
- * Remove all event handlers for an event.
955
- *
956
- * @param event - Event name (if omitted, clears all events)
957
- */
958
- clearEventHandlers(event) {
959
- if (event) {
960
- this._eventHandlers.delete(event);
961
- } else {
962
- this._eventHandlers.clear();
963
- }
880
+ return { networkId, ownerDid, name };
881
+ }
882
+ function buildNetworkId(ownerDid, name) {
883
+ if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
884
+ throw new NetworkIdError("ownerDid must be a DID");
964
885
  }
965
- // ============================================================
966
- // Lifecycle
967
- // ============================================================
968
- /**
969
- * Get the abort signal for cancelling operations.
970
- */
971
- get abortSignal() {
972
- return this._abortController.signal;
886
+ if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
887
+ throw new NetworkIdError(
888
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
889
+ );
973
890
  }
974
- get operationAbortSignal() {
975
- return this._operationAbortSignal;
976
- }
977
- setOperationAbortSignal(signal) {
978
- this._operationAbortSignal = signal;
979
- }
980
- /**
981
- * Permanently retire this graph after its owner installs a replacement.
982
- * Unlike `abort()`, retirement never creates a fresh controller, so captured
983
- * services cannot resume calls under a superseded session.
984
- */
985
- retire() {
986
- if (this._retired) return;
987
- this._retired = true;
988
- this._session = null;
989
- this._abortController.abort();
990
- for (const service of this._services.values()) {
991
- try {
992
- service.onSignOut();
993
- } catch (error) {
994
- console.error("Error retiring service:", error);
995
- }
996
- }
997
- }
998
- /**
999
- * Abort all pending operations and notify services.
1000
- * Creates a new AbortController for future operations.
1001
- */
1002
- abort() {
1003
- if (this._retired) return;
1004
- this._abortController.abort();
1005
- this._abortController = new AbortController();
1006
- for (const service of this._services.values()) {
1007
- try {
1008
- service.onSignOut();
1009
- } catch (error) {
1010
- console.error("Error signing out service:", error);
1011
- }
1012
- }
1013
- }
1014
- /**
1015
- * Sign out - abort operations and clear session.
1016
- */
1017
- signOut() {
1018
- if (this._retired) return;
1019
- this.abort();
1020
- this.setSession(null);
1021
- this.emit("session.expired", {});
1022
- }
1023
- // ============================================================
1024
- // Retry Policy
1025
- // ============================================================
1026
- /**
1027
- * Get the retry policy configuration.
1028
- */
1029
- get retryPolicy() {
1030
- return this._retryPolicy;
1031
- }
1032
- wrapInvoke(invoke) {
1033
- return (session, service, path, action, facts) => {
1034
- this.assertActive();
1035
- if (!tinyCloudDebugLogger.isEnabled()) {
1036
- return invoke(session, service, path, action, facts);
1037
- }
1038
- const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
1039
- service,
1040
- path,
1041
- action,
1042
- facts
1043
- });
1044
- try {
1045
- const result = invoke(session, service, path, action, facts);
1046
- timer.stop({ ok: true, service, path, action });
1047
- return result;
1048
- } catch (error) {
1049
- timer.stop({ ok: false, service, path, action, error });
1050
- throw error;
1051
- }
1052
- };
1053
- }
1054
- assertActive() {
1055
- if (this._retired) {
1056
- throw new Error("Service graph has been retired: its session was replaced or signed out.");
1057
- }
1058
- }
1059
- wrapInvokeAny(invokeAny) {
1060
- return (session, entries, facts) => {
1061
- this.assertActive();
1062
- if (!tinyCloudDebugLogger.isEnabled()) {
1063
- return invokeAny(session, entries, facts);
1064
- }
1065
- const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
1066
- entries,
1067
- facts
1068
- });
1069
- try {
1070
- const result = invokeAny(session, entries, facts);
1071
- timer.stop({ ok: true, entries });
1072
- return result;
1073
- } catch (error) {
1074
- timer.stop({ ok: false, entries, error });
1075
- throw error;
1076
- }
1077
- };
1078
- }
1079
- wrapFetch(fetchFn) {
1080
- return async (url, init) => {
1081
- this.assertActive();
1082
- const request = {
1083
- ...init,
1084
- signal: this.combineSignals(init?.signal)
1085
- };
1086
- if (!tinyCloudDebugLogger.isEnabled()) {
1087
- return fetchFn(url, request);
1088
- }
1089
- const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
1090
- url,
1091
- method: request.method ?? "GET",
1092
- init: request
1093
- });
1094
- try {
1095
- const response = await fetchFn(url, request);
1096
- timer.stop({
1097
- ok: response.ok,
1098
- url,
1099
- method: request.method ?? "GET",
1100
- status: response.status,
1101
- statusText: response.statusText
1102
- });
1103
- return response;
1104
- } catch (error) {
1105
- timer.stop({
1106
- ok: false,
1107
- url,
1108
- method: request.method ?? "GET",
1109
- error
1110
- });
1111
- throw error;
1112
- }
1113
- };
1114
- }
1115
- combineSignals(signal) {
1116
- if (!signal) return this._abortController.signal;
1117
- const combined = new AbortController();
1118
- const abort = (source) => combined.abort(source.reason);
1119
- if (signal.aborted) {
1120
- abort(signal);
1121
- } else if (this._abortController.signal.aborted) {
1122
- abort(this._abortController.signal);
1123
- } else {
1124
- signal.addEventListener("abort", () => abort(signal), { once: true });
1125
- this._abortController.signal.addEventListener(
1126
- "abort",
1127
- () => abort(this._abortController.signal),
1128
- { once: true }
1129
- );
1130
- }
1131
- return combined.signal;
1132
- }
1133
- };
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
-
1171
- // src/errors.ts
1172
- var import_bootstrap = require("@tinycloud/bootstrap");
1173
-
1174
- // src/encryption/networkId.ts
1175
- var URN_PREFIX = "urn:tinycloud:encryption:";
1176
- var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
1177
- var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
1178
- var NetworkIdError = class extends Error {
1179
- constructor(message) {
1180
- super(message);
1181
- this.name = "NetworkIdError";
1182
- }
1183
- };
1184
- function parseNetworkId(networkId) {
1185
- if (typeof networkId !== "string" || networkId.length === 0) {
1186
- throw new NetworkIdError("networkId must be a non-empty string");
1187
- }
1188
- if (!networkId.startsWith(URN_PREFIX)) {
1189
- throw new NetworkIdError(
1190
- `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
1191
- );
1192
- }
1193
- const body = networkId.slice(URN_PREFIX.length);
1194
- const lastColon = body.lastIndexOf(":");
1195
- if (lastColon <= 0 || lastColon === body.length - 1) {
1196
- throw new NetworkIdError(
1197
- `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
1198
- );
1199
- }
1200
- const ownerDid = body.slice(0, lastColon);
1201
- const name = body.slice(lastColon + 1);
1202
- if (!ownerDid.startsWith("did:")) {
1203
- throw new NetworkIdError(
1204
- `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
1205
- );
1206
- }
1207
- const didParts = ownerDid.split(":");
1208
- if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
1209
- throw new NetworkIdError(
1210
- `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
1211
- );
1212
- }
1213
- if (!NETWORK_NAME_RE.test(name)) {
1214
- throw new NetworkIdError(
1215
- `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1216
- );
1217
- }
1218
- return { networkId, ownerDid, name };
1219
- }
1220
- function buildNetworkId(ownerDid, name) {
1221
- if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
1222
- throw new NetworkIdError("ownerDid must be a DID");
1223
- }
1224
- if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
1225
- throw new NetworkIdError(
1226
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1227
- );
1228
- }
1229
- const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
1230
- parseNetworkId(networkId);
1231
- return networkId;
1232
- }
1233
- function isNetworkId(networkId) {
1234
- if (typeof networkId !== "string") {
1235
- return false;
891
+ const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
892
+ parseNetworkId(networkId);
893
+ return networkId;
894
+ }
895
+ function isNetworkId(networkId) {
896
+ if (typeof networkId !== "string") {
897
+ return false;
1236
898
  }
1237
899
  try {
1238
900
  parseNetworkId(networkId);
@@ -1331,7 +993,7 @@ function nodeRetryError(service, message, response, errorText) {
1331
993
  const body = errorText.trim();
1332
994
  const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
1333
995
  let code;
1334
- if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
996
+ if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable" || token === "read_audit_busy")) {
1335
997
  code = ErrorCodes.NODE_BUSY;
1336
998
  } else if (response.status === 503 && token === "request_timeout") {
1337
999
  code = ErrorCodes.REQUEST_TIMEOUT;
@@ -1580,25 +1242,411 @@ function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1580
1242
  if (error.name === "AbortError") {
1581
1243
  return abortedError(service);
1582
1244
  }
1583
- if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
1584
- return timeoutError(service);
1245
+ if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
1246
+ return timeoutError(service);
1247
+ }
1248
+ return {
1249
+ code: defaultCode,
1250
+ message: error.message,
1251
+ service,
1252
+ cause: error
1253
+ };
1254
+ }
1255
+ return {
1256
+ code: defaultCode,
1257
+ message: String(error),
1258
+ service
1259
+ };
1260
+ }
1261
+ function errorResult(error) {
1262
+ return err(error);
1263
+ }
1264
+
1265
+ // src/sleep.ts
1266
+ var abortableSleep = (ms, signal, unref = false) => new Promise((resolve) => {
1267
+ const done = () => {
1268
+ clearTimeout(timer);
1269
+ signal.removeEventListener("abort", done);
1270
+ resolve();
1271
+ };
1272
+ const timer = setTimeout(done, ms);
1273
+ if (unref) timer.unref?.();
1274
+ signal.aborted ? done() : signal.addEventListener("abort", done);
1275
+ });
1276
+
1277
+ // src/context.ts
1278
+ var ServiceContext = class {
1279
+ constructor(config) {
1280
+ this._session = null;
1281
+ this._services = /* @__PURE__ */ new Map();
1282
+ this._eventHandlers = /* @__PURE__ */ new Map();
1283
+ this._abortController = new AbortController();
1284
+ this._retired = false;
1285
+ this._invoke = this.wrapInvoke(config.invoke);
1286
+ this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
1287
+ this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
1288
+ this._hosts = config.hosts;
1289
+ this._session = config.session ?? null;
1290
+ this._retryPolicy = {
1291
+ ...defaultRetryPolicy,
1292
+ ...config.retryPolicy
1293
+ };
1294
+ this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
1295
+ this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
1296
+ }
1297
+ // ============================================================
1298
+ // Session Management
1299
+ // ============================================================
1300
+ /**
1301
+ * Get the current session.
1302
+ */
1303
+ get session() {
1304
+ return this._retired ? null : this._session;
1305
+ }
1306
+ /**
1307
+ * Check if the context has an authenticated session.
1308
+ */
1309
+ get isAuthenticated() {
1310
+ return !this._retired && this._session !== null;
1311
+ }
1312
+ /**
1313
+ * Update the session and notify all registered services.
1314
+ *
1315
+ * @param session - New session or null to clear
1316
+ */
1317
+ setSession(session) {
1318
+ this.assertActive();
1319
+ this._session = session;
1320
+ this.emit("session.changed", { authenticated: session !== null });
1321
+ for (const service of this._services.values()) {
1322
+ service.onSessionChange(session);
1323
+ }
1324
+ }
1325
+ // ============================================================
1326
+ // Platform Dependencies
1327
+ // ============================================================
1328
+ /**
1329
+ * Get the invoke function for WASM operations.
1330
+ */
1331
+ get invoke() {
1332
+ this.assertActive();
1333
+ return this._invoke;
1334
+ }
1335
+ /**
1336
+ * Get the multi-resource invoke function when available.
1337
+ */
1338
+ get invokeAny() {
1339
+ this.assertActive();
1340
+ return this._invokeAny;
1341
+ }
1342
+ /**
1343
+ * Get the fetch function for HTTP requests.
1344
+ */
1345
+ get fetch() {
1346
+ this.assertActive();
1347
+ return this._fetch;
1348
+ }
1349
+ /**
1350
+ * Get the list of TinyCloud host URLs.
1351
+ */
1352
+ get hosts() {
1353
+ this.assertActive();
1354
+ return this._hosts;
1355
+ }
1356
+ // ============================================================
1357
+ // Service Registry
1358
+ // ============================================================
1359
+ /**
1360
+ * Register a service with the context.
1361
+ *
1362
+ * @param name - Service name (e.g., 'kv')
1363
+ * @param service - Service instance
1364
+ */
1365
+ registerService(name, service) {
1366
+ this._services.set(name, service);
1367
+ }
1368
+ /**
1369
+ * Unregister a service from the context.
1370
+ *
1371
+ * @param name - Service name to remove
1372
+ */
1373
+ unregisterService(name) {
1374
+ this._services.delete(name);
1375
+ }
1376
+ /**
1377
+ * Get a registered service by name.
1378
+ *
1379
+ * @param name - Service name
1380
+ * @returns The service instance or undefined if not registered
1381
+ */
1382
+ getService(name) {
1383
+ return this._services.get(name);
1384
+ }
1385
+ // ============================================================
1386
+ // Event System (Telemetry)
1387
+ // ============================================================
1388
+ /**
1389
+ * Emit a telemetry event.
1390
+ *
1391
+ * @param event - Event name
1392
+ * @param data - Event data
1393
+ */
1394
+ emit(event, data) {
1395
+ tinyCloudDebugLogger.log(event, data);
1396
+ if (this._telemetryEnabled && this._telemetryHandler) {
1397
+ try {
1398
+ this._telemetryHandler(event, projectDiagnosticData(data));
1399
+ } catch (error) {
1400
+ console.error(`Error in telemetry handler for "${event}":`, error);
1401
+ }
1402
+ }
1403
+ const handlers = this._eventHandlers.get(event);
1404
+ if (handlers) {
1405
+ for (const handler of handlers) {
1406
+ try {
1407
+ handler(data);
1408
+ } catch (error) {
1409
+ console.error(`Error in event handler for "${event}":`, error);
1410
+ }
1411
+ }
1412
+ }
1413
+ }
1414
+ /**
1415
+ * Subscribe to telemetry events.
1416
+ *
1417
+ * @param event - Event name to subscribe to
1418
+ * @param handler - Handler function
1419
+ * @returns Unsubscribe function
1420
+ */
1421
+ on(event, handler) {
1422
+ if (!this._eventHandlers.has(event)) {
1423
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
1424
+ }
1425
+ this._eventHandlers.get(event).add(handler);
1426
+ return () => {
1427
+ const handlers = this._eventHandlers.get(event);
1428
+ if (handlers) {
1429
+ handlers.delete(handler);
1430
+ if (handlers.size === 0) {
1431
+ this._eventHandlers.delete(event);
1432
+ }
1433
+ }
1434
+ };
1435
+ }
1436
+ /**
1437
+ * Remove all event handlers for an event.
1438
+ *
1439
+ * @param event - Event name (if omitted, clears all events)
1440
+ */
1441
+ clearEventHandlers(event) {
1442
+ if (event) {
1443
+ this._eventHandlers.delete(event);
1444
+ } else {
1445
+ this._eventHandlers.clear();
1446
+ }
1447
+ }
1448
+ // ============================================================
1449
+ // Lifecycle
1450
+ // ============================================================
1451
+ /**
1452
+ * Get the abort signal for cancelling operations.
1453
+ */
1454
+ get abortSignal() {
1455
+ return this._abortController.signal;
1456
+ }
1457
+ get operationAbortSignal() {
1458
+ return this._operationAbortSignal;
1459
+ }
1460
+ setOperationAbortSignal(signal) {
1461
+ this._operationAbortSignal = signal;
1462
+ }
1463
+ /**
1464
+ * Permanently retire this graph after its owner installs a replacement.
1465
+ * Unlike `abort()`, retirement never creates a fresh controller, so captured
1466
+ * services cannot resume calls under a superseded session.
1467
+ */
1468
+ retire() {
1469
+ if (this._retired) return;
1470
+ this._retired = true;
1471
+ this._session = null;
1472
+ this._abortController.abort();
1473
+ for (const service of this._services.values()) {
1474
+ try {
1475
+ service.onSignOut();
1476
+ } catch (error) {
1477
+ console.error("Error retiring service:", error);
1478
+ }
1479
+ }
1480
+ }
1481
+ /**
1482
+ * Abort all pending operations and notify services.
1483
+ * Creates a new AbortController for future operations.
1484
+ */
1485
+ abort() {
1486
+ if (this._retired) return;
1487
+ this._abortController.abort();
1488
+ this._abortController = new AbortController();
1489
+ for (const service of this._services.values()) {
1490
+ try {
1491
+ service.onSignOut();
1492
+ } catch (error) {
1493
+ console.error("Error signing out service:", error);
1494
+ }
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Sign out - abort operations and clear session.
1499
+ */
1500
+ signOut() {
1501
+ if (this._retired) return;
1502
+ this.abort();
1503
+ this.setSession(null);
1504
+ this.emit("session.expired", {});
1505
+ }
1506
+ // ============================================================
1507
+ // Retry Policy
1508
+ // ============================================================
1509
+ /**
1510
+ * Get the retry policy configuration.
1511
+ */
1512
+ get retryPolicy() {
1513
+ return this._retryPolicy;
1514
+ }
1515
+ wrapInvoke(invoke) {
1516
+ return (session, service, path, action, facts) => {
1517
+ this.assertActive();
1518
+ if (!tinyCloudDebugLogger.isEnabled()) {
1519
+ return invoke(session, service, path, action, facts);
1520
+ }
1521
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
1522
+ service,
1523
+ path,
1524
+ action,
1525
+ facts
1526
+ });
1527
+ try {
1528
+ const result = invoke(session, service, path, action, facts);
1529
+ timer.stop({ ok: true, service, path, action });
1530
+ return result;
1531
+ } catch (error) {
1532
+ timer.stop({ ok: false, service, path, action, error });
1533
+ throw error;
1534
+ }
1535
+ };
1536
+ }
1537
+ assertActive() {
1538
+ if (this._retired) {
1539
+ throw new Error("Service graph has been retired: its session was replaced or signed out.");
1540
+ }
1541
+ }
1542
+ wrapInvokeAny(invokeAny) {
1543
+ return (session, entries, facts) => {
1544
+ this.assertActive();
1545
+ if (!tinyCloudDebugLogger.isEnabled()) {
1546
+ return invokeAny(session, entries, facts);
1547
+ }
1548
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
1549
+ entries,
1550
+ facts
1551
+ });
1552
+ try {
1553
+ const result = invokeAny(session, entries, facts);
1554
+ timer.stop({ ok: true, entries });
1555
+ return result;
1556
+ } catch (error) {
1557
+ timer.stop({ ok: false, entries, error });
1558
+ throw error;
1559
+ }
1560
+ };
1561
+ }
1562
+ /** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */
1563
+ async invokeWithRetry(url, build) {
1564
+ this.assertActive();
1565
+ const policy = this._retryPolicy;
1566
+ const startedAt = Date.now();
1567
+ let backoffMs = 0;
1568
+ let signal;
1569
+ for (let attempt = 1; ; attempt++) {
1570
+ const init = await build();
1571
+ signal ?? (signal = this.combineSignals(init.signal));
1572
+ const response = await this._fetch(url, init);
1573
+ const done = () => {
1574
+ try {
1575
+ response.retry = { attempts: attempt, backoffMs };
1576
+ } catch {
1577
+ }
1578
+ return response;
1579
+ };
1580
+ if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();
1581
+ const text = await response.clone().text().catch(() => "");
1582
+ const error = nodeRetryError("", "", response, text);
1583
+ if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {
1584
+ return done();
1585
+ }
1586
+ const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);
1587
+ if (remaining <= 0) return done();
1588
+ const retryAfterMs = error.meta.retryAfterMs;
1589
+ const step = Math.min(
1590
+ policy.maxDelayMs,
1591
+ policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** (attempt - 1) : policy.backoff === "linear" ? policy.baseDelayMs * attempt : 0
1592
+ );
1593
+ const delay = Math.min(
1594
+ typeof retryAfterMs === "number" ? retryAfterMs : Math.random() * step,
1595
+ remaining
1596
+ );
1597
+ response.body?.cancel?.()?.catch?.(() => {
1598
+ });
1599
+ await abortableSleep(delay, signal);
1600
+ if (signal.aborted) throw signal.reason;
1601
+ backoffMs += delay;
1585
1602
  }
1586
- return {
1587
- code: defaultCode,
1588
- message: error.message,
1589
- service,
1590
- cause: error
1603
+ }
1604
+ wrapFetch(fetchFn) {
1605
+ return async (url, init) => {
1606
+ this.assertActive();
1607
+ const request = {
1608
+ ...init,
1609
+ signal: this.combineSignals(init?.signal)
1610
+ };
1611
+ if (!tinyCloudDebugLogger.isEnabled()) {
1612
+ return fetchFn(url, request);
1613
+ }
1614
+ const meta = { url, method: request.method ?? "GET" };
1615
+ const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", { ...meta, init: request });
1616
+ try {
1617
+ const response = await fetchFn(url, request);
1618
+ timer.stop({
1619
+ ...meta,
1620
+ ok: response.ok,
1621
+ status: response.status,
1622
+ statusText: response.statusText
1623
+ });
1624
+ return response;
1625
+ } catch (error) {
1626
+ timer.stop({ ...meta, ok: false, error });
1627
+ throw error;
1628
+ }
1591
1629
  };
1592
1630
  }
1593
- return {
1594
- code: defaultCode,
1595
- message: String(error),
1596
- service
1597
- };
1598
- }
1599
- function errorResult(error) {
1600
- return err(error);
1601
- }
1631
+ combineSignals(signal) {
1632
+ if (!signal) return this._abortController.signal;
1633
+ const combined = new AbortController();
1634
+ const abort = (source) => combined.abort(source.reason);
1635
+ if (signal.aborted) {
1636
+ abort(signal);
1637
+ } else if (this._abortController.signal.aborted) {
1638
+ abort(this._abortController.signal);
1639
+ } else {
1640
+ signal.addEventListener("abort", () => abort(signal), { once: true });
1641
+ this._abortController.signal.addEventListener(
1642
+ "abort",
1643
+ () => abort(this._abortController.signal),
1644
+ { once: true }
1645
+ );
1646
+ }
1647
+ return combined.signal;
1648
+ }
1649
+ };
1602
1650
 
1603
1651
  // src/base/BaseService.ts
1604
1652
  var MAX_TIMER_DELAY_MS = 2147483647;
@@ -1782,6 +1830,18 @@ var BaseService = class {
1782
1830
  }
1783
1831
  return controller.signal;
1784
1832
  }
1833
+ /** POST a signed invocation with retry; `mint` re-signs per attempt, `init.headers` add to the signed ones. */
1834
+ invokeWithRetry(url, mint, { headers: extra, ...init } = {}) {
1835
+ const build = () => {
1836
+ const signed = mint();
1837
+ return {
1838
+ method: "POST",
1839
+ ...init,
1840
+ headers: Array.isArray(signed) ? [...signed, ...Object.entries(extra ?? {})] : { ...signed, ...extra }
1841
+ };
1842
+ };
1843
+ return this.context.invokeWithRetry ? this.context.invokeWithRetry(url, build) : this.context.fetch(url, build());
1844
+ }
1785
1845
  /**
1786
1846
  * Create the abort signal for one request, bounded by the request timeout.
1787
1847
  *
@@ -2149,15 +2209,6 @@ var KVService = class extends BaseService {
2149
2209
  get host() {
2150
2210
  return this.context.hosts[0];
2151
2211
  }
2152
- withJsonContentType(headers) {
2153
- if (Array.isArray(headers)) {
2154
- return [...headers, ["content-type", "application/json"]];
2155
- }
2156
- return {
2157
- ...headers,
2158
- "content-type": "application/json"
2159
- };
2160
- }
2161
2212
  /**
2162
2213
  * Execute an invoke operation.
2163
2214
  *
@@ -2168,20 +2219,11 @@ var KVService = class extends BaseService {
2168
2219
  * @returns Fetch response
2169
2220
  */
2170
2221
  async invokeOperation(path, action, body, signal, extraHeaders) {
2171
- const session = this.context.session;
2172
- const headers = this.context.invoke(
2173
- session,
2174
- "kv",
2175
- path,
2176
- action
2222
+ return this.invokeWithRetry(
2223
+ `${this.host}/invoke`,
2224
+ () => this.context.invoke(this.context.session, "kv", path, action),
2225
+ { headers: extraHeaders, body, signal }
2177
2226
  );
2178
- const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
2179
- return this.context.fetch(`${this.host}/invoke`, {
2180
- method: "POST",
2181
- headers: requestHeaders,
2182
- body,
2183
- signal
2184
- });
2185
2227
  }
2186
2228
  /**
2187
2229
  * Serialize a single put value into a fetch body.
@@ -2387,28 +2429,28 @@ var KVService = class extends BaseService {
2387
2429
  }
2388
2430
  const request = this.createRequestSignal(options?.signal, options?.timeout);
2389
2431
  try {
2390
- const session = this.context.session;
2391
- const invocationHeaders = this.context.invokeAny(
2392
- session,
2393
- paths.map((path) => ({
2394
- spaceId: session.spaceId,
2395
- service: "kv",
2396
- path,
2397
- action
2398
- }))
2399
- );
2400
2432
  const limitHeaders = {};
2401
2433
  if (getOptions?.maxResponseBytes !== void 0) {
2402
2434
  limitHeaders["x-tinycloud-max-response-bytes"] = String(
2403
2435
  getOptions.maxResponseBytes
2404
2436
  );
2405
2437
  }
2406
- const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
2407
- const response = await this.context.fetch(`${this.host}/invoke`, {
2408
- method: "POST",
2409
- headers,
2410
- signal: request.signal
2411
- });
2438
+ const response = await this.invokeWithRetry(
2439
+ `${this.host}/invoke`,
2440
+ () => {
2441
+ const session = this.context.session;
2442
+ return this.context.invokeAny(
2443
+ session,
2444
+ paths.map((path) => ({
2445
+ spaceId: session.spaceId,
2446
+ service: "kv",
2447
+ path,
2448
+ action
2449
+ }))
2450
+ );
2451
+ },
2452
+ { headers: limitHeaders, signal: request.signal }
2453
+ );
2412
2454
  if (!response.ok) {
2413
2455
  const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
2414
2456
  if (response.status === 401 || response.status === 403) {
@@ -2454,10 +2496,10 @@ var KVService = class extends BaseService {
2454
2496
  result: err(serviceError(item.error.code, item.error.message, "kv"))
2455
2497
  };
2456
2498
  }
2457
- const headers2 = item.headers;
2499
+ const headers = item.headers;
2458
2500
  const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
2459
2501
  item.dataBase64,
2460
- headers2,
2502
+ headers,
2461
2503
  getOptions?.raw,
2462
2504
  getOptions?.binary
2463
2505
  );
@@ -2465,7 +2507,7 @@ var KVService = class extends BaseService {
2465
2507
  key: keys[index],
2466
2508
  result: ok({
2467
2509
  data,
2468
- headers: this.createBatchResponseHeaders(headers2)
2510
+ headers: this.createBatchResponseHeaders(headers)
2469
2511
  })
2470
2512
  };
2471
2513
  });
@@ -2700,7 +2742,7 @@ var KVService = class extends BaseService {
2700
2742
  ));
2701
2743
  }
2702
2744
  const data = await this.parseResponse(response, options?.raw, options?.binary);
2703
- return ok({ data, headers: this.createResponseHeaders(response.headers) });
2745
+ return ok({ data, headers: this.createResponseHeaders(response.headers), ...response.retry && { retry: response.retry } });
2704
2746
  }
2705
2747
  async batchGet(keys, options) {
2706
2748
  return this.withTelemetry(
@@ -2790,7 +2832,8 @@ var KVService = class extends BaseService {
2790
2832
  }
2791
2833
  return ok({
2792
2834
  data: void 0,
2793
- headers: this.createResponseHeaders(response.headers)
2835
+ headers: this.createResponseHeaders(response.headers),
2836
+ ...response.retry && { retry: response.retry }
2794
2837
  });
2795
2838
  }
2796
2839
  /**
@@ -2863,25 +2906,23 @@ var KVService = class extends BaseService {
2863
2906
  entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
2864
2907
  );
2865
2908
  }
2866
- const headers = this.context.invokeAny(
2867
- session,
2868
- paths.map((path) => ({
2869
- spaceId: session.spaceId,
2870
- service: "kv",
2871
- path,
2872
- action: KVAction.PUT
2873
- }))
2909
+ const response = await this.invokeWithRetry(
2910
+ `${this.host}/invoke`,
2911
+ () => {
2912
+ const headers = this.context.invokeAny(
2913
+ session,
2914
+ paths.map((path) => ({
2915
+ spaceId: session.spaceId,
2916
+ service: "kv",
2917
+ path,
2918
+ action: KVAction.PUT
2919
+ }))
2920
+ );
2921
+ requestMayHaveDispatched = true;
2922
+ return headers;
2923
+ },
2924
+ { body, signal }
2874
2925
  );
2875
- const url = `${this.host}/invoke`;
2876
- const init = {
2877
- method: "POST",
2878
- headers,
2879
- body,
2880
- signal
2881
- };
2882
- const fetchFn = this.context.fetch;
2883
- requestMayHaveDispatched = true;
2884
- const response = await fetchFn(url, init);
2885
2926
  if (!response.ok) {
2886
2927
  let errorText;
2887
2928
  try {
@@ -3351,7 +3392,8 @@ var KVService = class extends BaseService {
3351
3392
  }
3352
3393
  return ok({
3353
3394
  data: void 0,
3354
- headers: this.createResponseHeaders(response.headers)
3395
+ headers: this.createResponseHeaders(response.headers),
3396
+ ...response.retry && { retry: response.retry }
3355
3397
  });
3356
3398
  }
3357
3399
  /**
@@ -3425,12 +3467,6 @@ var KVService = class extends BaseService {
3425
3467
  }
3426
3468
  const path = this.getFullPath(key, options?.prefix);
3427
3469
  const session = this.context.session;
3428
- const headers = this.context.invoke(
3429
- session,
3430
- "kv",
3431
- path,
3432
- KVAction.GET
3433
- );
3434
3470
  const body = {
3435
3471
  space: session.spaceId,
3436
3472
  path,
@@ -3444,12 +3480,15 @@ var KVService = class extends BaseService {
3444
3480
  }
3445
3481
  const request = this.createRequestSignal(options?.signal, options?.timeout);
3446
3482
  try {
3447
- const response = await this.context.fetch(`${this.host}/signed/kv`, {
3448
- method: "POST",
3449
- headers: this.withJsonContentType(headers),
3450
- body: JSON.stringify(body),
3451
- signal: request.signal
3452
- });
3483
+ const response = await this.invokeWithRetry(
3484
+ `${this.host}/signed/kv`,
3485
+ () => this.context.invoke(session, "kv", path, KVAction.GET),
3486
+ {
3487
+ headers: { "content-type": "application/json" },
3488
+ body: JSON.stringify(body),
3489
+ signal: request.signal
3490
+ }
3491
+ );
3453
3492
  if (!response.ok) {
3454
3493
  return await this.createSignedReadUrlError(response, key, path);
3455
3494
  }
@@ -3946,18 +3985,19 @@ var SQLService = class extends BaseService {
3946
3985
  }
3947
3986
  // === Private helpers ===
3948
3987
  async invokeSQL(dbName, actions, body, signal) {
3949
- const session = this.context.session;
3950
3988
  const actionList = Array.isArray(actions) ? actions : [actions];
3951
- const headers = actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
3952
- return this.context.fetch(`${this.host}/invoke`, {
3953
- method: "POST",
3954
- headers: {
3955
- ...headers,
3956
- "Content-Type": "application/json"
3989
+ return this.invokeWithRetry(
3990
+ `${this.host}/invoke`,
3991
+ () => {
3992
+ const session = this.context.session;
3993
+ return actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
3957
3994
  },
3958
- body: JSON.stringify(body),
3959
- signal
3960
- });
3995
+ {
3996
+ headers: { "Content-Type": "application/json" },
3997
+ body: JSON.stringify(body),
3998
+ signal
3999
+ }
4000
+ );
3961
4001
  }
3962
4002
  actionForSql(sql, fallback) {
3963
4003
  const token = firstSqlToken(sql);
@@ -4418,22 +4458,20 @@ var DuckDbService = class extends BaseService {
4418
4458
  }
4419
4459
  const request = this.createRequestSignal(options?.signal);
4420
4460
  try {
4421
- const session = this.context.session;
4422
- const headers = this.context.invoke(
4423
- session,
4424
- "duckdb",
4425
- dbName,
4426
- DuckDbAction.IMPORT
4461
+ const response = await this.invokeWithRetry(
4462
+ `${this.host}/invoke`,
4463
+ () => this.context.invoke(
4464
+ this.context.session,
4465
+ "duckdb",
4466
+ dbName,
4467
+ DuckDbAction.IMPORT
4468
+ ),
4469
+ {
4470
+ headers: { "Content-Type": "application/x-duckdb" },
4471
+ body: new Blob([data]),
4472
+ signal: request.signal
4473
+ }
4427
4474
  );
4428
- const response = await this.context.fetch(`${this.host}/invoke`, {
4429
- method: "POST",
4430
- headers: {
4431
- ...headers,
4432
- "Content-Type": "application/x-duckdb"
4433
- },
4434
- body: new Blob([data]),
4435
- signal: request.signal
4436
- });
4437
4475
  if (!response.ok) {
4438
4476
  return await this.handleErrorResponse(response, "import");
4439
4477
  }
@@ -4447,18 +4485,15 @@ var DuckDbService = class extends BaseService {
4447
4485
  }
4448
4486
  // === Private helpers ===
4449
4487
  async invokeDuckDb(dbName, action, body, signal, extraHeaders) {
4450
- const session = this.context.session;
4451
- const headers = this.context.invoke(session, "duckdb", dbName, action);
4452
- return this.context.fetch(`${this.host}/invoke`, {
4453
- method: "POST",
4454
- headers: {
4455
- ...headers,
4456
- "Content-Type": "application/json",
4457
- ...extraHeaders
4458
- },
4459
- body: JSON.stringify(body),
4460
- signal
4461
- });
4488
+ return this.invokeWithRetry(
4489
+ `${this.host}/invoke`,
4490
+ () => this.context.invoke(this.context.session, "duckdb", dbName, action),
4491
+ {
4492
+ headers: { "Content-Type": "application/json", ...extraHeaders },
4493
+ body: JSON.stringify(body),
4494
+ signal
4495
+ }
4496
+ );
4462
4497
  }
4463
4498
  async handleErrorResponse(response, operation) {
4464
4499
  const errorText = await response.text();
@@ -4647,26 +4682,24 @@ var HooksService = class extends BaseService {
4647
4682
  );
4648
4683
  }
4649
4684
  try {
4650
- const response = await this.context.fetch(`${this.host}/hooks/webhooks`, {
4651
- method: "POST",
4652
- headers: {
4653
- ...serviceHeadersToRecord(
4654
- this.createHookHeaders(
4655
- "tinycloud.hooks/register",
4656
- buildScopePath(webhook.service, webhook.pathPrefix)
4657
- )
4658
- ),
4659
- "content-type": "application/json"
4660
- },
4661
- body: JSON.stringify({
4662
- space: webhook.space,
4663
- service: webhook.service,
4664
- pathPrefix: normalizePathPrefix(webhook.pathPrefix),
4665
- abilities: webhook.abilities ?? [],
4666
- callbackUrl: webhook.callbackUrl,
4667
- secret: webhook.secret
4668
- })
4669
- });
4685
+ const response = await this.invokeWithRetry(
4686
+ `${this.host}/hooks/webhooks`,
4687
+ () => this.createHookHeaders(
4688
+ "tinycloud.hooks/register",
4689
+ buildScopePath(webhook.service, webhook.pathPrefix)
4690
+ ),
4691
+ {
4692
+ headers: { "content-type": "application/json" },
4693
+ body: JSON.stringify({
4694
+ space: webhook.space,
4695
+ service: webhook.service,
4696
+ pathPrefix: normalizePathPrefix(webhook.pathPrefix),
4697
+ abilities: webhook.abilities ?? [],
4698
+ callbackUrl: webhook.callbackUrl,
4699
+ secret: webhook.secret
4700
+ })
4701
+ }
4702
+ );
4670
4703
  if (!response.ok) {
4671
4704
  return err(
4672
4705
  await responseError("hooks", "failed to register webhook", response)
@@ -4704,17 +4737,13 @@ var HooksService = class extends BaseService {
4704
4737
  query.set("prefix", normalizedPrefix);
4705
4738
  }
4706
4739
  }
4707
- const response = await this.context.fetch(
4740
+ const response = await this.invokeWithRetry(
4708
4741
  `${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
4709
- {
4710
- method: "GET",
4711
- headers: serviceHeadersToRecord(
4712
- this.createHookHeaders(
4713
- "tinycloud.hooks/list",
4714
- options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
4715
- )
4716
- )
4717
- }
4742
+ () => this.createHookHeaders(
4743
+ "tinycloud.hooks/list",
4744
+ options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
4745
+ ),
4746
+ { method: "GET" }
4718
4747
  );
4719
4748
  if (!response.ok) {
4720
4749
  return err(
@@ -4741,20 +4770,16 @@ var HooksService = class extends BaseService {
4741
4770
  return err(authRequiredError("hooks"));
4742
4771
  }
4743
4772
  try {
4744
- const response = await this.context.fetch(
4773
+ const response = await this.invokeWithRetry(
4745
4774
  `${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
4746
- {
4747
- method: "DELETE",
4748
- headers: serviceHeadersToRecord(
4749
- this.createHookHeaders(
4750
- "tinycloud.hooks/unregister",
4751
- options.target ? buildScopePath(
4752
- options.target.service,
4753
- options.target.pathPrefix
4754
- ) : `webhooks/${id}`
4755
- )
4756
- )
4757
- }
4775
+ () => this.createHookHeaders(
4776
+ "tinycloud.hooks/unregister",
4777
+ options.target ? buildScopePath(
4778
+ options.target.service,
4779
+ options.target.pathPrefix
4780
+ ) : `webhooks/${id}`
4781
+ ),
4782
+ { method: "DELETE" }
4758
4783
  );
4759
4784
  if (!response.ok) {
4760
4785
  return err(
@@ -5210,23 +5235,7 @@ function readProperty(value, key) {
5210
5235
  return void 0;
5211
5236
  }
5212
5237
  }
5213
- function sleep(delayMs, _attempt, signal) {
5214
- return new Promise((resolve) => {
5215
- const finish = () => {
5216
- clearTimeout(timer);
5217
- signal.removeEventListener("abort", stop);
5218
- resolve();
5219
- };
5220
- const timer = setTimeout(finish, delayMs);
5221
- timer.unref?.();
5222
- const stop = () => finish();
5223
- if (signal.aborted) {
5224
- finish();
5225
- } else {
5226
- signal.addEventListener("abort", stop, { once: true });
5227
- }
5228
- });
5229
- }
5238
+ var sleep = (delayMs, _attempt, signal) => abortableSleep(delayMs, signal, true);
5230
5239
  function abortPromise(signal) {
5231
5240
  let resolve;
5232
5241
  const promise = new Promise((done) => {
@@ -5611,7 +5620,7 @@ var StorageFullMonitor = class {
5611
5620
  var UNKNOWN_OWNER = "\0unknown";
5612
5621
 
5613
5622
  // src/storage/status.ts
5614
- var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/billing";
5623
+ var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/storage";
5615
5624
  var STORAGE_NEARLY_FULL_RATIO = 0.9;
5616
5625
  function storageUsageState(usedBytes, limitBytes) {
5617
5626
  if (limitBytes === null) return "ok";
@@ -5643,40 +5652,150 @@ function parseStorageStatus(body) {
5643
5652
  if (status.account.plan) status.plan = status.account.plan;
5644
5653
  status.state = storageUsageState(account.usedBytes, account.limitBytes);
5645
5654
  }
5646
- return status;
5655
+ return status;
5656
+ }
5657
+
5658
+ // src/quota/TinyCloudQuota.ts
5659
+ var TinyCloudQuota = class {
5660
+ constructor(config = {}) {
5661
+ this.quotaUrl = null;
5662
+ this.config = config;
5663
+ }
5664
+ /** Set the quota URL discovered from the /info endpoint */
5665
+ setQuotaUrl(url) {
5666
+ this.quotaUrl = url;
5667
+ }
5668
+ /** Whether a quota service is available */
5669
+ get available() {
5670
+ return this.quotaUrl !== null;
5671
+ }
5672
+ /** Query quota status for a space from the quota URL */
5673
+ async getQuota(spaceId) {
5674
+ if (!this.quotaUrl) return null;
5675
+ const resp = await fetch(
5676
+ `${this.quotaUrl}/api/quota/${encodeURIComponent(spaceId)}`
5677
+ );
5678
+ if (!resp.ok) return null;
5679
+ const data = await resp.json();
5680
+ return {
5681
+ limitBytes: data.storage_limit_bytes ?? 0
5682
+ };
5683
+ }
5684
+ /** Trigger the upgrade callback when a quota error is encountered */
5685
+ handleQuotaError(info) {
5686
+ this.config.onUpgradeRequired?.(info);
5687
+ }
5688
+ };
5689
+
5690
+ // src/encryption/canonical.ts
5691
+ function canonicalize(value) {
5692
+ if (value === void 0) {
5693
+ return "";
5694
+ }
5695
+ return stringify(value);
5696
+ }
5697
+ function stringify(value) {
5698
+ if (value === null) return "null";
5699
+ switch (typeof value) {
5700
+ case "boolean":
5701
+ case "number":
5702
+ return JSON.stringify(value);
5703
+ case "string":
5704
+ return JSON.stringify(value);
5705
+ case "object": {
5706
+ if (Array.isArray(value)) {
5707
+ return `[${value.map(stringify).join(",")}]`;
5708
+ }
5709
+ const keys = Object.keys(value).sort();
5710
+ const parts = [];
5711
+ for (const k of keys) {
5712
+ const v = value[k];
5713
+ if (v === void 0) continue;
5714
+ parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
5715
+ }
5716
+ return `{${parts.join(",")}}`;
5717
+ }
5718
+ default:
5719
+ throw new TypeError(
5720
+ `canonicalize: unsupported value type ${typeof value}`
5721
+ );
5722
+ }
5723
+ }
5724
+ var HEX = "0123456789abcdef";
5725
+ function hexEncode(bytes) {
5726
+ let out = "";
5727
+ for (let i = 0; i < bytes.length; i++) {
5728
+ const b = bytes[i];
5729
+ out += HEX[b >> 4 & 15] + HEX[b & 15];
5730
+ }
5731
+ return out;
5647
5732
  }
5648
-
5649
- // src/quota/TinyCloudQuota.ts
5650
- var TinyCloudQuota = class {
5651
- constructor(config = {}) {
5652
- this.quotaUrl = null;
5653
- this.config = config;
5733
+ function hexDecode(hex) {
5734
+ if (hex.length % 2 !== 0) {
5735
+ throw new Error("hex string must have even length");
5654
5736
  }
5655
- /** Set the quota URL discovered from the /info endpoint */
5656
- setQuotaUrl(url) {
5657
- this.quotaUrl = url;
5737
+ const out = new Uint8Array(hex.length / 2);
5738
+ for (let i = 0; i < out.length; i++) {
5739
+ const hi = parseInt(hex[i * 2], 16);
5740
+ const lo = parseInt(hex[i * 2 + 1], 16);
5741
+ if (Number.isNaN(hi) || Number.isNaN(lo)) {
5742
+ throw new Error("invalid hex character");
5743
+ }
5744
+ out[i] = hi << 4 | lo;
5658
5745
  }
5659
- /** Whether a quota service is available */
5660
- get available() {
5661
- return this.quotaUrl !== null;
5746
+ return out;
5747
+ }
5748
+ function base64Encode(bytes) {
5749
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5750
+ let out = "";
5751
+ for (let i = 0; i < bytes.length; i += 3) {
5752
+ const b0 = bytes[i];
5753
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
5754
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
5755
+ out += chars[b0 >> 2 & 63];
5756
+ out += chars[(b0 << 4 | b1 >> 4) & 63];
5757
+ out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
5758
+ out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
5662
5759
  }
5663
- /** Query quota status for a space from the quota URL */
5664
- async getQuota(spaceId) {
5665
- if (!this.quotaUrl) return null;
5666
- const resp = await fetch(
5667
- `${this.quotaUrl}/api/quota/${encodeURIComponent(spaceId)}`
5668
- );
5669
- if (!resp.ok) return null;
5670
- const data = await resp.json();
5671
- return {
5672
- limitBytes: data.storage_limit_bytes ?? 0
5673
- };
5760
+ return out;
5761
+ }
5762
+ function base64Decode(s) {
5763
+ const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
5764
+ const len = clean.length;
5765
+ if (len % 4 !== 0) {
5766
+ throw new Error("invalid base64 input");
5674
5767
  }
5675
- /** Trigger the upgrade callback when a quota error is encountered */
5676
- handleQuotaError(info) {
5677
- this.config.onUpgradeRequired?.(info);
5768
+ const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
5769
+ const outLen = len / 4 * 3 - padding;
5770
+ const out = new Uint8Array(outLen);
5771
+ const lookup = {};
5772
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5773
+ for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
5774
+ let outIdx = 0;
5775
+ for (let i = 0; i < len; i += 4) {
5776
+ const v0 = lookup[clean[i]] ?? 0;
5777
+ const v1 = lookup[clean[i + 1]] ?? 0;
5778
+ const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
5779
+ const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
5780
+ const b0 = v0 << 2 | v1 >> 4;
5781
+ const b1 = (v1 & 15) << 4 | v2 >> 2;
5782
+ const b2 = (v2 & 3) << 6 | v3;
5783
+ if (outIdx < outLen) out[outIdx++] = b0;
5784
+ if (outIdx < outLen) out[outIdx++] = b1;
5785
+ if (outIdx < outLen) out[outIdx++] = b2;
5678
5786
  }
5679
- };
5787
+ return out;
5788
+ }
5789
+ function utf8Encode(s) {
5790
+ return new TextEncoder().encode(s);
5791
+ }
5792
+ function utf8Decode(b) {
5793
+ return new TextDecoder().decode(b);
5794
+ }
5795
+ function canonicalHashHex(sha256, value) {
5796
+ const canonical = canonicalize(value);
5797
+ return hexEncode(sha256(utf8Encode(canonical)));
5798
+ }
5680
5799
 
5681
5800
  // src/vault/types.ts
5682
5801
  var VaultPublicSpaceKVActions = [
@@ -5847,7 +5966,7 @@ function toBytes(str) {
5847
5966
  function fromBytes(bytes) {
5848
5967
  return new TextDecoder().decode(bytes);
5849
5968
  }
5850
- function hexEncode(bytes) {
5969
+ function hexEncode2(bytes) {
5851
5970
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5852
5971
  }
5853
5972
  function concatBytes(...arrays) {
@@ -5860,14 +5979,14 @@ function concatBytes(...arrays) {
5860
5979
  }
5861
5980
  return result;
5862
5981
  }
5863
- function base64Encode(bytes) {
5982
+ function base64Encode2(bytes) {
5864
5983
  let binary = "";
5865
5984
  for (let i = 0; i < bytes.length; i++) {
5866
5985
  binary += String.fromCharCode(bytes[i]);
5867
5986
  }
5868
5987
  return btoa(binary);
5869
5988
  }
5870
- function base64Decode(str) {
5989
+ function base64Decode2(str) {
5871
5990
  const binary = atob(str);
5872
5991
  const bytes = new Uint8Array(binary.length);
5873
5992
  for (let i = 0; i < binary.length; i++) {
@@ -5909,6 +6028,11 @@ function defaultVaultMessage(input) {
5909
6028
  return input.message;
5910
6029
  case "PERMISSION_DENIED":
5911
6030
  return input.message;
6031
+ case "AAD_MISMATCH":
6032
+ case "ETAG_MISMATCH":
6033
+ case "INVALID_ETAG":
6034
+ case "OUTCOME_UNKNOWN":
6035
+ case "NODE_BUSY":
5912
6036
  case "STORAGE_QUOTA_EXCEEDED":
5913
6037
  case "STORAGE_LIMIT_REACHED":
5914
6038
  return input.message;
@@ -5931,6 +6055,37 @@ function typedKvCause(error) {
5931
6055
  ...error.cause !== void 0 ? { cause: error.cause } : {}
5932
6056
  });
5933
6057
  }
6058
+ function canonicalAadSpaceId(spaceId) {
6059
+ return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
6060
+ }
6061
+ function checkEnvelopeAad(value, spaceId, key) {
6062
+ const e = value;
6063
+ if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
6064
+ (field) => typeof e[field] === "string" && e[field] !== ""
6065
+ ) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
6066
+ return "corrupt";
6067
+ }
6068
+ let aad = "";
6069
+ try {
6070
+ if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
6071
+ } catch {
6072
+ }
6073
+ const suffix = `:${key}`;
6074
+ return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
6075
+ }
6076
+ var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
6077
+ function ifMatchWriteError(key, error) {
6078
+ const status = error.meta?.status;
6079
+ 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;
6080
+ if (code === void 0) return void 0;
6081
+ return vaultError({
6082
+ code,
6083
+ key,
6084
+ message: error.message,
6085
+ ...error.meta !== void 0 ? { meta: error.meta } : {},
6086
+ cause: typedKvCause(error)
6087
+ });
6088
+ }
5934
6089
  function kvWriteError(context, error) {
5935
6090
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
5936
6091
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
@@ -6117,7 +6272,7 @@ var DataVaultService = class extends BaseService {
6117
6272
  }
6118
6273
  if (existingPubKey) {
6119
6274
  this.encryptionIdentity = {
6120
- publicKey: base64Decode(existingPubKey),
6275
+ publicKey: base64Decode2(existingPubKey),
6121
6276
  privateKey: new Uint8Array(0)
6122
6277
  // private key not available without signing
6123
6278
  };
@@ -6142,7 +6297,7 @@ var DataVaultService = class extends BaseService {
6142
6297
  );
6143
6298
  this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
6144
6299
  try {
6145
- const pubKeyB64 = base64Encode(this.encryptionIdentity.publicKey);
6300
+ const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
6146
6301
  await this.tc.ensurePublicSpace();
6147
6302
  await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
6148
6303
  await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
@@ -6208,6 +6363,21 @@ var DataVaultService = class extends BaseService {
6208
6363
  message: "Authentication required"
6209
6364
  });
6210
6365
  }
6366
+ if (options?.ifMatch !== void 0) {
6367
+ if (options.ifNoneMatch !== void 0) {
6368
+ return vaultError({
6369
+ code: "STORAGE_ERROR",
6370
+ cause: new Error("ifMatch cannot be combined with ifNoneMatch")
6371
+ });
6372
+ }
6373
+ if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
6374
+ return vaultError({
6375
+ code: "INVALID_ETAG",
6376
+ key,
6377
+ 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"
6378
+ });
6379
+ }
6380
+ }
6211
6381
  try {
6212
6382
  const { plaintext, contentType } = this.serializeValue(value, options);
6213
6383
  const metadata = {
@@ -6230,7 +6400,8 @@ var DataVaultService = class extends BaseService {
6230
6400
  }
6231
6401
  const valuePath = `vault/${key}`;
6232
6402
  const valueBody = JSON.stringify(envelopeResult.data);
6233
- const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
6403
+ const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
6404
+ const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
6234
6405
  if (!valuePutResult.ok) {
6235
6406
  if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
6236
6407
  return vaultError({
@@ -6240,6 +6411,10 @@ var DataVaultService = class extends BaseService {
6240
6411
  cause: typedKvCause(valuePutResult.error)
6241
6412
  });
6242
6413
  }
6414
+ if (options?.ifMatch !== void 0) {
6415
+ const classified = ifMatchWriteError(key, valuePutResult.error);
6416
+ if (classified !== void 0) return classified;
6417
+ }
6243
6418
  return kvWriteError("Failed to store encrypted value", valuePutResult.error);
6244
6419
  }
6245
6420
  return { ok: true, data: void 0 };
@@ -6274,6 +6449,19 @@ var DataVaultService = class extends BaseService {
6274
6449
  }
6275
6450
  const rawEnvelope = unwrapKVData(valueResult.data);
6276
6451
  const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
6452
+ if (options?.expectAad === true) {
6453
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6454
+ if (verdict === "corrupt") {
6455
+ return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
6456
+ }
6457
+ if (verdict === "mismatch") {
6458
+ return vaultError({
6459
+ code: "AAD_MISMATCH",
6460
+ key,
6461
+ message: `Envelope at ${key} is not bound to this space and key`
6462
+ });
6463
+ }
6464
+ }
6277
6465
  const proof = await this.decryptCapabilityProof();
6278
6466
  const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6279
6467
  if (!plaintextResult.ok) {
@@ -6290,7 +6478,11 @@ var DataVaultService = class extends BaseService {
6290
6478
  contentType,
6291
6479
  options
6292
6480
  );
6293
- return { ok: true, data: { value, metadata, keyId } };
6481
+ const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
6482
+ return {
6483
+ ok: true,
6484
+ data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6485
+ };
6294
6486
  } catch (error) {
6295
6487
  return vaultError({
6296
6488
  code: "STORAGE_ERROR",
@@ -6336,6 +6528,12 @@ var DataVaultService = class extends BaseService {
6336
6528
  } catch {
6337
6529
  return { status: "corrupt_envelope" };
6338
6530
  }
6531
+ if (options?.expectAad === true) {
6532
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6533
+ if (verdict !== "ok") {
6534
+ return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
6535
+ }
6536
+ }
6339
6537
  let plaintextResult;
6340
6538
  try {
6341
6539
  const proof = await this.decryptCapabilityProof();
@@ -6367,7 +6565,11 @@ var DataVaultService = class extends BaseService {
6367
6565
  contentType,
6368
6566
  options
6369
6567
  );
6370
- return { status: "ok", entry: { value, metadata, keyId } };
6568
+ const etag = valueResult.data.headers?.etag;
6569
+ return {
6570
+ status: "ok",
6571
+ entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6572
+ };
6371
6573
  } catch {
6372
6574
  return { status: "invalid_payload" };
6373
6575
  }
@@ -6417,6 +6619,14 @@ var DataVaultService = class extends BaseService {
6417
6619
  )
6418
6620
  });
6419
6621
  }
6622
+ if (options?.ifMatch !== void 0) {
6623
+ return vaultError({
6624
+ code: "STORAGE_ERROR",
6625
+ cause: new Error(
6626
+ "ifMatch is only supported by network-encrypted vaults"
6627
+ )
6628
+ });
6629
+ }
6420
6630
  if (!this._isUnlocked || !this.masterKey) {
6421
6631
  return vaultError({
6422
6632
  code: "VAULT_LOCKED",
@@ -6442,7 +6652,7 @@ var DataVaultService = class extends BaseService {
6442
6652
  }
6443
6653
  const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
6444
6654
  const entryKey = this.crypto.randomBytes(32);
6445
- const keyId = hexEncode(this.crypto.sha256(entryKey)).slice(0, 16);
6655
+ const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
6446
6656
  const encrypted = this.crypto.encrypt(entryKey, plaintext);
6447
6657
  const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
6448
6658
  const metadata = {
@@ -6460,7 +6670,7 @@ var DataVaultService = class extends BaseService {
6460
6670
  ...metadata
6461
6671
  });
6462
6672
  const keyPayload = JSON.stringify({
6463
- key: base64Encode(keyBlob),
6673
+ key: base64Encode2(keyBlob),
6464
6674
  metadata: keyMetadata
6465
6675
  });
6466
6676
  const keyPutResult = await this.tc.kv.put(
@@ -6471,7 +6681,7 @@ var DataVaultService = class extends BaseService {
6471
6681
  return kvWriteError("Failed to store key blob", keyPutResult.error);
6472
6682
  }
6473
6683
  const valuePayload = JSON.stringify({
6474
- data: base64Encode(encrypted),
6684
+ data: base64Encode2(encrypted),
6475
6685
  metadata
6476
6686
  });
6477
6687
  const valuePutResult = await this.tc.kv.put(
@@ -6502,6 +6712,12 @@ var DataVaultService = class extends BaseService {
6502
6712
  if (this.usesNetworkEncryption) {
6503
6713
  return this.getNetworkEncrypted(key, options);
6504
6714
  }
6715
+ if (options?.expectAad === true) {
6716
+ return vaultError({
6717
+ code: "STORAGE_ERROR",
6718
+ cause: new Error("expectAad is only supported by network-encrypted vaults")
6719
+ });
6720
+ }
6505
6721
  if (!this._isUnlocked || !this.masterKey) {
6506
6722
  return vaultError({
6507
6723
  code: "VAULT_LOCKED",
@@ -6523,7 +6739,7 @@ var DataVaultService = class extends BaseService {
6523
6739
  return vaultError({ code: "KEY_NOT_FOUND", key });
6524
6740
  }
6525
6741
  const keyEnvelope = JSON.parse(keyResult.data.data);
6526
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6742
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6527
6743
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6528
6744
  const valueResult = await this.tc.kv.get(`vault/${key}`, {
6529
6745
  raw: true,
@@ -6533,7 +6749,7 @@ var DataVaultService = class extends BaseService {
6533
6749
  return vaultError({ code: "KEY_NOT_FOUND", key });
6534
6750
  }
6535
6751
  const valueEnvelope = JSON.parse(valueResult.data.data);
6536
- const encryptedBytes = base64Decode(valueEnvelope.data);
6752
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6537
6753
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6538
6754
  const metadata = valueEnvelope.metadata ?? {};
6539
6755
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6781,7 +6997,7 @@ var DataVaultService = class extends BaseService {
6781
6997
  return vaultError({ code: "KEY_NOT_FOUND", key });
6782
6998
  }
6783
6999
  const keyEnvelope = JSON.parse(keyResult.data.data);
6784
- const keyBlobBytes = base64Decode(keyEnvelope.key);
7000
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6785
7001
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6786
7002
  const ephemeralSeed = this.crypto.randomBytes(32);
6787
7003
  const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
@@ -6800,7 +7016,7 @@ var DataVaultService = class extends BaseService {
6800
7016
  encryptedGrant
6801
7017
  );
6802
7018
  const grantPayload = JSON.stringify({
6803
- grant: base64Encode(grantBlob),
7019
+ grant: base64Encode2(grantBlob),
6804
7020
  spaceId: this.vaultConfig.spaceId,
6805
7021
  metadata: {
6806
7022
  [VaultHeaders.GRANT_VERSION]: "1",
@@ -6840,6 +7056,14 @@ var DataVaultService = class extends BaseService {
6840
7056
  */
6841
7057
  async getShared(grantorDID, key, options) {
6842
7058
  return this.withTelemetry("getShared", key, async () => {
7059
+ if (options?.expectAad === true) {
7060
+ return vaultError({
7061
+ code: "STORAGE_ERROR",
7062
+ cause: new Error(
7063
+ "expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
7064
+ )
7065
+ });
7066
+ }
6843
7067
  if (this.usesNetworkEncryption) {
6844
7068
  const grantorKV = options?.kv;
6845
7069
  if (!grantorKV) {
@@ -6934,7 +7158,7 @@ var DataVaultService = class extends BaseService {
6934
7158
  });
6935
7159
  }
6936
7160
  const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
6937
- const grantBlobBytes = base64Decode(grantEnvelope.grant);
7161
+ const grantBlobBytes = base64Decode2(grantEnvelope.grant);
6938
7162
  const ephemeralPubKey = grantBlobBytes.slice(0, 32);
6939
7163
  const encryptedGrant = grantBlobBytes.slice(32);
6940
7164
  const sharedSecret = this.crypto.x25519Dh(
@@ -6957,7 +7181,7 @@ var DataVaultService = class extends BaseService {
6957
7181
  });
6958
7182
  }
6959
7183
  const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
6960
- const encryptedBytes = base64Decode(valueEnvelope.data);
7184
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6961
7185
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6962
7186
  const metadata = valueEnvelope.metadata ?? {};
6963
7187
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -7011,7 +7235,7 @@ var DataVaultService = class extends BaseService {
7011
7235
  if (!result.ok) {
7012
7236
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
7013
7237
  }
7014
- const pubKeyBytes = base64Decode(result.data);
7238
+ const pubKeyBytes = base64Decode2(result.data);
7015
7239
  return { ok: true, data: pubKeyBytes };
7016
7240
  } catch (error) {
7017
7241
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
@@ -7130,7 +7354,7 @@ var DataVaultService = class extends BaseService {
7130
7354
  }
7131
7355
  const currentEntry = getResult.data;
7132
7356
  const newEntryKey = this.crypto.randomBytes(32);
7133
- const newKeyId = hexEncode(this.crypto.sha256(newEntryKey)).slice(
7357
+ const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
7134
7358
  0,
7135
7359
  16
7136
7360
  );
@@ -7147,7 +7371,7 @@ var DataVaultService = class extends BaseService {
7147
7371
  [VaultHeaders.KEY_ID]: newKeyId
7148
7372
  };
7149
7373
  const keyPayload = JSON.stringify({
7150
- key: base64Encode(newKeyBlob),
7374
+ key: base64Encode2(newKeyBlob),
7151
7375
  metadata: JSON.stringify({
7152
7376
  keyId: newKeyId,
7153
7377
  ...metadata
@@ -7161,7 +7385,7 @@ var DataVaultService = class extends BaseService {
7161
7385
  return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
7162
7386
  }
7163
7387
  const valuePayload = JSON.stringify({
7164
- data: base64Encode(encrypted),
7388
+ data: base64Encode2(encrypted),
7165
7389
  metadata
7166
7390
  });
7167
7391
  const valuePutResult = await this.tc.kv.put(
@@ -7453,116 +7677,6 @@ var SecretsService = class {
7453
7677
  }
7454
7678
  };
7455
7679
 
7456
- // src/encryption/canonical.ts
7457
- function canonicalize(value) {
7458
- if (value === void 0) {
7459
- return "";
7460
- }
7461
- return stringify(value);
7462
- }
7463
- function stringify(value) {
7464
- if (value === null) return "null";
7465
- switch (typeof value) {
7466
- case "boolean":
7467
- case "number":
7468
- return JSON.stringify(value);
7469
- case "string":
7470
- return JSON.stringify(value);
7471
- case "object": {
7472
- if (Array.isArray(value)) {
7473
- return `[${value.map(stringify).join(",")}]`;
7474
- }
7475
- const keys = Object.keys(value).sort();
7476
- const parts = [];
7477
- for (const k of keys) {
7478
- const v = value[k];
7479
- if (v === void 0) continue;
7480
- parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
7481
- }
7482
- return `{${parts.join(",")}}`;
7483
- }
7484
- default:
7485
- throw new TypeError(
7486
- `canonicalize: unsupported value type ${typeof value}`
7487
- );
7488
- }
7489
- }
7490
- var HEX = "0123456789abcdef";
7491
- function hexEncode2(bytes) {
7492
- let out = "";
7493
- for (let i = 0; i < bytes.length; i++) {
7494
- const b = bytes[i];
7495
- out += HEX[b >> 4 & 15] + HEX[b & 15];
7496
- }
7497
- return out;
7498
- }
7499
- function hexDecode(hex) {
7500
- if (hex.length % 2 !== 0) {
7501
- throw new Error("hex string must have even length");
7502
- }
7503
- const out = new Uint8Array(hex.length / 2);
7504
- for (let i = 0; i < out.length; i++) {
7505
- const hi = parseInt(hex[i * 2], 16);
7506
- const lo = parseInt(hex[i * 2 + 1], 16);
7507
- if (Number.isNaN(hi) || Number.isNaN(lo)) {
7508
- throw new Error("invalid hex character");
7509
- }
7510
- out[i] = hi << 4 | lo;
7511
- }
7512
- return out;
7513
- }
7514
- function base64Encode2(bytes) {
7515
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7516
- let out = "";
7517
- for (let i = 0; i < bytes.length; i += 3) {
7518
- const b0 = bytes[i];
7519
- const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
7520
- const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
7521
- out += chars[b0 >> 2 & 63];
7522
- out += chars[(b0 << 4 | b1 >> 4) & 63];
7523
- out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
7524
- out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
7525
- }
7526
- return out;
7527
- }
7528
- function base64Decode2(s) {
7529
- const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
7530
- const len = clean.length;
7531
- if (len % 4 !== 0) {
7532
- throw new Error("invalid base64 input");
7533
- }
7534
- const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
7535
- const outLen = len / 4 * 3 - padding;
7536
- const out = new Uint8Array(outLen);
7537
- const lookup = {};
7538
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7539
- for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
7540
- let outIdx = 0;
7541
- for (let i = 0; i < len; i += 4) {
7542
- const v0 = lookup[clean[i]] ?? 0;
7543
- const v1 = lookup[clean[i + 1]] ?? 0;
7544
- const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
7545
- const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
7546
- const b0 = v0 << 2 | v1 >> 4;
7547
- const b1 = (v1 & 15) << 4 | v2 >> 2;
7548
- const b2 = (v2 & 3) << 6 | v3;
7549
- if (outIdx < outLen) out[outIdx++] = b0;
7550
- if (outIdx < outLen) out[outIdx++] = b1;
7551
- if (outIdx < outLen) out[outIdx++] = b2;
7552
- }
7553
- return out;
7554
- }
7555
- function utf8Encode(s) {
7556
- return new TextEncoder().encode(s);
7557
- }
7558
- function utf8Decode(b) {
7559
- return new TextDecoder().decode(b);
7560
- }
7561
- function canonicalHashHex(sha256, value) {
7562
- const canonical = canonicalize(value);
7563
- return hexEncode2(sha256(utf8Encode(canonical)));
7564
- }
7565
-
7566
7680
  // src/encryption/types.ts
7567
7681
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
7568
7682
  var ENVELOPE_VERSION = 1;
@@ -7772,7 +7886,7 @@ function encryptToNetwork(crypto2, input) {
7772
7886
  const symmetricKey = crypto2.randomBytes(32);
7773
7887
  const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
7774
7888
  const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
7775
- const encryptedSymmetricKey = base64Encode2(wrapped);
7889
+ const encryptedSymmetricKey = base64Encode(wrapped);
7776
7890
  const encryptedSymmetricKeyHash = canonicalHashHex(
7777
7891
  crypto2.sha256,
7778
7892
  encryptedSymmetricKey
@@ -7784,8 +7898,8 @@ function encryptToNetwork(crypto2, input) {
7784
7898
  keyVersion,
7785
7899
  encryptedSymmetricKey,
7786
7900
  encryptedSymmetricKeyHash,
7787
- ciphertext: base64Encode2(ciphertext),
7788
- ...input.aad !== void 0 ? { aad: base64Encode2(input.aad) } : {},
7901
+ ciphertext: base64Encode(ciphertext),
7902
+ ...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
7789
7903
  ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
7790
7904
  };
7791
7905
  return { envelope, symmetricKey };
@@ -7859,8 +7973,8 @@ function validateEnvelope(crypto2, envelope) {
7859
7973
  return { ok: true, data: e };
7860
7974
  }
7861
7975
  function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
7862
- const ciphertext = base64Decode2(envelope.ciphertext);
7863
- const aad = envelope.aad !== void 0 ? base64Decode2(envelope.aad) : void 0;
7976
+ const ciphertext = base64Decode(envelope.ciphertext);
7977
+ const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
7864
7978
  return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
7865
7979
  }
7866
7980
 
@@ -7878,7 +7992,7 @@ function buildCanonicalDecryptRequest(input) {
7878
7992
  return { canonicalBody, bodyHash, receiverPublicKeyHash };
7879
7993
  }
7880
7994
  function buildDecryptFacts(input) {
7881
- const bodyHash = input.canonicalBody !== void 0 ? hexEncode2(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7995
+ const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7882
7996
  input.crypto.sha256,
7883
7997
  input.body
7884
7998
  );
@@ -8161,7 +8275,7 @@ function verifyDecryptResponse(input) {
8161
8275
  })
8162
8276
  };
8163
8277
  }
8164
- const expectedRequestHash = hexEncode2(
8278
+ const expectedRequestHash = hexEncode(
8165
8279
  crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
8166
8280
  );
8167
8281
  if (response.requestHash !== expectedRequestHash) {
@@ -8185,7 +8299,7 @@ function verifyDecryptResponse(input) {
8185
8299
  const signedBytes = new TextEncoder().encode(
8186
8300
  canonicalSignedResponse(response)
8187
8301
  );
8188
- const signatureBytes = base64Decode2(response.nodeSignature);
8302
+ const signatureBytes = base64Decode(response.nodeSignature);
8189
8303
  if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
8190
8304
  return {
8191
8305
  ok: false,
@@ -8197,7 +8311,7 @@ function verifyDecryptResponse(input) {
8197
8311
  return { ok: true, data: response };
8198
8312
  }
8199
8313
  function openWrappedKey(crypto2, receiverPrivateKey, response) {
8200
- const wrapped = base64Decode2(response.wrappedKey);
8314
+ const wrapped = base64Decode(response.wrappedKey);
8201
8315
  return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
8202
8316
  }
8203
8317
 
@@ -8241,7 +8355,7 @@ var EncryptionService = class extends BaseService {
8241
8355
  const usable = ensureNetworkUsableForDecrypt(discovered.data);
8242
8356
  if (!usable.ok) return usable;
8243
8357
  const descriptor = usable.data;
8244
- const networkPublicKey = base64Decode2(descriptor.publicEncryptionKey);
8358
+ const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
8245
8359
  const result = encryptToNetwork(this.crypto, {
8246
8360
  // Discovery returns the node's canonical network identifier. Persist
8247
8361
  // that spelling in the envelope so a later response can be compared
@@ -8269,7 +8383,7 @@ var EncryptionService = class extends BaseService {
8269
8383
  this.assertActive();
8270
8384
  const validated = validateEnvelope(this.crypto, envelope);
8271
8385
  if (!validated.ok) return validated;
8272
- if (options?.aad !== void 0 && validated.data.aad !== base64Encode2(options.aad)) {
8386
+ if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
8273
8387
  return encErr(
8274
8388
  encryptionError({
8275
8389
  code: "INVALID_INPUT",
@@ -8297,7 +8411,7 @@ var EncryptionService = class extends BaseService {
8297
8411
  );
8298
8412
  }
8299
8413
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
8300
- const receiverPublicKey = base64Encode2(receiverKey.publicKey);
8414
+ const receiverPublicKey = base64Encode(receiverKey.publicKey);
8301
8415
  const receiverPublicKeyHash = canonicalHashHex(
8302
8416
  this.crypto.sha256,
8303
8417
  receiverPublicKey