Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.

Commit b8c1e7b

Browse files
authored
Merge pull request #26 from patrixr/koa-context-entrypoint
ADD: Koa context entry point
2 parents 9fa09f6 + 991ed43 commit b8c1e7b

File tree

5 files changed

+114
-11
lines changed

5 files changed

+114
-11
lines changed

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ The module's configuration object supports the following properties
104104
| maxAge | 3600000 | Time in milliseconds after which a cache entry is invalidated |
105105
| cacheTimeout | 500 | Time in milliseconds after which a cache request is timed out |
106106
| logs | true | Setting it to false will disable any console output |
107+
| populateContext | false | Setting it to true will inject a cache entry point into the Koa context |
107108
| redisConfig _(redis only)_ | {} | The redis config object passed on to [ioredis](https://www.npmjs.com/package/ioredis) |
108109

109110
### Example
@@ -203,6 +204,38 @@ module.exports = ({ env }) => ({
203204
});
204205
```
205206

207+
## Cache entry point
208+
209+
By setting the `populateContext` configuration to `true`, the middleware will extend the Koa Context with an entry point which can be used to clear the cache from within controllers
210+
211+
```javascript
212+
// config/middleware.js
213+
module.exports = ({ env }) => ({
214+
settings: {
215+
cache: {
216+
enabled: true,
217+
populateContext: true
218+
models: ['post']
219+
}
220+
}
221+
});
222+
223+
// controller
224+
225+
module.exports = {
226+
async index(ctx) {
227+
ctx.middleware.cache.store // A direct access to the cache engine
228+
await ctx.middleware.cache.bust({ model: 'posts', id: '123' }); // Will bust the cache for this specific record
229+
await ctx.middleware.cache.bust({ model: 'posts' }); // Will bust the cache for the entire model collection
230+
await ctx.middleware.cache.bust({ model: 'homepage' }); // For single types, do not pluralize the model name
231+
232+
// ...
233+
}
234+
};
235+
```
236+
237+
**IMPORTANT**: We do not recommend using this unless truly necessary. It is disabled by default as it goes against the non-intrusive/transparent nature of this middleware.
238+
206239
## Admin panel interactions
207240

208-
The strapi admin panel uses a separate rest api to apply changes to records, e.g `/content-manager/explorer/application::post.post` the middleware will also watch for write operations on that endpoint and bust the cache accordingly
241+
The strapi admin panel uses a separate rest api to apply changes to records, e.g `/content-manager/explorer/application::post.post` the middleware will also watch for write operations on that endpoint and bust the cache accordingly

lib/defaults.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"max": 500,
66
"maxAge": 3600000,
77
"cacheTimeout": 500,
8-
"logs": true
8+
"logs": true,
9+
"populateContext": false
910
}
10-
}
11+
}

lib/index.js

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const pluralize = require('pluralize');
55

66
const PLUGIN_NAME = 'cache';
77

8+
const cacheKeyPrefix = ({ model, id = null}) => {
9+
return id ? `/${model}/${id}` : `/${model}`;
10+
}
11+
812
/**
913
* Generates a cache key for the current request
1014
*
@@ -15,7 +19,7 @@ const generateCacheKey = (model, ctx) => {
1519
const { params, query = {} } = ctx;
1620
const { id } = params;
1721

18-
const prefix = params.id ? `/${model}/${id}` : `/${model}`;
22+
const prefix = cacheKeyPrefix({ model, id })
1923
const suffix = Object
2024
.keys(query)
2125
.sort()
@@ -35,6 +39,7 @@ const Cache = (strapi) => {
3539
const options = _.get(strapi, `config.middleware.settings.${PLUGIN_NAME}`, {});
3640
const type = _.get(options, 'type', 'mem');
3741
const allowLogs = _.get(options, 'logs', true);
42+
const withKoaContext = _.get(options, 'populateContext', false);
3843
const defaultModelOptions = { singleType: false };
3944

4045
const info = (msg) => allowLogs && strapi.log.debug(`[Cache] ${msg}`);
@@ -61,6 +66,46 @@ const Cache = (strapi) => {
6166

6267
this.cache = cache;
6368

69+
// --- Helpers
70+
71+
/**
72+
*
73+
* @param {Object} params
74+
* @param {string} params.model The model to bust the cache of
75+
* @param {string} [params.id] The (optional) ID we want to bust the cache for
76+
*/
77+
const clearCache = async ({ model, id = null }) => {
78+
const keys = await cache.keys() || [];
79+
const rexps = []
80+
81+
if (!id) {
82+
rexps.push(new RegExp(`^/${model}`)) // Bust anything in relation to that model
83+
} else {
84+
rexps.push(new RegExp(`^/${model}/${id}`)) // Bust the record itself
85+
rexps.push(new RegExp(`^/${model}\\?`)) // Bust the collections, but not other individual records
86+
}
87+
88+
const shouldBust = key => _.find(rexps, r => r.test(key))
89+
const bust = key => cache.del(key)
90+
91+
await Promise.all(
92+
_.filter(keys, shouldBust).map(bust)
93+
);
94+
}
95+
96+
97+
// --- Populate Koa Context with cache entry point
98+
99+
if (withKoaContext) {
100+
strapi.app.use((ctx, next) => {
101+
_.set(ctx, 'middleware.cache', {
102+
bust: clearCache,
103+
store: cache
104+
})
105+
return next();
106+
});
107+
}
108+
64109
// --- Standard REST endpoints
65110

66111
/**
@@ -69,17 +114,14 @@ const Cache = (strapi) => {
69114
* @param {string} model
70115
*/
71116
const bust = (model) => async (ctx, next) => {
72-
const pattern = new RegExp(`^/${model}`);
73-
const keys = await cache.keys() || [];
117+
const { params } = ctx;
118+
const { id } = params;
74119

75120
await next();
76121

77122
if (!_.inRange(ctx.status, 200, 300)) return;
78123

79-
await Promise.all(
80-
_.filter(keys, k => pattern.test(k))
81-
.map(k => cache.del(k))
82-
);
124+
await clearCache({ model, id })
83125
}
84126

85127
_.each(toCache, (cacheConf) => {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "strapi-middleware-cache",
3-
"version": "1.0.11",
3+
"version": "1.1.0",
44
"description": "Cache strapi requests",
55
"main": "./lib",
66
"scripts": {

tests/cache.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ describe('Caching', () => {
1717
cache: {
1818
type: 'mem',
1919
enabled: true,
20+
populateContext: true,
2021
models: [
2122
'academy',
2223
{
@@ -32,6 +33,13 @@ describe('Caching', () => {
3233
middleware = initMiddleware(strapi);
3334
middleware.initialize();
3435

36+
strapi.app.use((ctx, next) => {
37+
expect(ctx.middleware.cache).not.to.be.undefined
38+
expect(ctx.middleware.cache.bust).to.be.a('function')
39+
expect(ctx.middleware.cache.store).to.be.an('object')
40+
next();
41+
});
42+
3543
strapi.start();
3644
});
3745

@@ -99,6 +107,25 @@ describe('Caching', () => {
99107
expect(requests).to.have.lengthOf(3);
100108
});
101109

110+
context(`when an ID is specified on a ${method.toUpperCase()} request`, () => {
111+
it(`doesn't bust the cache for other IDs`, async () => {
112+
const res1 = await agent(strapi.app).get('/academies/1').expect(200);
113+
const res2 = await agent(strapi.app).get('/academies/2').expect(200);
114+
115+
expect(requests).to.have.lengthOf(2);
116+
117+
const res3 = await agent(strapi.app)[method]('/academies/1').expect(200);
118+
119+
expect(res3.body.uid).to.equal(res2.body.uid + 1);
120+
expect(requests).to.have.lengthOf(3);
121+
122+
const res4 = await agent(strapi.app).get('/academies/2').expect(200);
123+
124+
expect(res4.body.uid).to.equal(res2.body.uid);
125+
expect(requests).to.have.lengthOf(3);
126+
});
127+
});
128+
102129
it(`busts the cache on an admin panel ${method.toUpperCase()} resquest`, async () => {
103130
const res1 = await agent(strapi.app).get('/academies').expect(200);
104131

0 commit comments

Comments
 (0)