From 30c7c310fff1bdc7e10915086feea3dae42f9018 Mon Sep 17 00:00:00 2001 From: Monty Anderson Date: Wed, 2 Oct 2024 15:14:34 +0100 Subject: [PATCH] `*`: v2 support --- tsconfig.json | 2 +- v2/index.ts | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 v2/index.ts diff --git a/tsconfig.json b/tsconfig.json index beaf29e..d530c5f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,5 @@ "baseUrl": ".", "declaration": true }, - "files": ["prodia.ts"] + "files": ["prodia.ts", "v2/index.ts"] } diff --git a/v2/index.ts b/v2/index.ts new file mode 100644 index 0000000..5ce7d81 --- /dev/null +++ b/v2/index.ts @@ -0,0 +1,101 @@ +type JsonObject = {[Key in string]: JsonValue} & {[Key in string]?: JsonValue | undefined}; +type JsonArray = JsonValue[] | readonly JsonValue[]; +type JsonPrimitive = string | number | boolean | null; +type JsonValue = JsonPrimitive | JsonObject | JsonArray; + +/* job and job configuration */ + +export type ProdiaJob = Record; + +export type ProdiaJobOptions = { + accept: "image/png" | + "image/jpeg" | + "image/webp" | + "multipart/form-data" | + "video/mp4"; +}; + +const defaultJobOptions: ProdiaJobOptions = { + accept: "image/jpeg", +}; + +export type ProdiaJobResponse = { + arrayBuffer: () => Promise; // we only support direct image response now +}; + +/* client & client configuration*/ + +export type Prodia = { + job: (params: ProdiaJob, options?: Partial) => Promise; +}; + +export type CreateProdiaOptions = { + token: string; + baseUrl?: string; + maxErrors?: number; + maxRetries?: number; +}; + +/* error types */ + +export class ProdiaCapacityError extends Error {} +export class ProdiaBadResponseError extends Error {} + +export const createProdia = ({ + token, + baseUrl = "https://inference.prodia.com/v2", + maxErrors = 1, + maxRetries = Infinity, +}: CreateProdiaOptions): Prodia => { + const job = async (params: ProdiaJob, _options?: Partial) => { + const options = { + ...defaultJobOptions, + ..._options + }; + + let response: Response; + + let errors = 0; + let retries = 0; + + do { + response = await fetch(`${baseUrl}/job`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + Accept: options.accept, + }, + body: JSON.stringify(params), + }); + + if(response.status === 429) { + retries += 1; + } else if(response.status < 200 || response.status > 299) { + errors += 1; + } + + const retryAfter = Number(response.headers.get("Retry-After")) || 1; + await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); + } while ( + (response.status < 200 || response.status > 299) && + errors <= maxErrors && + retries <= maxRetries + ); + + if(response.status === 429) { + throw new ProdiaCapacityError("CapacityError: Unable to schedule job with current token"); + } + + if(response.status < 200 || response.status > 299) { + throw new ProdiaBadResponseError("BadResponseError: Invalid response from Prodia API"); + } + + return { + arrayBuffer: () => response.arrayBuffer(), + }; + }; + + return { + job, + }; +};