dbgate-api-premium 7.1.3-alpha.3 → 7.1.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dbgate-api-premium",
3
3
  "main": "src/index.js",
4
- "version": "7.1.3-alpha.3",
4
+ "version": "7.1.3",
5
5
  "homepage": "https://www.dbgate.io/",
6
6
  "repository": {
7
7
  "type": "git",
@@ -30,11 +30,11 @@
30
30
  "compare-versions": "^3.6.0",
31
31
  "cors": "^2.8.5",
32
32
  "cross-env": "^6.0.3",
33
- "dbgate-datalib": "7.1.3-alpha.3",
33
+ "dbgate-datalib": "7.1.3",
34
34
  "dbgate-query-splitter": "^4.12.0",
35
- "dbgate-rest": "7.1.3-alpha.3",
36
- "dbgate-sqltree": "7.1.3-alpha.3",
37
- "dbgate-tools": "7.1.3-alpha.3",
35
+ "dbgate-rest": "7.1.3",
36
+ "dbgate-sqltree": "7.1.3",
37
+ "dbgate-tools": "7.1.3",
38
38
  "debug": "^4.3.4",
39
39
  "diff": "^5.0.0",
40
40
  "diff2html": "^3.4.13",
@@ -88,7 +88,7 @@
88
88
  "devDependencies": {
89
89
  "@types/fs-extra": "^9.0.11",
90
90
  "@types/lodash": "^4.14.149",
91
- "dbgate-types": "7.1.3-alpha.3",
91
+ "dbgate-types": "7.1.3",
92
92
  "env-cmd": "^10.1.0",
93
93
  "jsdoc-to-markdown": "^9.0.5",
94
94
  "node-loader": "^1.0.2",
@@ -34,7 +34,16 @@ async function loadPermissionsForUserId(userId) {
34
34
  const rolePermissions = sortPermissionsFromTheSameLevel(await storageReadUserRolePermissions(userId));
35
35
  const userPermissions = await storageReadUserPermissions(userId);
36
36
  const loggedUserPermissions = await storageReadRolePermissions(-2);
37
- return [...getPredefinedPermissions('logged-user'), ...loggedUserPermissions, ...rolePermissions, ...userPermissions];
37
+ const resp = [
38
+ ...getPredefinedPermissions('logged-user'),
39
+ ...loggedUserPermissions,
40
+ ...rolePermissions,
41
+ ...userPermissions,
42
+ ];
43
+ if (resp.some(p => p?.startsWith('admin/'))) {
44
+ resp.push('widgets/admin');
45
+ }
46
+ return resp;
38
47
  }
39
48
 
40
49
  function getBuiltinRoleIdFromRequest(req) {
@@ -390,10 +399,11 @@ class OauthProvider extends StorageProviderBase {
390
399
  const scopeParam = this.config.oauthScope ? `&scope=${this.config.oauthScope}` : '';
391
400
  return {
392
401
  status: 'ok',
393
- uri: `${this.config.oauthAuth}?client_id=${this.config.oauthClient
394
- }&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(
395
- state
396
- )}${scopeParam}`,
402
+ uri: `${this.config.oauthAuth}?client_id=${
403
+ this.config.oauthClient
404
+ }&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(
405
+ state
406
+ )}${scopeParam}`,
397
407
  };
398
408
  }
399
409
 
@@ -714,5 +724,5 @@ function createStorageAuthProvider(config) {
714
724
  module.exports = {
715
725
  createStorageAuthProvider,
716
726
  SuperadminAuthProvider,
717
- getBuiltinRoleIdFromRequest
727
+ getBuiltinRoleIdFromRequest,
718
728
  };
@@ -95,10 +95,12 @@ module.exports = {
95
95
  }
96
96
  },
97
97
  handle_response(conid, database, { msgid, ...response }) {
98
- const [resolve, reject, additionalData] = this.requests[msgid];
99
- resolve(response);
100
- if (additionalData?.auditLogger) {
101
- additionalData?.auditLogger(response);
98
+ const [resolve, reject, additionalData] = this.requests[msgid] || [];
99
+ if (resolve) {
100
+ resolve(response);
101
+ if (additionalData?.auditLogger) {
102
+ additionalData?.auditLogger(response);
103
+ }
102
104
  }
103
105
  delete this.requests[msgid];
104
106
  },
@@ -239,7 +241,7 @@ module.exports = {
239
241
  sendRequest(conn, message, additionalData = {}) {
240
242
  const msgid = crypto.randomUUID();
241
243
  const promise = new Promise((resolve, reject) => {
242
- this.requests[msgid] = [resolve, reject, additionalData];
244
+ this.requests[msgid] = [resolve, reject, additionalData, conn.conid, conn.database];
243
245
  try {
244
246
  const serializedMessage = serializeJsTypesForJsonStringify({ msgid, ...message });
245
247
  conn.subprocess.send(serializedMessage);
@@ -264,12 +266,12 @@ module.exports = {
264
266
  },
265
267
 
266
268
  sqlSelect_meta: true,
267
- async sqlSelect({ conid, database, select, auditLogSessionGroup }, req) {
269
+ async sqlSelect({ conid, database, select, commandTimeout, auditLogSessionGroup }, req) {
268
270
  await testConnectionPermission(conid, req);
269
271
  const opened = await this.ensureOpened(conid, database);
270
272
  const res = await this.sendRequest(
271
273
  opened,
272
- { msgtype: 'sqlSelect', select },
274
+ { msgtype: 'sqlSelect', select, commandTimeout },
273
275
  {
274
276
  auditLogger:
275
277
  auditLogSessionGroup && select?.from?.name?.pureName
@@ -344,9 +346,12 @@ module.exports = {
344
346
  },
345
347
 
346
348
  collectionData_meta: true,
347
- async collectionData({ conid, database, options, auditLogSessionGroup }, req) {
349
+ async collectionData({ conid, database, options, commandTimeout, auditLogSessionGroup }, req) {
348
350
  await testConnectionPermission(conid, req);
349
351
  const opened = await this.ensureOpened(conid, database);
352
+ if (commandTimeout && options) {
353
+ options.commandTimeout = commandTimeout;
354
+ }
350
355
  const res = await this.sendRequest(
351
356
  opened,
352
357
  { msgtype: 'collectionData', options },
@@ -580,6 +585,24 @@ module.exports = {
580
585
  };
581
586
  },
582
587
 
588
+ pingDatabases_meta: true,
589
+ async pingDatabases({ databases }, req) {
590
+ if (!databases || !Array.isArray(databases)) return { status: 'ok' };
591
+ for (const { conid, database } of databases) {
592
+ if (!conid || !database) continue;
593
+ const existing = this.opened.find(x => x.conid == conid && x.database == database);
594
+ if (existing) {
595
+ try {
596
+ existing.subprocess.send({ msgtype: 'ping' });
597
+ } catch (err) {
598
+ logger.error(extractErrorLogData(err), 'DBGM-00308 Error pinging DB connection');
599
+ this.close(conid, database);
600
+ }
601
+ }
602
+ }
603
+ return { status: 'ok' };
604
+ },
605
+
583
606
  refresh_meta: true,
584
607
  async refresh({ conid, database, keepOpen }, req) {
585
608
  await testConnectionPermission(conid, req);
@@ -622,6 +645,15 @@ module.exports = {
622
645
  structure: existing.structure,
623
646
  };
624
647
  socket.emitChanged(`database-status-changed`, { conid, database });
648
+
649
+ // Reject all pending requests for this connection
650
+ for (const [msgid, entry] of Object.entries(this.requests)) {
651
+ const [resolve, reject, additionalData, reqConid, reqDatabase] = entry;
652
+ if (reqConid === conid && reqDatabase === database) {
653
+ reject('DBGM-00309 Database connection closed');
654
+ delete this.requests[msgid];
655
+ }
656
+ }
625
657
  }
626
658
  },
627
659
 
@@ -15,7 +15,8 @@ const getDiagramExport = require('../utility/getDiagramExport');
15
15
  const apps = require('./apps');
16
16
  const getMapExport = require('../utility/getMapExport');
17
17
  const dbgateApi = require('../shell');
18
- const { getLogger } = require('dbgate-tools');
18
+ const { getLogger, getSqlFrontMatter } = require('dbgate-tools');
19
+ const yaml = require('js-yaml');
19
20
  const platformInfo = require('../utility/platformInfo');
20
21
  const { checkSecureFilePathsWithoutDirectory, checkSecureDirectories } = require('../utility/security');
21
22
  const { copyAppLogsIntoFile, getRecentAppLogRecords } = require('../utility/appLogStore');
@@ -35,13 +36,46 @@ function deserialize(format, text) {
35
36
 
36
37
  module.exports = {
37
38
  list_meta: true,
38
- async list({ folder }, req) {
39
+ async list({ folder, parseFrontMatter }, req) {
39
40
  const loadedPermissions = await loadPermissionsFromRequest(req);
40
41
  if (!hasPermission(`files/${folder}/read`, loadedPermissions)) return [];
41
42
  const dir = path.join(filesdir(), folder);
42
43
  if (!(await fs.exists(dir))) return [];
43
- const files = (await fs.readdir(dir)).map(file => ({ folder, file }));
44
- return files;
44
+ const fileNames = await fs.readdir(dir);
45
+ if (!parseFrontMatter) {
46
+ return fileNames.map(file => ({ folder, file }));
47
+ }
48
+ const result = [];
49
+ for (const file of fileNames) {
50
+ const item = { folder, file };
51
+ let fh;
52
+ try {
53
+ fh = await require('fs').promises.open(path.join(dir, file), 'r');
54
+ const buf = new Uint8Array(512);
55
+ const { bytesRead } = await fh.read(buf, 0, 512, 0);
56
+ let text = Buffer.from(buf.buffer, 0, bytesRead).toString('utf-8');
57
+
58
+ if (text.includes('-- >>>') && !text.includes('-- <<<')) {
59
+ const stat = await fh.stat();
60
+ const fullSize = Math.min(stat.size, 4096);
61
+ if (fullSize > 512) {
62
+ const fullBuf = new Uint8Array(fullSize);
63
+ const { bytesRead: fullBytesRead } = await fh.read(fullBuf, 0, fullSize, 0);
64
+ text = Buffer.from(fullBuf.buffer, 0, fullBytesRead).toString('utf-8');
65
+ }
66
+ }
67
+
68
+ const fm = getSqlFrontMatter(text, yaml);
69
+ if (fm?.connectionId) item.connectionId = fm.connectionId;
70
+ if (fm?.databaseName) item.databaseName = fm.databaseName;
71
+ } catch (e) {
72
+ // ignore read errors for individual files
73
+ } finally {
74
+ if (fh) await fh.close().catch(() => {});
75
+ }
76
+ result.push(item);
77
+ }
78
+ return result;
45
79
  },
46
80
 
47
81
  listAll_meta: true,
@@ -228,6 +228,19 @@ module.exports = {
228
228
  return { state: 'ok' };
229
229
  },
230
230
 
231
+ setIsolationLevel_meta: true,
232
+ async setIsolationLevel({ sesid, level }) {
233
+ const session = this.opened.find(x => x.sesid == sesid);
234
+ if (!session) {
235
+ throw new Error('Invalid session');
236
+ }
237
+
238
+ logger.info({ sesid, level }, 'DBGM-00315 Setting transaction isolation level');
239
+ session.subprocess.send({ msgtype: 'setIsolationLevel', level });
240
+
241
+ return { state: 'ok' };
242
+ },
243
+
231
244
  executeReader_meta: true,
232
245
  async executeReader({ conid, database, sql, queryName, appFolder }) {
233
246
  const { sesid } = await this.create({ conid, database });
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '7.1.3-alpha.3',
4
- buildTime: '2026-03-09T09:23:09.614Z'
3
+ version: '7.1.3',
4
+ buildTime: '2026-03-18T14:33:55.882Z'
5
5
  };
@@ -234,12 +234,12 @@ async function handleRunOperation({ msgid, operation, useTransaction }, skipRead
234
234
  }
235
235
  }
236
236
 
237
- async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false) {
237
+ async function handleQueryData({ msgid, sql, range, commandTimeout }, skipReadonlyCheck = false) {
238
238
  await waitConnected();
239
239
  const driver = requireEngineDriver(storedConnection);
240
240
  try {
241
241
  if (!skipReadonlyCheck) ensureExecuteCustomScript(driver);
242
- const res = await driver.query(dbhan, sql, { range });
242
+ const res = await driver.query(dbhan, sql, { range, commandTimeout });
243
243
  process.send({ msgtype: 'response', msgid, ...serializeJsTypesForJsonStringify(res) });
244
244
  } catch (err) {
245
245
  process.send({
@@ -250,11 +250,11 @@ async function handleQueryData({ msgid, sql, range }, skipReadonlyCheck = false)
250
250
  }
251
251
  }
252
252
 
253
- async function handleSqlSelect({ msgid, select }) {
253
+ async function handleSqlSelect({ msgid, select, commandTimeout }) {
254
254
  const driver = requireEngineDriver(storedConnection);
255
255
  const dmp = driver.createDumper();
256
256
  dumpSqlSelect(dmp, select);
257
- return handleQueryData({ msgid, sql: dmp.s, range: select.range }, true);
257
+ return handleQueryData({ msgid, sql: dmp.s, range: select.range, commandTimeout }, true);
258
258
  }
259
259
 
260
260
  async function handleDriverDataCore(msgid, callMethod, { logName }) {
@@ -11,6 +11,7 @@ const fs = require('fs');
11
11
  const { Readable, Transform } = require('stream');
12
12
  const { pipeline } = require('stream/promises');
13
13
  const getJslFileName = require('../utility/getJslFileName');
14
+ const { decryptPasswordString } = require('../utility/crypting');
14
15
  const {
15
16
  analyseOpenApiDefinition,
16
17
  analyseODataDefinition,
@@ -23,8 +24,37 @@ const {
23
24
 
24
25
  const logger = getLogger('restconnProcess');
25
26
 
27
+ function buildProxyConfig(proxyUrl, proxyUser, proxyPassword) {
28
+ const url = String(proxyUrl ?? '').trim();
29
+ const user = String(proxyUser ?? '').trim();
30
+ const pass = String(proxyPassword ?? '').trim();
31
+ if (!url && (user || pass)) {
32
+ throw new Error('DBGM-00327 Proxy user or password is set but proxy URL is missing');
33
+ }
34
+ if (!url) return undefined;
35
+ try {
36
+ const parsed = new URL(url.includes('://') ? url : `http://${url}`);
37
+ const config = {
38
+ protocol: parsed.protocol.replace(':', ''),
39
+ host: parsed.hostname,
40
+ port: parsed.port ? parseInt(parsed.port, 10) : (parsed.protocol === 'https:' ? 443 : 80),
41
+ };
42
+
43
+ const username = proxyUser ?? parsed.username;
44
+ const rawPassword = proxyPassword ?? parsed.password;
45
+ const password = decryptPasswordString(rawPassword);
46
+ if (username) {
47
+ config.auth = { username, password: password ?? '' };
48
+ }
49
+ return config;
50
+ } catch (err) {
51
+ throw new Error(`DBGM-00328 Invalid proxy URL "${url}": ${err && err.message ? err.message : err}`);
52
+ }
53
+ }
54
+
26
55
  let restInfo = null;
27
56
  let storedConnection = null;
57
+ let axiosInstance = axios.default;
28
58
  let lastSwaggerUrl = null;
29
59
  let lastApiInfo = null;
30
60
  let lastConnectError = null;
@@ -79,7 +109,7 @@ async function fetchOpenApiStructure() {
79
109
 
80
110
  let swaggerData = null;
81
111
  try {
82
- const response = await axios.default.get(url, {
112
+ const response = await axiosInstance.get(url, {
83
113
  timeout: 10000,
84
114
  headers,
85
115
  });
@@ -101,7 +131,7 @@ async function fetchOpenApiStructure() {
101
131
 
102
132
  async function fetchODataStructure() {
103
133
  if (!storedConnection.apiServerUrl1) {
104
- throw new Error('DBGM-00000 OData service URL is not configured');
134
+ throw new Error('DBGM-00316 OData service URL is not configured');
105
135
  }
106
136
 
107
137
  const url = storedConnection.apiServerUrl1;
@@ -112,25 +142,27 @@ async function fetchODataStructure() {
112
142
  'OData-Version': '4.0',
113
143
  };
114
144
 
145
+ const proxyConfig = buildProxyConfig(storedConnection?.httpProxyUrl, storedConnection?.httpProxyUser, storedConnection?.httpProxyPassword);
115
146
  let serviceDocument = null;
116
147
  try {
117
- const response = await axios.default.get(url, {
148
+ const response = await axiosInstance.get(url, {
118
149
  timeout: 10000,
119
150
  headers: odataHeaders,
151
+ ...(proxyConfig && { proxy: proxyConfig }),
120
152
  });
121
153
  serviceDocument = parseStructuredContent(response.data);
122
154
  } catch (err) {
123
- throw new Error(`DBGM-00000 Could not fetch OData service document: ${err.message}`);
155
+ throw new Error(`DBGM-00317 Could not fetch OData service document: ${err.message}`);
124
156
  }
125
157
 
126
158
  if (!serviceDocument || typeof serviceDocument !== 'object') {
127
- throw new Error('DBGM-00000 OData service document is empty or invalid');
159
+ throw new Error('DBGM-00318 OData service document is empty or invalid');
128
160
  }
129
161
 
130
162
  const metadataUrl = resolveODataMetadataUrl(serviceDocument['@odata.context'], url);
131
163
  let metadataXml = '';
132
164
  try {
133
- const response = await axios.default.get(metadataUrl, {
165
+ const response = await axiosInstance.get(metadataUrl, {
134
166
  timeout: 10000,
135
167
  headers: {
136
168
  ...authHeaders,
@@ -138,14 +170,15 @@ async function fetchODataStructure() {
138
170
  'OData-Version': '4.0',
139
171
  },
140
172
  transformResponse: value => value,
173
+ ...(proxyConfig && { proxy: proxyConfig }),
141
174
  });
142
175
  metadataXml = typeof response.data === 'string' ? response.data : String(response.data ?? '');
143
176
  } catch (err) {
144
- throw new Error(`DBGM-00000 Could not fetch OData metadata document: ${err.message}`);
177
+ throw new Error(`DBGM-00319 Could not fetch OData metadata document: ${err.message}`);
145
178
  }
146
179
 
147
180
  if (!metadataXml.trim()) {
148
- throw new Error('DBGM-00000 OData metadata document is empty');
181
+ throw new Error('DBGM-00320 OData metadata document is empty');
149
182
  }
150
183
 
151
184
  const apiInfo = analyseODataDefinition(serviceDocument, url, metadataXml);
@@ -184,7 +217,7 @@ async function fetchGraphQLSchemaViaIntrospection() {
184
217
  storedConnection.apiServerUrl1,
185
218
  // @ts-ignore
186
219
  buildRestAuthHeaders(storedConnection.restAuth),
187
- axios.default
220
+ axiosInstance
188
221
  );
189
222
 
190
223
  return {
@@ -275,6 +308,9 @@ async function handleConnect(connection) {
275
308
  setStatusName('pending');
276
309
 
277
310
  try {
311
+ const proxyConfig = buildProxyConfig(connection?.httpProxyUrl, connection?.httpProxyUser, connection?.httpProxyPassword);
312
+ axiosInstance = proxyConfig ? axios.default.create({ proxy: proxyConfig }) : axios.default;
313
+
278
314
  const isGraphQL = connection.engine === 'graphql@rest';
279
315
  const structure = await loadStructureWithProgress('loadStructure', isGraphQL);
280
316
 
@@ -287,7 +323,7 @@ async function handleConnect(connection) {
287
323
  const apiType = isGraphQL ? 'GraphQL' : connection.engine === 'odata@rest' ? 'OData' : 'REST';
288
324
  logger.info(
289
325
  { url: storedConnection.apiServerUrl1 },
290
- `DBGM-00000 Connected to ${apiType} API`
326
+ `DBGM-00321 Connected to ${apiType} API`
291
327
  );
292
328
  resolveAfterConnect();
293
329
  } catch (err) {
@@ -354,7 +390,7 @@ async function handleExecuteOpenapi({ msgid, endpoint, method, parameters, serve
354
390
  parameters,
355
391
  server,
356
392
  auth,
357
- axios.default
393
+ axiosInstance
358
394
  );
359
395
 
360
396
  process.send({
@@ -389,7 +425,7 @@ async function handleExecuteOdata({ msgid, endpoint, method, parameters, server,
389
425
  parameters,
390
426
  server,
391
427
  auth,
392
- axios.default
428
+ axiosInstance
393
429
  );
394
430
 
395
431
  process.send({
@@ -428,7 +464,7 @@ async function handleApiQuery({ msgid, server, query, variables, auth }) {
428
464
  ...buildRestAuthHeaders(auth),
429
465
  };
430
466
 
431
- const response = await axios.default.post(url, { query, variables }, { headers });
467
+ const response = await axiosInstance.post(url, { query, variables }, { headers });
432
468
 
433
469
  process.send({
434
470
  msgtype: 'response',
@@ -520,7 +556,7 @@ ${selectionBlock}
520
556
  function extractGraphQlRows({ body, operationName, projection }) {
521
557
  if (body?.errors?.length) {
522
558
  const message = body.errors.map(item => item?.message).filter(Boolean).join('; ') || 'GraphQL query error';
523
- throw new Error(`DBGM-00000 ${message}`);
559
+ throw new Error(`DBGM-00322 ${message}`);
524
560
  }
525
561
 
526
562
  const operationData = body?.data?.[operationName];
@@ -581,7 +617,7 @@ async function* streamGraphQlRows({ url, headers, operationName, projection, que
581
617
  filterValue,
582
618
  });
583
619
 
584
- const response = await axios.default.post(url, { query }, { headers });
620
+ const response = await axiosInstance.post(url, { query }, { headers });
585
621
  const { rows, hasNextPage, endCursor } = extractGraphQlRows({
586
622
  body: response?.data,
587
623
  operationName,
@@ -640,13 +676,13 @@ async function handleLoadGraphqlConnectionData({
640
676
 
641
677
  const url = server || storedConnection?.apiServerUrl1;
642
678
  if (!url) {
643
- throw new Error('DBGM-00000 GraphQL endpoint URL is not configured');
679
+ throw new Error('DBGM-00323 GraphQL endpoint URL is not configured');
644
680
  }
645
681
  if (!operationName) {
646
- throw new Error('DBGM-00000 GraphQL operation name is missing');
682
+ throw new Error('DBGM-00324 GraphQL operation name is missing');
647
683
  }
648
684
  if (!projection?.kind) {
649
- throw new Error('DBGM-00000 GraphQL projection is missing');
685
+ throw new Error('DBGM-00325 GraphQL projection is missing');
650
686
  }
651
687
 
652
688
  const rowsPageSize = pageSize || 100;
@@ -711,7 +747,7 @@ async function handleLoadGraphqlConnectionData({
711
747
  }
712
748
  }
713
749
 
714
- logger.error(extractErrorLogData(err), 'DBGM-00000 Error loading GraphQL connection data to JSL');
750
+ logger.error(extractErrorLogData(err), 'DBGM-00326 Error loading GraphQL connection data to JSL');
715
751
  process.send({
716
752
  msgtype: 'response',
717
753
  msgid,
@@ -77,6 +77,38 @@ async function handleStopProfiler({ jslid }) {
77
77
  currentProfiler = null;
78
78
  }
79
79
 
80
+ async function handleSetIsolationLevel({ level }) {
81
+ lastActivity = new Date().getTime();
82
+
83
+ await waitConnected();
84
+ const driver = requireEngineDriver(storedConnection);
85
+
86
+ if (!driver.setTransactionIsolationLevel) {
87
+ process.send({ msgtype: 'done', skipFinishedMessage: true });
88
+ return;
89
+ }
90
+
91
+ if (driver.isolationLevels && level && !driver.isolationLevels.includes(level)) {
92
+ process.send({
93
+ msgtype: 'info',
94
+ info: {
95
+ message: `Isolation level "${level}" is not supported by this driver. Supported levels: ${driver.isolationLevels.join(', ')}`,
96
+ severity: 'error',
97
+ },
98
+ });
99
+ process.send({ msgtype: 'done', skipFinishedMessage: true });
100
+ return;
101
+ }
102
+
103
+ executingScripts++;
104
+ try {
105
+ await driver.setTransactionIsolationLevel(dbhan, level);
106
+ process.send({ msgtype: 'done', controlCommand: 'setIsolationLevel' });
107
+ } finally {
108
+ executingScripts--;
109
+ }
110
+ }
111
+
80
112
  async function handleExecuteControlCommand({ command }) {
81
113
  lastActivity = new Date().getTime();
82
114
 
@@ -210,6 +242,7 @@ const messageHandlers = {
210
242
  connect: handleConnect,
211
243
  executeQuery: handleExecuteQuery,
212
244
  executeControlCommand: handleExecuteControlCommand,
245
+ setIsolationLevel: handleSetIsolationLevel,
213
246
  executeReader: handleExecuteReader,
214
247
  startProfiler: handleStartProfiler,
215
248
  stopProfiler: handleStopProfiler,
@@ -698,6 +698,30 @@ module.exports = {
698
698
  "columnName": "id_original",
699
699
  "dataType": "varchar(250)",
700
700
  "notNull": false
701
+ },
702
+ {
703
+ "pureName": "connections",
704
+ "columnName": "httpProxyUrl",
705
+ "dataType": "varchar(250)",
706
+ "notNull": false
707
+ },
708
+ {
709
+ "pureName": "connections",
710
+ "columnName": "httpProxyUser",
711
+ "dataType": "varchar(250)",
712
+ "notNull": false
713
+ },
714
+ {
715
+ "pureName": "connections",
716
+ "columnName": "httpProxyPassword",
717
+ "dataType": "varchar(250)",
718
+ "notNull": false
719
+ },
720
+ {
721
+ "pureName": "connections",
722
+ "columnName": "defaultIsolationLevel",
723
+ "dataType": "varchar(250)",
724
+ "notNull": false
701
725
  }
702
726
  ],
703
727
  "foreignKeys": [
@@ -132,7 +132,35 @@ async function connectUtility(driver, storedConnection, connectionMode, addition
132
132
  }
133
133
 
134
134
  connection.ssl = await extractConnectionSslParams(connection);
135
- connection.axios = axios.default;
135
+
136
+ const proxyUrl = String(connection.httpProxyUrl ?? '').trim();
137
+ const proxyUser = String(connection.httpProxyUser ?? '').trim();
138
+ const proxyPassword = String(connection.httpProxyPassword ?? '').trim();
139
+ if (!proxyUrl && (proxyUser || proxyPassword)) {
140
+ throw new Error('DBGM-00329 Proxy user or password is set but proxy URL is missing');
141
+ }
142
+ if (proxyUrl) {
143
+ let parsedProxy;
144
+ try {
145
+ const parsed = new URL(proxyUrl.includes('://') ? proxyUrl : `http://${proxyUrl}`);
146
+ parsedProxy = {
147
+ protocol: parsed.protocol.replace(':', ''),
148
+ host: parsed.hostname,
149
+ port: parsed.port ? parseInt(parsed.port, 10) : (parsed.protocol === 'https:' ? 443 : 80),
150
+ };
151
+ const username = connection.httpProxyUser ?? parsed.username;
152
+ const rawPassword = connection.httpProxyPassword ?? parsed.password;
153
+ const password = decryptPasswordString(rawPassword);
154
+ if (username) {
155
+ parsedProxy.auth = { username, password: password ?? '' };
156
+ }
157
+ } catch (err) {
158
+ throw new Error(`DBGM-00334 Invalid proxy URL "${proxyUrl}": ${err && err.message ? err.message : err}`);
159
+ }
160
+ connection.axios = axios.default.create({ proxy: parsedProxy });
161
+ } else {
162
+ connection.axios = axios.default;
163
+ }
136
164
 
137
165
  const conn = await driver.connect({ conid: connectionLoaded?._id, ...connection, ...additionalOptions });
138
166
  return conn;
@@ -101,7 +101,7 @@ function decryptObjectPasswordField(obj, field, encryptor = null) {
101
101
  return obj;
102
102
  }
103
103
 
104
- const fieldsToEncrypt = ['password', 'sshPassword', 'sshKeyfilePassword', 'connectionDefinition'];
104
+ const fieldsToEncrypt = ['password', 'sshPassword', 'sshKeyfilePassword', 'connectionDefinition', 'httpProxyPassword'];
105
105
  const additionalFieldsToMask = [
106
106
  'databaseUrl',
107
107
  'server',