diff --git a/libs/client/src/function.ts b/libs/client/src/function.ts index 2332898..eae9781 100644 --- a/libs/client/src/function.ts +++ b/libs/client/src/function.ts @@ -112,6 +112,50 @@ export async function run( return await responseHandler(response); } +/** + * Subscribes to updates for a specific request in the queue. + * + * @param id - The ID or URL of the function web endpoint. + * @param options - Options to configure how the request is run and how updates are received. + * @returns A promise that resolves to the result of the request once it's completed. + */ +export async function subscribe( + id: string, + options: RunOptions & QueueSubscribeOptions = {} +): Promise { + const { request_id: requestId } = await queue.submit(id, options); + if (options.onEnqueue) { + options.onEnqueue(requestId); + } + return new Promise((resolve, reject) => { + let timeoutId: ReturnType; + const pollInterval = options.pollInterval ?? 1000; + const poll = async () => { + try { + const requestStatus = await queue.status(id, requestId); + if (options.onQueueUpdate) { + options.onQueueUpdate(requestStatus); + } + if (requestStatus.status === 'COMPLETED') { + clearTimeout(timeoutId); + try { + const result = await queue.result(id, requestId); + resolve(result); + } catch (error) { + reject(error); + } + return; + } + timeoutId = setTimeout(poll, pollInterval); + } catch (error) { + clearTimeout(timeoutId); + reject(error); + } + }; + poll().catch(reject); + }); +} + /** * Options for subscribing to the request queue. */ @@ -168,11 +212,7 @@ interface Queue { result(id: string, requestId: string): Promise; /** - * Subscribes to updates for a specific request in the queue. - * - * @param id - The ID or URL of the function web endpoint. - * @param options - Options to configure how the request is run and how updates are received. - * @returns A promise that resolves to the result of the request once it's completed. + * @deprecated Use `fal.subscribe` instead. */ subscribe( id: string, @@ -204,40 +244,5 @@ export const queue: Queue = { path: `/fal/queue/requests/${requestId}/response`, }); }, - async subscribe( - id: string, - options: RunOptions & QueueSubscribeOptions = {} - ): Promise { - const { request_id: requestId } = await queue.submit(id, options); - if (options.onEnqueue) { - options.onEnqueue(requestId); - } - return new Promise((resolve, reject) => { - let timeoutId: ReturnType; - const pollInterval = options.pollInterval ?? 1000; - const poll = async () => { - try { - const requestStatus = await queue.status(id, requestId); - if (options.onQueueUpdate) { - options.onQueueUpdate(requestStatus); - } - if (requestStatus.status === 'COMPLETED') { - clearTimeout(timeoutId); - try { - const result = await queue.result(id, requestId); - resolve(result); - } catch (error) { - reject(error); - } - return; - } - timeoutId = setTimeout(poll, pollInterval); - } catch (error) { - clearTimeout(timeoutId); - reject(error); - } - }; - poll().catch(reject); - }); - }, + subscribe, }; diff --git a/libs/client/src/index.ts b/libs/client/src/index.ts index b3df91b..c94b8c0 100644 --- a/libs/client/src/index.ts +++ b/libs/client/src/index.ts @@ -1,6 +1,7 @@ export { config, getConfig } from './config'; -export { queue, run } from './function'; +export { queue, run, subscribe } from './function'; export { withMiddleware } from './middleware'; +export { ApiError, ValidationError } from './response'; export type { RequestMiddleware } from './middleware'; export type { ResponseHandler } from './response'; export type { QueueStatus } from './types'; diff --git a/libs/client/src/response.ts b/libs/client/src/response.ts index b0ee0c7..cf9cf6b 100644 --- a/libs/client/src/response.ts +++ b/libs/client/src/response.ts @@ -1,14 +1,17 @@ +import { ValidationErrorInfo } from './types'; + export type ResponseHandler = (response: Response) => Promise; type ApiErrorArgs = { message: string; status: number; + // eslint-disable-next-line @typescript-eslint/no-explicit-any body?: any; }; -export class ApiError extends Error { +export class ApiError extends Error { public readonly status: number; - public readonly body?: any; + public readonly body: Body; constructor({ message, status, body }: ApiErrorArgs) { super(message); this.name = 'ApiError'; @@ -17,6 +20,13 @@ export class ApiError extends Error { } } +export class ValidationError extends ApiError { + constructor(args: ApiErrorArgs) { + super(args); + this.name = 'ValidationError'; + } +} + export async function defaultResponseHandler( response: Response ): Promise { @@ -25,13 +35,14 @@ export async function defaultResponseHandler( if (!response.ok) { if (contentType?.includes('application/json')) { const body = await response.json(); - throw new ApiError({ + const ErrorType = status === 422 ? ValidationError : ApiError; + throw new ErrorType({ message: body.message || statusText, status, body, }); } - throw new Error(`HTTP ${status}: ${statusText}`); + throw new ApiError({ message: `HTTP ${status}: ${statusText}`, status }); } if (contentType?.includes('application/json')) { return response.json() as Promise; diff --git a/libs/client/src/types.ts b/libs/client/src/types.ts index 258c8e2..74ad756 100644 --- a/libs/client/src/types.ts +++ b/libs/client/src/types.ts @@ -33,3 +33,9 @@ export type QueueStatus = export function isQueueStatus(obj: any): obj is QueueStatus { return obj && obj.status && obj.response_url; } + +export type ValidationErrorInfo = { + msg: string; + loc: Array; + type: string; +};