@tinycloud/sdk-services 3.2.6-beta.4 → 3.3.0-beta.25

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.
Files changed (45) hide show
  1. package/dist/{BaseService-1Ug6y7DC.d.cts → BaseService-BhfKzKke.d.cts} +5 -1
  2. package/dist/{BaseService-Bn07Vudy.d.ts → BaseService-x4AFZiEa.d.ts} +5 -1
  3. package/dist/encryption/index.cjs +25 -4
  4. package/dist/encryption/index.cjs.map +1 -1
  5. package/dist/encryption/index.d.cts +2 -2
  6. package/dist/encryption/index.d.ts +2 -2
  7. package/dist/encryption/index.js +25 -4
  8. package/dist/encryption/index.js.map +1 -1
  9. package/dist/index.cjs +959 -808
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +114 -7
  12. package/dist/index.d.ts +114 -7
  13. package/dist/index.js +958 -808
  14. package/dist/index.js.map +1 -1
  15. package/dist/internal/decrypt-transport-response-error.d.cts +1 -1
  16. package/dist/internal/decrypt-transport-response-error.d.ts +1 -1
  17. package/dist/kv/index.cjs +136 -99
  18. package/dist/kv/index.cjs.map +1 -1
  19. package/dist/kv/index.d.cts +4 -5
  20. package/dist/kv/index.d.ts +4 -5
  21. package/dist/kv/index.js +136 -99
  22. package/dist/kv/index.js.map +1 -1
  23. package/dist/kv/replication/grantPolicy.cjs +82 -0
  24. package/dist/kv/replication/grantPolicy.cjs.map +1 -0
  25. package/dist/kv/replication/grantPolicy.d.cts +19 -0
  26. package/dist/kv/replication/grantPolicy.d.ts +19 -0
  27. package/dist/kv/replication/grantPolicy.js +50 -0
  28. package/dist/kv/replication/grantPolicy.js.map +1 -0
  29. package/dist/kv/replication/index.cjs +576 -132
  30. package/dist/kv/replication/index.cjs.map +1 -1
  31. package/dist/kv/replication/index.d.cts +3 -3
  32. package/dist/kv/replication/index.d.ts +3 -3
  33. package/dist/kv/replication/index.js +576 -132
  34. package/dist/kv/replication/index.js.map +1 -1
  35. package/dist/{scope-BnyVPZ5E.d.ts → scope-9U4gVNFN.d.ts} +312 -7
  36. package/dist/{scope-CQ5kmHlt.d.cts → scope-BzdcrOZG.d.cts} +312 -7
  37. package/dist/sql/index.cjs +98 -50
  38. package/dist/sql/index.cjs.map +1 -1
  39. package/dist/sql/index.d.cts +2 -2
  40. package/dist/sql/index.d.ts +2 -2
  41. package/dist/sql/index.js +98 -50
  42. package/dist/sql/index.js.map +1 -1
  43. package/dist/{types-gmdUiorz.d.cts → types-eVS1Whuf.d.cts} +29 -3
  44. package/dist/{types-gmdUiorz.d.ts → types-eVS1Whuf.d.ts} +29 -3
  45. package/package.json +16 -2
