@sera4/essentia 1.1.5 → 1.1.11
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/index.js +2 -13
- package/logger/s4-logger.js +36 -8
- package/package.json +4 -2
- package/package.tar.gz +0 -0
- package/queue/index.js +67 -3
- package/queue/publisher.js +5 -2
- package/safe_proxy/index.js +61 -0
- package/utils/index.js +1 -3
package/index.js
CHANGED
|
@@ -8,16 +8,5 @@ export { default as halDecorator } from "./hal/index.js";
|
|
|
8
8
|
export { default as formatter } from "./formatter/index.js";
|
|
9
9
|
export { HealthCheck as healthCheck } from "./health/index.js";
|
|
10
10
|
export { default as queue } from "./queue/index.js";
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
//export default {
|
|
14
|
-
// paginator : require("./paginator/s4-pagination"),
|
|
15
|
-
// sqlPaginator : require("./paginator/sql-pagination"),
|
|
16
|
-
// logger : require("./logger/s4-logger"),
|
|
17
|
-
// lastCommit : require("./last_commit"),
|
|
18
|
-
// halDecorator : require("./hal"),
|
|
19
|
-
// formatter : require("./formatter"),
|
|
20
|
-
// healthCheck : require("./health"),
|
|
21
|
-
// queue : require("./queue"),
|
|
22
|
-
// utils : require("./utils")
|
|
23
|
-
//}
|
|
11
|
+
export * from "./utils/index.js";
|
|
12
|
+
export * from "./safe_proxy/index.js";
|
package/logger/s4-logger.js
CHANGED
|
@@ -4,6 +4,30 @@ import jsonStringify from "fast-safe-stringify";
|
|
|
4
4
|
const colorizer = winston.format.colorize();
|
|
5
5
|
import "winston-logstash";
|
|
6
6
|
|
|
7
|
+
// IETF RFC5424:
|
|
8
|
+
// Severity of all levels is assumed to be numerically ascending from most important to least important.
|
|
9
|
+
const levels = {
|
|
10
|
+
error: 0,
|
|
11
|
+
warn: 1,
|
|
12
|
+
info: 2,
|
|
13
|
+
debug: 3,
|
|
14
|
+
trace: 4,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// These colors are not used, however if colors are not defined for custom levels
|
|
18
|
+
// then winston will throw an exception.
|
|
19
|
+
winston.addColors({
|
|
20
|
+
trace: 'white'
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const defaultLevel = process.env.NODE_ENV === 'production' ? 'info' : 'debug';
|
|
24
|
+
|
|
25
|
+
// These colors are not used, however if colors are not defined for custom levels
|
|
26
|
+
// then winston will throw an exception.
|
|
27
|
+
winston.addColors({
|
|
28
|
+
trace: 'white'
|
|
29
|
+
});
|
|
30
|
+
|
|
7
31
|
const devFormat = {
|
|
8
32
|
transform(info) {
|
|
9
33
|
const { message } = info;
|
|
@@ -16,11 +40,13 @@ const devFormat = {
|
|
|
16
40
|
};
|
|
17
41
|
|
|
18
42
|
const defaultTransport = new winston.transports.Console({
|
|
19
|
-
format: devFormat
|
|
43
|
+
format: devFormat,
|
|
44
|
+
level: 'trace'
|
|
20
45
|
});
|
|
21
46
|
|
|
22
47
|
const logger = winston.createLogger({
|
|
23
|
-
|
|
48
|
+
levels: levels,
|
|
49
|
+
level: defaultLevel,
|
|
24
50
|
defaultMeta: { },
|
|
25
51
|
transports: [defaultTransport]
|
|
26
52
|
});
|
|
@@ -31,15 +57,13 @@ class S4Logger {
|
|
|
31
57
|
return;
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
|
|
35
|
-
logger.level = options.level;
|
|
36
|
-
}
|
|
60
|
+
this.setLevel(options.level);
|
|
37
61
|
|
|
38
62
|
logger.defaultMeta.service = options.service;
|
|
39
63
|
switch(options.format.toLowerCase()) {
|
|
40
64
|
case "logstash":
|
|
41
|
-
|
|
42
|
-
|
|
65
|
+
defaultTransport.format = winston.format.logstash();
|
|
66
|
+
break;
|
|
43
67
|
case "json":
|
|
44
68
|
defaultTransport.format = winston.format.json();
|
|
45
69
|
break;
|
|
@@ -50,7 +74,7 @@ class S4Logger {
|
|
|
50
74
|
}
|
|
51
75
|
|
|
52
76
|
setLevel(level) {
|
|
53
|
-
if (level && ["debug", "info", "error", "warn"].indexOf(level.toLowerCase())) {
|
|
77
|
+
if (level && ["trace", "debug", "info", "error", "warn"].indexOf(level.toLowerCase())) {
|
|
54
78
|
logger.level = level;
|
|
55
79
|
}
|
|
56
80
|
}
|
|
@@ -67,6 +91,10 @@ class S4Logger {
|
|
|
67
91
|
});
|
|
68
92
|
}
|
|
69
93
|
|
|
94
|
+
trace(...args) {
|
|
95
|
+
logger.trace(util.format(...args));
|
|
96
|
+
}
|
|
97
|
+
|
|
70
98
|
debug(...args) {
|
|
71
99
|
logger.debug(util.format(...args));
|
|
72
100
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sera4/essentia",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "A library of utilities for Teleporte Web Services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,10 +28,12 @@
|
|
|
28
28
|
"sinon": "^5.1.1"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"amqplib": "^0.
|
|
31
|
+
"amqplib": "^0.8.0",
|
|
32
32
|
"async": "^2.6.3",
|
|
33
|
+
"axios": "^0.21.1",
|
|
33
34
|
"fast-safe-stringify": "^2.0.7",
|
|
34
35
|
"git-rev": "^0.2.1",
|
|
36
|
+
"lodash": "^4.17.21",
|
|
35
37
|
"url-parse": "^1.5.1",
|
|
36
38
|
"uuid": "^3.4.0",
|
|
37
39
|
"winston": "^3.3.3",
|
package/package.tar.gz
CHANGED
|
Binary file
|
package/queue/index.js
CHANGED
|
@@ -29,6 +29,7 @@ class S4Queue {
|
|
|
29
29
|
*/
|
|
30
30
|
async testConnection() {
|
|
31
31
|
if (!this.isConnected()) {
|
|
32
|
+
this.connectionTestChannel = null;
|
|
32
33
|
throw("queue service not connected")
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -72,12 +73,16 @@ class S4Queue {
|
|
|
72
73
|
amqp.connect(options.connectionUrl, socketOptions, (err, conn) => {
|
|
73
74
|
|
|
74
75
|
if (err) {
|
|
75
|
-
|
|
76
|
+
logger.error("Error while connecting", err)
|
|
77
|
+
logger.error("Attempting again in", this.retry, "seconds");
|
|
78
|
+
return setTimeout(() => this.openConnection(options), this.retry * 1000);
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
this.connection = conn;
|
|
79
82
|
if (!this.connection) {
|
|
80
83
|
return reject(new Error("Failed to obtain connection"));
|
|
84
|
+
} else {
|
|
85
|
+
this.updatePubsAndSubsConnectionRef(conn);
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
this.connection.on("error", (err) => {
|
|
@@ -91,8 +96,8 @@ class S4Queue {
|
|
|
91
96
|
logger.error("connection closed");
|
|
92
97
|
this.connection = null;
|
|
93
98
|
if (err) {
|
|
94
|
-
logger.
|
|
95
|
-
logger.
|
|
99
|
+
logger.error("connection closed due to error:", err);
|
|
100
|
+
logger.error("Reconnecting in", this.retry, "seconds");
|
|
96
101
|
setTimeout(() => this.openConnection(options), this.retry * 1000);
|
|
97
102
|
} else {
|
|
98
103
|
// If there was no error, it is us who closed
|
|
@@ -171,6 +176,22 @@ class S4Queue {
|
|
|
171
176
|
this.pubs[name] = p;
|
|
172
177
|
}
|
|
173
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Registers a new fanout publisher on the given exchange.
|
|
181
|
+
* If exchange doesn't exist, one will be created.
|
|
182
|
+
* @param {String} exchange the name of the exchange on AMQP service.
|
|
183
|
+
*/
|
|
184
|
+
async registerFanoutPublisher(exchange) {
|
|
185
|
+
const name = exchange;
|
|
186
|
+
if (Object.keys(this.pubs).includes(name)) {
|
|
187
|
+
throw new Error(`Publisher ${name} already registered`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const p = new Publisher(exchange, 'fanout', true, name);
|
|
191
|
+
await p.init(this.connection);
|
|
192
|
+
this.pubs[name] = p;
|
|
193
|
+
}
|
|
194
|
+
|
|
174
195
|
/**
|
|
175
196
|
* Registers a new direct subscriber on a given exchange and queue.
|
|
176
197
|
* If exchange and queue don't exists, they'll be created
|
|
@@ -273,6 +294,26 @@ class S4Queue {
|
|
|
273
294
|
return await p.publish({key: topic, content});
|
|
274
295
|
}
|
|
275
296
|
|
|
297
|
+
/**
|
|
298
|
+
* Binds an exchange to a queue
|
|
299
|
+
* @param {String} exchange the name of the exchange.
|
|
300
|
+
* @param {String} queue the name of the queue.
|
|
301
|
+
* @param {pattern} (optional) pattern for routing key
|
|
302
|
+
*/
|
|
303
|
+
async bindQueueToExchange(exchange, queue, pattern) {
|
|
304
|
+
const p = this.pubs[exchange];
|
|
305
|
+
|
|
306
|
+
if (!p) {
|
|
307
|
+
throw(new Error(`No PUB for ${exchange} found`));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!queue) {
|
|
311
|
+
throw(new Error(`Binding a queue requires a queue name`));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return await p.bindQueue(queue, pattern)
|
|
315
|
+
}
|
|
316
|
+
|
|
276
317
|
/**
|
|
277
318
|
* Adds a listener for a specific key.
|
|
278
319
|
* If a subscriber that was regitered was found
|
|
@@ -289,6 +330,29 @@ class S4Queue {
|
|
|
289
330
|
}
|
|
290
331
|
});
|
|
291
332
|
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Updates the connection reference to all the previously
|
|
336
|
+
* registered publishers and subscribers.
|
|
337
|
+
* After we successfully establish a connection and register pubs/subs,
|
|
338
|
+
* it's possible for the connection to break. This object will try
|
|
339
|
+
* to re-establish a new connection every 3 seconds (or whatever configuration is set)
|
|
340
|
+
* and if a new connection is established, we need to pass that connection to
|
|
341
|
+
* all our pubs/subs, otherwise they'll be operating with old/stale references
|
|
342
|
+
* @param {Object} connRef a new connection reference to the AMQP service
|
|
343
|
+
*/
|
|
344
|
+
updatePubsAndSubsConnectionRef(connRef) {
|
|
345
|
+
Object.keys(this.pubs).forEach(k => {
|
|
346
|
+
logger.debug(`Updating publisher ${k} with a new connection object`);
|
|
347
|
+
const p = this.pubs[k];
|
|
348
|
+
p.init(connRef);
|
|
349
|
+
});
|
|
350
|
+
Object.keys(this.subs).forEach(k => {
|
|
351
|
+
logger.debug(`Updating subscriber ${k} with a new connection object`);
|
|
352
|
+
const s = this.subs[k];
|
|
353
|
+
s.init(connRef);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
292
356
|
}
|
|
293
357
|
|
|
294
358
|
const queue = new S4Queue();
|
package/queue/publisher.js
CHANGED
|
@@ -5,9 +5,7 @@ import uuidv4 from "uuid/v4.js";
|
|
|
5
5
|
* A publisher for AMQP service
|
|
6
6
|
*/
|
|
7
7
|
export class Publisher {
|
|
8
|
-
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
9
|
* @param {String} exchange AMQP exchange name.
|
|
12
10
|
* @param {String} type direct|fanout| etc (supported types by RabbitMQ).
|
|
13
11
|
* @param {boolean} durable true if the messages should persist service restarts.
|
|
@@ -65,6 +63,7 @@ export class Publisher {
|
|
|
65
63
|
|
|
66
64
|
const parcel = {
|
|
67
65
|
tracking_id: uuidv4(),
|
|
66
|
+
timestamp: new Date(),
|
|
68
67
|
data: msg.content
|
|
69
68
|
}
|
|
70
69
|
logger.debug("Publishing message");
|
|
@@ -80,4 +79,8 @@ export class Publisher {
|
|
|
80
79
|
|
|
81
80
|
return parcel.tracking_id;
|
|
82
81
|
}
|
|
82
|
+
|
|
83
|
+
async bindQueue(queue, pattern, args) {
|
|
84
|
+
await this.channel.bindQueue(queue, this.exchange, pattern, args)
|
|
85
|
+
}
|
|
83
86
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
import lodash from 'lodash';
|
|
5
|
+
const { omitBy, isNil } = lodash;
|
|
6
|
+
|
|
7
|
+
/* A class which safely wraps an axios connection in order to pass the current user through to
|
|
8
|
+
* the next service. Useful when one of our microservices requires auth as either an admin (sera4tal)
|
|
9
|
+
* or as the end user (example: DELETE /auth/sessions/:id
|
|
10
|
+
*
|
|
11
|
+
* Similar to proxy however this isn't a proxy, it's a full out new call that we can manage
|
|
12
|
+
* each method requires a res.locals from a pre-authd session
|
|
13
|
+
*/
|
|
14
|
+
class SafeProxy {
|
|
15
|
+
constructor() {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// a safe post request
|
|
19
|
+
// locals = res.locals - your authentication details
|
|
20
|
+
post(locals, url, options = { headers: {}, data: {}}) {
|
|
21
|
+
let { account, tenant, membership } = locals;
|
|
22
|
+
return this._proxyRequest({ account, tenant, membership }, url, "post", options);
|
|
23
|
+
}
|
|
24
|
+
delete(locals, url, options = { headers: {}, data: {}}) {
|
|
25
|
+
let { account, tenant, membership } = locals;
|
|
26
|
+
return this._proxyRequest({ account, tenant, membership }, url, "delete", options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* private methods */
|
|
30
|
+
// supports get, delete, post
|
|
31
|
+
// options includes { headers, data } for post / delete payloads
|
|
32
|
+
async _proxyRequest(locals, url, method = "get", options) {
|
|
33
|
+
let request;
|
|
34
|
+
let membership = locals.membership ? JSON.stringify(locals.membership) : null;
|
|
35
|
+
let headers = omitBy({ ...options.headers,
|
|
36
|
+
"tws-account-id": locals.account?.id,
|
|
37
|
+
"tws-tenant-id": locals.tenant?.id,
|
|
38
|
+
"tws-membership-id": locals.membership?.id,
|
|
39
|
+
"tws-membership": membership
|
|
40
|
+
}, isNil);
|
|
41
|
+
|
|
42
|
+
switch(method) {
|
|
43
|
+
case "post":
|
|
44
|
+
request = axios.post(url, { ...options.data }, { headers })
|
|
45
|
+
break;
|
|
46
|
+
case "delete":
|
|
47
|
+
request = axios.delete(url, { ...options.data, headers } )
|
|
48
|
+
break;
|
|
49
|
+
case "get":
|
|
50
|
+
request = axios.get(url, { headers, data } )
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
throw new Error("uknown type called in proxyRequest");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return request;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const safeProxy = new SafeProxy();
|