@tinycloud/replica 0.3.0-beta.5 → 0.3.0-beta.7
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/kv-storage-testing.js.map +1 -1
- package/dist/kv-storage.js +7 -6
- package/dist/kv-storage.js.map +1 -1
- package/dist/{kv-storage.worker.67e46cebbda277040312a770449123633491901d6cceacea95754a843e01e9c9.js → kv-storage.worker.850a5e19b486939f03586f7d1294934f54f44cca0224450b1ba94dfb07912a75.js} +568 -560
- package/dist/kv-storage.worker.js +568 -560
- package/dist/kv-storage.worker.js.map +1 -1
- package/dist/replica.worker.js +562 -555
- package/dist/replica.worker.js.map +1 -1
- package/dist/sqlite.cjs +9 -6
- package/dist/sqlite.cjs.map +1 -1
- package/dist/sqlite.js +9 -6
- package/dist/sqlite.js.map +1 -1
- package/package.json +2 -2
|
@@ -2814,7 +2814,7 @@ var ErrorCodes = {
|
|
|
2814
2814
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
2815
2815
|
// Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
|
|
2816
2816
|
// retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
|
|
2817
|
-
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
2817
|
+
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
2818
2818
|
NODE_BUSY: "NODE_BUSY",
|
|
2819
2819
|
/** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
|
|
2820
2820
|
REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
|
|
@@ -2855,11 +2855,12 @@ var ErrorCodes = {
|
|
|
2855
2855
|
DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
|
|
2856
2856
|
};
|
|
2857
2857
|
var defaultRetryPolicy = {
|
|
2858
|
-
maxAttempts:
|
|
2858
|
+
maxAttempts: 4,
|
|
2859
2859
|
backoff: "exponential",
|
|
2860
|
-
baseDelayMs:
|
|
2861
|
-
maxDelayMs:
|
|
2862
|
-
retryableErrors: [ErrorCodes.
|
|
2860
|
+
baseDelayMs: 250,
|
|
2861
|
+
maxDelayMs: 4e3,
|
|
2862
|
+
retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],
|
|
2863
|
+
maxTotalMs: 12e3
|
|
2863
2864
|
};
|
|
2864
2865
|
var grantableActions = new Set(
|
|
2865
2866
|
CAPABILITY_REGISTRY.map(({ urn }) => urn).filter((urn) => !urn.endsWith("/*"))
|
|
@@ -11169,7 +11170,7 @@ var ErrorCodes2 = {
|
|
|
11169
11170
|
PERMISSION_DENIED: "PERMISSION_DENIED",
|
|
11170
11171
|
// Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
|
|
11171
11172
|
// retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
|
|
11172
|
-
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
11173
|
+
/** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
|
|
11173
11174
|
NODE_BUSY: "NODE_BUSY",
|
|
11174
11175
|
/** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
|
|
11175
11176
|
REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
|
|
@@ -11210,11 +11211,12 @@ var ErrorCodes2 = {
|
|
|
11210
11211
|
DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
|
|
11211
11212
|
};
|
|
11212
11213
|
var defaultRetryPolicy2 = {
|
|
11213
|
-
maxAttempts:
|
|
11214
|
+
maxAttempts: 4,
|
|
11214
11215
|
backoff: "exponential",
|
|
11215
|
-
baseDelayMs:
|
|
11216
|
-
maxDelayMs:
|
|
11217
|
-
retryableErrors: [ErrorCodes2.
|
|
11216
|
+
baseDelayMs: 250,
|
|
11217
|
+
maxDelayMs: 4e3,
|
|
11218
|
+
retryableErrors: [ErrorCodes2.NODE_BUSY, ErrorCodes2.REQUEST_TIMEOUT],
|
|
11219
|
+
maxTotalMs: 12e3
|
|
11218
11220
|
};
|
|
11219
11221
|
var TelemetryEvents = {
|
|
11220
11222
|
SPAN: "telemetry.span",
|
|
@@ -11634,7 +11636,9 @@ var RetryPolicySchema = external_exports.object({
|
|
|
11634
11636
|
/** Maximum delay in milliseconds between retries */
|
|
11635
11637
|
maxDelayMs: external_exports.number().nonnegative(),
|
|
11636
11638
|
/** Error codes that should trigger a retry */
|
|
11637
|
-
retryableErrors: external_exports.array(external_exports.string())
|
|
11639
|
+
retryableErrors: external_exports.array(external_exports.string()),
|
|
11640
|
+
/** Total time budget for all attempts and waits, in milliseconds */
|
|
11641
|
+
maxTotalMs: external_exports.number().positive().optional()
|
|
11638
11642
|
});
|
|
11639
11643
|
var ServiceSessionSchema = external_exports.object({
|
|
11640
11644
|
/** The delegation header containing the UCAN */
|
|
@@ -11650,345 +11654,6 @@ var ServiceSessionSchema = external_exports.object({
|
|
|
11650
11654
|
/** The session key JWK (required for invoke) */
|
|
11651
11655
|
jwk: external_exports.object({}).passthrough()
|
|
11652
11656
|
});
|
|
11653
|
-
var ServiceContext = class {
|
|
11654
|
-
constructor(config) {
|
|
11655
|
-
this._session = null;
|
|
11656
|
-
this._services = /* @__PURE__ */ new Map();
|
|
11657
|
-
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
11658
|
-
this._abortController = new AbortController();
|
|
11659
|
-
this._retired = false;
|
|
11660
|
-
this._invoke = this.wrapInvoke(config.invoke);
|
|
11661
|
-
this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
|
|
11662
|
-
this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
|
|
11663
|
-
this._hosts = config.hosts;
|
|
11664
|
-
this._session = config.session ?? null;
|
|
11665
|
-
this._retryPolicy = {
|
|
11666
|
-
...defaultRetryPolicy2,
|
|
11667
|
-
...config.retryPolicy
|
|
11668
|
-
};
|
|
11669
|
-
this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
|
|
11670
|
-
this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
|
|
11671
|
-
}
|
|
11672
|
-
// ============================================================
|
|
11673
|
-
// Session Management
|
|
11674
|
-
// ============================================================
|
|
11675
|
-
/**
|
|
11676
|
-
* Get the current session.
|
|
11677
|
-
*/
|
|
11678
|
-
get session() {
|
|
11679
|
-
return this._retired ? null : this._session;
|
|
11680
|
-
}
|
|
11681
|
-
/**
|
|
11682
|
-
* Check if the context has an authenticated session.
|
|
11683
|
-
*/
|
|
11684
|
-
get isAuthenticated() {
|
|
11685
|
-
return !this._retired && this._session !== null;
|
|
11686
|
-
}
|
|
11687
|
-
/**
|
|
11688
|
-
* Update the session and notify all registered services.
|
|
11689
|
-
*
|
|
11690
|
-
* @param session - New session or null to clear
|
|
11691
|
-
*/
|
|
11692
|
-
setSession(session) {
|
|
11693
|
-
this.assertActive();
|
|
11694
|
-
this._session = session;
|
|
11695
|
-
this.emit("session.changed", { authenticated: session !== null });
|
|
11696
|
-
for (const service of this._services.values()) {
|
|
11697
|
-
service.onSessionChange(session);
|
|
11698
|
-
}
|
|
11699
|
-
}
|
|
11700
|
-
// ============================================================
|
|
11701
|
-
// Platform Dependencies
|
|
11702
|
-
// ============================================================
|
|
11703
|
-
/**
|
|
11704
|
-
* Get the invoke function for WASM operations.
|
|
11705
|
-
*/
|
|
11706
|
-
get invoke() {
|
|
11707
|
-
this.assertActive();
|
|
11708
|
-
return this._invoke;
|
|
11709
|
-
}
|
|
11710
|
-
/**
|
|
11711
|
-
* Get the multi-resource invoke function when available.
|
|
11712
|
-
*/
|
|
11713
|
-
get invokeAny() {
|
|
11714
|
-
this.assertActive();
|
|
11715
|
-
return this._invokeAny;
|
|
11716
|
-
}
|
|
11717
|
-
/**
|
|
11718
|
-
* Get the fetch function for HTTP requests.
|
|
11719
|
-
*/
|
|
11720
|
-
get fetch() {
|
|
11721
|
-
this.assertActive();
|
|
11722
|
-
return this._fetch;
|
|
11723
|
-
}
|
|
11724
|
-
/**
|
|
11725
|
-
* Get the list of TinyCloud host URLs.
|
|
11726
|
-
*/
|
|
11727
|
-
get hosts() {
|
|
11728
|
-
this.assertActive();
|
|
11729
|
-
return this._hosts;
|
|
11730
|
-
}
|
|
11731
|
-
// ============================================================
|
|
11732
|
-
// Service Registry
|
|
11733
|
-
// ============================================================
|
|
11734
|
-
/**
|
|
11735
|
-
* Register a service with the context.
|
|
11736
|
-
*
|
|
11737
|
-
* @param name - Service name (e.g., 'kv')
|
|
11738
|
-
* @param service - Service instance
|
|
11739
|
-
*/
|
|
11740
|
-
registerService(name, service) {
|
|
11741
|
-
this._services.set(name, service);
|
|
11742
|
-
}
|
|
11743
|
-
/**
|
|
11744
|
-
* Unregister a service from the context.
|
|
11745
|
-
*
|
|
11746
|
-
* @param name - Service name to remove
|
|
11747
|
-
*/
|
|
11748
|
-
unregisterService(name) {
|
|
11749
|
-
this._services.delete(name);
|
|
11750
|
-
}
|
|
11751
|
-
/**
|
|
11752
|
-
* Get a registered service by name.
|
|
11753
|
-
*
|
|
11754
|
-
* @param name - Service name
|
|
11755
|
-
* @returns The service instance or undefined if not registered
|
|
11756
|
-
*/
|
|
11757
|
-
getService(name) {
|
|
11758
|
-
return this._services.get(name);
|
|
11759
|
-
}
|
|
11760
|
-
// ============================================================
|
|
11761
|
-
// Event System (Telemetry)
|
|
11762
|
-
// ============================================================
|
|
11763
|
-
/**
|
|
11764
|
-
* Emit a telemetry event.
|
|
11765
|
-
*
|
|
11766
|
-
* @param event - Event name
|
|
11767
|
-
* @param data - Event data
|
|
11768
|
-
*/
|
|
11769
|
-
emit(event, data) {
|
|
11770
|
-
tinyCloudDebugLogger.log(event, data);
|
|
11771
|
-
if (this._telemetryEnabled && this._telemetryHandler) {
|
|
11772
|
-
try {
|
|
11773
|
-
this._telemetryHandler(event, projectDiagnosticData(data));
|
|
11774
|
-
} catch (error) {
|
|
11775
|
-
console.error(`Error in telemetry handler for "${event}":`, error);
|
|
11776
|
-
}
|
|
11777
|
-
}
|
|
11778
|
-
const handlers = this._eventHandlers.get(event);
|
|
11779
|
-
if (handlers) {
|
|
11780
|
-
for (const handler of handlers) {
|
|
11781
|
-
try {
|
|
11782
|
-
handler(data);
|
|
11783
|
-
} catch (error) {
|
|
11784
|
-
console.error(`Error in event handler for "${event}":`, error);
|
|
11785
|
-
}
|
|
11786
|
-
}
|
|
11787
|
-
}
|
|
11788
|
-
}
|
|
11789
|
-
/**
|
|
11790
|
-
* Subscribe to telemetry events.
|
|
11791
|
-
*
|
|
11792
|
-
* @param event - Event name to subscribe to
|
|
11793
|
-
* @param handler - Handler function
|
|
11794
|
-
* @returns Unsubscribe function
|
|
11795
|
-
*/
|
|
11796
|
-
on(event, handler) {
|
|
11797
|
-
if (!this._eventHandlers.has(event)) {
|
|
11798
|
-
this._eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
11799
|
-
}
|
|
11800
|
-
this._eventHandlers.get(event).add(handler);
|
|
11801
|
-
return () => {
|
|
11802
|
-
const handlers = this._eventHandlers.get(event);
|
|
11803
|
-
if (handlers) {
|
|
11804
|
-
handlers.delete(handler);
|
|
11805
|
-
if (handlers.size === 0) {
|
|
11806
|
-
this._eventHandlers.delete(event);
|
|
11807
|
-
}
|
|
11808
|
-
}
|
|
11809
|
-
};
|
|
11810
|
-
}
|
|
11811
|
-
/**
|
|
11812
|
-
* Remove all event handlers for an event.
|
|
11813
|
-
*
|
|
11814
|
-
* @param event - Event name (if omitted, clears all events)
|
|
11815
|
-
*/
|
|
11816
|
-
clearEventHandlers(event) {
|
|
11817
|
-
if (event) {
|
|
11818
|
-
this._eventHandlers.delete(event);
|
|
11819
|
-
} else {
|
|
11820
|
-
this._eventHandlers.clear();
|
|
11821
|
-
}
|
|
11822
|
-
}
|
|
11823
|
-
// ============================================================
|
|
11824
|
-
// Lifecycle
|
|
11825
|
-
// ============================================================
|
|
11826
|
-
/**
|
|
11827
|
-
* Get the abort signal for cancelling operations.
|
|
11828
|
-
*/
|
|
11829
|
-
get abortSignal() {
|
|
11830
|
-
return this._abortController.signal;
|
|
11831
|
-
}
|
|
11832
|
-
get operationAbortSignal() {
|
|
11833
|
-
return this._operationAbortSignal;
|
|
11834
|
-
}
|
|
11835
|
-
setOperationAbortSignal(signal) {
|
|
11836
|
-
this._operationAbortSignal = signal;
|
|
11837
|
-
}
|
|
11838
|
-
/**
|
|
11839
|
-
* Permanently retire this graph after its owner installs a replacement.
|
|
11840
|
-
* Unlike `abort()`, retirement never creates a fresh controller, so captured
|
|
11841
|
-
* services cannot resume calls under a superseded session.
|
|
11842
|
-
*/
|
|
11843
|
-
retire() {
|
|
11844
|
-
if (this._retired) return;
|
|
11845
|
-
this._retired = true;
|
|
11846
|
-
this._session = null;
|
|
11847
|
-
this._abortController.abort();
|
|
11848
|
-
for (const service of this._services.values()) {
|
|
11849
|
-
try {
|
|
11850
|
-
service.onSignOut();
|
|
11851
|
-
} catch (error) {
|
|
11852
|
-
console.error("Error retiring service:", error);
|
|
11853
|
-
}
|
|
11854
|
-
}
|
|
11855
|
-
}
|
|
11856
|
-
/**
|
|
11857
|
-
* Abort all pending operations and notify services.
|
|
11858
|
-
* Creates a new AbortController for future operations.
|
|
11859
|
-
*/
|
|
11860
|
-
abort() {
|
|
11861
|
-
if (this._retired) return;
|
|
11862
|
-
this._abortController.abort();
|
|
11863
|
-
this._abortController = new AbortController();
|
|
11864
|
-
for (const service of this._services.values()) {
|
|
11865
|
-
try {
|
|
11866
|
-
service.onSignOut();
|
|
11867
|
-
} catch (error) {
|
|
11868
|
-
console.error("Error signing out service:", error);
|
|
11869
|
-
}
|
|
11870
|
-
}
|
|
11871
|
-
}
|
|
11872
|
-
/**
|
|
11873
|
-
* Sign out - abort operations and clear session.
|
|
11874
|
-
*/
|
|
11875
|
-
signOut() {
|
|
11876
|
-
if (this._retired) return;
|
|
11877
|
-
this.abort();
|
|
11878
|
-
this.setSession(null);
|
|
11879
|
-
this.emit("session.expired", {});
|
|
11880
|
-
}
|
|
11881
|
-
// ============================================================
|
|
11882
|
-
// Retry Policy
|
|
11883
|
-
// ============================================================
|
|
11884
|
-
/**
|
|
11885
|
-
* Get the retry policy configuration.
|
|
11886
|
-
*/
|
|
11887
|
-
get retryPolicy() {
|
|
11888
|
-
return this._retryPolicy;
|
|
11889
|
-
}
|
|
11890
|
-
wrapInvoke(invoke) {
|
|
11891
|
-
return (session, service, path, action, facts) => {
|
|
11892
|
-
this.assertActive();
|
|
11893
|
-
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
11894
|
-
return invoke(session, service, path, action, facts);
|
|
11895
|
-
}
|
|
11896
|
-
const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
|
|
11897
|
-
service,
|
|
11898
|
-
path,
|
|
11899
|
-
action,
|
|
11900
|
-
facts
|
|
11901
|
-
});
|
|
11902
|
-
try {
|
|
11903
|
-
const result = invoke(session, service, path, action, facts);
|
|
11904
|
-
timer.stop({ ok: true, service, path, action });
|
|
11905
|
-
return result;
|
|
11906
|
-
} catch (error) {
|
|
11907
|
-
timer.stop({ ok: false, service, path, action, error });
|
|
11908
|
-
throw error;
|
|
11909
|
-
}
|
|
11910
|
-
};
|
|
11911
|
-
}
|
|
11912
|
-
assertActive() {
|
|
11913
|
-
if (this._retired) {
|
|
11914
|
-
throw new Error("Service graph has been retired: its session was replaced or signed out.");
|
|
11915
|
-
}
|
|
11916
|
-
}
|
|
11917
|
-
wrapInvokeAny(invokeAny) {
|
|
11918
|
-
return (session, entries, facts) => {
|
|
11919
|
-
this.assertActive();
|
|
11920
|
-
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
11921
|
-
return invokeAny(session, entries, facts);
|
|
11922
|
-
}
|
|
11923
|
-
const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
|
|
11924
|
-
entries,
|
|
11925
|
-
facts
|
|
11926
|
-
});
|
|
11927
|
-
try {
|
|
11928
|
-
const result = invokeAny(session, entries, facts);
|
|
11929
|
-
timer.stop({ ok: true, entries });
|
|
11930
|
-
return result;
|
|
11931
|
-
} catch (error) {
|
|
11932
|
-
timer.stop({ ok: false, entries, error });
|
|
11933
|
-
throw error;
|
|
11934
|
-
}
|
|
11935
|
-
};
|
|
11936
|
-
}
|
|
11937
|
-
wrapFetch(fetchFn) {
|
|
11938
|
-
return async (url, init) => {
|
|
11939
|
-
this.assertActive();
|
|
11940
|
-
const request = {
|
|
11941
|
-
...init,
|
|
11942
|
-
signal: this.combineSignals(init?.signal)
|
|
11943
|
-
};
|
|
11944
|
-
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
11945
|
-
return fetchFn(url, request);
|
|
11946
|
-
}
|
|
11947
|
-
const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
|
|
11948
|
-
url,
|
|
11949
|
-
method: request.method ?? "GET",
|
|
11950
|
-
init: request
|
|
11951
|
-
});
|
|
11952
|
-
try {
|
|
11953
|
-
const response = await fetchFn(url, request);
|
|
11954
|
-
timer.stop({
|
|
11955
|
-
ok: response.ok,
|
|
11956
|
-
url,
|
|
11957
|
-
method: request.method ?? "GET",
|
|
11958
|
-
status: response.status,
|
|
11959
|
-
statusText: response.statusText
|
|
11960
|
-
});
|
|
11961
|
-
return response;
|
|
11962
|
-
} catch (error) {
|
|
11963
|
-
timer.stop({
|
|
11964
|
-
ok: false,
|
|
11965
|
-
url,
|
|
11966
|
-
method: request.method ?? "GET",
|
|
11967
|
-
error
|
|
11968
|
-
});
|
|
11969
|
-
throw error;
|
|
11970
|
-
}
|
|
11971
|
-
};
|
|
11972
|
-
}
|
|
11973
|
-
combineSignals(signal) {
|
|
11974
|
-
if (!signal) return this._abortController.signal;
|
|
11975
|
-
const combined = new AbortController();
|
|
11976
|
-
const abort = (source) => combined.abort(source.reason);
|
|
11977
|
-
if (signal.aborted) {
|
|
11978
|
-
abort(signal);
|
|
11979
|
-
} else if (this._abortController.signal.aborted) {
|
|
11980
|
-
abort(this._abortController.signal);
|
|
11981
|
-
} else {
|
|
11982
|
-
signal.addEventListener("abort", () => abort(signal), { once: true });
|
|
11983
|
-
this._abortController.signal.addEventListener(
|
|
11984
|
-
"abort",
|
|
11985
|
-
() => abort(this._abortController.signal),
|
|
11986
|
-
{ once: true }
|
|
11987
|
-
);
|
|
11988
|
-
}
|
|
11989
|
-
return combined.signal;
|
|
11990
|
-
}
|
|
11991
|
-
};
|
|
11992
11657
|
function parseServiceErrorBody(errorText) {
|
|
11993
11658
|
try {
|
|
11994
11659
|
return JSON.parse(errorText);
|
|
@@ -12139,7 +11804,7 @@ function nodeRetryError(service, message, response, errorText) {
|
|
|
12139
11804
|
const body = errorText.trim();
|
|
12140
11805
|
const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
|
|
12141
11806
|
let code;
|
|
12142
|
-
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
|
|
11807
|
+
if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable" || token === "read_audit_busy")) {
|
|
12143
11808
|
code = ErrorCodes2.NODE_BUSY;
|
|
12144
11809
|
} else if (response.status === 503 && token === "request_timeout") {
|
|
12145
11810
|
code = ErrorCodes2.REQUEST_TIMEOUT;
|
|
@@ -12343,50 +12008,432 @@ function parseStorageRejection(responseText) {
|
|
|
12343
12008
|
} catch {
|
|
12344
12009
|
return parseStorageQuotaBytes(responseText);
|
|
12345
12010
|
}
|
|
12346
|
-
if (typeof body !== "object" || body === null) return void 0;
|
|
12347
|
-
const record = body;
|
|
12348
|
-
const message = typeof record.message === "string" ? record.message : "";
|
|
12349
|
-
const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
|
|
12350
|
-
const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
|
|
12351
|
-
if (!space && !isStorageBody) {
|
|
12352
|
-
return void 0;
|
|
12011
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
12012
|
+
const record = body;
|
|
12013
|
+
const message = typeof record.message === "string" ? record.message : "";
|
|
12014
|
+
const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
|
|
12015
|
+
const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
|
|
12016
|
+
if (!space && !isStorageBody) {
|
|
12017
|
+
return void 0;
|
|
12018
|
+
}
|
|
12019
|
+
const details = { ...space };
|
|
12020
|
+
const account = byteCountsOf(record.account);
|
|
12021
|
+
if (account) {
|
|
12022
|
+
const plan = record.account.plan;
|
|
12023
|
+
details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
|
|
12024
|
+
}
|
|
12025
|
+
return details;
|
|
12026
|
+
}
|
|
12027
|
+
function storageRejectionError(service, status, meta, responseText) {
|
|
12028
|
+
const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
|
|
12029
|
+
return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
|
|
12030
|
+
}
|
|
12031
|
+
function wrapError(service, error, defaultCode = ErrorCodes2.NETWORK_ERROR) {
|
|
12032
|
+
if (error instanceof Error) {
|
|
12033
|
+
if (isRequestTimeoutError(error)) {
|
|
12034
|
+
return timeoutError(service, error.timeoutMs, error);
|
|
12035
|
+
}
|
|
12036
|
+
if (error.name === "AbortError") {
|
|
12037
|
+
return abortedError(service);
|
|
12038
|
+
}
|
|
12039
|
+
if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
|
|
12040
|
+
return timeoutError(service);
|
|
12041
|
+
}
|
|
12042
|
+
return {
|
|
12043
|
+
code: defaultCode,
|
|
12044
|
+
message: error.message,
|
|
12045
|
+
service,
|
|
12046
|
+
cause: error
|
|
12047
|
+
};
|
|
12048
|
+
}
|
|
12049
|
+
return {
|
|
12050
|
+
code: defaultCode,
|
|
12051
|
+
message: String(error),
|
|
12052
|
+
service
|
|
12053
|
+
};
|
|
12054
|
+
}
|
|
12055
|
+
var abortableSleep = (ms, signal, unref = false) => new Promise((resolve) => {
|
|
12056
|
+
const done = () => {
|
|
12057
|
+
clearTimeout(timer);
|
|
12058
|
+
signal.removeEventListener("abort", done);
|
|
12059
|
+
resolve();
|
|
12060
|
+
};
|
|
12061
|
+
const timer = setTimeout(done, ms);
|
|
12062
|
+
if (unref) timer.unref?.();
|
|
12063
|
+
signal.aborted ? done() : signal.addEventListener("abort", done);
|
|
12064
|
+
});
|
|
12065
|
+
var ServiceContext = class {
|
|
12066
|
+
constructor(config) {
|
|
12067
|
+
this._session = null;
|
|
12068
|
+
this._services = /* @__PURE__ */ new Map();
|
|
12069
|
+
this._eventHandlers = /* @__PURE__ */ new Map();
|
|
12070
|
+
this._abortController = new AbortController();
|
|
12071
|
+
this._retired = false;
|
|
12072
|
+
this._invoke = this.wrapInvoke(config.invoke);
|
|
12073
|
+
this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
|
|
12074
|
+
this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
|
|
12075
|
+
this._hosts = config.hosts;
|
|
12076
|
+
this._session = config.session ?? null;
|
|
12077
|
+
this._retryPolicy = {
|
|
12078
|
+
...defaultRetryPolicy2,
|
|
12079
|
+
...config.retryPolicy
|
|
12080
|
+
};
|
|
12081
|
+
this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
|
|
12082
|
+
this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
|
|
12083
|
+
}
|
|
12084
|
+
// ============================================================
|
|
12085
|
+
// Session Management
|
|
12086
|
+
// ============================================================
|
|
12087
|
+
/**
|
|
12088
|
+
* Get the current session.
|
|
12089
|
+
*/
|
|
12090
|
+
get session() {
|
|
12091
|
+
return this._retired ? null : this._session;
|
|
12092
|
+
}
|
|
12093
|
+
/**
|
|
12094
|
+
* Check if the context has an authenticated session.
|
|
12095
|
+
*/
|
|
12096
|
+
get isAuthenticated() {
|
|
12097
|
+
return !this._retired && this._session !== null;
|
|
12098
|
+
}
|
|
12099
|
+
/**
|
|
12100
|
+
* Update the session and notify all registered services.
|
|
12101
|
+
*
|
|
12102
|
+
* @param session - New session or null to clear
|
|
12103
|
+
*/
|
|
12104
|
+
setSession(session) {
|
|
12105
|
+
this.assertActive();
|
|
12106
|
+
this._session = session;
|
|
12107
|
+
this.emit("session.changed", { authenticated: session !== null });
|
|
12108
|
+
for (const service of this._services.values()) {
|
|
12109
|
+
service.onSessionChange(session);
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
// ============================================================
|
|
12113
|
+
// Platform Dependencies
|
|
12114
|
+
// ============================================================
|
|
12115
|
+
/**
|
|
12116
|
+
* Get the invoke function for WASM operations.
|
|
12117
|
+
*/
|
|
12118
|
+
get invoke() {
|
|
12119
|
+
this.assertActive();
|
|
12120
|
+
return this._invoke;
|
|
12121
|
+
}
|
|
12122
|
+
/**
|
|
12123
|
+
* Get the multi-resource invoke function when available.
|
|
12124
|
+
*/
|
|
12125
|
+
get invokeAny() {
|
|
12126
|
+
this.assertActive();
|
|
12127
|
+
return this._invokeAny;
|
|
12128
|
+
}
|
|
12129
|
+
/**
|
|
12130
|
+
* Get the fetch function for HTTP requests.
|
|
12131
|
+
*/
|
|
12132
|
+
get fetch() {
|
|
12133
|
+
this.assertActive();
|
|
12134
|
+
return this._fetch;
|
|
12135
|
+
}
|
|
12136
|
+
/**
|
|
12137
|
+
* Get the list of TinyCloud host URLs.
|
|
12138
|
+
*/
|
|
12139
|
+
get hosts() {
|
|
12140
|
+
this.assertActive();
|
|
12141
|
+
return this._hosts;
|
|
12142
|
+
}
|
|
12143
|
+
// ============================================================
|
|
12144
|
+
// Service Registry
|
|
12145
|
+
// ============================================================
|
|
12146
|
+
/**
|
|
12147
|
+
* Register a service with the context.
|
|
12148
|
+
*
|
|
12149
|
+
* @param name - Service name (e.g., 'kv')
|
|
12150
|
+
* @param service - Service instance
|
|
12151
|
+
*/
|
|
12152
|
+
registerService(name, service) {
|
|
12153
|
+
this._services.set(name, service);
|
|
12154
|
+
}
|
|
12155
|
+
/**
|
|
12156
|
+
* Unregister a service from the context.
|
|
12157
|
+
*
|
|
12158
|
+
* @param name - Service name to remove
|
|
12159
|
+
*/
|
|
12160
|
+
unregisterService(name) {
|
|
12161
|
+
this._services.delete(name);
|
|
12162
|
+
}
|
|
12163
|
+
/**
|
|
12164
|
+
* Get a registered service by name.
|
|
12165
|
+
*
|
|
12166
|
+
* @param name - Service name
|
|
12167
|
+
* @returns The service instance or undefined if not registered
|
|
12168
|
+
*/
|
|
12169
|
+
getService(name) {
|
|
12170
|
+
return this._services.get(name);
|
|
12171
|
+
}
|
|
12172
|
+
// ============================================================
|
|
12173
|
+
// Event System (Telemetry)
|
|
12174
|
+
// ============================================================
|
|
12175
|
+
/**
|
|
12176
|
+
* Emit a telemetry event.
|
|
12177
|
+
*
|
|
12178
|
+
* @param event - Event name
|
|
12179
|
+
* @param data - Event data
|
|
12180
|
+
*/
|
|
12181
|
+
emit(event, data) {
|
|
12182
|
+
tinyCloudDebugLogger.log(event, data);
|
|
12183
|
+
if (this._telemetryEnabled && this._telemetryHandler) {
|
|
12184
|
+
try {
|
|
12185
|
+
this._telemetryHandler(event, projectDiagnosticData(data));
|
|
12186
|
+
} catch (error) {
|
|
12187
|
+
console.error(`Error in telemetry handler for "${event}":`, error);
|
|
12188
|
+
}
|
|
12189
|
+
}
|
|
12190
|
+
const handlers = this._eventHandlers.get(event);
|
|
12191
|
+
if (handlers) {
|
|
12192
|
+
for (const handler of handlers) {
|
|
12193
|
+
try {
|
|
12194
|
+
handler(data);
|
|
12195
|
+
} catch (error) {
|
|
12196
|
+
console.error(`Error in event handler for "${event}":`, error);
|
|
12197
|
+
}
|
|
12198
|
+
}
|
|
12199
|
+
}
|
|
12200
|
+
}
|
|
12201
|
+
/**
|
|
12202
|
+
* Subscribe to telemetry events.
|
|
12203
|
+
*
|
|
12204
|
+
* @param event - Event name to subscribe to
|
|
12205
|
+
* @param handler - Handler function
|
|
12206
|
+
* @returns Unsubscribe function
|
|
12207
|
+
*/
|
|
12208
|
+
on(event, handler) {
|
|
12209
|
+
if (!this._eventHandlers.has(event)) {
|
|
12210
|
+
this._eventHandlers.set(event, /* @__PURE__ */ new Set());
|
|
12211
|
+
}
|
|
12212
|
+
this._eventHandlers.get(event).add(handler);
|
|
12213
|
+
return () => {
|
|
12214
|
+
const handlers = this._eventHandlers.get(event);
|
|
12215
|
+
if (handlers) {
|
|
12216
|
+
handlers.delete(handler);
|
|
12217
|
+
if (handlers.size === 0) {
|
|
12218
|
+
this._eventHandlers.delete(event);
|
|
12219
|
+
}
|
|
12220
|
+
}
|
|
12221
|
+
};
|
|
12222
|
+
}
|
|
12223
|
+
/**
|
|
12224
|
+
* Remove all event handlers for an event.
|
|
12225
|
+
*
|
|
12226
|
+
* @param event - Event name (if omitted, clears all events)
|
|
12227
|
+
*/
|
|
12228
|
+
clearEventHandlers(event) {
|
|
12229
|
+
if (event) {
|
|
12230
|
+
this._eventHandlers.delete(event);
|
|
12231
|
+
} else {
|
|
12232
|
+
this._eventHandlers.clear();
|
|
12233
|
+
}
|
|
12234
|
+
}
|
|
12235
|
+
// ============================================================
|
|
12236
|
+
// Lifecycle
|
|
12237
|
+
// ============================================================
|
|
12238
|
+
/**
|
|
12239
|
+
* Get the abort signal for cancelling operations.
|
|
12240
|
+
*/
|
|
12241
|
+
get abortSignal() {
|
|
12242
|
+
return this._abortController.signal;
|
|
12243
|
+
}
|
|
12244
|
+
get operationAbortSignal() {
|
|
12245
|
+
return this._operationAbortSignal;
|
|
12246
|
+
}
|
|
12247
|
+
setOperationAbortSignal(signal) {
|
|
12248
|
+
this._operationAbortSignal = signal;
|
|
12249
|
+
}
|
|
12250
|
+
/**
|
|
12251
|
+
* Permanently retire this graph after its owner installs a replacement.
|
|
12252
|
+
* Unlike `abort()`, retirement never creates a fresh controller, so captured
|
|
12253
|
+
* services cannot resume calls under a superseded session.
|
|
12254
|
+
*/
|
|
12255
|
+
retire() {
|
|
12256
|
+
if (this._retired) return;
|
|
12257
|
+
this._retired = true;
|
|
12258
|
+
this._session = null;
|
|
12259
|
+
this._abortController.abort();
|
|
12260
|
+
for (const service of this._services.values()) {
|
|
12261
|
+
try {
|
|
12262
|
+
service.onSignOut();
|
|
12263
|
+
} catch (error) {
|
|
12264
|
+
console.error("Error retiring service:", error);
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
12267
|
+
}
|
|
12268
|
+
/**
|
|
12269
|
+
* Abort all pending operations and notify services.
|
|
12270
|
+
* Creates a new AbortController for future operations.
|
|
12271
|
+
*/
|
|
12272
|
+
abort() {
|
|
12273
|
+
if (this._retired) return;
|
|
12274
|
+
this._abortController.abort();
|
|
12275
|
+
this._abortController = new AbortController();
|
|
12276
|
+
for (const service of this._services.values()) {
|
|
12277
|
+
try {
|
|
12278
|
+
service.onSignOut();
|
|
12279
|
+
} catch (error) {
|
|
12280
|
+
console.error("Error signing out service:", error);
|
|
12281
|
+
}
|
|
12282
|
+
}
|
|
12283
|
+
}
|
|
12284
|
+
/**
|
|
12285
|
+
* Sign out - abort operations and clear session.
|
|
12286
|
+
*/
|
|
12287
|
+
signOut() {
|
|
12288
|
+
if (this._retired) return;
|
|
12289
|
+
this.abort();
|
|
12290
|
+
this.setSession(null);
|
|
12291
|
+
this.emit("session.expired", {});
|
|
12353
12292
|
}
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12293
|
+
// ============================================================
|
|
12294
|
+
// Retry Policy
|
|
12295
|
+
// ============================================================
|
|
12296
|
+
/**
|
|
12297
|
+
* Get the retry policy configuration.
|
|
12298
|
+
*/
|
|
12299
|
+
get retryPolicy() {
|
|
12300
|
+
return this._retryPolicy;
|
|
12359
12301
|
}
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
}
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12302
|
+
wrapInvoke(invoke) {
|
|
12303
|
+
return (session, service, path, action, facts) => {
|
|
12304
|
+
this.assertActive();
|
|
12305
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
12306
|
+
return invoke(session, service, path, action, facts);
|
|
12307
|
+
}
|
|
12308
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
|
|
12309
|
+
service,
|
|
12310
|
+
path,
|
|
12311
|
+
action,
|
|
12312
|
+
facts
|
|
12313
|
+
});
|
|
12314
|
+
try {
|
|
12315
|
+
const result = invoke(session, service, path, action, facts);
|
|
12316
|
+
timer.stop({ ok: true, service, path, action });
|
|
12317
|
+
return result;
|
|
12318
|
+
} catch (error) {
|
|
12319
|
+
timer.stop({ ok: false, service, path, action, error });
|
|
12320
|
+
throw error;
|
|
12321
|
+
}
|
|
12322
|
+
};
|
|
12323
|
+
}
|
|
12324
|
+
assertActive() {
|
|
12325
|
+
if (this._retired) {
|
|
12326
|
+
throw new Error("Service graph has been retired: its session was replaced or signed out.");
|
|
12373
12327
|
}
|
|
12374
|
-
|
|
12375
|
-
|
|
12328
|
+
}
|
|
12329
|
+
wrapInvokeAny(invokeAny) {
|
|
12330
|
+
return (session, entries, facts) => {
|
|
12331
|
+
this.assertActive();
|
|
12332
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
12333
|
+
return invokeAny(session, entries, facts);
|
|
12334
|
+
}
|
|
12335
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
|
|
12336
|
+
entries,
|
|
12337
|
+
facts
|
|
12338
|
+
});
|
|
12339
|
+
try {
|
|
12340
|
+
const result = invokeAny(session, entries, facts);
|
|
12341
|
+
timer.stop({ ok: true, entries });
|
|
12342
|
+
return result;
|
|
12343
|
+
} catch (error) {
|
|
12344
|
+
timer.stop({ ok: false, entries, error });
|
|
12345
|
+
throw error;
|
|
12346
|
+
}
|
|
12347
|
+
};
|
|
12348
|
+
}
|
|
12349
|
+
/** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */
|
|
12350
|
+
async invokeWithRetry(url, build) {
|
|
12351
|
+
this.assertActive();
|
|
12352
|
+
const policy = this._retryPolicy;
|
|
12353
|
+
const startedAt = Date.now();
|
|
12354
|
+
let backoffMs = 0;
|
|
12355
|
+
let signal;
|
|
12356
|
+
for (let attempt = 1; ; attempt++) {
|
|
12357
|
+
const init = await build();
|
|
12358
|
+
signal ?? (signal = this.combineSignals(init.signal));
|
|
12359
|
+
const response = await this._fetch(url, init);
|
|
12360
|
+
const done = () => {
|
|
12361
|
+
try {
|
|
12362
|
+
response.retry = { attempts: attempt, backoffMs };
|
|
12363
|
+
} catch {
|
|
12364
|
+
}
|
|
12365
|
+
return response;
|
|
12366
|
+
};
|
|
12367
|
+
if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();
|
|
12368
|
+
const text = await response.clone().text().catch(() => "");
|
|
12369
|
+
const error = nodeRetryError("", "", response, text);
|
|
12370
|
+
if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {
|
|
12371
|
+
return done();
|
|
12372
|
+
}
|
|
12373
|
+
const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);
|
|
12374
|
+
if (remaining <= 0) return done();
|
|
12375
|
+
const retryAfterMs = error.meta.retryAfterMs;
|
|
12376
|
+
const step = Math.min(
|
|
12377
|
+
policy.maxDelayMs,
|
|
12378
|
+
policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** (attempt - 1) : policy.backoff === "linear" ? policy.baseDelayMs * attempt : 0
|
|
12379
|
+
);
|
|
12380
|
+
const delay = Math.min(
|
|
12381
|
+
typeof retryAfterMs === "number" ? retryAfterMs : Math.random() * step,
|
|
12382
|
+
remaining
|
|
12383
|
+
);
|
|
12384
|
+
response.body?.cancel?.()?.catch?.(() => {
|
|
12385
|
+
});
|
|
12386
|
+
await abortableSleep(delay, signal);
|
|
12387
|
+
if (signal.aborted) throw signal.reason;
|
|
12388
|
+
backoffMs += delay;
|
|
12376
12389
|
}
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12390
|
+
}
|
|
12391
|
+
wrapFetch(fetchFn) {
|
|
12392
|
+
return async (url, init) => {
|
|
12393
|
+
this.assertActive();
|
|
12394
|
+
const request = {
|
|
12395
|
+
...init,
|
|
12396
|
+
signal: this.combineSignals(init?.signal)
|
|
12397
|
+
};
|
|
12398
|
+
if (!tinyCloudDebugLogger.isEnabled()) {
|
|
12399
|
+
return fetchFn(url, request);
|
|
12400
|
+
}
|
|
12401
|
+
const meta = { url, method: request.method ?? "GET" };
|
|
12402
|
+
const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", { ...meta, init: request });
|
|
12403
|
+
try {
|
|
12404
|
+
const response = await fetchFn(url, request);
|
|
12405
|
+
timer.stop({
|
|
12406
|
+
...meta,
|
|
12407
|
+
ok: response.ok,
|
|
12408
|
+
status: response.status,
|
|
12409
|
+
statusText: response.statusText
|
|
12410
|
+
});
|
|
12411
|
+
return response;
|
|
12412
|
+
} catch (error) {
|
|
12413
|
+
timer.stop({ ...meta, ok: false, error });
|
|
12414
|
+
throw error;
|
|
12415
|
+
}
|
|
12382
12416
|
};
|
|
12383
12417
|
}
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12418
|
+
combineSignals(signal) {
|
|
12419
|
+
if (!signal) return this._abortController.signal;
|
|
12420
|
+
const combined = new AbortController();
|
|
12421
|
+
const abort = (source) => combined.abort(source.reason);
|
|
12422
|
+
if (signal.aborted) {
|
|
12423
|
+
abort(signal);
|
|
12424
|
+
} else if (this._abortController.signal.aborted) {
|
|
12425
|
+
abort(this._abortController.signal);
|
|
12426
|
+
} else {
|
|
12427
|
+
signal.addEventListener("abort", () => abort(signal), { once: true });
|
|
12428
|
+
this._abortController.signal.addEventListener(
|
|
12429
|
+
"abort",
|
|
12430
|
+
() => abort(this._abortController.signal),
|
|
12431
|
+
{ once: true }
|
|
12432
|
+
);
|
|
12433
|
+
}
|
|
12434
|
+
return combined.signal;
|
|
12435
|
+
}
|
|
12436
|
+
};
|
|
12390
12437
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
12391
12438
|
var BaseService = class {
|
|
12392
12439
|
constructor() {
|
|
@@ -12561,6 +12608,18 @@ var BaseService = class {
|
|
|
12561
12608
|
}
|
|
12562
12609
|
return controller.signal;
|
|
12563
12610
|
}
|
|
12611
|
+
/** POST a signed invocation with retry; `mint` re-signs per attempt, `init.headers` add to the signed ones. */
|
|
12612
|
+
invokeWithRetry(url, mint, { headers: extra, ...init } = {}) {
|
|
12613
|
+
const build = () => {
|
|
12614
|
+
const signed = mint();
|
|
12615
|
+
return {
|
|
12616
|
+
method: "POST",
|
|
12617
|
+
...init,
|
|
12618
|
+
headers: Array.isArray(signed) ? [...signed, ...Object.entries(extra ?? {})] : { ...signed, ...extra }
|
|
12619
|
+
};
|
|
12620
|
+
};
|
|
12621
|
+
return this.context.invokeWithRetry ? this.context.invokeWithRetry(url, build) : this.context.fetch(url, build());
|
|
12622
|
+
}
|
|
12564
12623
|
/**
|
|
12565
12624
|
* Create the abort signal for one request, bounded by the request timeout.
|
|
12566
12625
|
*
|
|
@@ -12922,15 +12981,6 @@ var KVService = class extends BaseService {
|
|
|
12922
12981
|
get host() {
|
|
12923
12982
|
return this.context.hosts[0];
|
|
12924
12983
|
}
|
|
12925
|
-
withJsonContentType(headers) {
|
|
12926
|
-
if (Array.isArray(headers)) {
|
|
12927
|
-
return [...headers, ["content-type", "application/json"]];
|
|
12928
|
-
}
|
|
12929
|
-
return {
|
|
12930
|
-
...headers,
|
|
12931
|
-
"content-type": "application/json"
|
|
12932
|
-
};
|
|
12933
|
-
}
|
|
12934
12984
|
/**
|
|
12935
12985
|
* Execute an invoke operation.
|
|
12936
12986
|
*
|
|
@@ -12941,20 +12991,11 @@ var KVService = class extends BaseService {
|
|
|
12941
12991
|
* @returns Fetch response
|
|
12942
12992
|
*/
|
|
12943
12993
|
async invokeOperation(path, action, body, signal, extraHeaders) {
|
|
12944
|
-
|
|
12945
|
-
|
|
12946
|
-
session,
|
|
12947
|
-
|
|
12948
|
-
path,
|
|
12949
|
-
action
|
|
12994
|
+
return this.invokeWithRetry(
|
|
12995
|
+
`${this.host}/invoke`,
|
|
12996
|
+
() => this.context.invoke(this.context.session, "kv", path, action),
|
|
12997
|
+
{ headers: extraHeaders, body, signal }
|
|
12950
12998
|
);
|
|
12951
|
-
const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
|
|
12952
|
-
return this.context.fetch(`${this.host}/invoke`, {
|
|
12953
|
-
method: "POST",
|
|
12954
|
-
headers: requestHeaders,
|
|
12955
|
-
body,
|
|
12956
|
-
signal
|
|
12957
|
-
});
|
|
12958
12999
|
}
|
|
12959
13000
|
/**
|
|
12960
13001
|
* Serialize a single put value into a fetch body.
|
|
@@ -13160,28 +13201,28 @@ var KVService = class extends BaseService {
|
|
|
13160
13201
|
}
|
|
13161
13202
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
13162
13203
|
try {
|
|
13163
|
-
const session = this.context.session;
|
|
13164
|
-
const invocationHeaders = this.context.invokeAny(
|
|
13165
|
-
session,
|
|
13166
|
-
paths.map((path) => ({
|
|
13167
|
-
spaceId: session.spaceId,
|
|
13168
|
-
service: "kv",
|
|
13169
|
-
path,
|
|
13170
|
-
action
|
|
13171
|
-
}))
|
|
13172
|
-
);
|
|
13173
13204
|
const limitHeaders = {};
|
|
13174
13205
|
if (getOptions?.maxResponseBytes !== void 0) {
|
|
13175
13206
|
limitHeaders["x-tinycloud-max-response-bytes"] = String(
|
|
13176
13207
|
getOptions.maxResponseBytes
|
|
13177
13208
|
);
|
|
13178
13209
|
}
|
|
13179
|
-
const
|
|
13180
|
-
|
|
13181
|
-
|
|
13182
|
-
|
|
13183
|
-
|
|
13184
|
-
|
|
13210
|
+
const response = await this.invokeWithRetry(
|
|
13211
|
+
`${this.host}/invoke`,
|
|
13212
|
+
() => {
|
|
13213
|
+
const session = this.context.session;
|
|
13214
|
+
return this.context.invokeAny(
|
|
13215
|
+
session,
|
|
13216
|
+
paths.map((path) => ({
|
|
13217
|
+
spaceId: session.spaceId,
|
|
13218
|
+
service: "kv",
|
|
13219
|
+
path,
|
|
13220
|
+
action
|
|
13221
|
+
}))
|
|
13222
|
+
);
|
|
13223
|
+
},
|
|
13224
|
+
{ headers: limitHeaders, signal: request.signal }
|
|
13225
|
+
);
|
|
13185
13226
|
if (!response.ok) {
|
|
13186
13227
|
const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
|
|
13187
13228
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -13227,10 +13268,10 @@ var KVService = class extends BaseService {
|
|
|
13227
13268
|
result: err(serviceError(item.error.code, item.error.message, "kv"))
|
|
13228
13269
|
};
|
|
13229
13270
|
}
|
|
13230
|
-
const
|
|
13271
|
+
const headers = item.headers;
|
|
13231
13272
|
const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
|
|
13232
13273
|
item.dataBase64,
|
|
13233
|
-
|
|
13274
|
+
headers,
|
|
13234
13275
|
getOptions?.raw,
|
|
13235
13276
|
getOptions?.binary
|
|
13236
13277
|
);
|
|
@@ -13238,7 +13279,7 @@ var KVService = class extends BaseService {
|
|
|
13238
13279
|
key: keys[index],
|
|
13239
13280
|
result: ok({
|
|
13240
13281
|
data,
|
|
13241
|
-
headers: this.createBatchResponseHeaders(
|
|
13282
|
+
headers: this.createBatchResponseHeaders(headers)
|
|
13242
13283
|
})
|
|
13243
13284
|
};
|
|
13244
13285
|
});
|
|
@@ -13473,7 +13514,7 @@ var KVService = class extends BaseService {
|
|
|
13473
13514
|
));
|
|
13474
13515
|
}
|
|
13475
13516
|
const data = await this.parseResponse(response, options?.raw, options?.binary);
|
|
13476
|
-
return ok({ data, headers: this.createResponseHeaders(response.headers) });
|
|
13517
|
+
return ok({ data, headers: this.createResponseHeaders(response.headers), ...response.retry && { retry: response.retry } });
|
|
13477
13518
|
}
|
|
13478
13519
|
async batchGet(keys, options) {
|
|
13479
13520
|
return this.withTelemetry(
|
|
@@ -13563,7 +13604,8 @@ var KVService = class extends BaseService {
|
|
|
13563
13604
|
}
|
|
13564
13605
|
return ok({
|
|
13565
13606
|
data: void 0,
|
|
13566
|
-
headers: this.createResponseHeaders(response.headers)
|
|
13607
|
+
headers: this.createResponseHeaders(response.headers),
|
|
13608
|
+
...response.retry && { retry: response.retry }
|
|
13567
13609
|
});
|
|
13568
13610
|
}
|
|
13569
13611
|
/**
|
|
@@ -13636,25 +13678,23 @@ var KVService = class extends BaseService {
|
|
|
13636
13678
|
entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
|
|
13637
13679
|
);
|
|
13638
13680
|
}
|
|
13639
|
-
const
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13681
|
+
const response = await this.invokeWithRetry(
|
|
13682
|
+
`${this.host}/invoke`,
|
|
13683
|
+
() => {
|
|
13684
|
+
const headers = this.context.invokeAny(
|
|
13685
|
+
session,
|
|
13686
|
+
paths.map((path) => ({
|
|
13687
|
+
spaceId: session.spaceId,
|
|
13688
|
+
service: "kv",
|
|
13689
|
+
path,
|
|
13690
|
+
action: KVAction.PUT
|
|
13691
|
+
}))
|
|
13692
|
+
);
|
|
13693
|
+
requestMayHaveDispatched = true;
|
|
13694
|
+
return headers;
|
|
13695
|
+
},
|
|
13696
|
+
{ body, signal }
|
|
13647
13697
|
);
|
|
13648
|
-
const url = `${this.host}/invoke`;
|
|
13649
|
-
const init = {
|
|
13650
|
-
method: "POST",
|
|
13651
|
-
headers,
|
|
13652
|
-
body,
|
|
13653
|
-
signal
|
|
13654
|
-
};
|
|
13655
|
-
const fetchFn = this.context.fetch;
|
|
13656
|
-
requestMayHaveDispatched = true;
|
|
13657
|
-
const response = await fetchFn(url, init);
|
|
13658
13698
|
if (!response.ok) {
|
|
13659
13699
|
let errorText;
|
|
13660
13700
|
try {
|
|
@@ -14124,7 +14164,8 @@ var KVService = class extends BaseService {
|
|
|
14124
14164
|
}
|
|
14125
14165
|
return ok({
|
|
14126
14166
|
data: void 0,
|
|
14127
|
-
headers: this.createResponseHeaders(response.headers)
|
|
14167
|
+
headers: this.createResponseHeaders(response.headers),
|
|
14168
|
+
...response.retry && { retry: response.retry }
|
|
14128
14169
|
});
|
|
14129
14170
|
}
|
|
14130
14171
|
/**
|
|
@@ -14198,12 +14239,6 @@ var KVService = class extends BaseService {
|
|
|
14198
14239
|
}
|
|
14199
14240
|
const path = this.getFullPath(key, options?.prefix);
|
|
14200
14241
|
const session = this.context.session;
|
|
14201
|
-
const headers = this.context.invoke(
|
|
14202
|
-
session,
|
|
14203
|
-
"kv",
|
|
14204
|
-
path,
|
|
14205
|
-
KVAction.GET
|
|
14206
|
-
);
|
|
14207
14242
|
const body = {
|
|
14208
14243
|
space: session.spaceId,
|
|
14209
14244
|
path,
|
|
@@ -14217,12 +14252,15 @@ var KVService = class extends BaseService {
|
|
|
14217
14252
|
}
|
|
14218
14253
|
const request = this.createRequestSignal(options?.signal, options?.timeout);
|
|
14219
14254
|
try {
|
|
14220
|
-
const response = await this.
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14255
|
+
const response = await this.invokeWithRetry(
|
|
14256
|
+
`${this.host}/signed/kv`,
|
|
14257
|
+
() => this.context.invoke(session, "kv", path, KVAction.GET),
|
|
14258
|
+
{
|
|
14259
|
+
headers: { "content-type": "application/json" },
|
|
14260
|
+
body: JSON.stringify(body),
|
|
14261
|
+
signal: request.signal
|
|
14262
|
+
}
|
|
14263
|
+
);
|
|
14226
14264
|
if (!response.ok) {
|
|
14227
14265
|
return await this.createSignedReadUrlError(response, key, path);
|
|
14228
14266
|
}
|
|
@@ -14648,18 +14686,19 @@ var SQLService = class extends BaseService {
|
|
|
14648
14686
|
}
|
|
14649
14687
|
// === Private helpers ===
|
|
14650
14688
|
async invokeSQL(dbName, actions, body, signal) {
|
|
14651
|
-
const session = this.context.session;
|
|
14652
14689
|
const actionList = Array.isArray(actions) ? actions : [actions];
|
|
14653
|
-
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
"Content-Type": "application/json"
|
|
14690
|
+
return this.invokeWithRetry(
|
|
14691
|
+
`${this.host}/invoke`,
|
|
14692
|
+
() => {
|
|
14693
|
+
const session = this.context.session;
|
|
14694
|
+
return actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
|
|
14659
14695
|
},
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14696
|
+
{
|
|
14697
|
+
headers: { "Content-Type": "application/json" },
|
|
14698
|
+
body: JSON.stringify(body),
|
|
14699
|
+
signal
|
|
14700
|
+
}
|
|
14701
|
+
);
|
|
14663
14702
|
}
|
|
14664
14703
|
actionForSql(sql, fallback) {
|
|
14665
14704
|
const token = firstSqlToken(sql);
|
|
@@ -15113,22 +15152,20 @@ var DuckDbService = class extends BaseService {
|
|
|
15113
15152
|
}
|
|
15114
15153
|
const request = this.createRequestSignal(options?.signal);
|
|
15115
15154
|
try {
|
|
15116
|
-
const
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15155
|
+
const response = await this.invokeWithRetry(
|
|
15156
|
+
`${this.host}/invoke`,
|
|
15157
|
+
() => this.context.invoke(
|
|
15158
|
+
this.context.session,
|
|
15159
|
+
"duckdb",
|
|
15160
|
+
dbName,
|
|
15161
|
+
DuckDbAction.IMPORT
|
|
15162
|
+
),
|
|
15163
|
+
{
|
|
15164
|
+
headers: { "Content-Type": "application/x-duckdb" },
|
|
15165
|
+
body: new Blob([data]),
|
|
15166
|
+
signal: request.signal
|
|
15167
|
+
}
|
|
15122
15168
|
);
|
|
15123
|
-
const response = await this.context.fetch(`${this.host}/invoke`, {
|
|
15124
|
-
method: "POST",
|
|
15125
|
-
headers: {
|
|
15126
|
-
...headers,
|
|
15127
|
-
"Content-Type": "application/x-duckdb"
|
|
15128
|
-
},
|
|
15129
|
-
body: new Blob([data]),
|
|
15130
|
-
signal: request.signal
|
|
15131
|
-
});
|
|
15132
15169
|
if (!response.ok) {
|
|
15133
15170
|
return await this.handleErrorResponse(response, "import");
|
|
15134
15171
|
}
|
|
@@ -15142,18 +15179,15 @@ var DuckDbService = class extends BaseService {
|
|
|
15142
15179
|
}
|
|
15143
15180
|
// === Private helpers ===
|
|
15144
15181
|
async invokeDuckDb(dbName, action, body, signal, extraHeaders) {
|
|
15145
|
-
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
|
|
15149
|
-
|
|
15150
|
-
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
body: JSON.stringify(body),
|
|
15155
|
-
signal
|
|
15156
|
-
});
|
|
15182
|
+
return this.invokeWithRetry(
|
|
15183
|
+
`${this.host}/invoke`,
|
|
15184
|
+
() => this.context.invoke(this.context.session, "duckdb", dbName, action),
|
|
15185
|
+
{
|
|
15186
|
+
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
15187
|
+
body: JSON.stringify(body),
|
|
15188
|
+
signal
|
|
15189
|
+
}
|
|
15190
|
+
);
|
|
15157
15191
|
}
|
|
15158
15192
|
async handleErrorResponse(response, operation) {
|
|
15159
15193
|
const errorText = await response.text();
|
|
@@ -15338,26 +15372,24 @@ var HooksService = class extends BaseService {
|
|
|
15338
15372
|
);
|
|
15339
15373
|
}
|
|
15340
15374
|
try {
|
|
15341
|
-
const response = await this.
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
})
|
|
15360
|
-
});
|
|
15375
|
+
const response = await this.invokeWithRetry(
|
|
15376
|
+
`${this.host}/hooks/webhooks`,
|
|
15377
|
+
() => this.createHookHeaders(
|
|
15378
|
+
"tinycloud.hooks/register",
|
|
15379
|
+
buildScopePath(webhook.service, webhook.pathPrefix)
|
|
15380
|
+
),
|
|
15381
|
+
{
|
|
15382
|
+
headers: { "content-type": "application/json" },
|
|
15383
|
+
body: JSON.stringify({
|
|
15384
|
+
space: webhook.space,
|
|
15385
|
+
service: webhook.service,
|
|
15386
|
+
pathPrefix: normalizePathPrefix(webhook.pathPrefix),
|
|
15387
|
+
abilities: webhook.abilities ?? [],
|
|
15388
|
+
callbackUrl: webhook.callbackUrl,
|
|
15389
|
+
secret: webhook.secret
|
|
15390
|
+
})
|
|
15391
|
+
}
|
|
15392
|
+
);
|
|
15361
15393
|
if (!response.ok) {
|
|
15362
15394
|
return err(
|
|
15363
15395
|
await responseError("hooks", "failed to register webhook", response)
|
|
@@ -15395,17 +15427,13 @@ var HooksService = class extends BaseService {
|
|
|
15395
15427
|
query.set("prefix", normalizedPrefix);
|
|
15396
15428
|
}
|
|
15397
15429
|
}
|
|
15398
|
-
const response = await this.
|
|
15430
|
+
const response = await this.invokeWithRetry(
|
|
15399
15431
|
`${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
|
|
15406
|
-
)
|
|
15407
|
-
)
|
|
15408
|
-
}
|
|
15432
|
+
() => this.createHookHeaders(
|
|
15433
|
+
"tinycloud.hooks/list",
|
|
15434
|
+
options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
|
|
15435
|
+
),
|
|
15436
|
+
{ method: "GET" }
|
|
15409
15437
|
);
|
|
15410
15438
|
if (!response.ok) {
|
|
15411
15439
|
return err(
|
|
@@ -15432,20 +15460,16 @@ var HooksService = class extends BaseService {
|
|
|
15432
15460
|
return err(authRequiredError("hooks"));
|
|
15433
15461
|
}
|
|
15434
15462
|
try {
|
|
15435
|
-
const response = await this.
|
|
15463
|
+
const response = await this.invokeWithRetry(
|
|
15436
15464
|
`${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
|
|
15437
|
-
|
|
15438
|
-
|
|
15439
|
-
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
) : `webhooks/${id}`
|
|
15446
|
-
)
|
|
15447
|
-
)
|
|
15448
|
-
}
|
|
15465
|
+
() => this.createHookHeaders(
|
|
15466
|
+
"tinycloud.hooks/unregister",
|
|
15467
|
+
options.target ? buildScopePath(
|
|
15468
|
+
options.target.service,
|
|
15469
|
+
options.target.pathPrefix
|
|
15470
|
+
) : `webhooks/${id}`
|
|
15471
|
+
),
|
|
15472
|
+
{ method: "DELETE" }
|
|
15449
15473
|
);
|
|
15450
15474
|
if (!response.ok) {
|
|
15451
15475
|
return err(
|
|
@@ -15901,23 +15925,7 @@ function readProperty(value, key) {
|
|
|
15901
15925
|
return void 0;
|
|
15902
15926
|
}
|
|
15903
15927
|
}
|
|
15904
|
-
|
|
15905
|
-
return new Promise((resolve) => {
|
|
15906
|
-
const finish = () => {
|
|
15907
|
-
clearTimeout(timer);
|
|
15908
|
-
signal.removeEventListener("abort", stop);
|
|
15909
|
-
resolve();
|
|
15910
|
-
};
|
|
15911
|
-
const timer = setTimeout(finish, delayMs);
|
|
15912
|
-
timer.unref?.();
|
|
15913
|
-
const stop = () => finish();
|
|
15914
|
-
if (signal.aborted) {
|
|
15915
|
-
finish();
|
|
15916
|
-
} else {
|
|
15917
|
-
signal.addEventListener("abort", stop, { once: true });
|
|
15918
|
-
}
|
|
15919
|
-
});
|
|
15920
|
-
}
|
|
15928
|
+
var sleep = (delayMs, _attempt, signal) => abortableSleep(delayMs, signal, true);
|
|
15921
15929
|
function abortPromise(signal) {
|
|
15922
15930
|
let resolve;
|
|
15923
15931
|
const promise = new Promise((done) => {
|