Skip to content

Commit caf33f3

Browse files
authored
Add Datasets and Uploads APIs, deprecate Tables API (#86)
1 parent f8e8718 commit caf33f3

File tree

11 files changed

+365
-2
lines changed

11 files changed

+365
-2
lines changed

.github/workflows/pull-request.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
runs-on: ubuntu-latest
3939
env:
4040
DUNE_API_KEY: ${{ secrets.DUNE_API_KEY }}
41+
DUNE_API_KEY_OWNER_HANDLE: ${{ secrets.DUNE_API_KEY_OWNER_HANDLE }}
4142

4243
steps:
4344
- name: Checkout code

src/api/client.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { TableAPI } from "./table";
2626
import { CustomAPI } from "./custom";
2727
import { UsageAPI } from "./usage";
2828
import { PipelineAPI } from "./pipeline";
29+
import { DatasetAPI } from "./dataset";
30+
import { UploadsAPI } from "./uploads";
2931
import { deprecationWarning } from "../deprecation";
3032

3133
/// Various states of query execution that are "terminal".
@@ -45,14 +47,18 @@ export class DuneClient {
4547
exec: ExecutionAPI;
4648
/// Query Management Interface.
4749
query: QueryAPI;
48-
/// Table Management Interface
50+
/// Table Management Interface (deprecated, use uploads instead)
4951
table: TableAPI;
5052
/// Custom Endpoint Interface
5153
custom: CustomAPI;
5254
/// Usage Interface
5355
usage: UsageAPI;
5456
/// Pipeline Interface
5557
pipeline: PipelineAPI;
58+
/// Dataset Interface
59+
dataset: DatasetAPI;
60+
/// Uploads Interface
61+
uploads: UploadsAPI;
5662

5763
constructor(apiKey: string) {
5864
this.exec = new ExecutionAPI(apiKey);
@@ -61,6 +67,8 @@ export class DuneClient {
6167
this.custom = new CustomAPI(apiKey);
6268
this.usage = new UsageAPI(apiKey);
6369
this.pipeline = new PipelineAPI(apiKey);
70+
this.dataset = new DatasetAPI(apiKey);
71+
this.uploads = new UploadsAPI(apiKey);
6472
}
6573

6674
/**

src/api/dataset.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Router } from "./router";
2+
import { DatasetResponse, ListDatasetsArgs, ListDatasetsResponse } from "../types";
3+
4+
export class DatasetAPI extends Router {
5+
async list(args?: ListDatasetsArgs): Promise<ListDatasetsResponse> {
6+
return this._get<ListDatasetsResponse>("datasets", args);
7+
}
8+
9+
async getBySlug(slug: string): Promise<DatasetResponse> {
10+
return this._get<DatasetResponse>(`datasets/${slug}`);
11+
}
12+
}

src/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export * from "./client";
22
export * from "./custom";
3+
export * from "./dataset";
34
export * from "./execution";
45
export * from "./pipeline";
56
export * from "./query";
67
export * from "./router";
78
export * from "./table";
9+
export * from "./uploads";
810
export * from "./usage";

src/api/table.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
DeleteTableResult,
1212
} from "../types";
1313
import { withDefaults } from "../utils";
14+
import { deprecationWarning } from "../deprecation";
1415

1516
/**
1617
* Table Management Interface (includes uploadCSV)
@@ -24,8 +25,10 @@ export class TableAPI extends Router {
2425
*
2526
* @param args UploadCSVParams relevant fields related to dataset upload.
2627
* @returns boolean representing if upload was successful.
28+
* @deprecated Use uploads.uploadCsv() instead. The /v1/table endpoints are deprecated in favor of /v1/uploads.
2729
*/
2830
async uploadCsv(args: UploadCSVArgs): Promise<boolean> {
31+
deprecationWarning("table.uploadCsv", "uploads.uploadCsv");
2932
const response = await this.post<SuccessResponse>("table/upload/csv", args);
3033
try {
3134
return Boolean(response.success);
@@ -44,10 +47,12 @@ export class TableAPI extends Router {
4447
*
4548
* The only limitations are:
4649
* - If a table already exists with the same name, the request will fail.
47-
* - Column names in the table cant start with a special character or a digit.
50+
* - Column names in the table can't start with a special character or a digit.
4851
* @param args
52+
* @deprecated Use uploads.create() instead. The /v1/table endpoints are deprecated in favor of /v1/uploads.
4953
*/
5054
async create(args: CreateTableArgs): Promise<CreateTableResult> {
55+
deprecationWarning("table.create", "uploads.create");
5156
return this.post<CreateTableResult>(
5257
"table/create",
5358
withDefaults<CreateTableArgs>(args, { description: "", is_private: false }),
@@ -59,8 +64,10 @@ export class TableAPI extends Router {
5964
* Delete a Dune table with the specified name and namespace.
6065
*
6166
* To be able to delete a table, it must have been created with the /create endpoint.
67+
* @deprecated Use uploads.delete() instead. The /v1/table endpoints are deprecated in favor of /v1/uploads.
6268
*/
6369
async delete(args: DeleteTableArgs): Promise<DeleteTableResult> {
70+
deprecationWarning("table.delete", "uploads.delete");
6471
const route = `table/${args.namespace}/${args.table_name}`;
6572
return this._delete<DeleteTableResult>(route);
6673
}
@@ -73,8 +80,10 @@ export class TableAPI extends Router {
7380
* - The file has to have the same schema as the table
7481
* @param args
7582
* @returns
83+
* @deprecated Use uploads.insert() instead. The /v1/table endpoints are deprecated in favor of /v1/uploads.
7684
*/
7785
async insert(args: InsertTableArgs): Promise<InsertTableResult> {
86+
deprecationWarning("table.insert", "uploads.insert");
7887
return this.post<InsertTableResult>(
7988
`table/${args.namespace}/${args.table_name}/insert`,
8089
args.data,

src/api/uploads.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Router } from "./router";
2+
import {
3+
CreateTableResult,
4+
SuccessResponse,
5+
UploadCSVArgs,
6+
CreateTableArgs,
7+
InsertTableArgs,
8+
InsertTableResult,
9+
DeleteTableArgs,
10+
DeleteTableResult,
11+
TableListResponse,
12+
ListUploadsArgs,
13+
ClearTableArgs,
14+
TableClearResponse,
15+
DuneError,
16+
} from "../types";
17+
import { withDefaults } from "../utils";
18+
19+
export class UploadsAPI extends Router {
20+
async list(args?: ListUploadsArgs): Promise<TableListResponse> {
21+
return this._get<TableListResponse>("uploads", args);
22+
}
23+
24+
async create(args: CreateTableArgs): Promise<CreateTableResult> {
25+
return this.post<CreateTableResult>(
26+
"uploads",
27+
withDefaults<CreateTableArgs>(args, { description: "", is_private: false }),
28+
);
29+
}
30+
31+
async uploadCsv(args: UploadCSVArgs): Promise<boolean> {
32+
const response = await this.post<SuccessResponse>("uploads/csv", args);
33+
try {
34+
return Boolean(response.success);
35+
} catch (error: unknown) {
36+
console.error(
37+
`Upload CSV Error ${error instanceof Error ? error.message : String(error)}`,
38+
);
39+
throw new DuneError(`UploadCsvResponse ${JSON.stringify(response)}`);
40+
}
41+
}
42+
43+
async delete(args: DeleteTableArgs): Promise<DeleteTableResult> {
44+
const route = `uploads/${args.namespace}/${args.table_name}`;
45+
return this._delete<DeleteTableResult>(route);
46+
}
47+
48+
async clear(args: ClearTableArgs): Promise<TableClearResponse> {
49+
const route = `uploads/${args.namespace}/${args.table_name}/clear`;
50+
return this.post<TableClearResponse>(route);
51+
}
52+
53+
async insert(args: InsertTableArgs): Promise<InsertTableResult> {
54+
return this.post<InsertTableResult>(
55+
`uploads/${args.namespace}/${args.table_name}/insert`,
56+
args.data,
57+
args.content_type,
58+
);
59+
}
60+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export {
66
TableAPI,
77
UsageAPI,
88
PipelineAPI,
9+
DatasetAPI,
10+
UploadsAPI,
911
} from "./api";
1012
export * from "./types";
1113
export { Paginator } from "./paginator";

src/types/requestArgs.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,31 @@ export interface InsertTableArgs {
304304
content_type: ContentType;
305305
}
306306

307+
export interface ListDatasetsArgs {
308+
/// Number of results to return (default 50, max 250)
309+
limit?: number;
310+
/// Offset for pagination
311+
offset?: number;
312+
/// Filter by owner handle
313+
owner_handle?: string;
314+
/// Filter by dataset types (comma-separated: transformation_view, transformation_table, uploaded_table, decoded_table, spell, dune_table)
315+
type?: string;
316+
}
317+
318+
export interface ListUploadsArgs {
319+
/// Number of tables to return on a page. Default: 50, max: 10000
320+
limit?: number;
321+
/// Offset used for pagination. Negative values are treated as 0
322+
offset?: number;
323+
}
324+
325+
export interface ClearTableArgs {
326+
/// The namespace of the table to clear (e.g. my_user).
327+
namespace: string;
328+
/// The name of the table to clear (e.g. interest_rates).
329+
table_name: string;
330+
}
331+
307332
export interface Options {
308333
/// The page size when retriving results.
309334
batchSize?: number;

src/types/response.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,68 @@ export interface UsageResponse {
247247
bytes_allowed: number;
248248
billing_periods: BillingPeriod[];
249249
}
250+
251+
export interface DatasetColumnMetadata {
252+
description?: string;
253+
filtering_column?: boolean;
254+
}
255+
256+
export interface DatasetColumn {
257+
name: string;
258+
type: string;
259+
nullable?: boolean;
260+
metadata?: DatasetColumnMetadata;
261+
}
262+
263+
export interface DatasetOwner {
264+
handle: string;
265+
type: string;
266+
}
267+
268+
export interface DatasetResponse {
269+
full_name: string;
270+
type: string;
271+
columns: DatasetColumn[];
272+
owner: DatasetOwner;
273+
is_private: boolean;
274+
created_at: string;
275+
updated_at: string;
276+
metadata?: Record<string, unknown>;
277+
}
278+
279+
export interface ListDatasetsResponse {
280+
datasets: DatasetResponse[];
281+
total: number;
282+
}
283+
284+
export interface TableOwner {
285+
handle: string;
286+
type: string;
287+
}
288+
289+
export interface TableColumnInfo {
290+
name: string;
291+
type: string;
292+
nullable?: boolean;
293+
metadata?: Record<string, unknown>;
294+
}
295+
296+
export interface TableListElement {
297+
full_name: string;
298+
is_private: boolean;
299+
owner: TableOwner;
300+
columns: TableColumnInfo[];
301+
table_size_bytes?: string;
302+
created_at: string;
303+
updated_at: string;
304+
purged_at?: string;
305+
}
306+
307+
export interface TableListResponse {
308+
tables: TableListElement[];
309+
next_offset?: number;
310+
}
311+
312+
export interface TableClearResponse {
313+
message: string;
314+
}

tests/e2e/dataset.spec.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import log from "loglevel";
2+
import { DatasetAPI } from "../../src/api";
3+
4+
log.setLevel("silent", true);
5+
6+
const API_KEY = process.env.DUNE_API_KEY!;
7+
8+
describe("Dataset API", () => {
9+
let datasetClient: DatasetAPI;
10+
11+
beforeAll(() => {
12+
datasetClient = new DatasetAPI(API_KEY);
13+
});
14+
15+
it("lists datasets with owner filter", async () => {
16+
const response = await datasetClient.list({
17+
limit: 5,
18+
owner_handle: "dune",
19+
});
20+
21+
expect(response.datasets).toBeDefined();
22+
expect(Array.isArray(response.datasets)).toBe(true);
23+
24+
if (response.datasets.length > 0) {
25+
expect(response.datasets[0].owner.handle).toBe("dune");
26+
}
27+
});
28+
29+
it("lists datasets with owner filter", async () => {
30+
const response = await datasetClient.list({
31+
limit: 1,
32+
type: "materialized_view",
33+
});
34+
35+
expect(response.datasets).toBeDefined();
36+
expect(Array.isArray(response.datasets)).toBe(true);
37+
38+
expect(response.datasets[0].type).toBe("materialized_view");
39+
});
40+
41+
it("gets dataset by slug", async () => {
42+
const dataset = await datasetClient.getBySlug("dex.trades");
43+
44+
expect(dataset).toHaveProperty("full_name");
45+
expect(dataset.full_name).toContain("dex.trades");
46+
expect(dataset).toHaveProperty("type");
47+
expect(dataset).toHaveProperty("columns");
48+
expect(dataset).toHaveProperty("owner");
49+
expect(dataset).toHaveProperty("is_private");
50+
expect(dataset).toHaveProperty("created_at");
51+
expect(dataset).toHaveProperty("updated_at");
52+
53+
expect(Array.isArray(dataset.columns)).toBe(true);
54+
expect(dataset.columns.length).toBeGreaterThan(0);
55+
56+
const firstColumn = dataset.columns[0];
57+
expect(firstColumn).toHaveProperty("name");
58+
expect(firstColumn).toHaveProperty("type");
59+
});
60+
});

0 commit comments

Comments
 (0)