@vxil/sdk 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +90 -33
- package/dist/index.js +31 -5
- package/dist/retry.d.ts +10 -1
- package/dist/retry.js +17 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -865,6 +865,47 @@ export interface FileObject {
|
|
|
865
865
|
/** The object's public URL when it is published to the public asset host
|
|
866
866
|
* (`files.publish`), else null. */
|
|
867
867
|
public_url?: string | null;
|
|
868
|
+
/** When the object expires (null = never). An expired object is no longer
|
|
869
|
+
* listed and answers 404, even before the sweep deletes it. */
|
|
870
|
+
expires_at?: string | null;
|
|
871
|
+
}
|
|
872
|
+
/** `files.createUploadUrl` answer. */
|
|
873
|
+
export interface FileUploadUrl {
|
|
874
|
+
object_id: string;
|
|
875
|
+
upload_url: string;
|
|
876
|
+
upload_method: string;
|
|
877
|
+
/** Seconds the UPLOAD URL stays valid (`uploadUrlTtl`, default 900). */
|
|
878
|
+
expires_in: number;
|
|
879
|
+
/** When the OBJECT expires (null = never). */
|
|
880
|
+
expires_at: string | null;
|
|
881
|
+
}
|
|
882
|
+
/** `files.usage` answer. */
|
|
883
|
+
export interface FilesUsage {
|
|
884
|
+
usage: {
|
|
885
|
+
object_count: number;
|
|
886
|
+
total_bytes: number;
|
|
887
|
+
};
|
|
888
|
+
/** `maxTotalBytes` = the storage quota enforced now (see `storage_plan`). */
|
|
889
|
+
quotas: {
|
|
890
|
+
maxObjectCount?: number;
|
|
891
|
+
maxObjectBytes?: number;
|
|
892
|
+
maxTotalBytes?: number;
|
|
893
|
+
};
|
|
894
|
+
available: Record<string, number | null>;
|
|
895
|
+
/** Your plan's storage ceiling, and whether `quotas.maxTotalBytes` is that
|
|
896
|
+
* ceiling (`plan`) or your own lower value (`config`). */
|
|
897
|
+
storage_plan?: {
|
|
898
|
+
tier: string | null;
|
|
899
|
+
max_total_bytes: number;
|
|
900
|
+
source: 'plan' | 'config';
|
|
901
|
+
};
|
|
902
|
+
public_assets?: {
|
|
903
|
+
enabled: boolean;
|
|
904
|
+
published_objects: number;
|
|
905
|
+
published_bytes: number;
|
|
906
|
+
max_published_bytes: number;
|
|
907
|
+
bytes_remaining: number;
|
|
908
|
+
};
|
|
868
909
|
}
|
|
869
910
|
/** A published object (guide ch. 6, files, "Public asset delivery"): a stable,
|
|
870
911
|
* content-addressed URL on vxil's public asset host, served with
|
|
@@ -1167,13 +1208,23 @@ export interface JobGenerationSettledEventPayload {
|
|
|
1167
1208
|
/** `completed` on the healthy terminal, `failed` on the broken one */
|
|
1168
1209
|
status: 'completed' | 'failed';
|
|
1169
1210
|
/** the run's SETTLED error class (`provider_error`, `PollExhausted`,
|
|
1170
|
-
* `GenerationExpired`, `
|
|
1211
|
+
* `GenerationExpired`, `RetryableHttp`, `ReserveInsufficient`,
|
|
1212
|
+
* `PaymentsUnavailable`, `Cancelled`, …); always null on `completed`.
|
|
1213
|
+
* BRANCH ON THIS in a settle handler: `ReserveInsufficient` (the caller got
|
|
1214
|
+
* a 402), `PaymentsUnavailable` (the run was refused before it started —
|
|
1215
|
+
* usually the caller got a 503; a caller handed this run by a duplicate
|
|
1216
|
+
* request that raced it (202 `deduplicated`) learns it HERE and re-sends
|
|
1217
|
+
* the same request — a NEW run follows) and `Cancelled` are not render
|
|
1218
|
+
* failures. */
|
|
1171
1219
|
error_class: string | null;
|
|
1172
1220
|
/** the provider hint alone (`Upstream said: gemini 400: …`); null on
|
|
1173
1221
|
* `completed` and whenever the failure carried none */
|
|
1174
1222
|
error_hint: string | null;
|
|
1175
|
-
/** the failure-event vocabulary every `*.failed` carries (guide 12)
|
|
1176
|
-
|
|
1223
|
+
/** the failure-event vocabulary every `*.failed` carries (guide 12):
|
|
1224
|
+
* `completed` → info; `failed` → error, except a refusal at admission
|
|
1225
|
+
* (`ReserveInsufficient`, `PaymentsUnavailable`) → warn and a cancel
|
|
1226
|
+
* (`Cancelled`) → info, so neither pages through the immediate digest */
|
|
1227
|
+
level: 'info' | 'warn' | 'error';
|
|
1177
1228
|
state: 'ok' | 'broken';
|
|
1178
1229
|
}
|
|
1179
1230
|
/** The union a handler subscribed to `job.generation.` receives as `data`:
|
|
@@ -3176,7 +3227,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3176
3227
|
* FORCED to the verified end-user (a mismatched user_id → 400). A 402 aborts
|
|
3177
3228
|
* the enqueue (insufficient balance; the run ends with job.generation.failed
|
|
3178
3229
|
* `ReserveInsufficient` and the 402 names its `run_id`); a runaway over the
|
|
3179
|
-
* per-tenant outstanding-holds ceiling → 429 (Retry-After 5).
|
|
3230
|
+
* per-tenant outstanding-holds ceiling → 429 (Retry-After 5). Payments
|
|
3231
|
+
* unreachable at the hold → 503 `payments_unavailable` (Retry-After 15;
|
|
3232
|
+
* nothing started, nothing held — re-send the same request), unless
|
|
3233
|
+
* `reserve_credits.on_unavailable: 'proceed'`.
|
|
3180
3234
|
*
|
|
3181
3235
|
* Generic completion options for queue-style providers (fal.ai and alike —
|
|
3182
3236
|
* no vendor adapter): `completion.callback.query_param` puts the signed
|
|
@@ -3193,7 +3247,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3193
3247
|
*
|
|
3194
3248
|
* `opts.retryOnCapacity: { maxWaitMs }` retries a CAPACITY 429
|
|
3195
3249
|
* (`generation_concurrency_exceeded`, `reserve_holds_exceeded`,
|
|
3196
|
-
* `queue_full`) honouring Retry-After
|
|
3250
|
+
* `queue_full`) and a 503 `payments_unavailable`, honouring Retry-After
|
|
3251
|
+
* with jitter, with the SAME
|
|
3197
3252
|
* idempotency_key (one is generated when the input has none), until
|
|
3198
3253
|
* maxWaitMs has passed — then the last 429 is thrown. */
|
|
3199
3254
|
generation: (input: {
|
|
@@ -3249,9 +3304,20 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3249
3304
|
amount: number;
|
|
3250
3305
|
user_id: string;
|
|
3251
3306
|
reason?: string;
|
|
3307
|
+
/** payments unreachable when the hold is placed (a 5xx other than
|
|
3308
|
+
* 501, a 429, no answer within 5 s; payments switched off is NOT
|
|
3309
|
+
* unreachable — that run starts without a hold): `'fail'` (default)
|
|
3310
|
+
* → 503 `payments_unavailable` + Retry-After, nothing started or
|
|
3311
|
+
* held — re-send the SAME request; `'proceed'` → start WITHOUT a
|
|
3312
|
+
* hold (a free run while payments is down) */
|
|
3313
|
+
on_unavailable?: "fail" | "proceed";
|
|
3252
3314
|
};
|
|
3253
3315
|
payload?: Record<string, unknown>;
|
|
3254
3316
|
idempotency_key?: string;
|
|
3317
|
+
/** START attempts = provider calls (1..20, default jobs.retry
|
|
3318
|
+
* .defaultMaxAttempts 5); a 408 / 429 / 5xx / network error retries
|
|
3319
|
+
* after min(2^n × 15, 600) s — or the provider's Retry-After on a
|
|
3320
|
+
* 429 / 503 (1 s..10 min, never past the deadline) */
|
|
3255
3321
|
max_attempts?: number;
|
|
3256
3322
|
}, opts?: {
|
|
3257
3323
|
retryOnCapacity?: {
|
|
@@ -4998,21 +5064,26 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
4998
5064
|
}) => Promise<WebSocket>;
|
|
4999
5065
|
};
|
|
5000
5066
|
readonly files: {
|
|
5001
|
-
/** Mint a presigned PUT; upload the bytes yourself, then call complete().
|
|
5067
|
+
/** Mint a presigned PUT; upload the bytes yourself, then call complete().
|
|
5068
|
+
* `expiresInSeconds` (60 s up to 100 years) makes the OBJECT expire — it is deleted that
|
|
5069
|
+
* long after the mint, whether or not `files.ttl` is enabled; `null` =
|
|
5070
|
+
* never. The answer's `expires_in` is the UPLOAD URL's life in seconds;
|
|
5071
|
+
* `expires_at` is when the object expires (null = never). `user_id` is
|
|
5072
|
+
* required with a server key and omitted in end-user mode. */
|
|
5002
5073
|
createUploadUrl: (input: {
|
|
5003
|
-
user_id
|
|
5074
|
+
user_id?: string;
|
|
5004
5075
|
filename: string;
|
|
5005
5076
|
content_type: string;
|
|
5006
5077
|
size_bytes: number;
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
expires_at: string;
|
|
5012
|
-
}>;
|
|
5078
|
+
expiresInSeconds?: number | null;
|
|
5079
|
+
}) => Promise<FileUploadUrl>;
|
|
5080
|
+
/** Confirm the PUT (bytes verified, object → available). Answers the
|
|
5081
|
+
* object's `expires_at` (null = never). */
|
|
5013
5082
|
complete: (objectId: string) => Promise<{
|
|
5014
5083
|
object_id: string;
|
|
5015
5084
|
status: string;
|
|
5085
|
+
checksum_sha256?: string | null;
|
|
5086
|
+
expires_at?: string | null;
|
|
5016
5087
|
}>;
|
|
5017
5088
|
downloadUrl: (objectId: string) => Promise<string>;
|
|
5018
5089
|
/** Mint up to 100 presigned GETs in ONE call (`GET /v1/files/download-urls`)
|
|
@@ -5045,27 +5116,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
5045
5116
|
/** Soft delete; bytes are hard-deleted 30 days later. */
|
|
5046
5117
|
delete: (objectId: string) => Promise<void>;
|
|
5047
5118
|
/** Aggregate storage usage vs quotas (the FilesManager Storage panel).
|
|
5119
|
+
* `quotas.maxTotalBytes` is the quota ENFORCED now — the plan's ceiling
|
|
5120
|
+
* unless you set a lower `quotas.maxTotalBytes` (`storage_plan.source`
|
|
5121
|
+
* says which). An object past its expiry no longer counts.
|
|
5048
5122
|
* `public_assets` = published copies vs the plan's published-bytes ceiling
|
|
5049
5123
|
* (identical bytes count once). */
|
|
5050
|
-
usage: () => Promise<
|
|
5051
|
-
usage: {
|
|
5052
|
-
object_count: number;
|
|
5053
|
-
total_bytes: number;
|
|
5054
|
-
};
|
|
5055
|
-
quotas: {
|
|
5056
|
-
maxObjectCount?: number;
|
|
5057
|
-
maxObjectBytes?: number;
|
|
5058
|
-
maxTotalBytes?: number;
|
|
5059
|
-
};
|
|
5060
|
-
available: Record<string, number | null>;
|
|
5061
|
-
public_assets?: {
|
|
5062
|
-
enabled: boolean;
|
|
5063
|
-
published_objects: number;
|
|
5064
|
-
published_bytes: number;
|
|
5065
|
-
max_published_bytes: number;
|
|
5066
|
-
bytes_remaining: number;
|
|
5067
|
-
};
|
|
5068
|
-
}>;
|
|
5124
|
+
usage: () => Promise<FilesUsage>;
|
|
5069
5125
|
/** SERVER-ONLY (403 server_only in end-user mode). Publish an available
|
|
5070
5126
|
* object to vxil's public asset host: a stable, content-addressed URL
|
|
5071
5127
|
* (`https://cdn.vxil.app/<tenant>/<sha256>.<ext>`) served from a global
|
|
@@ -5125,7 +5181,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
5125
5181
|
error?: string | null;
|
|
5126
5182
|
}>;
|
|
5127
5183
|
/** Set / extend / clear an object's TTL. `expiresInSeconds: null` clears it
|
|
5128
|
-
* (else a future auto-delete after the given seconds;
|
|
5184
|
+
* (else a future auto-delete after the given seconds; 60 s up to 100
|
|
5185
|
+
* years). An object already past its expiry is a 404 — never revived. */
|
|
5129
5186
|
setTtl: (objectId: string, expiresInSeconds: number | null) => Promise<{
|
|
5130
5187
|
object_id: string;
|
|
5131
5188
|
expires_at: string | null;
|
package/dist/index.js
CHANGED
|
@@ -353,9 +353,20 @@ export class Vxil {
|
|
|
353
353
|
body: JSON.stringify(payload ?? {}),
|
|
354
354
|
});
|
|
355
355
|
if (!res.ok) {
|
|
356
|
+
// A function answers its OWN error body: the platform envelope
|
|
357
|
+
// `{ error: { code, message } }`, or a bare string code
|
|
358
|
+
// `{ error: 'role_required', message? }` (the blueprints' shape) —
|
|
359
|
+
// both keep their code (a string used to collapse to http_<status>).
|
|
356
360
|
let e = {};
|
|
357
361
|
try {
|
|
358
|
-
|
|
362
|
+
const b = JSON.parse(text);
|
|
363
|
+
if (typeof b.error === 'string') {
|
|
364
|
+
if (b.error)
|
|
365
|
+
e = { code: b.error, message: typeof b.message === 'string' && b.message ? b.message : b.error };
|
|
366
|
+
}
|
|
367
|
+
else if (b.error && typeof b.error === 'object') {
|
|
368
|
+
e = b.error;
|
|
369
|
+
}
|
|
359
370
|
}
|
|
360
371
|
catch { /* non-JSON error body */ }
|
|
361
372
|
throw new VxilError(res.status, e.code ?? `http_${res.status}`, e.message ?? text.slice(0, 200), undefined, undefined, undefined, parseRetryAfter(res.headers.get('retry-after')));
|
|
@@ -754,7 +765,10 @@ export class Vxil {
|
|
|
754
765
|
* FORCED to the verified end-user (a mismatched user_id → 400). A 402 aborts
|
|
755
766
|
* the enqueue (insufficient balance; the run ends with job.generation.failed
|
|
756
767
|
* `ReserveInsufficient` and the 402 names its `run_id`); a runaway over the
|
|
757
|
-
* per-tenant outstanding-holds ceiling → 429 (Retry-After 5).
|
|
768
|
+
* per-tenant outstanding-holds ceiling → 429 (Retry-After 5). Payments
|
|
769
|
+
* unreachable at the hold → 503 `payments_unavailable` (Retry-After 15;
|
|
770
|
+
* nothing started, nothing held — re-send the same request), unless
|
|
771
|
+
* `reserve_credits.on_unavailable: 'proceed'`.
|
|
758
772
|
*
|
|
759
773
|
* Generic completion options for queue-style providers (fal.ai and alike —
|
|
760
774
|
* no vendor adapter): `completion.callback.query_param` puts the signed
|
|
@@ -771,7 +785,8 @@ export class Vxil {
|
|
|
771
785
|
*
|
|
772
786
|
* `opts.retryOnCapacity: { maxWaitMs }` retries a CAPACITY 429
|
|
773
787
|
* (`generation_concurrency_exceeded`, `reserve_holds_exceeded`,
|
|
774
|
-
* `queue_full`) honouring Retry-After
|
|
788
|
+
* `queue_full`) and a 503 `payments_unavailable`, honouring Retry-After
|
|
789
|
+
* with jitter, with the SAME
|
|
775
790
|
* idempotency_key (one is generated when the input has none), until
|
|
776
791
|
* maxWaitMs has passed — then the last 429 is thrown. */
|
|
777
792
|
generation: async (input, opts) => {
|
|
@@ -1832,8 +1847,15 @@ export class Vxil {
|
|
|
1832
1847
|
},
|
|
1833
1848
|
};
|
|
1834
1849
|
files = {
|
|
1835
|
-
/** Mint a presigned PUT; upload the bytes yourself, then call complete().
|
|
1850
|
+
/** Mint a presigned PUT; upload the bytes yourself, then call complete().
|
|
1851
|
+
* `expiresInSeconds` (60 s up to 100 years) makes the OBJECT expire — it is deleted that
|
|
1852
|
+
* long after the mint, whether or not `files.ttl` is enabled; `null` =
|
|
1853
|
+
* never. The answer's `expires_in` is the UPLOAD URL's life in seconds;
|
|
1854
|
+
* `expires_at` is when the object expires (null = never). `user_id` is
|
|
1855
|
+
* required with a server key and omitted in end-user mode. */
|
|
1836
1856
|
createUploadUrl: async (input) => (await this.call('POST', '/v1/files/upload-url', input)).data,
|
|
1857
|
+
/** Confirm the PUT (bytes verified, object → available). Answers the
|
|
1858
|
+
* object's `expires_at` (null = never). */
|
|
1837
1859
|
complete: async (objectId) => (await this.call('POST', `/v1/files/${encodeURIComponent(objectId)}/complete`)).data,
|
|
1838
1860
|
downloadUrl: async (objectId) => (await this.call('GET', `/v1/files/${encodeURIComponent(objectId)}/download-url`)).data.download_url,
|
|
1839
1861
|
/** Mint up to 100 presigned GETs in ONE call (`GET /v1/files/download-urls`)
|
|
@@ -1857,6 +1879,9 @@ export class Vxil {
|
|
|
1857
1879
|
await this.call('DELETE', `/v1/files/${encodeURIComponent(objectId)}`);
|
|
1858
1880
|
},
|
|
1859
1881
|
/** Aggregate storage usage vs quotas (the FilesManager Storage panel).
|
|
1882
|
+
* `quotas.maxTotalBytes` is the quota ENFORCED now — the plan's ceiling
|
|
1883
|
+
* unless you set a lower `quotas.maxTotalBytes` (`storage_plan.source`
|
|
1884
|
+
* says which). An object past its expiry no longer counts.
|
|
1860
1885
|
* `public_assets` = published copies vs the plan's published-bytes ceiling
|
|
1861
1886
|
* (identical bytes count once). */
|
|
1862
1887
|
usage: async () => (await this.call('GET', '/v1/files/usage')).data,
|
|
@@ -1894,7 +1919,8 @@ export class Vxil {
|
|
|
1894
1919
|
/** Fetch the cached extraction (status: not_extracted|pending|available|failed). */
|
|
1895
1920
|
getText: async (objectId) => (await this.call('GET', `/v1/files/${encodeURIComponent(objectId)}/text`)).data,
|
|
1896
1921
|
/** Set / extend / clear an object's TTL. `expiresInSeconds: null` clears it
|
|
1897
|
-
* (else a future auto-delete after the given seconds;
|
|
1922
|
+
* (else a future auto-delete after the given seconds; 60 s up to 100
|
|
1923
|
+
* years). An object already past its expiry is a 404 — never revived. */
|
|
1898
1924
|
setTtl: async (objectId, expiresInSeconds) => (await this.call('PUT', `/v1/files/${encodeURIComponent(objectId)}/ttl`, { expiresInSeconds })).data,
|
|
1899
1925
|
/** SERVER-ONLY (403 server_only in end-user mode). Account merge: move
|
|
1900
1926
|
* every object `from_user_id` owns (every status, tombstones included) and
|
package/dist/retry.d.ts
CHANGED
|
@@ -50,6 +50,14 @@ export declare function createTransport(opts: TransportOptions): Transport;
|
|
|
50
50
|
* and outstanding-holds ceiling, and the queue admission cap). Any other 429
|
|
51
51
|
* (a rate limit, a plan quota) is not a capacity answer and is rethrown. */
|
|
52
52
|
export declare const CAPACITY_ERROR_CODES: ReadonlySet<string>;
|
|
53
|
+
/** The 503 answers retried the same way (M1, 2026-10-04): the generation
|
|
54
|
+
* lane could not reach payments to place its credit hold — nothing was
|
|
55
|
+
* started and nothing held, and the SAME request re-sent after Retry-After
|
|
56
|
+
* starts the run (the refused run released its idempotency key). */
|
|
57
|
+
export declare const UNAVAILABLE_ERROR_CODES: ReadonlySet<string>;
|
|
58
|
+
/** Is this thrown error one `retryOnCapacity` waits out? Duck-typed on
|
|
59
|
+
* `{ status, code }` (VxilError's fields). */
|
|
60
|
+
export declare function isCapacityRetryable(e: unknown): boolean;
|
|
53
61
|
/** `retryOnCapacity` options. */
|
|
54
62
|
export interface RetryOnCapacityOptions {
|
|
55
63
|
/** Give up (rethrow the last 429) once waiting again would pass this many
|
|
@@ -60,7 +68,8 @@ export interface RetryOnCapacityOptions {
|
|
|
60
68
|
now?: () => number;
|
|
61
69
|
}
|
|
62
70
|
/**
|
|
63
|
-
* Re-run `attempt` while it throws a CAPACITY 429 (`CAPACITY_ERROR_CODES`)
|
|
71
|
+
* Re-run `attempt` while it throws a CAPACITY 429 (`CAPACITY_ERROR_CODES`) or
|
|
72
|
+
* a 503 `payments_unavailable` (`UNAVAILABLE_ERROR_CODES`):
|
|
64
73
|
* wait the server's `Retry-After` (else 5 s) plus up to 20 % jitter (≤ 1 s,
|
|
65
74
|
* so many clients told the same second do not all return on it), and never
|
|
66
75
|
* past `maxWaitMs` since the first attempt — then the last 429 is rethrown.
|
package/dist/retry.js
CHANGED
|
@@ -162,10 +162,25 @@ export function createTransport(opts) {
|
|
|
162
162
|
export const CAPACITY_ERROR_CODES = new Set([
|
|
163
163
|
'generation_concurrency_exceeded', 'reserve_holds_exceeded', 'queue_full',
|
|
164
164
|
]);
|
|
165
|
+
/** The 503 answers retried the same way (M1, 2026-10-04): the generation
|
|
166
|
+
* lane could not reach payments to place its credit hold — nothing was
|
|
167
|
+
* started and nothing held, and the SAME request re-sent after Retry-After
|
|
168
|
+
* starts the run (the refused run released its idempotency key). */
|
|
169
|
+
export const UNAVAILABLE_ERROR_CODES = new Set(['payments_unavailable']);
|
|
170
|
+
/** Is this thrown error one `retryOnCapacity` waits out? Duck-typed on
|
|
171
|
+
* `{ status, code }` (VxilError's fields). */
|
|
172
|
+
export function isCapacityRetryable(e) {
|
|
173
|
+
const err = e;
|
|
174
|
+
if (!err || typeof err.code !== 'string')
|
|
175
|
+
return false;
|
|
176
|
+
return (err.status === 429 && CAPACITY_ERROR_CODES.has(err.code))
|
|
177
|
+
|| (err.status === 503 && UNAVAILABLE_ERROR_CODES.has(err.code));
|
|
178
|
+
}
|
|
165
179
|
/** Fallback wait (ms) when a capacity 429 carries no Retry-After. */
|
|
166
180
|
const CAPACITY_DEFAULT_WAIT_MS = 5_000;
|
|
167
181
|
/**
|
|
168
|
-
* Re-run `attempt` while it throws a CAPACITY 429 (`CAPACITY_ERROR_CODES`)
|
|
182
|
+
* Re-run `attempt` while it throws a CAPACITY 429 (`CAPACITY_ERROR_CODES`) or
|
|
183
|
+
* a 503 `payments_unavailable` (`UNAVAILABLE_ERROR_CODES`):
|
|
169
184
|
* wait the server's `Retry-After` (else 5 s) plus up to 20 % jitter (≤ 1 s,
|
|
170
185
|
* so many clients told the same second do not all return on it), and never
|
|
171
186
|
* past `maxWaitMs` since the first attempt — then the last 429 is rethrown.
|
|
@@ -184,7 +199,7 @@ export async function retryOnCapacity(attempt, opts) {
|
|
|
184
199
|
}
|
|
185
200
|
catch (e) {
|
|
186
201
|
const err = e;
|
|
187
|
-
if (!err ||
|
|
202
|
+
if (!err || !isCapacityRetryable(err))
|
|
188
203
|
throw e;
|
|
189
204
|
const baseMs = typeof err.retryAfter === 'number' && err.retryAfter >= 0
|
|
190
205
|
? Math.round(err.retryAfter * 1000)
|
package/package.json
CHANGED