@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.js CHANGED
@@ -16,7 +16,7 @@ var ErrorCodes = {
16
16
  PERMISSION_DENIED: "PERMISSION_DENIED",
17
17
  // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
18
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`. */
19
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
20
20
  NODE_BUSY: "NODE_BUSY",
21
21
  /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
22
22
  REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
@@ -57,11 +57,12 @@ var ErrorCodes = {
57
57
  DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
58
58
  };
59
59
  var defaultRetryPolicy = {
60
- maxAttempts: 3,
60
+ maxAttempts: 4,
61
61
  backoff: "exponential",
62
- baseDelayMs: 1e3,
63
- maxDelayMs: 1e4,
64
- retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
62
+ baseDelayMs: 250,
63
+ maxDelayMs: 4e3,
64
+ retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],
65
+ maxTotalMs: 12e3
65
66
  };
66
67
  var TelemetryEvents = {
67
68
  SPAN: "telemetry.span",
@@ -488,7 +489,9 @@ var RetryPolicySchema = z.object({
488
489
  /** Maximum delay in milliseconds between retries */
489
490
  maxDelayMs: z.number().nonnegative(),
490
491
  /** Error codes that should trigger a retry */
491
- retryableErrors: z.array(z.string())
492
+ retryableErrors: z.array(z.string()),
493
+ /** Total time budget for all attempts and waits, in milliseconds */
494
+ maxTotalMs: z.number().positive().optional()
492
495
  });
493
496
  var ServiceSessionSchema = z.object({
494
497
  /** The delegation header containing the UCAN */
@@ -625,448 +628,107 @@ function validateTelemetrySpanEvent(data) {
625
628
  return { ok: true, data: result.data };
626
629
  }
627
630
 
628
- // src/context.ts
629
- var ServiceContext = class {
630
- constructor(config) {
631
- this._session = null;
632
- this._services = /* @__PURE__ */ new Map();
633
- this._eventHandlers = /* @__PURE__ */ new Map();
634
- this._abortController = new AbortController();
635
- this._retired = false;
636
- this._invoke = this.wrapInvoke(config.invoke);
637
- this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
638
- this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
639
- this._hosts = config.hosts;
640
- this._session = config.session ?? null;
641
- this._retryPolicy = {
642
- ...defaultRetryPolicy,
643
- ...config.retryPolicy
644
- };
645
- this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
646
- this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
647
- }
648
- // ============================================================
649
- // Session Management
650
- // ============================================================
651
- /**
652
- * Get the current session.
653
- */
654
- get session() {
655
- return this._retired ? null : this._session;
631
+ // src/responseErrors.ts
632
+ function parseServiceErrorBody(errorText) {
633
+ try {
634
+ return JSON.parse(errorText);
635
+ } catch {
636
+ return {};
656
637
  }
657
- /**
658
- * Check if the context has an authenticated session.
659
- */
660
- get isAuthenticated() {
661
- return !this._retired && this._session !== null;
638
+ }
639
+ function formatServiceResponseError(serviceLabel, operation, status, errorText, parsed) {
640
+ if (parsed.message) {
641
+ return compactMessage(parsed.message);
662
642
  }
663
- /**
664
- * Update the session and notify all registered services.
665
- *
666
- * @param session - New session or null to clear
667
- */
668
- setSession(session) {
669
- this.assertActive();
670
- this._session = session;
671
- this.emit("session.changed", { authenticated: session !== null });
672
- for (const service of this._services.values()) {
673
- service.onSessionChange(session);
643
+ if (looksLikeHtml(errorText)) {
644
+ if (status === 524 || /524\s*[:-]/i.test(errorText) || /a timeout occurred/i.test(errorText)) {
645
+ return `${serviceLabel} ${operation} failed: upstream request timed out. Please retry.`;
674
646
  }
647
+ return `${serviceLabel} ${operation} failed: upstream service returned an HTML error page (${status}).`;
675
648
  }
676
- // ============================================================
677
- // Platform Dependencies
678
- // ============================================================
679
- /**
680
- * Get the invoke function for WASM operations.
681
- */
682
- get invoke() {
683
- this.assertActive();
684
- return this._invoke;
685
- }
686
- /**
687
- * Get the multi-resource invoke function when available.
688
- */
689
- get invokeAny() {
690
- this.assertActive();
691
- return this._invokeAny;
649
+ return `${serviceLabel} ${operation} failed: ${status} - ${compactMessage(errorText)}`;
650
+ }
651
+ function responseErrorMeta(status, statusText, errorText) {
652
+ const meta = { status, statusText };
653
+ const snippet = compactMessage(errorText);
654
+ if (snippet) {
655
+ meta.responseSnippet = snippet.slice(0, 300);
692
656
  }
693
- /**
694
- * Get the fetch function for HTTP requests.
695
- */
696
- get fetch() {
697
- this.assertActive();
698
- return this._fetch;
657
+ return meta;
658
+ }
659
+ function looksLikeHtml(text) {
660
+ return /<!doctype html/i.test(text) || /<html[\s>]/i.test(text);
661
+ }
662
+ function compactMessage(message) {
663
+ const compact = message.replace(/\s+/g, " ").trim();
664
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
665
+ }
666
+
667
+ // src/errors.ts
668
+ import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
669
+
670
+ // src/encryption/networkId.ts
671
+ var URN_PREFIX = "urn:tinycloud:encryption:";
672
+ var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
673
+ var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
674
+ var NetworkIdError = class extends Error {
675
+ constructor(message) {
676
+ super(message);
677
+ this.name = "NetworkIdError";
699
678
  }
700
- /**
701
- * Get the list of TinyCloud host URLs.
702
- */
703
- get hosts() {
704
- this.assertActive();
705
- return this._hosts;
679
+ };
680
+ function parseNetworkId(networkId) {
681
+ if (typeof networkId !== "string" || networkId.length === 0) {
682
+ throw new NetworkIdError("networkId must be a non-empty string");
706
683
  }
707
- // ============================================================
708
- // Service Registry
709
- // ============================================================
710
- /**
711
- * Register a service with the context.
712
- *
713
- * @param name - Service name (e.g., 'kv')
714
- * @param service - Service instance
715
- */
716
- registerService(name, service) {
717
- this._services.set(name, service);
684
+ if (!networkId.startsWith(URN_PREFIX)) {
685
+ throw new NetworkIdError(
686
+ `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
687
+ );
718
688
  }
