dremiojs 1.1.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 CHANGED
@@ -10,6 +10,49 @@ npm install dremiojs
10
10
 
11
11
  ## Usage
12
12
 
13
+ The recommended way to use `dremiojs` is by configuring your connection in `~/.dremio/profiles.yaml` (compatible with `dremio-cli`).
14
+
15
+ ### 1. Setup Profile
16
+ Create `~/.dremio/profiles.yaml`:
17
+
18
+ ```yaml
19
+ profiles:
20
+ cloud:
21
+ type: cloud
22
+ base_url: https://api.dremio.cloud
23
+ project_id: <PROJECT_ID>
24
+ auth:
25
+ type: pat
26
+ token: <PAT>
27
+ local:
28
+ type: software
29
+ base_url: http://localhost:9047
30
+ auth:
31
+ type: username_password
32
+ username: admin
33
+ password: password1
34
+ default_profile: cloud
35
+ ```
36
+
37
+ ### 2. Connect & Query
38
+
39
+ ```typescript
40
+ import { Dremio } from 'dremiojs';
41
+
42
+ async function main() {
43
+ // Automatically loads 'default' profile from yaml
44
+ const client = await Dremio.fromProfile();
45
+
46
+ // Execute SQL
47
+ const results = await client.jobs.executeQuery('SELECT 1');
48
+ console.log(results);
49
+ }
50
+ ```
51
+
52
+ ## Advanced Usage (Explicit Configuration)
53
+
54
+ You can also configure clients manually without a profile file.
55
+
13
56
  ### Dremio Cloud
14
57
 
15
58
  ```typescript
@@ -20,16 +63,6 @@ const client = new DremioCloudClient({
20
63
  authToken: 'YOUR_PAT',
21
64
  projectId: 'YOUR_PROJECT_ID'
22
65
  });
23
-
24
- async function main() {
25
- // List catalog
26
- const catalog = await client.catalog.getByPath([]);
27
- console.log(catalog.children);
28
-
29
- // Execute SQL
30
- const results = await client.jobs.executeQuery('SELECT * FROM sys.version');
31
- console.log(results);
32
- }
33
66
  ```
34
67
 
35
68
  ### Dremio Software
