dbgate-api 6.3.2 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/package.json +9 -7
  2. package/src/controllers/archive.js +99 -6
  3. package/src/controllers/auth.js +3 -1
  4. package/src/controllers/config.js +135 -22
  5. package/src/controllers/connections.js +35 -2
  6. package/src/controllers/databaseConnections.js +101 -2
  7. package/src/controllers/files.js +59 -0
  8. package/src/controllers/jsldata.js +9 -0
  9. package/src/controllers/runners.js +25 -5
  10. package/src/controllers/serverConnections.js +22 -2
  11. package/src/controllers/storage.js +4 -0
  12. package/src/controllers/uploads.js +0 -46
  13. package/src/currentVersion.js +2 -2
  14. package/src/main.js +7 -1
  15. package/src/proc/connectProcess.js +14 -2
  16. package/src/proc/databaseConnectionProcess.js +70 -5
  17. package/src/proc/serverConnectionProcess.js +7 -1
  18. package/src/proc/sessionProcess.js +15 -178
  19. package/src/shell/archiveReader.js +3 -1
  20. package/src/shell/collectorWriter.js +2 -2
  21. package/src/shell/copyStream.js +1 -0
  22. package/src/shell/dataReplicator.js +96 -0
  23. package/src/shell/download.js +22 -6
  24. package/src/shell/index.js +12 -2
  25. package/src/shell/jsonLinesWriter.js +4 -3
  26. package/src/shell/queryReader.js +10 -3
  27. package/src/shell/unzipDirectory.js +91 -0
  28. package/src/shell/unzipJsonLinesData.js +60 -0
  29. package/src/shell/unzipJsonLinesFile.js +59 -0
  30. package/src/shell/zipDirectory.js +49 -0
  31. package/src/shell/zipJsonLinesData.js +49 -0
  32. package/src/storageModel.js +819 -0
  33. package/src/utility/DatastoreProxy.js +4 -0
  34. package/src/utility/cloudUpgrade.js +1 -59
  35. package/src/utility/connectUtility.js +3 -1
  36. package/src/utility/crypting.js +137 -22
  37. package/src/utility/extractSingleFileFromZip.js +77 -0
  38. package/src/utility/getMapExport.js +2 -0
  39. package/src/utility/handleQueryStream.js +186 -0
  40. package/src/utility/healthStatus.js +12 -1
  41. package/src/utility/listZipEntries.js +41 -0
  42. package/src/utility/processArgs.js +5 -0
  43. package/src/utility/sshTunnel.js +13 -2
  44. package/src/shell/dataDuplicator.js +0 -61
@@ -9,6 +9,9 @@ const scheduler = require('./scheduler');
9
9
  const getDiagramExport = require('../utility/getDiagramExport');
10
10
  const apps = require('./apps');
11
11
  const getMapExport = require('../utility/getMapExport');
12
+ const dbgateApi = require('../shell');
13
+ const { getLogger } = require('dbgate-tools');
14
+ const logger = getLogger('files');
12
15
 
13
16
  function serialize(format, data) {
14
17
  if (format == 'text') return data;
@@ -219,4 +222,60 @@ module.exports = {
219
222
  return path.join(dir, file);
220
223
  }
221
224
  },
225
+
226
+ createZipFromJsons_meta: true,
227
+ async createZipFromJsons({ db, filePath }) {
228
+ logger.info(`Creating zip file from JSONS ${filePath}`);
229
+ await dbgateApi.zipJsonLinesData(db, filePath);
230
+ return true;
231
+ },
232
+
233
+ getJsonsFromZip_meta: true,
234
+ async getJsonsFromZip({ filePath }) {
235
+ const res = await dbgateApi.unzipJsonLinesData(filePath);
236
+ return res;
237
+ },
238
+
239
+ downloadText_meta: true,
240
+ async downloadText({ uri }, req) {
241
+ if (!uri) return null;
242
+ const filePath = await dbgateApi.download(uri);
243
+ const text = await fs.readFile(filePath, {
244
+ encoding: 'utf-8',
245
+ });
246
+ return text;
247
+ },
248
+
249
+ saveUploadedFile_meta: true,
250
+ async saveUploadedFile({ filePath, fileName }) {
251
+ const FOLDERS = ['sql', 'sqlite'];
252
+ for (const folder of FOLDERS) {
253
+ if (fileName.toLowerCase().endsWith('.' + folder)) {
254
+ logger.info(`Saving ${folder} file ${fileName}`);
255
+ await fs.copyFile(filePath, path.join(filesdir(), folder, fileName));
256
+
257
+ socket.emitChanged(`files-changed`, { folder: folder });
258
+ socket.emitChanged(`all-files-changed`);
259
+ return {
260
+ name: path.basename(filePath),
261
+ folder: folder,
262
+ };
263
+ }
264
+ }
265
+
266
+ throw new Error(`${fileName} doesn't have one of supported extensions: ${FOLDERS.join(', ')}`);
267
+ },
268
+
269
+ exportFile_meta: true,
270
+ async exportFile({ folder, file, filePath }, req) {
271
+ if (!hasPermission(`files/${folder}/read`, req)) return false;
272
+ await fs.copyFile(path.join(filesdir(), folder, file), filePath);
273
+ return true;
274
+ },
275
+
276
+ simpleCopy_meta: true,
277
+ async simpleCopy({ sourceFilePath, targetFilePath }, req) {
278
+ await fs.copyFile(sourceFilePath, targetFilePath);
279
+ return true;
280
+ },
222
281
  };