719
- /**
720
- * Unregister a service from the context.
721
- *
722
- * @param name - Service name to remove
723
- */
724
- unregisterService(name) {
725
- this._services.delete(name);
689
+ const body = networkId.slice(URN_PREFIX.length);
690
+ const lastColon = body.lastIndexOf(":");
691
+ if (lastColon <= 0 || lastColon === body.length - 1) {
692
+ throw new NetworkIdError(
693
+ `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
694
+ );
726
695
  }
727
- /**
728
- * Get a registered service by name.
729
- *
730
- * @param name - Service name
731
- * @returns The service instance or undefined if not registered
732
- */
733
- getService(name) {
734
- return this._services.get(name);
696
+ const ownerDid = body.slice(0, lastColon);
697
+ const name = body.slice(lastColon + 1);
698
+ if (!ownerDid.startsWith("did:")) {
699
+ throw new NetworkIdError(
700
+ `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
701
+ );
735
702
  }
736
- // ============================================================
737
- // Event System (Telemetry)
738
- // ============================================================
739
- /**
740
- * Emit a telemetry event.
741
- *
742
- * @param event - Event name
743
- * @param data - Event data
744
- */
745
- emit(event, data) {
746
- tinyCloudDebugLogger.log(event, data);
747
- if (this._telemetryEnabled && this._telemetryHandler) {
748
- try {
749
- this._telemetryHandler(event, projectDiagnosticData(data));
750
- } catch (error) {
751
- console.error(`Error in telemetry handler for "${event}":`, error);
752
- }
753
- }
754
- const handlers = this._eventHandlers.get(event);
755
- if (handlers) {
756
- for (const handler of handlers) {
757
- try {
758
- handler(data);
759
- } catch (error) {
760
- console.error(`Error in event handler for "${event}":`, error);
761
- }
762
- }
763
- }
703
+ const didParts = ownerDid.split(":");
704
+ if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
705
+ throw new NetworkIdError(
706
+ `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
707
+ );
764
708
  }
765
- /**
766
- * Subscribe to telemetry events.
767
- *
768
- * @param event - Event name to subscribe to
769
- * @param handler - Handler function
770
- * @returns Unsubscribe function
771
- */
772
- on(event, handler) {
773
- if (!this._eventHandlers.has(event)) {
774
- this._eventHandlers.set(event, /* @__PURE__ */ new Set());
775
- }
776
- this._eventHandlers.get(event).add(handler);
777
- return () => {
778
- const handlers = this._eventHandlers.get(event);
779
- if (handlers) {
780
- handlers.delete(handler);
781
- if (handlers.size === 0) {
782
- this._eventHandlers.delete(event);
783
- }
784
- }
785
- };
709
+ if (!NETWORK_NAME_RE.test(name)) {
710
+ throw new NetworkIdError(
711
+ `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
712
+ );
786
713
  }
787
- /**
788
- * Remove all event handlers for an event.
789
- *
790
- * @param event - Event name (if omitted, clears all events)
791
- */
792
- clearEventHandlers(event) {
793
- if (event) {
794
- this._eventHandlers.delete(event);
795
- } else {
796
- this._eventHandlers.clear();
797
- }
714
+ return { networkId, ownerDid, name };
715
+ }
716
+ function buildNetworkId(ownerDid, name) {
717
+ if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
718
+ throw new NetworkIdError("ownerDid must be a DID");
798
719
  }
799
- // ============================================================
800
- // Lifecycle
801
- // ============================================================
802
- /**
803
- * Get the abort signal for cancelling operations.
804
- */
805
- get abortSignal() {
806
- return this._abortController.signal;
720
+ if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
721
+ throw new NetworkIdError(
722
+ `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
723
+ );
807
724
  }
808
- get operationAbortSignal() {
809
- return this._operationAbortSignal;
810
- }
811
- setOperationAbortSignal(signal) {
812
- this._operationAbortSignal = signal;
813
- }
814
- /**
815
- * Permanently retire this graph after its owner installs a replacement.
816
- * Unlike `abort()`, retirement never creates a fresh controller, so captured
817
- * services cannot resume calls under a superseded session.
818
- */
819
- retire() {
820
- if (this._retired) return;
821
- this._retired = true;
822
- this._session = null;
823
- this._abortController.abort();
824
- for (const service of this._services.values()) {
825
- try {
826
- service.onSignOut();
827
- } catch (error) {
828
- console.error("Error retiring service:", error);
829
- }
830
- }
831
- }
832
- /**
833
- * Abort all pending operations and notify services.
834
- * Creates a new AbortController for future operations.
835
- */
836
- abort() {
837
- if (this._retired) return;
838
- this._abortController.abort();
839
- this._abortController = new AbortController();
840
- for (const service of this._services.values()) {
841
- try {
842
- service.onSignOut();
843
- } catch (error) {
844
- console.error("Error signing out service:", error);
845
- }
846
- }
847
- }
848
- /**
849
- * Sign out - abort operations and clear session.
850
- */
851
- signOut() {
852
- if (this._retired) return;
853
- this.abort();
854
- this.setSession(null);
855
- this.emit("session.expired", {});
856
- }
857
- // ============================================================
858
- // Retry Policy
859
- // ============================================================
860
- /**
861
- * Get the retry policy configuration.
862
- */
863
- get retryPolicy() {
864
- return this._retryPolicy;
865
- }
866
- wrapInvoke(invoke) {
867
- return (session, service, path, action, facts) => {
868
- this.assertActive();
869
- if (!tinyCloudDebugLogger.isEnabled()) {
870
- return invoke(session, service, path, action, facts);
871
- }
872
- const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
873
- service,
874
- path,
875
- action,
876
- facts
877
- });
878
- try {
879
- const result = invoke(session, service, path, action, facts);
880
- timer.stop({ ok: true, service, path, action });
881
- return result;
882
- } catch (error) {
883
- timer.stop({ ok: false, service, path, action, error });
884
- throw error;
885
- }
886
- };
887
- }
888
- assertActive() {
889
- if (this._retired) {
890
- throw new Error("Service graph has been retired: its session was replaced or signed out.");
891
- }
892
- }
893
- wrapInvokeAny(invokeAny) {
894
- return (session, entries, facts) => {
895
- this.assertActive();
896
- if (!tinyCloudDebugLogger.isEnabled()) {
897
- return invokeAny(session, entries, facts);
898
- }
899
- const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
900
- entries,
901
- facts
902
- });
903
- try {
904
- const result = invokeAny(session, entries, facts);
905
- timer.stop({ ok: true, entries });
906
- return result;
907
- } catch (error) {
908
- timer.stop({ ok: false, entries, error });
909
- throw error;
910
- }
911
- };
912
- }
913
- wrapFetch(fetchFn) {
914
- return async (url, init) => {
915
- this.assertActive();
916
- const request = {
917
- ...init,
918
- signal: this.combineSignals(init?.signal)
919
- };
920
- if (!tinyCloudDebugLogger.isEnabled()) {
921
- return fetchFn(url, request);
922
- }
923
- const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
924
- url,
925
- method: request.method ?? "GET",
926
- init: request
927
- });
928
- try {
929
- const response = await fetchFn(url, request);
930
- timer.stop({
931
- ok: response.ok,
932
- url,
933
- method: request.method ?? "GET",
934
- status: response.status,
935
- statusText: response.statusText
936
- });
937
- return response;
938
- } catch (error) {
939
- timer.stop({
940
- ok: false,
941
- url,
942
- method: request.method ?? "GET",
943
- error
944
- });
945
- throw error;
946
- }
947
- };
948
- }
949
- combineSignals(signal) {
950
- if (!signal) return this._abortController.signal;
951
- const combined = new AbortController();
952
- const abort = (source) => combined.abort(source.reason);
953
- if (signal.aborted) {
954
- abort(signal);
955
- } else if (this._abortController.signal.aborted) {
956
- abort(this._abortController.signal);
957
- } else {
958
- signal.addEventListener("abort", () => abort(signal), { once: true });
959
- this._abortController.signal.addEventListener(
960
- "abort",
961
- () => abort(this._abortController.signal),
962
- { once: true }
963
- );
964
- }
965
- return combined.signal;
966
- }
967
- };
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
-
1005
- // src/errors.ts
1006
- import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
1007
-
1008
- // src/encryption/networkId.ts
1009
- var URN_PREFIX = "urn:tinycloud:encryption:";
1010
- var NETWORK_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
1011
- var PKH_EIP155_DID_RE = /^did:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40})$/;
1012
- var NetworkIdError = class extends Error {
1013
- constructor(message) {
1014
- super(message);
1015
- this.name = "NetworkIdError";
1016
- }
1017
- };
1018
- function parseNetworkId(networkId) {
1019
- if (typeof networkId !== "string" || networkId.length === 0) {
1020
- throw new NetworkIdError("networkId must be a non-empty string");
1021
- }
1022
- if (!networkId.startsWith(URN_PREFIX)) {
1023
- throw new NetworkIdError(
1024
- `networkId must start with ${URN_PREFIX} (got ${JSON.stringify(networkId)})`
1025
- );
1026
- }
1027
- const body = networkId.slice(URN_PREFIX.length);
1028
- const lastColon = body.lastIndexOf(":");
1029
- if (lastColon <= 0 || lastColon === body.length - 1) {
1030
- throw new NetworkIdError(
1031
- `networkId missing ownerDid or name segment (got ${JSON.stringify(networkId)})`
1032
- );
1033
- }
1034
- const ownerDid = body.slice(0, lastColon);
1035
- const name = body.slice(lastColon + 1);
1036
- if (!ownerDid.startsWith("did:")) {
1037
- throw new NetworkIdError(
1038
- `networkId ownerDid must be a DID (got ${JSON.stringify(ownerDid)})`
1039
- );
1040
- }
1041
- const didParts = ownerDid.split(":");
1042
- if (didParts.length < 3 || didParts.some((p) => p.length === 0)) {
1043
- throw new NetworkIdError(
1044
- `networkId ownerDid is not a well-formed DID (got ${JSON.stringify(ownerDid)})`
1045
- );
1046
- }
1047
- if (!NETWORK_NAME_RE.test(name)) {
1048
- throw new NetworkIdError(
1049
- `networkId name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1050
- );
1051
- }
1052
- return { networkId, ownerDid, name };
1053
- }
1054
- function buildNetworkId(ownerDid, name) {
1055
- if (typeof ownerDid !== "string" || !ownerDid.startsWith("did:")) {
1056
- throw new NetworkIdError("ownerDid must be a DID");
1057
- }
1058
- if (typeof name !== "string" || !NETWORK_NAME_RE.test(name)) {
1059
- throw new NetworkIdError(
1060
- `network name ${JSON.stringify(name)} must match ${NETWORK_NAME_RE.source}`
1061
- );
1062
- }
1063
- const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
1064
- parseNetworkId(networkId);
1065
- return networkId;
1066
- }
1067
- function isNetworkId(networkId) {
1068
- if (typeof networkId !== "string") {
1069
- return false;
725
+ const networkId = `${URN_PREFIX}${ownerDid}:${name}`;
726
+ parseNetworkId(networkId);
727
+ return networkId;
728
+ }
729
+ function isNetworkId(networkId) {
730
+ if (typeof networkId !== "string") {
731
+ return false;
1070
732
  }
1071
733
  try {
1072
734
  parseNetworkId(networkId);
@@ -1165,7 +827,7 @@ function nodeRetryError(service, message, response, errorText) {
1165
827
  const body = errorText.trim();
1166
828
  const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
1167
829
  let code;
1168
- if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
830
+ if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable" || token === "read_audit_busy")) {
1169
831
  code = ErrorCodes.NODE_BUSY;
1170
832
  } else if (response.status === 503 && token === "request_timeout") {
1171
833
  code = ErrorCodes.REQUEST_TIMEOUT;
@@ -1414,25 +1076,411 @@ function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
1414
1076
  if (error.name === "AbortError") {
1415
1077
  return abortedError(service);
1416
1078
  }
1417
- if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
1418
- return timeoutError(service);
1079
+ if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
1080
+ return timeoutError(service);
1081
+ }
1082
+ return {
1083
+ code: defaultCode,
1084
+ message: error.message,
1085
+ service,
1086
+ cause: error
1087
+ };
1088
+ }
1089
+ return {
1090
+ code: defaultCode,
1091
+ message: String(error),
1092
+ service
1093
+ };
1094
+ }
1095
+ function errorResult(error) {
1096
+ return err(error);
1097
+ }
1098
+
1099
+ // src/sleep.ts
1100
+ var abortableSleep = (ms, signal, unref = false) => new Promise((resolve) => {
1101
+ const done = () => {
1102
+ clearTimeout(timer);
1103
+ signal.removeEventListener("abort", done);
1104
+ resolve();
1105
+ };
1106
+ const timer = setTimeout(done, ms);
1107
+ if (unref) timer.unref?.();
1108
+ signal.aborted ? done() : signal.addEventListener("abort", done);
1109
+ });
1110
+
1111
+ // src/context.ts
1112
+ var ServiceContext = class {
1113
+ constructor(config) {
1114
+ this._session = null;
1115
+ this._services = /* @__PURE__ */ new Map();
1116
+ this._eventHandlers = /* @__PURE__ */ new Map();
1117
+ this._abortController = new AbortController();
1118
+ this._retired = false;
1119
+ this._invoke = this.wrapInvoke(config.invoke);
1120
+ this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
1121
+ this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
1122
+ this._hosts = config.hosts;
1123
+ this._session = config.session ?? null;
1124
+ this._retryPolicy = {
1125
+ ...defaultRetryPolicy,
1126
+ ...config.retryPolicy
1127
+ };
1128
+ this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
1129
+ this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
1130
+ }
1131
+ // ============================================================
1132
+ // Session Management
1133
+ // ============================================================
1134
+ /**
1135
+ * Get the current session.
1136
+ */
1137
+ get session() {
1138
+ return this._retired ? null : this._session;
1139
+ }
1140
+ /**
1141
+ * Check if the context has an authenticated session.
1142
+ */
1143
+ get isAuthenticated() {
1144
+ return !this._retired && this._session !== null;
1145
+ }
1146
+ /**
1147
+ * Update the session and notify all registered services.
1148
+ *
1149
+ * @param session - New session or null to clear
1150
+ */
1151
+ setSession(session) {
1152
+ this.assertActive();
1153
+ this._session = session;
1154
+ this.emit("session.changed", { authenticated: session !== null });
1155
+ for (const service of this._services.values()) {
1156
+ service.onSessionChange(session);
1157
+ }
1158
+ }
1159
+ // ============================================================
1160
+ // Platform Dependencies
1161
+ // ============================================================
1162
+ /**
1163
+ * Get the invoke function for WASM operations.
1164
+ */
1165
+ get invoke() {
1166
+ this.assertActive();
1167
+ return this._invoke;
1168
+ }
1169
+ /**
1170
+ * Get the multi-resource invoke function when available.
1171
+ */
1172
+ get invokeAny() {
1173
+ this.assertActive();
1174
+ return this._invokeAny;
1175
+ }
1176
+ /**
1177
+ * Get the fetch function for HTTP requests.
1178
+ */
1179
+ get fetch() {
1180
+ this.assertActive();
1181
+ return this._fetch;
1182
+ }
1183
+ /**
1184
+ * Get the list of TinyCloud host URLs.
1185
+ */
1186
+ get hosts() {
1187
+ this.assertActive();
1188
+ return this._hosts;
1189
+ }
1190
+ // ============================================================
1191
+ // Service Registry
1192
+ // ============================================================
1193
+ /**
1194
+ * Register a service with the context.
1195
+ *
1196
+ * @param name - Service name (e.g., 'kv')
1197
+ * @param service - Service instance
1198
+ */
1199
+ registerService(name, service) {
1200
+ this._services.set(name, service);
1201
+ }
1202
+ /**
1203
+ * Unregister a service from the context.
1204
+ *
1205
+ * @param name - Service name to remove
1206
+ */
1207
+ unregisterService(name) {
1208
+ this._services.delete(name);
1209
+ }
1210
+ /**
1211
+ * Get a registered service by name.
1212
+ *
1213
+ * @param name - Service name
1214
+ * @returns The service instance or undefined if not registered
1215
+ */
1216
+ getService(name) {
1217
+ return this._services.get(name);
1218
+ }
1219
+ // ============================================================
1220
+ // Event System (Telemetry)
1221
+ // ============================================================
1222
+ /**
1223
+ * Emit a telemetry event.
1224
+ *
1225
+ * @param event - Event name
1226
+ * @param data - Event data
1227
+ */
1228
+ emit(event, data) {
1229
+ tinyCloudDebugLogger.log(event, data);
1230
+ if (this._telemetryEnabled && this._telemetryHandler) {
1231
+ try {
1232
+ this._telemetryHandler(event, projectDiagnosticData(data));
1233
+ } catch (error) {
1234
+ console.error(`Error in telemetry handler for "${event}":`, error);
1235
+ }
1236
+ }
1237
+ const handlers = this._eventHandlers.get(event);
1238
+ if (handlers) {
1239
+ for (const handler of handlers) {
1240
+ try {
1241
+ handler(data);
1242
+ } catch (error) {
1243
+ console.error(`Error in event handler for "${event}":`, error);
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+ /**
1249
+ * Subscribe to telemetry events.
1250
+ *
1251
+ * @param event - Event name to subscribe to
1252
+ * @param handler - Handler function
1253
+ * @returns Unsubscribe function
1254
+ */
1255
+ on(event, handler) {
1256
+ if (!this._eventHandlers.has(event)) {
1257
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
1258
+ }
1259
+ this._eventHandlers.get(event).add(handler);
1260
+ return () => {
1261
+ const handlers = this._eventHandlers.get(event);
1262
+ if (handlers) {
1263
+ handlers.delete(handler);
1264
+ if (handlers.size === 0) {
1265
+ this._eventHandlers.delete(event);
1266
+ }
1267
+ }
1268
+ };
1269
+ }
1270
+ /**
1271
+ * Remove all event handlers for an event.
1272
+ *
1273
+ * @param event - Event name (if omitted, clears all events)
1274
+ */
1275
+ clearEventHandlers(event) {
1276
+ if (event) {
1277
+ this._eventHandlers.delete(event);
1278
+ } else {
1279
+ this._eventHandlers.clear();
1280
+ }
1281
+ }
1282
+ // ============================================================
1283
+ // Lifecycle
1284
+ // ============================================================
1285
+ /**
1286
+ * Get the abort signal for cancelling operations.
1287
+ */
1288
+ get abortSignal() {
1289
+ return this._abortController.signal;
1290
+ }
1291
+ get operationAbortSignal() {
1292
+ return this._operationAbortSignal;
1293
+ }
1294
+ setOperationAbortSignal(signal) {
1295
+ this._operationAbortSignal = signal;
1296
+ }
1297
+ /**
1298
+ * Permanently retire this graph after its owner installs a replacement.
1299
+ * Unlike `abort()`, retirement never creates a fresh controller, so captured
1300
+ * services cannot resume calls under a superseded session.
1301
+ */
1302
+ retire() {
1303
+ if (this._retired) return;
1304
+ this._retired = true;
1305
+ this._session = null;
1306
+ this._abortController.abort();
1307
+ for (const service of this._services.values()) {
1308
+ try {
1309
+ service.onSignOut();
1310
+ } catch (error) {
1311
+ console.error("Error retiring service:", error);
1312
+ }
1313
+ }
1314
+ }
1315
+ /**
1316
+ * Abort all pending operations and notify services.
1317
+ * Creates a new AbortController for future operations.
1318
+ */
1319
+ abort() {
1320
+ if (this._retired) return;
1321
+ this._abortController.abort();
1322
+ this._abortController = new AbortController();
1323
+ for (const service of this._services.values()) {
1324
+ try {
1325
+ service.onSignOut();
1326
+ } catch (error) {
1327
+ console.error("Error signing out service:", error);
1328
+ }
1329
+ }
1330
+ }
1331
+ /**
1332
+ * Sign out - abort operations and clear session.
1333
+ */
1334
+ signOut() {
1335
+ if (this._retired) return;
1336
+ this.abort();
1337
+ this.setSession(null);
1338
+ this.emit("session.expired", {});
1339
+ }
1340
+ // ============================================================
1341
+ // Retry Policy
1342
+ // ============================================================
1343
+ /**
1344
+ * Get the retry policy configuration.
1345
+ */
1346
+ get retryPolicy() {
1347
+ return this._retryPolicy;
1348
+ }
1349
+ wrapInvoke(invoke) {
1350
+ return (session, service, path, action, facts) => {
1351
+ this.assertActive();
1352
+ if (!tinyCloudDebugLogger.isEnabled()) {
1353
+ return invoke(session, service, path, action, facts);
1354
+ }
1355
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
1356
+ service,
1357
+ path,
1358
+ action,
1359
+ facts
1360
+ });
1361
+ try {
1362
+ const result = invoke(session, service, path, action, facts);
1363
+ timer.stop({ ok: true, service, path, action });
1364
+ return result;
1365
+ } catch (error) {
1366
+ timer.stop({ ok: false, service, path, action, error });
1367
+ throw error;
1368
+ }
1369
+ };
1370
+ }
1371
+ assertActive() {
1372
+ if (this._retired) {
1373
+ throw new Error("Service graph has been retired: its session was replaced or signed out.");
1374
+ }
1375
+ }
1376
+ wrapInvokeAny(invokeAny) {
1377
+ return (session, entries, facts) => {
1378
+ this.assertActive();
1379
+ if (!tinyCloudDebugLogger.isEnabled()) {
1380
+ return invokeAny(session, entries, facts);
1381
+ }
1382
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
1383
+ entries,
1384
+ facts
1385
+ });
1386
+ try {
1387
+ const result = invokeAny(session, entries, facts);
1388
+ timer.stop({ ok: true, entries });
1389
+ return result;
1390
+ } catch (error) {
1391
+ timer.stop({ ok: false, entries, error });
1392
+ throw error;
1393
+ }
1394
+ };
1395
+ }
1396
+ /** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */
1397
+ async invokeWithRetry(url, build) {
1398
+ this.assertActive();
1399
+ const policy = this._retryPolicy;
1400
+ const startedAt = Date.now();
1401
+ let backoffMs = 0;
1402
+ let signal;
1403
+ for (let attempt = 1; ; attempt++) {
1404
+ const init = await build();
1405
+ signal ?? (signal = this.combineSignals(init.signal));
1406
+ const response = await this._fetch(url, init);
1407
+ const done = () => {
1408
+ try {
1409
+ response.retry = { attempts: attempt, backoffMs };
1410
+ } catch {
1411
+ }
1412
+ return response;
1413
+ };
1414
+ if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();
1415
+ const text = await response.clone().text().catch(() => "");
1416
+ const error = nodeRetryError("", "", response, text);
1417
+ if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {
1418
+ return done();
1419
+ }
1420
+ const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);
1421
+ if (remaining <= 0) return done();
1422
+ const retryAfterMs = error.meta.retryAfterMs;
1423
+ const step = Math.min(
1424
+ policy.maxDelayMs,
1425
+ policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** (attempt - 1) : policy.backoff === "linear" ? policy.baseDelayMs * attempt : 0
1426
+ );
1427
+ const delay = Math.min(
1428
+ typeof retryAfterMs === "number" ? retryAfterMs : Math.random() * step,
1429
+ remaining
1430
+ );
1431
+ response.body?.cancel?.()?.catch?.(() => {
1432
+ });
1433
+ await abortableSleep(delay, signal);
1434
+ if (signal.aborted) throw signal.reason;
1435
+ backoffMs += delay;
1419
1436
  }
1420
- return {
1421
- code: defaultCode,
1422
- message: error.message,
1423
- service,
1424
- cause: error
1437
+ }
1438
+ wrapFetch(fetchFn) {
1439
+ return async (url, init) => {
1440
+ this.assertActive();
1441
+ const request = {
1442
+ ...init,
1443
+ signal: this.combineSignals(init?.signal)
1444
+ };
1445
+ if (!tinyCloudDebugLogger.isEnabled()) {
1446
+ return fetchFn(url, request);
1447
+ }
1448
+ const meta = { url, method: request.method ?? "GET" };
1449
+ const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", { ...meta, init: request });
1450
+ try {
1451
+ const response = await fetchFn(url, request);
1452
+ timer.stop({
1453
+ ...meta,
1454
+ ok: response.ok,
1455
+ status: response.status,
1456
+ statusText: response.statusText
1457
+ });
1458
+ return response;
1459
+ } catch (error) {
1460
+ timer.stop({ ...meta, ok: false, error });
1461
+ throw error;
1462
+ }
1425
1463
  };
1426
1464
  }
1427
- return {
1428
- code: defaultCode,
1429
- message: String(error),
1430
- service
1431
- };
1432
- }
1433
- function errorResult(error) {
1434
- return err(error);
1435
- }
1465
+ combineSignals(signal) {
1466
+ if (!signal) return this._abortController.signal;
1467
+ const combined = new AbortController();
1468
+ const abort = (source) => combined.abort(source.reason);
1469
+ if (signal.aborted) {
1470
+ abort(signal);
1471
+ } else if (this._abortController.signal.aborted) {
1472
+ abort(this._abortController.signal);
1473
+ } else {
1474
+ signal.addEventListener("abort", () => abort(signal), { once: true });
1475
+ this._abortController.signal.addEventListener(
1476
+ "abort",
1477
+ () => abort(this._abortController.signal),
1478
+ { once: true }
1479
+ );
1480
+ }
1481
+ return combined.signal;
1482
+ }
1483
+ };
1436
1484
 