@@ -1,7 +1,7 @@
1
- import { B as BaseService } from '../BaseService-1Ug6y7DC.cjs';
2
- import { R as Result, I as IService } from '../types-gmdUiorz.cjs';
3
- import { au as KVGetOptions, az as KVResponse, an as KVBatchReadResponse, ay as KVPutOptions, ak as KVBatchPutItem, al as KVBatchPutOptions, am as KVBatchPutResponse, aw as KVListOptions, ax as KVListResponse, aq as KVChangesOptions, ar as KVChangesResponse, at as KVDeleteOptions, av as KVHeadOptions, as as KVCreateSignedReadUrlOptions, aC as KVSignedReadUrlResponse, aB as KVServiceConfig, c as KVReadThrough } from '../scope-CQ5kmHlt.cjs';
4
- export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, ah as DEFAULT_SIGNED_READ_URL_EXPIRY_MS, ai as KVAction, aj as KVActionType, ao as KVChange, ap as KVChangesAuthority, b as KVListPage, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, a as KVReplicationController, K as KVReplicationDeps, aA as KVResponseHeaders, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, P as PendingWriteStore, m as PinnedKey, n as PurgeTarget, q as ReplicaAuthorityState, w as ReplicaCoverage, B as ReplicaDevice, M as ReplicaGatedHandle, Q as ReplicaGrantInfo, T as ReplicaInspectedStatus, a3 as ReplicaState, a4 as ReplicaStatusEntry, a5 as ReplicationAuthority, a6 as ReplicationControl, a7 as ReplicationEvent, R as ReplicationIdentity, a8 as ReplicationOptions, a9 as ReplicationPurgeReport, aa as ReplicationReason, ab as ReplicationScheduler, ac as ResolvedReplicationOptions, ae as canonicalReplicationIdentity, af as replicationIdentityKey, ag as requiresSecretsOptIn } from '../scope-CQ5kmHlt.cjs';
1
+ import { B as BaseService } from '../BaseService-BhfKzKke.cjs';
2
+ import { R as Result, I as IService } from '../types-eVS1Whuf.cjs';
3
+ import { aD as KVGetOptions, aI as KVResponse, aw as KVBatchReadResponse, aH as KVPutOptions, at as KVBatchPutItem, au as KVBatchPutOptions, av as KVBatchPutResponse, aF as KVListOptions, aG as KVListResponse, az as KVChangesOptions, aA as KVChangesResponse, aC as KVDeleteOptions, aE as KVHeadOptions, aB as KVCreateSignedReadUrlOptions, aL as KVSignedReadUrlResponse, aK as KVServiceConfig, c as KVReadThrough } from '../scope-BzdcrOZG.cjs';
4
+ export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, aq as DEFAULT_SIGNED_READ_URL_EXPIRY_MS, ar as KVAction, as as KVActionType, ax as KVChange, ay as KVChangesAuthority, b as KVListPage, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, a as KVReplicationController, K as KVReplicationDeps, aJ as KVResponseHeaders, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, P as PendingWriteStore, m as PinnedKey, w as PurgeTarget, z as ReplicaAuthorityState, H as ReplicaCoverage, N as ReplicaDevice, X as ReplicaGatedHandle, _ as ReplicaGrantInfo, a0 as ReplicaInspectedStatus, ac as ReplicaState, ad as ReplicaStatusEntry, ae as ReplicationAuthority, af as ReplicationControl, ag as ReplicationEvent, R as ReplicationIdentity, ah as ReplicationOptions, ai as ReplicationPurgeReport, aj as ReplicationReason, ak as ReplicationScheduler, al as ResolvedReplicationOptions, an as canonicalReplicationIdentity, ao as replicationIdentityKey, ap as requiresSecretsOptIn } from '../scope-BzdcrOZG.cjs';
5
5
  export { kvPrefixCovers } from './replication/prefix.cjs';
6
6
  import './replication/contractConstants.cjs';
7
7
  import './replication/grantPolicyConstants.cjs';
