/** * Lets the client tell success from failure the same way for every JSON * endpoint, through the envelope they all answer with. It also defines {@link * Wire}, the form a type takes once it has crossed JSON. The routes build the * envelope, and the frontend's API helpers unwrap it. * * @module */ /** * Lists the codes an {@link APIError} carries, one per HTTP status. The * backend's `APIException` derives its code from the status it is given, and * the frontend shows the message without reading the code. */ export const APIErrorCode = { BAD_REQUEST: "BAD_REQUEST", UNAUTHORIZED: "UNAUTHORIZED", FORBIDDEN: "FORBIDDEN", /** Also answers for what exists but the caller may not see. */ NOT_FOUND: "NOT_FOUND", CONFLICT: "CONFLICT", RATE_LIMITED: "RATE_LIMITED", SERVER_ERROR: "SERVER_ERROR", /** Answers when another site, such as Bandcamp or YouTube, failed us. */ BAD_GATEWAY: "BAD_GATEWAY", } as const; /** Carried by the backend's `APIException`, and sent in an {@link APIError}. */ export type APIErrorCode = typeof APIErrorCode[keyof typeof APIErrorCode]; /** * Sent in an {@link APIFailure}. The frontend shows `message` to the user, so * it is written for them. */ export interface APIError { code: APIErrorCode; message: string; } /** Sent by a route that succeeded, around its payload. */ export interface APISuccess { success: true; data: T; error?: never; } /** * Sent by the error middleware when a request fails, and by the few routes * that answer an error themselves. */ export interface APIFailure { success: false; data?: never; error: APIError; } /** * Returned by every JSON endpoint. A reader tells the two apart by `success`. */ export type APIResponse = APISuccess | APIFailure; /** * Returned by the list endpoints, as the `data` of an {@link APISuccess}. */ export interface PaginatedData { items: T[]; hasMore: boolean; /** The size of the whole list, set only by search, which shows it. */ total?: number; } /** * `T` as the client reads it from JSON, over HTTP, the socket or * localStorage, where every `Date` has become an ISO string. */ export type Wire = T extends Date ? string : T extends readonly (infer U)[] ? Wire[] : T extends object ? { [K in keyof T]: Wire } : T;