@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.
@@ -6808,7 +6808,7 @@ var ErrorCodes = {
6808
6808
  PERMISSION_DENIED: "PERMISSION_DENIED",
6809
6809
  // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
6810
6810
  // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
6811
- /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`: nothing was applied; retry after `meta.retryAfterMs`. */
6811
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
6812
6812
  NODE_BUSY: "NODE_BUSY",
6813
6813
  /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
6814
6814
  REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
@@ -6849,11 +6849,12 @@ var ErrorCodes = {
6849
6849
  DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
6850
6850
  };
6851
6851
  var defaultRetryPolicy = {
6852
- maxAttempts: 3,
6852
+ maxAttempts: 4,
6853
6853
  backoff: "exponential",
6854
- baseDelayMs: 1e3,
6855
- maxDelayMs: 1e4,
6856
- retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
6854
+ baseDelayMs: 250,
6855
+ maxDelayMs: 4e3,
6856
+ retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],
6857
+ maxTotalMs: 12e3
6857
6858
  };
6858
6859
  var TelemetryEvents = {
6859
6860
  SPAN: "telemetry.span",
@@ -7273,7 +7274,9 @@ var RetryPolicySchema = external_exports.object({
7273
7274
  /** Maximum delay in milliseconds between retries */
7274
7275
  maxDelayMs: external_exports.number().nonnegative(),
7275
7276
  /** Error codes that should trigger a retry */
7276
- retryableErrors: external_exports.array(external_exports.string())
7277
+ retryableErrors: external_exports.array(external_exports.string()),
7278
+ /** Total time budget for all attempts and waits, in milliseconds */
7279
+ maxTotalMs: external_exports.number().positive().optional()
7277
7280
  });
7278
7281
  var ServiceSessionSchema = external_exports.object({
7279
7282
  /** The delegation header containing the UCAN */
@@ -7289,345 +7292,6 @@ var ServiceSessionSchema = external_exports.object({
7289
7292
  /** The session key JWK (required for invoke) */
7290
7293
  jwk: external_exports.object({}).passthrough()
7291
7294
  });
7292
- var ServiceContext = class {
7293
- constructor(config) {
7294
- this._session = null;
7295
- this._services = /* @__PURE__ */ new Map();
7296
- this._eventHandlers = /* @__PURE__ */ new Map();
7297
- this._abortController = new AbortController();
7298
- this._retired = false;
7299
- this._invoke = this.wrapInvoke(config.invoke);
7300
- this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
7301
- this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
7302
- this._hosts = config.hosts;
7303
- this._session = config.session ?? null;
7304
- this._retryPolicy = {
7305
- ...defaultRetryPolicy,
7306
- ...config.retryPolicy
7307
- };
7308
- this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
7309
- this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
7310
- }
7311
- // ============================================================
7312
- // Session Management
7313
- // ============================================================
7314
- /**
7315
- * Get the current session.
7316
- */
7317
- get session() {
7318
- return this._retired ? null : this._session;
7319
- }
7320
- /**
7321
- * Check if the context has an authenticated session.
7322
- */
7323
- get isAuthenticated() {
7324
- return !this._retired && this._session !== null;
7325
- }
7326
- /**
7327
- * Update the session and notify all registered services.
7328
- *
7329
- * @param session - New session or null to clear
7330
- */
7331
- setSession(session2) {
7332
- this.assertActive();
7333
- this._session = session2;
7334
- this.emit("session.changed", { authenticated: session2 !== null });
7335
- for (const service of this._services.values()) {
7336
- service.onSessionChange(session2);
7337
- }
7338
- }
7339
- // ============================================================
7340
- // Platform Dependencies
7341
- // ============================================================
7342
- /**
7343
- * Get the invoke function for WASM operations.
7344
- */
7345
- get invoke() {
7346
- this.assertActive();
7347
- return this._invoke;
7348
- }
7349
- /**
7350
- * Get the multi-resource invoke function when available.
7351
- */
7352
- get invokeAny() {
7353
- this.assertActive();
7354
- return this._invokeAny;
7355
- }
7356
- /**
7357
- * Get the fetch function for HTTP requests.
7358
- */
7359
- get fetch() {
7360
- this.assertActive();
7361
- return this._fetch;
7362
- }
7363
- /**
7364
- * Get the list of TinyCloud host URLs.
7365
- */
7366
- get hosts() {
7367
- this.assertActive();
7368
- return this._hosts;
7369
- }
7370
- // ============================================================
7371
- // Service Registry
7372
- // ============================================================
7373
- /**
7374
- * Register a service with the context.
7375
- *
7376
- * @param name - Service name (e.g., 'kv')
7377
- * @param service - Service instance
7378
- */
7379
- registerService(name, service) {
7380
- this._services.set(name, service);
7381
- }
7382
- /**
7383
- * Unregister a service from the context.
7384
- *
7385
- * @param name - Service name to remove
7386
- */
7387
- unregisterService(name) {
7388
- this._services.delete(name);
7389
- }
7390
- /**
7391
- * Get a registered service by name.
7392
- *
7393
- * @param name - Service name
7394
- * @returns The service instance or undefined if not registered
7395
- */
7396
- getService(name) {
7397
- return this._services.get(name);
7398
- }
7399
- // ============================================================
7400
- // Event System (Telemetry)
7401
- // ============================================================
7402
- /**
7403
- * Emit a telemetry event.
7404
- *
7405
- * @param event - Event name
7406
- * @param data - Event data
7407
- */
7408
- emit(event, data) {
7409
- tinyCloudDebugLogger.log(event, data);
7410
- if (this._telemetryEnabled && this._telemetryHandler) {
7411
- try {
7412
- this._telemetryHandler(event, projectDiagnosticData(data));
7413
- } catch (error) {
7414
- console.error(`Error in telemetry handler for "${event}":`, error);
7415
- }
7416
- }
7417
- const handlers = this._eventHandlers.get(event);
7418
- if (handlers) {
7419
- for (const handler of handlers) {
7420
- try {
7421
- handler(data);
7422
- } catch (error) {
7423
- console.error(`Error in event handler for "${event}":`, error);
7424
- }
7425
- }
7426
- }
7427
- }
7428
- /**
7429
- * Subscribe to telemetry events.
7430
- *
7431
- * @param event - Event name to subscribe to
7432
- * @param handler - Handler function
7433
- * @returns Unsubscribe function
7434
- */
7435
- on(event, handler) {
7436
- if (!this._eventHandlers.has(event)) {
7437
- this._eventHandlers.set(event, /* @__PURE__ */ new Set());
7438
- }
7439
- this._eventHandlers.get(event).add(handler);
7440
- return () => {
7441
- const handlers = this._eventHandlers.get(event);
7442
- if (handlers) {
7443
- handlers.delete(handler);
7444
- if (handlers.size === 0) {
7445
- this._eventHandlers.delete(event);
7446
- }
7447
- }
7448
- };
7449
- }
7450
- /**
7451
- * Remove all event handlers for an event.
7452
- *
7453
- * @param event - Event name (if omitted, clears all events)
7454
- */
7455
- clearEventHandlers(event) {
7456
- if (event) {
7457
- this._eventHandlers.delete(event);
7458
- } else {
7459
- this._eventHandlers.clear();
7460
- }
7461
- }
7462
- // ============================================================
7463
- // Lifecycle
7464
- // ============================================================
7465
- /**
7466
- * Get the abort signal for cancelling operations.
7467
- */
7468
- get abortSignal() {
7469
- return this._abortController.signal;
7470
- }
7471
- get operationAbortSignal() {
7472
- return this._operationAbortSignal;
7473
- }
7474
- setOperationAbortSignal(signal) {
7475
- this._operationAbortSignal = signal;
7476
- }
7477
- /**
7478
- * Permanently retire this graph after its owner installs a replacement.
7479
- * Unlike `abort()`, retirement never creates a fresh controller, so captured
7480
- * services cannot resume calls under a superseded session.
7481
- */
7482
- retire() {
7483
- if (this._retired) return;
7484
- this._retired = true;
7485
- this._session = null;
7486
- this._abortController.abort();
7487
- for (const service of this._services.values()) {
7488
- try {
7489
- service.onSignOut();
7490
- } catch (error) {
7491
- console.error("Error retiring service:", error);
7492
- }
7493
- }
7494
- }
7495
- /**
7496
- * Abort all pending operations and notify services.
7497
- * Creates a new AbortController for future operations.
7498
- */
7499
- abort() {
7500
- if (this._retired) return;
7501
- this._abortController.abort();
7502
- this._abortController = new AbortController();
7503
- for (const service of this._services.values()) {
7504
- try {
7505
- service.onSignOut();
7506
- } catch (error) {
7507
- console.error("Error signing out service:", error);
7508
- }
7509
- }
7510
- }
7511
- /**
7512
- * Sign out - abort operations and clear session.
7513
- */
7514
- signOut() {
7515
- if (this._retired) return;
7516
- this.abort();
7517
- this.setSession(null);
7518
- this.emit("session.expired", {});
7519
- }
7520
- // ============================================================
7521
- // Retry Policy
7522
- // ============================================================
7523
- /**
7524
- * Get the retry policy configuration.
7525
- */
7526
- get retryPolicy() {
7527
- return this._retryPolicy;
7528
- }
7529
- wrapInvoke(invoke) {
7530
- return (session2, service, path, action, facts) => {
7531
- this.assertActive();
7532
- if (!tinyCloudDebugLogger.isEnabled()) {
7533
- return invoke(session2, service, path, action, facts);
7534
- }
7535
- const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
7536
- service,
7537
- path,
7538
- action,
7539
- facts
7540
- });
7541
- try {
7542
- const result = invoke(session2, service, path, action, facts);
7543
- timer.stop({ ok: true, service, path, action });
7544
- return result;
7545
- } catch (error) {
7546
- timer.stop({ ok: false, service, path, action, error });
7547
- throw error;
7548
- }
7549
- };
7550
- }
7551
- assertActive() {
7552
- if (this._retired) {
7553
- throw new Error("Service graph has been retired: its session was replaced or signed out.");
7554
- }
7555
- }
7556
- wrapInvokeAny(invokeAny) {
7557
- return (session2, entries, facts) => {
7558
- this.assertActive();
7559
- if (!tinyCloudDebugLogger.isEnabled()) {
7560
- return invokeAny(session2, entries, facts);
7561
- }
7562
- const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
7563
- entries,
7564
- facts
7565
- });
7566
- try {
7567
- const result = invokeAny(session2, entries, facts);
7568
- timer.stop({ ok: true, entries });
7569
- return result;
7570
- } catch (error) {
7571
- timer.stop({ ok: false, entries, error });
7572
- throw error;
7573
- }
7574
- };
7575
- }
7576
- wrapFetch(fetchFn) {
7577
- return async (url, init) => {
7578
- this.assertActive();
7579
- const request = {
7580
- ...init,
7581
- signal: this.combineSignals(init?.signal)
7582
- };
7583
- if (!tinyCloudDebugLogger.isEnabled()) {
7584
- return fetchFn(url, request);
7585
- }
7586
- const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", {
7587
- url,
7588
- method: request.method ?? "GET",
7589
- init: request
7590
- });
7591
- try {
7592
- const response = await fetchFn(url, request);
7593
- timer.stop({
7594
- ok: response.ok,
7595
- url,
7596
- method: request.method ?? "GET",
7597
- status: response.status,
7598
- statusText: response.statusText
7599
- });
7600
- return response;
7601
- } catch (error) {
7602
- timer.stop({
7603
- ok: false,
7604
- url,
7605
- method: request.method ?? "GET",
7606
- error
7607
- });
7608
- throw error;
7609
- }
7610
- };
7611
- }
7612
- combineSignals(signal) {
7613
- if (!signal) return this._abortController.signal;
7614
- const combined = new AbortController();
7615
- const abort = (source) => combined.abort(source.reason);
7616
- if (signal.aborted) {
7617
- abort(signal);
7618
- } else if (this._abortController.signal.aborted) {
7619
- abort(this._abortController.signal);
7620
- } else {
7621
- signal.addEventListener("abort", () => abort(signal), { once: true });
7622
- this._abortController.signal.addEventListener(
7623
- "abort",
7624
- () => abort(this._abortController.signal),
7625
- { once: true }
7626
- );
7627
- }
7628
- return combined.signal;
7629
- }
7630
- };
7631
7295
  function parseServiceErrorBody(errorText) {
7632
7296
  try {
7633
7297
  return JSON.parse(errorText);
@@ -7778,7 +7442,7 @@ function nodeRetryError(service, message, response, errorText) {
7778
7442
  const body = errorText.trim();
7779
7443
  const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
7780
7444
  let code;
7781
- if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable")) {
7445
+ if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable" || token === "read_audit_busy")) {
7782
7446
  code = ErrorCodes.NODE_BUSY;
7783
7447
  } else if (response.status === 503 && token === "request_timeout") {
7784
7448
  code = ErrorCodes.REQUEST_TIMEOUT;
@@ -7982,50 +7646,432 @@ function parseStorageRejection(responseText) {
7982
7646
  } catch {
7983
7647
  return parseStorageQuotaBytes(responseText);
7984
7648
  }
7985
- if (typeof body !== "object" || body === null) return void 0;
7986
- const record = body;
7987
- const message = typeof record.message === "string" ? record.message : "";
7988
- const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
7989
- const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
7990
- if (!space && !isStorageBody) {
7991
- return void 0;
7649
+ if (typeof body !== "object" || body === null) return void 0;
7650
+ const record = body;
7651
+ const message = typeof record.message === "string" ? record.message : "";
7652
+ const space = byteCountsOf(record.space) ?? parseStorageQuotaBytes(message);
7653
+ const isStorageBody = typeof record.error === "string" && STORAGE_REJECTION_BODY_ERRORS[record.error] === true;
7654
+ if (!space && !isStorageBody) {
7655
+ return void 0;
7656
+ }
7657
+ const details = { ...space };
7658
+ const account = byteCountsOf(record.account);
7659
+ if (account) {
7660
+ const plan = record.account.plan;
7661
+ details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
7662
+ }
7663
+ return details;
7664
+ }
7665
+ function storageRejectionError(service, status, meta, responseText) {
7666
+ const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
7667
+ return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
7668
+ }
7669
+ function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
7670
+ if (error instanceof Error) {
7671
+ if (isRequestTimeoutError(error)) {
7672
+ return timeoutError(service, error.timeoutMs, error);
7673
+ }
7674
+ if (error.name === "AbortError") {
7675
+ return abortedError(service);
7676
+ }
7677
+ if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
7678
+ return timeoutError(service);
7679
+ }
7680
+ return {
7681
+ code: defaultCode,
7682
+ message: error.message,
7683
+ service,
7684
+ cause: error
7685
+ };
7686
+ }
7687
+ return {
7688
+ code: defaultCode,
7689
+ message: String(error),
7690
+ service
7691
+ };
7692
+ }
7693
+ var abortableSleep = (ms, signal, unref = false) => new Promise((resolve) => {
7694
+ const done = () => {
7695
+ clearTimeout(timer);
7696
+ signal.removeEventListener("abort", done);
7697
+ resolve();
7698
+ };
7699
+ const timer = setTimeout(done, ms);
7700
+ if (unref) timer.unref?.();
7701
+ signal.aborted ? done() : signal.addEventListener("abort", done);
7702
+ });
7703
+ var ServiceContext = class {
7704
+ constructor(config) {
7705
+ this._session = null;
7706
+ this._services = /* @__PURE__ */ new Map();
7707
+ this._eventHandlers = /* @__PURE__ */ new Map();
7708
+ this._abortController = new AbortController();
7709
+ this._retired = false;
7710
+ this._invoke = this.wrapInvoke(config.invoke);
7711
+ this._invokeAny = config.invokeAny ? this.wrapInvokeAny(config.invokeAny) : void 0;
7712
+ this._fetch = this.wrapFetch(config.fetch ?? globalThis.fetch.bind(globalThis));
7713
+ this._hosts = config.hosts;
7714
+ this._session = config.session ?? null;
7715
+ this._retryPolicy = {
7716
+ ...defaultRetryPolicy,
7717
+ ...config.retryPolicy
7718
+ };
7719
+ this._telemetryEnabled = typeof config.telemetry === "boolean" ? config.telemetry : config.telemetry?.enabled === true;
7720
+ this._telemetryHandler = typeof config.telemetry === "object" ? config.telemetry.onEvent : void 0;
7721
+ }
7722
+ // ============================================================
7723
+ // Session Management
7724
+ // ============================================================
7725
+ /**
7726
+ * Get the current session.
7727
+ */
7728
+ get session() {
7729
+ return this._retired ? null : this._session;
7730
+ }
7731
+ /**
7732
+ * Check if the context has an authenticated session.
7733
+ */
7734
+ get isAuthenticated() {
7735
+ return !this._retired && this._session !== null;
7736
+ }
7737
+ /**
7738
+ * Update the session and notify all registered services.
7739
+ *
7740
+ * @param session - New session or null to clear
7741
+ */
7742
+ setSession(session2) {
7743
+ this.assertActive();
7744
+ this._session = session2;
7745
+ this.emit("session.changed", { authenticated: session2 !== null });
7746
+ for (const service of this._services.values()) {
7747
+ service.onSessionChange(session2);
7748
+ }
7749
+ }
7750
+ // ============================================================
7751
+ // Platform Dependencies
7752
+ // ============================================================
7753
+ /**
7754
+ * Get the invoke function for WASM operations.
7755
+ */
7756
+ get invoke() {
7757
+ this.assertActive();
7758
+ return this._invoke;
7759
+ }
7760
+ /**
7761
+ * Get the multi-resource invoke function when available.
7762
+ */
7763
+ get invokeAny() {
7764
+ this.assertActive();
7765
+ return this._invokeAny;
7766
+ }
7767
+ /**
7768
+ * Get the fetch function for HTTP requests.
7769
+ */
7770
+ get fetch() {
7771
+ this.assertActive();
7772
+ return this._fetch;
7773
+ }
7774
+ /**
7775
+ * Get the list of TinyCloud host URLs.
7776
+ */
7777
+ get hosts() {
7778
+ this.assertActive();
7779
+ return this._hosts;
7780
+ }
7781
+ // ============================================================
7782
+ // Service Registry
7783
+ // ============================================================
7784
+ /**
7785
+ * Register a service with the context.
7786
+ *
7787
+ * @param name - Service name (e.g., 'kv')
7788
+ * @param service - Service instance
7789
+ */
7790
+ registerService(name, service) {
7791
+ this._services.set(name, service);
7792
+ }
7793
+ /**
7794
+ * Unregister a service from the context.
7795
+ *
7796
+ * @param name - Service name to remove
7797
+ */
7798
+ unregisterService(name) {
7799
+ this._services.delete(name);
7800
+ }
7801
+ /**
7802
+ * Get a registered service by name.
7803
+ *
7804
+ * @param name - Service name
7805
+ * @returns The service instance or undefined if not registered
7806
+ */
7807
+ getService(name) {
7808
+ return this._services.get(name);
7809
+ }
7810
+ // ============================================================
7811
+ // Event System (Telemetry)
7812
+ // ============================================================
7813
+ /**
7814
+ * Emit a telemetry event.
7815
+ *
7816
+ * @param event - Event name
7817
+ * @param data - Event data
7818
+ */
7819
+ emit(event, data) {
7820
+ tinyCloudDebugLogger.log(event, data);
7821
+ if (this._telemetryEnabled && this._telemetryHandler) {
7822
+ try {
7823
+ this._telemetryHandler(event, projectDiagnosticData(data));
7824
+ } catch (error) {
7825
+ console.error(`Error in telemetry handler for "${event}":`, error);
7826
+ }
7827
+ }
7828
+ const handlers = this._eventHandlers.get(event);
7829
+ if (handlers) {
7830
+ for (const handler of handlers) {
7831
+ try {
7832
+ handler(data);
7833
+ } catch (error) {
7834
+ console.error(`Error in event handler for "${event}":`, error);
7835
+ }
7836
+ }
7837
+ }
7838
+ }
7839
+ /**
7840
+ * Subscribe to telemetry events.
7841
+ *
7842
+ * @param event - Event name to subscribe to
7843
+ * @param handler - Handler function
7844
+ * @returns Unsubscribe function
7845
+ */
7846
+ on(event, handler) {
7847
+ if (!this._eventHandlers.has(event)) {
7848
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
7849
+ }
7850
+ this._eventHandlers.get(event).add(handler);
7851
+ return () => {
7852
+ const handlers = this._eventHandlers.get(event);
7853
+ if (handlers) {
7854
+ handlers.delete(handler);
7855
+ if (handlers.size === 0) {
7856
+ this._eventHandlers.delete(event);
7857
+ }
7858
+ }
7859
+ };
7860
+ }
7861
+ /**
7862
+ * Remove all event handlers for an event.
7863
+ *
7864
+ * @param event - Event name (if omitted, clears all events)
7865
+ */
7866
+ clearEventHandlers(event) {
7867
+ if (event) {
7868
+ this._eventHandlers.delete(event);
7869
+ } else {
7870
+ this._eventHandlers.clear();
7871
+ }
7872
+ }
7873
+ // ============================================================
7874
+ // Lifecycle
7875
+ // ============================================================
7876
+ /**
7877
+ * Get the abort signal for cancelling operations.
7878
+ */
7879
+ get abortSignal() {
7880
+ return this._abortController.signal;
7881
+ }
7882
+ get operationAbortSignal() {
7883
+ return this._operationAbortSignal;
7884
+ }
7885
+ setOperationAbortSignal(signal) {
7886
+ this._operationAbortSignal = signal;
7887
+ }
7888
+ /**
7889
+ * Permanently retire this graph after its owner installs a replacement.
7890
+ * Unlike `abort()`, retirement never creates a fresh controller, so captured
7891
+ * services cannot resume calls under a superseded session.
7892
+ */
7893
+ retire() {
7894
+ if (this._retired) return;
7895
+ this._retired = true;
7896
+ this._session = null;
7897
+ this._abortController.abort();
7898
+ for (const service of this._services.values()) {
7899
+ try {
7900
+ service.onSignOut();
7901
+ } catch (error) {
7902
+ console.error("Error retiring service:", error);
7903
+ }
7904
+ }
7905
+ }
7906
+ /**
7907
+ * Abort all pending operations and notify services.
7908
+ * Creates a new AbortController for future operations.
7909
+ */
7910
+ abort() {
7911
+ if (this._retired) return;
7912
+ this._abortController.abort();
7913
+ this._abortController = new AbortController();
7914
+ for (const service of this._services.values()) {
7915
+ try {
7916
+ service.onSignOut();
7917
+ } catch (error) {
7918
+ console.error("Error signing out service:", error);
7919
+ }
7920
+ }
7921
+ }
7922
+ /**
7923
+ * Sign out - abort operations and clear session.
7924
+ */
7925
+ signOut() {
7926
+ if (this._retired) return;
7927
+ this.abort();
7928
+ this.setSession(null);
7929
+ this.emit("session.expired", {});
7992
7930
  }
7993
- const details = { ...space };
7994
- const account = byteCountsOf(record.account);
7995
- if (account) {
7996
- const plan = record.account.plan;
7997
- details.account = typeof plan === "string" && plan !== "" ? { ...account, plan } : account;
7931
+ // ============================================================
7932
+ // Retry Policy
7933
+ // ============================================================
7934
+ /**
7935
+ * Get the retry policy configuration.
7936
+ */
7937
+ get retryPolicy() {
7938
+ return this._retryPolicy;
7998
7939
  }
7999
- return details;
8000
- }
8001
- function storageRejectionError(service, status, meta, responseText) {
8002
- const quotaMeta = { ...meta, ...parseStorageRejection(responseText) };
8003
- return status === 402 ? storageQuotaExceededError(service, STORAGE_FULL_MESSAGE, quotaMeta) : storageLimitReachedError(service, STORAGE_WRITE_TOO_LARGE_MESSAGE, quotaMeta);
8004
- }
8005
- function wrapError(service, error, defaultCode = ErrorCodes.NETWORK_ERROR) {
8006
- if (error instanceof Error) {
8007
- if (isRequestTimeoutError(error)) {
8008
- return timeoutError(service, error.timeoutMs, error);
8009
- }
8010
- if (error.name === "AbortError") {
8011
- return abortedError(service);
7940
+ wrapInvoke(invoke) {
7941
+ return (session2, service, path, action, facts) => {
7942
+ this.assertActive();
7943
+ if (!tinyCloudDebugLogger.isEnabled()) {
7944
+ return invoke(session2, service, path, action, facts);
7945
+ }
7946
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invoke", {
7947
+ service,
7948
+ path,
7949
+ action,
7950
+ facts
7951
+ });
7952
+ try {
7953
+ const result = invoke(session2, service, path, action, facts);
7954
+ timer.stop({ ok: true, service, path, action });
7955
+ return result;
7956
+ } catch (error) {
7957
+ timer.stop({ ok: false, service, path, action, error });
7958
+ throw error;
7959
+ }
7960
+ };
7961
+ }
7962
+ assertActive() {
7963
+ if (this._retired) {
7964
+ throw new Error("Service graph has been retired: its session was replaced or signed out.");
8012
7965
  }
8013
- if (error.name === "TimeoutError" || error.message.toLowerCase().includes("timeout")) {
8014
- return timeoutError(service);
7966
+ }
7967
+ wrapInvokeAny(invokeAny) {
7968
+ return (session2, entries, facts) => {
7969
+ this.assertActive();
7970
+ if (!tinyCloudDebugLogger.isEnabled()) {
7971
+ return invokeAny(session2, entries, facts);
7972
+ }
7973
+ const timer = tinyCloudDebugLogger.startTimer("sdk.invokeAny", {
7974
+ entries,
7975
+ facts
7976
+ });
7977
+ try {
7978
+ const result = invokeAny(session2, entries, facts);
7979
+ timer.stop({ ok: true, entries });
7980
+ return result;
7981
+ } catch (error) {
7982
+ timer.stop({ ok: false, entries, error });
7983
+ throw error;
7984
+ }
7985
+ };
7986
+ }
7987
+ /** Retry `NODE_BUSY` / `REQUEST_TIMEOUT`; `build` must re-sign per attempt (the node 409s a repeated invocation). */
7988
+ async invokeWithRetry(url, build) {
7989
+ this.assertActive();
7990
+ const policy = this._retryPolicy;
7991
+ const startedAt = Date.now();
7992
+ let backoffMs = 0;
7993
+ let signal;
7994
+ for (let attempt = 1; ; attempt++) {
7995
+ const init = await build();
7996
+ signal ?? (signal = this.combineSignals(init.signal));
7997
+ const response = await this._fetch(url, init);
7998
+ const done = () => {
7999
+ try {
8000
+ response.retry = { attempts: attempt, backoffMs };
8001
+ } catch {
8002
+ }
8003
+ return response;
8004
+ };
8005
+ if (attempt >= policy.maxAttempts || response.status !== 503 || !response.clone) return done();
8006
+ const text = await response.clone().text().catch(() => "");
8007
+ const error = nodeRetryError("", "", response, text);
8008
+ if (!error || error.meta?.retryable !== true || !policy.retryableErrors.includes(error.code)) {
8009
+ return done();
8010
+ }
8011
+ const remaining = (policy.maxTotalMs ?? Infinity) - (Date.now() - startedAt);
8012
+ if (remaining <= 0) return done();
8013
+ const retryAfterMs = error.meta.retryAfterMs;
8014
+ const step = Math.min(
8015
+ policy.maxDelayMs,
8016
+ policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** (attempt - 1) : policy.backoff === "linear" ? policy.baseDelayMs * attempt : 0
8017
+ );
8018
+ const delay = Math.min(
8019
+ typeof retryAfterMs === "number" ? retryAfterMs : Math.random() * step,
8020
+ remaining
8021
+ );
8022
+ response.body?.cancel?.()?.catch?.(() => {
8023
+ });
8024
+ await abortableSleep(delay, signal);
8025
+ if (signal.aborted) throw signal.reason;
8026
+ backoffMs += delay;
8015
8027
  }
8016
- return {
8017
- code: defaultCode,
8018
- message: error.message,
8019
- service,
8020
- cause: error
8028
+ }
8029
+ wrapFetch(fetchFn) {
8030
+ return async (url, init) => {
8031
+ this.assertActive();
8032
+ const request = {
8033
+ ...init,
8034
+ signal: this.combineSignals(init?.signal)
8035
+ };
8036
+ if (!tinyCloudDebugLogger.isEnabled()) {
8037
+ return fetchFn(url, request);
8038
+ }
8039
+ const meta = { url, method: request.method ?? "GET" };
8040
+ const timer = tinyCloudDebugLogger.startTimer("sdk.fetch", { ...meta, init: request });
8041
+ try {
8042
+ const response = await fetchFn(url, request);
8043
+ timer.stop({
8044
+ ...meta,
8045
+ ok: response.ok,
8046
+ status: response.status,
8047
+ statusText: response.statusText
8048
+ });
8049
+ return response;
8050
+ } catch (error) {
8051
+ timer.stop({ ...meta, ok: false, error });
8052
+ throw error;
8053
+ }
8021
8054
  };
8022
8055
  }
8023
- return {
8024
- code: defaultCode,
8025
- message: String(error),
8026
- service
8027
- };
8028
- }
8056
+ combineSignals(signal) {
8057
+ if (!signal) return this._abortController.signal;
8058
+ const combined = new AbortController();
8059
+ const abort = (source) => combined.abort(source.reason);
8060
+ if (signal.aborted) {
8061
+ abort(signal);
8062
+ } else if (this._abortController.signal.aborted) {
8063
+ abort(this._abortController.signal);
8064
+ } else {
8065
+ signal.addEventListener("abort", () => abort(signal), { once: true });
8066
+ this._abortController.signal.addEventListener(
8067
+ "abort",
8068
+ () => abort(this._abortController.signal),
8069
+ { once: true }
8070
+ );
8071
+ }
8072
+ return combined.signal;
8073
+ }
8074
+ };
8029
8075
  var MAX_TIMER_DELAY_MS = 2147483647;
8030
8076
  var BaseService = class {
8031
8077
  constructor() {
@@ -8200,6 +8246,18 @@ var BaseService = class {
8200
8246
  }
8201
8247
  return controller.signal;
8202
8248
  }
8249
+ /** POST a signed invocation with retry; `mint` re-signs per attempt, `init.headers` add to the signed ones. */
8250
+ invokeWithRetry(url, mint, { headers: extra, ...init } = {}) {
8251
+ const build = () => {
8252
+ const signed = mint();
8253
+ return {
8254
+ method: "POST",
8255
+ ...init,
8256
+ headers: Array.isArray(signed) ? [...signed, ...Object.entries(extra ?? {})] : { ...signed, ...extra }
8257
+ };
8258
+ };
8259
+ return this.context.invokeWithRetry ? this.context.invokeWithRetry(url, build) : this.context.fetch(url, build());
8260
+ }
8203
8261
  /**
8204
8262
  * Create the abort signal for one request, bounded by the request timeout.
8205
8263
  *
@@ -8561,15 +8619,6 @@ var KVService = class extends BaseService {
8561
8619
  get host() {
8562
8620
  return this.context.hosts[0];
8563
8621
  }
8564
- withJsonContentType(headers) {
8565
- if (Array.isArray(headers)) {
8566
- return [...headers, ["content-type", "application/json"]];
8567
- }
8568
- return {
8569
- ...headers,
8570
- "content-type": "application/json"
8571
- };
8572
- }
8573
8622
  /**
8574
8623
  * Execute an invoke operation.
8575
8624
  *
@@ -8580,20 +8629,11 @@ var KVService = class extends BaseService {
8580
8629
  * @returns Fetch response
8581
8630
  */
8582
8631
  async invokeOperation(path, action, body, signal, extraHeaders) {
8583
- const session2 = this.context.session;
8584
- const headers = this.context.invoke(
8585
- session2,
8586
- "kv",
8587
- path,
8588
- action
8632
+ return this.invokeWithRetry(
8633
+ `${this.host}/invoke`,
8634
+ () => this.context.invoke(this.context.session, "kv", path, action),
8635
+ { headers: extraHeaders, body, signal }
8589
8636
  );
8590
- const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
8591
- return this.context.fetch(`${this.host}/invoke`, {
8592
- method: "POST",
8593
- headers: requestHeaders,
8594
- body,
8595
- signal
8596
- });
8597
8637
  }
8598
8638
  /**
8599
8639
  * Serialize a single put value into a fetch body.
@@ -8799,28 +8839,28 @@ var KVService = class extends BaseService {
8799
8839
  }
8800
8840
  const request = this.createRequestSignal(options?.signal, options?.timeout);
8801
8841
  try {
8802
- const session2 = this.context.session;
8803
- const invocationHeaders = this.context.invokeAny(
8804
- session2,
8805
- paths.map((path) => ({
8806
- spaceId: session2.spaceId,
8807
- service: "kv",
8808
- path,
8809
- action
8810
- }))
8811
- );
8812
8842
  const limitHeaders = {};
8813
8843
  if (getOptions?.maxResponseBytes !== void 0) {
8814
8844
  limitHeaders["x-tinycloud-max-response-bytes"] = String(
8815
8845
  getOptions.maxResponseBytes
8816
8846
  );
8817
8847
  }
8818
- const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
8819
- const response = await this.context.fetch(`${this.host}/invoke`, {
8820
- method: "POST",
8821
- headers,
8822
- signal: request.signal
8823
- });
8848
+ const response = await this.invokeWithRetry(
8849
+ `${this.host}/invoke`,
8850
+ () => {
8851
+ const session2 = this.context.session;
8852
+ return this.context.invokeAny(
8853
+ session2,
8854
+ paths.map((path) => ({
8855
+ spaceId: session2.spaceId,
8856
+ service: "kv",
8857
+ path,
8858
+ action
8859
+ }))
8860
+ );
8861
+ },
8862
+ { headers: limitHeaders, signal: request.signal }
8863
+ );
8824
8864
  if (!response.ok) {
8825
8865
  const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
8826
8866
  if (response.status === 401 || response.status === 403) {
@@ -8866,10 +8906,10 @@ var KVService = class extends BaseService {
8866
8906
  result: err(serviceError(item.error.code, item.error.message, "kv"))
8867
8907
  };
8868
8908
  }
8869
- const headers2 = item.headers;
8909
+ const headers = item.headers;
8870
8910
  const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
8871
8911
  item.dataBase64,
8872
- headers2,
8912
+ headers,
8873
8913
  getOptions?.raw,
8874
8914
  getOptions?.binary
8875
8915
  );
@@ -8877,7 +8917,7 @@ var KVService = class extends BaseService {
8877
8917
  key: keys[index],
8878
8918
  result: ok({
8879
8919
  data,
8880
- headers: this.createBatchResponseHeaders(headers2)
8920
+ headers: this.createBatchResponseHeaders(headers)
8881
8921
  })
8882
8922
  };
8883
8923
  });
@@ -9112,7 +9152,7 @@ var KVService = class extends BaseService {
9112
9152
  ));
9113
9153
  }
9114
9154
  const data = await this.parseResponse(response, options?.raw, options?.binary);
9115
- return ok({ data, headers: this.createResponseHeaders(response.headers) });
9155
+ return ok({ data, headers: this.createResponseHeaders(response.headers), ...response.retry && { retry: response.retry } });
9116
9156
  }
9117
9157
  async batchGet(keys, options) {
9118
9158
  return this.withTelemetry(
@@ -9202,7 +9242,8 @@ var KVService = class extends BaseService {
9202
9242
  }
9203
9243
  return ok({
9204
9244
  data: void 0,
9205
- headers: this.createResponseHeaders(response.headers)
9245
+ headers: this.createResponseHeaders(response.headers),
9246
+ ...response.retry && { retry: response.retry }
9206
9247
  });
9207
9248
  }
9208
9249
  /**
@@ -9275,25 +9316,23 @@ var KVService = class extends BaseService {
9275
9316
  entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
9276
9317
  );
9277
9318
  }
9278
- const headers = this.context.invokeAny(
9279
- session2,
9280
- paths.map((path) => ({
9281
- spaceId: session2.spaceId,
9282
- service: "kv",
9283
- path,
9284
- action: KVAction.PUT
9285
- }))
9319
+ const response = await this.invokeWithRetry(
9320
+ `${this.host}/invoke`,
9321
+ () => {
9322
+ const headers = this.context.invokeAny(
9323
+ session2,
9324
+ paths.map((path) => ({
9325
+ spaceId: session2.spaceId,
9326
+ service: "kv",
9327
+ path,
9328
+ action: KVAction.PUT
9329
+ }))
9330
+ );
9331
+ requestMayHaveDispatched = true;
9332
+ return headers;
9333
+ },
9334
+ { body, signal }
9286
9335
  );
9287
- const url = `${this.host}/invoke`;
9288
- const init = {
9289
- method: "POST",
9290
- headers,
9291
- body,
9292
- signal
9293
- };
9294
- const fetchFn = this.context.fetch;
9295
- requestMayHaveDispatched = true;
9296
- const response = await fetchFn(url, init);
9297
9336
  if (!response.ok) {
9298
9337
  let errorText;
9299
9338
  try {
@@ -9763,7 +9802,8 @@ var KVService = class extends BaseService {
9763
9802
  }
9764
9803
  return ok({
9765
9804
  data: void 0,
9766
- headers: this.createResponseHeaders(response.headers)
9805
+ headers: this.createResponseHeaders(response.headers),
9806
+ ...response.retry && { retry: response.retry }
9767
9807
  });
9768
9808
  }
9769
9809
  /**
@@ -9837,12 +9877,6 @@ var KVService = class extends BaseService {
9837
9877
  }
9838
9878
  const path = this.getFullPath(key, options?.prefix);
9839
9879
  const session2 = this.context.session;
9840
- const headers = this.context.invoke(
9841
- session2,
9842
- "kv",
9843
- path,
9844
- KVAction.GET
9845
- );
9846
9880
  const body = {
9847
9881
  space: session2.spaceId,
9848
9882
  path,
@@ -9856,12 +9890,15 @@ var KVService = class extends BaseService {
9856
9890
  }
9857
9891
  const request = this.createRequestSignal(options?.signal, options?.timeout);
9858
9892
  try {
9859
- const response = await this.context.fetch(`${this.host}/signed/kv`, {
9860
- method: "POST",
9861
- headers: this.withJsonContentType(headers),
9862
- body: JSON.stringify(body),
9863
- signal: request.signal
9864
- });
9893
+ const response = await this.invokeWithRetry(
9894
+ `${this.host}/signed/kv`,
9895
+ () => this.context.invoke(session2, "kv", path, KVAction.GET),
9896
+ {
9897
+ headers: { "content-type": "application/json" },
9898
+ body: JSON.stringify(body),
9899
+ signal: request.signal
9900
+ }
9901
+ );
9865
9902
  if (!response.ok) {
9866
9903
  return await this.createSignedReadUrlError(response, key, path);
9867
9904
  }
@@ -10287,18 +10324,19 @@ var SQLService = class extends BaseService {
10287
10324
  }
10288
10325
  // === Private helpers ===
10289
10326
  async invokeSQL(dbName, actions, body, signal) {
10290
- const session2 = this.context.session;
10291
10327
  const actionList = Array.isArray(actions) ? actions : [actions];
10292
- const headers = actionList.length === 1 ? this.context.invoke(session2, "sql", dbName, actionList[0]) : this.invokeSQLAny(session2, dbName, actionList);
10293
- return this.context.fetch(`${this.host}/invoke`, {
10294
- method: "POST",
10295
- headers: {
10296
- ...headers,
10297
- "Content-Type": "application/json"
10328
+ return this.invokeWithRetry(
10329
+ `${this.host}/invoke`,
10330
+ () => {
10331
+ const session2 = this.context.session;
10332
+ return actionList.length === 1 ? this.context.invoke(session2, "sql", dbName, actionList[0]) : this.invokeSQLAny(session2, dbName, actionList);
10298
10333
  },
10299
- body: JSON.stringify(body),
10300
- signal
10301
- });
10334
+ {
10335
+ headers: { "Content-Type": "application/json" },
10336
+ body: JSON.stringify(body),
10337
+ signal
10338
+ }
10339
+ );
10302
10340
  }
10303
10341
  actionForSql(sql, fallback) {
10304
10342
  const token = firstSqlToken(sql);
@@ -10752,22 +10790,20 @@ var DuckDbService = class extends BaseService {
10752
10790
  }
10753
10791
  const request = this.createRequestSignal(options?.signal);
10754
10792
  try {
10755
- const session2 = this.context.session;
10756
- const headers = this.context.invoke(
10757
- session2,
10758
- "duckdb",
10759
- dbName,
10760
- DuckDbAction.IMPORT
10793
+ const response = await this.invokeWithRetry(
10794
+ `${this.host}/invoke`,
10795
+ () => this.context.invoke(
10796
+ this.context.session,
10797
+ "duckdb",
10798
+ dbName,
10799
+ DuckDbAction.IMPORT
10800
+ ),
10801
+ {
10802
+ headers: { "Content-Type": "application/x-duckdb" },
10803
+ body: new Blob([data]),
10804
+ signal: request.signal
10805
+ }
10761
10806
  );
10762
- const response = await this.context.fetch(`${this.host}/invoke`, {
10763
- method: "POST",
10764
- headers: {
10765
- ...headers,
10766
- "Content-Type": "application/x-duckdb"
10767
- },
10768
- body: new Blob([data]),
10769
- signal: request.signal
10770
- });
10771
10807
  if (!response.ok) {
10772
10808
  return await this.handleErrorResponse(response, "import");
10773
10809
  }
@@ -10781,18 +10817,15 @@ var DuckDbService = class extends BaseService {
10781
10817
  }
10782
10818
  // === Private helpers ===
10783
10819
  async invokeDuckDb(dbName, action, body, signal, extraHeaders) {
10784
- const session2 = this.context.session;
10785
- const headers = this.context.invoke(session2, "duckdb", dbName, action);
10786
- return this.context.fetch(`${this.host}/invoke`, {
10787
- method: "POST",
10788
- headers: {
10789
- ...headers,
10790
- "Content-Type": "application/json",
10791
- ...extraHeaders
10792
- },
10793
- body: JSON.stringify(body),
10794
- signal
10795
- });
10820
+ return this.invokeWithRetry(
10821
+ `${this.host}/invoke`,
10822
+ () => this.context.invoke(this.context.session, "duckdb", dbName, action),
10823
+ {
10824
+ headers: { "Content-Type": "application/json", ...extraHeaders },
10825
+ body: JSON.stringify(body),
10826
+ signal
10827
+ }
10828
+ );
10796
10829
  }
10797
10830
  async handleErrorResponse(response, operation) {
10798
10831
  const errorText = await response.text();
@@ -10977,26 +11010,24 @@ var HooksService = class extends BaseService {
10977
11010
  );
10978
11011
  }
10979
11012
  try {
10980
- const response = await this.context.fetch(`${this.host}/hooks/webhooks`, {
10981
- method: "POST",
10982
- headers: {
10983
- ...serviceHeadersToRecord(
10984
- this.createHookHeaders(
10985
- "tinycloud.hooks/register",
10986
- buildScopePath(webhook.service, webhook.pathPrefix)
10987
- )
10988
- ),
10989
- "content-type": "application/json"
10990
- },
10991
- body: JSON.stringify({
10992
- space: webhook.space,
10993
- service: webhook.service,
10994
- pathPrefix: normalizePathPrefix(webhook.pathPrefix),
10995
- abilities: webhook.abilities ?? [],
10996
- callbackUrl: webhook.callbackUrl,
10997
- secret: webhook.secret
10998
- })
10999
- });
11013
+ const response = await this.invokeWithRetry(
11014
+ `${this.host}/hooks/webhooks`,
11015
+ () => this.createHookHeaders(
11016
+ "tinycloud.hooks/register",
11017
+ buildScopePath(webhook.service, webhook.pathPrefix)
11018
+ ),
11019
+ {
11020
+ headers: { "content-type": "application/json" },
11021
+ body: JSON.stringify({
11022
+ space: webhook.space,
11023
+ service: webhook.service,
11024
+ pathPrefix: normalizePathPrefix(webhook.pathPrefix),
11025
+ abilities: webhook.abilities ?? [],
11026
+ callbackUrl: webhook.callbackUrl,
11027
+ secret: webhook.secret
11028
+ })
11029
+ }
11030
+ );
11000
11031
  if (!response.ok) {
11001
11032
  return err(
11002
11033
  await responseError("hooks", "failed to register webhook", response)
@@ -11034,17 +11065,13 @@ var HooksService = class extends BaseService {
11034
11065
  query.set("prefix", normalizedPrefix);
11035
11066
  }
11036
11067
  }
11037
- const response = await this.context.fetch(
11068
+ const response = await this.invokeWithRetry(
11038
11069
  `${this.host}/hooks/webhooks${query.size > 0 ? `?${query.toString()}` : ""}`,
11039
- {
11040
- method: "GET",
11041
- headers: serviceHeadersToRecord(
11042
- this.createHookHeaders(
11043
- "tinycloud.hooks/list",
11044
- options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
11045
- )
11046
- )
11047
- }
11070
+ () => this.createHookHeaders(
11071
+ "tinycloud.hooks/list",
11072
+ options.service ? buildScopePath(options.service, options.pathPrefix) : "webhooks"
11073
+ ),
11074
+ { method: "GET" }
11048
11075
  );
11049
11076
  if (!response.ok) {
11050
11077
  return err(
@@ -11071,20 +11098,16 @@ var HooksService = class extends BaseService {
11071
11098
  return err(authRequiredError("hooks"));
11072
11099
  }
11073
11100
  try {
11074
- const response = await this.context.fetch(
11101
+ const response = await this.invokeWithRetry(
11075
11102
  `${this.host}/hooks/webhooks/${encodeURIComponent(id)}`,
11076
- {
11077
- method: "DELETE",
11078
- headers: serviceHeadersToRecord(
11079
- this.createHookHeaders(
11080
- "tinycloud.hooks/unregister",
11081
- options.target ? buildScopePath(
11082
- options.target.service,
11083
- options.target.pathPrefix
11084
- ) : `webhooks/${id}`
11085
- )
11086
- )
11087
- }
11103
+ () => this.createHookHeaders(
11104
+ "tinycloud.hooks/unregister",
11105
+ options.target ? buildScopePath(
11106
+ options.target.service,
11107
+ options.target.pathPrefix
11108
+ ) : `webhooks/${id}`
11109
+ ),
11110
+ { method: "DELETE" }
11088
11111
  );
11089
11112
  if (!response.ok) {
11090
11113
  return err(
@@ -11540,23 +11563,7 @@ function readProperty(value, key) {
11540
11563
  return void 0;
11541
11564
  }
11542
11565
  }
11543
- function sleep(delayMs, _attempt, signal) {
11544
- return new Promise((resolve) => {
11545
- const finish = () => {
11546
- clearTimeout(timer);
11547
- signal.removeEventListener("abort", stop);
11548
- resolve();
11549
- };
11550
- const timer = setTimeout(finish, delayMs);
11551
- timer.unref?.();
11552
- const stop = () => finish();
11553
- if (signal.aborted) {
11554
- finish();
11555
- } else {
11556
- signal.addEventListener("abort", stop, { once: true });
11557
- }
11558
- });
11559
- }
11566
+ var sleep = (delayMs, _attempt, signal) => abortableSleep(delayMs, signal, true);
11560
11567
  function abortPromise(signal) {
11561
11568
  let resolve;
11562
11569
  const promise = new Promise((done) => {