@@ -8,6 +8,8 @@ const getJslFileName = require('../utility/getJslFileName');
8
8
  const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
9
9
  const requirePluginFunction = require('../utility/requirePluginFunction');
10
10
  const socket = require('../utility/socket');
11
+ const crypto = require('crypto');
12
+ const dbgateApi = require('../shell');
11
13
 
12
14
  function readFirstLine(file) {
13
15
  return new Promise((resolve, reject) => {
@@ -293,4 +295,11 @@ module.exports = {
293
295
  })),
294
296
  };
295
297
  },
298
+
299
+ downloadJslData_meta: true,
300
+ async downloadJslData({ uri }) {
301
+ const jslid = crypto.randomUUID();
302
+ await dbgateApi.download(uri, { targetFile: getJslFileName(jslid) });
303
+ return { jslid };
304
+ },
296
305
  };
@@ -8,7 +8,7 @@ const { fork, spawn } = require('child_process');
8
8
  const { rundir, uploadsdir, pluginsdir, getPluginBackendPath, packagedPluginList } = require('../utility/directories');
9
9
  const {
10
10
  extractShellApiPlugins,
11
- extractShellApiFunctionName,
11
+ compileShellApiFunctionName,
12
12
  jsonScriptToJavascript,
13
13
  getLogger,
14
14
  safeJsonParse,
@@ -58,7 +58,7 @@ dbgateApi.initializeApiEnvironment();
58
58
  ${requirePluginsTemplate(extractShellApiPlugins(functionName, props))}
59
59
  require=null;
60
60
  async function run() {
61
- const reader=await ${extractShellApiFunctionName(functionName)}(${JSON.stringify(props)});
61
+ const reader=await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});
62
62
  const writer=await dbgateApi.collectorWriter({runid: '${runid}'});
63
63
  await dbgateApi.copyStream(reader, writer);
64
64
  }
@@ -96,9 +96,9 @@ module.exports = {
96
96
 
97
97
  handle_ping() {},
98
98
 
99
- handle_freeData(runid, { freeData }) {
99
+ handle_dataResult(runid, { dataResult }) {
100
100
  const { resolve } = this.requests[runid];
101
- resolve(freeData);
101
+ resolve(dataResult);
102
102
  delete this.requests[runid];
103
103
  },
104
104
 
@@ -273,7 +273,7 @@ module.exports = {
273
273
  const runid = crypto.randomUUID();
274
274
 
275
275
  if (script.type == 'json') {
276
- const js = jsonScriptToJavascript(script);
276
+ const js = await jsonScriptToJavascript(script);
277
277
  return this.startCore(runid, scriptTemplate(js, false));
278
278
  }
279
279
 
@@ -328,4 +328,24 @@ module.exports = {
328
328
  });
329
329
  return promise;
330
330
  },
331
+
332
+ scriptResult_meta: true,
333
+ async scriptResult({ script }) {
334
+ if (script.type != 'json') {
335
+ return { errorMessage: 'Only JSON scripts are allowed' };
336
+ }
337
+
338
+ const promise = new Promise(async (resolve, reject) => {
339
+ const runid = crypto.randomUUID();
340
+ this.requests[runid] = { resolve, reject, exitOnStreamError: true };
341
+ const cloned = _.cloneDeepWith(script, node => {
342
+ if (node?.$replace == 'runid') {
343
+ return runid;
344
+ }
345
+ });
346
+ const js = await jsonScriptToJavascript(cloned);
347
+ this.startCore(runid, scriptTemplate(js, false));
348
+ });
349
+ return promise;
350
+ },
331
351
  };
