@thatopen/services 0.9.1 → 0.11.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/cli.js CHANGED
@@ -6795,6 +6795,7 @@ Object.assign(lookup, {
6795
6795
  io: lookup,
6796
6796
  connect: lookup
6797
6797
  });
6798
+ const STORAGE_BATCH_MAX = 100;
6798
6799
  function parseErrorBody(body) {
6799
6800
  try {
6800
6801
  const json = JSON.parse(body);
@@ -6810,12 +6811,22 @@ function parseErrorBody(body) {
6810
6811
  }
6811
6812
  return {};
6812
6813
  }
6814
+ function retryAfterFromDetails(details) {
6815
+ if (!details || typeof details !== "object") return void 0;
6816
+ const value2 = details.retryAfter;
6817
+ return typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0 ? value2 : void 0;
6818
+ }
6813
6819
  class RequestError extends Error {
6814
6820
  status;
6815
6821
  code;
6816
6822
  details;
6817
6823
  body;
6818
- constructor(status, statusText, body) {
6824
+ /**
6825
+ * Seconds to wait before retrying, taken from the `Retry-After` header or
6826
+ * from `details.retryAfter`. Only present on rate-limited (429) responses.
6827
+ */
6828
+ retryAfter;
6829
+ constructor(status, statusText, body, retryAfter) {
6819
6830
  const parsed = parseErrorBody(body);
6820
6831
  super(parsed.message ?? `${statusText || "Request failed"} (${status})`);
6821
6832
  this.name = "RequestError";
@@ -6823,6 +6834,7 @@ class RequestError extends Error {
6823
6834
  this.code = parsed.code;
6824
6835
  this.details = parsed.details;
6825
6836
  this.body = body;
6837
+ this.retryAfter = retryAfter ?? retryAfterFromDetails(parsed.details);
6826
6838
  }
6827
6839
  }
6828
6840
  const FOLDER_PATH = "item/folder";
@@ -6833,6 +6845,35 @@ const HIDDEN_PATH = "hidden";
6833
6845
  const ITEM_TYPE_FILE = "FILE";
6834
6846
  const ITEM_TYPE_COMPONENT = "TOOL";
6835
6847
  const ITEM_TYPE_APP = "APP";
6848
+ const RETRY_BASE_DELAY_MS = 500;
6849
+ const RETRY_MAX_DELAY_MS = 3e4;
6850
+ const RETRY_JITTER_RATIO = 0.25;
6851
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6852
+ function parseRetryAfter(response) {
6853
+ const raw = response.headers?.get?.("Retry-After");
6854
+ if (!raw) return void 0;
6855
+ const seconds = Number(raw);
6856
+ if (Number.isFinite(seconds)) return Math.max(0, seconds);
6857
+ const timestamp = Date.parse(raw);
6858
+ if (Number.isNaN(timestamp)) return void 0;
6859
+ return Math.max(0, (timestamp - Date.now()) / 1e3);
6860
+ }
6861
+ function isRetryable(error2) {
6862
+ if (!(error2 instanceof RequestError)) return true;
6863
+ return error2.status === 429 || error2.status >= 500;
6864
+ }
6865
+ function retryDelayMs(error2, attempt) {
6866
+ const backoff = Math.min(
6867
+ RETRY_BASE_DELAY_MS * 2 ** attempt,
6868
+ RETRY_MAX_DELAY_MS
6869
+ );
6870
+ const jitter = backoff * RETRY_JITTER_RATIO * Math.random();
6871
+ const serverDelay = error2 instanceof RequestError && error2.retryAfter != null ? error2.retryAfter * 1e3 : void 0;
6872
+ if (serverDelay != null) {
6873
+ return Math.min(serverDelay, RETRY_MAX_DELAY_MS) + jitter;
6874
+ }
6875
+ return backoff + jitter;
6876
+ }
6836
6877
  class EngineServicesClient {
6837
6878
  apiUrl;
6838
6879
  accessToken;
@@ -6905,7 +6946,9 @@ class EngineServicesClient {
6905
6946
  }
6906
6947
  }
6907
6948
  /**
6908
- * Sets the number of automatic retries for failed requests.
6949
+ * Sets the number of automatic retries for failed requests. Retries use
6950
+ * exponential backoff with jitter and honour `Retry-After`; only network
6951
+ * failures, 429s and 5xx are retried.
6909
6952
  * @param retries - Number of retries (0 = no retries).
6910
6953
  */
6911
6954
  setRetries(retries) {
@@ -6965,7 +7008,7 @@ class EngineServicesClient {
6965
7008
  return this.accessToken;
6966
7009
  }
6967
7010
  async #requestApi(method, path2, requestData) {
6968
- const { body, query, contentType, retries, responseType } = requestData || {};
7011
+ const { body, query, contentType, retries, responseType, retryAttempt } = requestData || {};
6969
7012
  const url2 = this.#buildUrl(path2);
6970
7013
  const cleanQuery = this.#cleanData(query);
6971
7014
  const token = await this.resolveAccessToken();
@@ -6991,7 +7034,8 @@ class EngineServicesClient {
6991
7034
  throw new RequestError(
6992
7035
  response.status,
6993
7036
  response.statusText,
6994
- textResponse
7037
+ textResponse,
7038
+ parseRetryAfter(response)
6995
7039
  );
6996
7040
  }
6997
7041
  if (responseType === "blob") {
@@ -6999,16 +7043,17 @@ class EngineServicesClient {
6999
7043
  }
7000
7044
  return response.json().then((data) => data).catch(() => void 0);
7001
7045
  } catch (e) {
7002
- let retriesAmmount = retries != null ? retries : this.retries;
7003
- if (retriesAmmount) {
7004
- retriesAmmount = retriesAmmount - 1;
7046
+ const retriesLeft = retries != null ? retries : this.retries;
7047
+ if (retriesLeft > 0 && isRetryable(e)) {
7048
+ const attempt = retryAttempt ?? 0;
7049
+ await sleep(retryDelayMs(e, attempt));
7005
7050
  return await this.#requestApi(method, path2, {
7006
7051
  ...requestData,
7007
- retries: retriesAmmount
7052
+ retries: retriesLeft - 1,
7053
+ retryAttempt: attempt + 1
7008
7054
  });
7009
- } else {
7010
- throw e;
7011
7055
  }
7056
+ throw e;
7012
7057
  }
7013
7058
  }
7014
7059
  /**
@@ -7161,6 +7206,36 @@ class EngineServicesClient {
7161
7206
  `${ITEM_PATH}/${encodeURIComponent(fileId)}/version/${encodeURIComponent(versionTag)}/metadata`
7162
7207
  );
7163
7208
  }
7209
+ /**
7210
+ * Batch variant of {@link getFileVersionMetadata}. Reads the metadata of
7211
+ * many (file, version) pairs in one request instead of one request per pair,
7212
+ * which is what a file list needs when it hydrates a whole page at once.
7213
+ *
7214
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
7215
+ * requests automatically. Entries come back in the order they were asked
7216
+ * for; a pair the caller cannot read carries an `error` instead of
7217
+ * `metadata`, so one bad id does not lose the whole page.
7218
+ *
7219
+ * @param entries - The `{ itemId, versionTag }` pairs to read.
7220
+ * @param params - Optional flags such as `withDraft`.
7221
+ */
7222
+ async getFileVersionMetadataBatch(entries, params) {
7223
+ const { withDraft } = params || {};
7224
+ return await this.#requestBatches(
7225
+ entries,
7226
+ async (chunk) => (await this.#requestApi(
7227
+ "POST",
7228
+ `${ITEM_PATH}/batch/version-metadata`,
7229
+ {
7230
+ body: JSON.stringify({
7231
+ entries: chunk,
7232
+ ...withDraft !== void 0 && { withDraft }
7233
+ }),
7234
+ contentType: "application/json"
7235
+ }
7236
+ )).results
7237
+ );
7238
+ }
7164
7239
  // ─── Folders ─────────────────────────────────────────────────────
