dbgate-api 5.1.5 → 5.1.7-alpha.13

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.
@@ -7,6 +7,7 @@ const DatastoreProxy = require('../utility/DatastoreProxy');
7
7
  const { saveFreeTableData } = require('../utility/freeTableStorage');
8
8
  const getJslFileName = require('../utility/getJslFileName');
9
9
  const JsonLinesDatastore = require('../utility/JsonLinesDatastore');
10
+ const requirePluginFunction = require('../utility/requirePluginFunction');
10
11
  const socket = require('../utility/socket');
11
12
 
12
13
  function readFirstLine(file) {
@@ -99,10 +100,13 @@ module.exports = {
99
100
  // return readerInfo;
100
101
  // },
101
102
 
102
- async ensureDatastore(jslid) {
103
+ async ensureDatastore(jslid, formatterFunction) {
103
104
  let datastore = this.datastores[jslid];
104
- if (!datastore) {
105
- datastore = new JsonLinesDatastore(getJslFileName(jslid));
105
+ if (!datastore || datastore.formatterFunction != formatterFunction) {
106
+ if (datastore) {
107
+ datastore._closeReader();
108
+ }
109
+ datastore = new JsonLinesDatastore(getJslFileName(jslid), formatterFunction);
106
110
  // datastore = new DatastoreProxy(getJslFileName(jslid));
107
111
  this.datastores[jslid] = datastore;
108
112
  }
@@ -131,8 +135,8 @@ module.exports = {
131
135
  },
132
136
 
133
137
  getRows_meta: true,
134
- async getRows({ jslid, offset, limit, filters }) {
135
- const datastore = await this.ensureDatastore(jslid);
138
+ async getRows({ jslid, offset, limit, filters, formatterFunction }) {
139
+ const datastore = await this.ensureDatastore(jslid, formatterFunction);
136
140
  return datastore.getRows(offset, limit, _.isEmpty(filters) ? null : filters);
137
141
  },
138
142
 
@@ -150,8 +154,8 @@ module.exports = {
150
154
  },
151
155
 
152
156
  loadFieldValues_meta: true,
153
- async loadFieldValues({ jslid, field, search }) {
154
- const datastore = await this.ensureDatastore(jslid);
157
+ async loadFieldValues({ jslid, field, search, formatterFunction }) {
158
+ const datastore = await this.ensureDatastore(jslid, formatterFunction);
155
159
  const res = new Set();
156
160
  await datastore.enumRows(row => {
157
161
  if (!filterName(search, row[field])) return true;
@@ -188,4 +192,85 @@ module.exports = {
188
192
  await fs.promises.writeFile(getJslFileName(jslid), text);
189
193
  return true;
190
194
  },
195
+
196
+ extractTimelineChart_meta: true,
197
+ async extractTimelineChart({ jslid, timestampFunction, aggregateFunction, measures }) {
198
+ const timestamp = requirePluginFunction(timestampFunction);
199
+ const aggregate = requirePluginFunction(aggregateFunction);
200
+ const datastore = new JsonLinesDatastore(getJslFileName(jslid));
201
+ let mints = null;
202
+ let maxts = null;
203
+ // pass 1 - counts stats, time range
204
+ await datastore.enumRows(row => {
205
+ const ts = timestamp(row);
206
+ if (!mints || ts < mints) mints = ts;
207
+ if (!maxts || ts > maxts) maxts = ts;
208
+ return true;
209
+ });
210
+ const minTime = new Date(mints).getTime();
211
+ const maxTime = new Date(maxts).getTime();
212
+ const duration = maxTime - minTime;
213
+ const STEPS = 100;
214
+ let stepCount = duration > 100 * 1000 ? STEPS : Math.round((maxTime - minTime) / 1000);
215
+ if (stepCount < 2) {
216
+ stepCount = 2;
217
+ }
218
+ const stepDuration = duration / stepCount;
219
+ const labels = _.range(stepCount).map(i => new Date(minTime + stepDuration / 2 + stepDuration * i));
220
+
221
+ // const datasets = measures.map(m => ({
222
+ // label: m.label,
223
+ // data: Array(stepCount).fill(0),
224
+ // }));
225
+
226
+ const mproc = measures.map(m => ({
227
+ ...m,
228
+ }));
229
+
230
+ const data = Array(stepCount)
231
+ .fill(0)
232
+ .map(() => ({}));
233
+
234
+ // pass 2 - count measures
235
+ await datastore.enumRows(row => {
236
+ const ts = timestamp(row);
237
+ let part = Math.round((new Date(ts).getTime() - minTime) / stepDuration);
238
+ if (part < 0) part = 0;
239
+ if (part >= stepCount) part - stepCount - 1;
240
+ if (data[part]) {
241
+ data[part] = aggregate(data[part], row, stepDuration);
242
+ }
243
+ return true;
244
+ });
245
+
246
+ datastore._closeReader();
247
+
248
+ // const measureByField = _.fromPairs(measures.map((m, i) => [m.field, i]));
249
+
250
+ // for (let mindex = 0; mindex < measures.length; mindex++) {
251
+ // for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) {
252
+ // const measure = measures[mindex];
253
+ // if (measure.perSecond) {
254
+ // datasets[mindex].data[stepIndex] /= stepDuration / 1000;
255
+ // }
256
+ // if (measure.perField) {
257
+ // datasets[mindex].data[stepIndex] /= datasets[measureByField[measure.perField]].data[stepIndex];
258
+ // }
259
+ // }
260
+ // }
261
+
262
+ // for (let i = 0; i < measures.length; i++) {
263
+ // if (measures[i].hidden) {
264
+ // datasets[i] = null;
265
+ // }
266
+ // }
267
+
268
+ return {
269
+ labels,
270
+ datasets: mproc.map(m => ({
271
+ label: m.label,
272
+ data: data.map(d => d[m.field] || 0),
273
+ })),
274
+ };
275
+ },
191
276
  };
@@ -1,6 +1,7 @@
1
1
  const connections = require('./connections');
2
2
  const socket = require('../utility/socket');
3
3
  const { fork } = require('child_process');
4
+ const uuidv1 = require('uuid/v1');
4
5
  const _ = require('lodash');
5
6
  const AsyncLock = require('async-lock');
6
7
  const { handleProcessCommunication } = require('../utility/processComm');
@@ -8,23 +9,25 @@ const lock = new AsyncLock();
8
9
  const config = require('./config');
9
10
  const processArgs = require('../utility/processArgs');
10
11
  const { testConnectionPermission } = require('../utility/hasPermission');
12
+ const { MissingCredentialsError } = require('../utility/exceptions');
11
13
 
12
14
  module.exports = {
13
15
  opened: [],
14
16
  closed: {},
15
17
  lastPinged: {},
18
+ requests: {},
16
19
 
17
20
  handle_databases(conid, { databases }) {
18
21
  const existing = this.opened.find(x => x.conid == conid);
19
22
  if (!existing) return;
20
23
  existing.databases = databases;
21
- socket.emitChanged(`database-list-changed-${conid}`);
24
+ socket.emitChanged(`database-list-changed`, { conid });
22
25
  },
23
26
  handle_version(conid, { version }) {
24
27
  const existing = this.opened.find(x => x.conid == conid);
25
28
  if (!existing) return;
26
29
  existing.version = version;
27
- socket.emitChanged(`server-version-changed-${conid}`);
30
+ socket.emitChanged(`server-version-changed`, { conid });
28
31
  },
29
32
  handle_status(conid, { status }) {
30
33
  const existing = this.opened.find(x => x.conid == conid);
@@ -33,12 +36,20 @@ module.exports = {
33
36
  socket.emitChanged(`server-status-changed`);
34
37
  },
35
38
  handle_ping() {},
39
+ handle_response(conid, { msgid, ...response }) {
40
+ const [resolve, reject] = this.requests[msgid];
41
+ resolve(response);
42
+ delete this.requests[msgid];
43
+ },
36
44
 
37
45
  async ensureOpened(conid) {
38
46
  const res = await lock.acquire(conid, async () => {
39
47
  const existing = this.opened.find(x => x.conid == conid);
40
48
  if (existing) return existing;
41
49
  const connection = await connections.getCore({ conid });
50
+ if (connection.passwordMode == 'askPassword' || connection.passwordMode == 'askUser') {
51
+ throw new MissingCredentialsError({ conid, passwordMode: connection.passwordMode });
52
+ }
42
53
  const subprocess = fork(global['API_PACKAGE'] || process.argv[1], [
43
54
  '--is-forked-api',
44
55
  '--start-process',
@@ -120,9 +131,9 @@ module.exports = {
120
131
  },
121
132
 
122
133
  ping_meta: true,
123
- async ping({ connections }) {
134
+ async ping({ conidArray }) {
124
135
  await Promise.all(
125
- _.uniq(connections).map(async conid => {
136
+ _.uniq(conidArray).map(async conid => {
126
137
  const last = this.lastPinged[conid];
127
138
  if (last && new Date().getTime() - last < 30 * 1000) {
128
139
  return Promise.resolve();
@@ -161,4 +172,41 @@ module.exports = {
161
172
  opened.subprocess.send({ msgtype: 'dropDatabase', name });
162
173
  return { status: 'ok' };
163
174
  },
175
+
176
+ sendRequest(conn, message) {
177
+ const msgid = uuidv1();
178
+ const promise = new Promise((resolve, reject) => {
179
+ this.requests[msgid] = [resolve, reject];
180
+ conn.subprocess.send({ msgid, ...message });
181
+ });
182
+ return promise;
183
+ },
184
+
185
+ async loadDataCore(msgtype, { conid, ...args }, req) {
186
+ testConnectionPermission(conid, req);
187
+ const opened = await this.ensureOpened(conid);
188
+ const res = await this.sendRequest(opened, { msgtype, ...args });
189
+ if (res.errorMessage) {
190
+ console.error(res.errorMessage);
191
+
192
+ return {
193
+ errorMessage: res.errorMessage,
194
+ };
195
+ }
196
+ return res.result || null;
197
+ },
198
+
199
+ serverSummary_meta: true,
200
+ async serverSummary({ conid }, req) {
201
+ testConnectionPermission(conid, req);
202
+ return this.loadDataCore('serverSummary', { conid });
203
+ },
204
+
205
+ summaryCommand_meta: true,
206
+ async summaryCommand({ conid, command, row }, req) {
207
+ testConnectionPermission(conid, req);
208
+ const opened = await this.ensureOpened(conid);
209
+ if (opened.connection.isReadOnly) return false;
210
+ return this.loadDataCore('summaryCommand', { conid, command, row });
211
+ },
164
212
  };
@@ -103,6 +103,12 @@ module.exports = {
103
103
  if (handleProcessCommunication(message, subprocess)) return;
104
104
  this[`handle_${msgtype}`](sesid, message);
105
105
  });
106
+ subprocess.on('exit', () => {
107
+ this.opened = this.opened.filter(x => x.sesid != sesid);
108
+ this.dispatchMessage(sesid, 'Query session closed');
109
+ socket.emit(`session-closed-${sesid}`);
110
+ });
111
+
106
112
  subprocess.send({ msgtype: 'connect', ...connection, database });
107
113
  return _.pick(newOpened, ['conid', 'database', 'sesid']);
108
114
  },
@@ -144,6 +150,31 @@ module.exports = {
144
150
  return true;
145
151
  },
146
152
 
153
+ startProfiler_meta: true,
154
+ async startProfiler({ sesid }) {
155
+ const jslid = uuidv1();
156
+ const session = this.opened.find(x => x.sesid == sesid);
157
+ if (!session) {
158
+ throw new Error('Invalid session');
159
+ }
160
+
161
+ console.log(`Starting profiler, sesid=${sesid}`);
162
+ session.loadingReader_jslid = jslid;
163
+ session.subprocess.send({ msgtype: 'startProfiler', jslid });
164
+
165
+ return { state: 'ok', jslid };
166
+ },
167
+
168
+ stopProfiler_meta: true,
169
+ async stopProfiler({ sesid }) {
170
+ const session = this.opened.find(x => x.sesid == sesid);
171
+ if (!session) {
172
+ throw new Error('Invalid session');
173
+ }
174
+ session.subprocess.send({ msgtype: 'stopProfiler' });
175
+ return { state: 'ok' };
176
+ },
177
+
147
178
  // cancel_meta: true,
148
179
  // async cancel({ sesid }) {
149
180
  // const session = this.opened.find((x) => x.sesid == sesid);
@@ -165,6 +196,17 @@ module.exports = {
165
196
  return { state: 'ok' };
166
197
  },
167
198
 
199
+ ping_meta: true,
200
+ async ping({ sesid }) {
201
+ const session = this.opened.find(x => x.sesid == sesid);
202
+ if (!session) {
203
+ throw new Error('Invalid session');
204
+ }
205
+ session.subprocess.send({ msgtype: 'ping' });
206
+
207
+ return { state: 'ok' };
208
+ },
209
+
168
210
  // runCommand_meta: true,
169
211
  // async runCommand({ conid, database, sql }) {
170
212
  // console.log(`Running SQL command , conid=${conid}, database=${database}, sql=${sql}`);
@@ -1,5 +1,5 @@
1
1
 
2
2
  module.exports = {
3
- version: '5.1.5',
4
- buildTime: '2022-10-15T20:12:15.112Z'
3
+ version: '5.1.7-alpha.13',
4
+ buildTime: '2023-01-02T17:41:24.485Z'
5
5
  };
package/src/main.js CHANGED
@@ -20,6 +20,7 @@ const jsldata = require('./controllers/jsldata');
20
20
  const config = require('./controllers/config');
21
21
  const archive = require('./controllers/archive');
22
22
  const apps = require('./controllers/apps');
23
+ const auth = require('./controllers/auth');
23
24
  const uploads = require('./controllers/uploads');
24
25
  const plugins = require('./controllers/plugins');
25
26
  const files = require('./controllers/files');
@@ -41,7 +42,7 @@ function start() {
41
42
  const server = http.createServer(app);
42
43
 
43
44
  const logins = getLogins();
44
- if (logins) {
45
+ if (logins && process.env.BASIC_AUTH) {
45
46
  app.use(
46
47
  basicAuth({
47
48
  users: _.fromPairs(logins.map(x => [x.login, x.password])),
@@ -53,6 +54,25 @@ function start() {
53
54
 
54
55
  app.use(cors());
55
56
 
57
+ if (platformInfo.isDocker) {
58
+ // server static files inside docker container
59
+ app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
60
+ } else if (platformInfo.isNpmDist) {
61
+ app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
62
+ } else if (process.env.DEVWEB) {
63
+ console.log('__dirname', __dirname);
64
+ console.log(path.join(__dirname, '../../web/public/build'));
65
+ app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
66
+ } else {
67
+ app.get(getExpressPath('/'), (req, res) => {
68
+ res.send('DbGate API');
69
+ });
70
+ }
71
+
72
+ if (auth.shouldAuthorizeApi()) {
73
+ app.use(auth.authMiddleware);
74
+ }
75
+
56
76
  app.get(getExpressPath('/stream'), async function (req, res) {
57
77
  res.set({
58
78
  'Cache-Control': 'no-cache',
@@ -88,14 +108,10 @@ function start() {
88
108
  app.use(getExpressPath('/runners/data'), express.static(rundir()));
89
109
 
90
110
  if (platformInfo.isDocker) {
91
- // server static files inside docker container
92
- app.use(getExpressPath('/'), express.static('/home/dbgate-docker/public'));
93
-
94
111
  const port = process.env.PORT || 3000;
95
112
  console.log('DbGate API listening on port (docker build)', port);
96
113
  server.listen(port);
97
114
  } else if (platformInfo.isNpmDist) {
98
- app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../dbgate-web/public')));
99
115
  getPort({
100
116
  port: parseInt(
101
117
  // @ts-ignore
@@ -107,18 +123,10 @@ function start() {
107
123
  });
108
124
  });
109
125
  } else if (process.env.DEVWEB) {
110
- console.log('__dirname', __dirname);
111
- console.log(path.join(__dirname, '../../web/public/build'));
112
- app.use(getExpressPath('/'), express.static(path.join(__dirname, '../../web/public')));
113
-
114
126
  const port = process.env.PORT || 3000;
115
127
  console.log('DbGate API & web listening on port (dev web build)', port);
116
128
  server.listen(port);
117
129
  } else {
118
- app.get(getExpressPath('/'), (req, res) => {
119
- res.send('DbGate API');
120
- });
121
-
122
130
  const port = process.env.PORT || 3000;
123
131
  console.log('DbGate API listening on port (dev API build)', port);
124
132
  server.listen(port);
@@ -157,6 +165,7 @@ function useAllControllers(app, electron) {
157
165
  useController(app, electron, '/scheduler', scheduler);
158
166
  useController(app, electron, '/query-history', queryHistory);
159
167
  useController(app, electron, '/apps', apps);
168
+ useController(app, electron, '/auth', auth);
160
169
  }
161
170
 
162
171
  function setElectronSender(electronSender) {
@@ -2,6 +2,6 @@
2
2
  // this file is generated automatically by script fillNativeModules.js, do not edit it manually
3
3
  const content = {};
4
4
 
5
- content['better-sqlite3'] = () => require('better-sqlite3');
5
+ content.oracledb = () => require('oracledb');content['better-sqlite3'] = () => require('better-sqlite3');
6
6
 
7
7
  module.exports = content;
@@ -335,11 +335,11 @@ function start() {
335
335
 
336
336
  setInterval(() => {
337
337
  const time = new Date().getTime();
338
- if (time - lastPing > 120 * 1000) {
338
+ if (time - lastPing > 40 * 1000) {
339
339
  console.log('Database connection not alive, exiting');
340
340
  process.exit(0);
341
341
  }
342
- }, 60 * 1000);
342
+ }, 10 * 1000);
343
343
 
344
344
  process.on('message', async message => {
345
345
  if (handleProcessCommunication(message)) return;
@@ -10,6 +10,7 @@ let storedConnection;
10
10
  let lastDatabases = null;
11
11
  let lastStatus = null;
12
12
  let lastPing = null;
13
+ let afterConnectCallbacks = [];
13
14
 
14
15
  async function handleRefresh() {
15
16
  const driver = requireEngineDriver(storedConnection);
@@ -74,6 +75,18 @@ async function handleConnect(connection) {
74
75
  // console.error(err);
75
76
  setTimeout(() => process.exit(1), 1000);
76
77
  }
78
+
79
+ for (const [resolve] of afterConnectCallbacks) {
80
+ resolve();
81
+ }
82
+ afterConnectCallbacks = [];
83
+ }
84
+
85
+ function waitConnected() {
86
+ if (systemConnection) return Promise.resolve();
87
+ return new Promise((resolve, reject) => {
88
+ afterConnectCallbacks.push([resolve, reject]);
89
+ });
77
90
  }
78
91
 
79
92
  function handlePing() {
@@ -94,9 +107,30 @@ async function handleDatabaseOp(op, { name }) {
94
107
  await handleRefresh();
95
108
  }
96
109
 
110
+ async function handleDriverDataCore(msgid, callMethod) {
111
+ await waitConnected();
112
+ const driver = requireEngineDriver(storedConnection);
113
+ try {
114
+ const result = await callMethod(driver);
115
+ process.send({ msgtype: 'response', msgid, result });
116
+ } catch (err) {
117
+ process.send({ msgtype: 'response', msgid, errorMessage: err.message });
118
+ }
119
+ }
120
+
121
+ async function handleServerSummary({ msgid }) {
122
+ return handleDriverDataCore(msgid, driver => driver.serverSummary(systemConnection));
123
+ }
124
+
125
+ async function handleSummaryCommand({ msgid, command, row }) {
126
+ return handleDriverDataCore(msgid, driver => driver.summaryCommand(systemConnection, command, row));
127
+ }
128
+
97
129
  const messageHandlers = {
98
130
  connect: handleConnect,
99
131
  ping: handlePing,
132
+ serverSummary: handleServerSummary,
133
+ summaryCommand: handleSummaryCommand,
100
134
  createDatabase: props => handleDatabaseOp('createDatabase', props),
101
135
  dropDatabase: props => handleDatabaseOp('dropDatabase', props),
102
136
  };
@@ -111,11 +145,11 @@ function start() {
111
145
 
112
146
  setInterval(() => {
113
147
  const time = new Date().getTime();
114
- if (time - lastPing > 120 * 1000) {
148
+ if (time - lastPing > 40 * 1000) {
115
149
  console.log('Server connection not alive, exiting');
116
150
  process.exit(0);
117
151
  }
118
- }, 60 * 1000);
152
+ }, 10 * 1000);
119
153
 
120
154
  process.on('message', async message => {
121
155
  if (handleProcessCommunication(message)) return;
@@ -15,6 +15,8 @@ let systemConnection;
15
15
  let storedConnection;
16
16
  let afterConnectCallbacks = [];
17
17
  // let currentHandlers = [];
18
+ let lastPing = null;
19
+ let currentProfiler = null;
18
20
 
19
21
  class TableWriter {
20
22
  constructor() {
@@ -209,6 +211,31 @@ function waitConnected() {
209
211
  });
210
212
  }
211
213
 
214
+ async function handleStartProfiler({ jslid }) {
215
+ await waitConnected();
216
+ const driver = requireEngineDriver(storedConnection);
217
+
218
+ if (!allowExecuteCustomScript(driver)) {
219
+ process.send({ msgtype: 'done' });
220
+ return;
221
+ }
222
+
223
+ const writer = new TableWriter();
224
+ writer.initializeFromReader(jslid);
225
+
226
+ currentProfiler = await driver.startProfiler(systemConnection, {
227
+ row: data => writer.rowFromReader(data),
228
+ });
229
+ currentProfiler.writer = writer;
230
+ }
231
+
232
+ async function handleStopProfiler({ jslid }) {
233
+ const driver = requireEngineDriver(storedConnection);
234
+ currentProfiler.writer.close();
235
+ driver.stopProfiler(systemConnection, currentProfiler);
236
+ currentProfiler = null;
237
+ }
238
+
212
239
  async function handleExecuteQuery({ sql }) {
213
240
  await waitConnected();
214
241
  const driver = requireEngineDriver(storedConnection);
@@ -271,10 +298,17 @@ async function handleExecuteReader({ jslid, sql, fileName }) {
271
298
  });
272
299
  }
273
300
 
301
+ function handlePing() {
302
+ lastPing = new Date().getTime();
303
+ }
304
+
274
305
  const messageHandlers = {
275
306
  connect: handleConnect,
276
307
  executeQuery: handleExecuteQuery,
277
308
  executeReader: handleExecuteReader,
309
+ startProfiler: handleStartProfiler,
310
+ stopProfiler: handleStopProfiler,
311
+ ping: handlePing,
278
312
  // cancel: handleCancel,
279
313
  };
280
314
 
@@ -285,6 +319,17 @@ async function handleMessage({ msgtype, ...other }) {
285
319
 
286
320
  function start() {
287
321
  childProcessChecker();
322
+
323
+ lastPing = new Date().getTime();
324
+
325
+ setInterval(() => {
326
+ const time = new Date().getTime();
327
+ if (time - lastPing > 25 * 1000) {
328
+ console.log('Session not alive, exiting');
329
+ process.exit(0);
330
+ }
331
+ }, 10 * 1000);
332
+
288
333
  process.on('message', async message => {
289
334
  if (handleProcessCommunication(message)) return;
290
335
  try {
@@ -90,6 +90,12 @@ class JsonLinesDatabase {
90
90
  return obj;
91
91
  }
92
92
 
93
+ async updateAll(mapFunction) {
94
+ await this._ensureLoaded();
95
+ this.data = this.data.map(mapFunction);
96
+ await this._save();
97
+ }
98
+
93
99
  async patch(id, values) {
94
100
  await this._ensureLoaded();
95
101
  this.data = this.data.map(x => (x._id == id ? { ...x, ...values } : x));
@@ -3,6 +3,7 @@ const AsyncLock = require('async-lock');
3
3
  const lock = new AsyncLock();
4
4
  const stableStringify = require('json-stable-stringify');
5
5
  const { evaluateCondition } = require('dbgate-sqltree');
6
+ const requirePluginFunction = require('./requirePluginFunction');
6
7
 
7
8
  function fetchNextLineFromReader(reader) {
8
9
  return new Promise((resolve, reject) => {
@@ -22,14 +23,16 @@ function fetchNextLineFromReader(reader) {
22
23
  }
23
24
 
24
25
  class JsonLinesDatastore {
25
- constructor(file) {
26
+ constructor(file, formatterFunction) {
26
27
  this.file = file;
28
+ this.formatterFunction = formatterFunction;
27
29
  this.reader = null;
28
30
  this.readedDataRowCount = 0;
29
31
  this.readedSchemaRow = false;
30
32
  // this.firstRowToBeReturned = null;
31
33
  this.notifyChangedCallback = null;
32
34
  this.currentFilter = null;
35
+ this.rowFormatter = requirePluginFunction(formatterFunction);
33
36
  }
34
37
 
35
38
  _closeReader() {
@@ -62,6 +65,11 @@ class JsonLinesDatastore {
62
65
  );
63
66
  }
64
67
 
68
+ parseLine(line) {
69
+ const res = JSON.parse(line);
70
+ return this.rowFormatter ? this.rowFormatter(res) : res;
71
+ }
72
+
65
73
  async _readLine(parse) {
66
74
  // if (this.firstRowToBeReturned) {
67
75
  // const res = this.firstRowToBeReturned;
@@ -84,14 +92,14 @@ class JsonLinesDatastore {
84
92
  }
85
93
  }
86
94
  if (this.currentFilter) {
87
- const parsedLine = JSON.parse(line);
95
+ const parsedLine = this.parseLine(line);
88
96
  if (evaluateCondition(this.currentFilter, parsedLine)) {
89
97
  this.readedDataRowCount += 1;
90
98
  return parse ? parsedLine : true;
91
99
  }
92
100
  } else {
93
101
  this.readedDataRowCount += 1;
94
- return parse ? JSON.parse(line) : true;
102
+ return parse ? this.parseLine(line) : true;
95
103
  }
96
104
  }
97
105
 
@@ -0,0 +1,9 @@
1
+ class MissingCredentialsError {
2
+ constructor(detail) {
3
+ this.detail = detail;
4
+ }
5
+ }
6
+
7
+ module.exports = {
8
+ MissingCredentialsError,
9
+ };
@@ -0,0 +1,16 @@
1
+ const _ = require('lodash');
2
+ const requirePlugin = require('../shell/requirePlugin');
3
+
4
+ function requirePluginFunction(functionName) {
5
+ if (!functionName) return null;
6
+ if (functionName.includes('@')) {
7
+ const [shortName, packageName] = functionName.split('@');
8
+ const plugin = requirePlugin(packageName);
9
+ if (plugin.functions) {
10
+ return plugin.functions[shortName];
11
+ }
12
+ }
13
+ return null;
14
+ }
15
+
16
+ module.exports = requirePluginFunction;
@@ -1,4 +1,5 @@
1
1
  const _ = require('lodash');
2
+ const stableStringify = require('json-stable-stringify');
2
3
 
3
4
  const sseResponses = [];
4
5
  let electronSender = null;
@@ -27,12 +28,12 @@ module.exports = {
27
28
  electronSender.send(message, data == null ? null : data);
28
29
  }
29
30
  for (const res of sseResponses) {
30
- res.write(`event: ${message}\ndata: ${JSON.stringify(data == null ? null : data)}\n\n`);
31
+ res.write(`event: ${message}\ndata: ${stableStringify(data == null ? null : data)}\n\n`);
31
32
  }
32
33
  },
33
- emitChanged(key) {
34
+ emitChanged(key, params = undefined) {
34
35
  // console.log('EMIT CHANGED', key);
35
- this.emit('changed-cache', key);
36
+ this.emit('changed-cache', { key, ...params });
36
37
  // this.emit(key);
37
38
  },
38
39
  };