@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.
@@ -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: 3,
2858
+ maxAttempts: 4,
2859
2859
  backoff: "exponential",
2860
- baseDelayMs: 1e3,
2861
- maxDelayMs: 1e4,
2862
- retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
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: 3,
11214
+ maxAttempts: 4,
11214
11215
  backoff: "exponential",
11215
- baseDelayMs: 1e3,
11216
- maxDelayMs: 1e4,
11217
- retryableErrors: [ErrorCodes2.NETWORK_ERROR, ErrorCodes2.TIMEOUT]
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
- const details = { ...space };
12355
- const account = byteCountsOf(record.account);
12356
- if (account) {
12357
- const plan = record.account.plan;
12358
- details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
12293
+ // ============================================================
12294
+ // Retry Policy
12295
+ // ============================================================
12296
+ /**
12297
+ * Get the retry policy configuration.
12298
+ */
12299
+ get retryPolicy() {
12300
+ return this._retryPolicy;
12359
12301
  }
12360
- return details;
12361
- }
12362
- function storageRejectionError(service, status, meta, responseText) {
12363
- const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
12364
- return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
12365
- }
12366
- function wrapError(service, error, defaultCode = ErrorCodes2.NETWORK_ERROR) {
12367
- if (error instanceof Error) {
12368
- if (isRequestTimeoutError(error)) {
12369
- return timeoutError(service, error.timeoutMs, error);
12370
- }
12371
- if (error.name === "AbortError") {
12372
- return abortedError(service);
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
- if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
12375
- return timeoutError(service);
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
- return {
12378
- code: defaultCode,
12379
- message: error.message,
12380
- service,
12381
- cause: error
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
- return {
12385
- code: defaultCode,
12386
- message: String(error),
12387
- service
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
- const session = this.context.session;
12945
- const headers = this.context.invoke(
12946
- session,
12947
- "kv",
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 headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
13180
- const response = await this.context.fetch(`${this.host}/invoke`, {
13181
- method: "POST",
13182
- headers,
13183
- signal: request.signal
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 headers2 = item.headers;
13271
+ const headers = item.headers;
13231
13272
  const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
13232
13273
  item.dataBase64,
13233
- headers2,
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(headers2)
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 headers = this.context.invokeAny(
13640
- session,
13641
- paths.map((path) => ({
13642
- spaceId: session.spaceId,
13643
- service: "kv",
13644
- path,
13645
- action: KVAction.PUT
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.context.fetch(`${this.host}/signed/kv`, {
14221
- method: "POST",
14222
- headers: this.withJsonContentType(headers),
14223
- body: JSON.stringify(body),
14224
- signal: request.signal
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
- const headers = actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
14654
- return this.context.fetch(`${this.host}/invoke`, {
14655
- method: "POST",
14656
- headers: {
14657
- ...headers,
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
- body: JSON.stringify(body),
14661
- signal
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 session = this.context.session;
15117
- const headers = this.context.invoke(
15118
- session,
15119
- "duckdb",
15120
- dbName,
15121
- DuckDbAction.IMPORT
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
- const session = this.context.session;
15146
- const headers = this.context.invoke(session, "duckdb", dbName, action);
15147
- return this.context.fetch(`${this.host}/invoke`, {
15148
- method: "POST",
15149
- headers: {
15150
- ...headers,
15151
- "Content-Type": "application/json",
15152
- ...extraHeaders
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.context.fetch(`${this.host}/hooks/webhooks`, {
15342
- method: "POST",
15343
- headers: {
15344
- ...serviceHeadersToRecord(
15345
- this.createHookHeaders(
15346
- "tinycloud.hooks/register",
15347
- buildScopePath(webhook.service, webhook.pathPrefix)
15348
- )
15349
- ),
15350
- "content-type": "application/json"
15351
- },
15352
- body: JSON.stringify({
15353
- space: webhook.space,
15354
- service: webhook.service,
15355
- pathPrefix: normalizePathPrefix(webhook.pathPrefix),
15356
- abilities: webhook.abilities ?? [],
15357
- callbackUrl: webhook.callbackUrl,
15358
- secret: webhook.secret
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.context.fetch(
15430
+ const response = await this.invokeWithRetry(
15399
15431
  `${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
15400
- {
15401
- method: "GET",
15402
- headers: serviceHeadersToRecord(
15403
- this.createHookHeaders(
15404
- "tinycloud.hooks/list",
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.context.fetch(
15463
+ const response = await this.invokeWithRetry(
15436
15464
  `${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
15437
- {
15438
- method: "DELETE",
15439
- headers: serviceHeadersToRecord(
15440
- this.createHookHeaders(
15441
- "tinycloud.hooks/unregister",
15442
- options.target ? buildScopePath(
15443
- options.target.service,
15444
- options.target.pathPrefix
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
- function sleep(delayMs, _attempt, signal) {
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) => {