@getanyapi/sdk 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -8
- package/dist/index.cjs +60 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -6
- package/dist/index.d.ts +28 -6
- package/dist/index.js +60 -84
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,11 @@ console.log(api.pricing.from, api.pricing.failoverMaxUsd, api.inputSchema);
|
|
|
73
73
|
|
|
74
74
|
`catalog` is category-only browsing. Ranked queries always use `search`, which returns
|
|
75
75
|
`results`, `total`, and `ranking`; `describe` includes schemas. Discovery prices are nested
|
|
76
|
-
USD flat/linear offers, lanes are anonymous, and provider is always `"AnyAPI"`.
|
|
76
|
+
USD flat/linear offers, lanes are anonymous, and provider is always `"AnyAPI"`. The gateway
|
|
77
|
+
owns validation, routing, lane order, failover, pricing relationships, health semantics, and
|
|
78
|
+
billing. This handwritten discovery client safety-scans the response, projects known fields,
|
|
79
|
+
preserves schemas as opaque JSON, and ignores safe additions. It does not recompute gateway
|
|
80
|
+
business rules. Generated per-SKU methods remain a separate OpenAPI-driven surface.
|
|
77
81
|
|
|
78
82
|
## Pagination
|
|
79
83
|
|
|
@@ -113,7 +117,8 @@ await client.google.search(
|
|
|
113
117
|
);
|
|
114
118
|
```
|
|
115
119
|
|
|
116
|
-
Per-call transport overrides: `timeoutMs`, `maxRetries`, and an
|
|
120
|
+
Per-call transport overrides: `timeoutMs`, `maxRetries`, `maxInProgressWaitMs`, and an
|
|
121
|
+
`AbortSignal` via `signal`.
|
|
117
122
|
|
|
118
123
|
## Errors and retries
|
|
119
124
|
|
|
@@ -129,12 +134,30 @@ Per-call transport overrides: `timeoutMs`, `maxRetries`, and an `AbortSignal` vi
|
|
|
129
134
|
| `ConnectionError` | 0 | Network or transport failure |
|
|
130
135
|
| `TimeoutError` | 0 | Request exceeded its timeout (not retried) |
|
|
131
136
|
|
|
132
|
-
All extend `AnyAPIError` (with `status` and `requestId`). Retries cover
|
|
133
|
-
failures proven to happen before a request was sent, with jittered
|
|
134
|
-
`Retry-After`. Default `maxRetries` is 2 (up to 3 attempts); set it
|
|
135
|
-
Timeouts are never retried. Connection failures during or after a
|
|
136
|
-
retried because the call may already have been charged. When the
|
|
137
|
-
does not retry. Configure with
|
|
137
|
+
All extend `AnyAPIError` (with `status` and `requestId`). Retries cover 429, one specific 409
|
|
138
|
+
(below), and network failures proven to happen before a request was sent, with jittered
|
|
139
|
+
exponential backoff honoring `Retry-After`. Default `maxRetries` is 2 (up to 3 attempts); set it
|
|
140
|
+
on the client or per request. Timeouts are never retried. Connection failures during or after a
|
|
141
|
+
billed `POST /v1/run` are not retried because the call may already have been charged. When the
|
|
142
|
+
send phase is unknown, the SDK does not retry. Configure with
|
|
143
|
+
`new AnyAPI({ timeoutMs, maxRetries })`.
|
|
144
|
+
|
|
145
|
+
### Waiting out a run that is still in flight
|
|
146
|
+
|
|
147
|
+
Settlement is detached from your connection, so a run keeps going after a connection drops. When
|
|
148
|
+
you re-issue a call whose `Idempotency-Key` is still executing, the gateway answers `409` with
|
|
149
|
+
`code: "idempotency_in_progress"` and `Retry-After: 30`. The SDK waits that full delay and
|
|
150
|
+
retries, so you get the original run's replayed result (`replayed: true`, no second charge)
|
|
151
|
+
instead of an error.
|
|
152
|
+
|
|
153
|
+
The 8s ordinary-backoff ceiling does not apply here; a separate whole-call budget does.
|
|
154
|
+
`maxInProgressWaitMs` (default `60000`) caps the TOTAL time one `run()` may block on these
|
|
155
|
+
waits, across every retry. A wait that does not fit the remaining budget is refused and the
|
|
156
|
+
`409` is thrown rather than truncated into an attempt that would fail anyway. Set
|
|
157
|
+
`maxInProgressWaitMs: 0` to surface the `409` immediately and handle it yourself.
|
|
158
|
+
|
|
159
|
+
No other `409` retries: `idempotency_conflict` (the same key with different input) and
|
|
160
|
+
`idempotency_needs_review` are caller-side problems a retry cannot fix.
|
|
138
161
|
|
|
139
162
|
Automatic network retry of a billed `run()` requires structured runtime evidence that the
|
|
140
163
|
request body was not sent:
|
package/dist/index.cjs
CHANGED
|
@@ -101,7 +101,10 @@ module.exports = __toCommonJS(index_exports);
|
|
|
101
101
|
var AnyAPIError = class extends Error {
|
|
102
102
|
/** HTTP status code, or 0 for transport-level failures (connection/timeout). */
|
|
103
103
|
status;
|
|
104
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* The gateway's X-Anyapi-Request-Id response header when present (falling back to a
|
|
106
|
+
* proxy-set x-request-id), else undefined. Quote it to AnyAPI support.
|
|
107
|
+
*/
|
|
105
108
|
requestId;
|
|
106
109
|
/** Stable gateway error code when the JSON body includes one, else undefined. */
|
|
107
110
|
code;
|
|
@@ -136,6 +139,14 @@ var ConnectionError = class extends AnyAPIError {
|
|
|
136
139
|
};
|
|
137
140
|
var TimeoutError = class extends AnyAPIError {
|
|
138
141
|
};
|
|
142
|
+
var REQUEST_ID_HEADERS = ["x-anyapi-request-id", "x-request-id"];
|
|
143
|
+
function requestIdOf(headers) {
|
|
144
|
+
for (const name of REQUEST_ID_HEADERS) {
|
|
145
|
+
const value = headers.get(name);
|
|
146
|
+
if (value) return value;
|
|
147
|
+
}
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
139
150
|
function errorFromStatus(status, message, requestId, code) {
|
|
140
151
|
switch (status) {
|
|
141
152
|
case 400:
|
|
@@ -207,17 +218,20 @@ var DEFAULT_BASE_URL = "https://api.getanyapi.com";
|
|
|
207
218
|
function malformed(path) {
|
|
208
219
|
throw new AnyAPIError(`malformed discovery response: ${path}`, 0);
|
|
209
220
|
}
|
|
210
|
-
function
|
|
221
|
+
function rejectUnsafeDiscoveryFields(value, path) {
|
|
211
222
|
if (Array.isArray(value)) {
|
|
212
223
|
value.forEach(
|
|
213
|
-
(item, index) =>
|
|
224
|
+
(item, index) => rejectUnsafeDiscoveryFields(item, `${path}[${index}]`)
|
|
214
225
|
);
|
|
215
226
|
return;
|
|
216
227
|
}
|
|
217
228
|
if (typeof value !== "object" || value === null) return;
|
|
218
229
|
for (const [key, item] of Object.entries(value)) {
|
|
219
230
|
if (key.toLowerCase().includes("credit")) malformed(`${path}.${key}`);
|
|
220
|
-
|
|
231
|
+
if (key === "provider" && item !== "AnyAPI") {
|
|
232
|
+
malformed(`${path}.${key}`);
|
|
233
|
+
}
|
|
234
|
+
rejectUnsafeDiscoveryFields(item, `${path}.${key}`);
|
|
221
235
|
}
|
|
222
236
|
}
|
|
223
237
|
function record(value, path) {
|
|
@@ -226,12 +240,6 @@ function record(value, path) {
|
|
|
226
240
|
}
|
|
227
241
|
return value;
|
|
228
242
|
}
|
|
229
|
-
function exactKeys(raw, allowed, path) {
|
|
230
|
-
const keys = new Set(allowed);
|
|
231
|
-
for (const key of Object.keys(raw)) {
|
|
232
|
-
if (!keys.has(key)) malformed(`${path}.${key}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
243
|
function stringField(raw, key, path) {
|
|
236
244
|
const value = raw[key];
|
|
237
245
|
if (typeof value !== "string") return malformed(`${path}.${key}`);
|
|
@@ -262,14 +270,12 @@ function parseOffer(value, path) {
|
|
|
262
270
|
const unit = stringField(raw, "unit", path);
|
|
263
271
|
const maxUsd = numberField(raw, "maxUsd", path);
|
|
264
272
|
if (model === "flat") {
|
|
265
|
-
|
|
266
|
-
if (unit !== "request" || "baseUsd" in raw || "perUnitUsd" in raw) {
|
|
273
|
+
if (unit !== "request") {
|
|
267
274
|
return malformed(path);
|
|
268
275
|
}
|
|
269
276
|
return { model, unit, maxUsd };
|
|
270
277
|
}
|
|
271
278
|
if (model === "linear") {
|
|
272
|
-
exactKeys(raw, ["model", "unit", "baseUsd", "perUnitUsd", "maxUsd"], path);
|
|
273
279
|
if (unit.length === 0) return malformed(`${path}.unit`);
|
|
274
280
|
return {
|
|
275
281
|
model,
|
|
@@ -283,7 +289,6 @@ function parseOffer(value, path) {
|
|
|
283
289
|
}
|
|
284
290
|
function parsePricing(value, path) {
|
|
285
291
|
const raw = record(value, path);
|
|
286
|
-
exactKeys(raw, ["from", "failoverMaxUsd"], path);
|
|
287
292
|
return {
|
|
288
293
|
from: parseOffer(raw["from"], `${path}.from`),
|
|
289
294
|
failoverMaxUsd: numberField(raw, "failoverMaxUsd", path)
|
|
@@ -291,10 +296,8 @@ function parsePricing(value, path) {
|
|
|
291
296
|
}
|
|
292
297
|
function parseHealth(value, path) {
|
|
293
298
|
const raw = record(value, path);
|
|
294
|
-
exactKeys(raw, ["window", "uptimePct", "latencyP50Ms", "requests"], path);
|
|
295
|
-
if (raw["window"] !== "30d") return malformed(`${path}.window`);
|
|
296
299
|
return {
|
|
297
|
-
window: "
|
|
300
|
+
window: stringField(raw, "window", path),
|
|
298
301
|
uptimePct: boundedNumberField(raw, "uptimePct", path, void 0, 100),
|
|
299
302
|
latencyP50Ms: integerField(raw, "latencyP50Ms", path),
|
|
300
303
|
requests: integerField(raw, "requests", path)
|
|
@@ -302,7 +305,6 @@ function parseHealth(value, path) {
|
|
|
302
305
|
}
|
|
303
306
|
function parseLane(value, path) {
|
|
304
307
|
const raw = record(value, path);
|
|
305
|
-
exactKeys(raw, ["pricing", "health"], path);
|
|
306
308
|
const lane = {
|
|
307
309
|
pricing: parseOffer(raw["pricing"], `${path}.pricing`)
|
|
308
310
|
};
|
|
@@ -320,7 +322,6 @@ function parseSchema(value, path) {
|
|
|
320
322
|
}
|
|
321
323
|
function parseHighlight(value, path) {
|
|
322
324
|
const raw = record(value, path);
|
|
323
|
-
exactKeys(raw, ["path", "type", "why"], path);
|
|
324
325
|
const field = {
|
|
325
326
|
path: stringField(raw, "path", path),
|
|
326
327
|
type: stringField(raw, "type", path)
|
|
@@ -341,28 +342,10 @@ function mapProfile(raw) {
|
|
|
341
342
|
return profile;
|
|
342
343
|
}
|
|
343
344
|
function mapCatalogEntry(raw) {
|
|
344
|
-
|
|
345
|
+
rejectUnsafeDiscoveryFields(raw, "api");
|
|
345
346
|
const value = record(raw, "api");
|
|
346
|
-
exactKeys(
|
|
347
|
-
value,
|
|
348
|
-
[
|
|
349
|
-
"id",
|
|
350
|
-
"slug",
|
|
351
|
-
"category",
|
|
352
|
-
"name",
|
|
353
|
-
"description",
|
|
354
|
-
"provider",
|
|
355
|
-
"pricing",
|
|
356
|
-
"lanes",
|
|
357
|
-
"heavy",
|
|
358
|
-
"tryEligible",
|
|
359
|
-
"inputSchema",
|
|
360
|
-
"outputSchema"
|
|
361
|
-
],
|
|
362
|
-
"api"
|
|
363
|
-
);
|
|
364
347
|
const lanesRaw = value["lanes"];
|
|
365
|
-
if (!Array.isArray(lanesRaw)
|
|
348
|
+
if (!Array.isArray(lanesRaw)) {
|
|
366
349
|
return malformed("api.lanes");
|
|
367
350
|
}
|
|
368
351
|
const entry = {
|
|
@@ -384,32 +367,24 @@ function mapCatalogEntry(raw) {
|
|
|
384
367
|
}
|
|
385
368
|
if (typeof value["tryEligible"] !== "boolean")
|
|
386
369
|
return malformed("api.tryEligible");
|
|
370
|
+
if (value["failover"] !== void 0) {
|
|
371
|
+
if (typeof value["failover"] !== "boolean")
|
|
372
|
+
return malformed("api.failover");
|
|
373
|
+
entry.failover = value["failover"];
|
|
374
|
+
}
|
|
375
|
+
if (value["excludesCallerDelay"] !== void 0) {
|
|
376
|
+
if (typeof value["excludesCallerDelay"] !== "boolean")
|
|
377
|
+
return malformed("api.excludesCallerDelay");
|
|
378
|
+
entry.excludesCallerDelay = value["excludesCallerDelay"];
|
|
379
|
+
}
|
|
387
380
|
if (value["inputSchema"] !== void 0) {
|
|
388
381
|
entry.inputSchema = parseSchema(value["inputSchema"], "api.inputSchema");
|
|
389
382
|
}
|
|
390
383
|
if (value["outputSchema"] !== void 0) {
|
|
391
384
|
entry.outputSchema = parseSchema(value["outputSchema"], "api.outputSchema");
|
|
392
385
|
}
|
|
393
|
-
if (!offersEqual(entry.pricing.from, entry.lanes[0].pricing)) {
|
|
394
|
-
return malformed("api.pricing.from");
|
|
395
|
-
}
|
|
396
|
-
const failoverMaxUsd = Math.max(
|
|
397
|
-
...entry.lanes.map((lane) => lane.pricing.maxUsd)
|
|
398
|
-
);
|
|
399
|
-
if (entry.pricing.failoverMaxUsd !== failoverMaxUsd) {
|
|
400
|
-
return malformed("api.pricing.failoverMaxUsd");
|
|
401
|
-
}
|
|
402
386
|
return entry;
|
|
403
387
|
}
|
|
404
|
-
function offersEqual(left, right) {
|
|
405
|
-
if (left.model !== right.model || left.unit !== right.unit || left.maxUsd !== right.maxUsd) {
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
if (left.model === "flat" || right.model === "flat") {
|
|
409
|
-
return left.model === right.model;
|
|
410
|
-
}
|
|
411
|
-
return left.baseUsd === right.baseUsd && left.perUnitUsd === right.perUnitUsd;
|
|
412
|
-
}
|
|
413
388
|
function mapCatalogDetail(raw) {
|
|
414
389
|
const entry = mapCatalogEntry(raw);
|
|
415
390
|
if (entry.inputSchema === void 0) return malformed("api.inputSchema");
|
|
@@ -417,28 +392,13 @@ function mapCatalogDetail(raw) {
|
|
|
417
392
|
return entry;
|
|
418
393
|
}
|
|
419
394
|
function mapCatalogList(raw) {
|
|
395
|
+
rejectUnsafeDiscoveryFields(raw, "catalog");
|
|
420
396
|
const envelope = record(raw, "catalog");
|
|
421
|
-
exactKeys(envelope, ["apis"], "catalog");
|
|
422
397
|
if (!Array.isArray(envelope["apis"])) return malformed("catalog.apis");
|
|
423
398
|
return envelope["apis"].map(mapCatalogEntry);
|
|
424
399
|
}
|
|
425
400
|
function mapSearchResult(value, path) {
|
|
426
401
|
const raw = record(value, path);
|
|
427
|
-
exactKeys(
|
|
428
|
-
raw,
|
|
429
|
-
[
|
|
430
|
-
"slug",
|
|
431
|
-
"platformId",
|
|
432
|
-
"name",
|
|
433
|
-
"description",
|
|
434
|
-
"category",
|
|
435
|
-
"provider",
|
|
436
|
-
"pricing",
|
|
437
|
-
"relevance",
|
|
438
|
-
"highlightFields"
|
|
439
|
-
],
|
|
440
|
-
path
|
|
441
|
-
);
|
|
442
402
|
const result = {
|
|
443
403
|
slug: stringField(raw, "slug", path),
|
|
444
404
|
platformId: stringField(raw, "platformId", path),
|
|
@@ -459,9 +419,8 @@ function mapSearchResult(value, path) {
|
|
|
459
419
|
return result;
|
|
460
420
|
}
|
|
461
421
|
function mapCatalogSearch(raw) {
|
|
462
|
-
|
|
422
|
+
rejectUnsafeDiscoveryFields(raw, "search");
|
|
463
423
|
const envelope = record(raw, "search");
|
|
464
|
-
exactKeys(envelope, ["results", "total", "ranking"], "search");
|
|
465
424
|
if (!Array.isArray(envelope["results"])) return malformed("search.results");
|
|
466
425
|
const ranking = envelope["ranking"];
|
|
467
426
|
if (ranking !== "semantic" && ranking !== "keyword")
|
|
@@ -506,7 +465,7 @@ async function agentSignup(options = {}) {
|
|
|
506
465
|
0
|
|
507
466
|
);
|
|
508
467
|
}
|
|
509
|
-
const requestId = response.headers
|
|
468
|
+
const requestId = requestIdOf(response.headers);
|
|
510
469
|
const text = await response.text().catch(() => "");
|
|
511
470
|
if (response.status !== 200) {
|
|
512
471
|
let message = `request failed with status ${response.status}`;
|
|
@@ -538,6 +497,8 @@ var DEFAULT_TIMEOUT_MS = 6e4;
|
|
|
538
497
|
var DEFAULT_MAX_RETRIES = 2;
|
|
539
498
|
var RETRY_BASE_DELAY_MS = 500;
|
|
540
499
|
var RETRY_MAX_DELAY_MS = 8e3;
|
|
500
|
+
var DEFAULT_MAX_IN_PROGRESS_WAIT_MS = 6e4;
|
|
501
|
+
var IDEMPOTENCY_IN_PROGRESS_CODE = "idempotency_in_progress";
|
|
541
502
|
var PRE_SEND_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
542
503
|
"EADDRNOTAVAIL",
|
|
543
504
|
"EAI_AGAIN",
|
|
@@ -581,7 +542,7 @@ function backoffDelay(attempt) {
|
|
|
581
542
|
const factor = 0.5 + Math.random();
|
|
582
543
|
return base * factor;
|
|
583
544
|
}
|
|
584
|
-
function
|
|
545
|
+
function parseRetryAfterMs(header) {
|
|
585
546
|
if (header === null) {
|
|
586
547
|
return void 0;
|
|
587
548
|
}
|
|
@@ -591,13 +552,11 @@ function parseRetryAfter(header) {
|
|
|
591
552
|
}
|
|
592
553
|
const seconds = Number(trimmed);
|
|
593
554
|
if (Number.isFinite(seconds)) {
|
|
594
|
-
|
|
595
|
-
return Math.min(Math.max(ms, 0), RETRY_MAX_DELAY_MS);
|
|
555
|
+
return Math.max(seconds * 1e3, 0);
|
|
596
556
|
}
|
|
597
557
|
const dateMs = Date.parse(trimmed);
|
|
598
558
|
if (Number.isFinite(dateMs)) {
|
|
599
|
-
|
|
600
|
-
return Math.min(Math.max(delta, 0), RETRY_MAX_DELAY_MS);
|
|
559
|
+
return Math.max(dateMs - Date.now(), 0);
|
|
601
560
|
}
|
|
602
561
|
return void 0;
|
|
603
562
|
}
|
|
@@ -702,6 +661,7 @@ var AnyAPI = class {
|
|
|
702
661
|
maxRetries;
|
|
703
662
|
timeoutMs;
|
|
704
663
|
idempotency;
|
|
664
|
+
maxInProgressWaitMs;
|
|
705
665
|
/**
|
|
706
666
|
* The network seam the generated per-platform namespaces target. The base client IS a
|
|
707
667
|
* ClientCore (it implements `run`), so the generated subclass hands `this._core` to each
|
|
@@ -724,6 +684,7 @@ var AnyAPI = class {
|
|
|
724
684
|
this.fetchImpl = resolvedFetch;
|
|
725
685
|
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
726
686
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
687
|
+
this.maxInProgressWaitMs = options.maxInProgressWaitMs ?? DEFAULT_MAX_IN_PROGRESS_WAIT_MS;
|
|
727
688
|
const idempotency = options.idempotency ?? "auto";
|
|
728
689
|
if (idempotency !== "auto" && idempotency !== "off") {
|
|
729
690
|
throw new TypeError('idempotency must be "auto" or "off"');
|
|
@@ -744,6 +705,7 @@ var AnyAPI = class {
|
|
|
744
705
|
body,
|
|
745
706
|
timeoutMs: options?.timeoutMs ?? this.timeoutMs,
|
|
746
707
|
maxRetries: options?.maxRetries ?? this.maxRetries,
|
|
708
|
+
maxInProgressWaitMs: options?.maxInProgressWaitMs ?? this.maxInProgressWaitMs,
|
|
747
709
|
...options?.idempotencyKey !== void 0 ? { idempotencyKey: options.idempotencyKey } : {},
|
|
748
710
|
...options?.signal ? { signal: options.signal } : {}
|
|
749
711
|
}
|
|
@@ -816,6 +778,11 @@ var AnyAPI = class {
|
|
|
816
778
|
headers["Idempotency-Key"] = key;
|
|
817
779
|
}
|
|
818
780
|
let attempt = 0;
|
|
781
|
+
let inProgressWaitedMs = 0;
|
|
782
|
+
const inProgressBudgetMs = Math.max(
|
|
783
|
+
opts.maxInProgressWaitMs ?? DEFAULT_MAX_IN_PROGRESS_WAIT_MS,
|
|
784
|
+
0
|
|
785
|
+
);
|
|
819
786
|
for (; ; ) {
|
|
820
787
|
const { signal, timeoutSignal } = composeSignal(
|
|
821
788
|
opts.timeoutMs,
|
|
@@ -849,7 +816,7 @@ var AnyAPI = class {
|
|
|
849
816
|
}
|
|
850
817
|
throw connErr;
|
|
851
818
|
}
|
|
852
|
-
const requestId = response.headers
|
|
819
|
+
const requestId = requestIdOf(response.headers);
|
|
853
820
|
if (response.status === 200) {
|
|
854
821
|
const text = await response.text();
|
|
855
822
|
try {
|
|
@@ -864,13 +831,22 @@ var AnyAPI = class {
|
|
|
864
831
|
}
|
|
865
832
|
const body = await response.text().catch(() => "");
|
|
866
833
|
const { message, code } = messageFromBody(body, response.status);
|
|
834
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
867
835
|
if (response.status === 429 && attempt < opts.maxRetries) {
|
|
868
|
-
const
|
|
869
|
-
const delay = retryAfter ?? backoffDelay(attempt);
|
|
836
|
+
const delay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, RETRY_MAX_DELAY_MS) : backoffDelay(attempt);
|
|
870
837
|
await sleep(delay, opts.signal);
|
|
871
838
|
attempt += 1;
|
|
872
839
|
continue;
|
|
873
840
|
}
|
|
841
|
+
if (billedPost && response.status === 409 && code === IDEMPOTENCY_IN_PROGRESS_CODE && attempt < opts.maxRetries) {
|
|
842
|
+
const delay = retryAfterMs ?? backoffDelay(attempt);
|
|
843
|
+
if (inProgressWaitedMs + delay <= inProgressBudgetMs) {
|
|
844
|
+
await sleep(delay, opts.signal);
|
|
845
|
+
inProgressWaitedMs += delay;
|
|
846
|
+
attempt += 1;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
874
850
|
throw errorFromStatus(response.status, message, requestId, code);
|
|
875
851
|
}
|
|
876
852
|
}
|