jxp 2.12.4 → 2.13.1

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.
@@ -0,0 +1,13 @@
1
+ # Caching
2
+
3
+ Enable caching to speed up your queries. Caching is disabled by default. Enable caching through the config:
4
+
5
+ ```JSON
6
+ {
7
+ "cache": {
8
+ "enabled": true,
9
+ "debug": false,
10
+ "ttl": 3600
11
+ }
12
+ }
13
+ ```
package/libs/cache.js ADDED
@@ -0,0 +1,76 @@
1
+ const NodeCache = require('node-cache');
2
+ const config = require('config');
3
+ const cache = new NodeCache({ stdTTL: config.cache?.ttl || 5 * 60 });
4
+
5
+ const generateKey = (req) => {
6
+ let key = `${req.modelname}/`;
7
+ if (req.params.item_id) {
8
+ key += `${req.params.item_id}`
9
+ }
10
+ if (req.query) {
11
+ key += `?${JSON.stringify(req.query)}`
12
+ }
13
+ return key;
14
+ }
15
+
16
+ const set = async (req, res) => {
17
+ if (!config.cache?.enabled) return;
18
+ const key = generateKey(req)
19
+ cache.set(key, res.result)
20
+ if (config.cache?.debug) {
21
+ console.log('cache set', key)
22
+ }
23
+ }
24
+
25
+ const get = async (req, res) => {
26
+ if (!config.cache?.enabled) return;
27
+ const key = generateKey(req)
28
+ res.header('jxp-cache-key', key);
29
+ const cached = cache.get(key)
30
+ if (cached) {
31
+ if (config.cache?.debug) {
32
+ console.log('cache hit', key)
33
+ }
34
+ res.header('jxp-cache', 'hit')
35
+ res.result = cached
36
+ return res.send(res.result)
37
+ }
38
+ if (config.cache?.debug) {
39
+ console.log('cache miss', key)
40
+ }
41
+ res.header('jxp-cache', 'miss')
42
+ }
43
+
44
+ const clear = async (req, res) => {
45
+ if (!config.cache?.enabled) return;
46
+ const keys = cache.keys()
47
+ keys.forEach(key => {
48
+ if (key.startsWith(`${req.modelname}/`)) {
49
+ if (config.cache?.debug) {
50
+ console.log('cache del', key)
51
+ }
52
+ cache.del(key)
53
+ }
54
+ })
55
+ }
56
+
57
+ const clearAll = async () => {
58
+ if (!config.cache?.enabled) return;
59
+ if (config.cache?.debug) {
60
+ console.log('cache flushAll')
61
+ }
62
+ cache.flushAll()
63
+ }
64
+
65
+ const stats = async (req, res) => {
66
+ if (!config.cache?.enabled) return {};
67
+ res.result = cache.getStats()
68
+ }
69
+
70
+ module.exports = {
71
+ set,
72
+ get,
73
+ clear,
74
+ clearAll,
75
+ stats,
76
+ }
package/libs/jxp.js CHANGED
@@ -15,6 +15,7 @@ const modeldir = require("./modeldir");
15
15
  const query_manipulation = require("./query_manipulation");
16
16
  const corsMiddleware = require('restify-cors-middleware2');
17
17
  const json2csv = require('json2csv').parse;
18
+ const cache = require("./cache");
18
19
  global.JXPSchema = require("./schema");
19
20
 
20
21
  var models = {};
@@ -53,10 +54,9 @@ const middlewareCheckAdmin = (req, res, next) => {
53
54
  };
54
55
 
55
56
  // Outputs whatever is in res.result as JSON
