Skip to content

Commit

Permalink
Latest Results & Query CRUD Operations (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
bh2smith authored Feb 14, 2024
1 parent ac6b64a commit 9992833
Show file tree
Hide file tree
Showing 16 changed files with 873 additions and 220 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Dune Client TS

This [NPM package](https://www.npmjs.com/package/@cowprotocol/ts-dune-client) implements all the basic routes defined in the [Dune API Docs](https://dune.com/docs/api/). It also introduces a convenience method `refresh` which combines `execute`, `getStatus` and `getResults` in a way that makes it nearly trivial to fetch query execution results.
This [NPM package](https://www.npmjs.com/package/@cowprotocol/ts-dune-client) implements all the basic routes defined in the [Dune API Docs](https://dune.com/docs/api/). It also introduces a convenience method `refresh` which combines `executeQuery`, `getExecutionStatus` and `gettExecutionResults` in a way that makes it nearly trivial to fetch query execution results.

Install the package

Expand All @@ -25,20 +25,20 @@ const parameters = [
];

client
.refresh(queryID, parameters)
.runQuery(queryID, parameters)
.then((executionResult) => console.log(executionResult.result?.rows));

// should look like
// [
// {
// date_field: "2022-05-04 00:00:00",
// list_field: "Option 1",
// number_field: "3.1415926535",
// text_field: "Plain Text",
// },
// ]
// {
// date_field: "2022-05-04 00:00:00.000",
// list_field: "Option 1",
// number_field: "3.1415926535",
// text_field: "Plain Text",
// },
// ]
```

Note also that the client has methods `execute`, `getStatus`, `getResult` and `cancelExecution`
Note also that the client has methods `executeQuery`, `getExecutionStatus`, `getExecutionResult` and `cancelExecution`

Check out this [Demo Project](https://github.com/bh2smith/demo-ts-dune-client)!
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"build": "tsc",
"test": "ts-mocha 'tests/**/*.spec.ts' -r dotenv/config --timeout 10000",
"fmt": "prettier --write \"./**/*.ts\"",
"lint": "tslint -p tsconfig.json"
"lint": "eslint"
},
"devDependencies": {
"@types/chai": "^4.3.3",
Expand All @@ -22,12 +22,12 @@
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"dotenv": "^16.0.3",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"mocha": "^10.3.0",
"prettier": "^3.2.5",
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.1",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0",
"typescript": "^5.3.3"
},
"dependencies": {
Expand Down
7 changes: 3 additions & 4 deletions src/api/execution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ export class ExecutionClient extends Router {
queryID: number,
parameters?: QueryParameter[],
): Promise<ExecutionResponse> {
const response = await this._post<ExecutionResponse>(
`query/${queryID}/execute`,
parameters,
);
const response = await this._post<ExecutionResponse>(`query/${queryID}/execute`, {
query_parameters: parameters ? parameters : [],
});
log.debug(logPrefix, `execute response ${JSON.stringify(response)}`);
return response as ExecutionResponse;
}
Expand Down
23 changes: 21 additions & 2 deletions src/api/extensions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { DuneError, ResultsResponse, ExecutionState, QueryParameter } from "../types";
import { sleep } from "../utils";
import { ageInHours, sleep } from "../utils";
import log from "loglevel";
import { logPrefix } from "../utils";
import { ExecutionClient } from "./execution";
import { POLL_FREQUENCY_SECONDS } from "../constants";
import { POLL_FREQUENCY_SECONDS, THREE_MONTHS_IN_HOURS } from "../constants";

const TERMINAL_STATES = [
ExecutionState.CANCELLED,
Expand Down Expand Up @@ -43,6 +43,25 @@ export class ExtendedClient extends ExecutionClient {
}
}

async getLatestResult(
queryId: number,
parameters?: QueryParameter[],
maxAgeHours: number = THREE_MONTHS_IN_HOURS,
): Promise<ResultsResponse> {
let results = await this._get<ResultsResponse>(`query/${queryId}/results`, {
query_parameters: parameters ? parameters : [],
});
const lastRun: Date = results.execution_ended_at!;
if (lastRun !== undefined && ageInHours(lastRun) > maxAgeHours) {
log.info(
logPrefix,
`results (from ${lastRun}) older than ${maxAgeHours} hours, re-running query.`,
);
results = await this.runQuery(queryId, parameters);
}
return results;
}

/**
* @deprecated since version 0.0.2 Use runQuery
*/
Expand Down
4 changes: 4 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./execution";
export * from "./extensions";
export * from "./query";
export * from "./router";
80 changes: 80 additions & 0 deletions src/api/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Assuming the existence of these imports based on your Python code
import { Router } from "./router";
import {
DuneQuery,
QueryParameter,
CreateQueryResponse,
} from "../types";
import { CreateQueryPayload, UpdateQueryPayload } from "../types/requestPayload";

export class QueryAPI extends Router {
/**
* Creates a Dune Query by ID
* https://dune.com/docs/api/api-reference/edit-queries/create-query/
*/
async createQuery(
name: string,
querySql: string,
params?: QueryParameter[],
isPrivate: boolean = false,
): Promise<DuneQuery> {
const payload: CreateQueryPayload = {
name,
query_sql: querySql,
is_private: isPrivate,
query_parameters: params ? params : [],
};
try {
const responseJson = await this._post<CreateQueryResponse>("query/", payload);
return this.getQuery(responseJson.query_id);
} catch (err: unknown) {
throw new Error(`Fokin Broken: ${err}`);
// throw new DuneError(responseJson, "CreateQueryResponse", err);
}
}

/**
* Retrieves a Dune Query by ID
* https://dune.com/docs/api/api-reference/edit-queries/get-query/
*/
async getQuery(queryId: number): Promise<DuneQuery> {
const responseJson = await this._get(`query/${queryId}`);
return responseJson as DuneQuery;
}

/**
* Updates a Dune Query by ID
* https://dune.com/docs/api/api-reference/edit-queries/update-query/
*/
async updateQuery(
queryId: number,
name?: string,
querySql?: string,
params?: QueryParameter[],
description?: string,
tags?: string[],
): Promise<number> {
const parameters: UpdateQueryPayload = {};
if (name !== undefined) parameters.name = name;
if (description !== undefined) parameters.description = description;
if (tags !== undefined) parameters.tags = tags;
if (querySql !== undefined) parameters.query_sql = querySql;
if (params !== undefined) parameters.query_parameters = params;

if (Object.keys(parameters).length === 0) {
console.warn("Called updateQuery with no proposed changes.");
return queryId;
}

try {
const responseJson = await this._patch<CreateQueryResponse>(
`query/${queryId}`,
parameters,
);
return responseJson.query_id;
} catch (err: unknown) {
throw new Error(`Fokin Broken: ${err}`);
// throw new DuneError(responseJson, "UpdateQueryResponse", err);
}
}
}
30 changes: 13 additions & 17 deletions src/api/router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { DuneError, QueryParameter } from "../types";
import { DuneError } from "../types";
import fetch from "cross-fetch";
import log from "loglevel";
import { logPrefix } from "../utils";
import {
RequestPayload,
payloadJSON,
} from "../types/requestPayload";

const BASE_URL = "https://api.dune.com/api/v1";

Expand Down Expand Up @@ -48,43 +52,35 @@ export class Router {
protected async _request<T>(
method: RequestMethod,
url: string,
params?: QueryParameter[],
payload?: RequestPayload,
): Promise<T> {
log.debug(
logPrefix,
`${method} received input url=${url}, params=${JSON.stringify(params)}`,
);
// Transform Query Parameter list into "dict"
const reducedParams = params?.reduce<Record<string, string>>(
(acc, { name, value }) => ({ ...acc, [name]: value }),
{},
);
const requestParameters = JSON.stringify({ query_parameters: reducedParams || {} });
const payloadData = payloadJSON(payload);
log.debug(logPrefix, `${method} received input url=${url}, payload=${payloadData}`);
const response = fetch(url, {
method,
headers: {
"x-dune-api-key": this.apiKey,
},
// conditionally add the body property
...(method !== RequestMethod.GET && {
body: requestParameters,
body: payloadJSON(payload),
}),
...(method === RequestMethod.GET && {
params: requestParameters,
params: payloadJSON(payload),
}),
});
return this._handleResponse<T>(response);
}

protected async _get<T>(route: string, params?: QueryParameter[]): Promise<T> {
protected async _get<T>(route: string, params?: RequestPayload): Promise<T> {
return this._request(RequestMethod.GET, this.url(route), params);
}

protected async _post<T>(route: string, params?: QueryParameter[]): Promise<T> {
protected async _post<T>(route: string, params?: RequestPayload): Promise<T> {
return this._request(RequestMethod.POST, this.url(route), params);
}

protected async _patch<T>(route: string, params?: QueryParameter[]): Promise<T> {
protected async _patch<T>(route: string, params?: RequestPayload): Promise<T> {
return this._request(RequestMethod.PATCH, this.url(route), params);
}

Expand Down
Loading

0 comments on commit 9992833

Please sign in to comment.