7165
7240
  /**
7166
7241
  * Lists all folders accessible by the current token.
@@ -7187,6 +7262,32 @@ class EngineServicesClient {
7187
7262
  `${FOLDER_PATH}/${folderId}`
7188
7263
  );
7189
7264
  }
7265
+ /**
7266
+ * Batch variant of {@link getFolder}. Resolves a known set of folder ids in
7267
+ * one request — for example the parents behind a file list's breadcrumbs.
7268
+ *
7269
+ * Reach for {@link listFolders} instead when the whole (project) tree is
7270
+ * wanted; this is for the case where the ids are already known.
7271
+ *
7272
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
7273
+ * requests automatically. A folder the caller cannot read carries an
7274
+ * `error` instead of `folder`.
7275
+ *
7276
+ * @param folderIds - The folder ids to resolve.
7277
+ */
7278
+ async getFoldersBatch(folderIds) {
7279
+ return await this.#requestBatches(
7280
+ folderIds,
7281
+ async (chunk) => (await this.#requestApi(
7282
+ "POST",
7283
+ `${ITEM_PATH}/batch/folders`,
7284
+ {
7285
+ body: JSON.stringify({ folderIds: chunk }),
7286
+ contentType: "application/json"
7287
+ }
7288
+ )).results
7289
+ );
7290
+ }
7190
7291
  /**
7191
7292
  * Creates a new folder.
7192
7293
  * @param name - Display name for the folder.
@@ -7529,15 +7630,12 @@ return main;`);
7529
7630
  */
