Implement custom retry handling of rate limit errors
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { Printful } from "./util/types";
|
||||
import { type DeepPartial, type PagingOptions } from "./util/misc";
|
||||
import { RateLimitError, parseRetryAfterMs } from "@blade-and-brawn/domain";
|
||||
|
||||
const parsePayload = (res: Response): Promise<unknown> =>
|
||||
res.json().catch(() => res.text().catch(() => undefined));
|
||||
@@ -26,6 +27,12 @@ export class PrintfulError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
class PrintfulRateLimitError extends RateLimitError {
|
||||
constructor(retryAfterHeader: string | null, payload: unknown) {
|
||||
super("Printful", parseRetryAfterMs(retryAfterHeader, 60_000), payload);
|
||||
}
|
||||
}
|
||||
|
||||
class VariantsClient {
|
||||
constructor(private creds: Credentials) { }
|
||||
|
||||
@@ -34,6 +41,7 @@ class VariantsClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeaders,
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (res.status === 404 || res.status === 400) return;
|
||||
if (!res.ok) throw new PrintfulError("Failed to get Printful variant", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Printful.MetaDataSingle<Printful.Products.SyncVariant>;
|
||||
@@ -56,6 +64,7 @@ class ProductsClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeaders,
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new PrintfulError("Failed to list Printful products", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Printful.MetaDataMulti<Printful.Products.SyncProduct>;
|
||||
allSyncProducts.push(...payload.result);
|
||||
@@ -70,6 +79,7 @@ class ProductsClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeaders,
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (res.status === 404 || res.status === 400) return;
|
||||
if (!res.ok) throw new PrintfulError("Failed to get Printful product", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Printful.MetaDataSingle<Printful.Products.Product>;
|
||||
@@ -85,6 +95,7 @@ class ProductsClient {
|
||||
},
|
||||
body: JSON.stringify(pProduct),
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new PrintfulError("Failed to update Printful product", res.status, await parsePayload(res));
|
||||
}
|
||||
}
|
||||
@@ -101,6 +112,7 @@ class OrdersClient {
|
||||
},
|
||||
body: JSON.stringify(pOrder),
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new PrintfulError("Failed to create Printful order", res.status, await parsePayload(res));
|
||||
}
|
||||
|
||||
@@ -112,6 +124,7 @@ class OrdersClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeaders,
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new PrintfulError("Failed to list Printful orders", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Printful.MetaDataMulti<Printful.Orders.Order>;
|
||||
allPOrders.push(...payload.result);
|
||||
@@ -133,6 +146,7 @@ class WebhooksClient {
|
||||
...this.creds.authHeaders,
|
||||
}
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (res.status === 404 || res.status === 400) return;
|
||||
if (!res.ok) throw new PrintfulError("Failed to get webhook configuration", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Printful.Webhook.Config;
|
||||
@@ -155,6 +169,7 @@ class WebhooksClient {
|
||||
"types": events
|
||||
}),
|
||||
});
|
||||
if (res.status === 429) throw new PrintfulRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new PrintfulError("Failed to register webhook URL", res.status, await parsePayload(res));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Webflow } from "./util/types";
|
||||
import { type DeepPartial, type PagingOptions } from "./util/misc";
|
||||
import crypto from "node:crypto";
|
||||
import { RateLimitError, parseRetryAfterMs } from "@blade-and-brawn/domain";
|
||||
|
||||
const parsePayload = (res: Response): Promise<unknown> =>
|
||||
res.json().catch(() => res.text().catch(() => undefined));
|
||||
@@ -31,6 +32,12 @@ export class WebflowError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
class WebflowRateLimitError extends RateLimitError {
|
||||
constructor(retryAfterHeader: string | null, payload: unknown) {
|
||||
super("Webflow", parseRetryAfterMs(retryAfterHeader, 60_000), payload);
|
||||
}
|
||||
}
|
||||
|
||||
class SkusClient {
|
||||
constructor(private creds: Credentials) { }
|
||||
|
||||
@@ -49,6 +56,7 @@ class SkusClient {
|
||||
body: JSON.stringify({ skus }),
|
||||
},
|
||||
);
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to create Webflow product SKU", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as { skus: { id: string }[] };
|
||||
return payload.skus.map((sku) => sku.id);
|
||||
@@ -70,6 +78,7 @@ class SkusClient {
|
||||
body: JSON.stringify({ sku: wSku }),
|
||||
},
|
||||
);
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to update Webflow product SKU", res.status, await parsePayload(res));
|
||||
}
|
||||
}
|
||||
@@ -92,6 +101,7 @@ class ProductsClient {
|
||||
},
|
||||
body: JSON.stringify(wProductAndSku),
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to create Webflow product", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as { product: { id: string } };
|
||||
return payload.product.id;
|
||||
@@ -105,6 +115,7 @@ class ProductsClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeader,
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to list Webflow products", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Webflow.MetaDataMulti<"items", Webflow.Products.ProductAndSkus>;
|
||||
allWProducts.push(...payload.items);
|
||||
@@ -119,6 +130,7 @@ class ProductsClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeader,
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (res.status === 404 || res.status === 400) return;
|
||||
if (!res.ok) throw new WebflowError("Failed to get Webflow product", res.status, await parsePayload(res));
|
||||
return (await res.json()) as Webflow.Products.ProductAndSkus;
|
||||
@@ -136,6 +148,7 @@ class ProductsClient {
|
||||
},
|
||||
body: JSON.stringify(wProduct),
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to update Webflow product", res.status, await parsePayload(res));
|
||||
}
|
||||
|
||||
@@ -151,6 +164,7 @@ class ProductsClient {
|
||||
body: JSON.stringify({}),
|
||||
},
|
||||
);
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to remove Webflow product", res.status, await parsePayload(res));
|
||||
}
|
||||
}
|
||||
@@ -170,6 +184,7 @@ class OrdersClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeader,
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to list Webflow orders", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Webflow.MetaDataMulti<"orders", Webflow.Orders.Order>;
|
||||
allOrders.push(...payload.orders);
|
||||
@@ -196,6 +211,7 @@ class OrdersClient {
|
||||
},
|
||||
body: JSON.stringify(wOrderUpdate),
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to update Webflow order", res.status, await parsePayload(res));
|
||||
}
|
||||
|
||||
@@ -211,6 +227,7 @@ class OrdersClient {
|
||||
},
|
||||
body: JSON.stringify(opt),
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to fulfill Webflow order", res.status, await parsePayload(res));
|
||||
}
|
||||
}
|
||||
@@ -226,6 +243,7 @@ class WebhooksClient {
|
||||
method: "GET",
|
||||
headers: this.creds.authHeader,
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to list Webflow webhooks", res.status, await parsePayload(res));
|
||||
const payload = (await res.json()) as Webflow.MetaDataMulti<"webhooks", Webflow.Webhook.Config>;
|
||||
allWebhooks.push(...payload.webhooks);
|
||||
@@ -250,6 +268,7 @@ class WebhooksClient {
|
||||
"triggerType": event
|
||||
}),
|
||||
});
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to create Webflow webhook", res.status, await parsePayload(res));
|
||||
}
|
||||
|
||||
@@ -265,6 +284,7 @@ class WebhooksClient {
|
||||
body: JSON.stringify({}),
|
||||
},
|
||||
);
|
||||
if (res.status === 429) throw new WebflowRateLimitError(res.headers.get("Retry-After"), await parsePayload(res));
|
||||
if (!res.ok) throw new WebflowError("Failed to remove Webflow webhook", res.status, await parsePayload(res));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user