export interface ApiResult<T = unknown> {
ok: boolean;
data?: T;
error?: string;
status: number;
}
export class PulseApiClient {
private apiKey: string;
private baseUrl: string;
constructor(apiKey: string, baseUrl: string) {
this.apiKey = apiKey;
this.baseUrl = baseUrl.replace(/\/$/, "");
}
private async request<T = unknown>(
method: string,
path: string,
body?: unknown,
query?: Record<string, string | undefined>
): Promise<ApiResult<T>> {
let url = `${this.baseUrl}${path}`;
if (query) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(query)) {
if (value !== undefined) params.set(key, value);
}
const qs = params.toString();
if (qs) url += `?${qs}`;
}
try {
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
},
body: body ? JSON.stringify(body) : undefined,
});
const data = await response.json().catch(() => null);
if (!response.ok) {
const message =
(data as any)?.message ||
(data as any)?.error ||
`HTTP ${response.status} ${response.statusText}`;
return { ok: false, error: message, status: response.status };
}
return { ok: true, data: data as T, status: response.status };
} catch (err) {
const message =
err instanceof Error ? err.message : "Unknown network error";
return { ok: false, error: message, status: 0 };
}
}
async get<T = unknown>(
path: string,
query?: Record<string, string | undefined>
): Promise<ApiResult<T>> {
return this.request<T>("GET", path, undefined, query);
}
async post<T = unknown>(
path: string,
body?: unknown
): Promise<ApiResult<T>> {
return this.request<T>("POST", path, body);
}
async patch<T = unknown>(
path: string,
body?: unknown
): Promise<ApiResult<T>> {
return this.request<T>("PATCH", path, body);
}
async delete<T = unknown>(path: string): Promise<ApiResult<T>> {
return this.request<T>("DELETE", path);
}
}