7530
7631
  async downloadAppBundle(appId, params) {
7531
7632
  const { versionTag, withDraft } = params || {};
7532
- return await this.#requestFile(
7533
- `${ITEM_PATH}/${appId}/download/bundle`,
7534
- {
7535
- query: {
7536
- ...versionTag && { versionTag },
7537
- ...withDraft && { withDraft }
7538
- }
7633
+ return await this.#requestFile(`${ITEM_PATH}/${appId}/download/bundle`, {
7634
+ query: {
7635
+ ...versionTag && { versionTag },
7636
+ ...withDraft && { withDraft }
7539
7637
  }
7540
- );
7638
+ });
7541
7639
  }
7542
7640
  /**
7543
7641
  * Archives (soft-deletes) an app.
@@ -7586,7 +7684,9 @@ return main;`);
7586
7684
  });
7587
7685
  if (!response.ok) {
7588
7686
  const text = await response.text().catch(() => "");
7589
- throw new Error(`Local server request failed: ${response.status} - ${text}`);
7687
+ throw new Error(
7688
+ `Local server request failed: ${response.status} - ${text}`
7689
+ );
7590
7690
  }
7591
7691
  return await response.json();
7592
7692
  }
@@ -7610,7 +7710,9 @@ return main;`);
7610
7710
  const response = await fetch(url2, { method: "POST" });
7611
7711
  if (!response.ok) {
7612
7712
  const text = await response.text().catch(() => "");
7613
- throw new Error(`Local server request failed: ${response.status} - ${text}`);
7713
+ throw new Error(
7714
+ `Local server request failed: ${response.status} - ${text}`
7715
+ );
7614
7716
  }
7615
7717
  return await response.json();
7616
7718
  }
@@ -7662,7 +7764,9 @@ return main;`);
7662
7764
  const response = await fetch(url2);
7663
7765
  if (!response.ok) {
7664
7766
  const text = await response.text().catch(() => "");
7665
- throw new Error(`Local server request failed: ${response.status} - ${text}`);
7767
+ throw new Error(
7768
+ `Local server request failed: ${response.status} - ${text}`
7769
+ );
7666
7770
  }
7667
7771
  return await response.json();
7668
7772
  }
@@ -7786,6 +7890,40 @@ return main;`);
7786
7890
  { ...expiresIn != null && { query: { expiresIn } } }
7787
7891
  );
7788
7892
  }
7893
+ /**
7894
+ * Batch variant of {@link getHiddenFileSignedUrl}. Mints presigned URLs for
7895
+ * many hidden files in one request.
7896
+ *
7897
+ * This is the right call for tile-based viewers (splats, point clouds),
7898
+ * which mint a URL per piece as the camera moves. One request here replaces
7899
+ * up to {@link STORAGE_BATCH_MAX} single calls, which keeps a busy session
7900
+ * well under the per-endpoint rate limit.
7901
+ *
7902
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
7903
+ * requests automatically. Entries come back in request order; a file that
7904
+ * is gone or not accessible carries an `error` instead of a `url`, so the
7905
+ * remaining tiles still load. Re-call to re-mint as `expiresAt` approaches.
7906
+ *
7907
+ * @param hiddenIds - The hidden files to sign.
7908
+ * @param expiresIn - Desired URL lifetime in seconds (60–3600). Defaults to
7909
+ * 900 (15 min) server-side; values are clamped to that range.
7910
+ */
7911
+ async getHiddenFileSignedUrlsBatch(hiddenIds, expiresIn) {
7912
+ return await this.#requestBatches(
7913
+ hiddenIds,
7914
+ async (chunk) => (await this.#requestApi(
7915
+ "POST",
7916
+ `${ITEM_PATH}/${HIDDEN_PATH}/signed-url/batch`,
7917
+ {
7918
+ body: JSON.stringify({
7919
+ hiddenFileIds: chunk,
7920
+ ...expiresIn != null && { expiresIn }
7921
+ }),
7922
+ contentType: "application/json"
7923
+ }
7924
+ )).results
7925
+ );
7926
+ }
7789
7927
  /**
7790
7928
  * Lists all hidden files attached to a parent item.
7791
7929
  * @param parentFileId - The parent item's unique identifier.
@@ -7818,11 +7956,9 @@ return main;`);
7818
7956
  async uploadItemIcon(itemId, icon) {
7819
7957
  const formData = new FormData();
7820
7958
  formData.append("icon", icon);
7821
- return await this.#requestApi(
7822
- "PUT",
7823
- `${ITEM_PATH}/${itemId}/icon`,
7824
- { body: formData }
7825
- );
7959
+ return await this.#requestApi("PUT", `${ITEM_PATH}/${itemId}/icon`, {
7960
+ body: formData
7961
+ });
7826
7962
  }
7827
7963
  /**
7828
7964
  * Downloads the icon for an item as a binary stream.
@@ -7892,6 +8028,38 @@ return main;`);
7892
8028
  { query: params }
7893
8029
  );
7894
8030
  }
8031
+ /**
8032
+ * Batch variant of {@link listVersions}. Lists the versions of many items in
8033
+ * one request, which is what a file panel needs to render a whole page.
8034
+ *
8035
+ * The returned versions are the full records, metadata included, so a list
8036
+ * that only needs version metadata does not need
8037
+ * {@link getFileVersionMetadataBatch} on top.
8038
+ *
8039
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
8040
+ * requests automatically. An item the caller cannot read carries an `error`
8041
+ * instead of `versions`.
8042
+ *
8043
+ * @param itemIds - The items whose versions to list.
8044
+ * @param params - Optional `{ archived }` filter, applied to every item.
8045
+ */
8046
+ async listVersionsBatch(itemIds, params = {}) {
8047
+ const { archived } = params;
8048
+ return await this.#requestBatches(
8049
+ itemIds,
8050
+ async (chunk) => (await this.#requestApi(
8051
+ "POST",
8052
+ `${ITEM_PATH}/batch/versions`,
8053
+ {
8054
+ body: JSON.stringify({
8055
+ itemIds: chunk,
8056
+ ...archived !== void 0 && { archived }
8057
+ }),
8058
+ contentType: "application/json"
8059
+ }
8060
+ )).results
8061
+ );
8062
+ }
7895
8063
  /**
7896
8064
  * Archives a version of an item. Archived versions remain available via
7897
8065
  * `listVersions({ archived: true })` and can be recovered or permanently
@@ -7943,6 +8111,21 @@ return main;`);
7943
8111
  // checkPermission, checkPermissionBatch) live on `PlatformClient` — they
7944
8112
  // cannot be called with an access token.
7945
8113
  // ─── Private Helpers ─────────────────────────────────────────────
8114
+ /**
8115
+ * Splits `inputs` into chunks the API accepts and concatenates the results,
8116
+ * so callers never have to think about the batch ceiling. Chunks go out
8117
+ * together; an empty input skips the network entirely, since the API
8118
+ * rejects an empty batch.
8119
+ */
8120
+ async #requestBatches(inputs, requestChunk) {
8121
+ if (!inputs.length) return [];
8122
+ const chunks = [];
8123
+ for (let start = 0; start < inputs.length; start += STORAGE_BATCH_MAX) {
8124
+ chunks.push(inputs.slice(start, start + STORAGE_BATCH_MAX));
8125
+ }
8126
+ const responses = await Promise.all(chunks.map(requestChunk));
8127
+ return responses.flat();
8128
+ }
7946
8129
  async #downloadItem(itemId, params) {
