cutsio 0.2.4 → 0.2.5

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 (2) hide show
  1. package/dist/index.js +34 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -137,6 +137,18 @@ var ApiError = class extends Error {
137
137
  this.status = status;
138
138
  }
139
139
  };
140
+ var MAX_REQUEST_RETRIES = 3;
141
+ var BASE_RETRY_DELAY_MS = 1e3;
142
+ var MAX_RETRY_DELAY_MS = 3e4;
143
+ function retryDelayMs(attempt, retryAfter) {
144
+ const headerSeconds = retryAfter ? Number(retryAfter) : Number.NaN;
145
+ const headerMs = Number.isFinite(headerSeconds) && headerSeconds >= 0 ? headerSeconds * 1e3 : null;
146
+ const backoffMs = Math.min(BASE_RETRY_DELAY_MS * 2 ** attempt, MAX_RETRY_DELAY_MS);
147
+ return Math.min(headerMs ?? backoffMs, MAX_RETRY_DELAY_MS) + Math.random() * 500;
148
+ }
149
+ function sleep(ms) {
150
+ return new Promise((resolve) => setTimeout(resolve, ms));
151
+ }
140
152
  var CutsioClient = class {
141
153
  base;
142
154
  apiKey;
@@ -181,17 +193,21 @@ var CutsioClient = class {
181
193
  ...init?.body ? { "Content-Type": "application/json" } : {},
182
194
  ...init?.headers ?? {}
183
195
  };
184
- const response = await fetch(`${this.base}${path}`, { ...init, headers });
185
- if (!response.ok) {
186
- let message = `HTTP ${response.status}`;
187
- try {
188
- const body = await response.json();
189
- if (typeof body.error === "string") message = body.error;
190
- } catch {
196
+ for (let attempt = 0; ; attempt += 1) {
197
+ const response = await fetch(`${this.base}${path}`, { ...init, headers });
198
+ if (response.ok) return response.json();
199
+ const retryable = response.status === 429 || response.status === 502 || response.status === 503 || response.status === 504;
200
+ if (!retryable || attempt >= MAX_REQUEST_RETRIES) {
201
+ let message = `HTTP ${response.status}`;
202
+ try {
203
+ const body = await response.json();
204
+ if (typeof body.error === "string") message = body.error;
205
+ } catch {
206
+ }
207
+ throw new ApiError(message, response.status);
191
208
  }
192
- throw new ApiError(message, response.status);
209
+ await sleep(retryDelayMs(attempt, response.headers.get("retry-after")));
193
210
  }
194
- return response.json();
195
211
  }
196
212
  };
197
213
 
@@ -751,12 +767,16 @@ async function sync(client, options) {
751
767
  while (queue.size > 0) {
752
768
  const ready = [];
753
769
  const ids = [...queue.keys()];
770
+ let serverDelayMs = 0;
754
771
  for (let index = 0; index < ids.length; index += PREPARE_BATCH_SIZE) {
755
772
  const batch = ids.slice(index, index + PREPARE_BATCH_SIZE);
756
773
  const results = await client.prepare(batch);
757
774
  for (const result of results) {
758
775
  const item = queue.get(result.id);
759
776
  if (!item) continue;
777
+ if (typeof result.retryAfterSeconds === "number" && result.retryAfterSeconds > 0) {
778
+ serverDelayMs = Math.max(serverDelayMs, result.retryAfterSeconds * 1e3);
779
+ }
760
780
  switch (result.status) {
761
781
  case "ready":
762
782
  if (result.url) {
@@ -850,7 +870,7 @@ async function sync(client, options) {
850
870
  }
851
871
  break;
852
872
  }
853
- await sleep(prepareDelayMs);
873
+ await sleep2(Math.min(Math.max(serverDelayMs, prepareDelayMs), MAX_PREPARE_DELAY_MS));
854
874
  prepareDelayMs = Math.min(prepareDelayMs * 2, MAX_PREPARE_DELAY_MS);
855
875
  }
856
876
  return { downloaded, unchanged, skipped, failed };
@@ -870,7 +890,7 @@ async function runPool(items, concurrency, worker) {
870
890
  })
871
891
  );
872
892
  }
873
- function sleep(ms) {
893
+ function sleep2(ms) {
874
894
  return new Promise((resolve) => setTimeout(resolve, ms));
875
895
  }
876
896
 
@@ -903,7 +923,7 @@ var color = {
903
923
  };
904
924
  var isInteractive = colorEnabled;
905
925
  var animationsEnabled = colorEnabled && process.env.CUTSIO_NO_ANIM !== "1";
906
- function sleep2(ms) {
926
+ function sleep3(ms) {
907
927
  return new Promise((resolve) => setTimeout(resolve, ms));
908
928
  }
909
929
 
@@ -1253,12 +1273,12 @@ async function revealBanner() {
1253
1273
  for (let index = 0; index < WORDMARK.length; index += 1) {
1254
1274
  process.stdout.write(`${shade(WORDMARK[index] ?? "", index)}
1255
1275
  `);
1256
- if (animationsEnabled) await sleep2(38);
1276
+ if (animationsEnabled) await sleep3(38);
1257
1277
  }
1258
1278
  process.stdout.write(`${color.dim(" mezzanine sync for your Mac")}
1259
1279
 
1260
1280
  `);
1261
- if (animationsEnabled) await sleep2(60);
1281
+ if (animationsEnabled) await sleep3(60);
1262
1282
  }
1263
1283
 
1264
1284
  // src/commands/welcome.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cutsio",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Sync Cutsio mezzanine masters to a local folder",
5
5
  "homepage": "https://cutsio.com",
6
6
  "type": "module",