@@ -37,29 +70,27 @@ async function main() {
37
70
  ```typescript
38
71
  import { DremioSoftwareClient } from 'dremiojs';
39
72
 
73
+ // OAuth (Client Credentials)
40
74
  const client = new DremioSoftwareClient({
41
- baseUrl: 'http://dremio:9047/api/v3',
42
- authToken: 'YOUR_PAT'
43
- // OR username/password:
44
- // username: 'dremio',
45
- // password: 'password123'
75
+ baseUrl: 'https://dremio.local/api/v3',
76
+ auth: {
77
+ type: 'oauth',
78
+ client_id: 'service-id',
79
+ client_secret: 'secret'
80
+ }
46
81
  });
47
-
48
- async function main() {
49
- const results = await client.jobs.executeQuery('SELECT 1');
50
- }
51
82
  ```
52
83
 
53
84
  ## Features
54
85
 
86
+ - **Profile Support**: Seamless `profiles.yaml` integration for Cloud/Software.
87
+ - **Advanced Auth**: Support for PAT Exchange and OAuth Client Credentials.
55
88
  - **Universal Support**: Works with both Dremio Cloud and Dremio Software.
56
89
  - **Strict Typing**: Full TypeScript definitions for Catalog, Jobs, Reflections, etc.
57
90
  - **SQL Helper**: Easy `executeQuery` method that handles job submission and polling.
58
91
  - **Catalog Management**: Create, update, delete Spaces, Sources, Folders, and Datasets.
59
92
  - **Reflections**: Manage reflections programmatically.
60
93
 
61
- ## Documentation
62
-
63
94
  - [Configuration & Environment Variables](docs/configuration.md)
64
95
  - [Client Usage](docs/client.md)
65
96
  - [Catalog API](docs/catalog.md)
@@ -1,54 +1,150 @@
1
- # Configuration & Environment Variables
1
+ # Configuration & Authentication
2
2
 
3
- `dremiojs` is designed to be flexible and secure. It supports configuration via a typed configuration object passed to the client constructor. While it does not automatically read environment variables (to avoid strict dependency on `dotenv` in client-side bundling scenarios), we recommend using standard environment variables in your application.
3
+ `dremiojs` offers a flexible configuration system that supports:
4
+ 1. **Profiles (New):** Automatically load configuration from `~/.dremio/profiles.yaml`.
5
+ 2. **Explicit Config:** Pass configuration objects directly to the client constructor.
6
+ 3. **Environment Variables:** Fallback support for standard environment variables.
4
7
 
5
- ## Best Practices
8
+ ## 1. Using Profiles (Recommended)
6
9
 
7
- Store your sensitive credentials in a `.env` file and use a library like `dotenv` to load them.
10
+ The easiest way to specificy connection details is using the `Dremio` factory, which reads from your local `~/.dremio/profiles.yaml` file (compatible with `dremio-cli` and `dremio-simple-query`).
8
11
 
9
- ```bash
10
- # .env example
11
- DREMIO_CLOUD_TOKEN=your_pat_here
12
- DREMIO_CLOUD_PROJECT_ID=your_project_id
13
- DREMIO_SOFTWARE_URL=http://dremio:9047/api/v3
14
- DREMIO_SOFTWARE_USER=admin
15
- DREMIO_SOFTWARE_PASS=password123
12
+ ### Installation
13
+ Ensure you have a profiles file at `~/.dremio/profiles.yaml`.
14
+
15
+ ```typescript
16
+ import { Dremio } from 'dremiojs';
17
+
18
+ async function main() {
19
+ // 1. Load the 'default' profile
20
+ const client = await Dremio.fromProfile();
21
+
22
+ // 2. Load a specific named profile (e.g. 'prod')
23
+ const prodClient = await Dremio.fromProfile('prod');
24
+
25
+ const results = await client.jobs.executeQuery('SELECT 1');
26
+ console.log(results);
27
+ }
16
28
  ```
17
29
 
18
- ## Configuration Interfaces
30
+ ### Profile Reference (`profiles.yaml`)
31
+
32
+ Below is a complete reference of supported profile types.
33
+
34
+ ```yaml
35
+ profiles:
36
+ # --- Dremio Cloud (PAT Auth) ---
37
+ cloud:
38
+ type: cloud
39
+ # Base URL (defaults to https://api.dremio.cloud)
40
+ base_url: https://api.dremio.cloud
41
+ project_id: <YOUR_PROJECT_ID>
42
+ auth:
43
+ type: pat
44
+ token: <YOUR_PAT_TOKEN>
45
+
46
+ # --- Dremio Software (PAT Auth) ---
47
+ software_pat:
48
+ type: software
49
+ base_url: https://dremio.company.com
50
+ auth:
51
+ type: pat
52
+ token: <YOUR_PAT_TOKEN>
53
+
54
+ # --- Dremio Software (Username/Password) ---
55
+ # Useful for local development or legacy auth
56
+ local:
57
+ type: software
58
+ base_url: http://localhost:9047
59
+ ssl: 'false' # Disable SSL verification for localhost
60
+ auth:
61
+ type: username_password
62
+ username: admin
63
+ password: password123
19
64
 
20
- ### Dremio Cloud Config
65
+ # --- Dremio Software (OAuth 2.0 / Service Account) ---
66
+ # Uses Client Credentials Grant
67
+ service_account:
68
+ type: software
69
+ base_url: https://dremio.company.com
70
+ auth:
71
+ type: oauth
72
+ client_id: <CLIENT_ID>
73
+ client_secret: <CLIENT_SECRET>
74
+ # Scope is optional, defaults to implicit or 'dremio.all' on retry
75
+ scope: dremio.all
76
+
77
+ default_profile: cloud
78
+ ```
79
+
80
+ ## 2. Explicit Configuration
81
+
82
+ You can also instantiate clients manually, which is useful for web applications or when configuration is injected at runtime.
83
+
84
+ ### Dremio Cloud Client
21
85
 
22
86
  ```typescript
23
- interface DremioCloudConfig {
24
- baseUrl: string; // Usually 'https://api.dremio.cloud/v0'
25
- authToken: string; // Your Personal Access Token
26
- projectId: string; // The Project ID provided in the Cloud Console URL
27
- timeout?: number; // Request timeout in ms (default: 30000)
28
- }
87
+ import { DremioCloudClient } from 'dremiojs';
88
+
89
+ const client = new DremioCloudClient({
90
+ projectId: 'your-project-id',
91
+ authToken: 'your-pat', // Can be a PAT or a Session Token
92
+ baseUrl: 'https://api.dremio.cloud/v0', // Optional
93
+ // Or use the auth object
94
+ auth: {
95
+ type: 'pat',
96
+ token: 'your-pat'
97
+ }
98
+ });
29
99
  ```
30
100
 
31
- ### Dremio Software Config
101
+ ### Dremio Software Client
32
102
 
33
103
  ```typescript
34
- interface DremioSoftwareConfig {
35
- baseUrl: string; // e.g. 'http://localhost:9047/api/v3'
36
- authToken?: string; // PAT (Recommended)
37
- username?: string; // For legacy auth
38
- password?: string; // For legacy auth
39
- checkSSLCerts?: boolean; // Set to false for self-signed certs (Node.js only)
40
- timeout?: number; // Request timeout in ms
41
- }
104
+ import { DremioSoftwareClient } from 'dremiojs';
105
+
106
+ // PAT Auth
107
+ const client = new DremioSoftwareClient({
108
+ baseUrl: 'http://localhost:9047/api/v3',
109
+ authToken: 'your-pat'
110
+ });
111
+
112
+ // Username/Password
113
+ const client = new DremioSoftwareClient({
114
+ baseUrl: 'http://localhost:9047/api/v3',
115
+ auth: {
116
+ type: 'username_password',
117
+ username: 'admin',
118
+ password: 'password123'
119
+ }
120
+ });
121
+
122
+ // OAuth (Client Credentials)
123
+ const client = new DremioSoftwareClient({
124
+ baseUrl: 'https://dremio.example.com/api/v3',
125
+ auth: {
126
+ type: 'oauth',
127
+ client_id: '...',
128
+ client_secret: '...'
129
+ }
130
+ });
42
131
  ```
43
132
 
44
- ## SSL/TLS Configuration (Self-Signed Certs)
133
+ ## 3. Environment Variables (Fallback)
134
+
135
+ If no profiles are found, `ConfigLoader` checks for these legacy environment variables:
45
136
 
46
- If you are connecting to a Dremio Software instance with a self-signed certificate, you may need to disable SSL verification in Node.js.
137
+ - **Cloud**: `DREMIO_CLOUD_TOKEN`, `DREMIO_CLOUD_PROJECTID`, `DREMIO_CLOUD_BASE_URL`
138
+ - **Software**: `DREMIO_SOFTWARE_TOKEN`, `DREMIO_SOFTWARE_BASE_URL`, `DREMIO_USER`, `DREMIO_PASSWORD`
139
+
140
+ ## SSL/TLS Configuration
141
+
142
+ For self-signed certificates (common in local software deployments), set `checkSSLCerts: false`. In `profiles.yaml`, set `ssl: 'false'`.
47
143
 
48
144
  ```typescript
49
145
  const client = new DremioSoftwareClient({
50
- baseUrl: 'https://dremio.local:9047/api/v3',
51
- authToken: '...',
52
- checkSSLCerts: false // WARNING: Use only for development
146
+ baseUrl: 'https://localhost:9047',
147
+ checkSSLCerts: false
53
148
  });
54
149
  ```
150
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dremiojs",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A TypeScript client library for Dremio Cloud and Software.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,6 +16,7 @@
16
16
  "type": "commonjs",
17
17
  "devDependencies": {
18
18
  "@types/jest": "^30.0.0",
19
+ "@types/js-yaml": "^4.0.9",
19
20
  "@types/node": "^25.0.3",
20
21
  "axios": "^1.13.2",
21
22
  "dotenv": "^17.2.3",
@@ -25,5 +26,8 @@
25
26
  "ts-jest": "^29.4.6",
26
27
  "ts-node": "^10.9.2",
27
28
  "typescript": "^5.9.3"
29
+ },
30
+ "dependencies": {
31
+ "js-yaml": "^4.1.1"
28
32
  }
29
- }
33
+ }
@@ -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
- if (!config.authToken) {
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,12 +31,47 @@ 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.config.authToken}`,
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;
@@ -1,9 +1,15 @@
1
1
  import { BaseClient } from './base';
2
2
  import { DremioSoftwareConfig } from '../types/config';
3
+ import axios from 'axios';
3
4
 
4
5
  interface LoginResponse {
5
6
  token: string;
6
- // Add other fields if necessary
7
+ }
8
+
9
+ interface OAuthResponse {
10
+ access_token: string;
11
+ token_type: string;
12
+ expires_in: number;
7
13
  }
8
14
 
9
15
  export class DremioSoftwareClient extends BaseClient {
@@ -13,8 +19,11 @@ export class DremioSoftwareClient extends BaseClient {
13
19
  constructor(config: DremioSoftwareConfig) {
14
20
  super(config);
15
21
  this.config = config;
22
+ // Priority: Explicit authToken -> auth.type='pat' -> null (will login)
16
23
  if (config.authToken) {
17
24
  this.token = config.authToken;
25
+ } else if (config.auth && config.auth.type === 'pat') {
26
+ this.token = config.auth.token;
18
27
  }
19
28
  }
20
29
 
@@ -27,46 +36,81 @@ export class DremioSoftwareClient extends BaseClient {
27
36
  };
28
37
  }
29
38
 
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
- }
39
+ private getRootUrl(): string {
40
+ // Robustly strip /api/v3 to find the server root
41
+ // Handles: https://dremio.org/api/v3 -> https://dremio.org
42
+ // http://localhost:9047 -> http://localhost:9047
43
+ // http://localhost:9047/ -> http://localhost:9047
44
+ return this.config.baseUrl.replace(/\/api\/v3\/?$/, '').replace(/\/$/, '');
45
+ }
34
46
 
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.
47
+ private async login(): Promise<void> {
48
+ const auth = this.config.auth;
49
+ const rootUrl = this.getRootUrl();
43
50
 
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.
51
+ if (auth?.type === 'oauth') {
52
+ await this.loginOAuth(rootUrl, auth.client_id, auth.client_secret, auth.scope);
53
+ } else if (auth?.type === 'username_password' || (this.config.username && this.config.password)) {
54
+ const user = auth?.type === 'username_password' ? auth.username : this.config.username;
55
+ const pass = auth?.type === 'username_password' ? auth.password : this.config.password;
56
+
57
+ if (!user || !pass) throw new Error('Username/Password required');
58
+ await this.loginBasic(rootUrl, user, pass);
59
+ } else {
60
+ throw new Error('No valid authentication configuration found (Token, User/Pass, or OAuth).');
49
61
  }
62
+ }
50
63
 
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
-
64
+ private async loginBasic(rootUrl: string, user: string, pass: string): Promise<void> {
59
65
  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,
66
+ const response = await axios.post<LoginResponse>(`${rootUrl}/apiv2/login`, {
67
+ userName: user,
68
+ password: pass,
69
+ }, {
70
+ timeout: this.config.timeout || 10000
65
71
  });
66
-
67
72
  this.token = response.data.token;
68
73
  } catch (error) {
69
- console.error('Login failed');
74
+ console.error('Login (Basic) failed:', (error as any).message);
75
+ throw error;
76
+ }
77
+ }
78
+
79
+ private async loginOAuth(rootUrl: string, clientId: string, clientSecret: string, scope?: string): Promise<void> {
80
+ const params = new URLSearchParams();
81
+ params.append('grant_type', 'client_credentials');
82
+ params.append('client_id', clientId);
83
+ params.append('client_secret', clientSecret);
84
+ if (scope) {
85
+ params.append('scope', scope);
86
+ }
87
+
88
+ const doRequest = async (p: URLSearchParams) => {
89
+ return axios.post<OAuthResponse>(`${rootUrl}/oauth/token`, p, {
90
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
91
+ timeout: this.config.timeout || 10000
92
+ });
93
+ };
94
+
95
+ try {
96
+ const response = await doRequest(params);
97
+ this.token = response.data.access_token;
98
+ } catch (error: any) {
99
+ // Check for Scope error (400) and retry with dremio.all if not already present
100
+ if (error.response && error.response.status === 400 && !scope) {
101
+ // Heuristic: Check error message content if possible, or just blind retry
102
+ console.warn('OAuth login failed (400). Retrying with scope=dremio.all default...');
103
+ params.set('scope', 'dremio.all');
104
+ try {
105
+ const retryResponse = await doRequest(params);
106
+ this.token = retryResponse.data.access_token;
107
+ return;
108
+ } catch (retryError: any) {
109
+ console.error('OAuth retry failed:', retryError.message);
110
+ throw retryError;
111
+ }
112
+ }
113
+ console.error('OAuth login failed:', error.message);
70
114
  throw error;
71
115
  }
72
116
  }
