@riverfl0w/dune-client
Version:
A TypeScript client for querying the Dune API, designed to simplify the integration of Dune's powerful analytics into your projects.
67 lines (54 loc) • 1.84 kB
text/typescript
import type { ZodSchema, z } from 'zod';
import DuneError from '../DuneError.js';
import ErrorResponse from '../schemas/ErrorResponse.js';
export interface CallOptions<S extends ZodSchema> extends RequestInit {
path: string;
searchParams?: URLSearchParams;
schema: S;
delay?: number;
}
const MAX_RATE_LIMIT_DELAY = 60000;
/**
* Base client for Dune API. It handles the API key and error handling.
*/
export default class BaseClient {
private readonly base = 'https://api.dune.com/api';
constructor(private readonly apiKey: string) {}
protected async call<S extends ZodSchema>({
path,
searchParams,
schema,
delay = 0,
...options
}: CallOptions<S>): Promise<z.infer<S>> {
if (searchParams) {
const search = searchParams.toString();
if (search.length > 0) {
path += `?${search}`;
}
}
const response = await fetch(`${this.base}${path}`, {
...options,
headers: {
...options.headers,
'x-dune-api-key': this.apiKey,
},
});
const data = await response.json();
const hasError = await ErrorResponse.safeParseAsync(data);
if (hasError.success) {
if (hasError.data.error.match(/too many requests/)) {
// We are being rate limited, so we should wait and try again
await new Promise((resolve) => setTimeout(resolve, delay));
if (delay < MAX_RATE_LIMIT_DELAY) {
const newDelay = (delay + Math.floor(Math.random() * 1000)) * 2;
return this.call({ path, searchParams, schema, delay: newDelay, ...options });
}
// If we have been waiting for more than a minute, we should throw an error
}
throw new DuneError(hasError.data.error);
}
// console.log(`${options.method} ${path}`, data);
return await schema.parseAsync(data);
}
}