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.
- package/docs/caching.md +13 -0
- package/libs/cache.js +76 -0
- package/libs/jxp.js +29 -7
- package/mkdocs.yml +1 -0
- package/package.json +2 -1
- package/test/init.js +2 -0
- package/test/test.js +55 -15
package/docs/caching.md
ADDED
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
|
|
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.
|
|
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
|
-
|
|
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
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.
|
|
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
|
});
|