1437
1485
  // src/base/BaseService.ts
1438
1486
  var MAX_TIMER_DELAY_MS = 2147483647;
@@ -1616,6 +1664,18 @@ var BaseService = class {
1616
1664
  }
1617
1665
  return controller.signal;
1618
1666
  }
1667
+ /** POST a signed invocation with retry; `mint` re-signs per attempt, `init.headers` add to the signed ones. */
1668
+ invokeWithRetry(url, mint, { headers: extra, ...init } = {}) {
1669
+ const build = () => {
1670
+ const signed = mint();
1671
+ return {
1672
+ method: "POST",
1673
+ ...init,
1674
+ headers: Array.isArray(signed) ? [...signed, ...Object.entries(extra ?? {})] : { ...signed, ...extra }
1675
+ };
1676
+ };
1677
+ return this.context.invokeWithRetry ? this.context.invokeWithRetry(url, build) : this.context.fetch(url, build());
1678
+ }
1619
1679
  /**
1620
1680
  * Create the abort signal for one request, bounded by the request timeout.
1621
1681
  *
@@ -1983,15 +2043,6 @@ var KVService = class extends BaseService {
1983
2043
  get host() {
1984
2044
  return this.context.hosts[0];
1985
2045
  }
1986
- withJsonContentType(headers) {
1987
- if (Array.isArray(headers)) {
1988
- return [...headers, ["content-type", "application/json"]];
1989
- }
1990
- return {
1991
- ...headers,
1992
- "content-type": "application/json"
1993
- };
1994
- }
1995
2046
  /**
1996
2047
  * Execute an invoke operation.
1997
2048
  *
@@ -2002,20 +2053,11 @@ var KVService = class extends BaseService {
2002
2053
  * @returns Fetch response
2003
2054
  */
