/** * Defines the envelope every JSON endpoint answers with, and {@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 */ /** * The codes an {@link APIError} may carry. The frontend shows the message and * doesn't read the code, and a code goes with several statuses. */ export const APIErrorCode = { BAD_REQUEST: "BAD_REQUEST", /** Also answers for what exists but the caller may not see. */ NOT_FOUND: "NOT_FOUND", /** Sent by the error middleware for any error that isn't an APIException. */ SERVER_ERROR: "SERVER_ERROR", /** Nothing sends it. */ TIMEOUT: "TIMEOUT", /** * Sent with 401 to a caller not signed in, and with 403 to one who may not. */ UNAUTHORIZED: "UNAUTHORIZED", VALIDATION_ERROR: "VALIDATION_ERROR", } 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;