dremiojs 1.0.0 → 1.2.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/README.md +69 -19
- package/docs/catalog.md +76 -0
- package/docs/client.md +55 -0
- package/docs/configuration.md +150 -0
- package/docs/governance_collaboration.md +41 -0
- package/docs/provisioning.md +15 -0
- package/docs/reflections.md +54 -0
- package/docs/scripts_lineage_tokens.md +45 -0
- package/docs/service_users.md +77 -0
- package/docs/sources.md +53 -0
- package/docs/sql_and_jobs.md +59 -0
- package/docs/users.md +19 -0
- package/package.json +10 -2
- package/src/api/collaboration.ts +55 -0
- package/src/api/credentials.ts +74 -0
- package/src/api/governance.ts +52 -0
- package/src/api/lineage.ts +19 -0
- package/src/api/provisioning.ts +32 -0
- package/src/api/scripts.ts +64 -0
- package/src/api/token.ts +50 -0
- package/src/api/user.ts +39 -3
- package/src/client/base.ts +25 -0
- package/src/client/cloud.ts +51 -5
- package/src/client/software.ts +77 -33
- package/src/factory.ts +20 -0
- package/src/index.ts +1 -1
- package/src/types/config.ts +28 -5
- package/src/utils/config.ts +113 -0
- package/tests/integration_manual.ts +49 -0
- package/tests/verify_profiles.ts +28 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# SQL & Jobs API Guide
|
|
2
|
+
|
|
3
|
+
Executing SQL in Dremio is an asynchronous process involving job submission, polling, and result retrieval. `dremiojs` simplifies this with a helper method while still exposing raw controls.
|
|
4
|
+
|
|
5
|
+
## The Easy Way: `executeQuery`
|
|
6
|
+
|
|
7
|
+
The `executeQuery` method handles the entire lifecycle: submitting the job, polling for completion, and fetching results.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const rows = await client.jobs.executeQuery('SELECT * FROM sys.version');
|
|
11
|
+
console.log(rows);
|
|
12
|
+
// Output: [{ version: '24.2.0', ... }]
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Context
|
|
16
|
+
You can provide a context for the query (e.g., to simplify table names).
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// Queries table inside 'MarketingSpace' without full qualification
|
|
20
|
+
const rows = await client.jobs.executeQuery('SELECT * FROM "Campaigns"', {
|
|
21
|
+
context: ['MarketingSpace']
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## The Query Lifecycle (Manual Control)
|
|
26
|
+
|
|
27
|
+
For long-running queries or advanced UI integration (like progress bars), you may want to manage the lifecycle yourself.
|
|
28
|
+
|
|
29
|
+
### 1. Submit Job
|
|
30
|
+
```typescript
|
|
31
|
+
const jobStub = await client.jobs.submitSQL('SELECT count(*) FROM big_table');
|
|
32
|
+
const jobId = jobStub.id;
|
|
33
|
+
console.log('Job submitted:', jobId);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Check Status
|
|
37
|
+
Poll the job status until it reaches a terminal state (`COMPLETED`, `FAILED`, `CANCELED`).
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const status = await client.jobs.getJobStatus(jobId);
|
|
41
|
+
console.log('Current State:', status.jobState); // e.g., 'RUNNING'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 3. Get Results
|
|
45
|
+
Once the job is `COMPLETED`, fetch the results.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Fetch first 100 rows
|
|
49
|
+
const results = await client.jobs.getJobResults(jobId, 0, 100);
|
|
50
|
+
console.log('Rows:', results.rows);
|
|
51
|
+
console.log('Schema:', results.schema);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. Cancel Job
|
|
55
|
+
If the user wants to stop the query:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
await client.jobs.cancelJob(jobId);
|
|
59
|
+
```
|
package/docs/users.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Users API Guide
|
|
2
|
+
|
|
3
|
+
The Users API allows you to lookup user information. This is useful for administration and governance workflows.
|
|
4
|
+
|
|
5
|
+
## Getting a User by ID
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
const user = await client.users.get('user-guid');
|
|
9
|
+
console.log(user.name, user.email);
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Getting a User by Name
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
const user = await client.users.getByName('admin');
|
|
16
|
+
console.log(user.id);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
*Note: User management capabilities (Create/Update/Delete) are typically restricted to administrative APIs and may differ significantly between Software and Cloud. The current library focuses on lookup/retrieval.*
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dremiojs",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "A TypeScript client library for Dremio Cloud and Software.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/developer-advocacy-dremio/dremiojs.git"
|
|
8
|
+
},
|
|
5
9
|
"main": "index.js",
|
|
6
10
|
"scripts": {
|
|
7
11
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -12,6 +16,7 @@
|
|
|
12
16
|
"type": "commonjs",
|
|
13
17
|
"devDependencies": {
|
|
14
18
|
"@types/jest": "^30.0.0",
|
|
19
|
+
"@types/js-yaml": "^4.0.9",
|
|
15
20
|
"@types/node": "^25.0.3",
|
|
16
21
|
"axios": "^1.13.2",
|
|
17
22
|
"dotenv": "^17.2.3",
|
|
@@ -21,5 +26,8 @@
|
|
|
21
26
|
"ts-jest": "^29.4.6",
|
|
22
27
|
"ts-node": "^10.9.2",
|
|
23
28
|
"typescript": "^5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"js-yaml": "^4.1.1"
|
|
24
32
|
}
|
|
25
33
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export interface Tag {
|
|
4
|
+
tags: string[];
|
|
5
|
+
version?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface Wiki {
|
|
9
|
+
text: string;
|
|
10
|
+
version?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class CollaborationResource {
|
|
14
|
+
private client: BaseClient;
|
|
15
|
+
|
|
16
|
+
constructor(client: BaseClient) {
|
|
17
|
+
this.client = client;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async getTags(catalogId: string): Promise<Tag> {
|
|
21
|
+
return this.client.request<Tag>({
|
|
22
|
+
method: 'GET',
|
|
23
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/collaboration/tag`
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async setTags(catalogId: string, tags: string[], version?: string): Promise<void> {
|
|
28
|
+
const data: any = { tags };
|
|
29
|
+
if (version) data.version = version;
|
|
30
|
+
|
|
31
|
+
await this.client.request({
|
|
32
|
+
method: 'POST',
|
|
33
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/collaboration/tag`,
|
|
34
|
+
data
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getWiki(catalogId: string): Promise<Wiki> {
|
|
39
|
+
return this.client.request<Wiki>({
|
|
40
|
+
method: 'GET',
|
|
41
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/collaboration/wiki`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async setWiki(catalogId: string, text: string, version?: string): Promise<void> {
|
|
46
|
+
const data: any = { text };
|
|
47
|
+
if (version) data.version = version;
|
|
48
|
+
|
|
49
|
+
await this.client.request({
|
|
50
|
+
method: 'POST',
|
|
51
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/collaboration/wiki`,
|
|
52
|
+
data
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export interface Credential {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
credentialType: 'CLIENT_SECRET' | 'EXTERNAL_JWT';
|
|
7
|
+
clientSecretConfig?: {
|
|
8
|
+
clientId: string;
|
|
9
|
+
clientSecret?: string; // Only returned on creation
|
|
10
|
+
createdAt?: string;
|
|
11
|
+
expiresAt?: string;
|
|
12
|
+
};
|
|
13
|
+
// ... External JWT config properties could be added here
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class CredentialsResource {
|
|
17
|
+
private client: BaseClient;
|
|
18
|
+
|
|
19
|
+
constructor(client: BaseClient) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* List credentials for a Service User.
|
|
25
|
+
*/
|
|
26
|
+
async list(userId: string): Promise<Credential[]> {
|
|
27
|
+
const response = await this.client.request<Credential[]>({
|
|
28
|
+
method: 'GET',
|
|
29
|
+
url: `/user/${encodeURIComponent(userId)}/oauth/credentials`
|
|
30
|
+
});
|
|
31
|
+
// Response might be wrapped or array, usually array for list endpoints but docs example showed {id...} which is odd for a list.
|
|
32
|
+
// Re-reading docs: GET /user/{id}/oauth/credentials returns A LIST?
|
|
33
|
+
// Actually the doc example showed `curl -X GET .../credentials` returning a SINGLE object?
|
|
34
|
+
// Wait, normally `credentials` implies list. Let's assume list for now or container.
|
|
35
|
+
// Actually, if it returns a single object it might be 'get by id'.
|
|
36
|
+
// Correction: The docs show GET .../credentials returning ONE credential object in the example?
|
|
37
|
+
// That implies you might only have one? Or the doc example is misleading/specific ID.
|
|
38
|
+
// Let's assume it returns an array for safety or we handle both.
|
|
39
|
+
return Array.isArray(response) ? response : [response];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a Client Secret for a Service User.
|
|
44
|
+
* @param userId Service User ID
|
|
45
|
+
* @param name Name for the credential
|
|
46
|
+
* @param lifetimeDays Duration in days (max 180)
|
|
47
|
+
*/
|
|
48
|
+
async create(userId: string, name: string, lifetimeDays: number = 180): Promise<Credential> {
|
|
49
|
+
return this.client.request<Credential>({
|
|
50
|
+
method: 'POST',
|
|
51
|
+
url: `/user/${encodeURIComponent(userId)}/oauth/credentials`,
|
|
52
|
+
data: {
|
|
53
|
+
name,
|
|
54
|
+
credentialType: 'CLIENT_SECRET',
|
|
55
|
+
clientSecretConfig: {
|
|
56
|
+
lifetime: {
|
|
57
|
+
quantity: lifetimeDays,
|
|
58
|
+
units: 'DAYS'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Delete a credential.
|
|
67
|
+
*/
|
|
68
|
+
async delete(userId: string, credentialId: string): Promise<void> {
|
|
69
|
+
await this.client.request({
|
|
70
|
+
method: 'DELETE',
|
|
71
|
+
url: `/user/${encodeURIComponent(userId)}/oauth/credentials/${encodeURIComponent(credentialId)}`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export interface Grant {
|
|
4
|
+
id: string; // User or Role ID
|
|
5
|
+
granteeType: 'USER' | 'ROLE';
|
|
6
|
+
privileges: string[]; // e.g. ['SELECT', 'ALTER']
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AccessControlList {
|
|
10
|
+
users?: { id: string, permissions: string[] }[];
|
|
11
|
+
roles?: { id: string, permissions: string[] }[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class GovernanceResource {
|
|
15
|
+
private client: BaseClient;
|
|
16
|
+
|
|
17
|
+
constructor(client: BaseClient) {
|
|
18
|
+
this.client = client;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Dremio Cloud: Get Grants for a catalog entity.
|
|
23
|
+
* Dremio Software: Typically uses ACLs embedded in Catalog.
|
|
24
|
+
* This method targets the /grants endpoints primarily used in Cloud/Newer Software.
|
|
25
|
+
*/
|
|
26
|
+
async getGrants(catalogId: string): Promise<Grant[]> {
|
|
27
|
+
// Dremio Cloud: /v0/projects/{id}/catalog/{id}/grants
|
|
28
|
+
// Note: ProjectId is handled by client base url interceptor/logic usually.
|
|
29
|
+
const response = await this.client.request<{ grants: Grant[] }>({
|
|
30
|
+
method: 'GET',
|
|
31
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/grants`
|
|
32
|
+
});
|
|
33
|
+
return response.grants || [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Dremio Cloud: Update Grants.
|
|
38
|
+
*/
|
|
39
|
+
async updateGrants(catalogId: string, grants: Grant[]): Promise<void> {
|
|
40
|
+
await this.client.request({
|
|
41
|
+
method: 'PUT',
|
|
42
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/grants`,
|
|
43
|
+
data: { grants }
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Dremio Software: Set ACL (Software v3 style) on an entity.
|
|
49
|
+
* Technically this usually goes via Catalog Update, but we provide a helper here if beneficial.
|
|
50
|
+
* For now, we'll focus on the 'Grants' endpoint which is cleaner if supported.
|
|
51
|
+
*/
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export class LineageResource {
|
|
4
|
+
private client: BaseClient;
|
|
5
|
+
|
|
6
|
+
constructor(client: BaseClient) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async get(catalogId: string): Promise<any> {
|
|
11
|
+
return this.client.request({
|
|
12
|
+
method: 'GET',
|
|
13
|
+
url: `/catalog/${encodeURIComponent(catalogId)}/graph`
|
|
14
|
+
});
|
|
15
|
+
// Note: Graph/Lineage endpoints can vary.
|
|
16
|
+
// Software v3 doc: GET /api/v3/catalog/{id}/graph
|
|
17
|
+
// Cloud doc checks: /v0/projects/{id}/catalog/{id}/graph
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
// Simplified Engine/Queue types
|
|
4
|
+
export interface Engine {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
size?: string;
|
|
8
|
+
status?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ProvisioningResource {
|
|
12
|
+
private client: BaseClient;
|
|
13
|
+
|
|
14
|
+
constructor(client: BaseClient) {
|
|
15
|
+
this.client = client;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Dremio Cloud: List Engines
|
|
20
|
+
*/
|
|
21
|
+
async listEngines(): Promise<Engine[]> {
|
|
22
|
+
// Cloud: /v0/projects/{id}/engines
|
|
23
|
+
const response = await this.client.request<any>({
|
|
24
|
+
method: 'GET',
|
|
25
|
+
url: '/engines'
|
|
26
|
+
});
|
|
27
|
+
// Normalize response
|
|
28
|
+
return response.data || (Array.isArray(response) ? response : []);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Additional methods for start/stop engines or queue management could be added here
|
|
32
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export interface Script {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
content: string; // SQL
|
|
8
|
+
context?: string[];
|
|
9
|
+
createdAt?: string;
|
|
10
|
+
modifiedAt?: string;
|
|
11
|
+
createdBy?: string;
|
|
12
|
+
modifiedBy?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ScriptsResource {
|
|
16
|
+
private client: BaseClient;
|
|
17
|
+
|
|
18
|
+
constructor(client: BaseClient) {
|
|
19
|
+
this.client = client;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async list(options: { maxResults?: number, ownedBy?: string } = {}): Promise<Script[]> {
|
|
23
|
+
const params: any = {};
|
|
24
|
+
if (options.maxResults) params.maxResults = options.maxResults;
|
|
25
|
+
if (options.ownedBy) params.ownedBy = options.ownedBy;
|
|
26
|
+
|
|
27
|
+
const response = await this.client.request<{ data: Script[] }>({
|
|
28
|
+
method: 'GET',
|
|
29
|
+
url: '/scripts',
|
|
30
|
+
params
|
|
31
|
+
});
|
|
32
|
+
return response.data || [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async get(id: string): Promise<Script> {
|
|
36
|
+
return this.client.request<Script>({
|
|
37
|
+
method: 'GET',
|
|
38
|
+
url: `/scripts/${encodeURIComponent(id)}`
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async create(script: Partial<Script>): Promise<Script> {
|
|
43
|
+
return this.client.request<Script>({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
url: '/scripts',
|
|
46
|
+
data: script
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async update(id: string, script: Partial<Script>): Promise<Script> {
|
|
51
|
+
return this.client.request<Script>({
|
|
52
|
+
method: 'PUT',
|
|
53
|
+
url: `/scripts/${encodeURIComponent(id)}`,
|
|
54
|
+
data: script
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(id: string): Promise<void> {
|
|
59
|
+
return this.client.request({
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
url: `/scripts/${encodeURIComponent(id)}`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/api/token.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BaseClient } from '../client/base';
|
|
2
|
+
|
|
3
|
+
export class TokenResource {
|
|
4
|
+
private client: BaseClient;
|
|
5
|
+
|
|
6
|
+
constructor(client: BaseClient) {
|
|
7
|
+
this.client = client;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Usually administrative
|
|
11
|
+
// Implement standard PAT creation endpoints if available/documented for target version
|
|
12
|
+
|
|
13
|
+
// Dremio Software: POST /api/v3/user/{uid}/token
|
|
14
|
+
// Dremio Cloud: /v0/projects/{id}/users/{uid}/token ? (Check docs)
|
|
15
|
+
// Often PAT management is done via specialized internal APIs or different paths.
|
|
16
|
+
// We will provide a placeholder that attempts the standard V3 path.
|
|
17
|
+
|
|
18
|
+
// Supports Dremio Cloud (PAT) and Software (PAT) creation
|
|
19
|
+
async createToken(userId: string, label: string, lifetimeDays: number = 30): Promise<{ token: string }> {
|
|
20
|
+
const config = this.client.getConfig();
|
|
21
|
+
let baseUrl = config.baseUrl.replace(/\/$/, '');
|
|
22
|
+
|
|
23
|
+
// Ensure we target the correct API root
|
|
24
|
+
// Cloud: https://api.dremio.cloud/v0/user/{id}/token
|
|
25
|
+
// Software: http://host:9047/api/v3/user/{id}/token
|
|
26
|
+
|
|
27
|
+
// If the client is Cloud, the getConfig() returns the raw URL (without project),
|
|
28
|
+
// effectively handling the path correctly.
|
|
29
|
+
|
|
30
|
+
const fullUrl = `${baseUrl}/user/${encodeURIComponent(userId)}/token`;
|
|
31
|
+
|
|
32
|
+
const millisecondsToExpire = lifetimeDays * 24 * 60 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
const response = await this.client.request({
|
|
35
|
+
method: 'POST',
|
|
36
|
+
url: fullUrl, // Absolute URL to bypass axios baseURL prefix
|
|
37
|
+
data: {
|
|
38
|
+
label,
|
|
39
|
+
millisecondsToExpire
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Cloud API returns the token string directly in some contexts, or JSON?
|
|
44
|
+
// Based on docs example, checking if response is string or object.
|
|
45
|
+
if (typeof response === 'string') {
|
|
46
|
+
return { token: response };
|
|
47
|
+
}
|
|
48
|
+
return response; // Assuming { token: ... } or similar JSON
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/api/user.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { BaseClient } from '../client/base';
|
|
2
2
|
|
|
3
|
+
// Basic types
|
|
3
4
|
export interface User {
|
|
4
5
|
id: string;
|
|
5
6
|
name: string;
|
|
6
7
|
firstName?: string;
|
|
7
8
|
lastName?: string;
|
|
8
9
|
email?: string;
|
|
9
|
-
|
|
10
|
+
tag?: string;
|
|
11
|
+
identityType?: 'REGULAR_USER' | 'SERVICE_USER';
|
|
12
|
+
active?: boolean;
|
|
13
|
+
roles?: { id: string, name: string, type: string }[];
|
|
14
|
+
oauthClientId?: string; // For Service Users
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export class UserResource {
|
|
@@ -17,16 +22,47 @@ export class UserResource {
|
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
async get(id: string): Promise<User> {
|
|
20
|
-
return this.client.request({
|
|
25
|
+
return this.client.request<User>({
|
|
21
26
|
method: 'GET',
|
|
22
27
|
url: `/user/${encodeURIComponent(id)}`
|
|
23
28
|
});
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
async getByName(name: string): Promise<User> {
|
|
27
|
-
return this.client.request({
|
|
32
|
+
return this.client.request<User>({
|
|
28
33
|
method: 'GET',
|
|
29
34
|
url: `/user/by-name/${encodeURIComponent(name)}`
|
|
30
35
|
});
|
|
31
36
|
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a new user (Regular or Service).
|
|
40
|
+
* @param user User definition. For Service Users, set identityType to 'SERVICE_USER'.
|
|
41
|
+
*/
|
|
42
|
+
async create(user: Partial<User>): Promise<User> {
|
|
43
|
+
return this.client.request<User>({
|
|
44
|
+
method: 'POST',
|
|
45
|
+
url: '/user',
|
|
46
|
+
data: user
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async update(id: string, user: Partial<User>): Promise<User> {
|
|
51
|
+
return this.client.request<User>({
|
|
52
|
+
method: 'PUT',
|
|
53
|
+
url: `/user/${encodeURIComponent(id)}`,
|
|
54
|
+
data: user
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async delete(id: string, versionTag?: string): Promise<void> {
|
|
59
|
+
const config: any = {
|
|
60
|
+
method: 'DELETE',
|
|
61
|
+
url: `/user/${encodeURIComponent(id)}`
|
|
62
|
+
};
|
|
63
|
+
if (versionTag) {
|
|
64
|
+
config.params = { version: versionTag };
|
|
65
|
+
}
|
|
66
|
+
await this.client.request(config);
|
|
67
|
+
}
|
|
32
68
|
}
|
package/src/client/base.ts
CHANGED
|
@@ -5,6 +5,13 @@ import { JobResource } from '../api/jobs';
|
|
|
5
5
|
import { ReflectionResource } from '../api/reflection';
|
|
6
6
|
import { SourceResource } from '../api/source';
|
|
7
7
|
import { UserResource } from '../api/user';
|
|
8
|
+
import { GovernanceResource } from '../api/governance';
|
|
9
|
+
import { CollaborationResource } from '../api/collaboration';
|
|
10
|
+
import { ScriptsResource } from '../api/scripts';
|
|
11
|
+
import { LineageResource } from '../api/lineage';
|
|
12
|
+
import { ProvisioningResource } from '../api/provisioning';
|
|
13
|
+
import { TokenResource } from '../api/token';
|
|
14
|
+
import { CredentialsResource } from '../api/credentials';
|
|
8
15
|
|
|
9
16
|
export abstract class BaseClient {
|
|
10
17
|
protected axiosInstance: AxiosInstance;
|
|
@@ -14,6 +21,13 @@ export abstract class BaseClient {
|
|
|
14
21
|
public reflections: ReflectionResource;
|
|
15
22
|
public sources: SourceResource;
|
|
16
23
|
public users: UserResource;
|
|
24
|
+
public governance: GovernanceResource;
|
|
25
|
+
public collaboration: CollaborationResource;
|
|
26
|
+
public scripts: ScriptsResource;
|
|
27
|
+
public lineage: LineageResource;
|
|
28
|
+
public provisioning: ProvisioningResource;
|
|
29
|
+
public tokens: TokenResource;
|
|
30
|
+
public credentials: CredentialsResource;
|
|
17
31
|
|
|
18
32
|
constructor(config: BaseDremioConfig) {
|
|
19
33
|
this.config = config;
|
|
@@ -30,6 +44,13 @@ export abstract class BaseClient {
|
|
|
30
44
|
this.reflections = new ReflectionResource(this);
|
|
31
45
|
this.sources = new SourceResource(this);
|
|
32
46
|
this.users = new UserResource(this);
|
|
47
|
+
this.governance = new GovernanceResource(this);
|
|
48
|
+
this.collaboration = new CollaborationResource(this);
|
|
49
|
+
this.scripts = new ScriptsResource(this);
|
|
50
|
+
this.lineage = new LineageResource(this);
|
|
51
|
+
this.provisioning = new ProvisioningResource(this);
|
|
52
|
+
this.tokens = new TokenResource(this);
|
|
53
|
+
this.credentials = new CredentialsResource(this);
|
|
33
54
|
|
|
34
55
|
// Add generic interceptors if needed
|
|
35
56
|
this.axiosInstance.interceptors.response.use(
|
|
@@ -41,6 +62,10 @@ export abstract class BaseClient {
|
|
|
41
62
|
);
|
|
42
63
|
}
|
|
43
64
|
|
|
65
|
+
public getConfig(): BaseDremioConfig {
|
|
66
|
+
return this.config;
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
protected abstract getAuthHeaders(): Record<string, string> | Promise<Record<string, string>>;
|
|
45
70
|
|
|
46
71
|
public async request<T = any>(config: AxiosRequestConfig): Promise<T> {
|
package/src/client/cloud.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import { BaseClient } from './base';
|
|
2
2
|
import { DremioCloudConfig } from '../types/config';
|
|
3
|
+
import axios from 'axios';
|
|
3
4
|
|
|
4
5
|
export class DremioCloudClient extends BaseClient {
|
|
5
6
|
protected config: DremioCloudConfig;
|
|
7
|
+
private sessionToken: string | null = null;
|
|
6
8
|
|
|
7
9
|
constructor(config: DremioCloudConfig) {
|
|
8
|
-
|
|
9
|
-
throw new Error('Auth token is required for Dremio Cloud.');
|
|
10
|
-
}
|
|
10
|
+
// Validation: Must have Project ID, and EITHER authToken OR auth.type='pat'
|
|
11
11
|
if (!config.projectId) {
|
|
12
12
|
throw new Error('Project ID is required for Dremio Cloud.');
|
|
13
13
|
}
|
|
14
|
+
const hasLegacyToken = !!config.authToken;
|
|
15
|
+
const hasAuthObj = config.auth && config.auth.type === 'pat';
|
|
16
|
+
|
|
17
|
+
if (!hasLegacyToken && !hasAuthObj) {
|
|
18
|
+
throw new Error('Auth token (PAT) is required for Dremio Cloud.');
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
// Adjust BaseUrl to include project context
|
|
22
|
+
// We do this for the main axios instance, but we keep original config for login
|
|
16
23
|
const configWithProject = { ...config };
|
|
17
24
|
if (!configWithProject.baseUrl.includes('/projects/')) {
|
|
18
25
|
// Remove trailing slash if present
|
|
@@ -24,14 +31,53 @@ export class DremioCloudClient extends BaseClient {
|
|
|
24
31
|
this.config = config;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
|
-
protected getAuthHeaders(): Record<string, string
|
|
34
|
+
protected async getAuthHeaders(): Promise<Record<string, string>> {
|
|
35
|
+
if (!this.sessionToken) {
|
|
36
|
+
await this.login();
|
|
37
|
+
}
|
|
28
38
|
return {
|
|
29
|
-
Authorization: `Bearer ${this.
|
|
39
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
30
40
|
};
|
|
31
41
|
}
|
|
32
42
|
|
|
43
|
+
private async login(): Promise<void> {
|
|
44
|
+
const pat = this.config.auth?.type === 'pat' ? this.config.auth.token : this.config.authToken;
|
|
45
|
+
|
|
46
|
+
if (!pat) {
|
|
47
|
+
throw new Error('No PAT available for login.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Try to exchange PAT for Session Token
|
|
51
|
+
try {
|
|
52
|
+
// Original Base URL (e.g. https://api.dremio.cloud/v0)
|
|
53
|
+
const cleanBase = this.config.baseUrl.replace(/\/$/, '');
|
|
54
|
+
const loginUrl = `${cleanBase}/login`;
|
|
55
|
+
|
|
56
|
+
const response = await axios.post(loginUrl, { token: pat }, {
|
|
57
|
+
headers: { 'Content-Type': 'application/json' },
|
|
58
|
+
timeout: this.config.timeout || 10000
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (response.data && response.data.token) {
|
|
62
|
+
this.sessionToken = response.data.token;
|
|
63
|
+
} else {
|
|
64
|
+
// Unexpected response structure
|
|
65
|
+
console.warn('Login response did not contain token, falling back to PAT/Legacy mode.');
|
|
66
|
+
this.sessionToken = pat;
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.warn('PAT Exchange failed, falling back to raw PAT usage. Error:', (error as any).message);
|
|
70
|
+
// Fallback to using PAT directly
|
|
71
|
+
this.sessionToken = pat;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
33
75
|
// Cloud specific methods can go here or generic methods can use projectId from config
|
|
34
76
|
getProjectId(): string {
|
|
35
77
|
return this.config.projectId;
|
|
36
78
|
}
|
|
79
|
+
|
|
80
|
+
public getConfig(): DremioCloudConfig {
|
|
81
|
+
return this.config;
|
|
82
|
+
}
|
|
37
83
|
}
|