78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
// Minimal fetch wrapper for the LiveOps platform/admin HTTP API used by the seed
|
|
// and by tests' admin-side assertions. Retries network errors and 5xx with backoff;
|
|
// fails fast on 4xx.
|
|
|
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
public readonly status: number,
|
|
public readonly path: string,
|
|
public readonly body: string,
|
|
) {
|
|
super(`HTTP ${status} on ${path}: ${body.slice(0, 400)}`);
|
|
this.name = 'ApiError';
|
|
}
|
|
}
|
|
|
|
export interface AdminClient {
|
|
setToken(token: string): void;
|
|
readonly token: string | undefined;
|
|
get<T>(path: string, opts?: { auth?: boolean }): Promise<T>;
|
|
post<T>(path: string, body?: unknown, opts?: { auth?: boolean }): Promise<T>;
|
|
put<T>(path: string, body?: unknown, opts?: { auth?: boolean }): Promise<T>;
|
|
del<T>(path: string, opts?: { auth?: boolean }): Promise<T>;
|
|
}
|
|
|
|
export function createAdminClient(baseUrl: string): AdminClient {
|
|
let token: string | undefined;
|
|
|
|
async function request<T>(
|
|
method: string,
|
|
path: string,
|
|
body?: unknown,
|
|
opts?: { auth?: boolean },
|
|
): Promise<T> {
|
|
const url = `${baseUrl}${path}`;
|
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
if (opts?.auth !== false && token) headers['Authorization'] = `Bearer ${token}`;
|
|
|
|
let lastErr: unknown;
|
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
try {
|
|
const res = await fetch(url, {
|
|
method,
|
|
headers,
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
const text = await res.text();
|
|
if (res.status >= 500) {
|
|
lastErr = new ApiError(res.status, path, text);
|
|
await sleep(300 * (attempt + 1));
|
|
continue;
|
|
}
|
|
if (!res.ok) throw new ApiError(res.status, path, text);
|
|
return (text ? JSON.parse(text) : undefined) as T;
|
|
} catch (err) {
|
|
if (err instanceof ApiError) throw err; // 4xx — don't retry
|
|
lastErr = err; // network error — retry
|
|
await sleep(300 * (attempt + 1));
|
|
}
|
|
}
|
|
throw lastErr;
|
|
}
|
|
|
|
return {
|
|
setToken(t: string) {
|
|
token = t;
|
|
},
|
|
get token() {
|
|
return token;
|
|
},
|
|
get: (path, opts) => request('GET', path, undefined, opts),
|
|
post: (path, body, opts) => request('POST', path, body, opts),
|
|
put: (path, body, opts) => request('PUT', path, body, opts),
|
|
del: (path, opts) => request('DELETE', path, undefined, opts),
|
|
};
|
|
}
|