@bytebase/dbhub 0.4.2 → 0.4.6
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 +6 -6
- package/dist/index.js +200 -89
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -184,9 +184,9 @@ You can specify the SSL mode using the `sslmode` parameter in your DSN string:
|
|
|
184
184
|
| PostgreSQL | ✅ | ✅ | Certificate verification |
|
|
185
185
|
| MySQL | ✅ | ✅ | Certificate verification |
|
|
186
186
|
| MariaDB | ✅ | ✅ | Certificate verification |
|
|
187
|
-
| SQL Server |
|
|
187
|
+
| SQL Server | ✅ | ✅ | Certificate verification |
|
|
188
|
+
| Oracle | ✅ | ✅ | N/A (use Oracle client config) |
|
|
188
189
|
| SQLite | ❌ | ❌ | N/A (file-based) |
|
|
189
|
-
| Oracle | ❌ | ❌ | Built-in encryption |
|
|
190
190
|
|
|
191
191
|
**SSL Mode Options:**
|
|
192
192
|
|
|
@@ -257,12 +257,12 @@ DBHub supports the following database connection string formats:
|
|
|
257
257
|
|
|
258
258
|
| Database | DSN Format | Example |
|
|
259
259
|
| ---------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
260
|
-
| MySQL | `mysql://[user]:[password]@[host]:[port]/[database]` | `mysql://user:password@localhost:3306/dbname` |
|
|
261
|
-
| MariaDB | `mariadb://[user]:[password]@[host]:[port]/[database]` | `mariadb://user:password@localhost:3306/dbname` |
|
|
260
|
+
| MySQL | `mysql://[user]:[password]@[host]:[port]/[database]` | `mysql://user:password@localhost:3306/dbname?sslmode=disable` |
|
|
261
|
+
| MariaDB | `mariadb://[user]:[password]@[host]:[port]/[database]` | `mariadb://user:password@localhost:3306/dbname?sslmode=disable` |
|
|
262
262
|
| PostgreSQL | `postgres://[user]:[password]@[host]:[port]/[database]` | `postgres://user:password@localhost:5432/dbname?sslmode=disable` |
|
|
263
|
-
| SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname`
|
|
263
|
+
| SQL Server | `sqlserver://[user]:[password]@[host]:[port]/[database]` | `sqlserver://user:password@localhost:1433/dbname?sslmode=disable` |
|
|
264
264
|
| SQLite | `sqlite:///[path/to/file]` or `sqlite::memory:` | `sqlite:///path/to/database.db`, `sqlite:C:/Users/YourName/data/database.db (windows)` or `sqlite::memory:` |
|
|
265
|
-
| Oracle | `oracle://[user]:[password]@[host]:[port]/[service_name]` | `oracle://username:password@localhost:1521/service_name`
|
|
265
|
+
| Oracle | `oracle://[user]:[password]@[host]:[port]/[service_name]` | `oracle://username:password@localhost:1521/service_name?sslmode=disable` |
|
|
266
266
|
|
|
267
267
|
#### Oracle
|
|
268
268
|
|
package/dist/index.js
CHANGED
|
@@ -57,6 +57,97 @@ var _ConnectorRegistry = class _ConnectorRegistry {
|
|
|
57
57
|
_ConnectorRegistry.connectors = /* @__PURE__ */ new Map();
|
|
58
58
|
var ConnectorRegistry = _ConnectorRegistry;
|
|
59
59
|
|
|
60
|
+
// src/utils/safe-url.ts
|
|
61
|
+
var SafeURL = class {
|
|
62
|
+
/**
|
|
63
|
+
* Parse a URL and handle special characters in passwords
|
|
64
|
+
* This is a safe alternative to the URL constructor
|
|
65
|
+
*
|
|
66
|
+
* @param urlString - The DSN string to parse
|
|
67
|
+
*/
|
|
68
|
+
constructor(urlString) {
|
|
69
|
+
this.protocol = "";
|
|
70
|
+
this.hostname = "";
|
|
71
|
+
this.port = "";
|
|
72
|
+
this.pathname = "";
|
|
73
|
+
this.username = "";
|
|
74
|
+
this.password = "";
|
|
75
|
+
this.searchParams = /* @__PURE__ */ new Map();
|
|
76
|
+
if (!urlString || urlString.trim() === "") {
|
|
77
|
+
throw new Error("URL string cannot be empty");
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const protocolSeparator = urlString.indexOf("://");
|
|
81
|
+
if (protocolSeparator !== -1) {
|
|
82
|
+
this.protocol = urlString.substring(0, protocolSeparator + 1);
|
|
83
|
+
urlString = urlString.substring(protocolSeparator + 3);
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error('Invalid URL format: missing protocol (e.g., "mysql://")');
|
|
86
|
+
}
|
|
87
|
+
const questionMarkIndex = urlString.indexOf("?");
|
|
88
|
+
let queryParams = "";
|
|
89
|
+
if (questionMarkIndex !== -1) {
|
|
90
|
+
queryParams = urlString.substring(questionMarkIndex + 1);
|
|
91
|
+
urlString = urlString.substring(0, questionMarkIndex);
|
|
92
|
+
queryParams.split("&").forEach((pair) => {
|
|
93
|
+
const parts = pair.split("=");
|
|
94
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
95
|
+
this.searchParams.set(parts[0], decodeURIComponent(parts[1]));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const atIndex = urlString.indexOf("@");
|
|
100
|
+
if (atIndex !== -1) {
|
|
101
|
+
const auth = urlString.substring(0, atIndex);
|
|
102
|
+
urlString = urlString.substring(atIndex + 1);
|
|
103
|
+
const colonIndex2 = auth.indexOf(":");
|
|
104
|
+
if (colonIndex2 !== -1) {
|
|
105
|
+
this.username = auth.substring(0, colonIndex2);
|
|
106
|
+
this.password = auth.substring(colonIndex2 + 1);
|
|
107
|
+
this.username = decodeURIComponent(this.username);
|
|
108
|
+
this.password = decodeURIComponent(this.password);
|
|
109
|
+
} else {
|
|
110
|
+
this.username = auth;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const pathSeparatorIndex = urlString.indexOf("/");
|
|
114
|
+
if (pathSeparatorIndex !== -1) {
|
|
115
|
+
this.pathname = urlString.substring(pathSeparatorIndex);
|
|
116
|
+
urlString = urlString.substring(0, pathSeparatorIndex);
|
|
117
|
+
}
|
|
118
|
+
const colonIndex = urlString.indexOf(":");
|
|
119
|
+
if (colonIndex !== -1) {
|
|
120
|
+
this.hostname = urlString.substring(0, colonIndex);
|
|
121
|
+
this.port = urlString.substring(colonIndex + 1);
|
|
122
|
+
} else {
|
|
123
|
+
this.hostname = urlString;
|
|
124
|
+
}
|
|
125
|
+
if (this.protocol === "") {
|
|
126
|
+
throw new Error("Invalid URL: protocol is required");
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
throw new Error(`Failed to parse URL: ${error instanceof Error ? error.message : String(error)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Helper method to safely get a parameter from query string
|
|
134
|
+
*
|
|
135
|
+
* @param name - The parameter name to retrieve
|
|
136
|
+
* @returns The parameter value or null if not found
|
|
137
|
+
*/
|
|
138
|
+
getSearchParam(name) {
|
|
139
|
+
return this.searchParams.has(name) ? this.searchParams.get(name) : null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Helper method to iterate over all parameters
|
|
143
|
+
*
|
|
144
|
+
* @param callback - Function to call for each parameter
|
|
145
|
+
*/
|
|
146
|
+
forEachSearchParam(callback) {
|
|
147
|
+
this.searchParams.forEach((value, key) => callback(value, key));
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
60
151
|
// src/connectors/postgres/index.ts
|
|
61
152
|
var { Pool } = pg;
|
|
62
153
|
var PostgresDSNParser = class {
|
|
@@ -65,16 +156,16 @@ var PostgresDSNParser = class {
|
|
|
65
156
|
throw new Error(`Invalid PostgreSQL DSN: ${dsn}`);
|
|
66
157
|
}
|
|
67
158
|
try {
|
|
68
|
-
const url = new
|
|
159
|
+
const url = new SafeURL(dsn);
|
|
69
160
|
const config = {
|
|
70
161
|
host: url.hostname,
|
|
71
162
|
port: url.port ? parseInt(url.port) : 5432,
|
|
72
|
-
database: url.pathname.substring(1),
|
|
73
|
-
// Remove leading '/'
|
|
163
|
+
database: url.pathname ? url.pathname.substring(1) : "",
|
|
164
|
+
// Remove leading '/' if exists
|
|
74
165
|
user: url.username,
|
|
75
|
-
password: url.password
|
|
166
|
+
password: url.password
|
|
76
167
|
};
|
|
77
|
-
url.
|
|
168
|
+
url.forEachSearchParam((value, key) => {
|
|
78
169
|
if (key === "sslmode") {
|
|
79
170
|
if (value === "disable") {
|
|
80
171
|
config.ssl = false;
|
|
@@ -97,8 +188,7 @@ var PostgresDSNParser = class {
|
|
|
97
188
|
}
|
|
98
189
|
isValidDSN(dsn) {
|
|
99
190
|
try {
|
|
100
|
-
|
|
101
|
-
return url.protocol === "postgres:" || url.protocol === "postgresql:";
|
|
191
|
+
return dsn.startsWith("postgres://") || dsn.startsWith("postgresql://");
|
|
102
192
|
} catch (error) {
|
|
103
193
|
return false;
|
|
104
194
|
}
|
|
@@ -386,66 +476,73 @@ var SQLServerDSNParser = class {
|
|
|
386
476
|
"Invalid SQL Server DSN format. Expected: sqlserver://username:password@host:port/database"
|
|
387
477
|
);
|
|
388
478
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
options.
|
|
405
|
-
|
|
406
|
-
options.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
user,
|
|
411
|
-
password,
|
|
412
|
-
server: host,
|
|
413
|
-
port,
|
|
414
|
-
database,
|
|
415
|
-
options: {
|
|
416
|
-
encrypt: options.encrypt ?? true,
|
|
417
|
-
// Default to encrypted connection
|
|
418
|
-
trustServerCertificate: options.trustServerCertificate === true,
|
|
419
|
-
// Need explicit conversion to boolean
|
|
420
|
-
connectTimeout: options.connectTimeout ?? 15e3,
|
|
421
|
-
requestTimeout: options.requestTimeout ?? 15e3
|
|
479
|
+
try {
|
|
480
|
+
const url = new SafeURL(dsn);
|
|
481
|
+
const options = {};
|
|
482
|
+
url.forEachSearchParam((value, key) => {
|
|
483
|
+
if (key === "connectTimeout") {
|
|
484
|
+
options.connectTimeout = parseInt(value, 10);
|
|
485
|
+
} else if (key === "requestTimeout") {
|
|
486
|
+
options.requestTimeout = parseInt(value, 10);
|
|
487
|
+
} else if (key === "authentication") {
|
|
488
|
+
options.authentication = value;
|
|
489
|
+
} else if (key === "sslmode") {
|
|
490
|
+
options.sslmode = value;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
if (options.sslmode) {
|
|
494
|
+
if (options.sslmode === "disable") {
|
|
495
|
+
options.encrypt = false;
|
|
496
|
+
} else if (options.sslmode === "require") {
|
|
497
|
+
options.encrypt = true;
|
|
498
|
+
options.trustServerCertificate = true;
|
|
499
|
+
}
|
|
422
500
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
501
|
+
const config = {
|
|
502
|
+
user: url.username,
|
|
503
|
+
password: url.password,
|
|
504
|
+
server: url.hostname,
|
|
505
|
+
port: url.port ? parseInt(url.port) : 1433,
|
|
506
|
+
// Default SQL Server port
|
|
507
|
+
database: url.pathname ? url.pathname.substring(1) : "",
|
|
508
|
+
// Remove leading slash
|
|
509
|
+
options: {
|
|
510
|
+
encrypt: options.encrypt ?? true,
|
|
511
|
+
// Default to encrypted connection
|
|
512
|
+
trustServerCertificate: options.trustServerCertificate === true,
|
|
513
|
+
connectTimeout: options.connectTimeout ?? 15e3,
|
|
514
|
+
requestTimeout: options.requestTimeout ?? 15e3
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
if (options.authentication === "azure-active-directory-access-token") {
|
|
518
|
+
try {
|
|
519
|
+
const credential = new DefaultAzureCredential();
|
|
520
|
+
const token = await credential.getToken("https://database.windows.net/");
|
|
521
|
+
config.authentication = {
|
|
522
|
+
type: "azure-active-directory-access-token",
|
|
523
|
+
options: {
|
|
524
|
+
token: token.token
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
} catch (error) {
|
|
528
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
529
|
+
throw new Error(`Failed to get Azure AD token: ${errorMessage}`);
|
|
530
|
+
}
|
|
437
531
|
}
|
|
532
|
+
return config;
|
|
533
|
+
} catch (error) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`Failed to parse SQL Server DSN: ${error instanceof Error ? error.message : String(error)}`
|
|
536
|
+
);
|
|
438
537
|
}
|
|
439
|
-
return config;
|
|
440
538
|
}
|
|
441
539
|
getSampleDSN() {
|
|
442
|
-
return "sqlserver://username:password@localhost:1433/database?
|
|
540
|
+
return "sqlserver://username:password@localhost:1433/database?sslmode=require";
|
|
443
541
|
}
|
|
444
542
|
isValidDSN(dsn) {
|
|
445
543
|
try {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
} catch (e) {
|
|
544
|
+
return dsn.startsWith("sqlserver://");
|
|
545
|
+
} catch (error) {
|
|
449
546
|
return false;
|
|
450
547
|
}
|
|
451
548
|
}
|
|
@@ -723,7 +820,7 @@ var SQLiteDSNParser = class {
|
|
|
723
820
|
throw new Error(`Invalid SQLite DSN: ${dsn}`);
|
|
724
821
|
}
|
|
725
822
|
try {
|
|
726
|
-
const url = new
|
|
823
|
+
const url = new SafeURL(dsn);
|
|
727
824
|
let dbPath;
|
|
728
825
|
if (url.hostname === "" && url.pathname === ":memory:") {
|
|
729
826
|
dbPath = ":memory:";
|
|
@@ -746,8 +843,7 @@ var SQLiteDSNParser = class {
|
|
|
746
843
|
}
|
|
747
844
|
isValidDSN(dsn) {
|
|
748
845
|
try {
|
|
749
|
-
|
|
750
|
-
return url.protocol === "sqlite:";
|
|
846
|
+
return dsn.startsWith("sqlite://");
|
|
751
847
|
} catch (error) {
|
|
752
848
|
return false;
|
|
753
849
|
}
|
|
@@ -926,16 +1022,16 @@ var MySQLDSNParser = class {
|
|
|
926
1022
|
throw new Error(`Invalid MySQL DSN: ${dsn}`);
|
|
927
1023
|
}
|
|
928
1024
|
try {
|
|
929
|
-
const url = new
|
|
1025
|
+
const url = new SafeURL(dsn);
|
|
930
1026
|
const config = {
|
|
931
1027
|
host: url.hostname,
|
|
932
1028
|
port: url.port ? parseInt(url.port) : 3306,
|
|
933
|
-
database: url.pathname.substring(1),
|
|
934
|
-
// Remove leading '/'
|
|
1029
|
+
database: url.pathname ? url.pathname.substring(1) : "",
|
|
1030
|
+
// Remove leading '/' if exists
|
|
935
1031
|
user: url.username,
|
|
936
|
-
password: url.password
|
|
1032
|
+
password: url.password
|
|
937
1033
|
};
|
|
938
|
-
url.
|
|
1034
|
+
url.forEachSearchParam((value, key) => {
|
|
939
1035
|
if (key === "sslmode") {
|
|
940
1036
|
if (value === "disable") {
|
|
941
1037
|
config.ssl = void 0;
|
|
@@ -958,8 +1054,7 @@ var MySQLDSNParser = class {
|
|
|
958
1054
|
}
|
|
959
1055
|
isValidDSN(dsn) {
|
|
960
1056
|
try {
|
|
961
|
-
|
|
962
|
-
return url.protocol === "mysql:";
|
|
1057
|
+
return dsn.startsWith("mysql://");
|
|
963
1058
|
} catch (error) {
|
|
964
1059
|
return false;
|
|
965
1060
|
}
|
|
@@ -1283,15 +1378,16 @@ var MariadbDSNParser = class {
|
|
|
1283
1378
|
throw new Error(`Invalid MariaDB DSN: ${dsn}`);
|
|
1284
1379
|
}
|
|
1285
1380
|
try {
|
|
1286
|
-
const url = new
|
|
1381
|
+
const url = new SafeURL(dsn);
|
|
1287
1382
|
const config = {
|
|
1288
1383
|
host: url.hostname,
|
|
1289
1384
|
port: url.port ? parseInt(url.port) : 3306,
|
|
1290
|
-
database: url.pathname.substring(1),
|
|
1385
|
+
database: url.pathname ? url.pathname.substring(1) : "",
|
|
1386
|
+
// Remove leading '/' if exists
|
|
1291
1387
|
user: url.username,
|
|
1292
|
-
password:
|
|
1388
|
+
password: url.password
|
|
1293
1389
|
};
|
|
1294
|
-
url.
|
|
1390
|
+
url.forEachSearchParam((value, key) => {
|
|
1295
1391
|
if (key === "sslmode") {
|
|
1296
1392
|
if (value === "disable") {
|
|
1297
1393
|
config.ssl = void 0;
|
|
@@ -1314,8 +1410,7 @@ var MariadbDSNParser = class {
|
|
|
1314
1410
|
}
|
|
1315
1411
|
isValidDSN(dsn) {
|
|
1316
1412
|
try {
|
|
1317
|
-
|
|
1318
|
-
return url.protocol === "mariadb:";
|
|
1413
|
+
return dsn.startsWith("mariadb://");
|
|
1319
1414
|
} catch (error) {
|
|
1320
1415
|
return false;
|
|
1321
1416
|
}
|
|
@@ -1635,7 +1730,7 @@ ConnectorRegistry.register(mariadbConnector);
|
|
|
1635
1730
|
// src/connectors/oracle/index.ts
|
|
1636
1731
|
import oracledb from "oracledb";
|
|
1637
1732
|
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT;
|
|
1638
|
-
var
|
|
1733
|
+
var _OracleConnector = class _OracleConnector {
|
|
1639
1734
|
// constructor(config: ConnectionConfig) { // Removed config
|
|
1640
1735
|
constructor() {
|
|
1641
1736
|
// Connector ID and Name are part of the Connector interface
|
|
@@ -1650,25 +1745,22 @@ var OracleConnector = class {
|
|
|
1650
1745
|
throw new Error(`Invalid Oracle DSN: ${dsn}`);
|
|
1651
1746
|
}
|
|
1652
1747
|
try {
|
|
1653
|
-
const url = new
|
|
1654
|
-
const username = url.username;
|
|
1655
|
-
const password = url.password;
|
|
1656
|
-
const host = url.hostname;
|
|
1657
|
-
const port = url.port ? parseInt(url.port, 10) : 1521;
|
|
1748
|
+
const url = new SafeURL(dsn);
|
|
1658
1749
|
let serviceName = url.pathname;
|
|
1659
1750
|
if (serviceName.startsWith("/")) {
|
|
1660
1751
|
serviceName = serviceName.substring(1);
|
|
1661
1752
|
}
|
|
1662
|
-
const
|
|
1753
|
+
const port = url.port ? parseInt(url.port) : 1521;
|
|
1754
|
+
const connectString = `${url.hostname}:${port}/${serviceName}`;
|
|
1663
1755
|
const config = {
|
|
1664
|
-
user: username,
|
|
1665
|
-
password,
|
|
1756
|
+
user: url.username,
|
|
1757
|
+
password: url.password,
|
|
1666
1758
|
connectString,
|
|
1667
1759
|
poolMin: 0,
|
|
1668
1760
|
poolMax: 10,
|
|
1669
1761
|
poolIncrement: 1
|
|
1670
1762
|
};
|
|
1671
|
-
url.
|
|
1763
|
+
url.forEachSearchParam((value, key) => {
|
|
1672
1764
|
switch (key.toLowerCase()) {
|
|
1673
1765
|
case "poolmin":
|
|
1674
1766
|
config.poolMin = parseInt(value, 10);
|
|
@@ -1679,6 +1771,15 @@ var OracleConnector = class {
|
|
|
1679
1771
|
case "poolincrement":
|
|
1680
1772
|
config.poolIncrement = parseInt(value, 10);
|
|
1681
1773
|
break;
|
|
1774
|
+
case "sslmode":
|
|
1775
|
+
switch (value.toLowerCase()) {
|
|
1776
|
+
case "disable":
|
|
1777
|
+
break;
|
|
1778
|
+
case "require":
|
|
1779
|
+
config.sslServerDNMatch = false;
|
|
1780
|
+
break;
|
|
1781
|
+
}
|
|
1782
|
+
break;
|
|
1682
1783
|
}
|
|
1683
1784
|
});
|
|
1684
1785
|
return config;
|
|
@@ -1687,17 +1788,23 @@ var OracleConnector = class {
|
|
|
1687
1788
|
}
|
|
1688
1789
|
},
|
|
1689
1790
|
getSampleDSN: () => {
|
|
1690
|
-
return "oracle://username:password@host:1521/service_name";
|
|
1791
|
+
return "oracle://username:password@host:1521/service_name?sslmode=require";
|
|
1691
1792
|
},
|
|
1692
1793
|
isValidDSN: (dsn) => {
|
|
1693
1794
|
try {
|
|
1694
|
-
|
|
1695
|
-
return url.protocol === "oracle:";
|
|
1795
|
+
return dsn.startsWith("oracle://");
|
|
1696
1796
|
} catch (error) {
|
|
1697
1797
|
return false;
|
|
1698
1798
|
}
|
|
1699
1799
|
}
|
|
1700
1800
|
};
|
|
1801
|
+
oracledb.autoCommit = true;
|
|
1802
|
+
}
|
|
1803
|
+
// Initialize Oracle client only once
|
|
1804
|
+
initClient() {
|
|
1805
|
+
if (_OracleConnector.clientInitialized) {
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1701
1808
|
try {
|
|
1702
1809
|
if (process.env.ORACLE_LIB_DIR) {
|
|
1703
1810
|
oracledb.initOracleClient({ libDir: process.env.ORACLE_LIB_DIR });
|
|
@@ -1705,13 +1812,14 @@ var OracleConnector = class {
|
|
|
1705
1812
|
} else {
|
|
1706
1813
|
console.error("ORACLE_LIB_DIR not specified, will use Thin mode by default");
|
|
1707
1814
|
}
|
|
1815
|
+
_OracleConnector.clientInitialized = true;
|
|
1708
1816
|
} catch (err) {
|
|
1709
1817
|
console.error("Failed to initialize Oracle client:", err);
|
|
1710
1818
|
}
|
|
1711
|
-
oracledb.autoCommit = true;
|
|
1712
1819
|
}
|
|
1713
1820
|
async connect(dsn, initializationScript) {
|
|
1714
1821
|
try {
|
|
1822
|
+
this.initClient();
|
|
1715
1823
|
const config = await this.dsnParser.parse(dsn);
|
|
1716
1824
|
this.pool = await oracledb.createPool(config);
|
|
1717
1825
|
const conn = await this.getConnection();
|
|
@@ -2092,6 +2200,9 @@ To resolve this, you need to use Thick mode:
|
|
|
2092
2200
|
return this.pool.getConnection();
|
|
2093
2201
|
}
|
|
2094
2202
|
};
|
|
2203
|
+
// Track if we've already initialized the client
|
|
2204
|
+
_OracleConnector.clientInitialized = false;
|
|
2205
|
+
var OracleConnector = _OracleConnector;
|
|
2095
2206
|
function formatOracleDataType(dataType, dataLength, dataPrecision, dataScale) {
|
|
2096
2207
|
if (!dataType) {
|
|
2097
2208
|
return "UNKNOWN";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bytebase/dbhub",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Universal Database MCP Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -29,18 +29,21 @@
|
|
|
29
29
|
"zod": "^3.24.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/oracledb": "^6.6.0",
|
|
33
32
|
"@types/better-sqlite3": "^7.6.12",
|
|
34
33
|
"@types/express": "^4.17.21",
|
|
35
34
|
"@types/mssql": "^9.1.7",
|
|
36
35
|
"@types/node": "^22.13.10",
|
|
36
|
+
"@types/oracledb": "^6.6.0",
|
|
37
37
|
"@types/pg": "^8.11.11",
|
|
38
38
|
"cross-env": "^7.0.3",
|
|
39
|
+
"husky": "^9.0.11",
|
|
40
|
+
"lint-staged": "^15.2.2",
|
|
39
41
|
"prettier": "^3.5.3",
|
|
40
42
|
"ts-node": "^10.9.2",
|
|
41
43
|
"tsup": "^8.4.0",
|
|
42
44
|
"tsx": "^4.19.3",
|
|
43
|
-
"typescript": "^5.8.2"
|
|
45
|
+
"typescript": "^5.8.2",
|
|
46
|
+
"vitest": "^1.6.1"
|
|
44
47
|
},
|
|
45
48
|
"compilerOptions": {
|
|
46
49
|
"target": "ES2020",
|
|
@@ -54,10 +57,16 @@
|
|
|
54
57
|
"include": [
|
|
55
58
|
"src/**/*"
|
|
56
59
|
],
|
|
60
|
+
"lint-staged": {
|
|
61
|
+
"*.{js,ts}": "vitest related --run"
|
|
62
|
+
},
|
|
57
63
|
"scripts": {
|
|
58
64
|
"build": "tsup",
|
|
59
65
|
"start": "node dist/index.js",
|
|
60
66
|
"dev": "NODE_ENV=development tsx src/index.ts",
|
|
61
|
-
"crossdev": "cross-env NODE_ENV=development tsx src/index.ts"
|
|
67
|
+
"crossdev": "cross-env NODE_ENV=development tsx src/index.ts",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:watch": "vitest",
|
|
70
|
+
"pre-commit": "lint-staged"
|
|
62
71
|
}
|
|
63
72
|
}
|