2004
2055
  async invokeOperation(path, action, body, signal, extraHeaders) {
2005
- const session = this.context.session;
2006
- const headers = this.context.invoke(
2007
- session,
2008
- "kv",
2009
- path,
2010
- action
2056
+ return this.invokeWithRetry(
2057
+ `${this.host}/invoke`,
2058
+ () => this.context.invoke(this.context.session, "kv", path, action),
2059
+ { headers: extraHeaders, body, signal }
2011
2060
  );
2012
- const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
2013
- return this.context.fetch(`${this.host}/invoke`, {
2014
- method: "POST",
2015
- headers: requestHeaders,
2016
- body,
2017
- signal
2018
- });
2019
2061
  }
2020
2062
  /**
2021
2063
  * Serialize a single put value into a fetch body.
@@ -2221,28 +2263,28 @@ var KVService = class extends BaseService {
2221
2263
  }
2222
2264
  const request = this.createRequestSignal(options?.signal, options?.timeout);
2223
2265
  try {
2224
- const session = this.context.session;
2225
- const invocationHeaders = this.context.invokeAny(
2226
- session,
2227
- paths.map((path) => ({
2228
- spaceId: session.spaceId,
2229
- service: "kv",
2230
- path,
2231
- action
2232
- }))
2233
- );
2234
2266
  const limitHeaders = {};
2235
2267
  if (getOptions?.maxResponseBytes !== void 0) {
2236
2268
  limitHeaders["x-tinycloud-max-response-bytes"] = String(
2237
2269
  getOptions.maxResponseBytes
2238
2270
  );
2239
2271
  }
2240
- const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
2241
- const response = await this.context.fetch(`${this.host}/invoke`, {
2242
- method: "POST",
2243
- headers,
2244
- signal: request.signal
2245
- });
2272
+ const response = await this.invokeWithRetry(
2273
+ `${this.host}/invoke`,
2274
+ () => {
2275
+ const session = this.context.session;
2276
+ return this.context.invokeAny(
2277
+ session,
2278
+ paths.map((path) => ({
2279
+ spaceId: session.spaceId,
2280
+ service: "kv",
2281
+ path,
2282
+ action
2283
+ }))
2284
+ );
2285
+ },
2286
+ { headers: limitHeaders, signal: request.signal }
2287
+ );
2246
2288
  if (!response.ok) {
2247
2289
  const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
2248
2290
  if (response.status === 401 || response.status === 403) {
@@ -2288,10 +2330,10 @@ var KVService = class extends BaseService {
2288
2330
  result: err(serviceError(item.error.code, item.error.message, "kv"))
2289
2331
  };
2290
2332
  }
2291
- const headers2 = item.headers;
2333
+ const headers = item.headers;
2292
2334
  const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
2293
2335
  item.dataBase64,
2294
- headers2,
2336
+ headers,
2295
2337
  getOptions?.raw,
2296
2338
  getOptions?.binary
2297
2339
  );
@@ -2299,7 +2341,7 @@ var KVService = class extends BaseService {
2299
2341
  key: keys[index],
2300
2342
  result: ok({
2301
2343
  data,
2302
- headers: this.createBatchResponseHeaders(headers2)
2344
+ headers: this.createBatchResponseHeaders(headers)
2303
2345
  })
2304
2346
  };
2305
2347
  });
@@ -2534,7 +2576,7 @@ var KVService = class extends BaseService {
2534
2576
  ));
2535
2577
  }
2536
2578
  const data = await this.parseResponse(response, options?.raw, options?.binary);
2537
- return ok({ data, headers: this.createResponseHeaders(response.headers) });
2579
+ return ok({ data, headers: this.createResponseHeaders(response.headers), ...response.retry && { retry: response.retry } });
2538
2580
  }
2539
2581
  async batchGet(keys, options) {
2540
2582
  return this.withTelemetry(
@@ -2624,7 +2666,8 @@ var KVService = class extends BaseService {
2624
2666
  }
2625
2667
  return ok({
2626
2668
  data: void 0,
2627
- headers: this.createResponseHeaders(response.headers)
2669
+ headers: this.createResponseHeaders(response.headers),
2670
+ ...response.retry && { retry: response.retry }
2628
2671
  });
2629
2672
  }
2630
2673
  /**
@@ -2697,25 +2740,23 @@ var KVService = class extends BaseService {
2697
2740
  entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
2698
2741
  );
2699
2742
  }
2700
- const headers = this.context.invokeAny(
2701
- session,
2702
- paths.map((path) => ({
2703
- spaceId: session.spaceId,
2704
- service: "kv",
2705
- path,
2706
- action: KVAction.PUT
2707
- }))
2743
+ const response = await this.invokeWithRetry(
2744
+ `${this.host}/invoke`,
2745
+ () => {
2746
+ const headers = this.context.invokeAny(
2747
+ session,
2748
+ paths.map((path) => ({
2749
+ spaceId: session.spaceId,
2750
+ service: "kv",
2751
+ path,
2752
+ action: KVAction.PUT
2753
+ }))
2754
+ );
2755
+ requestMayHaveDispatched = true;
2756
+ return headers;
2757
+ },
2758
+ { body, signal }
2708
2759
  );
2709
- const url = `${this.host}/invoke`;
2710
- const init = {
2711
- method: "POST",
2712
- headers,
2713
- body,
2714
- signal
2715
- };
2716
- const fetchFn = this.context.fetch;
2717
- requestMayHaveDispatched = true;
2718
- const response = await fetchFn(url, init);
2719
2760
  if (!response.ok) {
2720
2761
  let errorText;
2721
2762
  try {
@@ -3185,7 +3226,8 @@ var KVService = class extends BaseService {
3185
3226
  }
3186
3227
  return ok({
3187
3228
  data: void 0,
3188
- headers: this.createResponseHeaders(response.headers)
3229
+ headers: this.createResponseHeaders(response.headers),
3230
+ ...response.retry && { retry: response.retry }
3189
3231
  });
3190
3232
  }
3191
3233
  /**
@@ -3259,12 +3301,6 @@ var KVService = class extends BaseService {
3259
3301
  }
3260
3302
  const path = this.getFullPath(key, options?.prefix);
3261
3303
  const session = this.context.session;
3262
- const headers = this.context.invoke(
3263
- session,
3264
- "kv",
3265
- path,
3266
- KVAction.GET
3267
- );
3268
3304
  const body = {
3269
3305
  space: session.spaceId,
3270
3306
  path,
@@ -3278,12 +3314,15 @@ var KVService = class extends BaseService {
3278
3314
  }
3279
3315
  const request = this.createRequestSignal(options?.signal, options?.timeout);
3280
3316
  try {
3281
- const response = await this.context.fetch(`${this.host}/signed/kv`, {
3282
- method: "POST",
3283
- headers: this.withJsonContentType(headers),
3284
- body: JSON.stringify(body),
3285
- signal: request.signal
3286
- });
3317
+ const response = await this.invokeWithRetry(
3318
+ `${this.host}/signed/kv`,
3319
+ () => this.context.invoke(session, "kv", path, KVAction.GET),
3320
+ {
3321
+ headers: { "content-type": "application/json" },
3322
+ body: JSON.stringify(body),
3323
+ signal: request.signal
3324
+ }
3325
+ );
3287
3326
  if (!response.ok) {
3288
3327
  return await this.createSignedReadUrlError(response, key, path);
3289
3328
  }
@@ -3780,18 +3819,19 @@ var SQLService = class extends BaseService {
3780
3819
  }
3781
3820
  // === Private helpers ===
3782
3821
  async invokeSQL(dbName, actions, body, signal) {
3783
- const session = this.context.session;
3784
3822
  const actionList = Array.isArray(actions) ? actions : [actions];
3785
- const headers = actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
3786
- return this.context.fetch(`${this.host}/invoke`, {
3787
- method: "POST",
3788
- headers: {
3789
- ...headers,
3790
- "Content-Type": "application/json"
3823
+ return this.invokeWithRetry(
3824
+ `${this.host}/invoke`,
3825
+ () => {
3826
+ const session = this.context.session;
3827
+ return actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
3791
3828
  },
3792
- body: JSON.stringify(body),
3793
- signal
3794
- });
3829
+ {
3830
+ headers: { "Content-Type": "application/json" },
3831
+ body: JSON.stringify(body),
3832
+ signal
3833
+ }
3834
+ );
3795
3835
  }
3796
3836
  actionForSql(sql, fallback) {
3797
3837
  const token = firstSqlToken(sql);
@@ -4252,22 +4292,20 @@ var DuckDbService = class extends BaseService {
4252
4292
  }
4253
4293
  const request = this.createRequestSignal(options?.signal);
4254
4294
  try {
4255
- const session = this.context.session;
4256
- const headers = this.context.invoke(
4257
- session,
4258
- "duckdb",
4259
- dbName,
4260
- DuckDbAction.IMPORT
4295
+ const response = await this.invokeWithRetry(
4296
+ `${this.host}/invoke`,
4297
+ () => this.context.invoke(
4298
+ this.context.session,
4299
+ "duckdb",
4300
+ dbName,
4301
+ DuckDbAction.IMPORT
4302
+ ),
4303
+ {
4304
+ headers: { "Content-Type": "application/x-duckdb" },
4305
+ body: new Blob([data]),
4306
+ signal: request.signal
4307
+ }
4261
4308
  );
4262
- const response = await this.context.fetch(`${this.host}/invoke`, {
4263
- method: "POST",
4264
- headers: {
4265
- ...headers,
4266
- "Content-Type": "application/x-duckdb"
4267
- },
4268
- body: new Blob([data]),
4269
- signal: request.signal
4270
- });
4271
4309
  if (!response.ok) {
4272
4310
  return await this.handleErrorResponse(response, "import");
4273
4311
  }
@@ -4281,18 +4319,15 @@ var DuckDbService = class extends BaseService {
4281
4319
  }
4282
4320
  // === Private helpers ===
4283
4321
  async invokeDuckDb(dbName, action, body, signal, extraHeaders) {
4284
- const session = this.context.session;
4285
- const headers = this.context.invoke(session, "duckdb", dbName, action);
4286
- return this.context.fetch(`${this.host}/invoke`, {
4287
- method: "POST",
4288
- headers: {
4289
- ...headers,
4290
- "Content-Type": "application/json",
4291
- ...extraHeaders
4292
- },
4293
- body: JSON.stringify(body),
4294
- signal
4295
- });
4322
+ return this.invokeWithRetry(
4323
+ `${this.host}/invoke`,
4324
+ () => this.context.invoke(this.context.session, "duckdb", dbName, action),
4325
+ {
4326
+ headers: { "Content-Type": "application/json", ...extraHeaders },
4327
+ body: JSON.stringify(body),
4328
+ signal
4329
+ }
4330
+ );
4296
4331
  }
4297
4332
  async handleErrorResponse(response, operation) {
4298
4333
  const errorText = await response.text();
@@ -4481,26 +4516,24 @@ var HooksService = class extends BaseService {
4481
4516
  );
4482
4517
  }
4483
4518
  try {
4484
- const response = await this.context.fetch(`${this.host}/hooks/webhooks`, {
4485
- method: "POST",
4486
- headers: {
4487
- ...serviceHeadersToRecord(
4488
- this.createHookHeaders(
4489
- "tinycloud.hooks/register",
4490
- buildScopePath(webhook.service, webhook.pathPrefix)
4491
- )
4492
- ),
4493
- "content-type": "application/json"
4494
- },
4495
- body: JSON.stringify({
4496
- space: webhook.space,
4497
- service: webhook.service,
4498
- pathPrefix: normalizePathPrefix(webhook.pathPrefix),
4499
- abilities: webhook.abilities ?? [],
4500
- callbackUrl: webhook.callbackUrl,
4501
- secret: webhook.secret
4502
- })
4503
- });
4519
+ const response = await this.invokeWithRetry(
4520
+ `${this.host}/hooks/webhooks`,
4521
+ () => this.createHookHeaders(
4522
+ "tinycloud.hooks/register",
4523
+ buildScopePath(webhook.service, webhook.pathPrefix)
4524
+ ),
4525
+ {
4526
+ headers: { "content-type": "application/json" },
4527
+ body: JSON.stringify({
4528
+ space: webhook.space,
4529
+ service: webhook.service,
4530
+ pathPrefix: normalizePathPrefix(webhook.pathPrefix),
4531
+ abilities: webhook.abilities ?? [],
4532
+ callbackUrl: webhook.callbackUrl,
4533
+ secret: webhook.secret
4534
+ })
4535
+ }
4536
+ );
4504
4537
  if (!response.ok) {
4505
4538
  return err(
4506
4539
  await responseError("hooks", "failed to register webhook", response)
@@ -4538,17 +4571,13 @@ var HooksService = class extends BaseService {
4538
4571
  query.set("prefix", normalizedPrefix);
4539
4572
  }
4540
4573
  }
4541
- const response = await this.context.fetch(
4574
+ const response = await this.invokeWithRetry(
4542
4575
  `${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
4543
- {
4544
- method: "GET",
4545
- headers: serviceHeadersToRecord(
4546
- this.createHookHeaders(
4547
- "tinycloud.hooks/list",
4548
- options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
4549
- )
4550
- )
4551
- }
4576
+ () => this.createHookHeaders(
4577
+ "tinycloud.hooks/list",
4578
+ options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
4579
+ ),
4580
+ { method: "GET" }
4552
4581
  );
4553
4582
  if (!response.ok) {
4554
4583
  return err(
@@ -4575,20 +4604,16 @@ var HooksService = class extends BaseService {
4575
4604
  return err(authRequiredError("hooks"));
4576
4605
  }
4577
4606
  try {
4578
- const response = await this.context.fetch(
4607
+ const response = await this.invokeWithRetry(
4579
4608
  `${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
4580
- {
4581
- method: "DELETE",
4582
- headers: serviceHeadersToRecord(
4583
- this.createHookHeaders(
4584
- "tinycloud.hooks/unregister",
4585
- options.target ? buildScopePath(
4586
- options.target.service,
4587
- options.target.pathPrefix
4588
- ) : `webhooks/${id}`
4589
- )
4590
- )
4591
- }
4609
+ () => this.createHookHeaders(
4610
+ "tinycloud.hooks/unregister",
4611
+ options.target ? buildScopePath(
4612
+ options.target.service,
4613
+ options.target.pathPrefix
4614
+ ) : `webhooks/${id}`
4615
+ ),
4616
+ { method: "DELETE" }
4592
4617
  );
4593
4618
  if (!response.ok) {
4594
4619
  return err(
@@ -5044,23 +5069,7 @@ function readProperty(value, key) {
5044
5069
  return void 0;
5045
5070
  }
5046
5071
  }
5047
- function sleep(delayMs, _attempt, signal) {
5048
- return new Promise((resolve) => {
5049
- const finish = () => {
5050
- clearTimeout(timer);
5051
- signal.removeEventListener("abort", stop);
5052
- resolve();
5053
- };
5054
- const timer = setTimeout(finish, delayMs);
5055
- timer.unref?.();
5056
- const stop = () => finish();
5057
- if (signal.aborted) {
5058
- finish();
5059
- } else {
5060
- signal.addEventListener("abort", stop, { once: true });
5061
- }
5062
- });
5063
- }
5072
+ var sleep = (delayMs, _attempt, signal) => abortableSleep(delayMs, signal, true);
5064
5073
  function abortPromise(signal) {
5065
5074
  let resolve;
5066
5075
  const promise = new Promise((done) => {
@@ -5445,7 +5454,7 @@ var StorageFullMonitor = class {
5445
5454
  var UNKNOWN_OWNER = "\0unknown";
5446
5455
 
5447
5456
  // src/storage/status.ts
5448
- var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/billing";
5457
+ var STORAGE_MANAGE_URL = "https://account.tinycloud.xyz/storage";
5449
5458
  var STORAGE_NEARLY_FULL_RATIO = 0.9;
5450
5459
  function storageUsageState(usedBytes, limitBytes) {
5451
5460
  if (limitBytes === null) return "ok";
@@ -5477,40 +5486,150 @@ function parseStorageStatus(body) {
5477
5486
  if (status.account.plan) status.plan = status.account.plan;
5478
5487
  status.state = storageUsageState(account.usedBytes, account.limitBytes);
5479
5488
  }
5480
- return status;
5489
+ return status;
5490
+ }
5491
+
5492
+ // src/quota/TinyCloudQuota.ts
5493
+ var TinyCloudQuota = class {
5494
+ constructor(config = {}) {
5495
+ this.quotaUrl = null;
5496
+ this.config = config;
5497
+ }
5498
+ /** Set the quota URL discovered from the /info endpoint */
5499
+ setQuotaUrl(url) {
5500
+ this.quotaUrl = url;
5501
+ }
5502
+ /** Whether a quota service is available */
5503
+ get available() {
5504
+ return this.quotaUrl !== null;
5505
+ }
5506
+ /** Query quota status for a space from the quota URL */
5507
+ async getQuota(spaceId) {
5508
+ if (!this.quotaUrl) return null;
5509
+ const resp = await fetch(
5510
+ `${this.quotaUrl}/api/quota/${encodeURIComponent(spaceId)}`
5511
+ );
5512
+ if (!resp.ok) return null;
5513
+ const data = await resp.json();
5514
+ return {
5515
+ limitBytes: data.storage_limit_bytes ?? 0
5516
+ };
5517
+ }
5518
+ /** Trigger the upgrade callback when a quota error is encountered */
5519
+ handleQuotaError(info) {
5520
+ this.config.onUpgradeRequired?.(info);
5521
+ }
5522
+ };
5523
+
5524
+ // src/encryption/canonical.ts
5525
+ function canonicalize(value) {
5526
+ if (value === void 0) {
5527
+ return "";
5528
+ }
5529
+ return stringify(value);
5530
+ }
5531
+ function stringify(value) {
5532
+ if (value === null) return "null";
5533
+ switch (typeof value) {
5534
+ case "boolean":
5535
+ case "number":
5536
+ return JSON.stringify(value);
5537
+ case "string":
5538
+ return JSON.stringify(value);
5539
+ case "object": {
5540
+ if (Array.isArray(value)) {
5541
+ return `[${value.map(stringify).join(",")}]`;
5542
+ }
5543
+ const keys = Object.keys(value).sort();
5544
+ const parts = [];
5545
+ for (const k of keys) {
5546
+ const v = value[k];
5547
+ if (v === void 0) continue;
5548
+ parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
5549
+ }
5550
+ return `{${parts.join(",")}}`;
5551
+ }
5552
+ default:
5553
+ throw new TypeError(
5554
+ `canonicalize: unsupported value type ${typeof value}`
5555
+ );
5556
+ }
5557
+ }
5558
+ var HEX = "0123456789abcdef";
5559
+ function hexEncode(bytes) {
5560
+ let out = "";
5561
+ for (let i = 0; i < bytes.length; i++) {
5562
+ const b = bytes[i];
5563
+ out += HEX[b >> 4 & 15] + HEX[b & 15];
5564
+ }
5565
+ return out;
5481
5566
  }