@@ -54,6 +54,9 @@ module.exports = {
54
54
  if (!connection) {
55
55
  throw new Error(`Connection with conid="${conid}" not found`);
56
56
  }
57
+ if (connection.singleDatabase) {
58
+ return null;
59
+ }
57
60
  if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
58
61
  throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
59
62
  }
@@ -98,6 +101,11 @@ module.exports = {
98
101
  if (newOpened.disconnected) return;
99
102
  this.close(conid, false);
100
103
  });
104
+ subprocess.on('error', err => {
105
+ logger.error(extractErrorLogData(err), 'Error in server connection subprocess');
106
+ if (newOpened.disconnected) return;
107
+ this.close(conid, false);
108
+ });
101
109
  subprocess.send({ msgtype: 'connect', ...connection, globalSettings: await config.getSettings() });
102
110
  return newOpened;
103
111
  });
@@ -137,14 +145,14 @@ module.exports = {
137
145
  if (conid == '__model') return [];
138
146
  testConnectionPermission(conid, req);
139
147
  const opened = await this.ensureOpened(conid);
140
- return opened.databases;
148
+ return opened?.databases ?? [];
141
149
  },
142
150
 
143
151
  version_meta: true,
144
152
  async version({ conid }, req) {
145
153
  testConnectionPermission(conid, req);
146
154
  const opened = await this.ensureOpened(conid);
147
- return opened.version;
155
+ return opened?.version ?? null;
148
156
  },
149
157
 
150
158
  serverStatus_meta: true,
@@ -165,6 +173,9 @@ module.exports = {
165
173
  }
166
174
  this.lastPinged[conid] = new Date().getTime();
167
175
  const opened = await this.ensureOpened(conid);
