@sera4/essentia 1.0.37 → 1.0.39

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/cache/index.js ADDED
@@ -0,0 +1,188 @@
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/health/index.js CHANGED
@@ -15,10 +15,12 @@ class HealthCheckError extends Error {
15
15
  * const router = require('express').Router();
16
16
  *
17
17
  * ....
18
+ *
19
+ * const checkRedis = async () => { await cache.ping(); }
20
+ * hc.setupCheck({ name: "redis", checkResult: false, critical: true, healthFunction: checkRedis });
18
21
  * app.use(hc.defaultPath(serverConfigs.api_prefix), hc.routes(router));
19
22
  *
20
23
  */
21
-
22
24
  class HealthCheck {
23
25
  constructor(appendData = {}) {
24
26
  this.defaultResponse = {
@@ -33,7 +35,8 @@ class HealthCheck {
33
35
  this.healthChecks = [];
34
36
  }
35
37
 
36
- /* call this with an async function, must support wait capability
38
+ /* Deprecated and replaced by setupCheck
39
+ * call this with an async function, must support wait capability
37
40
  *
38
41
  * const dbCheck = async () => {
39
42
  * return { name: "db", "result": "success", critical: false };
@@ -60,15 +63,38 @@ class HealthCheck {
60
63
  return router
61
64
  }
62
65
 
63
- /* performs health checks and returns 200 if true and 500 if not */
66
+ // v2 version of health check, replaced addCheck
67
+ // example:
68
+ // const checkRedisFcn = async () => { await cache.ping().then(() => true) }
69
+ // setupCheck({ name: "redis", checkResult: false, critical: false, healthFunction: checkRedisFcn });
70
+ setupCheck(config) {
71
+ this.healthChecks.push(config);
72
+ }
73
+
74
+ /* performs health checks and returns 200 if true and 500 if not */
64
75
  async checkHealth(req, res) {
65
76
  const checkFormat = pathHelper.extname(req.baseUrl);
66
77
 
67
- try {
68
- const result = await Promise.all(this.healthChecks.map(async healthCallback => {
69
- return await healthCallback();
70
- }));
78
+ const result = await Promise.all(this.healthChecks.map(async hc => {
79
+ if (typeof(hc) === "function") {
80
+ try {
81
+ return await hc();
82
+ return { "result": "success", critical: false };
83
+ } catch(e) {
84
+ return { name: "unknown", result: 'fail', status: e.status }
85
+ }
86
+ } else {
87
+ let res = { name: hc.name, critical: hc.critical }
88
+ try {
89
+ await hc.healthFunction();
90
+ return { ...res, "result": "success" };
91
+ } catch(e) {
92
+ return { ...res, "result": "fail", status: e.status }
93
+ }
94
+ }
95
+ }));
71
96
 
97
+ try {
72
98
  let callId = ++this.lastCallId;
73
99
 
74
100
  // start the counter over
@@ -78,10 +104,18 @@ class HealthCheck {
78
104
 
79
105
  let status = this.defaultResponse.status;
80
106
 
107
+ let nonCriticalFail = false;
108
+ let criticalFail = false;
109
+
81
110
  result.some(e => {
82
111
  if (e.result.match(/fail/)) {
83
- status = 500;
84
- return true; // break
112
+ if (e.critical == false) {
113
+ nonCriticalFail = true;
114
+ } else {
115
+ criticalFail = true;
116
+ status = 500;
117
+ return true; // break
118
+ }
85
119
  }
86
120
  })
87
121
 
@@ -89,33 +123,36 @@ class HealthCheck {
89
123
  res.setHeader('check', callId)
90
124
 
91
125
  if (checkFormat === ".json") {
92
- const payload = { "check": callId }
126
+ const payload = { "check": callId, healthy: "true", message: "success" }
93
127
 
94
- if (status === 200) {
128
+ if (criticalFail) {
95
129
  res.status(status).json({
96
130
  ... payload,
97
- "healthy":true,
98
- "message":"success"
131
+ "healthy": false,
132
+ "message": "fail",
133
+ "services": [result]
99
134
  });
100
- } else {
135
+ } else if (status === 200) {
136
+ res.status(status).json(payload);
137
+ } else if (nonCriticalFailure) {
101
138
  res.status(status).json({
102
139
  ... payload,
103
- "healthy": false,
104
140
  "message": "fail",
105
141
  "services": [result]
106
142
  });
107
143
  }
108
144
  } else {
109
145
  let status_message;
110
- if (status === 200) {
111
- status_message = "success"
112
- } else {
146
+ if (criticalFail) {
113
147
  status_message = "fail"
114
- }
148
+ } else if ((status === 200) || nonCriticalFail) {
149
+ status_message = "success"
150
+ }
115
151
 
116
152
  res.status(status).send(status_message);
117
153
  }
118
154
  } catch(error) {
155
+ console.debug("error: ", error)
119
156
  res.status(500).send();
120
157
  }
121
158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sera4/essentia",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "A library of utilities for Teleporte Web Services",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/package.tar.gz CHANGED
Binary file