5482
-
5483
- // src/quota/TinyCloudQuota.ts
5484
- var TinyCloudQuota = class {
5485
- constructor(config = {}) {
5486
- this.quotaUrl = null;
5487
- this.config = config;
5567
+ function hexDecode(hex) {
5568
+ if (hex.length % 2 !== 0) {
5569
+ throw new Error("hex string must have even length");
5488
5570
  }
5489
- /** Set the quota URL discovered from the /info endpoint */
5490
- setQuotaUrl(url) {
5491
- this.quotaUrl = url;
5571
+ const out = new Uint8Array(hex.length / 2);
5572
+ for (let i = 0; i < out.length; i++) {
5573
+ const hi = parseInt(hex[i * 2], 16);
5574
+ const lo = parseInt(hex[i * 2 + 1], 16);
5575
+ if (Number.isNaN(hi) || Number.isNaN(lo)) {
5576
+ throw new Error("invalid hex character");
5577
+ }
5578
+ out[i] = hi << 4 | lo;
5492
5579
  }
5493
- /** Whether a quota service is available */
5494
- get available() {
5495
- return this.quotaUrl !== null;
5580
+ return out;
5581
+ }
5582
+ function base64Encode(bytes) {
5583
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5584
+ let out = "";
5585
+ for (let i = 0; i < bytes.length; i += 3) {
5586
+ const b0 = bytes[i];
5587
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
5588
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
5589
+ out += chars[b0 >> 2 & 63];
5590
+ out += chars[(b0 << 4 | b1 >> 4) & 63];
5591
+ out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
5592
+ out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
5496
5593
  }
5497
- /** Query quota status for a space from the quota URL */
5498
- async getQuota(spaceId) {
5499
- if (!this.quotaUrl) return null;
5500
- const resp = await fetch(
5501
- `${this.quotaUrl}/api/quota/${encodeURIComponent(spaceId)}`
5502
- );
5503
- if (!resp.ok) return null;
5504
- const data = await resp.json();
5505
- return {
5506
- limitBytes: data.storage_limit_bytes ?? 0
5507
- };
5594
+ return out;
5595
+ }
5596
+ function base64Decode(s) {
5597
+ const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
5598
+ const len = clean.length;
5599
+ if (len % 4 !== 0) {
5600
+ throw new Error("invalid base64 input");
5508
5601
  }
5509
- /** Trigger the upgrade callback when a quota error is encountered */
5510
- handleQuotaError(info) {
5511
- this.config.onUpgradeRequired?.(info);
5602
+ const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
5603
+ const outLen = len / 4 * 3 - padding;
5604
+ const out = new Uint8Array(outLen);
5605
+ const lookup = {};
5606
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5607
+ for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
5608
+ let outIdx = 0;
5609
+ for (let i = 0; i < len; i += 4) {
5610
+ const v0 = lookup[clean[i]] ?? 0;
5611
+ const v1 = lookup[clean[i + 1]] ?? 0;
5612
+ const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
5613
+ const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
5614
+ const b0 = v0 << 2 | v1 >> 4;
5615
+ const b1 = (v1 & 15) << 4 | v2 >> 2;
5616
+ const b2 = (v2 & 3) << 6 | v3;
5617
+ if (outIdx < outLen) out[outIdx++] = b0;
5618
+ if (outIdx < outLen) out[outIdx++] = b1;
5619
+ if (outIdx < outLen) out[outIdx++] = b2;
5512
5620
  }
5513
- };
5621
+ return out;
5622
+ }
5623
+ function utf8Encode(s) {
5624
+ return new TextEncoder().encode(s);
5625
+ }
5626
+ function utf8Decode(b) {
5627
+ return new TextDecoder().decode(b);
5628
+ }
5629
+ function canonicalHashHex(sha256, value) {
5630
+ const canonical = canonicalize(value);
5631
+ return hexEncode(sha256(utf8Encode(canonical)));
5632
+ }
5514
5633
 
5515
5634
  // src/vault/types.ts
5516
5635
  var VaultPublicSpaceKVActions = [
@@ -5681,7 +5800,7 @@ function toBytes(str) {
5681
5800
  function fromBytes(bytes) {
5682
5801
  return new TextDecoder().decode(bytes);
5683
5802
  }
5684
- function hexEncode(bytes) {
5803
+ function hexEncode2(bytes) {
5685
5804
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
5686
5805
  }
5687
5806
  function concatBytes(...arrays) {
@@ -5694,14 +5813,14 @@ function concatBytes(...arrays) {
5694
5813
  }
5695
5814
  return result;
5696
5815
  }
5697
- function base64Encode(bytes) {
5816
+ function base64Encode2(bytes) {
5698
5817
  let binary = "";
5699
5818
  for (let i = 0; i < bytes.length; i++) {
5700
5819
  binary += String.fromCharCode(bytes[i]);
5701
5820
  }
5702
5821
  return btoa(binary);
5703
5822
  }
5704
- function base64Decode(str) {
5823
+ function base64Decode2(str) {
5705
5824
  const binary = atob(str);
5706
5825
  const bytes = new Uint8Array(binary.length);
5707
5826
  for (let i = 0; i < binary.length; i++) {
@@ -5743,6 +5862,11 @@ function defaultVaultMessage(input) {
5743
5862
  return input.message;
5744
5863
  case "PERMISSION_DENIED":
5745
5864
  return input.message;
5865
+ case "AAD_MISMATCH":
5866
+ case "ETAG_MISMATCH":
5867
+ case "INVALID_ETAG":
5868
+ case "OUTCOME_UNKNOWN":
5869
+ case "NODE_BUSY":
5746
5870
  case "STORAGE_QUOTA_EXCEEDED":
5747
5871
  case "STORAGE_LIMIT_REACHED":
5748
5872
  return input.message;
@@ -5765,6 +5889,37 @@ function typedKvCause(error) {
5765
5889
  ...error.cause !== void 0 ? { cause: error.cause } : {}
5766
5890
  });
5767
5891
  }
5892
+ function canonicalAadSpaceId(spaceId) {
5893
+ return spaceId.replace(/0x[0-9a-fA-F]{40}/g, (address) => address.toLowerCase());
5894
+ }
5895
+ function checkEnvelopeAad(value, spaceId, key) {
5896
+ const e = value;
5897
+ if (typeof e !== "object" || e === null || Array.isArray(e) || !["networkId", "alg", "encryptedSymmetricKey", "encryptedSymmetricKeyHash", "ciphertext"].every(
5898
+ (field) => typeof e[field] === "string" && e[field] !== ""
5899
+ ) || !Number.isInteger(e.keyVersion) || e.aad !== void 0 && typeof e.aad !== "string") {
5900
+ return "corrupt";
5901
+ }
5902
+ let aad = "";
5903
+ try {
5904
+ if (typeof e.aad === "string") aad = fromBytes(base64Decode(e.aad));
5905
+ } catch {
5906
+ }
5907
+ const suffix = `:${key}`;
5908
+ return aad.endsWith(suffix) && canonicalAadSpaceId(aad.slice(0, -suffix.length)) === canonicalAadSpaceId(`tinycloud.vault:${spaceId}`) ? "ok" : "mismatch";
5909
+ }
5910
+ var AMBIGUOUS_WRITE_STATUSES = [500, 502, 503, 504, 520, 522, 524];
5911
+ function ifMatchWriteError(key, error) {
5912
+ const status = error.meta?.status;
5913
+ 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;
5914
+ if (code === void 0) return void 0;
5915
+ return vaultError({
5916
+ code,
5917
+ key,
5918
+ message: error.message,
5919
+ ...error.meta !== void 0 ? { meta: error.meta } : {},
5920
+ cause: typedKvCause(error)
5921
+ });
5922
+ }
5768
5923
  function kvWriteError(context, error) {
5769
5924
  if (error.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error.code === ErrorCodes.STORAGE_LIMIT_REACHED) {
5770
5925
  return vaultError({ code: error.code, message: error.message, meta: error.meta });
@@ -5951,7 +6106,7 @@ var DataVaultService = class extends BaseService {
5951
6106
  }
5952
6107
  if (existingPubKey) {
5953
6108
  this.encryptionIdentity = {
5954
- publicKey: base64Decode(existingPubKey),
6109
+ publicKey: base64Decode2(existingPubKey),
5955
6110
  privateKey: new Uint8Array(0)
5956
6111
  // private key not available without signing
5957
6112
  };
@@ -5976,7 +6131,7 @@ var DataVaultService = class extends BaseService {
5976
6131
  );
5977
6132
  this.encryptionIdentity = this.crypto.x25519FromSeed(seed);
5978
6133
  try {
5979
- const pubKeyB64 = base64Encode(this.encryptionIdentity.publicKey);
6134
+ const pubKeyB64 = base64Encode2(this.encryptionIdentity.publicKey);
5980
6135
  await this.tc.ensurePublicSpace();
5981
6136
  await this.tc.publicKV.put(".well-known/vault-pubkey", pubKeyB64);
5982
6137
  await this.tc.publicKV.put(".well-known/vault-version", CURRENT_VAULT_VERSION);
@@ -6042,6 +6197,21 @@ var DataVaultService = class extends BaseService {
6042
6197
  message: "Authentication required"
6043
6198
  });
6044
6199
  }
6200
+ if (options?.ifMatch !== void 0) {
6201
+ if (options.ifNoneMatch !== void 0) {
6202
+ return vaultError({
6203
+ code: "STORAGE_ERROR",
6204
+ cause: new Error("ifMatch cannot be combined with ifNoneMatch")
6205
+ });
6206
+ }
6207
+ if (!/^"[^"\s]+"$/.test(options.ifMatch)) {
6208
+ return vaultError({
6209
+ code: "INVALID_ETAG",
6210
+ key,
6211
+ 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"
6212
+ });
6213
+ }
6214
+ }
6045
6215
  try {
6046
6216
  const { plaintext, contentType } = this.serializeValue(value, options);
6047
6217
  const metadata = {
@@ -6064,7 +6234,8 @@ var DataVaultService = class extends BaseService {
6064
6234
  }
6065
6235
  const valuePath = `vault/${key}`;
6066
6236
  const valueBody = JSON.stringify(envelopeResult.data);
6067
- const valuePutResult = options?.ifNoneMatch !== void 0 ? await this.tc.kv.put(valuePath, valueBody, { ifNoneMatch: options.ifNoneMatch }) : await this.tc.kv.put(valuePath, valueBody);
6237
+ const putOptions = options?.ifMatch !== void 0 ? { ifMatch: options.ifMatch } : options?.ifNoneMatch !== void 0 ? { ifNoneMatch: options.ifNoneMatch } : void 0;
6238
+ const valuePutResult = putOptions !== void 0 ? await this.tc.kv.put(valuePath, valueBody, putOptions) : await this.tc.kv.put(valuePath, valueBody);
6068
6239
  if (!valuePutResult.ok) {
6069
6240
  if (options?.ifNoneMatch !== void 0 && valuePutResult.error.code === ErrorCodes.KV_PRECONDITION_FAILED) {
6070
6241
  return vaultError({
@@ -6074,6 +6245,10 @@ var DataVaultService = class extends BaseService {
6074
6245
  cause: typedKvCause(valuePutResult.error)
6075
6246
  });
6076
6247
  }
6248
+ if (options?.ifMatch !== void 0) {
6249
+ const classified = ifMatchWriteError(key, valuePutResult.error);
6250
+ if (classified !== void 0) return classified;
6251
+ }
6077
6252
  return kvWriteError("Failed to store encrypted value", valuePutResult.error);
6078
6253
  }
6079
6254
  return { ok: true, data: void 0 };
@@ -6108,6 +6283,19 @@ var DataVaultService = class extends BaseService {
6108
6283
  }
6109
6284
  const rawEnvelope = unwrapKVData(valueResult.data);
6110
6285
  const envelope = typeof rawEnvelope === "string" ? JSON.parse(rawEnvelope) : rawEnvelope;
6286
+ if (options?.expectAad === true) {
6287
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6288
+ if (verdict === "corrupt") {
6289
+ return vaultError({ code: "DECRYPTION_FAILED", message: "envelope is malformed" });
6290
+ }
6291
+ if (verdict === "mismatch") {
6292
+ return vaultError({
6293
+ code: "AAD_MISMATCH",
6294
+ key,
6295
+ message: `Envelope at ${key} is not bound to this space and key`
6296
+ });
6297
+ }
6298
+ }
6111
6299
  const proof = await this.decryptCapabilityProof();
6112
6300
  const plaintextResult = await config.service.decryptEnvelope(envelope, proof);
6113
6301
  if (!plaintextResult.ok) {
@@ -6124,7 +6312,11 @@ var DataVaultService = class extends BaseService {
6124
6312
  contentType,
6125
6313
  options
6126
6314
  );
6127
- return { ok: true, data: { value, metadata, keyId } };
6315
+ const etag = options?.source === "network" ? valueResult.data.headers?.etag : void 0;
6316
+ return {
6317
+ ok: true,
6318
+ data: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6319
+ };
6128
6320
  } catch (error) {
6129
6321
  return vaultError({
6130
6322
  code: "STORAGE_ERROR",
@@ -6170,6 +6362,12 @@ var DataVaultService = class extends BaseService {
6170
6362
  } catch {
6171
6363
  return { status: "corrupt_envelope" };
6172
6364
  }
6365
+ if (options?.expectAad === true) {
6366
+ const verdict = checkEnvelopeAad(envelope, this.vaultConfig.spaceId, key);
6367
+ if (verdict !== "ok") {
6368
+ return { status: verdict === "corrupt" ? "corrupt_envelope" : "aad_mismatch" };
6369
+ }
6370
+ }
6173
6371
  let plaintextResult;
6174
6372
  try {
6175
6373
  const proof = await this.decryptCapabilityProof();
@@ -6201,7 +6399,11 @@ var DataVaultService = class extends BaseService {
6201
6399
  contentType,
6202
6400
  options
6203
6401
  );
6204
- return { status: "ok", entry: { value, metadata, keyId } };
6402
+ const etag = valueResult.data.headers?.etag;
6403
+ return {
6404
+ status: "ok",
6405
+ entry: { value, metadata, keyId, ...etag === void 0 ? {} : { etag } }
6406
+ };
6205
6407
  } catch {
6206
6408
  return { status: "invalid_payload" };
6207
6409
  }
@@ -6251,6 +6453,14 @@ var DataVaultService = class extends BaseService {
6251
6453
  )
6252
6454
  });
6253
6455
  }
6456
+ if (options?.ifMatch !== void 0) {
6457
+ return vaultError({
6458
+ code: "STORAGE_ERROR",
6459
+ cause: new Error(
6460
+ "ifMatch is only supported by network-encrypted vaults"
6461
+ )
6462
+ });
6463
+ }
6254
6464
  if (!this._isUnlocked || !this.masterKey) {
6255
6465
  return vaultError({
6256
6466
  code: "VAULT_LOCKED",
@@ -6276,7 +6486,7 @@ var DataVaultService = class extends BaseService {
6276
6486
  }
6277
6487
  const contentType = options?.contentType ?? (value instanceof Uint8Array ? "application/octet-stream" : "application/json");
6278
6488
  const entryKey = this.crypto.randomBytes(32);
6279
- const keyId = hexEncode(this.crypto.sha256(entryKey)).slice(0, 16);
6489
+ const keyId = hexEncode2(this.crypto.sha256(entryKey)).slice(0, 16);
6280
6490
  const encrypted = this.crypto.encrypt(entryKey, plaintext);
6281
6491
  const keyBlob = this.crypto.encrypt(this.masterKey, entryKey);
6282
6492
  const metadata = {
@@ -6294,7 +6504,7 @@ var DataVaultService = class extends BaseService {
6294
6504
  ...metadata
6295
6505
  });
6296
6506
  const keyPayload = JSON.stringify({
6297
- key: base64Encode(keyBlob),
6507
+ key: base64Encode2(keyBlob),
6298
6508
  metadata: keyMetadata
6299
6509
  });
6300
6510
  const keyPutResult = await this.tc.kv.put(
@@ -6305,7 +6515,7 @@ var DataVaultService = class extends BaseService {
6305
6515
  return kvWriteError("Failed to store key blob", keyPutResult.error);
6306
6516
  }
6307
6517
  const valuePayload = JSON.stringify({
6308
- data: base64Encode(encrypted),
6518
+ data: base64Encode2(encrypted),
6309
6519
  metadata
6310
6520
  });
6311
6521
  const valuePutResult = await this.tc.kv.put(
@@ -6336,6 +6546,12 @@ var DataVaultService = class extends BaseService {
6336
6546
  if (this.usesNetworkEncryption) {
6337
6547
  return this.getNetworkEncrypted(key, options);
6338
6548
  }
6549
+ if (options?.expectAad === true) {
6550
+ return vaultError({
6551
+ code: "STORAGE_ERROR",
6552
+ cause: new Error("expectAad is only supported by network-encrypted vaults")
6553
+ });
6554
+ }
6339
6555
  if (!this._isUnlocked || !this.masterKey) {
6340
6556
  return vaultError({
6341
6557
  code: "VAULT_LOCKED",
@@ -6357,7 +6573,7 @@ var DataVaultService = class extends BaseService {
6357
6573
  return vaultError({ code: "KEY_NOT_FOUND", key });
6358
6574
  }
6359
6575
  const keyEnvelope = JSON.parse(keyResult.data.data);
6360
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6576
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6361
6577
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6362
6578
  const valueResult = await this.tc.kv.get(`vault/${key}`, {
6363
6579
  raw: true,
@@ -6367,7 +6583,7 @@ var DataVaultService = class extends BaseService {
6367
6583
  return vaultError({ code: "KEY_NOT_FOUND", key });
6368
6584
  }
6369
6585
  const valueEnvelope = JSON.parse(valueResult.data.data);
6370
- const encryptedBytes = base64Decode(valueEnvelope.data);
6586
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6371
6587
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6372
6588
  const metadata = valueEnvelope.metadata ?? {};
6373
6589
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6615,7 +6831,7 @@ var DataVaultService = class extends BaseService {
6615
6831
  return vaultError({ code: "KEY_NOT_FOUND", key });
6616
6832
  }
6617
6833
  const keyEnvelope = JSON.parse(keyResult.data.data);
6618
- const keyBlobBytes = base64Decode(keyEnvelope.key);
6834
+ const keyBlobBytes = base64Decode2(keyEnvelope.key);
6619
6835
  const entryKey = this.crypto.decrypt(this.masterKey, keyBlobBytes);
6620
6836
  const ephemeralSeed = this.crypto.randomBytes(32);
6621
6837
  const ephemeralKeyPair = this.crypto.x25519FromSeed(ephemeralSeed);
@@ -6634,7 +6850,7 @@ var DataVaultService = class extends BaseService {
6634
6850
  encryptedGrant
6635
6851
  );
6636
6852
  const grantPayload = JSON.stringify({
6637
- grant: base64Encode(grantBlob),
6853
+ grant: base64Encode2(grantBlob),
6638
6854
  spaceId: this.vaultConfig.spaceId,
6639
6855
  metadata: {
6640
6856
  [VaultHeaders.GRANT_VERSION]: "1",
@@ -6674,6 +6890,14 @@ var DataVaultService = class extends BaseService {
6674
6890
  */
6675
6891
  async getShared(grantorDID, key, options) {
6676
6892
  return this.withTelemetry("getShared", key, async () => {
6893
+ if (options?.expectAad === true) {
6894
+ return vaultError({
6895
+ code: "STORAGE_ERROR",
6896
+ cause: new Error(
6897
+ "expectAad is not supported by getShared: the AAD binds the grantor's space, which this vault does not know"
6898
+ )
6899
+ });
6900
+ }
6677
6901
  if (this.usesNetworkEncryption) {
6678
6902
  const grantorKV = options?.kv;
6679
6903
  if (!grantorKV) {
@@ -6768,7 +6992,7 @@ var DataVaultService = class extends BaseService {
6768
6992
  });
6769
6993
  }
6770
6994
  const grantEnvelope = typeof grantResult.data?.data === "string" ? JSON.parse(grantResult.data.data) : grantResult.data?.data;
6771
- const grantBlobBytes = base64Decode(grantEnvelope.grant);
6995
+ const grantBlobBytes = base64Decode2(grantEnvelope.grant);
6772
6996
  const ephemeralPubKey = grantBlobBytes.slice(0, 32);
6773
6997
  const encryptedGrant = grantBlobBytes.slice(32);
6774
6998
  const sharedSecret = this.crypto.x25519Dh(
@@ -6791,7 +7015,7 @@ var DataVaultService = class extends BaseService {
6791
7015
  });
6792
7016
  }
6793
7017
  const valueEnvelope = typeof valueResult.data?.data === "string" ? JSON.parse(valueResult.data.data) : valueResult.data?.data;
6794
- const encryptedBytes = base64Decode(valueEnvelope.data);
7018
+ const encryptedBytes = base64Decode2(valueEnvelope.data);
6795
7019
  const plaintext = this.crypto.decrypt(entryKey, encryptedBytes);
6796
7020
  const metadata = valueEnvelope.metadata ?? {};
6797
7021
  const contentType = metadata[VaultHeaders.CONTENT_TYPE] ?? "application/json";
@@ -6845,7 +7069,7 @@ var DataVaultService = class extends BaseService {
6845
7069
  if (!result.ok) {
6846
7070
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
6847
7071
  }
6848
- const pubKeyBytes = base64Decode(result.data);
7072
+ const pubKeyBytes = base64Decode2(result.data);
6849
7073
  return { ok: true, data: pubKeyBytes };
6850
7074
  } catch (error) {
6851
7075
  return vaultError({ code: "PUBLIC_KEY_NOT_FOUND", did });
@@ -6964,7 +7188,7 @@ var DataVaultService = class extends BaseService {
6964
7188
  }
6965
7189
  const currentEntry = getResult.data;
6966
7190
  const newEntryKey = this.crypto.randomBytes(32);
6967
- const newKeyId = hexEncode(this.crypto.sha256(newEntryKey)).slice(
7191
+ const newKeyId = hexEncode2(this.crypto.sha256(newEntryKey)).slice(
6968
7192
  0,
6969
7193
  16
6970
7194
  );
@@ -6981,7 +7205,7 @@ var DataVaultService = class extends BaseService {
6981
7205
  [VaultHeaders.KEY_ID]: newKeyId
6982
7206
  };
6983
7207
  const keyPayload = JSON.stringify({
6984
- key: base64Encode(newKeyBlob),
7208
+ key: base64Encode2(newKeyBlob),
6985
7209
  metadata: JSON.stringify({
6986
7210
  keyId: newKeyId,
6987
7211
  ...metadata
@@ -6995,7 +7219,7 @@ var DataVaultService = class extends BaseService {
6995
7219
  return kvWriteError("Failed to store rotated key blob", keyPutResult.error);
6996
7220
  }
6997
7221
  const valuePayload = JSON.stringify({
6998
- data: base64Encode(encrypted),
7222
+ data: base64Encode2(encrypted),
6999
7223
  metadata
7000
7224
  });
7001
7225
  const valuePutResult = await this.tc.kv.put(
@@ -7287,116 +7511,6 @@ var SecretsService = class {
7287
7511
  }
7288
7512
  };
7289
7513
 
7290
- // src/encryption/canonical.ts
7291
- function canonicalize(value) {
7292
- if (value === void 0) {
7293
- return "";
7294
- }
7295
- return stringify(value);
7296
- }
7297
- function stringify(value) {
7298
- if (value === null) return "null";
7299
- switch (typeof value) {
7300
- case "boolean":
7301
- case "number":
7302
- return JSON.stringify(value);
7303
- case "string":
7304
- return JSON.stringify(value);
7305
- case "object": {
7306
- if (Array.isArray(value)) {
7307
- return `[${value.map(stringify).join(",")}]`;
7308
- }
7309
- const keys = Object.keys(value).sort();
7310
- const parts = [];
7311
- for (const k of keys) {
7312
- const v = value[k];
7313
- if (v === void 0) continue;
7314
- parts.push(`${JSON.stringify(k)}:${stringify(v)}`);
7315
- }
7316
- return `{${parts.join(",")}}`;
7317
- }
7318
- default:
7319
- throw new TypeError(
7320
- `canonicalize: unsupported value type ${typeof value}`
7321
- );
7322
- }
7323
- }
7324
- var HEX = "0123456789abcdef";
7325
- function hexEncode2(bytes) {
7326
- let out = "";
7327
- for (let i = 0; i < bytes.length; i++) {
7328
- const b = bytes[i];
7329
- out += HEX[b >> 4 & 15] + HEX[b & 15];
7330
- }
7331
- return out;
7332
- }
7333
- function hexDecode(hex) {
7334
- if (hex.length % 2 !== 0) {
7335
- throw new Error("hex string must have even length");
7336
- }
7337
- const out = new Uint8Array(hex.length / 2);
7338
- for (let i = 0; i < out.length; i++) {
7339
- const hi = parseInt(hex[i * 2], 16);
7340
- const lo = parseInt(hex[i * 2 + 1], 16);
7341
- if (Number.isNaN(hi) || Number.isNaN(lo)) {
7342
- throw new Error("invalid hex character");
7343
- }
7344
- out[i] = hi << 4 | lo;
7345
- }
7346
- return out;
7347
- }
7348
- function base64Encode2(bytes) {
7349
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7350
- let out = "";
7351
- for (let i = 0; i < bytes.length; i += 3) {
7352
- const b0 = bytes[i];
7353
- const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
7354
- const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
7355
- out += chars[b0 >> 2 & 63];
7356
- out += chars[(b0 << 4 | b1 >> 4) & 63];
7357
- out += i + 1 < bytes.length ? chars[(b1 << 2 | b2 >> 6) & 63] : "=";
7358
- out += i + 2 < bytes.length ? chars[b2 & 63] : "=";
7359
- }
7360
- return out;
7361
- }
7362
- function base64Decode2(s) {
7363
- const clean = s.replace(/[^A-Za-z0-9+/=]/g, "");
7364
- const len = clean.length;
7365
- if (len % 4 !== 0) {
7366
- throw new Error("invalid base64 input");
7367
- }
7368
- const padding = clean.endsWith("==") ? 2 : clean.endsWith("=") ? 1 : 0;
7369
- const outLen = len / 4 * 3 - padding;
7370
- const out = new Uint8Array(outLen);
7371
- const lookup = {};
7372
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
7373
- for (let i = 0; i < chars.length; i++) lookup[chars[i]] = i;
7374
- let outIdx = 0;
7375
- for (let i = 0; i < len; i += 4) {
7376
- const v0 = lookup[clean[i]] ?? 0;
7377
- const v1 = lookup[clean[i + 1]] ?? 0;
7378
- const v2 = clean[i + 2] === "=" ? 0 : lookup[clean[i + 2]] ?? 0;
7379
- const v3 = clean[i + 3] === "=" ? 0 : lookup[clean[i + 3]] ?? 0;
7380
- const b0 = v0 << 2 | v1 >> 4;
7381
- const b1 = (v1 & 15) << 4 | v2 >> 2;
7382
- const b2 = (v2 & 3) << 6 | v3;
7383
- if (outIdx < outLen) out[outIdx++] = b0;
7384
- if (outIdx < outLen) out[outIdx++] = b1;
7385
- if (outIdx < outLen) out[outIdx++] = b2;
7386
- }
7387
- return out;
7388
- }
7389
- function utf8Encode(s) {
7390
- return new TextEncoder().encode(s);
7391
- }
7392
- function utf8Decode(b) {
7393
- return new TextDecoder().decode(b);
7394
- }
7395
- function canonicalHashHex(sha256, value) {
7396
- const canonical = canonicalize(value);
7397
- return hexEncode2(sha256(utf8Encode(canonical)));
7398
- }
7399
-
7400
7514
  // src/encryption/types.ts
7401
7515
  var DEFAULT_ENCRYPTION_ALG = "x25519-aes256gcm/v1";
7402
7516
  var ENVELOPE_VERSION = 1;
@@ -7606,7 +7720,7 @@ function encryptToNetwork(crypto2, input) {
7606
7720
  const symmetricKey = crypto2.randomBytes(32);
7607
7721
  const ciphertext = crypto2.authEncrypt(symmetricKey, input.plaintext, input.aad);
7608
7722
  const wrapped = crypto2.sealToNetworkKey(input.networkPublicKey, symmetricKey);
7609
- const encryptedSymmetricKey = base64Encode2(wrapped);
7723
+ const encryptedSymmetricKey = base64Encode(wrapped);
7610
7724
  const encryptedSymmetricKeyHash = canonicalHashHex(
7611
7725
  crypto2.sha256,
7612
7726
  encryptedSymmetricKey
@@ -7618,8 +7732,8 @@ function encryptToNetwork(crypto2, input) {
7618
7732
  keyVersion,
7619
7733
  encryptedSymmetricKey,
7620
7734
  encryptedSymmetricKeyHash,
7621
- ciphertext: base64Encode2(ciphertext),
7622
- ...input.aad !== void 0 ? { aad: base64Encode2(input.aad) } : {},
7735
+ ciphertext: base64Encode(ciphertext),
7736
+ ...input.aad !== void 0 ? { aad: base64Encode(input.aad) } : {},
7623
7737
  ...input.metadata !== void 0 ? { metadata: input.metadata } : {}
7624
7738
  };
7625
7739
  return { envelope, symmetricKey };
@@ -7693,8 +7807,8 @@ function validateEnvelope(crypto2, envelope) {
7693
7807
  return { ok: true, data: e };
7694
7808
  }
7695
7809
  function decryptEnvelopeWithKey(crypto2, envelope, symmetricKey) {
7696
- const ciphertext = base64Decode2(envelope.ciphertext);
7697
- const aad = envelope.aad !== void 0 ? base64Decode2(envelope.aad) : void 0;
7810
+ const ciphertext = base64Decode(envelope.ciphertext);
7811
+ const aad = envelope.aad !== void 0 ? base64Decode(envelope.aad) : void 0;
7698
7812
  return crypto2.authDecrypt(symmetricKey, ciphertext, aad);
7699
7813
  }
7700
7814
 
@@ -7712,7 +7826,7 @@ function buildCanonicalDecryptRequest(input) {
7712
7826
  return { canonicalBody, bodyHash, receiverPublicKeyHash };
7713
7827
  }
7714
7828
  function buildDecryptFacts(input) {
7715
- const bodyHash = input.canonicalBody !== void 0 ? hexEncode2(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7829
+ const bodyHash = input.canonicalBody !== void 0 ? hexEncode(input.crypto.sha256(utf8Encode(input.canonicalBody))) : canonicalHashHex(
7716
7830
  input.crypto.sha256,
7717
7831
  input.body
7718
7832
  );
@@ -7995,7 +8109,7 @@ function verifyDecryptResponse(input) {
7995
8109
  })
7996
8110
  };
7997
8111
  }
7998
- const expectedRequestHash = hexEncode2(
8112
+ const expectedRequestHash = hexEncode(
7999
8113
  crypto2.sha256(utf8Encode(`${invocationCid}${requestBodyHash}`))
8000
8114
  );
8001
8115
  if (response.requestHash !== expectedRequestHash) {
@@ -8019,7 +8133,7 @@ function verifyDecryptResponse(input) {
8019
8133
  const signedBytes = new TextEncoder().encode(
8020
8134
  canonicalSignedResponse(response)
8021
8135
  );
8022
- const signatureBytes = base64Decode2(response.nodeSignature);
8136
+ const signatureBytes = base64Decode(response.nodeSignature);
8023
8137
  if (!crypto2.verifyNodeSignature(response.nodeId, signedBytes, signatureBytes)) {
8024
8138
  return {
8025
8139
  ok: false,
@@ -8031,7 +8145,7 @@ function verifyDecryptResponse(input) {
8031
8145
  return { ok: true, data: response };
8032
8146
  }
8033
8147
  function openWrappedKey(crypto2, receiverPrivateKey, response) {
8034
- const wrapped = base64Decode2(response.wrappedKey);
8148
+ const wrapped = base64Decode(response.wrappedKey);
8035
8149
  return crypto2.openWithReceiverKey(receiverPrivateKey, wrapped);
8036
8150
  }
8037
8151
 
@@ -8075,7 +8189,7 @@ var EncryptionService = class extends BaseService {
8075
8189
  const usable = ensureNetworkUsableForDecrypt(discovered.data);
8076
8190
  if (!usable.ok) return usable;
8077
8191
  const descriptor = usable.data;
8078
- const networkPublicKey = base64Decode2(descriptor.publicEncryptionKey);
8192
+ const networkPublicKey = base64Decode(descriptor.publicEncryptionKey);
8079
8193
  const result = encryptToNetwork(this.crypto, {
8080
8194
  // Discovery returns the node's canonical network identifier. Persist
8081
8195
  // that spelling in the envelope so a later response can be compared
@@ -8103,7 +8217,7 @@ var EncryptionService = class extends BaseService {
8103
8217
  this.assertActive();
8104
8218
  const validated = validateEnvelope(this.crypto, envelope);
8105
8219
  if (!validated.ok) return validated;
8106
- if (options?.aad !== void 0 && validated.data.aad !== base64Encode2(options.aad)) {
8220
+ if (options?.aad !== void 0 && validated.data.aad !== base64Encode(options.aad)) {
8107
8221
  return encErr(
8108
8222
  encryptionError({
8109
8223
  code: "INVALID_INPUT",
@@ -8131,7 +8245,7 @@ var EncryptionService = class extends BaseService {
8131
8245
  );
8132
8246
  }
8133
8247
  const receiverKey = generateRandomReceiverKey({ crypto: this.crypto });
8134
- const receiverPublicKey = base64Encode2(receiverKey.publicKey);
8248
+ const receiverPublicKey = base64Encode(receiverKey.publicKey);
8135
8249
  const receiverPublicKeyHash = canonicalHashHex(
8136
8250
  this.crypto.sha256,
8137
8251
  receiverPublicKey
@@ -8307,8 +8421,8 @@ export {
8307
8421
  authRequiredError,
8308
8422
  authUnauthorizedError,
8309
8423
  authorizationVerdictOf,
8310
- base64Decode2 as base64Decode,
8311
- base64Encode2 as base64Encode,
8424
+ base64Decode,
8425
+ base64Encode,
8312
8426
  buildCanonicalDecryptRequest,
8313
8427
  buildDecryptAttenuation,
8314
8428
  buildDecryptFacts,
@@ -8338,7 +8452,7 @@ export {
8338
8452
  generateRandomReceiverKey,
8339
8453
  getTinyCloudDebugLogs,
8340
8454
  hexDecode,
8341
- hexEncode2 as hexEncode,
8455
+ hexEncode,
8342
8456
  installTinyCloudDebugGlobals,
8343
8457
  isNetworkId,
8344
8458
  isStorageFullError,