80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
/**
|
|
* 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<T> {
|
|
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<T> = APISuccess<T> | APIFailure;
|
|
|
|
/**
|
|
* Returned by the list endpoints, as the `data` of an {@link APISuccess}.
|
|
*/
|
|
export interface PaginatedData<T> {
|
|
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> = T extends Date ? string
|
|
: T extends readonly (infer U)[] ? Wire<U>[]
|
|
: T extends object ? { [K in keyof T]: Wire<T[K]> }
|
|
: T;
|