UNPKG

6.92 kBJavaScriptView Raw
1const commonUtils = require('./common-utils');
2const deepcopy = function(obj) {
3 return JSON.parse(JSON.stringify(obj));
4};
5const util = require('util');
6
7const pDebounce = require('p-debounce');
8
9const checkCacheSize = (cache, maxSize) => {
10 return new Promise(resolve => {
11 //console.log('we gaan eens kijken of de cache niet te groot is');
12 if(memorySizeOf(cache) > maxSize) {
13 console.warn('The cache is too big! Help!');
14 // Do clean up!
15 } else {
16 resolve();
17 }
18 });
19};
20
21const debouncedCheckCacheSize = pDebounce(checkCacheSize, 10000);
22
23
24const createCacheRecord = function (body) {
25 const timestamp = new Date();
26 const cacheRecord = {
27 timestamp: timestamp,
28 lastUsed: timestamp,
29 body: body,
30 estimatedSize: null
31 };
32 //body.then(result => cacheRecord.estimatedSize = memorySizeOf(result));
33 return cacheRecord;
34};
35
36const getHrefType = (fullHref, isList) => {
37 const containsQuestionMark = fullHref.match(/^.*\?.*$/g);
38 if(containsQuestionMark) {
39 if(fullHref.toLowerCase().match(/\?(limit\=[^&]+|offset\=[^&]+|keyoffset\=[^&]+|hrefs\=[^&]+|expand\=(full|summary|none))(&(limit\=[^&]+|offset\=[^&]+|keyoffset\=[^&]+|hrefs\=[^&]+|expand\=(full|summary|none)))*$/g)) {
40 return 'BASIC_LIST';
41 } else {
42 return 'COMPLEX';
43 }
44 } else {
45 if(isList) {
46 return 'BASIC_LIST';
47 } else {
48 return 'PERMALINK';
49 }
50 }
51};
52
53function memorySizeOf(object) {
54 var objectList = [];
55 var stack = [ object ];
56 var bytes = 0;
57
58 while ( stack.length ) {
59 var value = stack.pop();
60
61 if ( typeof value === 'boolean' ) {
62 bytes += 4;
63 }
64 else if ( typeof value === 'string' ) {
65 bytes += value.length * 2;
66 }
67 else if ( typeof value === 'number' ) {
68 bytes += 8;
69 }
70 else if (typeof value === 'object' && objectList.indexOf( value ) === -1) {
71 objectList.push( value );
72
73 for( var i in value ) {
74 stack.push( value[ i ] );
75 }
76 }
77 }
78 return bytes;
79};
80
81module.exports = class Cache {
82 constructor(config = {}, api) {
83 this.timeout = config.timeout || 0;
84 this.maxSize = config.maxSize || 10;
85 this.api = api;
86 if(config.initialise && !Array.isArray(config.initialise)) {
87 this.initialConfig = [config.initialise];
88 } else {
89 this.initialConfig = config.initialise;
90 }
91 this.cache = {
92 basicLists: {},
93 complexHrefs: {},
94 totalSize: 0
95 };
96 }
97
98
99 initialise() {
100 if(this.initialConfig) {
101 this.initialConfig.forEach(init => {
102 init.hrefs.forEach(href => {
103 const cacheConfig = {timeout: init.timeout ? init.timeout : this.timeout};
104 this.api.getAll(href, undefined, {caching: cacheConfig});
105 });
106 });
107 }
108 }
109
110 async get(href, params, options = {}, isList) {
111 const cacheOptions = options.caching || {};
112 const timeout = cacheOptions.timeout || this.timeout;
113 if(timeout === 0) {
114 return this.api.getRaw(href, params, options);
115 }
116 const fullHref = commonUtils.parametersToString(href, params);
117 const cacheRecord = this.getCacheRecord(fullHref, isList);
118 if(!cacheRecord || (new Date().getTime() - cacheRecord.timestamp.getTime() > timeout * 1000)) {
119 const logging = options.logging || this.api.configuration.logging;
120 if(/caching/.test(logging)) {
121 console.log('cache MISS for ' + fullHref);
122 }
123 const body = this.api.getRaw(href, params, options);
124 this.updateCacheRecord(fullHref, isList, body);
125 body.then(result => {
126 if(isList && (!href.toLowerCase().match(/^.+[\?\&]expand\=.+$/) || href.toLowerCase().match(/^.+[\?\&]expand\=full.*$/))) {
127 result.results.forEach(obj => {
128 this.updateCacheRecord(obj.href, false, Promise.resolve(obj.$$expanded));
129 });
130 }
131
132 // do not do cache size checking yet. Gunther experience problems when loading 6000 resources of ZILL curriculum which made the app stall for 6 seconds
133 //debouncedCheckCacheSize(this.cache, this.maxSize);
134 });
135 const resolvedBody = await body;
136 return deepcopy(resolvedBody);
137 } else {
138 if(options.logging === 'cacheInfo' || options.logging === 'debug') {
139 console.log('cache HIT for ' + fullHref);
140 }
141 //console.log(util.inspect(cacheRecord, {depth: 10}))
142 cacheRecord.lastUsed = new Date;
143 const resolvedBody = await cacheRecord.body;
144 return deepcopy(resolvedBody);
145 }
146 }
147
148 has(href, params, cacheOptions = {}, isList) {
149 const timeout = cacheOptions.timeout || this.timeout;
150 if(timeout === 0) {
151 return false;
152 }
153 const fullHref = commonUtils.parametersToString(href, params);
154 const cacheRecord = this.getCacheRecord(fullHref);
155 return cacheRecord && (new Date().getTime() - cacheRecord.timestamp.getTime() <= timeout * 1000);
156 }
157
158 getCacheRecord(fullHref, isList) {
159 switch(getHrefType(fullHref, isList)) {
160 case 'PERMALINK':
161 const parts = commonUtils.splitPermalink(fullHref);
162 const group = this.cache[parts.path];
163 return group ? group[parts.key] : undefined;
164 break;
165 case 'BASIC_LIST':
166 return this.cache.basicLists[fullHref];
167 break;
168 case 'COMPLEX':
169 return this.cache.complexHrefs[fullHref];
170 break;
171 }
172 }
173
174 updateCacheRecord(fullHref, isList, body) {
175 const cacheRecord = createCacheRecord(body);
176 switch(getHrefType(fullHref, isList)) {
177 case 'PERMALINK':
178 const parts = commonUtils.splitPermalink(fullHref);
179 if(!this.cache[parts.path]) {
180 this.cache[parts.path] = {};
181 }
182 this.cache[parts.path][parts.key] = cacheRecord;
183 break;
184 case 'BASIC_LIST':
185 this.cache.basicLists[fullHref] = cacheRecord;
186 break;
187 case 'COMPLEX':
188 return this.cache.complexHrefs[fullHref] = cacheRecord;
189 break;
190 }
191 return cacheRecord;
192 }
193
194 onResourceUpdated(permalink) {
195 this.cache.complex = {};
196 const parts = commonUtils.splitPermalink(permalink);
197 Object.keys(this.cache.basicLists).forEach(entry => {
198 if(entry.startsWith(parts.path)) {
199 delete this.cache.basicLists[entry];
200 }
201 });
202 const group = this.cache[parts.path];
203 if(group) {
204 delete group[parts.key];
205 }
206 }
207
208 onBatchPerformed(batch) {
209 batch.forEach(outerBatchElem => {
210 if(!Array.isArray(outerBatchElem)) {
211 outerBatchElem = [outerBatchElem];
212 }
213 outerBatchElem.forEach(batchElem => {
214 if(batchElem.verb === 'PUT' || batchElem.verb === 'DELETE') {
215 this.onResourceUpdated(batchElem.href);
216 }
217 });
218 });
219 }
220
221 onDataAltered(href, payload, method) {
222 if((method === 'PUT' || method === 'POST') && href.match(/\/batch$/)) {
223 this.onBatchPerformed(payload);
224 } else if(method === 'PUT' || method === 'DELETE') {
225 this.onResourceUpdated(href);
226 }
227 }
228};