@@ -0,0 +1,77 @@
|
||||
// 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),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user