@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.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:
|
|
60
|
+
maxAttempts: 4,
|
|
61
61
|
backoff: "exponential",
|
|
62
|
-
baseDelayMs:
|
|
63
|
-
maxDelayMs:
|
|
64
|
-
retryableErrors: [ErrorCodes.
|
|
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/
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
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
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
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
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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;
|
|
@@ -1383,56 +1045,442 @@ function parseStorageRejection(responseText) {
|
|
|
1383
1045
|
} catch {
|
|
1384
1046
|
return parseStorageQuotaBytes(responseText);
|
|
1385
1047
|
}
|
|
1386
|
-
if (typeof body !== "object" || body === null) return void 0;
|
|
1387
|
-
const record = body;
|
|
1388
|
-
const message = typeof record.message === "string" ? record.message : "";
|
|
1389
|
-
const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
|
|
1390
|
-
const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
|
|
1391
|
-
if (!space && !isStorageBody) {
|
|
1392
|
-
return void 0;
|
|
1048
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1049
|
+
const record = body;
|
|
1050
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
1051
|
+
const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
|
|
1052
|
+
const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
|
|
1053
|
+
if (!space && !isStorageBody) {
|
|
1054
|
+
return void 0;
|
|
1055
|
+
}
|
|
1056
|
+
const details = { ...space };
|
|
1057
|
+
const account = byteCountsOf(record.account);
|
|
1058
|
+
if (account) {
|
|
1059
|
+
const plan = record.account.plan;
|
|
1060
|
+
details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
|
|
1061
|
+
}
|
|
1062
|
+
return details;
|
|
1063
|
+
}
|
|
1064
|
+
function storageRejectionError(service, status, meta, responseText) {
|
|
1065
|
+
const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
|
|
1066
|
+
return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
|
|
1067
|
+
}
|
|
1068
|
+
function isStorageFullError(error) {
|
|
1069
|
+
return error?.code === ErrorCodes.STORAGE_QUOTA_EXCEEDED || error?.code === ErrorCodes.STORAGE_LIMIT_REACHED;
|
|
1070
|
+
}
|
|
1071
|
+
function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
|
|
1072
|
+
if (error instanceof Error) {
|
|
1073
|
+
if (isRequestTimeoutError(error)) {
|
|
1074
|
+
return timeoutError(service, error.timeoutMs, error);
|
|
1075
|
+
}
|
|
1076
|
+
if (error.name === "AbortError") {
|
|
1077
|
+
return abortedError(service);
|
|
1078
|
+
}
|
|
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", {});
|
|
1393
1339
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1340
|
+
// ============================================================
|
|
1341
|
+
// Retry Policy
|
|
1342
|
+
// ============================================================
|
|
1343
|
+
/**
|
|
1344
|
+
* Get the retry policy configuration.
|
|
1345
|
+
*/
|
|
1346
|
+
get retryPolicy() {
|
|
1347
|
+
return this._retryPolicy;
|
|
1399
1348
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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.");
|
|
1416
1374
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
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
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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
|
-
|
|
2006
|
-
|
|
2007
|
-
session,
|
|
2008
|
-
|
|
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
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
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
|
|
2333
|
+
const headers = item.headers;
|
|
2292
2334
|
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
2293
2335
|
item.dataBase64,
|
|
2294
|
-
|
|
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(
|
|
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
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
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.
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
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
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
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
|
-
|
|
3793
|
-
|
|
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
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
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
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
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.
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
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.
|
|
4574
|
+
const response = await this.invokeWithRetry(
|
|
4542
4575
|
`${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
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.
|
|
4607
|
+
const response = await this.invokeWithRetry(
|
|
4579
4608
|
`${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
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
|
-
|
|
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/
|
|
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";
|