99 lines
3.7 KiB
TypeScript
99 lines
3.7 KiB
TypeScript
import type { components, paths } from "./api.gen.js";
|
|
|
|
export type Schemas = components["schemas"];
|
|
export type User = Schemas["User"];
|
|
export type Project = Schemas["Project"];
|
|
export type Task = Schemas["Task"];
|
|
export type Unmerged = Schemas["Unmerged"];
|
|
export type AgentSession = Schemas["AgentSession"];
|
|
export type InboxItem = Schemas["InboxItem"];
|
|
export type InboxResponse = Schemas["InboxResponse"];
|
|
export type InboxResponseCreate = Schemas["InboxResponseCreate"];
|
|
export type Attachment = Schemas["Attachment"];
|
|
export type ByreEvent = Schemas["Event"];
|
|
export type Status = Schemas["Status"];
|
|
export type TurnRef = Schemas["TurnRef"];
|
|
|
|
type Method = "get" | "post" | "put";
|
|
|
|
type PathsWith<M extends Method> = {
|
|
[P in keyof paths]: paths[P][M] extends { responses: unknown } ? P : never;
|
|
}[keyof paths];
|
|
|
|
type Op<P extends keyof paths, M extends Method> = NonNullable<paths[P][M]>;
|
|
type Params<O> = O extends { parameters: infer X } ? X : never;
|
|
type PathParams<O> = Params<O> extends { path: infer X } ? X : never;
|
|
type QueryParams<O> = Params<O> extends { query?: infer X } ? X : never;
|
|
type Body<O> = O extends { requestBody: { content: { "application/json": infer B } } }
|
|
? B
|
|
: O extends { requestBody: { content: Record<`image/${string}`, unknown> } }
|
|
? Blob
|
|
: never;
|
|
type Success<O> = O extends { responses: infer R } ? R[Extract<keyof R, 200 | 201 | 202>] : never;
|
|
type Result<O> = Success<O> extends { content: { "application/json": infer J } } ? J : never;
|
|
|
|
type Options<O> = {
|
|
path?: PathParams<O>;
|
|
query?: QueryParams<O>;
|
|
body?: Body<O>;
|
|
};
|
|
|
|
export class ApiError extends Error {
|
|
constructor(readonly status: number, message: string) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
export async function request<M extends Method, P extends PathsWith<M>>(
|
|
method: M,
|
|
path: P,
|
|
opts: Options<Op<P, M>> = {},
|
|
): Promise<Result<Op<P, M>>> {
|
|
const pathParams = (opts.path ?? {}) as Record<string, unknown>;
|
|
let url: string = path.replace(/\{(\w+)\}/g, (_, key: string) => {
|
|
const value = pathParams[key];
|
|
if (value === undefined) {
|
|
throw new Error(`missing path parameter ${key} for ${path}`);
|
|
}
|
|
return encodeURIComponent(String(value));
|
|
});
|
|
|
|
const query = new URLSearchParams();
|
|
for (const [key, value] of Object.entries((opts.query ?? {}) as Record<string, unknown>)) {
|
|
if (value !== undefined) {
|
|
query.set(key, String(value));
|
|
}
|
|
}
|
|
if (query.size > 0) {
|
|
url += `?${query}`;
|
|
}
|
|
|
|
const init: RequestInit = { method: method.toUpperCase() };
|
|
if (opts.body instanceof Blob) {
|
|
init.headers = { "Content-Type": opts.body.type || "application/octet-stream" };
|
|
init.body = opts.body;
|
|
} else if (opts.body !== undefined) {
|
|
init.headers = { "Content-Type": "application/json" };
|
|
init.body = JSON.stringify(opts.body);
|
|
}
|
|
|
|
const res = await fetch(url, init);
|
|
const data = await res.json();
|
|
if (!res.ok) {
|
|
throw new ApiError(res.status, (data as Schemas["Error"]).message);
|
|
}
|
|
return data as Result<Op<P, M>>;
|
|
}
|
|
|
|
export const get = <P extends PathsWith<"get">>(path: P, opts?: Options<Op<P, "get">>) => request("get", path, opts);
|
|
export const post = <P extends PathsWith<"post">>(path: P, opts?: Options<Op<P, "post">>) => request("post", path, opts);
|
|
export const put = <P extends PathsWith<"put">>(path: P, opts?: Options<Op<P, "put">>) => request("put", path, opts);
|
|
|
|
export function subscribe(after: number, onEvent: (ev: ByreEvent) => void, onState: (connected: boolean) => void): EventSource {
|
|
const source = new EventSource(`/api/events?after=${after}`);
|
|
source.onopen = () => onState(true);
|
|
source.onerror = () => onState(false);
|
|
source.onmessage = (msg: MessageEvent<string>) => onEvent(JSON.parse(msg.data) as ByreEvent);
|
|
return source;
|
|
}
|