crushdataai 1.2.6 → 1.2.8
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/assets/antigravity/data-analyst.md +8 -4
- package/assets/claude/SKILL.md +7 -2
- package/assets/copilot/data-analyst.prompt.md +8 -4
- package/assets/cursor/data-analyst.md +8 -4
- package/assets/kiro/data-analyst.md +8 -4
- package/assets/windsurf/data-analyst.md +8 -4
- package/dist/commands/schema.d.ts +1 -0
- package/dist/commands/schema.js +54 -0
- package/dist/connections.d.ts +3 -1
- package/dist/connections.js +125 -0
- package/dist/connectors/additional/index.d.ts +42 -0
- package/dist/connectors/additional/index.js +268 -0
- package/dist/connectors/cloud/index.d.ts +8 -3
- package/dist/connectors/cloud/index.js +321 -10
- package/dist/connectors/csv/index.d.ts +1 -0
- package/dist/connectors/csv/index.js +27 -7
- package/dist/connectors/custom/index.d.ts +10 -0
- package/dist/connectors/custom/index.js +61 -0
- package/dist/connectors/index.d.ts +6 -0
- package/dist/connectors/mysql/index.d.ts +1 -0
- package/dist/connectors/mysql/index.js +162 -9
- package/dist/connectors/postgresql/index.d.ts +2 -0
- package/dist/connectors/postgresql/index.js +140 -8
- package/dist/connectors/shopify/index.d.ts +1 -0
- package/dist/connectors/shopify/index.js +44 -5
- package/dist/index.js +7 -0
- package/dist/routes/connections.js +2 -0
- package/dist/server.js +8 -0
- package/package.json +8 -3
- package/ui/assets/index-Ba1mRihD.js +40 -0
- package/ui/assets/index-rlcHFDJB.css +1 -0
- package/ui/favicon.svg +4 -18
- package/ui/index.html +7 -331
- package/ui/favicon-32x32.png +0 -0
- package/ui/main.js +0 -542
- package/ui/styles.css +0 -680
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { Connector, Table, TableData } from '../index';
|
|
1
|
+
import { Connector, Table, TableData, ColumnInfo } from '../index';
|
|
2
2
|
import { Connection } from '../../connections';
|
|
3
3
|
export declare class BigQueryConnector implements Connector {
|
|
4
4
|
type: string;
|
|
5
|
+
private createClient;
|
|
5
6
|
test(connection: Connection): Promise<boolean>;
|
|
6
7
|
getTables(connection: Connection): Promise<Table[]>;
|
|
7
|
-
getData(connection: Connection,
|
|
8
|
+
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
9
|
+
getSchema(connection: Connection, tableName: string): Promise<ColumnInfo[]>;
|
|
8
10
|
getSnippet(connection: Connection, lang: string): string;
|
|
9
11
|
}
|
|
10
12
|
export declare class SnowflakeConnector implements Connector {
|
|
11
13
|
type: string;
|
|
14
|
+
private createConnection;
|
|
15
|
+
private executeQuery;
|
|
12
16
|
test(connection: Connection): Promise<boolean>;
|
|
13
17
|
getTables(connection: Connection): Promise<Table[]>;
|
|
14
|
-
getData(connection: Connection,
|
|
18
|
+
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
19
|
+
getSchema(connection: Connection, tableName: string): Promise<ColumnInfo[]>;
|
|
15
20
|
getSnippet(connection: Connection, lang: string): string;
|
|
16
21
|
}
|
|
@@ -1,21 +1,172 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.SnowflakeConnector = exports.BigQueryConnector = void 0;
|
|
37
|
+
const bigquery_1 = require("@google-cloud/bigquery");
|
|
38
|
+
const snowflake = __importStar(require("snowflake-sdk"));
|
|
4
39
|
class BigQueryConnector {
|
|
5
40
|
constructor() {
|
|
6
41
|
this.type = 'bigquery';
|
|
7
42
|
}
|
|
43
|
+
createClient(connection) {
|
|
44
|
+
return new bigquery_1.BigQuery({
|
|
45
|
+
projectId: connection.projectId,
|
|
46
|
+
keyFilename: connection.keyFile
|
|
47
|
+
});
|
|
48
|
+
}
|
|
8
49
|
async test(connection) {
|
|
9
|
-
|
|
50
|
+
console.log(`[BigQuery] Testing connection for ${connection.name} (Project: ${connection.projectId})`);
|
|
51
|
+
if (!connection.projectId || !connection.keyFile) {
|
|
52
|
+
throw new Error('Project ID and Key File path are required');
|
|
53
|
+
}
|
|
54
|
+
// Check if key file exists
|
|
55
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
56
|
+
if (!fs.existsSync(connection.keyFile)) {
|
|
57
|
+
throw new Error(`Key file not found: ${connection.keyFile}`);
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const bigquery = this.createClient(connection);
|
|
61
|
+
await bigquery.getDatasets({ maxResults: 1 });
|
|
62
|
+
console.log(`[BigQuery] Connection test successful for ${connection.name}`);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
console.error(`[BigQuery] Connection test failed:`, error.message);
|
|
67
|
+
throw new Error(`BigQuery connection failed: ${error.message}`);
|
|
68
|
+
}
|
|
10
69
|
}
|
|
11
70
|
async getTables(connection) {
|
|
12
|
-
|
|
71
|
+
console.log(`[BigQuery] getTables called for ${connection.name}`);
|
|
72
|
+
try {
|
|
73
|
+
const bigquery = this.createClient(connection);
|
|
74
|
+
const [datasets] = await bigquery.getDatasets();
|
|
75
|
+
const tables = [];
|
|
76
|
+
for (const dataset of datasets) {
|
|
77
|
+
const [datasetTables] = await dataset.getTables();
|
|
78
|
+
for (const table of datasetTables) {
|
|
79
|
+
const [metadata] = await table.getMetadata();
|
|
80
|
+
tables.push({
|
|
81
|
+
name: `${dataset.id}.${table.id}`,
|
|
82
|
+
type: metadata.type || 'TABLE',
|
|
83
|
+
rowCount: parseInt(metadata.numRows || '0', 10)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return tables;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error(`[BigQuery] getTables failed:`, error.message);
|
|
91
|
+
throw new Error(`Failed to fetch tables: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async getData(connection, tableName, page, limit) {
|
|
95
|
+
console.log(`[BigQuery] getData called for ${connection.name}, table: ${tableName}, page: ${page}`);
|
|
96
|
+
try {
|
|
97
|
+
const bigquery = this.createClient(connection);
|
|
98
|
+
const offset = (page - 1) * limit;
|
|
99
|
+
const countQuery = `SELECT COUNT(*) as total FROM \`${tableName}\``;
|
|
100
|
+
const [countJob] = await bigquery.createQueryJob({ query: countQuery });
|
|
101
|
+
const [countRows] = await countJob.getQueryResults();
|
|
102
|
+
const totalRows = parseInt(countRows[0]?.total || '0', 10);
|
|
103
|
+
const dataQuery = `SELECT * FROM \`${tableName}\` LIMIT ${limit} OFFSET ${offset}`;
|
|
104
|
+
const [dataJob] = await bigquery.createQueryJob({ query: dataQuery });
|
|
105
|
+
const [rows] = await dataJob.getQueryResults();
|
|
106
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
107
|
+
const totalPages = Math.ceil(totalRows / limit) || 1;
|
|
108
|
+
return {
|
|
109
|
+
columns,
|
|
110
|
+
rows: rows,
|
|
111
|
+
pagination: {
|
|
112
|
+
page,
|
|
113
|
+
limit,
|
|
114
|
+
totalRows,
|
|
115
|
+
totalPages,
|
|
116
|
+
startIdx: offset + 1,
|
|
117
|
+
endIdx: offset + rows.length
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
console.error(`[BigQuery] getData failed:`, error.message);
|
|
123
|
+
throw new Error(`Failed to fetch data: ${error.message}`);
|
|
124
|
+
}
|
|
13
125
|
}
|
|
14
|
-
async
|
|
15
|
-
|
|
126
|
+
async getSchema(connection, tableName) {
|
|
127
|
+
console.log(`[BigQuery] getSchema called for ${connection.name}, table: ${tableName}`);
|
|
128
|
+
try {
|
|
129
|
+
const bigquery = this.createClient(connection);
|
|
130
|
+
const [dataset, table] = tableName.split('.');
|
|
131
|
+
const tableRef = bigquery.dataset(dataset).table(table);
|
|
132
|
+
const [metadata] = await tableRef.getMetadata();
|
|
133
|
+
return (metadata.schema?.fields || []).map((field) => ({
|
|
134
|
+
name: field.name,
|
|
135
|
+
type: field.type,
|
|
136
|
+
nullable: field.mode !== 'REQUIRED'
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error(`[BigQuery] getSchema failed:`, error.message);
|
|
141
|
+
throw new Error(`Failed to fetch schema: ${error.message}`);
|
|
142
|
+
}
|
|
16
143
|
}
|
|
17
144
|
getSnippet(connection, lang) {
|
|
18
|
-
|
|
145
|
+
const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
146
|
+
if (lang === 'python') {
|
|
147
|
+
return `import os
|
|
148
|
+
from google.cloud import bigquery
|
|
149
|
+
import pandas as pd
|
|
150
|
+
|
|
151
|
+
# Connection: ${connection.name}
|
|
152
|
+
# Type: bigquery
|
|
153
|
+
# Credentials loaded from environment variables (set in .env file)
|
|
154
|
+
client = bigquery.Client.from_service_account_json(os.environ["${prefix}_KEY_FILE"])
|
|
155
|
+
|
|
156
|
+
# Example: Query a table
|
|
157
|
+
project_id = os.environ["${prefix}_PROJECT_ID"]
|
|
158
|
+
query = f"""
|
|
159
|
+
SELECT *
|
|
160
|
+
FROM \`{project_id}.your_dataset.your_table\`
|
|
161
|
+
LIMIT 100
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
df = client.query(query).to_dataframe()
|
|
165
|
+
print(f"Successfully loaded {len(df)} rows from ${connection.name}")
|
|
166
|
+
print(df.head())
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
return `# Language ${lang} not supported for BigQuery connector yet.`;
|
|
19
170
|
}
|
|
20
171
|
}
|
|
21
172
|
exports.BigQueryConnector = BigQueryConnector;
|
|
@@ -23,17 +174,177 @@ class SnowflakeConnector {
|
|
|
23
174
|
constructor() {
|
|
24
175
|
this.type = 'snowflake';
|
|
25
176
|
}
|
|
177
|
+
async createConnection(connection) {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const conn = snowflake.createConnection({
|
|
180
|
+
account: connection.account || '',
|
|
181
|
+
username: connection.user || '',
|
|
182
|
+
password: connection.password || '',
|
|
183
|
+
warehouse: connection.warehouse,
|
|
184
|
+
database: connection.database
|
|
185
|
+
});
|
|
186
|
+
conn.connect((err, conn) => {
|
|
187
|
+
if (err) {
|
|
188
|
+
reject(err);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
resolve(conn);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
executeQuery(conn, query) {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
conn.execute({
|
|
199
|
+
sqlText: query,
|
|
200
|
+
complete: (err, stmt, rows) => {
|
|
201
|
+
if (err) {
|
|
202
|
+
reject(err);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
resolve(rows || []);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
}
|
|
26
211
|
async test(connection) {
|
|
27
|
-
|
|
212
|
+
console.log(`[Snowflake] Testing connection for ${connection.name} (Account: ${connection.account})`);
|
|
213
|
+
// Validate required fields
|
|
214
|
+
const account = connection.account?.trim();
|
|
215
|
+
const user = connection.user?.trim();
|
|
216
|
+
const password = connection.password?.trim();
|
|
217
|
+
if (!account) {
|
|
218
|
+
throw new Error('Snowflake account is required');
|
|
219
|
+
}
|
|
220
|
+
if (!user) {
|
|
221
|
+
throw new Error('Snowflake username is required');
|
|
222
|
+
}
|
|
223
|
+
if (!password) {
|
|
224
|
+
throw new Error('Snowflake password is required');
|
|
225
|
+
}
|
|
226
|
+
let conn = null;
|
|
227
|
+
try {
|
|
228
|
+
conn = await this.createConnection(connection);
|
|
229
|
+
await this.executeQuery(conn, 'SELECT CURRENT_VERSION()');
|
|
230
|
+
console.log(`[Snowflake] Connection test successful for ${connection.name}`);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
console.error(`[Snowflake] Connection test failed:`, error.message);
|
|
235
|
+
throw new Error(`Snowflake connection failed: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
if (conn)
|
|
239
|
+
conn.destroy(() => { });
|
|
240
|
+
}
|
|
28
241
|
}
|
|
29
242
|
async getTables(connection) {
|
|
30
|
-
|
|
243
|
+
console.log(`[Snowflake] getTables called for ${connection.name}`);
|
|
244
|
+
let conn = null;
|
|
245
|
+
try {
|
|
246
|
+
conn = await this.createConnection(connection);
|
|
247
|
+
const rows = await this.executeQuery(conn, 'SHOW TABLES');
|
|
248
|
+
return rows.map((row) => ({
|
|
249
|
+
name: row.name || row.TABLE_NAME,
|
|
250
|
+
type: 'table',
|
|
251
|
+
rowCount: row.rows || null
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
console.error(`[Snowflake] getTables failed:`, error.message);
|
|
256
|
+
throw new Error(`Failed to fetch tables: ${error.message}`);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
if (conn)
|
|
260
|
+
conn.destroy(() => { });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async getData(connection, tableName, page, limit) {
|
|
264
|
+
console.log(`[Snowflake] getData called for ${connection.name}, table: ${tableName}, page: ${page}`);
|
|
265
|
+
let conn = null;
|
|
266
|
+
try {
|
|
267
|
+
conn = await this.createConnection(connection);
|
|
268
|
+
const offset = (page - 1) * limit;
|
|
269
|
+
const countRows = await this.executeQuery(conn, `SELECT COUNT(*) as TOTAL FROM "${tableName}"`);
|
|
270
|
+
const totalRows = countRows[0]?.TOTAL || 0;
|
|
271
|
+
const rows = await this.executeQuery(conn, `SELECT * FROM "${tableName}" LIMIT ${limit} OFFSET ${offset}`);
|
|
272
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
|
|
273
|
+
const totalPages = Math.ceil(totalRows / limit) || 1;
|
|
274
|
+
return {
|
|
275
|
+
columns,
|
|
276
|
+
rows,
|
|
277
|
+
pagination: {
|
|
278
|
+
page,
|
|
279
|
+
limit,
|
|
280
|
+
totalRows,
|
|
281
|
+
totalPages,
|
|
282
|
+
startIdx: offset + 1,
|
|
283
|
+
endIdx: offset + rows.length
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.error(`[Snowflake] getData failed:`, error.message);
|
|
289
|
+
throw new Error(`Failed to fetch data: ${error.message}`);
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
if (conn)
|
|
293
|
+
conn.destroy(() => { });
|
|
294
|
+
}
|
|
31
295
|
}
|
|
32
|
-
async
|
|
33
|
-
|
|
296
|
+
async getSchema(connection, tableName) {
|
|
297
|
+
console.log(`[Snowflake] getSchema called for ${connection.name}, table: ${tableName}`);
|
|
298
|
+
let conn = null;
|
|
299
|
+
try {
|
|
300
|
+
conn = await this.createConnection(connection);
|
|
301
|
+
const rows = await this.executeQuery(conn, `DESCRIBE TABLE "${tableName}"`);
|
|
302
|
+
return rows.map((row) => ({
|
|
303
|
+
name: row.name || row.COLUMN_NAME,
|
|
304
|
+
type: row.type || row.DATA_TYPE,
|
|
305
|
+
nullable: (row.null || row.IS_NULLABLE) === 'Y'
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
console.error(`[Snowflake] getSchema failed:`, error.message);
|
|
310
|
+
throw new Error(`Failed to fetch schema: ${error.message}`);
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
if (conn)
|
|
314
|
+
conn.destroy(() => { });
|
|
315
|
+
}
|
|
34
316
|
}
|
|
35
317
|
getSnippet(connection, lang) {
|
|
36
|
-
|
|
318
|
+
const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
319
|
+
if (lang === 'python') {
|
|
320
|
+
return `import os
|
|
321
|
+
import snowflake.connector
|
|
322
|
+
import pandas as pd
|
|
323
|
+
|
|
324
|
+
# Connection: ${connection.name}
|
|
325
|
+
# Type: snowflake
|
|
326
|
+
# Credentials loaded from environment variables (set in .env file)
|
|
327
|
+
conn = snowflake.connector.connect(
|
|
328
|
+
account=os.environ["${prefix}_ACCOUNT"],
|
|
329
|
+
user=os.environ["${prefix}_USER"],
|
|
330
|
+
password=os.environ["${prefix}_PASSWORD"],
|
|
331
|
+
warehouse=os.environ.get("${prefix}_WAREHOUSE", "COMPUTE_WH"),
|
|
332
|
+
database=os.environ.get("${prefix}_DATABASE", "")
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
# Example: Query a table
|
|
337
|
+
query = "SELECT * FROM your_table LIMIT 100"
|
|
338
|
+
cursor = conn.cursor()
|
|
339
|
+
cursor.execute(query)
|
|
340
|
+
df = cursor.fetch_pandas_all()
|
|
341
|
+
print(f"Successfully loaded {len(df)} rows from ${connection.name}")
|
|
342
|
+
print(df.head())
|
|
343
|
+
finally:
|
|
344
|
+
conn.close()
|
|
345
|
+
`;
|
|
346
|
+
}
|
|
347
|
+
return `# Language ${lang} not supported for Snowflake connector yet.`;
|
|
37
348
|
}
|
|
38
349
|
}
|
|
39
350
|
exports.SnowflakeConnector = SnowflakeConnector;
|
|
@@ -5,5 +5,6 @@ export declare class CSVConnector implements Connector {
|
|
|
5
5
|
test(connection: Connection): Promise<boolean>;
|
|
6
6
|
getTables(connection: Connection): Promise<Table[]>;
|
|
7
7
|
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
8
|
+
getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
|
|
8
9
|
getSnippet(connection: Connection, lang: string): string;
|
|
9
10
|
}
|
|
@@ -107,19 +107,39 @@ class CSVConnector {
|
|
|
107
107
|
}
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
let filePath = connection.filePath
|
|
112
|
-
if (filePath.startsWith('"') && filePath.endsWith('"')) {
|
|
110
|
+
async getSchema(connection, tableName) {
|
|
111
|
+
let filePath = connection.filePath;
|
|
112
|
+
if (filePath && filePath.startsWith('"') && filePath.endsWith('"')) {
|
|
113
113
|
filePath = filePath.slice(1, -1);
|
|
114
114
|
}
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
116
|
+
throw new Error('CSV file not found');
|
|
117
|
+
}
|
|
118
|
+
// Read first row to get headers and infer types from first data row
|
|
119
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
120
|
+
const result = papaparse_1.default.parse(fileContent, {
|
|
121
|
+
header: true,
|
|
122
|
+
skipEmptyLines: true,
|
|
123
|
+
preview: 2 // Only parse header + 1 data row for type inference
|
|
124
|
+
});
|
|
125
|
+
const columns = result.meta.fields || [];
|
|
126
|
+
const firstRow = result.data[0] || {};
|
|
127
|
+
return columns.map(col => ({
|
|
128
|
+
name: col,
|
|
129
|
+
type: typeof firstRow[col] === 'number' ? 'number' : 'string',
|
|
130
|
+
nullable: true
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
getSnippet(connection, lang) {
|
|
134
|
+
const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
117
135
|
if (lang === 'python') {
|
|
118
|
-
return `import
|
|
136
|
+
return `import os
|
|
137
|
+
import pandas as pd
|
|
119
138
|
|
|
120
139
|
# Connection: ${connection.name}
|
|
121
140
|
# Type: csv
|
|
122
|
-
|
|
141
|
+
# File path loaded from environment variable (set in .env file)
|
|
142
|
+
file_path = os.environ["${prefix}_FILE_PATH"]
|
|
123
143
|
|
|
124
144
|
try:
|
|
125
145
|
df = pd.read_csv(file_path)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Connector, Table, TableData, ColumnInfo } from '../index';
|
|
2
|
+
import { Connection } from '../../connections';
|
|
3
|
+
export declare class CustomConnector implements Connector {
|
|
4
|
+
type: string;
|
|
5
|
+
test(connection: Connection): Promise<boolean>;
|
|
6
|
+
getTables(connection: Connection): Promise<Table[]>;
|
|
7
|
+
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
8
|
+
getSchema(connection: Connection, tableName: string): Promise<ColumnInfo[]>;
|
|
9
|
+
getSnippet(connection: Connection, lang: string): string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CustomConnector = void 0;
|
|
4
|
+
class CustomConnector {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.type = 'custom';
|
|
7
|
+
}
|
|
8
|
+
async test(connection) {
|
|
9
|
+
console.log(`[Custom] Testing connection for ${connection.name}`);
|
|
10
|
+
if (!connection.connectionString?.trim()) {
|
|
11
|
+
throw new Error('Connection string is required');
|
|
12
|
+
}
|
|
13
|
+
// For custom connections, we just validate the string exists
|
|
14
|
+
// We cannot actually test without knowing the database type
|
|
15
|
+
console.log(`[Custom] Connection string provided (${connection.connectionString.length} chars)`);
|
|
16
|
+
console.log(`[Custom] Note: Custom connections cannot be fully tested - only validating string is present`);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
async getTables(connection) {
|
|
20
|
+
console.log(`[Custom] getTables called for ${connection.name}`);
|
|
21
|
+
// Custom connections don't support table listing through our UI
|
|
22
|
+
// Return empty - user will use their own queries
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
async getData(connection, tableName, page, limit) {
|
|
26
|
+
console.log(`[Custom] getData called for ${connection.name}`);
|
|
27
|
+
// Custom connections don't support data fetching through our UI
|
|
28
|
+
throw new Error('Custom connections do not support data preview. Use the snippet to query directly.');
|
|
29
|
+
}
|
|
30
|
+
async getSchema(connection, tableName) {
|
|
31
|
+
console.log(`[Custom] getSchema called for ${connection.name}`);
|
|
32
|
+
// Custom connections don't support schema discovery
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
getSnippet(connection, lang) {
|
|
36
|
+
const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
|
|
37
|
+
if (lang === 'python') {
|
|
38
|
+
return `import os
|
|
39
|
+
from sqlalchemy import create_engine
|
|
40
|
+
import pandas as pd
|
|
41
|
+
|
|
42
|
+
# Connection: ${connection.name}
|
|
43
|
+
# Type: custom
|
|
44
|
+
# Connection string loaded from environment variable (set in .env file)
|
|
45
|
+
connection_string = os.environ["${prefix}_CONNECTION_STRING"]
|
|
46
|
+
engine = create_engine(connection_string)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Example: Query a table
|
|
50
|
+
query = "SELECT * FROM your_table LIMIT 100"
|
|
51
|
+
df = pd.read_sql(query, engine)
|
|
52
|
+
print(f"Successfully loaded {len(df)} rows from ${connection.name}")
|
|
53
|
+
print(df.head())
|
|
54
|
+
finally:
|
|
55
|
+
engine.dispose()
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
return `# Language ${lang} not supported for Custom connector yet.`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.CustomConnector = CustomConnector;
|
|
@@ -4,6 +4,11 @@ export interface Table {
|
|
|
4
4
|
type?: string;
|
|
5
5
|
rowCount?: number | null;
|
|
6
6
|
}
|
|
7
|
+
export interface ColumnInfo {
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
nullable: boolean;
|
|
11
|
+
}
|
|
7
12
|
export interface TableData {
|
|
8
13
|
columns: string[];
|
|
9
14
|
rows: any[];
|
|
@@ -21,6 +26,7 @@ export interface Connector {
|
|
|
21
26
|
test(connection: Connection): Promise<boolean>;
|
|
22
27
|
getTables(connection: Connection): Promise<Table[]>;
|
|
23
28
|
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
29
|
+
getSchema(connection: Connection, tableName: string): Promise<ColumnInfo[]>;
|
|
24
30
|
getSnippet(connection: Connection, lang: string): string;
|
|
25
31
|
}
|
|
26
32
|
export declare class ConnectorRegistry {
|
|
@@ -5,5 +5,6 @@ export declare class MySQLConnector implements Connector {
|
|
|
5
5
|
test(connection: Connection): Promise<boolean>;
|
|
6
6
|
getTables(connection: Connection): Promise<Table[]>;
|
|
7
7
|
getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
|
|
8
|
+
getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
|
|
8
9
|
getSnippet(connection: Connection, lang: string): string;
|
|
9
10
|
}
|