@sera4/essentia 1.0.42 → 1.1.4
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/formatter/index.js +2 -1
- package/hal/README.md +2 -2
- package/hal/index.js +3 -1
- package/health/index.js +25 -67
- package/index.js +21 -13
- package/last-commit.js +2 -1
- package/last_commit/index.js +11 -7
- package/logger/s4-logger.js +11 -31
- package/package.json +15 -16
- package/package.tar.gz +0 -0
- package/paginator/README.md +2 -2
- package/paginator/s4-pagination.js +2 -1
- package/paginator/sql-pagination.js +2 -1
- package/queue/index.js +10 -75
- package/queue/publisher.js +3 -11
- package/queue/queue-logger.js +5 -3
- package/queue/subscriber.js +2 -6
- package/utils/index.js +6 -35
- package/cache/index.js +0 -188
- package/constants/index.js +0 -13
- package/health/health-logger.js +0 -38
- package/safe_proxy/index.js +0 -59
package/formatter/index.js
CHANGED
package/hal/README.md
CHANGED
|
@@ -7,7 +7,7 @@ A module to add HAL decoration to JSON responses in Sequelize ORM based projects
|
|
|
7
7
|
In your model definition, set decoration on your model
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
|
|
10
|
+
import { halDecorator as decorator } from "@sera4/essentia"
|
|
11
11
|
...
|
|
12
12
|
|
|
13
13
|
module.exports = (sequelize, DataTypes) => {
|
|
@@ -51,4 +51,4 @@ The above configuration (which is optional) should produce the following JSON fo
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
|
-
The links use a similar pattern for parameters interpolation like the express route. For example `/posts/:id/by_user/:user_id` becomes `/posts/20/by_user/123`. If no matching attribute is found on the record, the variable is not replaced.
|
|
54
|
+
The links use a similar pattern for parameters interpolation like the express route. For example `/posts/:id/by_user/:user_id` becomes `/posts/20/by_user/123`. If no matching attribute is found on the record, the variable is not replaced.
|
package/hal/index.js
CHANGED
package/health/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
const logger = require("./health-logger");
|
|
3
|
+
import pathHelper from "path";
|
|
5
4
|
|
|
6
5
|
class HealthCheckError extends Error {
|
|
7
6
|
constructor(m) {
|
|
@@ -12,20 +11,16 @@ class HealthCheckError extends Error {
|
|
|
12
11
|
/* Health Checking router + controller for use in all of our modules
|
|
13
12
|
* Example use: Place the following code in your routes file
|
|
14
13
|
*
|
|
15
|
-
*
|
|
14
|
+
* import { healthCheck } from "@sera4/essentia";
|
|
16
15
|
* const router = require('express').Router();
|
|
17
16
|
*
|
|
18
17
|
* ....
|
|
19
|
-
*
|
|
20
|
-
* const checkRedis = async () => { await cache.ping(); }
|
|
21
|
-
* hc.setupCheck({ name: "redis", checkResult: false, critical: true, healthFunction: checkRedis });
|
|
22
18
|
* app.use(hc.defaultPath(serverConfigs.api_prefix), hc.routes(router));
|
|
23
19
|
*
|
|
24
20
|
*/
|
|
25
|
-
class HealthCheck {
|
|
26
|
-
constructor(appendData = {}, enableLogging = true) {
|
|
27
|
-
logger.setEnabled(enableLogging);
|
|
28
21
|
|
|
22
|
+
class HealthCheck {
|
|
23
|
+
constructor(appendData = {}) {
|
|
29
24
|
this.defaultResponse = {
|
|
30
25
|
...
|
|
31
26
|
appendData,
|
|
@@ -34,13 +29,11 @@ class HealthCheck {
|
|
|
34
29
|
};
|
|
35
30
|
|
|
36
31
|
this.defaultPathRoute = `health_check\(.*\)?`
|
|
37
|
-
this.suppressLogs = false;
|
|
38
32
|
this.lastCallId = 0; // default start point/integer
|
|
39
33
|
this.healthChecks = [];
|
|
40
34
|
}
|
|
41
35
|
|
|
42
|
-
/*
|
|
43
|
-
* call this with an async function, must support wait capability
|
|
36
|
+
/* call this with an async function, must support wait capability
|
|
44
37
|
*
|
|
45
38
|
* const dbCheck = async () => {
|
|
46
39
|
* return { name: "db", "result": "success", critical: false };
|
|
@@ -67,40 +60,15 @@ class HealthCheck {
|
|
|
67
60
|
return router
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
// example:
|
|
72
|
-
// const checkRedisFcn = async () => { await cache.ping().then(() => true) }
|
|
73
|
-
// setupCheck({ name: "redis", checkResult: false, critical: false, healthFunction: checkRedisFcn });
|
|
74
|
-
setupCheck(config) {
|
|
75
|
-
this.healthChecks.push(config);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/* performs health checks and returns 200 if true and 500 if not */
|
|
63
|
+
/* performs health checks and returns 200 if true and 500 if not */
|
|
79
64
|
async checkHealth(req, res) {
|
|
80
65
|
const checkFormat = pathHelper.extname(req.baseUrl);
|
|
81
66
|
|
|
82
|
-
const result = await Promise.all(this.healthChecks.map(async hc => {
|
|
83
|
-
if (typeof(hc) === "function") {
|
|
84
|
-
try {
|
|
85
|
-
return await hc();
|
|
86
|
-
return { "result": "success", critical: false };
|
|
87
|
-
} catch(e) {
|
|
88
|
-
return { name: "unknown", result: 'fail', status: e.status }
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
let res = { name: hc.name, critical: hc.critical }
|
|
92
|
-
try {
|
|
93
|
-
await hc.healthFunction();
|
|
94
|
-
return { ...res, "result": "success" };
|
|
95
|
-
} catch(e) {
|
|
96
|
-
const appendStatus = e.status ? ` (${e.status})` : ""
|
|
97
|
-
logger.warn(`${hc.name} service failed${appendStatus}` )
|
|
98
|
-
return { ...res, "result": "fail", status: e.status }
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}));
|
|
102
|
-
|
|
103
67
|
try {
|
|
68
|
+
const result = await Promise.all(this.healthChecks.map(async healthCallback => {
|
|
69
|
+
return await healthCallback();
|
|
70
|
+
}));
|
|
71
|
+
|
|
104
72
|
let callId = ++this.lastCallId;
|
|
105
73
|
|
|
106
74
|
// start the counter over
|
|
@@ -110,18 +78,10 @@ class HealthCheck {
|
|
|
110
78
|
|
|
111
79
|
let status = this.defaultResponse.status;
|
|
112
80
|
|
|
113
|
-
let nonCriticalFail = false;
|
|
114
|
-
let criticalFail = false;
|
|
115
|
-
|
|
116
81
|
result.some(e => {
|
|
117
82
|
if (e.result.match(/fail/)) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
} else {
|
|
121
|
-
criticalFail = true;
|
|
122
|
-
status = 500;
|
|
123
|
-
return true; // break
|
|
124
|
-
}
|
|
83
|
+
status = 500;
|
|
84
|
+
return true; // break
|
|
125
85
|
}
|
|
126
86
|
})
|
|
127
87
|
|
|
@@ -129,39 +89,37 @@ class HealthCheck {
|
|
|
129
89
|
res.setHeader('check', callId)
|
|
130
90
|
|
|
131
91
|
if (checkFormat === ".json") {
|
|
132
|
-
const payload = { "check": callId
|
|
92
|
+
const payload = { "check": callId }
|
|
133
93
|
|
|
134
|
-
if (
|
|
94
|
+
if (status === 200) {
|
|
135
95
|
res.status(status).json({
|
|
136
96
|
... payload,
|
|
137
|
-
"healthy":
|
|
138
|
-
"message":
|
|
139
|
-
"services": result
|
|
97
|
+
"healthy":true,
|
|
98
|
+
"message":"success"
|
|
140
99
|
});
|
|
141
|
-
} else
|
|
100
|
+
} else {
|
|
142
101
|
res.status(status).json({
|
|
143
102
|
... payload,
|
|
103
|
+
"healthy": false,
|
|
144
104
|
"message": "fail",
|
|
145
|
-
"services": result
|
|
105
|
+
"services": [result]
|
|
146
106
|
});
|
|
147
|
-
} else if (status === 200) {
|
|
148
|
-
res.status(status).json(payload);
|
|
149
107
|
}
|
|
150
108
|
} else {
|
|
151
109
|
let status_message;
|
|
152
|
-
if (
|
|
153
|
-
status_message = "fail"
|
|
154
|
-
} else if ((status === 200) || nonCriticalFail) {
|
|
110
|
+
if (status === 200) {
|
|
155
111
|
status_message = "success"
|
|
156
|
-
}
|
|
112
|
+
} else {
|
|
113
|
+
status_message = "fail"
|
|
114
|
+
}
|
|
157
115
|
|
|
158
116
|
res.status(status).send(status_message);
|
|
159
117
|
}
|
|
160
118
|
} catch(error) {
|
|
161
|
-
console.debug("error: ", error)
|
|
162
119
|
res.status(500).send();
|
|
163
120
|
}
|
|
164
121
|
}
|
|
165
122
|
}
|
|
166
123
|
|
|
167
|
-
|
|
124
|
+
|
|
125
|
+
export { HealthCheck };
|
package/index.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
export { default as paginator } from "./paginator/s4-pagination.js";
|
|
4
|
+
export { default as sqlPaginator } from "./paginator/sql-pagination.js";
|
|
5
|
+
export { default as logger } from "./logger/s4-logger.js";
|
|
6
|
+
export { default as lastCommit } from "./last_commit/index.js";
|
|
7
|
+
export { default as halDecorator } from "./hal/index.js";
|
|
8
|
+
export { default as formatter } from "./formatter/index.js";
|
|
9
|
+
export { HealthCheck as healthCheck } from "./health/index.js";
|
|
10
|
+
export { default as queue } from "./queue/index.js";
|
|
11
|
+
export { default as utils } from "./utils/index.js";
|
|
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
|
+
//}
|
package/last-commit.js
CHANGED
package/last_commit/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import git from "git-rev";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import async from "async";
|
|
6
|
+
import { dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const appDir = dirname(fileURLToPath(import.meta.url));
|
|
8
10
|
const commitFile = `${appDir}/last-commit.json`;
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
const lastCommit = {
|
|
11
13
|
parseLastCommit() {
|
|
12
14
|
return new Promise((resolve, reject) => {
|
|
13
15
|
async.waterfall([
|
|
@@ -75,4 +77,6 @@ module.exports = {
|
|
|
75
77
|
})
|
|
76
78
|
});
|
|
77
79
|
}
|
|
78
|
-
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default lastCommit;
|
package/logger/s4-logger.js
CHANGED
|
@@ -1,25 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import winston from "winston";
|
|
2
|
+
import util from "util";
|
|
3
|
+
import jsonStringify from "fast-safe-stringify";
|
|
4
4
|
const colorizer = winston.format.colorize();
|
|
5
|
-
|
|
6
|
-
require('winston-logstash');
|
|
7
|
-
|
|
8
|
-
// IETF RFC5424:
|
|
9
|
-
// Severity of all levels is assumed to be numerically ascending from most important to least important.
|
|
10
|
-
const levels = {
|
|
11
|
-
error: 0,
|
|
12
|
-
warn: 1,
|
|
13
|
-
info: 2,
|
|
14
|
-
debug: 3,
|
|
15
|
-
trace: 4,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// These colors are not used, however if colors are not defined for custom levels
|
|
19
|
-
// then winston will throw an exception.
|
|
20
|
-
winston.addColors({
|
|
21
|
-
trace: 'white'
|
|
22
|
-
})
|
|
5
|
+
import "winston-logstash";
|
|
23
6
|
|
|
24
7
|
const devFormat = {
|
|
25
8
|
transform(info) {
|
|
@@ -33,12 +16,10 @@ const devFormat = {
|
|
|
33
16
|
};
|
|
34
17
|
|
|
35
18
|
const defaultTransport = new winston.transports.Console({
|
|
36
|
-
format: devFormat
|
|
37
|
-
level: 'trace'
|
|
19
|
+
format: devFormat
|
|
38
20
|
});
|
|
39
21
|
|
|
40
22
|
const logger = winston.createLogger({
|
|
41
|
-
levels: levels,
|
|
42
23
|
level: 'debug',
|
|
43
24
|
defaultMeta: { },
|
|
44
25
|
transports: [defaultTransport]
|
|
@@ -50,7 +31,9 @@ class S4Logger {
|
|
|
50
31
|
return;
|
|
51
32
|
}
|
|
52
33
|
|
|
53
|
-
|
|
34
|
+
if (options.level && ["debug", "info", "error", "warn"].indexOf(options.level.toLowerCase())) {
|
|
35
|
+
logger.level = options.level;
|
|
36
|
+
}
|
|
54
37
|
|
|
55
38
|
logger.defaultMeta.service = options.service;
|
|
56
39
|
switch(options.format.toLowerCase()) {
|
|
@@ -67,7 +50,7 @@ class S4Logger {
|
|
|
67
50
|
}
|
|
68
51
|
|
|
69
52
|
setLevel(level) {
|
|
70
|
-
if (level && ["
|
|
53
|
+
if (level && ["debug", "info", "error", "warn"].indexOf(level.toLowerCase())) {
|
|
71
54
|
logger.level = level;
|
|
72
55
|
}
|
|
73
56
|
}
|
|
@@ -84,10 +67,6 @@ class S4Logger {
|
|
|
84
67
|
});
|
|
85
68
|
}
|
|
86
69
|
|
|
87
|
-
trace(...args) {
|
|
88
|
-
logger.trace(util.format(...args));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
70
|
debug(...args) {
|
|
92
71
|
logger.debug(util.format(...args));
|
|
93
72
|
}
|
|
@@ -117,4 +96,5 @@ class S4Logger {
|
|
|
117
96
|
}
|
|
118
97
|
}
|
|
119
98
|
|
|
120
|
-
|
|
99
|
+
const s4logger = new S4Logger();
|
|
100
|
+
export default s4logger;
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sera4/essentia",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "A library of utilities for Teleporte Web Services",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"test": "NODE_ENV=test ./node_modules/.bin/mocha --forbid-only --reporter mocha-junit-reporter --reporter-options mochaFile=./test-results/result.xml --exit",
|
|
8
9
|
"test:dev": "NODE_ENV=test ./node_modules/.bin/mocha --watch --forbid-only",
|
|
@@ -17,25 +18,23 @@
|
|
|
17
18
|
},
|
|
18
19
|
"license": "UNLICENSED",
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"chai": "^4.
|
|
21
|
-
"express": "^4.
|
|
22
|
-
"mocha": "^
|
|
23
|
-
"mocha-junit-reporter": "^1.
|
|
24
|
-
"node-mocks-http": "^1.
|
|
25
|
-
"nodemon": "^
|
|
26
|
-
"should": "^13.2.
|
|
27
|
-
"sinon": "^5.
|
|
21
|
+
"chai": "^4.3.4",
|
|
22
|
+
"express": "^4.17.1",
|
|
23
|
+
"mocha": "^8.3.2",
|
|
24
|
+
"mocha-junit-reporter": "^1.23.3",
|
|
25
|
+
"node-mocks-http": "^1.10.1",
|
|
26
|
+
"nodemon": "^2.0.7",
|
|
27
|
+
"should": "^13.2.3",
|
|
28
|
+
"sinon": "^5.1.1"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"amqplib": "^0.
|
|
31
|
-
"async": "^2.6.
|
|
32
|
-
"
|
|
33
|
-
"fast-safe-stringify": "^2.0.6",
|
|
31
|
+
"amqplib": "^0.5.6",
|
|
32
|
+
"async": "^2.6.3",
|
|
33
|
+
"fast-safe-stringify": "^2.0.7",
|
|
34
34
|
"git-rev": "^0.2.1",
|
|
35
|
-
"lodash": "^4.17.21",
|
|
36
35
|
"url-parse": "^1.5.1",
|
|
37
|
-
"uuid": "^3.
|
|
38
|
-
"winston": "^3.
|
|
36
|
+
"uuid": "^3.4.0",
|
|
37
|
+
"winston": "^3.3.3",
|
|
39
38
|
"winston-logstash": "^0.4.0"
|
|
40
39
|
}
|
|
41
40
|
}
|
package/package.tar.gz
CHANGED
|
Binary file
|
package/paginator/README.md
CHANGED
|
@@ -7,7 +7,7 @@ This is a module to add pagination to Sequelize ORM based projects.
|
|
|
7
7
|
In your model definition, set pagination on your model
|
|
8
8
|
|
|
9
9
|
```
|
|
10
|
-
|
|
10
|
+
import { paginator } from "@sera4/essentia";
|
|
11
11
|
...
|
|
12
12
|
|
|
13
13
|
module.exports = (sequelize, DataTypes) => {
|
|
@@ -155,7 +155,7 @@ You may notice that the links in the pagination only have query parameters. If y
|
|
|
155
155
|
|
|
156
156
|
For example:
|
|
157
157
|
```
|
|
158
|
-
|
|
158
|
+
import { paginator } from "@sera4/essentia"
|
|
159
159
|
...
|
|
160
160
|
|
|
161
161
|
module.exports = (sequelize, DataTypes) => {
|
package/queue/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import amqp from "amqplib/callback_api.js";
|
|
3
|
+
import logger from "./queue-logger.js";
|
|
4
|
+
import urlParse from "url-parse";
|
|
5
|
+
import { Publisher } from "./publisher.js";
|
|
6
|
+
import { Subscriber } from "./subscriber.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* An interface to AMQP service
|
|
@@ -29,7 +29,6 @@ class S4Queue {
|
|
|
29
29
|
*/
|
|
30
30
|
async testConnection() {
|
|
31
31
|
if (!this.isConnected()) {
|
|
32
|
-
this.connectionTestChannel = null;
|
|
33
32
|
throw("queue service not connected")
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -73,16 +72,12 @@ class S4Queue {
|
|
|
73
72
|
amqp.connect(options.connectionUrl, socketOptions, (err, conn) => {
|
|
74
73
|
|
|
75
74
|
if (err) {
|
|
76
|
-
|
|
77
|
-
logger.error("Attempting again in", this.retry, "seconds");
|
|
78
|
-
return setTimeout(() => this.openConnection(options), this.retry * 1000);
|
|
75
|
+
return reject(err);
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
this.connection = conn;
|
|
82
79
|
if (!this.connection) {
|
|
83
80
|
return reject(new Error("Failed to obtain connection"));
|
|
84
|
-
} else {
|
|
85
|
-
this.updatePubsAndSubsConnectionRef(conn);
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
this.connection.on("error", (err) => {
|
|
@@ -96,8 +91,8 @@ class S4Queue {
|
|
|
96
91
|
logger.error("connection closed");
|
|
97
92
|
this.connection = null;
|
|
98
93
|
if (err) {
|
|
99
|
-
logger.
|
|
100
|
-
logger.
|
|
94
|
+
logger.debug("connection closed due to error:", err);
|
|
95
|
+
logger.debug("Reconnecting in", this.retry, "seconds");
|
|
101
96
|
setTimeout(() => this.openConnection(options), this.retry * 1000);
|
|
102
97
|
} else {
|
|
103
98
|
// If there was no error, it is us who closed
|
|
@@ -176,22 +171,6 @@ class S4Queue {
|
|
|
176
171
|
this.pubs[name] = p;
|
|
177
172
|
}
|
|
178
173
|
|
|
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
|
-
|
|
195
174
|
/**
|
|
196
175
|
* Registers a new direct subscriber on a given exchange and queue.
|
|
197
176
|
* If exchange and queue don't exists, they'll be created
|
|
@@ -294,28 +273,6 @@ class S4Queue {
|
|
|
294
273
|
return await p.publish({key: topic, content});
|
|
295
274
|
}
|
|
296
275
|
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
276
|
/**
|
|
320
277
|
* Adds a listener for a specific key.
|
|
321
278
|
* If a subscriber that was regitered was found
|
|
@@ -332,29 +289,7 @@ class S4Queue {
|
|
|
332
289
|
}
|
|
333
290
|
});
|
|
334
291
|
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* Updates the connection reference to all the previously
|
|
338
|
-
* registered publishers and subscribers.
|
|
339
|
-
* After we successfully establish a connection and register pubs/subs,
|
|
340
|
-
* it's possible for the connection to break. This object will try
|
|
341
|
-
* to re-establish a new connection every 3 seconds (or whatever configuration is set)
|
|
342
|
-
* and if a new connection is established, we need to pass that connection to
|
|
343
|
-
* all our pubs/subs, otherwise they'll be operating with old/stale references
|
|
344
|
-
* @param {Object} connRef a new connection reference to the AMQP service
|
|
345
|
-
*/
|
|
346
|
-
updatePubsAndSubsConnectionRef(connRef) {
|
|
347
|
-
Object.keys(this.pubs).forEach(k => {
|
|
348
|
-
logger.debug(`Updating publisher ${k} with a new connection object`);
|
|
349
|
-
const p = this.pubs[k];
|
|
350
|
-
p.init(connRef);
|
|
351
|
-
});
|
|
352
|
-
Object.keys(this.subs).forEach(k => {
|
|
353
|
-
logger.debug(`Updating subscriber ${k} with a new connection object`);
|
|
354
|
-
const s = this.subs[k];
|
|
355
|
-
s.init(connRef);
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
292
|
}
|
|
359
293
|
|
|
360
|
-
|
|
294
|
+
const queue = new S4Queue();
|
|
295
|
+
export default queue;
|
package/queue/publisher.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import logger from "./queue-logger.js";
|
|
2
|
+
import uuidv4 from "uuid/v4.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A publisher for AMQP service
|
|
6
6
|
*/
|
|
7
|
-
class Publisher {
|
|
7
|
+
export class Publisher {
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
*
|
|
@@ -80,12 +80,4 @@ class Publisher {
|
|
|
80
80
|
|
|
81
81
|
return parcel.tracking_id;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
async bindQueue(queue, pattern, args) {
|
|
85
|
-
await this.channel.bindQueue(queue, this.exchange, pattern, args)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
module.exports = {
|
|
90
|
-
Publisher
|
|
91
83
|
}
|
package/queue/queue-logger.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import logger from "../logger/s4-logger.js";
|
|
2
2
|
let enabled = false;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const helpers = {
|
|
5
5
|
debug(...args) {
|
|
6
6
|
if (enabled) {
|
|
7
7
|
logger.debug(":: AMQP ::", args);
|
|
@@ -35,4 +35,6 @@ module.exports = {
|
|
|
35
35
|
setEnabled(e) {
|
|
36
36
|
enabled = e;
|
|
37
37
|
}
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default helpers;
|
package/queue/subscriber.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import logger from "./queue-logger.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A subscriber for AMQP service
|
|
5
5
|
*/
|
|
6
|
-
class Subscriber {
|
|
6
|
+
export class Subscriber {
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Builds a subscriber
|
|
@@ -89,7 +89,3 @@ class Subscriber {
|
|
|
89
89
|
this.callbacks.push(cb);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
module.exports = {
|
|
94
|
-
Subscriber
|
|
95
|
-
}
|
package/utils/index.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
module.exports = {
|
|
3
|
+
const utils = {
|
|
6
4
|
isValidUuidV4 : (input) => {
|
|
7
5
|
if (!input) {
|
|
8
6
|
return false
|
|
@@ -12,8 +10,8 @@ module.exports = {
|
|
|
12
10
|
},
|
|
13
11
|
// convert a regular string or Array to array (ie, "[1,2,3]", "1,2,3")
|
|
14
12
|
// useful for controller inputs
|
|
15
|
-
convertToIntegerArray
|
|
16
|
-
return
|
|
13
|
+
convertToIntegerArray(input) {
|
|
14
|
+
return this.convertToArray(input)
|
|
17
15
|
.filter((el) => /^[0-9]+/.test(el))
|
|
18
16
|
.map((el) => parseInt(el));
|
|
19
17
|
},
|
|
@@ -36,34 +34,7 @@ module.exports = {
|
|
|
36
34
|
return []
|
|
37
35
|
}
|
|
38
36
|
}
|
|
39
|
-
}
|
|
40
|
-
isBlank: (str) => {
|
|
41
|
-
return ((str === null) || (str === undefined) || /^\s*$/.test(str));
|
|
42
|
-
},
|
|
43
|
-
getRouteType: (domain) => {
|
|
44
|
-
if (!domain)
|
|
45
|
-
return -1;
|
|
46
|
-
|
|
47
|
-
let routeType;
|
|
48
|
-
|
|
49
|
-
switch (true) {
|
|
50
|
-
case /^(tws-web|local-gateway)/i.test(domain):
|
|
51
|
-
routeType = TWS_ROUTE_TYPES['web'];
|
|
52
|
-
break;
|
|
53
|
-
case /^tws-mob/i.test(domain):
|
|
54
|
-
routeType = TWS_ROUTE_TYPES['mobile'];
|
|
55
|
-
break;
|
|
56
|
-
case /^(tws-api-)/i.test(domain):
|
|
57
|
-
routeType = TWS_ROUTE_TYPES['api'];
|
|
58
|
-
break;
|
|
59
|
-
default:
|
|
60
|
-
routeType = TWS_ROUTE_TYPES['other'];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return routeType;
|
|
64
|
-
},
|
|
65
|
-
isWebRoute: (sessionType) => (sessionType === TWS_ROUTE_TYPES['web']),
|
|
66
|
-
isApiRoute: (sessionType) => (sessionType === TWS_ROUTE_TYPES['api']),
|
|
67
|
-
isMobileRoute: (sessionType) => (sessionType === TWS_ROUTE_TYPES['mobile']),
|
|
68
|
-
isOtherRoute: (sessionType) => (sessionType === TWS_ROUTE_TYPES['other'])
|
|
37
|
+
}
|
|
69
38
|
}
|
|
39
|
+
|
|
40
|
+
export default utils;
|
package/cache/index.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
"use strict"
|
|
2
|
-
|
|
3
|
-
import { promisify } from "util";
|
|
4
|
-
|
|
5
|
-
const isEmpty = obj => [Object, Array].includes((obj || {}).constructor) && !Object.entries((obj || {})).length;
|
|
6
|
-
|
|
7
|
-
class CacheConfigError extends Error {
|
|
8
|
-
constructor(m) {
|
|
9
|
-
super(m);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/* Redis cache helper
|
|
14
|
-
* Example use: Place the following code in your project to setup redis use
|
|
15
|
-
*
|
|
16
|
-
* import { cache } from "@sera4/essentia";
|
|
17
|
-
* const cacheHelper = cache({ password: .., database: .., host: .., port: .. }, "service:"));
|
|
18
|
-
*
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const convertRedisKeyValueToJson = (v) => {
|
|
22
|
-
return (v && v.length > 0 && v[0] !== null) ? JSON.parse(v) : null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const convertRedisHashToJson = (v) => {
|
|
26
|
-
if (v) {
|
|
27
|
-
var result = {};
|
|
28
|
-
for(var k in v) {
|
|
29
|
-
if (v.hasOwnProperty(k)) {
|
|
30
|
-
result[k] = JSON.parse(v[k]);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
} else {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function convertJsonToRedisValue(j) {
|
|
40
|
-
var result = {};
|
|
41
|
-
for (var key in j) {
|
|
42
|
-
if (j.hasOwnProperty(key)) {
|
|
43
|
-
result[key] = JSON.stringify(j[key]);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return result;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
class Cache {
|
|
50
|
-
constructor(config, servicePrefix) {
|
|
51
|
-
if ((config == undefined) || isEmpty(config)) {
|
|
52
|
-
throw new CacheConfigError("Invalid cache configuration");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (servicePrefix == null) {
|
|
56
|
-
throw new CacheConfigError("Invalid service prefix");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (config.fake) {
|
|
60
|
-
// used in testing
|
|
61
|
-
this.redisClient = require("fakeredis").createClient("fake backend")
|
|
62
|
-
} else {
|
|
63
|
-
this.redisClient = require("redis").createClient(config);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
this.redisClient.on("error", (err) => {
|
|
67
|
-
logger.error("Cache/Redis error: ", err);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
this.servicePrefix = servicePrefix;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
ping() {
|
|
74
|
-
return this.redisClient.ping();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
properKey(key, path) {
|
|
78
|
-
let p = this.servicePrefix + key
|
|
79
|
-
if (path)
|
|
80
|
-
p += path
|
|
81
|
-
return p
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
writeHash(path, key, value) {
|
|
85
|
-
return this.redisClient.hsetAsync(properKey(key), key, value);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
writeHashBlocking(path, key, value) {
|
|
89
|
-
return this.redisClient.hset(properKey(key), key, value);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// write multiple hash keys and stringify every value
|
|
93
|
-
writeMultiHash(path, j) {
|
|
94
|
-
return this.redisClient.hmsetAsync(properKey(key), convertJsonToRedisValue(j));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
readHashKey(obj, key, autoConvert=true) {
|
|
98
|
-
var k = autoConvert;
|
|
99
|
-
|
|
100
|
-
return new Promise((resolve, reject) => {
|
|
101
|
-
this.redisClient.hget(properKey(key, obj), key, (e, r) => {
|
|
102
|
-
if (e) {
|
|
103
|
-
logger.error("Redis hmget error:", e);
|
|
104
|
-
reject(e);
|
|
105
|
-
} else {
|
|
106
|
-
resolve(k ? convertRedisKeyValueToJson(r) : r);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
readHash(path) {
|
|
113
|
-
return new Promise((resolve, reject) => {
|
|
114
|
-
this.redisClient.hgetall(properKey(key, path), (e, r) => {
|
|
115
|
-
if (e) {
|
|
116
|
-
console.log("Redis hget error:", e);
|
|
117
|
-
reject(e);
|
|
118
|
-
} else {
|
|
119
|
-
resolve(convertRedisHashToJson(r));
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
deleteHashKey(hash, key) {
|
|
126
|
-
return new Promise((resolve, reject) => {
|
|
127
|
-
this.redisClient.hdel(properKey(key, hash), key, (err) => {
|
|
128
|
-
if (err) {
|
|
129
|
-
logger.error("Redis hdel error:", err);
|
|
130
|
-
reject(err);
|
|
131
|
-
} else {
|
|
132
|
-
resolve();
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
get(key) {
|
|
139
|
-
return this.redisClient.getAsync(properKey(key));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
getAsync(key) {
|
|
143
|
-
return promisify(this.redisClient.get(key)).bind(this.redisClient)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
/* a single key write with optional expiry */
|
|
148
|
-
// returns bluebird promise
|
|
149
|
-
writeKey(key, value, expiry) {
|
|
150
|
-
key = this.servicePrefix + key;
|
|
151
|
-
if (expiry) {
|
|
152
|
-
return this.redisClient.setAsync(key, value, "EX", expiry);
|
|
153
|
-
} else {
|
|
154
|
-
return this.redisClient.setAsync(key, value);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
deleteKey(key) {
|
|
159
|
-
return redisClient.delAsync(properKey(key));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
expire(path, expiry) {
|
|
163
|
-
return this.redisClient.expire(path, expiry);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
exists(path) {
|
|
167
|
-
return this.redisClient.exists(path);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
deleteAllKeys() {
|
|
171
|
-
return new Promise((resolve, reject) => {
|
|
172
|
-
if(process.env.NODE_ENV === "production") {
|
|
173
|
-
throw new Error("Clearing system cache is not permitted in production");
|
|
174
|
-
}
|
|
175
|
-
this.redisClient.flushdb((err, res) => {
|
|
176
|
-
if (err) {
|
|
177
|
-
reject(err);
|
|
178
|
-
} else {
|
|
179
|
-
resolve(res);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export default function(config, servicePrefix) {
|
|
187
|
-
return new Cache(config, servicePrefix)
|
|
188
|
-
}
|
package/constants/index.js
DELETED
package/health/health-logger.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
const logger = require("../logger/s4-logger");
|
|
2
|
-
let enabled = false;
|
|
3
|
-
|
|
4
|
-
module.exports = {
|
|
5
|
-
debug(...args) {
|
|
6
|
-
if (enabled) {
|
|
7
|
-
logger.debug(":: HealthCheck ::", args);
|
|
8
|
-
}
|
|
9
|
-
},
|
|
10
|
-
|
|
11
|
-
log(...args) {
|
|
12
|
-
if (enabled) {
|
|
13
|
-
logger.debug(":: HealthCheck ::", args);
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
warn(...args) {
|
|
18
|
-
if (enabled) {
|
|
19
|
-
logger.warn(":: HealthCheck ::", args);
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
info(...args) {
|
|
24
|
-
if (enabled) {
|
|
25
|
-
logger.info(":: HealthCheck ::", args);
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
error(...args) {
|
|
30
|
-
if (enabled) {
|
|
31
|
-
logger.error(":: HealthCheck ::", args);
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
setEnabled(e) {
|
|
36
|
-
enabled = e;
|
|
37
|
-
}
|
|
38
|
-
}
|
package/safe_proxy/index.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const axios = require("axios"); // required only until migration is complete
|
|
4
|
-
const { omitBy, isNil } = require("lodash");
|
|
5
|
-
|
|
6
|
-
/* A class which safely wraps an axios connection in order to pass the current user through to
|
|
7
|
-
* the next service. Useful when one of our microservices requires auth as either an admin (sera4tal)
|
|
8
|
-
* or as the end user (example: DELETE /auth/sessions/:id
|
|
9
|
-
*
|
|
10
|
-
* each method requires a res.locals from a pre-authd session
|
|
11
|
-
*/
|
|
12
|
-
class SafeProxy {
|
|
13
|
-
constructor() {
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// a safe post request
|
|
17
|
-
// locals = res.locals - your authentication details
|
|
18
|
-
post(locals, url, options = { headers: {}, data: {}}) {
|
|
19
|
-
let { account, tenant, membership } = locals;
|
|
20
|
-
return this._proxyRequest({ account, tenant, membership }, url, "post", options);
|
|
21
|
-
}
|
|
22
|
-
delete(locals, url, options = { headers: {}, data: {}}) {
|
|
23
|
-
let { account, tenant, membership } = locals;
|
|
24
|
-
return this._proxyRequest({ account, tenant, membership }, url, "delete", options);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/* private methods */
|
|
28
|
-
// supports get, delete, post
|
|
29
|
-
// options includes { headers, data } for post / delete payloads
|
|
30
|
-
async _proxyRequest(locals, url, method = "get", options) {
|
|
31
|
-
let request;
|
|
32
|
-
let membership = locals.membership ? JSON.stringify(locals.membership) : null;
|
|
33
|
-
let headers = omitBy({ ...options.headers,
|
|
34
|
-
"tws-account-id": locals.account?.id,
|
|
35
|
-
"tws-tenant-id": locals.tenant?.id,
|
|
36
|
-
"tws-membership-id": locals.membership?.id,
|
|
37
|
-
"tws-membership": membership
|
|
38
|
-
}, isNil);
|
|
39
|
-
|
|
40
|
-
switch(method) {
|
|
41
|
-
case "post":
|
|
42
|
-
request = axios.post(url, { ...options.data }, { headers })
|
|
43
|
-
break;
|
|
44
|
-
case "delete":
|
|
45
|
-
request = axios.delete(url, { ...options.data, headers } )
|
|
46
|
-
break;
|
|
47
|
-
case "get":
|
|
48
|
-
request = axios.get(url, { headers, data } )
|
|
49
|
-
break;
|
|
50
|
-
default:
|
|
51
|
-
throw new Error("uknown type called in proxyRequest");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return request;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
module.exports = new SafeProxy();
|