dune-api-client
Version:
A minimal TypeScript client for interacting with the Dune API.
120 lines (103 loc) • 3.54 kB
text/typescript
import {
ExecuteQuery,
ExecutionStatus,
ExecutionResult,
CancelQuery,
ExecuteQueryOptions,
DataOrError,
} from './types'
export class Dune {
private API_KEY: string
private BASE_URL = 'https://api.dune.com/api/v1'
constructor(API_KEY: string | undefined) {
if (!API_KEY) {
throw new Error('Dune API key is required')
}
this.API_KEY = API_KEY
}
/**
* Wrapper to handle HTTP requests with clean typing
* @param endpoint API endpoint
* @returns Dune response
*/
private async fetchDune<T>(
method: 'GET' | 'POST',
endpoint: string,
body?: { query_parameters?: ExecuteQueryOptions['params'] }
): Promise<DataOrError<T>> {
const res = await fetch(endpoint, {
method,
headers: {
'x-dune-api-key': this.API_KEY,
},
body: method === 'POST' ? JSON.stringify(body) : undefined,
})
const json = await res.json()
if (json.error) {
return json as { error: string }
}
return { data: json as T }
}
/**
* Execute a query
* @param query_id Dune query id
* @returns Execution id and state
*/
async execute(
query_id: number,
options?: ExecuteQueryOptions
): Promise<DataOrError<ExecuteQuery>> {
const endpoint = this.BASE_URL + '/query/' + query_id + '/execute'
const body = { query_parameters: options?.params }
return await this.fetchDune<ExecuteQuery>('POST', endpoint, body)
}
/**
* Cancel an execution
* @param execution_id Dune execution id
* @returns Success of cancellation
*/
async cancel(execution_id: string): Promise<DataOrError<CancelQuery>> {
const endpoint = this.BASE_URL + '/execution/' + execution_id + '/cancel'
return await this.fetchDune<CancelQuery>('POST', endpoint)
}
/**
* Check the status of an execution
* @param execution_id Dune execution id
* @returns Status of execution
*/
async status(execution_id: string): Promise<DataOrError<ExecutionStatus>> {
const endpoint = this.BASE_URL + '/execution/' + execution_id + '/status'
return await this.fetchDune<ExecutionStatus>('GET', endpoint)
}
/**
* Fetch the results of an execution
* @param exec_or_query_id Dune execution id or query id
* @returns Results of execution, including row data if available
*/
async results<T>(
exec_or_query_id: string | number,
options?: ExecuteQueryOptions
): Promise<DataOrError<ExecutionResult<T>>> {
const isIdNumber = /^\d+$/.test(exec_or_query_id.toString())
let endpoint: string
if (isIdNumber) {
// If `exec_or_query_id` is a number, we want the latest query results
// Since this is a GET request, the params need to be in the URL query string
// https://dune.com/docs/api/api-reference/get-results/latest-results/
const searchParams = new URLSearchParams()
if (options?.params) {
for (const [key, value] of Object.entries(options.params)) {
const formattedKey = 'params.' + key
const formattedValue =
value instanceof Date ? value.toISOString() : value.toString()
searchParams.append(formattedKey, formattedValue)
}
}
endpoint = `${this.BASE_URL}/query/${exec_or_query_id}/results?${searchParams}`
} else {
// If `exec_or_query_id` is a string, we want the results of a specific execution
endpoint = this.BASE_URL + '/execution/' + exec_or_query_id + '/results'
}
return await this.fetchDune<ExecutionResult<T>>('GET', endpoint)
}
}