56
- const outputJSON = (req, res, next) => {
57
+ const outputJSON = async (req, res) => {
57
58
  try {
58
59
  res.send(res.result);
59
- next();
60
60
  } catch (err) {
61
61
  console.error(new Date(), err);
62
62
  throw new errors.InternalServerError(err.toString());
@@ -202,7 +202,7 @@ const actionGetOne = async (req, res) => {
202
202
  console.time(opname);
203
203
  try {
204
204
  const data = await getOne(req.Model, req.params.item_id, req.query, { user: res.user });
205
- res.send({ data });
205
+ res.result = { data };
206
206
  if (debug) console.timeEnd(opname);
207
207
  } catch(err) {
208
208
  console.error(new Date(), err);
@@ -908,7 +908,9 @@ const JXP = function(options) {
908
908
  security.login,
909
909
  security.auth,
910
910
  config.pre_hooks.get,
911
+ cache.get,
911
912
  actionGet,
913
+ cache.set,
912
914
  outputJSON
913
915
  );
914
916
  server.get(
@@ -917,7 +919,10 @@ const JXP = function(options) {
917
919
  security.login,
918
920
  security.auth,
919
921
  config.pre_hooks.getOne,
920
- actionGetOne
922
+ cache.get,
923
+ actionGetOne,
924
+ cache.set,
925
+ outputJSON
921
926
  );
922
927
  server.post(
923
928
  "/api/:modelname",
@@ -927,6 +932,7 @@ const JXP = function(options) {
927
932
  middlewarePasswords,
928
933
  config.pre_hooks.post,
929
934
  actionPost,
935
+ cache.clear,
930
936
  (req, res, next) => {
931
937
  next();
932
938
  },
@@ -940,6 +946,10 @@ const JXP = function(options) {
940
946
  middlewareCheckAdmin,
941
947
  config.pre_hooks.put,
942
948
  actionPut,
949
+ cache.clear,
950
+ (req, res, next) => {
951
+ next();
952
+ },
943
953
  );
944
954
  server.del(
945
955
  "/api/:modelname/:item_id",
@@ -948,6 +958,7 @@ const JXP = function(options) {
948
958
  security.auth,
949
959
  config.pre_hooks.delete,
950
960
  actionDelete,
961
+ cache.clear,
951
962
  );
952
963
 
953
964
  // Count
@@ -957,7 +968,9 @@ const JXP = function(options) {
957
968
  security.login,
958
969
  security.auth,
959
970
  config.pre_hooks.get,
971
+ // cache.get,
960
972
  actionCount,
973
+ // cache.set,
961
974
  outputJSON
962
975
  );
963
976
 
@@ -1009,6 +1022,7 @@ const JXP = function(options) {
1009
1022
  middlewareCheckAdmin,
1010
1023
  config.pre_hooks.update,
1011
1024
  actionUpdate,
1025
+ cache.clear,
1012
1026
  );
1013
1027
 
1014
1028
  /* Batch routes - ROLLED BACK FOR NOW */
@@ -1020,21 +1034,24 @@ const JXP = function(options) {
1020
1034
  middlewareModel,
1021
1035
  security.login,
1022
1036
  security.auth,
1023
- actionCall
1037
+ actionCall,
1038
+ cache.clear,
1024
1039
  );
1025
1040
  server.post(
1026
1041
  "/call/:modelname/:method_name",
1027
1042
  middlewareModel,
1028
1043
  security.login,
1029
1044
  security.auth,
1030
- actionCall
1045
+ actionCall,
1046
+ cache.clear,
1031
1047
  );
1032
1048
  server.get(
1033
1049
  "/call/:modelname/:item_id/:method_name",
1034
1050
  middlewareModel,
1035
1051
  security.login,
1036
1052
  security.auth,
1037
- actionCallItem
1053
+ actionCallItem,
1054
+ cache.clear,
1038
1055
  );
1039
1056
 
1040
1057
  /* Login and authentication */
@@ -1081,6 +1098,11 @@ const JXP = function(options) {
1081
1098
 
1082
1099
  /* Websocket */
1083
1100
  server.on("upgrade", ws.upgrade)
1101
+
1102
+ /* Cache */
1103
+ server.get("/cache/stats", cache.stats, outputJSON);
1104
+ server.get("/cache/clear", cache.clear, outputJSON);
1105
+
1084
1106
  return server;
1085
1107
  };
1086
1108
 
package/mkdocs.yml CHANGED
@@ -11,6 +11,7 @@ nav:
11
11
  - Aggregations: aggregations.md
12
12
  - Bulk Writes: bulk_writes.md
13
13
  - Queries: queries.md
14
+ - Caching: caching.md
14
15
  - Websocket: websocket.md
15
16
  - Special Features: special.md
16
17
  - Changelog: changelog.md
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jxp",
3
3
  "description:": "An opinionated RESTful API library based on Mongoose and Restify. Make an API by just writing Mongoose models.",
4
- "version": "2.12.4",
4
+ "version": "2.13.1",
5
5
  "private": false,
6
6
  "main": "libs/jxp.js",
7
7
  "scripts": {
@@ -45,6 +45,7 @@
45
45
  "mongoose": "6.8.4",
46
46
  "mongoose-friendly": "^0.1.4",
47
47
  "morgan": "^1.10.0",
48
+ "node-cache": "^5.1.2",
48
49
  "nodemailer": "^6.9.0",
49
50
  "nodemailer-smtp-transport": "^2.7.4",
50
51
  "path": "^0.12.7",
package/test/init.js CHANGED
@@ -8,6 +8,7 @@ global.JXPSchema = require("../libs/schema");
8
8
  const User = require(path.join(model_dir, "user_model"));
9
9
  const Apikey = require(path.join(model_dir, "apikey_model"));
10
10
  const Test = require(path.join(model_dir, "test_model"));
11
+ const cache = require("../libs/cache");
11
12
 
12
13
  const security = require("../libs/security");
13
14
 
@@ -38,6 +39,7 @@ const admin_password = "SecretPassword";
38
39
 
39
40
  const init = async () => {
40
41
  try {
42
+ await cache.clearAll();
41
43
  await empty_user_collections();
42
44
  await post(User, { name: "Admin User", email: admin_email, password: security.encPassword(admin_password), urlid: "admin-user", admin: true });
43
45
  await post(User, { name: "Test User", email, password: security.encPassword(password), urlid: "test-user" });
package/test/test.js CHANGED
@@ -127,7 +127,6 @@ describe('Test', () => {
127
127
  .get("/api/user")
128
128
  .auth(init.email, init.password)
129
129
  .end((err, res) => {
130
- // console.log(res.body);
131
130
  res.should.have.status(200);
132
131
  res.body.data.should.be.an('array');
133
132
  done();
@@ -166,7 +165,6 @@ describe('Test', () => {
166
165
  chai.request(server)
167
166
  .get(`/api/user`)
168
167
  .end((err, res) => {
169
- // console.log(res.body);
170
168
  res.should.have.status(403);
171
169
  done();
172
170
  });
@@ -374,7 +372,6 @@ describe('Test', () => {
374
372
  .get(`/api/test/${post_id}?populate=link`)
375
373
  .auth(init.email, init.password)
376
374
  .end((err, res) => {
377
- // console.log(res.body);
378
375
  res.should.have.status(200);
379
376
  res.body.should.have.property("data");
380
377
  res.body.data.should.have.property("link");
@@ -391,7 +388,6 @@ describe('Test', () => {
391
388
  .get(`/api/test/${post_id}?populate=other_link`)
392
389
  .auth(init.email, init.password)
393
390
  .end((err, res) => {
394
- // console.log(res.body);
395
391
  res.should.have.status(200);
396
392
  res.body.should.have.property("data");
397
393
  res.body.data.should.have.property("other_link")
@@ -408,7 +404,6 @@ describe('Test', () => {
408
404
  .get(`/api/test?autopopulate=true`)
409
405
  .auth(init.email, init.password)
410
406
  .end((err, res) => {
411
- // console.log(res.body);
412
407
  res.should.have.status(200);
413
408
  res.body.data[0].should.have.property("link")
414
409
  res.body.data[0].link.should.be.an('object');
@@ -426,7 +421,6 @@ describe('Test', () => {
426
421
  .get(`/api/test/${post_id}?autopopulate=true`)
427
422
  .auth(init.email, init.password)
428
423
  .end((err, res) => {
429
- // console.log(res.body);
430
424
  res.should.have.status(200);
431
425
  res.body.data.should.have.property("link")
432
426
  res.body.data.link.should.be.an('object');
@@ -444,7 +438,6 @@ describe('Test', () => {
444
438
  .get(`/api/test/${post_id}?populate=link`)
445
439
  .auth(init.email, init.password)
446
440
  .end((err, res) => {
447
- console.log(res.body);
448
441
  res.should.have.status(200);
449
442
  res.body.should.have.property("data");
450
443
  res.body.data.should.have.property("link")
@@ -573,7 +566,6 @@ describe('Test', () => {
573
566
  .get(`/api/test?populate=array_link`)
574
567
  .auth(init.email, init.password)
575
568
  .end((err, res) => {
576
- // console.log(res.body);
577
569
  res.should.have.status(200);
578
570
  res.body.data[0].should.have.property("array_link");
579
571
  res.body.data[0].array_link.should.be.an("array");
@@ -727,7 +719,6 @@ describe('Test', () => {
727
719
  .send({ query })
728
720
  .end((err, res) => {
729
721
  res.should.have.status(200);
730
- // console.log(res.body);
731
722
  res.body.data.should.be.an('array');
732
723
  res.body.data[0].should.have.property("_id");
733
724
  res.body.data[0].should.have.property("count");
@@ -876,7 +867,6 @@ describe('Test', () => {
876
867
  .del(`/api/test/${post_id}`)
877
868
  .auth(init.email, init.password)
878
869
  .end((err, res) => {
879
- // console.log(res.body);
880
870
  res.should.have.status(200);
881
871
  res.body.status.should.equal('ok');
882
872
  done();
@@ -887,7 +877,6 @@ describe('Test', () => {
887
877
  .get(`/api/test/${post_id}`)
888
878
  .auth(init.email, init.password)
889
879
  .end((err, res) => {
890
- // console.log(res.body);
891
880
  res.should.have.status(404);
892
881
  res.body.message.should.equal(`Document ${post_id} is deleted on Test`);
893
882
  res.body.code.should.equal('NotFound');
@@ -992,7 +981,6 @@ describe('Test', () => {
992
981
  .auth(init.email, init.password)
993
982
  .end((err, res) => {
994
983
  res.should.have.status(409);
995
- // console.log(res.body.message);
996
984
  res.body.message.should.equal(`Parent link item exists in test/link_id`);
997
985
  done();
998
986
  });
@@ -1002,7 +990,6 @@ describe('Test', () => {
1002
990
  .del(`/api/link/${link_id}?_cascade=1`)
1003
991
  .auth(init.email, init.password)
1004
992
  .end((err, res) => {
1005
- // console.log(res.body, res.status);
1006
993
  res.should.have.status(200);
1007
994
  res.body.status.should.equal('ok');
1008
995
  done();
@@ -1133,12 +1120,65 @@ describe('Test', () => {
1133
1120
  bar: "Throw an error"
1134
1121
  })
1135
1122
  .end((err, res) => {
1136
- // console.log(res.body);
1137
- console.log(res.headers);
1138
1123
  res.should.have.status(418);
1139
1124
  res.body.message.should.equal(`I'm a teapot`);
1140
1125
  done();
1141
1126
  });
1142
1127
  })
1143
1128
  });
1129
+
1130
+ describe("Caching", () => {
1131
+ it ("should give us cache stats", (done) => {
1132
+ chai.request(server)
1133
+ .get("/cache/stats")
1134
+ .end((err, res) => {
1135
+ // console.log(res.body);
1136
+ res.should.have.status(200);
1137
+ res.body.should.have.property("hits");
1138
+ done();
1139
+ });
1140
+ })
1141
+ it ("should clear the cache stats", (done) => {
1142
+ chai.request(server)
1143
+ .get("/cache/clear")
1144
+ .end((err, res) => {
1145
+ console.log(res.body);
1146
+ res.should.have.status(200);
1147
+ done();
1148
+ });
1149
+ });
1150
+ it ("should get an uncached request", (done) => {
1151
+ chai.request(server)
1152
+ .get("/api/test")
1153
+ .auth(init.email, init.password)
1154
+ .end((err, res) => {
1155
+ res.should.have.status(200);
1156
+ res.headers.should.have.property("jxp-cache");
1157
+ res.headers["jxp-cache"].should.equal("miss");
1158
+ done();
1159
+ });
1160
+ });
1161
+ it ("should get an cached request", (done) => {
1162
+ chai.request(server)
1163
+ .get("/api/test")
1164
+ .auth(init.email, init.password)
1165
+ .end((err, res) => {
1166
+ res.should.have.status(200);
1167
+ res.headers.should.have.property("jxp-cache");
1168
+ res.headers["jxp-cache"].should.equal("hit");
1169
+ done();
1170
+ });
1171
+ });
1172
+ it ("should give us cache stats", (done) => {
1173
+ chai.request(server)
1174
+ .get("/cache/stats")
1175
+ .end((err, res) => {
1176
+ res.should.have.status(200);
1177
+ res.body.should.have.property("hits");
1178
+ res.body.hits.should.be.greaterThan(0);
1179
+ res.body.misses.should.be.greaterThan(0);
1180
+ done();
1181
+ });
1182
+ })
1183
+ });
1144
1184
  });