dremiojs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +14 -0
- package/.prettierrc +7 -0
- package/README.md +59 -0
- package/dremiodocs/dremio-cloud/cloud-api-reference.md +748 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-about.md +225 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-admin.md +3754 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-bring-data.md +6098 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-changelog.md +32 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-developer.md +1147 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-explore-analyze.md +2522 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-get-started.md +300 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-help-support.md +869 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-manage-govern.md +800 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-overview.md +36 -0
- package/dremiodocs/dremio-cloud/dremio-cloud-security.md +1844 -0
- package/dremiodocs/dremio-cloud/sql-docs.md +7180 -0
- package/dremiodocs/dremio-software/dremio-software-acceleration.md +1575 -0
- package/dremiodocs/dremio-software/dremio-software-admin.md +884 -0
- package/dremiodocs/dremio-software/dremio-software-client-applications.md +3277 -0
- package/dremiodocs/dremio-software/dremio-software-data-products.md +560 -0
- package/dremiodocs/dremio-software/dremio-software-data-sources.md +8701 -0
- package/dremiodocs/dremio-software/dremio-software-deploy-dremio.md +3446 -0
- package/dremiodocs/dremio-software/dremio-software-get-started.md +848 -0
- package/dremiodocs/dremio-software/dremio-software-monitoring.md +422 -0
- package/dremiodocs/dremio-software/dremio-software-reference.md +677 -0
- package/dremiodocs/dremio-software/dremio-software-security.md +2074 -0
- package/dremiodocs/dremio-software/dremio-software-v25-api.md +32637 -0
- package/dremiodocs/dremio-software/dremio-software-v26-api.md +36757 -0
- package/jest.config.js +10 -0
- package/package.json +25 -0
- package/src/api/catalog.ts +74 -0
- package/src/api/jobs.ts +105 -0
- package/src/api/reflection.ts +77 -0
- package/src/api/source.ts +61 -0
- package/src/api/user.ts +32 -0
- package/src/client/base.ts +66 -0
- package/src/client/cloud.ts +37 -0
- package/src/client/software.ts +73 -0
- package/src/index.ts +16 -0
- package/src/types/catalog.ts +31 -0
- package/src/types/config.ts +18 -0
- package/src/types/job.ts +18 -0
- package/src/types/reflection.ts +29 -0
- package/tests/integration_manual.ts +95 -0
- package/tsconfig.json +19 -0
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dremiojs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@types/jest": "^30.0.0",
|
|
15
|
+
"@types/node": "^25.0.3",
|
|
16
|
+
"axios": "^1.13.2",
|
|
17
|
+
"dotenv": "^17.2.3",
|
|
18
|
+
"eslint": "^9.39.2",
|
|
19
|
+
"jest": "^30.2.0",
|
|
20
|
+
"prettier": "^3.7.4",
|
|
21
|
+
"ts-jest": "^29.4.6",
|
|
22
|
+
"ts-node": "^10.9.2",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
import { CatalogItem } from '../types/catalog';
|
|
3
|
+
|
|
4
|
+
export class CatalogResource {
|
|
5
|
+
private client: BaseClient;
|
|
6
|
+
|
|
7
|
+
constructor(client: BaseClient) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get a catalog item by ID.
|
|
13
|
+
*/
|
|
14
|
+
async get(id: string): Promise<CatalogItem> {
|
|
15
|
+
// Need access to protected request method of client.
|
|
16
|
+
// Usually we'd expose a public 'request' or make CatalogResource extend BaseClient (but that's wrong inheritance).
|
|
17
|
+
// Or we pass a request function.
|
|
18
|
+
// For now, let's assume we can call a public method on client, or cast it to any.
|
|
19
|
+
// Better: Make `request` public on BaseClient but keep it low-level.
|
|
20
|
+
return this.client.request({
|
|
21
|
+
method: 'GET',
|
|
22
|
+
url: `/catalog/${encodeURIComponent(id)}`,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get a catalog item by Path.
|
|
28
|
+
* Path should be an array of strings ['space', 'folder', 'dataset']
|
|
29
|
+
*/
|
|
30
|
+
async getByPath(path: string[]): Promise<CatalogItem> {
|
|
31
|
+
if (path.length === 0) {
|
|
32
|
+
const response: any = await this.client.request({
|
|
33
|
+
method: 'GET',
|
|
34
|
+
url: '/catalog'
|
|
35
|
+
});
|
|
36
|
+
// Normalize root response which is usually { data: [...] }
|
|
37
|
+
const children = response.data || (Array.isArray(response) ? response : []);
|
|
38
|
+
return {
|
|
39
|
+
id: 'ROOT',
|
|
40
|
+
path: [],
|
|
41
|
+
type: 'CONTAINER',
|
|
42
|
+
containerType: 'HOME',
|
|
43
|
+
children: children
|
|
44
|
+
} as CatalogItem;
|
|
45
|
+
}
|
|
46
|
+
return this.client.request({
|
|
47
|
+
method: 'GET',
|
|
48
|
+
url: `/catalog/by-path/${path.map(p => encodeURIComponent(p)).join('/')}` // Note: formatting might vary
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async create(item: Partial<CatalogItem>): Promise<CatalogItem> {
|
|
53
|
+
return this.client.request({
|
|
54
|
+
method: 'POST',
|
|
55
|
+
url: '/catalog',
|
|
56
|
+
data: item
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async update(id: string, item: Partial<CatalogItem>): Promise<CatalogItem> {
|
|
61
|
+
return this.client.request({
|
|
62
|
+
method: 'PUT',
|
|
63
|
+
url: `/catalog/${encodeURIComponent(id)}`,
|
|
64
|
+
data: item
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async delete(id: string): Promise<void> {
|
|
69
|
+
return this.client.request({
|
|
70
|
+
method: 'DELETE',
|
|
71
|
+
url: `/catalog/${encodeURIComponent(id)}`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/api/jobs.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
import { JobState, JobStatus, JobSubmissionResponse, JobResultsResponse } from '../types/job';
|
|
3
|
+
|
|
4
|
+
export class JobResource {
|
|
5
|
+
private client: BaseClient;
|
|
6
|
+
|
|
7
|
+
constructor(client: BaseClient) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Submit a SQL query for execution.
|
|
13
|
+
*/
|
|
14
|
+
async submitSQL(sql: string, context?: string[]): Promise<JobSubmissionResponse> {
|
|
15
|
+
const payload: any = { sql };
|
|
16
|
+
if (context) {
|
|
17
|
+
payload.context = context;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return this.client.request({
|
|
21
|
+
method: 'POST',
|
|
22
|
+
url: '/sql',
|
|
23
|
+
data: payload
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the status of a job.
|
|
29
|
+
*/
|
|
30
|
+
async getJobStatus(jobId: string): Promise<JobStatus> {
|
|
31
|
+
return this.client.request({
|
|
32
|
+
method: 'GET',
|
|
33
|
+
url: `/job/${encodeURIComponent(jobId)}`
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get results of a completed job.
|
|
39
|
+
*/
|
|
40
|
+
async getJobResults(jobId: string, offset = 0, limit = 100): Promise<JobResultsResponse> {
|
|
41
|
+
return this.client.request({
|
|
42
|
+
method: 'GET',
|
|
43
|
+
url: `/job/${encodeURIComponent(jobId)}/results`,
|
|
44
|
+
params: {
|
|
45
|
+
offset,
|
|
46
|
+
limit
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Cancel a job.
|
|
53
|
+
*/
|
|
54
|
+
async cancelJob(jobId: string): Promise<void> {
|
|
55
|
+
// NOTE: Cancellation endpoint varies.
|
|
56
|
+
// Software: POST /api/v3/job/{id}/cancel
|
|
57
|
+
// Cloud: POST /v0/projects/{id}/job/{id}/cancel (verify?)
|
|
58
|
+
|
|
59
|
+
// Usually Cloud uses delete method or cancel action?
|
|
60
|
+
// Documentation check: "Cancel Job"
|
|
61
|
+
|
|
62
|
+
// Let's assume standard POST /job/{id}/cancel pattern works for both relative to base/project.
|
|
63
|
+
// If it doesn't, we might need correction.
|
|
64
|
+
return this.client.request({
|
|
65
|
+
method: 'POST',
|
|
66
|
+
url: `/job/${encodeURIComponent(jobId)}/cancel`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Helper: Execute SQL and wait for completion.
|
|
72
|
+
* Polls every 1s by default.
|
|
73
|
+
*/
|
|
74
|
+
async executeQuery(sql: string, options: { context?: string[], pollIntervalMs?: number } = {}): Promise<any[]> {
|
|
75
|
+
// 1. Submit
|
|
76
|
+
const submission = await this.submitSQL(sql, options.context);
|
|
77
|
+
const jobId = submission.id;
|
|
78
|
+
|
|
79
|
+
// 2. Poll
|
|
80
|
+
const pollInterval = options.pollIntervalMs || 1000;
|
|
81
|
+
let jobState: JobState = 'STARTING';
|
|
82
|
+
|
|
83
|
+
while (true) {
|
|
84
|
+
const status = await this.getJobStatus(jobId);
|
|
85
|
+
jobState = status.jobState;
|
|
86
|
+
|
|
87
|
+
if (jobState === 'COMPLETED') {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (jobState === 'FAILED' || jobState === 'CANCELED') {
|
|
92
|
+
throw new Error(`Job ${jobId} failed with state ${jobState}: ${status.errorMessage}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Wait
|
|
96
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 3. fetch results
|
|
100
|
+
// TODO: Handle pagination if needed? executeQuery usually returns all or iterator.
|
|
101
|
+
// For simplicity/safety, let's fetch first 500 rows.
|
|
102
|
+
const results = await this.getJobResults(jobId, 0, 500);
|
|
103
|
+
return results.rows;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
import { Reflection, ReflectionSummary } from '../types/reflection';
|
|
3
|
+
|
|
4
|
+
export class ReflectionResource {
|
|
5
|
+
private client: BaseClient;
|
|
6
|
+
|
|
7
|
+
constructor(client: BaseClient) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async list(options: { datasetId?: string } = {}): Promise<ReflectionSummary[]> {
|
|
12
|
+
const params: any = {};
|
|
13
|
+
// Filter by datasetId if provided (common use case, though client-side filtering might be needed if API doesn't support it directly on list)
|
|
14
|
+
// Dremio API docs v3 suggest /reflection endpoint returns all? Or supports filter?
|
|
15
|
+
// Convention usually supports 'filter' query param.
|
|
16
|
+
// Dremio Cloud: /reflection?filter=datasetId=="..."
|
|
17
|
+
// Software: similar.
|
|
18
|
+
|
|
19
|
+
// Let's assume we return raw list and let the user filter, or try to use `filter` param if we can construct RQL.
|
|
20
|
+
// For now, simpler is creating a filtered list if possible or just returning all.
|
|
21
|
+
// NOTE: Listing ALL reflections can be heavy.
|
|
22
|
+
|
|
23
|
+
// Checking `dremio_api_integration_patterns`: "When listing, applications often need to filter by `datasetId` locally".
|
|
24
|
+
|
|
25
|
+
const response = await this.client.request<{ data?: ReflectionSummary[], dataCount?: number }>({
|
|
26
|
+
method: 'GET',
|
|
27
|
+
url: '/reflection'
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Software/Cloud return list in different structure?
|
|
31
|
+
// Software v3 returns { data: [...] }
|
|
32
|
+
// Cloud v0 returns { data: [...] } ?
|
|
33
|
+
|
|
34
|
+
let allReflections: ReflectionSummary[] = [];
|
|
35
|
+
if (Array.isArray(response)) {
|
|
36
|
+
allReflections = response;
|
|
37
|
+
} else if (response.data && Array.isArray(response.data)) {
|
|
38
|
+
allReflections = response.data;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (options.datasetId) {
|
|
42
|
+
return allReflections.filter(r => r.datasetId === options.datasetId);
|
|
43
|
+
}
|
|
44
|
+
return allReflections;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async get(id: string): Promise<Reflection> {
|
|
48
|
+
return this.client.request({
|
|
49
|
+
method: 'GET',
|
|
50
|
+
url: `/reflection/${encodeURIComponent(id)}`
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async create(reflection: Partial<Reflection>): Promise<Reflection> {
|
|
55
|
+
return this.client.request({
|
|
56
|
+
method: 'POST',
|
|
57
|
+
url: '/reflection',
|
|
58
|
+
data: reflection
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async update(id: string, reflection: Partial<Reflection>): Promise<Reflection> {
|
|
63
|
+
// Optimistic locking? Usually requires `tag`.
|
|
64
|
+
return this.client.request({
|
|
65
|
+
method: 'PUT',
|
|
66
|
+
url: `/reflection/${encodeURIComponent(id)}`,
|
|
67
|
+
data: reflection
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async delete(id: string): Promise<void> {
|
|
72
|
+
return this.client.request({
|
|
73
|
+
method: 'DELETE',
|
|
74
|
+
url: `/reflection/${encodeURIComponent(id)}`
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
import { Source } from '../types/catalog';
|
|
3
|
+
|
|
4
|
+
export class SourceResource {
|
|
5
|
+
private client: BaseClient;
|
|
6
|
+
|
|
7
|
+
constructor(client: BaseClient) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async list(): Promise<Source[]> {
|
|
12
|
+
// Sources are catalog items with type=SOURCE
|
|
13
|
+
// Dremio API doesn't always support filtering by type in top level listing easily without ?include=...
|
|
14
|
+
// but /catalog root usually returns top level spaces and sources.
|
|
15
|
+
// Dremio Cloud: /v0/projects/{id}/catalog returns sources and spaces.
|
|
16
|
+
|
|
17
|
+
const response = await this.client.request<{ data?: any[] }>({
|
|
18
|
+
method: 'GET',
|
|
19
|
+
url: '/catalog'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
let items: any[] = [];
|
|
23
|
+
if (Array.isArray(response)) {
|
|
24
|
+
items = response;
|
|
25
|
+
} else if (response.data && Array.isArray(response.data)) {
|
|
26
|
+
items = response.data;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return items.filter(item => item.type === 'SOURCE' || item.containerType === 'SOURCE');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async get(id: string): Promise<Source> {
|
|
33
|
+
return this.client.request({
|
|
34
|
+
method: 'GET',
|
|
35
|
+
url: `/catalog/${encodeURIComponent(id)}`
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async create(source: Source): Promise<Source> {
|
|
40
|
+
return this.client.request({
|
|
41
|
+
method: 'POST',
|
|
42
|
+
url: '/catalog',
|
|
43
|
+
data: source
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async update(id: string, source: Partial<Source>): Promise<Source> {
|
|
48
|
+
return this.client.request({
|
|
49
|
+
method: 'PUT',
|
|
50
|
+
url: `/catalog/${encodeURIComponent(id)}`,
|
|
51
|
+
data: source
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async delete(id: string): Promise<void> {
|
|
56
|
+
return this.client.request({
|
|
57
|
+
method: 'DELETE',
|
|
58
|
+
url: `/catalog/${encodeURIComponent(id)}`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/api/user.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export interface User {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
firstName?: string;
|
|
7
|
+
lastName?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
roles?: any[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class UserResource {
|
|
13
|
+
private client: BaseClient;
|
|
14
|
+
|
|
15
|
+
constructor(client: BaseClient) {
|
|
16
|
+
this.client = client;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async get(id: string): Promise<User> {
|
|
20
|
+
return this.client.request({
|
|
21
|
+
method: 'GET',
|
|
22
|
+
url: `/user/${encodeURIComponent(id)}`
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getByName(name: string): Promise<User> {
|
|
27
|
+
return this.client.request({
|
|
28
|
+
method: 'GET',
|
|
29
|
+
url: `/user/by-name/${encodeURIComponent(name)}`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
import { BaseDremioConfig } from '../types/config';
|
|
3
|
+
import { CatalogResource } from '../api/catalog';
|
|
4
|
+
import { JobResource } from '../api/jobs';
|
|
5
|
+
import { ReflectionResource } from '../api/reflection';
|
|
6
|
+
import { SourceResource } from '../api/source';
|
|
7
|
+
import { UserResource } from '../api/user';
|
|
8
|
+
|
|
9
|
+
export abstract class BaseClient {
|
|
10
|
+
protected axiosInstance: AxiosInstance;
|
|
11
|
+
protected config: BaseDremioConfig;
|
|
12
|
+
public catalog: CatalogResource;
|
|
13
|
+
public jobs: JobResource;
|
|
14
|
+
public reflections: ReflectionResource;
|
|
15
|
+
public sources: SourceResource;
|
|
16
|
+
public users: UserResource;
|
|
17
|
+
|
|
18
|
+
constructor(config: BaseDremioConfig) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.axiosInstance = axios.create({
|
|
21
|
+
baseURL: config.baseUrl,
|
|
22
|
+
timeout: config.timeout || 30000,
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
this.catalog = new CatalogResource(this);
|
|
29
|
+
this.jobs = new JobResource(this);
|
|
30
|
+
this.reflections = new ReflectionResource(this);
|
|
31
|
+
this.sources = new SourceResource(this);
|
|
32
|
+
this.users = new UserResource(this);
|
|
33
|
+
|
|
34
|
+
// Add generic interceptors if needed
|
|
35
|
+
this.axiosInstance.interceptors.response.use(
|
|
36
|
+
(response) => response,
|
|
37
|
+
(error) => {
|
|
38
|
+
// Enhance error handling here
|
|
39
|
+
return Promise.reject(error);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected abstract getAuthHeaders(): Record<string, string> | Promise<Record<string, string>>;
|
|
45
|
+
|
|
46
|
+
public async request<T = any>(config: AxiosRequestConfig): Promise<T> {
|
|
47
|
+
const authHeaders = await this.getAuthHeaders();
|
|
48
|
+
const mergedConfig = {
|
|
49
|
+
...config,
|
|
50
|
+
headers: {
|
|
51
|
+
...config.headers,
|
|
52
|
+
...authHeaders,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const response: AxiosResponse<T> = await this.axiosInstance.request(mergedConfig);
|
|
58
|
+
return response.data;
|
|
59
|
+
} catch (error: any) {
|
|
60
|
+
if (error.response) {
|
|
61
|
+
throw new Error(`Dremio API Error: ${error.response.status} ${error.response.statusText} - ${JSON.stringify(error.response.data)}`);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseClient } from './base';
|
|
2
|
+
import { DremioCloudConfig } from '../types/config';
|
|
3
|
+
|
|
4
|
+
export class DremioCloudClient extends BaseClient {
|
|
5
|
+
protected config: DremioCloudConfig;
|
|
6
|
+
|
|
7
|
+
constructor(config: DremioCloudConfig) {
|
|
8
|
+
if (!config.authToken) {
|
|
9
|
+
throw new Error('Auth token is required for Dremio Cloud.');
|
|
10
|
+
}
|
|
11
|
+
if (!config.projectId) {
|
|
12
|
+
throw new Error('Project ID is required for Dremio Cloud.');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Adjust BaseUrl to include project context
|
|
16
|
+
const configWithProject = { ...config };
|
|
17
|
+
if (!configWithProject.baseUrl.includes('/projects/')) {
|
|
18
|
+
// Remove trailing slash if present
|
|
19
|
+
const cleanBase = configWithProject.baseUrl.replace(/\/$/, '');
|
|
20
|
+
configWithProject.baseUrl = `${cleanBase}/projects/${config.projectId}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
super(configWithProject);
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
protected getAuthHeaders(): Record<string, string> {
|
|
28
|
+
return {
|
|
29
|
+
Authorization: `Bearer ${this.config.authToken}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Cloud specific methods can go here or generic methods can use projectId from config
|
|
34
|
+
getProjectId(): string {
|
|
35
|
+
return this.config.projectId;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { BaseClient } from './base';
|
|
2
|
+
import { DremioSoftwareConfig } from '../types/config';
|
|
3
|
+
|
|
4
|
+
interface LoginResponse {
|
|
5
|
+
token: string;
|
|
6
|
+
// Add other fields if necessary
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class DremioSoftwareClient extends BaseClient {
|
|
10
|
+
protected config: DremioSoftwareConfig;
|
|
11
|
+
private token: string | null = null;
|
|
12
|
+
|
|
13
|
+
constructor(config: DremioSoftwareConfig) {
|
|
14
|
+
super(config);
|
|
15
|
+
this.config = config;
|
|
16
|
+
if (config.authToken) {
|
|
17
|
+
this.token = config.authToken;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected async getAuthHeaders(): Promise<Record<string, string>> {
|
|
22
|
+
if (!this.token) {
|
|
23
|
+
await this.login();
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
Authorization: `Bearer ${this.token}`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private async login(): Promise<void> {
|
|
31
|
+
if (!this.config.username || !this.config.password) {
|
|
32
|
+
throw new Error('Username and password are required for login if no token is provided.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Heuristic to handle base URL variations for login
|
|
36
|
+
// If base URL is .../api/v3, login is often at .../apiv2/login
|
|
37
|
+
let loginUrl = '/apiv2/login';
|
|
38
|
+
if (this.config.baseUrl.endsWith('/api/v3')) {
|
|
39
|
+
const baseUrlRoot = this.config.baseUrl.replace('/api/v3', '');
|
|
40
|
+
// We will perform a direct axios call to the root + /apiv2/login
|
|
41
|
+
// But since our instance is bound to baseUrl, we might need a separate call or specific path
|
|
42
|
+
// Actually, axios instance is bound to /api/v3 usually.
|
|
43
|
+
|
|
44
|
+
// Let's rely on the user providing a correct Base URL.
|
|
45
|
+
// IMPLEMENTATION NOTE: Dremio Software V3 API usually sits at /api/v3.
|
|
46
|
+
// Login is at /apiv2/login.
|
|
47
|
+
// We need to construct the login URL relative to the server root, not the V3 API root.
|
|
48
|
+
// For simplicity, we will try to infer the root.
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Workaround: create a new axios request for login that overrides the baseURL if necessary
|
|
52
|
+
// or just use absolute URL if we can derive it.
|
|
53
|
+
|
|
54
|
+
// Simplest approach: Assume config.baseUrl is the API base.
|
|
55
|
+
// We'll strip the API part for the login call.
|
|
56
|
+
const apiBase = this.config.baseUrl;
|
|
57
|
+
const rootUrl = apiBase.replace(/\/api\/v3\/?$/, '');
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Direct call using imported axios (not the instance) to avoid prefix issues if needed,
|
|
61
|
+
// or just re-config the instance call.
|
|
62
|
+
const response = await this.axiosInstance.post<LoginResponse>(`${rootUrl}/apiv2/login`, {
|
|
63
|
+
userName: this.config.username,
|
|
64
|
+
password: this.config.password,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
this.token = response.data.token;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Login failed');
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Clients
|
|
2
|
+
export { DremioCloudClient } from './client/cloud';
|
|
3
|
+
export { DremioSoftwareClient } from './client/software';
|
|
4
|
+
|
|
5
|
+
// Types
|
|
6
|
+
export * from './types/config';
|
|
7
|
+
export * from './types/catalog';
|
|
8
|
+
export * from './types/job';
|
|
9
|
+
export * from './types/reflection';
|
|
10
|
+
|
|
11
|
+
// Resources (if needed directly, though usually accessed via client)
|
|
12
|
+
export * from './api/catalog';
|
|
13
|
+
export * from './api/jobs';
|
|
14
|
+
export * from './api/reflection';
|
|
15
|
+
export * from './api/source';
|
|
16
|
+
export * from './api/user';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type CatalogType = 'CONTAINER' | 'DATASET' | 'FILE' | 'HOME' | 'SPACE' | 'SOURCE' | 'FOLDER';
|
|
2
|
+
|
|
3
|
+
export interface CatalogItem {
|
|
4
|
+
id: string;
|
|
5
|
+
path: string[];
|
|
6
|
+
tag?: string;
|
|
7
|
+
type: CatalogType;
|
|
8
|
+
containerType?: string; // HOME, SPACE, SOURCE, FOLDER
|
|
9
|
+
createdAt?: string;
|
|
10
|
+
children?: CatalogItem[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Dataset extends CatalogItem {
|
|
14
|
+
type: 'DATASET';
|
|
15
|
+
fields?: any[];
|
|
16
|
+
sql?: string;
|
|
17
|
+
format?: any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface Source extends CatalogItem {
|
|
21
|
+
type: 'SOURCE';
|
|
22
|
+
config?: any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Space extends CatalogItem {
|
|
26
|
+
type: 'SPACE';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Folder extends CatalogItem {
|
|
30
|
+
type: 'FOLDER';
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BaseDremioConfig {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
timeout?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface DremioCloudConfig extends BaseDremioConfig {
|
|
7
|
+
authToken: string;
|
|
8
|
+
projectId: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface DremioSoftwareConfig extends BaseDremioConfig {
|
|
12
|
+
authToken?: string;
|
|
13
|
+
username?: string;
|
|
14
|
+
password?: string;
|
|
15
|
+
checkSSLCerts?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type DremioConfig = DremioCloudConfig | DremioSoftwareConfig;
|
package/src/types/job.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type JobState = 'NOT_SUBMITTED' | 'STARTING' | 'RUNNING' | 'COMPLETED' | 'CANCELED' | 'FAILED' | 'QUEUED' | 'PLANNING' | 'ENGINE_START' | 'EXECUTION_PLANNING';
|
|
2
|
+
|
|
3
|
+
export interface JobStatus {
|
|
4
|
+
id: string;
|
|
5
|
+
jobState: JobState;
|
|
6
|
+
errorMessage?: string;
|
|
7
|
+
rowCount?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface JobSubmissionResponse {
|
|
11
|
+
id: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface JobResultsResponse {
|
|
15
|
+
rowCount: number;
|
|
16
|
+
schema: any[];
|
|
17
|
+
rows: any[];
|
|
18
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ReflectionType = 'RAW' | 'AGGREGATION';
|
|
2
|
+
|
|
3
|
+
export interface Reflection {
|
|
4
|
+
id: string;
|
|
5
|
+
type: ReflectionType;
|
|
6
|
+
name: string;
|
|
7
|
+
datasetId: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
status?: {
|
|
10
|
+
availability: string;
|
|
11
|
+
config: string;
|
|
12
|
+
refresh: string;
|
|
13
|
+
};
|
|
14
|
+
dimensionFields?: any[]; // Simplified
|
|
15
|
+
measureFields?: any[]; // Simplified
|
|
16
|
+
distributionFields?: any[];
|
|
17
|
+
partitionFields?: any[];
|
|
18
|
+
tag?: string;
|
|
19
|
+
createdAt?: string;
|
|
20
|
+
updatedAt?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ReflectionSummary {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
type: ReflectionType;
|
|
27
|
+
datasetId: string;
|
|
28
|
+
status: string;
|
|
29
|
+
}
|