@@ -588,7 +588,6 @@ declare class KVService extends BaseService implements IKVService {
588
588
  * Get the host URL.
589
589
  */
590
590
  private get host();
591
- private withJsonContentType;
592
591
  /**
593
592
  * Execute an invoke operation.
594
593
  *
@@ -1,7 +1,7 @@
1
- import { B as BaseService } from '../BaseService-Bn07Vudy.js';
2
- import { R as Result, I as IService } from '../types-gmdUiorz.js';
3
- import { au as KVGetOptions, az as KVResponse, an as KVBatchReadResponse, ay as KVPutOptions, ak as KVBatchPutItem, al as KVBatchPutOptions, am as KVBatchPutResponse, aw as KVListOptions, ax as KVListResponse, aq as KVChangesOptions, ar as KVChangesResponse, at as KVDeleteOptions, av as KVHeadOptions, as as KVCreateSignedReadUrlOptions, aC as KVSignedReadUrlResponse, aB as KVServiceConfig, c as KVReadThrough } from '../scope-BnyVPZ5E.js';
4
- export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, ah as DEFAULT_SIGNED_READ_URL_EXPIRY_MS, ai as KVAction, aj as KVActionType, ao as KVChange, ap as KVChangesAuthority, b as KVListPage, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, a as KVReplicationController, K as KVReplicationDeps, aA as KVResponseHeaders, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, P as PendingWriteStore, m as PinnedKey, n as PurgeTarget, q as ReplicaAuthorityState, w as ReplicaCoverage, B as ReplicaDevice, M as ReplicaGatedHandle, Q as ReplicaGrantInfo, T as ReplicaInspectedStatus, a3 as ReplicaState, a4 as ReplicaStatusEntry, a5 as ReplicationAuthority, a6 as ReplicationControl, a7 as ReplicationEvent, R as ReplicationIdentity, a8 as ReplicationOptions, a9 as ReplicationPurgeReport, aa as ReplicationReason, ab as ReplicationScheduler, ac as ResolvedReplicationOptions, ae as canonicalReplicationIdentity, af as replicationIdentityKey, ag as requiresSecretsOptIn } from '../scope-BnyVPZ5E.js';
1
+ import { B as BaseService } from '../BaseService-x4AFZiEa.js';
2
+ import { R as Result, I as IService } from '../types-eVS1Whuf.js';
3
+ import { aD as KVGetOptions, aI as KVResponse, aw as KVBatchReadResponse, aH as KVPutOptions, at as KVBatchPutItem, au as KVBatchPutOptions, av as KVBatchPutResponse, aF as KVListOptions, aG as KVListResponse, az as KVChangesOptions, aA as KVChangesResponse, aC as KVDeleteOptions, aE as KVHeadOptions, aB as KVCreateSignedReadUrlOptions, aL as KVSignedReadUrlResponse, aK as KVServiceConfig, c as KVReadThrough } from '../scope-9U4gVNFN.js';
4
+ export { A as AuthorityRefusal, C as CLEAR_PENDING_WARNING, aq as DEFAULT_SIGNED_READ_URL_EXPIRY_MS, ar as KVAction, as as KVActionType, ax as KVChange, ay as KVChangesAuthority, b as KVListPage, d as KVReplicaHandle, e as KVReplicaSpec, f as KVReplicaStorage, a as KVReplicationController, K as KVReplicationDeps, aJ as KVResponseHeaders, L as LocalGetResult, g as LocalListResult, h as LocalReadMeta, i as LocalReplicaStatus, j as LocalSyncResult, k as PendingWriteRecord, l as PendingWriteState, P as PendingWriteStore, m as PinnedKey, w as PurgeTarget, z as ReplicaAuthorityState, H as ReplicaCoverage, N as ReplicaDevice, X as ReplicaGatedHandle, _ as ReplicaGrantInfo, a0 as ReplicaInspectedStatus, ac as ReplicaState, ad as ReplicaStatusEntry, ae as ReplicationAuthority, af as ReplicationControl, ag as ReplicationEvent, R as ReplicationIdentity, ah as ReplicationOptions, ai as ReplicationPurgeReport, aj as ReplicationReason, ak as ReplicationScheduler, al as ResolvedReplicationOptions, an as canonicalReplicationIdentity, ao as replicationIdentityKey, ap as requiresSecretsOptIn } from '../scope-9U4gVNFN.js';
5
5
  export { kvPrefixCovers } from './replication/prefix.js';
6
6
  import './replication/contractConstants.js';
7
7
  import './replication/grantPolicyConstants.js';
@@ -588,7 +588,6 @@ declare class KVService extends BaseService implements IKVService {
588
588
  * Get the host URL.
589
589
  */
590
590
  private get host();
591
- private withJsonContentType;
592
591
  /**
593
592
  * Execute an invoke operation.
594
593
  *
package/dist/kv/index.js CHANGED
@@ -14,6 +14,14 @@ var ErrorCodes = {
14
14
  ABORTED: "ABORTED",
15
15
  INVALID_INPUT: "INVALID_INPUT",
16
16
  PERMISSION_DENIED: "PERMISSION_DENIED",
17
+ // Node retry signals (tinycloud-node #347). `meta.retryable` says whether a
18
+ // retry is safe; `meta.retryAfterMs` carries the node's Retry-After.
19
+ /** HTTP 503 `sql_busy:<stage>` / `unavailable_retryable`/`read_audit_busy`: nothing was applied; retry after `meta.retryAfterMs`. */
20
+ NODE_BUSY: "NODE_BUSY",
21
+ /** HTTP 503 `request_timeout`: the node's deadline passed before any write began; nothing was applied. */
22
+ REQUEST_TIMEOUT: "REQUEST_TIMEOUT",
23
+ /** HTTP 500 `outcome_unknown`: a write may have been applied; read back before retrying a non-idempotent operation. */
24
+ OUTCOME_UNKNOWN: "OUTCOME_UNKNOWN",
17
25
  // KV-specific errors
18
26
  KV_NOT_FOUND: "KV_NOT_FOUND",
19
27
  KV_WRITE_FAILED: "KV_WRITE_FAILED",
@@ -49,11 +57,12 @@ var ErrorCodes = {
49
57
  DUCKDB_READONLY_VIOLATION: "DUCKDB_READONLY_VIOLATION"
50
58
  };
51
59
  var defaultRetryPolicy = {
52
- maxAttempts: 3,
60
+ maxAttempts: 4,
53
61
  backoff: "exponential",
54
- baseDelayMs: 1e3,
55
- maxDelayMs: 1e4,
56
- retryableErrors: [ErrorCodes.NETWORK_ERROR, ErrorCodes.TIMEOUT]
62
+ baseDelayMs: 250,
63
+ maxDelayMs: 4e3,
64
+ retryableErrors: [ErrorCodes.NODE_BUSY, ErrorCodes.REQUEST_TIMEOUT],
65
+ maxTotalMs: 12e3
57
66
  };
58
67
  var TelemetryEvents = {
59
68
  SPAN: "telemetry.span",
@@ -89,6 +98,20 @@ function serviceError(code, message, service, options) {
89
98
  };
90
99
  }
91
100
 
101
+ // src/responseErrors.ts
102
+ function responseErrorMeta(status, statusText, errorText) {
103
+ const meta = { status, statusText };
104
+ const snippet = compactMessage(errorText);
105
+ if (snippet) {
106
+ meta.responseSnippet = snippet.slice(0, 300);
107
+ }
108
+ return meta;
109
+ }
110
+ function compactMessage(message) {
111
+ const compact = message.replace(/\s+/g, " ").trim();
112
+ return compact.length > 500 ? `${compact.slice(0, 500)}...` : compact;
113
+ }
114
+
92
115
  // src/errors.ts
93
116
  import { CAPABILITY_REGISTRY } from "@tinycloud/bootstrap";
94
117
 
@@ -182,6 +205,30 @@ function authRequiredError(service) {
182
205
  service
183
206
  };
184
207
  }
