Services refactor

This commit is contained in:
Dominic Ferrando
2026-06-23 15:36:53 -04:00
parent 5bae0f067d
commit 720aa61c66
6 changed files with 502 additions and 638 deletions
+115 -184
View File
@@ -1,199 +1,130 @@
import type { Printful } from "./util/types";
import { FetchError, type DeepPartial } from "./util/misc";
export class PrintfulService {
static Products = class {
static Variants = class {
static async get(
variantId: number | string,
): Promise<Printful.Products.SyncVariant | undefined> {
const res = await fetch(
`${env().API_URL}/store/variants/${variantId}`,
{
method: "GET",
headers: { ...env().AUTH_HEADERS },
},
);
type ClientOptions = {
token: string;
storeId: string;
};
if (res.status === 404 || res.status === 400) {
return;
}
type Credentials = {
apiUrl: string;
authHeaders: Record<string, string>;
};
if (!res.ok) {
throw new FetchError("Failed to get Printful variant", res);
}
class VariantsClient {
constructor(private creds: Credentials) { }
const payload =
(await res.json()) as Printful.Products.MetaDataSingle<Printful.Products.SyncVariant>;
return payload.result;
}
};
static async getAll(
offset: number = 0,
): Promise<Printful.Products.SyncProduct[]> {
const allSyncProducts: Printful.Products.SyncProduct[] = [];
while (true) {
const res = await fetch(
`${env().API_URL}/store/products?offset=${offset}`,
{
method: "GET",
headers: { ...env().AUTH_HEADERS },
},
);
if (!res.ok) {
throw new FetchError(
"Failed to get all Printful products",
res,
);
}
const payload =
(await res.json()) as Printful.Products.MetaDataMulti<Printful.Products.SyncProduct>;
allSyncProducts.push(...payload.result);
offset = allSyncProducts.length;
if (allSyncProducts.length >= payload.paging.total) break;
}
return allSyncProducts;
}
static async get(
productId: number | string,
): Promise<Printful.Products.Product | undefined> {
const res = await fetch(
`${env().API_URL}/store/products/${productId}`,
{
method: "GET",
headers: { ...env().AUTH_HEADERS },
},
);
if (res.status === 404 || res.status === 400) {
return;
}
if (!res.ok) {
throw new FetchError("Failed to get Printful product", res);
}
const payload =
(await res.json()) as Printful.Products.MetaDataSingle<Printful.Products.Product>;
return payload.result;
}
static async update(
printfulProductId: number,
printfulProduct: DeepPartial<Printful.Products.Product>,
) {
const res = await fetch(
`${env().API_URL}/store/products/${printfulProductId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
...env().AUTH_HEADERS,
},
body: JSON.stringify(printfulProduct),
},
);
if (!res.ok) {
throw new FetchError("Failed to update Printful product", res);
}
}
};
static Orders = class {
static async create(printfulOrder: Printful.Orders.Order) {
const res = await fetch(`${env().API_URL}/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...env().AUTH_HEADERS,
},
body: JSON.stringify(printfulOrder),
});
if (!res.ok) {
throw new FetchError("Failed to create printful order", res);
}
}
static async getAll(
offset: number = 0,
): Promise<Printful.Orders.Order[]> {
const allPrintfulOrders: Printful.Orders.Order[] = [];
while (true) {
const res = await fetch(
`${env().API_URL}/orders?offset=${offset}`,
{
method: "GET",
headers: { ...env().AUTH_HEADERS },
},
);
if (!res.ok) {
throw new FetchError(
"Failed to get all Printful orders",
res,
);
}
const payload =
(await res.json()) as Printful.Products.MetaDataMulti<Printful.Orders.Order>;
allPrintfulOrders.push(...payload.result);
offset = allPrintfulOrders.length;
if (allPrintfulOrders.length >= payload.paging.total) break;
}
return allPrintfulOrders;
}
};
static Util = class {
static getVariantMainImage(
syncVariant: Printful.Products.SyncVariant,
): string {
const previewFile = syncVariant.files.find(
(f) => f.type === "preview",
);
return previewFile?.preview_url ?? syncVariant.product.image;
}
};
async get(variantId: number | string): Promise<Printful.Products.SyncVariant | undefined> {
const res = await fetch(`${this.creds.apiUrl}/store/variants/${variantId}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (res.status === 404 || res.status === 400) return;
if (!res.ok) throw new FetchError("Failed to get Printful variant", res);
const payload = (await res.json()) as Printful.Products.MetaDataSingle<Printful.Products.SyncVariant>;
return payload.result;
}
}
export const env = () => {
if (typeof Bun === "undefined") {
throw new Error(
"Must be in a server context. Make sure to run using --bun.",
);
class ProductsClient {
readonly Variants: VariantsClient;
constructor(private creds: Credentials) {
this.Variants = new VariantsClient(creds);
}
const vars = {
AUTH_TOKEN: Bun.env.PRINTFUL_AUTH,
STORE_ID: Bun.env.PRINTFUL_STORE_ID,
} as Record<string, string>;
for (const varKey in vars) {
if (!vars[varKey]) {
console.log(`Missing ${varKey} environment variable`);
async getAll(offset = 0): Promise<Printful.Products.SyncProduct[]> {
const allSyncProducts: Printful.Products.SyncProduct[] = [];
while (true) {
const res = await fetch(`${this.creds.apiUrl}/store/products?offset=${offset}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (!res.ok) throw new FetchError("Failed to get all Printful products", res);
const payload = (await res.json()) as Printful.Products.MetaDataMulti<Printful.Products.SyncProduct>;
allSyncProducts.push(...payload.result);
offset = allSyncProducts.length;
if (allSyncProducts.length >= payload.paging.total) break;
}
return allSyncProducts;
}
return {
...vars,
API_URL: "https://api.printful.com",
AUTH_HEADERS: {
Authorization: `Bearer ${vars.AUTH_TOKEN}`,
"X-PF-Store-Id": `${vars.STORE_ID}`,
async get(productId: number | string): Promise<Printful.Products.Product | undefined> {
const res = await fetch(`${this.creds.apiUrl}/store/products/${productId}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (res.status === 404 || res.status === 400) return;
if (!res.ok) throw new FetchError("Failed to get Printful product", res);
const payload = (await res.json()) as Printful.Products.MetaDataSingle<Printful.Products.Product>;
return payload.result;
}
async update(printfulProductId: number, printfulProduct: DeepPartial<Printful.Products.Product>) {
const res = await fetch(`${this.creds.apiUrl}/store/products/${printfulProductId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...this.creds.authHeaders,
},
body: JSON.stringify(printfulProduct),
});
if (!res.ok) throw new FetchError("Failed to update Printful product", res);
}
}
class OrdersClient {
constructor(private creds: Credentials) { }
async create(printfulOrder: Printful.Orders.Order) {
const res = await fetch(`${this.creds.apiUrl}/orders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...this.creds.authHeaders,
},
body: JSON.stringify(printfulOrder),
});
if (!res.ok) throw new FetchError("Failed to create printful order", res);
}
async getAll(offset = 0): Promise<Printful.Orders.Order[]> {
const allPrintfulOrders: Printful.Orders.Order[] = [];
while (true) {
const res = await fetch(`${this.creds.apiUrl}/orders?offset=${offset}`, {
method: "GET",
headers: this.creds.authHeaders,
});
if (!res.ok) throw new FetchError("Failed to get all Printful orders", res);
const payload = (await res.json()) as Printful.Products.MetaDataMulti<Printful.Orders.Order>;
allPrintfulOrders.push(...payload.result);
offset = allPrintfulOrders.length;
if (allPrintfulOrders.length >= payload.paging.total) break;
}
return allPrintfulOrders;
}
}
export class PrintfulClient {
readonly Products: ProductsClient;
readonly Orders: OrdersClient;
constructor(options: ClientOptions) {
const creds: Credentials = {
apiUrl: "https://api.printful.com",
authHeaders: {
Authorization: `Bearer ${options.token}`,
"X-PF-Store-Id": options.storeId,
},
};
this.Products = new ProductsClient(creds);
this.Orders = new OrdersClient(creds);
}
static Util = {
getVariantMainImage(syncVariant: Printful.Products.SyncVariant): string {
const previewFile = syncVariant.files.find((f) => f.type === "preview");
return previewFile?.preview_url ?? syncVariant.product.image;
},
};
};
}