@tinycloud/sdk-services 3.3.0-beta.11 → 3.3.0-beta.39
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.
- package/dist/{BaseService-Cc6Ueicm.d.cts → BaseService-BhfKzKke.d.cts} +5 -1
- package/dist/{BaseService-DOdXd7sr.d.ts → BaseService-x4AFZiEa.d.ts} +5 -1
- package/dist/encryption/index.cjs +18 -5
- package/dist/encryption/index.cjs.map +1 -1
- package/dist/encryption/index.d.cts +2 -2
- package/dist/encryption/index.d.ts +2 -2
- package/dist/encryption/index.js +18 -5
- package/dist/encryption/index.js.map +1 -1
- package/dist/index.cjs +660 -651
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -10
- package/dist/index.d.ts +17 -10
- package/dist/index.js +660 -651
- package/dist/index.js.map +1 -1
- package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
- package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
- package/dist/kv/index.cjs +72 -80
- package/dist/kv/index.cjs.map +1 -1
- package/dist/kv/index.d.cts +4 -5
- package/dist/kv/index.d.ts +4 -5
- package/dist/kv/index.js +72 -80
- package/dist/kv/index.js.map +1 -1
- package/dist/kv/replication/index.cjs +6 -5
- package/dist/kv/replication/index.cjs.map +1 -1
- package/dist/kv/replication/index.d.cts +3 -3
- package/dist/kv/replication/index.d.ts +3 -3
- package/dist/kv/replication/index.js +6 -5
- package/dist/kv/replication/index.js.map +1 -1
- package/dist/{scope-B-m6cKH-.d.ts → scope-BHTkk5Yk.d.ts} +13 -2
- package/dist/{scope-B7oeKdLM.d.cts → scope-DMhalRFn.d.cts} +13 -2
- package/dist/sql/index.cjs +30 -16
- package/dist/sql/index.cjs.map +1 -1
- package/dist/sql/index.d.cts +2 -2
- package/dist/sql/index.d.ts +2 -2
- package/dist/sql/index.js +30 -16
- package/dist/sql/index.js.map +1 -1
- package/dist/{types-BjH0ZtGy.d.cts → types-eVS1Whuf.d.cts} +24 -4
- package/dist/{types-BjH0ZtGy.d.ts → types-eVS1Whuf.d.ts} +24 -4
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -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:
|
|
226
|
+
maxAttempts: 4,
|
|
227
227
|
backoff: "exponential",
|
|
228
|
-
baseDelayMs:
|
|
229
|
-
maxDelayMs:
|
|
230
|
-
retryableErrors: [ErrorCodes.
|
|
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/
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
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
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
-
|
|
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
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
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;
|
|
@@ -1549,56 +1211,442 @@ function parseStorageRejection(responseText) {
|
|
|
1549
1211
|
} catch {
|
|
1550
1212
|
return parseStorageQuotaBytes(responseText);
|
|
1551
1213
|
}
|
|
1552
|
-
if (typeof body !== "object" || body === null) return void 0;
|
|
1553
|
-
const record = body;
|
|
1554
|
-
const message = typeof record.message === "string" ? record.message : "";
|
|
1555
|
-
const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
|
|
1556
|
-
const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
|
|
1557
|
-
if (!space && !isStorageBody) {
|
|
1558
|
-
return void 0;
|
|
1214
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1215
|
+
const record = body;
|
|
1216
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
1217
|
+
const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
|
|
1218
|
+
const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
|
|
1219
|
+
if (!space && !isStorageBody) {
|
|
1220
|
+
return void 0;
|
|
1221
|
+
}
|
|
1222
|
+
const details = { ...space };
|
|
1223
|
+
const account = byteCountsOf(record.account);
|
|
1224
|
+
if (account) {
|
|
1225
|
+
const plan = record.account.plan;
|
|
1226
|
+
details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
|
|
1227
|
+
}
|
|
1228
|
+
return details;
|
|
1229
|
+
}
|
|
1230
|
+
function storageRejectionError(service, status, meta, responseText) {
|
|
1231
|
+
const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
|
|
1232
|
+
return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
|
|
1233
|
+
}
|
|
1234
|
+
function isStorageFullError(error) {
|
|
1235
|
+
return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
|
|
1236
|
+
}
|
|
1237
|
+
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
1238
|
+
if (error instanceof Error) {
|
|
1239
|
+
if (isRequestTimeoutError(error)) {
|
|
1240
|
+
return timeoutError(service, error.timeoutMs, error);
|
|
1241
|
+
}
|
|
1242
|
+
if (error.name === "AbortError") {
|
|
1243
|
+
return abortedError(service);
|
|
1244
|
+
}
|
|
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", {});
|
|
1559
1505
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1506
|
+
// ============================================================
|
|
1507
|
+
// Retry Policy
|
|
1508
|
+
// ============================================================
|
|
1509
|
+
/**
|
|
1510
|
+
* Get the retry policy configuration.
|
|
1511
|
+
*/
|
|
1512
|
+
get retryPolicy() {
|
|
1513
|
+
return this._retryPolicy;
|
|
1565
1514
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
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.");
|
|
1582
1540
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
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
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
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
|
-
|
|
2172
|
-
|
|
2173
|
-
session,
|
|
2174
|
-
|
|
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
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
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
|
|
2499
|
+
const headers = item.headers;
|
|
2458
2500
|
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
2459
2501
|
item.dataBase64,
|
|
2460
|
-
|
|
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(
|
|
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
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
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.
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
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
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
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
|
-
|
|
3959
|
-
|
|
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
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
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
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
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.
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
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.
|
|
4740
|
+
const response = await this.invokeWithRetry(
|
|
4708
4741
|
`${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
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.
|
|
4773
|
+
const response = await this.invokeWithRetry(
|
|
4745
4774
|
`${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
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
|
-
|
|
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/
|
|
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";
|