176
+ if (!opened) {
177
+ return Promise.resolve();
178
+ }
168
179
  try {
169
180
  opened.subprocess.send({ msgtype: 'ping' });
170
181
  } catch (err) {
@@ -189,6 +200,9 @@ module.exports = {
189
200
  async sendDatabaseOp({ conid, msgtype, name }, req) {
190
201
  testConnectionPermission(conid, req);
191
202
  const opened = await this.ensureOpened(conid);
203
+ if (!opened) {
204
+ return null;
205
+ }
192
206
  if (opened.connection.isReadOnly) return false;
193
207
  const res = await this.sendRequest(opened, { msgtype, name });
194
208
  if (res.errorMessage) {
@@ -228,6 +242,9 @@ module.exports = {
228
242
  async loadDataCore(msgtype, { conid, ...args }, req) {
229
243
  testConnectionPermission(conid, req);
230
244
  const opened = await this.ensureOpened(conid);
245
+ if (!opened) {
246
+ return null;
247
+ }
231
248
  const res = await this.sendRequest(opened, { msgtype, ...args });
232
249
  if (res.errorMessage) {
233
250
  console.error(res.errorMessage);
@@ -249,6 +266,9 @@ module.exports = {
249
266
  async summaryCommand({ conid, command, row }, req) {
250
267
  testConnectionPermission(conid, req);
251
268
  const opened = await this.ensureOpened(conid);
269
+ if (!opened) {
270
+ return null;
271
+ }
252
272
  if (opened.connection.isReadOnly) return false;
253
273
  return this.loadDataCore('summaryCommand', { conid, command, row });
254
274
  },
@@ -4,6 +4,10 @@ module.exports = {
4
4
  return null;
5
5
  },
6
6
 
7
+ async getExportedDatabase() {
8
+ return {};
9
+ },
10
+
7
11
  getConnection_meta: true,
8
12
  async getConnection({ conid }) {
9
13
  return null;
@@ -39,52 +39,6 @@ module.exports = {
39
39
  });
40
40
  },
41
41
 
42
- uploadDataFile_meta: {
43
- method: 'post',
44
- raw: true,
45
- },
46
- uploadDataFile(req, res) {
47
- const { data } = req.files || {};
48
-
49
- if (!data) {
50
- res.json(null);
51
- return;
52
- }
53
-
54
- if (data.name.toLowerCase().endsWith('.sql')) {
55
- logger.info(`Uploading SQL file ${data.name}, size=${data.size}`);
56
- data.mv(path.join(filesdir(), 'sql', data.name), () => {
57
- res.json({
58
- name: data.name,
59
- folder: 'sql',
60
- });
61
-
62
- socket.emitChanged(`files-changed`, { folder: 'sql' });
63
- socket.emitChanged(`all-files-changed`);
64
- });
65
- return;
66
- }
67
-
68
- res.json(null);
69
- },
70
-
71
- saveDataFile_meta: true,
72
- async saveDataFile({ filePath }) {
73
- if (filePath.toLowerCase().endsWith('.sql')) {
74
- logger.info(`Saving SQL file ${filePath}`);
75
- await fs.copyFile(filePath, path.join(filesdir(), 'sql', path.basename(filePath)));
76
-
77
- socket.emitChanged(`files-changed`, { folder: 'sql' });
78
- socket.emitChanged(`all-files-changed`);
79
- return {
80
- name: path.basename(filePath),
81
- folder: 'sql',
82
- };
83
- }
84
-
85
- return null;
86
- },
87
-
88
42
  get_meta: {
89
43
  method: 'get',
90
44
  raw: true,
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '6.3.2',
4
- buildTime: '2025-04-02T12:09:59.169Z'
3
+ version: '6.4.0',
4
+ buildTime: '2025-04-30T07:59:44.320Z'
5
5
  };
package/src/main.js CHANGED
@@ -38,7 +38,7 @@ const { getLogger } = require('dbgate-tools');
38
38
  const { getDefaultAuthProvider } = require('./auth/authProvider');
39
39
  const startCloudUpgradeTimer = require('./utility/cloudUpgrade');
40
40
  const { isProApp } = require('./utility/checkLicense');
41
- const getHealthStatus = require('./utility/healthStatus');
41
+ const { getHealthStatus, getHealthStatusSprinx } = require('./utility/healthStatus');
42
42
 
43
43
  const logger = getLogger('main');
44
44
 
@@ -124,6 +124,12 @@ function start() {
124
124
  res.end(JSON.stringify(health, null, 2));
125
125
  });
126
126
 
127
+ app.get(getExpressPath('/__health'), async function (req, res) {
128
+ res.setHeader('Content-Type', 'application/json');
129
+ const health = await getHealthStatusSprinx();
130
+ res.end(JSON.stringify(health, null, 2));
131
+ });
132
+
127
133
  app.use(bodyParser.json({ limit: '50mb' }));
128
134
 
129
135
  app.use(
@@ -4,6 +4,8 @@ const { connectUtility } = require('../utility/connectUtility');
4
4
  const { handleProcessCommunication } = require('../utility/processComm');
5
5
  const { pickSafeConnectionInfo } = require('../utility/crypting');
6
6
  const _ = require('lodash');
7
+ const { getLogger, extractErrorLogData } = require('dbgate-tools');
8
+ const logger = getLogger('connectProcess');
7
9
 
8
10
  const formatErrorDetail = (e, connection) => `${e.stack}
9
11
 
@@ -23,12 +25,22 @@ function start() {
23
25
  try {
24
26
  const driver = requireEngineDriver(connection);
25
27
  const dbhan = await connectUtility(driver, connection, 'app');
26
- const res = await driver.getVersion(dbhan);
28
+ let version = {
29
+ version: 'Unknown',
30
+ };
31
+ try {
32
+ version = await driver.getVersion(dbhan);
33
+ } catch (err) {
34
+ logger.error(extractErrorLogData(err), 'Error getting DB server version');
35
+ version = {
36
+ version: 'Unknown',
37
+ };
38
+ }
27
39
  let databases = undefined;
28
40
  if (requestDbList) {
29
41
  databases = await driver.listDatabases(dbhan);
30
42
  }
31
- process.send({ msgtype: 'connected', ...res, databases });
43
+ process.send({ msgtype: 'connected', ...version, databases });
32
44
  await driver.close(dbhan);
33
45
  } catch (e) {
34
46
  console.error(e);
@@ -9,13 +9,21 @@ const {
9
9
  dbNameLogCategory,
10
10
  extractErrorMessage,
11
11
  extractErrorLogData,
12
+ ScriptWriterEval,
13
+ SqlGenerator,
14
+ playJsonScriptWriter,
12
15
  } = require('dbgate-tools');
13
16
  const requireEngineDriver = require('../utility/requireEngineDriver');
14
17
  const { connectUtility } = require('../utility/connectUtility');
15
18
  const { handleProcessCommunication } = require('../utility/processComm');
16
- const { SqlGenerator } = require('dbgate-tools');
17
19
  const generateDeploySql = require('../shell/generateDeploySql');
18
20
  const { dumpSqlSelect } = require('dbgate-sqltree');
21
+ const { allowExecuteCustomScript, handleQueryStream } = require('../utility/handleQueryStream');
22
+ const dbgateApi = require('../shell');
23
+ const requirePlugin = require('../shell/requirePlugin');
24
+ const path = require('path');
25
+ const { rundir } = require('../utility/directories');
26
+ const fs = require('fs-extra');
19
27
 
20
28
  const logger = getLogger('dbconnProcess');
21
29
 
@@ -120,10 +128,15 @@ function setStatusName(name) {
120
128
 
121
129
  async function readVersion() {
122
130
  const driver = requireEngineDriver(storedConnection);
123
- const version = await driver.getVersion(dbhan);
124
- logger.debug(`Got server version: ${version.version}`);
125
- process.send({ msgtype: 'version', version });
126
- serverVersion = version;
131
+ try {
132
+ const version = await driver.getVersion(dbhan);
133
+ logger.debug(`Got server version: ${version.version}`);
134
+ serverVersion = version;
135
+ } catch (err) {
136
+ logger.error(extractErrorLogData(err), 'Error getting DB server version');
137
+ serverVersion = { version: 'Unknown' };
138
+ }
139
+ process.send({ msgtype: 'version', version: serverVersion });
127
140
  }
128
141
 
129
142
  async function handleConnect({ connection, structure, globalSettings }) {
@@ -370,6 +383,56 @@ async function handleGenerateDeploySql({ msgid, modelFolder }) {
370
383
  }
371
384
  }
372
385
 
386
+ async function handleExecuteSessionQuery({ sesid, sql }) {
387
+ await waitConnected();
388
+ const driver = requireEngineDriver(storedConnection);
389
+
390
+ if (!allowExecuteCustomScript(storedConnection, driver)) {
391
+ process.send({
392
+ msgtype: 'info',
393
+ info: {
394
+ message: 'Connection without read-only sessions is read only',
395
+ severity: 'error',
396
+ },
397
+ sesid,
398
+ });
399
+ process.send({ msgtype: 'done', sesid, skipFinishedMessage: true });
400
+ return;
401
+ //process.send({ msgtype: 'error', error: e.message });
402
+ }
403
+
404
+ const queryStreamInfoHolder = {
405
+ resultIndex: 0,
406
+ canceled: false,
407
+ };
408
+ for (const sqlItem of splitQuery(sql, {
409
+ ...driver.getQuerySplitterOptions('stream'),
410
+ returnRichInfo: true,
411
+ })) {
412
+ await handleQueryStream(dbhan, driver, queryStreamInfoHolder, sqlItem, sesid);
413
+ if (queryStreamInfoHolder.canceled) {
414
+ break;
415
+ }
416
+ }
417
+ process.send({ msgtype: 'done', sesid });
418
+ }
419
+
420
+ async function handleEvalJsonScript({ script, runid }) {
421
+ const directory = path.join(rundir(), runid);
422
+ fs.mkdirSync(directory);
423
+ const originalCwd = process.cwd();
424
+
425
+ try {
426
+ process.chdir(directory);
427
+
428
+ const evalWriter = new ScriptWriterEval(dbgateApi, requirePlugin, dbhan, runid);
429
+ await playJsonScriptWriter(script, evalWriter);
430
+ process.send({ msgtype: 'runnerDone', runid });
431
+ } finally {
432
+ process.chdir(originalCwd);
433
+ }
434
+ }
435
+
373
436
  // async function handleRunCommand({ msgid, sql }) {
374
437
  // await waitConnected();
375
438
  // const driver = engines(storedConnection);
@@ -400,6 +463,8 @@ const messageHandlers = {
400
463
  sqlSelect: handleSqlSelect,
401
464
  exportKeys: handleExportKeys,
402
465
  schemaList: handleSchemaList,
466
+ executeSessionQuery: handleExecuteSessionQuery,
467
+ evalJsonScript: handleEvalJsonScript,
403
468
  // runCommand: handleRunCommand,
404
469
  };
405
470
 
@@ -46,7 +46,13 @@ async function handleRefresh() {
46
46
 
47
47
  async function readVersion() {
48
48
  const driver = requireEngineDriver(storedConnection);
49
- const version = await driver.getVersion(dbhan);
49
+ let version;
50
+ try {
51
+ version = await driver.getVersion(dbhan);
52
+ } catch (err) {
53
+ logger.error(extractErrorLogData(err), 'Error getting DB server version');
54
+ version = { version: 'Unknown' };
55
+ }
50
56
  process.send({ msgtype: 'version', version });
51
57
  }
52
58