208
+ function nodeRetryError(service, message, response, errorText) {
209
+ const body = errorText.trim();
210
+ const [, token, stage] = /^([a-z_]+)(?::\s*([a-z_]+):)?/.exec(body) ?? [];
211
+ let code;
212
+ if (response.status === 503 && (token === "sql_busy" || token === "unavailable_retryable" || token === "read_audit_busy")) {
213
+ code = ErrorCodes.NODE_BUSY;
214
+ } else if (response.status === 503 && token === "request_timeout") {
215
+ code = ErrorCodes.REQUEST_TIMEOUT;
216
+ } else if (response.status === 500 && token === "outcome_unknown") {
217
+ code = ErrorCodes.OUTCOME_UNKNOWN;
218
+ } else {
219
+ return void 0;
220
+ }
221
+ const retryable = code !== ErrorCodes.OUTCOME_UNKNOWN;
222
+ const retryAfter = response.headers.get("retry-after")?.trim();
223
+ return serviceError(code, message, service, {
224
+ meta: {
225
+ ...responseErrorMeta(response.status, response.statusText, errorText),
226
+ retryable,
227
+ ...token === "sql_busy" && stage ? { stage } : {},
228
+ ...retryable && retryAfter && /^\d{1,9}$/.test(retryAfter) ? { retryAfterMs: Number(retryAfter) * 1e3 } : {}
229
+ }
230
+ });
231
+ }
185
232
  var RequestTimeoutError = class extends Error {
186
233
  constructor(timeoutMs) {
187
234
  super(`Request timed out after ${timeoutMs}ms.`);
@@ -593,6 +640,18 @@ var BaseService = class {
593
640
  }
594
641
  return controller.signal;
595
642
  }
643
+ /** POST a signed invocation with retry; `mint` re-signs per attempt, `init.headers` add to the signed ones. */
644
+ invokeWithRetry(url, mint, { headers: extra, ...init } = {}) {
645
+ const build = () => {
646
+ const signed = mint();
647
+ return {
648
+ method: "POST",
649
+ ...init,
650
+ headers: Array.isArray(signed) ? [...signed, ...Object.entries(extra ?? {})] : { ...signed, ...extra }
651
+ };
652
+ };
653
+ return this.context.invokeWithRetry ? this.context.invokeWithRetry(url, build) : this.context.fetch(url, build());
654
+ }
596
655
  /**
597
656
  * Create the abort signal for one request, bounded by the request timeout.
598
657
  *
@@ -960,15 +1019,6 @@ var KVService = class extends BaseService {
960
1019
  get host() {
961
1020
  return this.context.hosts[0];
962
1021
  }
963
- withJsonContentType(headers) {
964
- if (Array.isArray(headers)) {
965
- return [...headers, ["content-type", "application/json"]];
966
- }
967
- return {
968
- ...headers,
969
- "content-type": "application/json"
970
- };
971
- }
972
1022
  /**
973
1023
  * Execute an invoke operation.
974
1024
  *
@@ -979,20 +1029,11 @@ var KVService = class extends BaseService {
979
1029
  * @returns Fetch response
980
1030
  */
981
1031
  async invokeOperation(path, action, body, signal, extraHeaders) {
982
- const session = this.context.session;
983
- const headers = this.context.invoke(
984
- session,
985
- "kv",
986
- path,
987
- action
1032
+ return this.invokeWithRetry(
1033
+ `${this.host}/invoke`,
1034
+ () => this.context.invoke(this.context.session, "kv", path, action),
1035
+ { headers: extraHeaders, body, signal }
988
1036
  );
989
- const requestHeaders = Array.isArray(headers) ? [...headers, ...Object.entries(extraHeaders ?? {})] : { ...headers, ...extraHeaders };
990
- return this.context.fetch(`${this.host}/invoke`, {
991
- method: "POST",
992
- headers: requestHeaders,
993
- body,
994
- signal
995
- });
996
1037
  }
997
1038
  /**
998
1039
  * Serialize a single put value into a fetch body.
@@ -1198,28 +1239,28 @@ var KVService = class extends BaseService {
1198
1239
  }
1199
1240
  const request = this.createRequestSignal(options?.signal, options?.timeout);
1200
1241
  try {
1201
- const session = this.context.session;
1202
- const invocationHeaders = this.context.invokeAny(
1203
- session,
1204
- paths.map((path) => ({
1205
- spaceId: session.spaceId,
1206
- service: "kv",
1207
- path,
1208
- action
1209
- }))
1210
- );
1211
1242
  const limitHeaders = {};
1212
1243
  if (getOptions?.maxResponseBytes !== void 0) {
1213
1244
  limitHeaders["x-tinycloud-max-response-bytes"] = String(
1214
1245
  getOptions.maxResponseBytes
1215
1246
  );
1216
1247
  }
1217
- const headers = Array.isArray(invocationHeaders) ? [...invocationHeaders, ...Object.entries(limitHeaders)] : { ...invocationHeaders, ...limitHeaders };
1218
- const response = await this.context.fetch(`${this.host}/invoke`, {
1219
- method: "POST",
1220
- headers,
1221
- signal: request.signal
1222
- });
1248
+ const response = await this.invokeWithRetry(
1249
+ `${this.host}/invoke`,
1250
+ () => {
1251
+ const session = this.context.session;
1252
+ return this.context.invokeAny(
1253
+ session,
1254
+ paths.map((path) => ({
1255
+ spaceId: session.spaceId,
1256
+ service: "kv",
1257
+ path,
1258
+ action
1259
+ }))
1260
+ );
1261
+ },
1262
+ { headers: limitHeaders, signal: request.signal }
1263
+ );
1223
1264
  if (!response.ok) {
1224
1265
  const errorText = response.status === 401 || response.status === 403 ? await this.readAuthorizationText(response) : await response.text();
1225
1266
  if (response.status === 401 || response.status === 403) {
@@ -1265,10 +1306,10 @@ var KVService = class extends BaseService {
1265
1306
  result: err(serviceError(item.error.code, item.error.message, "kv"))
1266
1307
  };
1267
1308
  }
1268
- const headers2 = item.headers;
1309
+ const headers = item.headers;
1269
1310
  const data = action === KVAction.HEAD ? void 0 : this.parseBatchValue(
1270
1311
  item.dataBase64,
1271
- headers2,
1312
+ headers,
1272
1313
  getOptions?.raw,
1273
1314
  getOptions?.binary
1274
1315
  );
@@ -1276,7 +1317,7 @@ var KVService = class extends BaseService {
1276
1317
  key: keys[index],
1277
1318
  result: ok({
1278
1319
  data,
1279
- headers: this.createBatchResponseHeaders(headers2)
1320
+ headers: this.createBatchResponseHeaders(headers)
1280
1321
  })
1281
1322
  };
1282
1323
  });
@@ -1500,15 +1541,18 @@ var KVService = class extends BaseService {
1500
1541
  { meta: { status: response.status, statusText: response.statusText } }
1501
1542
  ));
1502
1543
  }
1544
+ const message = `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
1545
+ const retryError = nodeRetryError("kv", message, response, errorText);
1546
+ if (retryError) return err(retryError);
1503
1547
  return err(serviceError(
1504
1548
  ErrorCodes.NETWORK_ERROR,
1505
- `Failed to get key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
1549
+ message,
1506
1550
  "kv",
1507
1551
  { meta: { status: response.status, statusText: response.statusText } }
1508
1552
  ));
1509
1553
  }
1510
1554
  const data = await this.parseResponse(response, options?.raw, options?.binary);
1511
- return ok({ data, headers: this.createResponseHeaders(response.headers) });
1555
+ return ok({ data, headers: this.createResponseHeaders(response.headers), ...response.retry && { retry: response.retry } });
1512
1556
  }
1513
1557
  async batchGet(keys, options) {
1514
1558
  return this.withTelemetry(
@@ -1584,26 +1628,22 @@ var KVService = class extends BaseService {
1584
1628
  { meta: { status: response.status, statusText: response.statusText } }
1585
1629
  ));
1586
1630
  }
1587
- if (response.status === 503 && (options?.ifMatch !== void 0 || options?.ifNoneMatch !== void 0)) {
1588
- return err(serviceError(
1589
- ErrorCodes.KV_CONFLICT,
1590
- `Concurrent KV update conflicted for key ${JSON.stringify(key)}`,
1591
- "kv",
1592
- { meta: { status: response.status, statusText: response.statusText } }
1593
- ));
1594
- }
1631
+ const message = `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
1632
+ const retryError = nodeRetryError("kv", message, response, errorText);
1633
+ if (retryError) return err(retryError);
1595
1634
  const quotaError = this.handleQuotaErrorResponse(response, errorText, key);
1596
1635
  if (quotaError) return quotaError;
1597
1636
  return err(serviceError(
1598
1637
  ErrorCodes.KV_WRITE_FAILED,
1599
- `Failed to put key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
1638
+ message,
1600
1639
  "kv",
1601
1640
  { meta: { status: response.status, statusText: response.statusText } }
1602
1641
  ));
1603
1642
  }
1604
1643
  return ok({
1605
1644
  data: void 0,
1606
- headers: this.createResponseHeaders(response.headers)
1645
+ headers: this.createResponseHeaders(response.headers),
1646
+ ...response.retry && { retry: response.retry }
1607
1647
  });
1608
1648
  }
1609
1649
  /**
@@ -1676,25 +1716,23 @@ var KVService = class extends BaseService {
1676
1716
  entries?.[index]?.body ?? this.serializeBatchPutValue(items[index])
1677
1717
  );
1678
1718
  }
1679
- const headers = this.context.invokeAny(
1680
- session,
1681
- paths.map((path) => ({
1682
- spaceId: session.spaceId,
1683
- service: "kv",
1684
- path,
1685
- action: KVAction.PUT
1686
- }))
1719
+ const response = await this.invokeWithRetry(
1720
+ `${this.host}/invoke`,
1721
+ () => {
1722
+ const headers = this.context.invokeAny(
1723
+ session,
1724
+ paths.map((path) => ({
1725
+ spaceId: session.spaceId,
1726
+ service: "kv",
1727
+ path,
1728
+ action: KVAction.PUT
1729
+ }))
1730
+ );
1731
+ requestMayHaveDispatched = true;
1732
+ return headers;
1733
+ },
1734
+ { body, signal }
1687
1735
  );
1688
- const url = `${this.host}/invoke`;
1689
- const init = {
1690
- method: "POST",
1691
- headers,
1692
- body,
1693
- signal
1694
- };
1695
- const fetchFn = this.context.fetch;
1696
- requestMayHaveDispatched = true;
1697
- const response = await fetchFn(url, init);
1698
1736
  if (!response.ok) {
1699
1737
  let errorText;
1700
1738
  try {
@@ -1896,9 +1934,12 @@ var KVService = class extends BaseService {
1896
1934
  );
1897
1935
  }
1898
1936
  const errorText = await response.text();
1937
+ const message = `Failed to list keys: ${response.status} - ${errorText}`;
1938
+ const retryError = nodeRetryError("kv", message, response, errorText);
1939
+ if (retryError) return err(retryError);
1899
1940
  return err(serviceError(
1900
1941
  ErrorCodes.NETWORK_ERROR,
1901
- `Failed to list keys: ${response.status} - ${errorText}`,
1942
+ message,
1902
1943
  "kv",
1903
1944
  { meta: { status: response.status, statusText: response.statusText } }
1904
1945
  ));
@@ -2149,24 +2190,20 @@ var KVService = class extends BaseService {
2149
2190
  { meta: { status: response.status, statusText: response.statusText } }
2150
2191
  ));
2151
2192
  }
2152
- if (response.status === 503 && options?.ifMatch !== void 0) {
2153
- return err(serviceError(
2154
- ErrorCodes.KV_CONFLICT,
2155
- `Concurrent KV delete conflicted for key ${JSON.stringify(key)}`,
2156
- "kv",
2157
- { meta: { status: response.status, statusText: response.statusText } }
2158
- ));
2159
- }
2193
+ const message = `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
2194
+ const retryError = nodeRetryError("kv", message, response, errorText);
2195
+ if (retryError) return err(retryError);
2160
2196
  return err(serviceError(
2161
2197
  ErrorCodes.NETWORK_ERROR,
2162
- `Failed to delete key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2198
+ message,
2163
2199
  "kv",
2164
2200
  { meta: { status: response.status, statusText: response.statusText } }
2165
2201
  ));
2166
2202
  }
2167
2203
  return ok({
2168
2204
  data: void 0,
2169
- headers: this.createResponseHeaders(response.headers)
2205
+ headers: this.createResponseHeaders(response.headers),
2206
+ ...response.retry && { retry: response.retry }
2170
2207
  });
2171
2208
  }
2172
2209
  /**
@@ -2200,10 +2237,13 @@ var KVService = class extends BaseService {
2200
2237
  return await this.classifyNotFound(response, key);
2201
2238
  }
2202
2239
  const errorText = await response.text();
2240
+ const message = `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`;
2241
+ const retryError = nodeRetryError("kv", message, response, errorText);
2242
+ if (retryError) return err(retryError);
2203
2243
  return err(
2204
2244
  serviceError(
2205
2245
  ErrorCodes.NETWORK_ERROR,
2206
- `Failed to get metadata for key ${JSON.stringify(key)}: ${response.status} - ${errorText}`,
2246
+ message,
2207
2247
  "kv",
2208
2248
  { meta: { status: response.status, statusText: response.statusText } }
2209
2249
  )
@@ -2237,12 +2277,6 @@ var KVService = class extends BaseService {
2237
2277
  }
2238
2278
  const path = this.getFullPath(key, options?.prefix);
2239
2279
  const session = this.context.session;
2240
- const headers = this.context.invoke(
2241
- session,
2242
- "kv",
2243
- path,
2244
- KVAction.GET
2245
- );
2246
2280
  const body = {
2247
2281
  space: session.spaceId,
2248
2282
  path,
@@ -2256,12 +2290,15 @@ var KVService = class extends BaseService {
2256
2290
  }
2257
2291
  const request = this.createRequestSignal(options?.signal, options?.timeout);
2258
2292
  try {
2259
- const response = await this.context.fetch(`${this.host}/signed/kv`, {
2260
- method: "POST",
2261
- headers: this.withJsonContentType(headers),
2262
- body: JSON.stringify(body),
2263
- signal: request.signal
2264
- });
2293
+ const response = await this.invokeWithRetry(
2294
+ `${this.host}/signed/kv`,
2295
+ () => this.context.invoke(session, "kv", path, KVAction.GET),
2296
+ {
2297
+ headers: { "content-type": "application/json" },
2298
+ body: JSON.stringify(body),
2299
+ signal: request.signal
2300
+ }
2301
+ );
2265
2302
  if (!response.ok) {
2266
2303
  return await this.createSignedReadUrlError(response, key, path);
2267
2304
  }