package/src/factory.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { ConfigLoader } from './utils/config';
2
+ import { DremioCloudClient } from './client/cloud';
3
+ import { DremioSoftwareClient } from './client/software';
4
+ import { DremioCloudConfig, DremioSoftwareConfig } from './types/config';
5
+
6
+ export class Dremio {
7
+ /**
8
+ * Create a Dremio Client from a named profile in ~/.dremio/profiles.yaml
9
+ * If no profile is specified, it loads the default profile.
10
+ */
11
+ static async fromProfile(profileName?: string): Promise<DremioCloudClient | DremioSoftwareClient> {
12
+ const config = await ConfigLoader.loadProfile(profileName);
13
+
14
+ if ('projectId' in config) {
15
+ return new DremioCloudClient(config as DremioCloudConfig);
16
+ } else {
17
+ return new DremioSoftwareClient(config as DremioSoftwareConfig);
18
+ }
19
+ }
20
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- // Clients
2
1
  export { DremioCloudClient } from './client/cloud';
3
2
  export { DremioSoftwareClient } from './client/software';
3
+ export { Dremio } from './factory';
4
4
 
5
5
  // Types
6
6
  export * from './types/config';
@@ -1,18 +1,41 @@
1
1
  export interface BaseDremioConfig {
2
2
  baseUrl: string;
3
3
  timeout?: number;
4
+ checkSSLCerts?: boolean;
4
5
  }
5
6
 
7
+ export type AuthConfig =
8
+ | { type: 'pat'; token: string }
9
+ | { type: 'username_password'; username: string; password: string }
10
+ | { type: 'oauth'; client_id: string; client_secret: string; scope?: string };
11
+
6
12
  export interface DremioCloudConfig extends BaseDremioConfig {
7
- authToken: string;
13
+ authToken?: string; // Legacy/Direct support
8
14
  projectId: string;
15
+ auth?: AuthConfig;
9
16
  }
10
17
 
11
18
  export interface DremioSoftwareConfig extends BaseDremioConfig {
12
- authToken?: string;
13
- username?: string;
14
- password?: string;
15
- checkSSLCerts?: boolean;
19
+ authToken?: string; // Legacy/Direct support
20
+ username?: string; // Legacy/Direct support
21
+ password?: string; // Legacy/Direct support
22
+ auth?: AuthConfig;
16
23
  }
17
24
 
18
25
  export type DremioConfig = DremioCloudConfig | DremioSoftwareConfig;
26
+
27
+ // Profile definition matching profiles.yaml
28
+ export interface ProfileConfig {
29
+ type: 'cloud' | 'software';
30
+ base_url?: string;
31
+ project_id?: string;
32
+ test_folder?: string;
33
+ ssl?: string;
34
+ auth: AuthConfig;
35
+ mode?: string; // e.g. 'v25'
36
+ }
37
+
38
+ export interface ProfilesFile {
39
+ profiles: Record<string, ProfileConfig>;
40
+ default_profile?: string;
41
+ }
@@ -0,0 +1,113 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import yaml from 'js-yaml';
5
+ import { ProfilesFile, ProfileConfig, DremioConfig, DremioCloudConfig, DremioSoftwareConfig } from '../types/config';
6
+
7
+ export class ConfigLoader {
8
+ private static PROFILES_PATH = path.join(os.homedir(), '.dremio', 'profiles.yaml');
9
+
10
+ static async loadProfile(profileName?: string): Promise<DremioConfig> {
11
+ let profileConfig: ProfileConfig | undefined;
12
+
13
+ // 1. Try to load from profiles.yaml
14
+ if (fs.existsSync(this.PROFILES_PATH)) {
15
+ try {
16
+ const fileContent = fs.readFileSync(this.PROFILES_PATH, 'utf8');
17
+ const profilesData = yaml.load(fileContent) as ProfilesFile;
18
+
19
+ const targetProfile = profileName || profilesData.default_profile || 'default';
20
+
21
+ if (profilesData.profiles && profilesData.profiles[targetProfile]) {
22
+ profileConfig = profilesData.profiles[targetProfile];
23
+ } else if (profileName) {
24
+ throw new Error(`Profile '${profileName}' not found in ${this.PROFILES_PATH}`);
25
+ }
26
+ } catch (error) {
27
+ console.warn(`Warning: Failed to parse ${this.PROFILES_PATH}`, error);
28
+ }
29
+ }
30
+
31
+ // 2. Convert to DremioConfig
32
+ if (profileConfig) {
33
+ return this.convertProfileToConfig(profileConfig);
34
+ }
35
+
36
+ // 3. Fallback to Env Vars (legacy support)
37
+ return this.loadFromEnv();
38
+ }
39
+
40
+ private static convertProfileToConfig(profile: ProfileConfig): DremioConfig {
41
+ const checkSSLCerts = profile.ssl === 'false' ? false : true; // Default true unless 'false' string
42
+
43
+ if (profile.type === 'cloud') {
44
+ if (!profile.project_id) throw new Error('Cloud profile missing project_id');
45
+ // Cloud profile usually has base_url
46
+ let baseUrl = profile.base_url || 'https://api.dremio.cloud';
47
+
48
+ // Normalize: Ensure /v0 assumption for Cloud if not present
49
+ if (!baseUrl.endsWith('/v0')) {
50
+ baseUrl = baseUrl.replace(/\/$/, '');
51
+ baseUrl = `${baseUrl}/v0`;
52
+ }
53
+
54
+ return {
55
+ baseUrl,
56
+ projectId: profile.project_id,
57
+ authToken: profile.auth.type === 'pat' ? profile.auth.token : undefined,
58
+ auth: profile.auth,
59
+ checkSSLCerts
60
+ } as DremioCloudConfig;
61
+ } else {
62
+ // Software
63
+ let baseUrl = profile.base_url || 'http://localhost:9047';
64
+
65
+ // Normalize URL: Ensure it ends with /api/v3 for Software
66
+ if (!baseUrl.endsWith('/api/v3')) {
67
+ // Strip trailing slash first
68
+ baseUrl = baseUrl.replace(/\/$/, '');
69
+ baseUrl = `${baseUrl}/api/v3`;
70
+ }
71
+
72
+ const config: DremioSoftwareConfig = {
73
+ baseUrl,
74
+ checkSSLCerts,
75
+ auth: profile.auth
76
+ };
77
+
78
+ // Map flat properties if present, though 'auth' object is preferred now
79
+ if (profile.auth.type === 'pat') {
80
+ config.authToken = profile.auth.token;
81
+ } else if (profile.auth.type === 'username_password') {
82
+ config.username = profile.auth.username;
83
+ config.password = profile.auth.password;
84
+ }
85
+
86
+ return config;
87
+ }
88
+ }
89
+
90
+ private static loadFromEnv(): DremioConfig {
91
+ // Fallback implementation for existing .env support
92
+ // This mirrors the logic users might expect if no profile exists
93
+ if (process.env.DREMIO_CLOUD_TOKEN && process.env.DREMIO_CLOUD_PROJECTID) {
94
+ return {
95
+ baseUrl: process.env.DREMIO_CLOUD_BASE_URL || 'https://api.dremio.cloud/v0',
96
+ authToken: process.env.DREMIO_CLOUD_TOKEN,
97
+ projectId: process.env.DREMIO_CLOUD_PROJECTID
98
+ } as DremioCloudConfig;
99
+ }
100
+
101
+ if (process.env.DREMIO_SOFTWARE_TOKEN || (process.env.DREMIO_USER && process.env.DREMIO_PASSWORD)) {
102
+ return {
103
+ baseUrl: process.env.DREMIO_SOFTWARE_BASE_URL || 'http://localhost:9047',
104
+ authToken: process.env.DREMIO_SOFTWARE_TOKEN,
105
+ username: process.env.DREMIO_USER,
106
+ password: process.env.DREMIO_PASSWORD,
107
+ checkSSLCerts: process.env.DREMIO_SSL_VERIFY === 'false' ? false : true
108
+ } as DremioSoftwareConfig;
109
+ }
110
+
111
+ throw new Error('No configuration found in profiles.yaml or environment variables.');
112
+ }
113
+ }
@@ -0,0 +1,28 @@
1
+ import { Dremio } from '../src/factory';
2
+
3
+ async function testProfile(name: string) {
4
+ console.log(`\n--- Testing Profile: ${name} ---`);
5
+ try {
6
+ const client = await Dremio.fromProfile(name);
7
+ console.log(`Initialized client for ${name}. Executing 'SELECT 1'...`);
8
+
9
+ const job = await client.jobs.executeQuery('SELECT 1');
10
+ console.log('Query result:', JSON.stringify(job, null, 2));
11
+ console.log(`✅ [PASS] ${name}`);
12
+ } catch (error: any) {
13
+ console.error(`❌ [FAIL] ${name}`);
14
+ console.error('Error:', error.message);
15
+ if (error.response) {
16
+ console.error('API Response:', error.response.data);
17
+ }
18
+ }
19
+ }
20
+
21
+ async function runTests() {
22
+ await testProfile('cloud');
23
+ await testProfile('software');
24
+ await testProfile('v25');
25
+ await testProfile('service');
26
+ }
27
+
28
+ runTests();