datapeek 0.1.8 → 0.1.10
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/dist/cli/chunk-3XB4P3G3.js +228 -0
- package/dist/cli/{chunk-5X2YZYYM.js → chunk-G7KSYREO.js} +43 -5
- package/dist/cli/chunk-Y3EHCZQX.js +220 -0
- package/dist/cli/{chunk-PTI2CUG6.js → chunk-Z2LV7EWL.js} +75 -4
- package/dist/cli/dev.js +317 -49
- package/dist/cli/index.js +317 -49
- package/dist/cli/{mssql-D7NDIUG7.js → mssql-AYS72PRQ.js} +1 -1
- package/dist/cli/{mssql-5VFXLOGV.js → mssql-HI3S4B7E.js} +3 -1
- package/dist/cli/{mssql-4KECNFPA.js → mssql-JB63TSGG.js} +4 -1
- package/dist/cli/mssql-Y5A62OYG.js +20 -0
- package/dist/client/assets/index-BhZ7NrhL.css +1 -0
- package/dist/client/assets/index-D-mash8_.js +370 -0
- package/dist/client/index.html +2 -2
- package/dist/server/{chunk-XFS5KTMI.js → chunk-JYZHE6GB.js} +43 -5
- package/dist/server/{chunk-7G2KHS5I.js → chunk-MJJGJZFC.js} +75 -4
- package/dist/server/chunk-VEX42Z2L.js +225 -0
- package/dist/server/chunk-VQPGGH2I.js +217 -0
- package/dist/server/dev.js +317 -49
- package/dist/server/index.js +317 -49
- package/dist/server/{mssql-UUT2IKOP.js → mssql-H6LKT5KN.js} +1 -1
- package/dist/server/mssql-JDRJT5H4.js +18 -0
- package/dist/server/mssql-LLTFKNX2.js +18 -0
- package/dist/server/mssql-UZUN227W.js +18 -0
- package/package.json +4 -3
- package/dist/cli/chunk-4IGBRCNN.js +0 -118
- package/dist/cli/chunk-IPR5JXB5.js +0 -108
- package/dist/cli/mssql-2REU4IX7.js +0 -17
- package/dist/client/assets/index-C32MC3if.js +0 -239
- package/dist/client/assets/index-piPKidzx.css +0 -1
- package/dist/server/chunk-XMPF5YCJ.js +0 -106
- package/dist/server/mssql-7KV6MCZK.js +0 -16
- package/dist/server/mssql-XRYTOVSF.js +0 -16
package/dist/client/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Datapeek - SQL Database Browser</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-D-mash8_.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BhZ7NrhL.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -68,7 +68,23 @@ async function testConnection(config) {
|
|
|
68
68
|
async function connect(config) {
|
|
69
69
|
await disconnect();
|
|
70
70
|
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
71
|
-
|
|
71
|
+
const poolConfig = {
|
|
72
|
+
...connectionConfig,
|
|
73
|
+
connectionTimeout: 3e4,
|
|
74
|
+
// 30 seconds for connection
|
|
75
|
+
pool: {
|
|
76
|
+
max: 10,
|
|
77
|
+
min: 0,
|
|
78
|
+
idleTimeoutMillis: 3e4,
|
|
79
|
+
acquireTimeoutMillis: 3e4
|
|
80
|
+
},
|
|
81
|
+
options: {
|
|
82
|
+
...connectionConfig.options,
|
|
83
|
+
requestTimeout: 12e4
|
|
84
|
+
// 2 minutes for queries (increased for complex queries with JOINs)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
pool = new sql.ConnectionPool(poolConfig);
|
|
72
88
|
try {
|
|
73
89
|
await pool.connect();
|
|
74
90
|
} catch (error) {
|
|
@@ -93,6 +109,7 @@ async function executeQuery(query, parameters) {
|
|
|
93
109
|
throw new Error("Not connected to database");
|
|
94
110
|
}
|
|
95
111
|
const request = pool.request();
|
|
112
|
+
request.timeout = 12e4;
|
|
96
113
|
if (parameters) {
|
|
97
114
|
for (const param of parameters) {
|
|
98
115
|
if (param.type) {
|
|
@@ -102,14 +119,25 @@ async function executeQuery(query, parameters) {
|
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
121
|
}
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
try {
|
|
123
|
+
const result = await request.query(query);
|
|
124
|
+
return result.recordset || [];
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
127
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try disabling foreign key displays or reducing the page size.");
|
|
128
|
+
timeoutError.code = "ETIMEOUT";
|
|
129
|
+
timeoutError.originalError = error;
|
|
130
|
+
throw timeoutError;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
107
134
|
}
|
|
108
135
|
async function executeQueryMultiple(query, parameters) {
|
|
109
136
|
if (!pool || !pool.connected) {
|
|
110
137
|
throw new Error("Not connected to database");
|
|
111
138
|
}
|
|
112
139
|
const request = pool.request();
|
|
140
|
+
request.timeout = 12e4;
|
|
113
141
|
if (parameters) {
|
|
114
142
|
for (const param of parameters) {
|
|
115
143
|
if (param.type) {
|
|
@@ -119,8 +147,18 @@ async function executeQueryMultiple(query, parameters) {
|
|
|
119
147
|
}
|
|
120
148
|
}
|
|
121
149
|
}
|
|
122
|
-
|
|
123
|
-
|
|
150
|
+
try {
|
|
151
|
+
const result = await request.query(query);
|
|
152
|
+
return result.recordsets || [];
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
155
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try simplifying your query or reducing the result set size.");
|
|
156
|
+
timeoutError.code = "ETIMEOUT";
|
|
157
|
+
timeoutError.originalError = error;
|
|
158
|
+
throw timeoutError;
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
124
162
|
}
|
|
125
163
|
|
|
126
164
|
export {
|
|
@@ -68,7 +68,23 @@ async function testConnection(config) {
|
|
|
68
68
|
async function connect(config) {
|
|
69
69
|
await disconnect();
|
|
70
70
|
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
71
|
-
|
|
71
|
+
const poolConfig = {
|
|
72
|
+
...connectionConfig,
|
|
73
|
+
connectionTimeout: 3e4,
|
|
74
|
+
// 30 seconds for connection
|
|
75
|
+
pool: {
|
|
76
|
+
max: 10,
|
|
77
|
+
min: 0,
|
|
78
|
+
idleTimeoutMillis: 3e4,
|
|
79
|
+
acquireTimeoutMillis: 3e4
|
|
80
|
+
},
|
|
81
|
+
options: {
|
|
82
|
+
...connectionConfig.options,
|
|
83
|
+
requestTimeout: 12e4
|
|
84
|
+
// 2 minutes for queries (increased for complex queries with JOINs)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
pool = new sql.ConnectionPool(poolConfig);
|
|
72
88
|
try {
|
|
73
89
|
await pool.connect();
|
|
74
90
|
} catch (error) {
|
|
@@ -93,6 +109,7 @@ async function executeQuery(query, parameters) {
|
|
|
93
109
|
throw new Error("Not connected to database");
|
|
94
110
|
}
|
|
95
111
|
const request = pool.request();
|
|
112
|
+
request.timeout = 12e4;
|
|
96
113
|
if (parameters) {
|
|
97
114
|
for (const param of parameters) {
|
|
98
115
|
if (param.type) {
|
|
@@ -102,8 +119,61 @@ async function executeQuery(query, parameters) {
|
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
121
|
}
|
|
105
|
-
|
|
106
|
-
|
|
122
|
+
try {
|
|
123
|
+
const result = await request.query(query);
|
|
124
|
+
return result.recordset || [];
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
127
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try disabling foreign key displays or reducing the page size.");
|
|
128
|
+
timeoutError.code = "ETIMEOUT";
|
|
129
|
+
timeoutError.originalError = error;
|
|
130
|
+
throw timeoutError;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function executeQueryMultiple(query, parameters) {
|
|
136
|
+
if (!pool || !pool.connected) {
|
|
137
|
+
throw new Error("Not connected to database");
|
|
138
|
+
}
|
|
139
|
+
const request = pool.request();
|
|
140
|
+
request.timeout = 12e4;
|
|
141
|
+
if (parameters) {
|
|
142
|
+
for (const param of parameters) {
|
|
143
|
+
if (param.type) {
|
|
144
|
+
request.input(param.name, param.type, param.value);
|
|
145
|
+
} else {
|
|
146
|
+
request.input(param.name, param.value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const result = await request.query(query);
|
|
152
|
+
const recordsets = result.recordsets || [];
|
|
153
|
+
const columnMetadata = [];
|
|
154
|
+
if (result.recordset && recordsets.length > 0) {
|
|
155
|
+
recordsets.forEach((recordset, index) => {
|
|
156
|
+
if (recordset.length === 0) {
|
|
157
|
+
const resultSet = index === 0 ? result.recordset : result.recordsets?.[index] || null;
|
|
158
|
+
if (resultSet && resultSet.columns) {
|
|
159
|
+
const columns = Object.keys(resultSet.columns);
|
|
160
|
+
if (columns.length > 0) {
|
|
161
|
+
columnMetadata.push({ resultSetIndex: index, columns });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return { recordsets, columnMetadata: columnMetadata.length > 0 ? columnMetadata : void 0 };
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
170
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try simplifying your query or reducing the result set size.");
|
|
171
|
+
timeoutError.code = "ETIMEOUT";
|
|
172
|
+
timeoutError.originalError = error;
|
|
173
|
+
throw timeoutError;
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
107
177
|
}
|
|
108
178
|
|
|
109
179
|
export {
|
|
@@ -112,5 +182,6 @@ export {
|
|
|
112
182
|
connect,
|
|
113
183
|
disconnect,
|
|
114
184
|
getConnection,
|
|
115
|
-
executeQuery
|
|
185
|
+
executeQuery,
|
|
186
|
+
executeQueryMultiple
|
|
116
187
|
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// src/server/db/mssql.ts
|
|
2
|
+
import sql from "mssql";
|
|
3
|
+
var pool = null;
|
|
4
|
+
function parseConnectionString(connectionString) {
|
|
5
|
+
const config = {
|
|
6
|
+
server: "",
|
|
7
|
+
database: "",
|
|
8
|
+
options: {
|
|
9
|
+
encrypt: true,
|
|
10
|
+
trustServerCertificate: false,
|
|
11
|
+
enableArithAbort: true
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const parts = connectionString.split(";");
|
|
15
|
+
for (const part of parts) {
|
|
16
|
+
const [key, ...valueParts] = part.split("=");
|
|
17
|
+
const value = valueParts.join("=").trim();
|
|
18
|
+
const keyLower = key.trim().toLowerCase();
|
|
19
|
+
switch (keyLower) {
|
|
20
|
+
case "server":
|
|
21
|
+
case "data source":
|
|
22
|
+
let serverValue = value;
|
|
23
|
+
if (serverValue.startsWith("tcp:")) {
|
|
24
|
+
serverValue = serverValue.substring(4);
|
|
25
|
+
}
|
|
26
|
+
const [serverHost, serverPort] = serverValue.split(",");
|
|
27
|
+
config.server = serverHost.trim();
|
|
28
|
+
if (serverPort) {
|
|
29
|
+
config.port = parseInt(serverPort.trim(), 10);
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
case "database":
|
|
33
|
+
case "initial catalog":
|
|
34
|
+
config.database = value;
|
|
35
|
+
break;
|
|
36
|
+
case "user id":
|
|
37
|
+
case "userid":
|
|
38
|
+
case "uid":
|
|
39
|
+
config.user = value;
|
|
40
|
+
break;
|
|
41
|
+
case "password":
|
|
42
|
+
case "pwd":
|
|
43
|
+
config.password = value;
|
|
44
|
+
break;
|
|
45
|
+
case "port":
|
|
46
|
+
config.port = parseInt(value, 10);
|
|
47
|
+
break;
|
|
48
|
+
case "encrypt":
|
|
49
|
+
config.options.encrypt = value.toLowerCase() === "true";
|
|
50
|
+
break;
|
|
51
|
+
case "trustservercertificate":
|
|
52
|
+
case "trust server certificate":
|
|
53
|
+
config.options.trustServerCertificate = value.toLowerCase() === "true";
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
59
|
+
async function testConnection(config) {
|
|
60
|
+
const testPool = typeof config === "string" ? new sql.ConnectionPool(parseConnectionString(config)) : new sql.ConnectionPool(config);
|
|
61
|
+
try {
|
|
62
|
+
await testPool.connect();
|
|
63
|
+
await testPool.close();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function connect(config) {
|
|
69
|
+
await disconnect();
|
|
70
|
+
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
71
|
+
const poolConfig = {
|
|
72
|
+
...connectionConfig,
|
|
73
|
+
connectionTimeout: 3e4,
|
|
74
|
+
// 30 seconds for connection
|
|
75
|
+
pool: {
|
|
76
|
+
max: 10,
|
|
77
|
+
min: 0,
|
|
78
|
+
idleTimeoutMillis: 3e4,
|
|
79
|
+
acquireTimeoutMillis: 3e4
|
|
80
|
+
},
|
|
81
|
+
options: {
|
|
82
|
+
...connectionConfig.options,
|
|
83
|
+
requestTimeout: 12e4
|
|
84
|
+
// 2 minutes for queries (increased for complex queries with JOINs)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
pool = new sql.ConnectionPool(poolConfig);
|
|
88
|
+
try {
|
|
89
|
+
await pool.connect();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
pool = null;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function disconnect() {
|
|
96
|
+
if (pool) {
|
|
97
|
+
try {
|
|
98
|
+
await pool.close();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
}
|
|
101
|
+
pool = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getConnection() {
|
|
105
|
+
return pool;
|
|
106
|
+
}
|
|
107
|
+
async function executeQuery(query, parameters) {
|
|
108
|
+
if (!pool || !pool.connected) {
|
|
109
|
+
throw new Error("Not connected to database");
|
|
110
|
+
}
|
|
111
|
+
const request = pool.request();
|
|
112
|
+
request.timeout = 12e4;
|
|
113
|
+
if (parameters) {
|
|
114
|
+
for (const param of parameters) {
|
|
115
|
+
if (param.type) {
|
|
116
|
+
request.input(param.name, param.type, param.value);
|
|
117
|
+
} else {
|
|
118
|
+
request.input(param.name, param.value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const result = await request.query(query);
|
|
124
|
+
return result.recordset || [];
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
127
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try disabling foreign key displays or reducing the page size.");
|
|
128
|
+
timeoutError.code = "ETIMEOUT";
|
|
129
|
+
timeoutError.originalError = error;
|
|
130
|
+
throw timeoutError;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function executeQueryMultiple(query, parameters) {
|
|
136
|
+
if (!pool || !pool.connected) {
|
|
137
|
+
throw new Error("Not connected to database");
|
|
138
|
+
}
|
|
139
|
+
const request = pool.request();
|
|
140
|
+
request.timeout = 12e4;
|
|
141
|
+
if (parameters) {
|
|
142
|
+
for (const param of parameters) {
|
|
143
|
+
if (param.type) {
|
|
144
|
+
request.input(param.name, param.type, param.value);
|
|
145
|
+
} else {
|
|
146
|
+
request.input(param.name, param.value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const result = await request.query(query);
|
|
152
|
+
const recordsets = result.recordsets || [];
|
|
153
|
+
const columnMetadata = [];
|
|
154
|
+
if (recordsets.length > 0) {
|
|
155
|
+
for (let index = 0; index < recordsets.length; index++) {
|
|
156
|
+
const recordset = recordsets[index];
|
|
157
|
+
if (recordset.length === 0) {
|
|
158
|
+
let columns = [];
|
|
159
|
+
const resultSet = index === 0 ? result.recordset : result.recordsets?.[index] || null;
|
|
160
|
+
if (resultSet) {
|
|
161
|
+
try {
|
|
162
|
+
const recordsetObj = resultSet;
|
|
163
|
+
if (recordsetObj.columns && typeof recordsetObj.columns === "object") {
|
|
164
|
+
columns = Object.keys(recordsetObj.columns);
|
|
165
|
+
} else if (recordsetObj.recordset && recordsetObj.recordset.columns) {
|
|
166
|
+
columns = Object.keys(recordsetObj.recordset.columns);
|
|
167
|
+
}
|
|
168
|
+
if (columns.length === 0 && result.recordset) {
|
|
169
|
+
if (index === 0 && result.recordset.columns) {
|
|
170
|
+
columns = Object.keys(result.recordset.columns);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch (e) {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (columns.length === 0) {
|
|
177
|
+
try {
|
|
178
|
+
const trimmedQuery = query.trim().toUpperCase();
|
|
179
|
+
if (trimmedQuery.startsWith("SELECT")) {
|
|
180
|
+
const metadataRequest = pool.request();
|
|
181
|
+
metadataRequest.timeout = 5e3;
|
|
182
|
+
let metadataQuery = query;
|
|
183
|
+
if (!trimmedQuery.match(/\bTOP\s+\d+/i)) {
|
|
184
|
+
metadataQuery = query.replace(/^(\s*SELECT\s+)(.*)$/i, "$1TOP 0 $2");
|
|
185
|
+
} else {
|
|
186
|
+
metadataQuery = query.replace(/\bTOP\s+\d+/i, "TOP 0");
|
|
187
|
+
}
|
|
188
|
+
const metadataResult = await metadataRequest.query(metadataQuery);
|
|
189
|
+
if (metadataResult.recordset) {
|
|
190
|
+
const metadataRecordset = metadataResult.recordset;
|
|
191
|
+
if (metadataRecordset.columns) {
|
|
192
|
+
columns = Object.keys(metadataRecordset.columns);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (metadataError) {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (columns.length > 0) {
|
|
200
|
+
columnMetadata.push({ resultSetIndex: index, columns });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { recordsets, columnMetadata: columnMetadata.length > 0 ? columnMetadata : void 0 };
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
208
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try simplifying your query or reducing the result set size.");
|
|
209
|
+
timeoutError.code = "ETIMEOUT";
|
|
210
|
+
timeoutError.originalError = error;
|
|
211
|
+
throw timeoutError;
|
|
212
|
+
}
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export {
|
|
218
|
+
parseConnectionString,
|
|
219
|
+
testConnection,
|
|
220
|
+
connect,
|
|
221
|
+
disconnect,
|
|
222
|
+
getConnection,
|
|
223
|
+
executeQuery,
|
|
224
|
+
executeQueryMultiple
|
|
225
|
+
};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// src/server/db/mssql.ts
|
|
2
|
+
import sql from "mssql";
|
|
3
|
+
var pool = null;
|
|
4
|
+
function parseConnectionString(connectionString) {
|
|
5
|
+
const config = {
|
|
6
|
+
server: "",
|
|
7
|
+
database: "",
|
|
8
|
+
options: {
|
|
9
|
+
encrypt: true,
|
|
10
|
+
trustServerCertificate: false,
|
|
11
|
+
enableArithAbort: true
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const parts = connectionString.split(";");
|
|
15
|
+
for (const part of parts) {
|
|
16
|
+
const [key, ...valueParts] = part.split("=");
|
|
17
|
+
const value = valueParts.join("=").trim();
|
|
18
|
+
const keyLower = key.trim().toLowerCase();
|
|
19
|
+
switch (keyLower) {
|
|
20
|
+
case "server":
|
|
21
|
+
case "data source":
|
|
22
|
+
let serverValue = value;
|
|
23
|
+
if (serverValue.startsWith("tcp:")) {
|
|
24
|
+
serverValue = serverValue.substring(4);
|
|
25
|
+
}
|
|
26
|
+
const [serverHost, serverPort] = serverValue.split(",");
|
|
27
|
+
config.server = serverHost.trim();
|
|
28
|
+
if (serverPort) {
|
|
29
|
+
config.port = parseInt(serverPort.trim(), 10);
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
case "database":
|
|
33
|
+
case "initial catalog":
|
|
34
|
+
config.database = value;
|
|
35
|
+
break;
|
|
36
|
+
case "user id":
|
|
37
|
+
case "userid":
|
|
38
|
+
case "uid":
|
|
39
|
+
config.user = value;
|
|
40
|
+
break;
|
|
41
|
+
case "password":
|
|
42
|
+
case "pwd":
|
|
43
|
+
config.password = value;
|
|
44
|
+
break;
|
|
45
|
+
case "port":
|
|
46
|
+
config.port = parseInt(value, 10);
|
|
47
|
+
break;
|
|
48
|
+
case "encrypt":
|
|
49
|
+
config.options.encrypt = value.toLowerCase() === "true";
|
|
50
|
+
break;
|
|
51
|
+
case "trustservercertificate":
|
|
52
|
+
case "trust server certificate":
|
|
53
|
+
config.options.trustServerCertificate = value.toLowerCase() === "true";
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
59
|
+
async function testConnection(config) {
|
|
60
|
+
const testPool = typeof config === "string" ? new sql.ConnectionPool(parseConnectionString(config)) : new sql.ConnectionPool(config);
|
|
61
|
+
try {
|
|
62
|
+
await testPool.connect();
|
|
63
|
+
await testPool.close();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function connect(config) {
|
|
69
|
+
await disconnect();
|
|
70
|
+
const connectionConfig = typeof config === "string" ? parseConnectionString(config) : config;
|
|
71
|
+
const poolConfig = {
|
|
72
|
+
...connectionConfig,
|
|
73
|
+
connectionTimeout: 3e4,
|
|
74
|
+
// 30 seconds for connection
|
|
75
|
+
pool: {
|
|
76
|
+
max: 10,
|
|
77
|
+
min: 0,
|
|
78
|
+
idleTimeoutMillis: 3e4,
|
|
79
|
+
acquireTimeoutMillis: 3e4
|
|
80
|
+
},
|
|
81
|
+
options: {
|
|
82
|
+
...connectionConfig.options,
|
|
83
|
+
requestTimeout: 12e4
|
|
84
|
+
// 2 minutes for queries (increased for complex queries with JOINs)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
pool = new sql.ConnectionPool(poolConfig);
|
|
88
|
+
try {
|
|
89
|
+
await pool.connect();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
pool = null;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function disconnect() {
|
|
96
|
+
if (pool) {
|
|
97
|
+
try {
|
|
98
|
+
await pool.close();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
}
|
|
101
|
+
pool = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function getConnection() {
|
|
105
|
+
return pool;
|
|
106
|
+
}
|
|
107
|
+
async function executeQuery(query, parameters) {
|
|
108
|
+
if (!pool || !pool.connected) {
|
|
109
|
+
throw new Error("Not connected to database");
|
|
110
|
+
}
|
|
111
|
+
const request = pool.request();
|
|
112
|
+
request.timeout = 12e4;
|
|
113
|
+
if (parameters) {
|
|
114
|
+
for (const param of parameters) {
|
|
115
|
+
if (param.type) {
|
|
116
|
+
request.input(param.name, param.type, param.value);
|
|
117
|
+
} else {
|
|
118
|
+
request.input(param.name, param.value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const result = await request.query(query);
|
|
124
|
+
return result.recordset || [];
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
127
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try disabling foreign key displays or reducing the page size.");
|
|
128
|
+
timeoutError.code = "ETIMEOUT";
|
|
129
|
+
timeoutError.originalError = error;
|
|
130
|
+
throw timeoutError;
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function executeQueryMultiple(query, parameters) {
|
|
136
|
+
if (!pool || !pool.connected) {
|
|
137
|
+
throw new Error("Not connected to database");
|
|
138
|
+
}
|
|
139
|
+
const request = pool.request();
|
|
140
|
+
request.timeout = 12e4;
|
|
141
|
+
if (parameters) {
|
|
142
|
+
for (const param of parameters) {
|
|
143
|
+
if (param.type) {
|
|
144
|
+
request.input(param.name, param.type, param.value);
|
|
145
|
+
} else {
|
|
146
|
+
request.input(param.name, param.value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const result = await request.query(query);
|
|
152
|
+
const recordsets = result.recordsets || [];
|
|
153
|
+
const columnMetadata = [];
|
|
154
|
+
const emptyResultSetIndices = [];
|
|
155
|
+
recordsets.forEach((recordset, index) => {
|
|
156
|
+
if (recordset.length === 0) {
|
|
157
|
+
emptyResultSetIndices.push(index);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
if (emptyResultSetIndices.length > 0) {
|
|
161
|
+
try {
|
|
162
|
+
const trimmedQuery = query.trim().toUpperCase();
|
|
163
|
+
if (trimmedQuery.startsWith("SELECT")) {
|
|
164
|
+
const metadataRequest = pool.request();
|
|
165
|
+
metadataRequest.timeout = 5e3;
|
|
166
|
+
let metadataQuery = query;
|
|
167
|
+
if (!trimmedQuery.includes("TOP ")) {
|
|
168
|
+
metadataQuery = query.replace(/^(\s*SELECT\s+)(.*)$/i, "$1TOP 0 $2");
|
|
169
|
+
} else {
|
|
170
|
+
metadataQuery = query.replace(/TOP\s+\d+/i, "TOP 0");
|
|
171
|
+
}
|
|
172
|
+
const metadataResult = await metadataRequest.query(metadataQuery);
|
|
173
|
+
if (metadataResult.recordset) {
|
|
174
|
+
const metadataRecordset = metadataResult.recordset;
|
|
175
|
+
if (metadataRecordset.columns && typeof metadataRecordset.columns === "object") {
|
|
176
|
+
const cols = Object.keys(metadataRecordset.columns);
|
|
177
|
+
if (cols.length > 0) {
|
|
178
|
+
emptyResultSetIndices.forEach((index) => {
|
|
179
|
+
columnMetadata.push({ resultSetIndex: index, columns: cols });
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (columnMetadata.length === 0 && metadataResult.recordset.columns) {
|
|
184
|
+
const cols = Object.keys(metadataResult.recordset.columns);
|
|
185
|
+
if (cols.length > 0) {
|
|
186
|
+
emptyResultSetIndices.forEach((index) => {
|
|
187
|
+
columnMetadata.push({ resultSetIndex: index, columns: cols });
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch (metadataError) {
|
|
194
|
+
console.debug("Could not extract column metadata for empty result set:", metadataError);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return { recordsets, columnMetadata: columnMetadata.length > 0 ? columnMetadata : void 0 };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error.code === "ETIMEOUT" || error.code === "ESOCKET" || error.message?.includes("timeout") || error.message?.includes("ETIMEDOUT")) {
|
|
200
|
+
const timeoutError = new Error("Query execution timeout. The query took too long to execute. Try simplifying your query or reducing the result set size.");
|
|
201
|
+
timeoutError.code = "ETIMEOUT";
|
|
202
|
+
timeoutError.originalError = error;
|
|
203
|
+
throw timeoutError;
|
|
204
|
+
}
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export {
|
|
210
|
+
parseConnectionString,
|
|
211
|
+
testConnection,
|
|
212
|
+
connect,
|
|
213
|
+
disconnect,
|
|
214
|
+
getConnection,
|
|
215
|
+
executeQuery,
|
|
216
|
+
executeQueryMultiple
|
|
217
|
+
};
|