/**
* Shared HTTP client for CryptoDataAPI.
*
* Uses native fetch (Node 18+). Reads CRYPTODATA_API_KEY from env
* and sets it as the X-API-Key header on every request.
*/
const BASE_URL = process.env.CRYPTODATA_API_URL ?? "https://cryptodataapi.com";
const API_KEY = process.env.CRYPTODATA_API_KEY;
if (!API_KEY) {
console.error(
"Error: CRYPTODATA_API_KEY environment variable is required.\n" +
"Get your key at https://cryptodataapi.com"
);
process.exit(1);
}
export interface ApiResult<T = unknown> {
ok: true;
data: T;
}
export interface ApiError {
ok: false;
error: string;
status: number;
}
export type ApiResponse<T = unknown> = ApiResult<T> | ApiError;
/**
* Make a GET request to the CryptoDataAPI.
*
* @param path - API path starting with /api/v1/...
* @param params - Optional query parameters
*/
export async function apiGet<T = unknown>(
path: string,
params?: Record<string, string | number | boolean | undefined>
): Promise<ApiResponse<T>> {
const url = new URL(path, BASE_URL);
if (params) {
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) {
url.searchParams.set(key, String(value));
}
}
}
try {
const response = await fetch(url.toString(), {
headers: {
"X-API-Key": API_KEY!,
Accept: "application/json",
},
});
if (!response.ok) {
const body = await response.text();
let detail: string;
try {
detail = JSON.parse(body).detail ?? body;
} catch {
detail = body;
}
return { ok: false, error: detail, status: response.status };
}
const data = (await response.json()) as T;
return { ok: true, data };
} catch (err) {
return {
ok: false,
error: err instanceof Error ? err.message : String(err),
status: 0,
};
}
}