7947
8130
  const { versionTag, withDraft } = params || {};
7948
8131
  return await this.#requestFile(`${ITEM_PATH}/${itemId}/download`, {
@@ -2,6 +2,7 @@ import { ExecutionEntity, ExecutionSuscriptionReturnType } from '../types/execut
2
2
  import { AppVersionProps, ComponentVersionProps, ComponentItem, AppItem, Item, ItemFolder, ItemVersion, ItemWithVersions } from '../types/items';
3
3
  import { CreateItemResponse, UpdateItemResponse } from '../types/response';
4
4
  import { CreateHiddenItemResult, CreateHiddenItemsBatchResult, HiddenFileEntity, Metadata } from '../types/files';
5
+ import { HiddenFileSignedUrlBatchEntry, ItemFoldersBatchEntry, ItemVersionsBatchEntry, VersionMetadataBatchEntry, VersionMetadataBatchRequest } from '../types/batch';
5
6
  import { ThatOpenContext } from '../types/context';
6
7
  import { NpmCredentials } from '../types/npm';
7
8
 
@@ -83,7 +84,13 @@ export type DownloadItemFileParams = {
83
84
  };
84
85
  /** Configuration options for the {@link EngineServicesClient} constructor. */
85
86
  export type EngineServicesClientProps = {
86
- /** Number of automatic retries on request failure. Default: 0. */
87
+ /**
88
+ * Number of automatic retries on request failure. Default: 0.
89
+ *
90
+ * Retries are spaced with exponential backoff plus jitter, and honour the
91
+ * server's `Retry-After` when it sends one. Only network failures, 429s and
92
+ * 5xx are retried — other 4xx fail immediately.
93
+ */
87
94
  retries?: number;
88
95
  /**
89
96
  * If true, sends the token as an `Authorization: Bearer` header instead of
@@ -166,7 +173,9 @@ export declare class EngineServicesClient {
166
173
  */
167
174
  constructor(accessToken: string, apiUrl: string, props?: EngineServicesClientProps);
168
175
  /**
169
- * Sets the number of automatic retries for failed requests.
176
+ * Sets the number of automatic retries for failed requests. Retries use
177
+ * exponential backoff with jitter and honour `Retry-After`; only network
178
+ * failures, 429s and 5xx are retried.
170
179
  * @param retries - Number of retries (0 = no retries).
171
180
  */
172
181
  setRetries(retries: number): void;
@@ -305,6 +314,22 @@ export declare class EngineServicesClient {
305
314
  deleteFileVersionMetadata(fileId: string, versionTag: string): Promise<{
306
315
  success: boolean;
307
316
  }>;
317
+ /**
318
+ * Batch variant of {@link getFileVersionMetadata}. Reads the metadata of
319
+ * many (file, version) pairs in one request instead of one request per pair,
320
+ * which is what a file list needs when it hydrates a whole page at once.
321
+ *
322
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
323
+ * requests automatically. Entries come back in the order they were asked
324
+ * for; a pair the caller cannot read carries an `error` instead of
325
+ * `metadata`, so one bad id does not lose the whole page.
326
+ *
327
+ * @param entries - The `{ itemId, versionTag }` pairs to read.
328
+ * @param params - Optional flags such as `withDraft`.
329
+ */
330
+ getFileVersionMetadataBatch(entries: VersionMetadataBatchRequest[], params?: {
331
+ withDraft?: boolean;
332
+ }): Promise<VersionMetadataBatchEntry[]>;
308
333
  /**
309
334
  * Lists all folders accessible by the current token.
310
335
  * @param params - Optional filters for parent folder and archive status.
@@ -324,6 +349,20 @@ export declare class EngineServicesClient {
324
349
  * @param folderId - The folder's unique identifier.
325
350
  */
326
351
  getFolder(folderId: string): Promise<ItemFolder>;
352
+ /**
353
+ * Batch variant of {@link getFolder}. Resolves a known set of folder ids in
354
+ * one request — for example the parents behind a file list's breadcrumbs.
355
+ *
356
+ * Reach for {@link listFolders} instead when the whole (project) tree is
357
+ * wanted; this is for the case where the ids are already known.
358
+ *
359
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
360
+ * requests automatically. A folder the caller cannot read carries an
361
+ * `error` instead of `folder`.
362
+ *
363
+ * @param folderIds - The folder ids to resolve.
364
+ */
365
+ getFoldersBatch(folderIds: string[]): Promise<ItemFoldersBatchEntry[]>;
327
366
  /**
328
367
  * Creates a new folder.
329
368
  * @param name - Display name for the folder.
@@ -638,6 +677,25 @@ export declare class EngineServicesClient {
638
677
  url: string;
639
678
  expiresAt: string;
640
679
  }>;
680
+ /**
681
+ * Batch variant of {@link getHiddenFileSignedUrl}. Mints presigned URLs for
682
+ * many hidden files in one request.
683
+ *
684
+ * This is the right call for tile-based viewers (splats, point clouds),
685
+ * which mint a URL per piece as the camera moves. One request here replaces
686
+ * up to {@link STORAGE_BATCH_MAX} single calls, which keeps a busy session
687
+ * well under the per-endpoint rate limit.
688
+ *
689
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
690
+ * requests automatically. Entries come back in request order; a file that
691
+ * is gone or not accessible carries an `error` instead of a `url`, so the
692
+ * remaining tiles still load. Re-call to re-mint as `expiresAt` approaches.
693
+ *
694
+ * @param hiddenIds - The hidden files to sign.
695
+ * @param expiresIn - Desired URL lifetime in seconds (60–3600). Defaults to
696
+ * 900 (15 min) server-side; values are clamped to that range.
697
+ */
698
+ getHiddenFileSignedUrlsBatch(hiddenIds: string[], expiresIn?: number): Promise<HiddenFileSignedUrlBatchEntry[]>;
641
699
  /**
642
700
  * Lists all hidden files attached to a parent item.
643
701
  * @param parentFileId - The parent item's unique identifier.
@@ -700,6 +758,24 @@ export declare class EngineServicesClient {
700
758
  listVersions(itemId: string, params?: {
701
759
  archived?: boolean;
702
760
  }): Promise<ItemVersion[]>;
761
+ /**
762
+ * Batch variant of {@link listVersions}. Lists the versions of many items in
763
+ * one request, which is what a file panel needs to render a whole page.
764
+ *
765
+ * The returned versions are the full records, metadata included, so a list
766
+ * that only needs version metadata does not need
767
+ * {@link getFileVersionMetadataBatch} on top.
768
+ *
769
+ * Inputs longer than {@link STORAGE_BATCH_MAX} are split into several
770
+ * requests automatically. An item the caller cannot read carries an `error`
771
+ * instead of `versions`.
772
+ *
773
+ * @param itemIds - The items whose versions to list.
774
+ * @param params - Optional `{ archived }` filter, applied to every item.
775
+ */
776
+ listVersionsBatch(itemIds: string[], params?: {
777
+ archived?: boolean;
778
+ }): Promise<ItemVersionsBatchEntry[]>;
703
779
  /**
704
780
  * Archives a version of an item. Archived versions remain available via
705
781
  * `listVersions({ archived: true })` and can be recovered or permanently
@@ -0,0 +1 @@
1
+ export {};
@@ -15,11 +15,27 @@
15
15
  * }
16
16
  * }
17
17
  * ```
18
+ *
19
+ * @example Rate limiting
20
+ * ```ts
21
+ * catch (err) {
22
+ * if (err instanceof RequestError && err.status === 429) {
23
+ * console.log(err.code); // "RATE_LIMITED"
24
+ * console.log(err.details); // { limit, windowSeconds, retryAfter, scope }
25
+ * console.log(err.retryAfter); // seconds to wait before trying again
26
+ * }
27
+ * }
28
+ * ```
18
29
  */
19
30
  export declare class RequestError extends Error {
20
31
  readonly status: number;
21
32
  readonly code?: string;
22
33
  readonly details?: unknown;
23
34
  readonly body: string;
24
- constructor(status: number, statusText: string, body: string);
35
+ /**
36
+ * Seconds to wait before retrying, taken from the `Retry-After` header or
37
+ * from `details.retryAfter`. Only present on rate-limited (429) responses.
38
+ */
39
+ readonly retryAfter?: number;
40
+ constructor(status: number, statusText: string, body: string, retryAfter?: number);
25
41
  }