strapi-cache 1.5.7-rc.2 → 1.5.8

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/README.md CHANGED
@@ -50,6 +50,7 @@ In your Strapi project, navigate to `config/plugins.js` and add the following co
50
50
  size: 1024 * 1024 * 1024, // Maximum size of the cache (1 GB) (only for memory cache)
51
51
  allowStale: false, // Allow stale cache items (only for memory cache)
52
52
  cacheableRoutes: ['/api/products', '/api/categories'], // Caches routes which start with these paths (if empty array, all '/api' routes are cached)
53
+ excludeRoutes: ['/api/products/private'], // (NEW) Exclude routes which start with these paths from being cached (takes precedence over cacheableRoutes). **Note:** `excludeRoutes` takes precedence over `cacheableRoutes`.
53
54
  provider: 'memory', // Cache provider ('memory' or 'redis')
54
55
  redisConfig: env('REDIS_URL', 'redis://localhost:6379'), // Redis config takes either a string or an object see https://github.com/redis/ioredis for references to what object is available, the object or string is passed directly to ioredis client (if using Redis)
55
56
  redisClusterNodes: [], // If provided any cluster node (this list is not empty), initialize ioredis redis cluster client. Each object must have keys 'host' and 'port'. See https://github.com/redis/ioredis for references
@@ -60,6 +61,7 @@ In your Strapi project, navigate to `config/plugins.js` and add the following co
60
61
  cacheAuthorizedRequests: false, // Cache requests with authorization headers (set to true if you want to cache authorized requests)
61
62
  cacheGetTimeoutInMs: 1000, // Timeout for getting cached data in milliseconds (default is 1 second)
62
63
  autoPurgeCache: true, // Automatically purge cache on content CRUD operations
64
+ autoPurgeCacheOnStart: true, // Automatically purge cache on Strapi startup
63
65
  },
64
66
  },
65
67
  ```
@@ -95,34 +97,6 @@ All of these routes are protected by the policies `admin::isAuthenticatedAdmin`
95
97
 
96
98
  If you encounter any issues, please feel free to open an issue on the [GitHub repo](https://github.com/TupiC/strapi-cache/issues/new).
97
99
 
98
- ## 🛠️ Troubleshooting
99
-
100
- If you encounter an error like:
101
-
102
- ```
103
- Access to fetch at 'http://your-backend.com' from origin 'http://your-origin.com' has been blocked by CORS policy:
104
- Request header field cache-control is not allowed by Access-Control-Allow-Headers in preflight response.
105
- ```
106
-
107
- You might need to adjust your CORS middleware settings in Strapi:
108
-
109
- ```javascript
110
- // config/middlewares.{js,ts}
111
- 'strapi::cors';
112
- ```
113
-
114
- with:
115
-
116
- ```javascript
117
- // config/middlewares.{js,ts}
118
- {
119
- name: "strapi::cors",
120
- config: {
121
- headers: ["Content-Type", "Authorization", "Cache-Control"], // Add 'Cache-Control' to the allowed headers
122
- },
123
- },
124
- ```
125
-
126
100
  ## 🛠️ Contributing
127
101
 
128
102
  Contributions are welcome! If you have suggestions or improvements, please open an issue or submit a pull request.
@@ -161,7 +161,11 @@ function PurgeCacheButton() {
161
161
  function PurgeEntityButton() {
162
162
  const { formatMessage } = reactIntl.useIntl();
163
163
  const { id, isSingleType, contentType } = admin.unstable_useContentManagerContext();
164
- const keyToUse = isSingleType ? contentType?.info.singularName : id;
164
+ const apiPath = isSingleType ? contentType?.info.singularName : id;
165
+ if (!apiPath) {
166
+ return null;
167
+ }
168
+ const keyToUse = encodeURIComponent(apiPath);
165
169
  const contentTypeName = isSingleType ? contentType?.info.singularName : contentType?.info.pluralName;
166
170
  return /* @__PURE__ */ jsxRuntime.jsx(
167
171
  PurgeModal,
@@ -160,7 +160,11 @@ function PurgeCacheButton() {
160
160
  function PurgeEntityButton() {
161
161
  const { formatMessage } = useIntl();
162
162
  const { id, isSingleType, contentType } = unstable_useContentManagerContext();
163
- const keyToUse = isSingleType ? contentType?.info.singularName : id;
163
+ const apiPath = isSingleType ? contentType?.info.singularName : id;
164
+ if (!apiPath) {
165
+ return null;
166
+ }
167
+ const keyToUse = encodeURIComponent(apiPath);
164
168
  const contentTypeName = isSingleType ? contentType?.info.singularName : contentType?.info.pluralName;
165
169
  return /* @__PURE__ */ jsx(
166
170
  PurgeModal,
@@ -1,2 +1,2 @@
1
- declare function PurgeEntityButton(): import("react/jsx-runtime").JSX.Element;
1
+ declare function PurgeEntityButton(): import("react/jsx-runtime").JSX.Element | null;
2
2
  export default PurgeEntityButton;
@@ -73,6 +73,7 @@ const bootstrap = ({ strapi: strapi2 }) => {
73
73
  try {
74
74
  const cacheService = strapi2.plugin("strapi-cache").services.service;
75
75
  const autoPurgeCache = strapi2.plugin("strapi-cache").config("autoPurgeCache");
76
+ const autoPurgeCacheOnStart = strapi2.plugin("strapi-cache").config("autoPurgeCacheOnStart");
76
77
  const cacheStore = cacheService.getCacheInstance();
77
78
  if (!cacheStore) {
78
79
  loggy.error("Plugin could not be initialized");
@@ -95,6 +96,13 @@ const bootstrap = ({ strapi: strapi2 }) => {
95
96
  }
96
97
  });
97
98
  }
99
+ if (autoPurgeCacheOnStart) {
100
+ cacheStore.reset().then(() => {
101
+ loggy.info("Cache purged successfully");
102
+ }).catch((error) => {
103
+ loggy.error(`Error purging cache on start: ${error.message}`);
104
+ });
105
+ }
98
106
  } catch (error) {
99
107
  loggy.error("Plugin could not be initialized");
100
108
  return;
@@ -179,6 +187,7 @@ function getCacheHeaderConfig() {
179
187
  const middleware$1 = async (ctx, next) => {
180
188
  const cacheService = strapi.plugin("strapi-cache").services.service;
181
189
  const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
190
+ const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
182
191
  const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
183
192
  const cacheStore = cacheService.getCacheInstance();
184
193
  const { url } = ctx.request;
@@ -186,6 +195,12 @@ const middleware$1 = async (ctx, next) => {
186
195
  const cacheEntry = await cacheStore.get(key);
187
196
  const cacheControlHeader = ctx.request.headers["cache-control"];
188
197
  const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
198
+ const routeIsExcluded = excludeRoutes.some((route) => url.startsWith(route));
199
+ if (routeIsExcluded) {
200
+ loggy.info(`Route excluded from cache: ${url}`);
201
+ await next();
202
+ return;
203
+ }
189
204
  const routeIsCachable = cacheableRoutes.some((route) => url.startsWith(route)) || cacheableRoutes.length === 0 && url.startsWith("/api");
190
205
  const authorizationHeader = ctx.request.headers["authorization"];
191
206
  if (authorizationHeader && !cacheAuthorizedRequests) {
@@ -193,12 +208,32 @@ const middleware$1 = async (ctx, next) => {
193
208
  await next();
194
209
  return;
195
210
  }
211
+ const middlewaresConfig = strapi.config.get("middlewares");
212
+ const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
213
+ const corsConfig = corsMiddleware?.config;
214
+ const origin = ctx?.request?.headers?.origin;
215
+ let allowedOrigins = corsConfig?.origin ?? "*";
216
+ if (typeof allowedOrigins === "string") {
217
+ allowedOrigins = [allowedOrigins];
218
+ }
196
219
  if (cacheEntry && !noCache) {
197
220
  loggy.info(`HIT with key: ${key}`);
198
221
  ctx.status = 200;
199
222
  ctx.body = cacheEntry.body;
200
223
  if (cacheHeaders) {
201
224
  ctx.set(cacheEntry.headers);
225
+ }
226
+ if (corsMiddleware) {
227
+ loggy.info("CORS middleware is set, checking allowed origins");
228
+ if (allowedOrigins.includes(origin)) {
229
+ loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
230
+ ctx.set("Access-Control-Allow-Origin", origin);
231
+ } else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
232
+ loggy.info("No origin header or * in allowed origins, setting to *");
233
+ ctx.set("Access-Control-Allow-Origin", "*");
234
+ }
235
+ } else {
236
+ loggy.info("No CORS middleware set, setting to request origin or *");
202
237
  ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
203
238
  }
204
239
  return;
@@ -212,15 +247,27 @@ const middleware$1 = async (ctx, next) => {
212
247
  cacheHeadersAllowList,
213
248
  cacheHeadersDenyList
214
249
  );
250
+ let setCache = true;
251
+ if (corsMiddleware) {
252
+ if (allowedOrigins.includes(origin)) ;
253
+ else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
254
+ else {
255
+ setCache = false;
256
+ }
257
+ }
215
258
  if (ctx.body instanceof Stream__default.default) {
216
259
  const buf = await streamToBuffer(ctx.body);
217
260
  const contentEncoding = ctx.response.headers["content-encoding"];
218
261
  const decompressed = await decompressBuffer(buf, contentEncoding);
219
262
  const responseText = decodeBufferToText(decompressed);
220
- await cacheStore.set(key, { body: responseText, headers: headersToStore });
263
+ if (setCache) {
264
+ await cacheStore.set(key, { body: responseText, headers: headersToStore });
265
+ }
221
266
  ctx.body = buf;
222
267
  } else {
223
- await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
268
+ if (setCache) {
269
+ await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
270
+ }
224
271
  }
225
272
  }
226
273
  };
@@ -259,12 +306,32 @@ const middleware = async (ctx, next) => {
259
306
  await next();
260
307
  return;
261
308
  }
309
+ const middlewaresConfig = strapi.config.get("middlewares");
310
+ const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
311
+ const corsConfig = corsMiddleware?.config;
312
+ const origin = ctx?.request?.headers?.origin;
313
+ let allowedOrigins = corsConfig?.origin ?? "*";
314
+ if (typeof allowedOrigins === "string") {
315
+ allowedOrigins = [allowedOrigins];
316
+ }
262
317
  if (cacheEntry && !noCache) {
263
318
  loggy.info(`HIT with key: ${key}`);
264
319
  ctx.status = 200;
265
320
  ctx.body = cacheEntry.body;
266
321
  if (cacheHeaders) {
267
322
  ctx.set(cacheEntry.headers);
323
+ }
324
+ if (corsMiddleware) {
325
+ loggy.info("CORS middleware is set, checking allowed origins");
326
+ if (allowedOrigins.includes(origin)) {
327
+ loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
328
+ ctx.set("Access-Control-Allow-Origin", origin);
329
+ } else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
330
+ loggy.info("No origin header or * in allowed origins, setting to *");
331
+ ctx.set("Access-Control-Allow-Origin", "*");
332
+ }
333
+ } else {
334
+ loggy.info("No CORS middleware set, setting to request origin or *");
268
335
  ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
269
336
  }
270
337
  return;
@@ -284,15 +351,27 @@ const middleware = async (ctx, next) => {
284
351
  cacheHeadersAllowList,
285
352
  cacheHeadersDenyList
286
353
  );
354
+ let setCache = true;
355
+ if (corsMiddleware) {
356
+ if (allowedOrigins.includes(origin)) ;
357
+ else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
358
+ else {
359
+ setCache = false;
360
+ }
361
+ }
287
362
  if (ctx.body instanceof Stream__default.default) {
288
363
  const buf = await streamToBuffer(ctx.body);
289
364
  const contentEncoding = ctx.response.headers["content-encoding"];
290
365
  const decompressed = await decompressBuffer(buf, contentEncoding);
291
366
  const responseText = decodeBufferToText(decompressed);
292
- await cacheStore.set(key, { body: responseText, headers: headersToStore });
367
+ if (setCache) {
368
+ await cacheStore.set(key, { body: responseText, headers: headersToStore });
369
+ }
293
370
  ctx.body = buf;
294
371
  } else {
295
- await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
372
+ if (setCache) {
373
+ await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
374
+ }
296
375
  }
297
376
  }
298
377
  };
@@ -313,6 +392,7 @@ const config = {
313
392
  allowStale: false,
314
393
  cacheableRoutes: [],
315
394
  provider: "memory",
395
+ excludeRoutes: [],
316
396
  redisConfig: env("REDIS_URL"),
317
397
  redisClusterNodes: [],
318
398
  redisClusterOptions: {},
@@ -321,7 +401,8 @@ const config = {
321
401
  cacheHeadersAllowList: [],
322
402
  cacheAuthorizedRequests: false,
323
403
  cacheGetTimeoutInMs: 1e3,
324
- autoPurgeCache: true
404
+ autoPurgeCache: true,
405
+ autoPurgeCacheOnStart: true
325
406
  }),
326
407
  validator: (config2) => {
327
408
  if (typeof config2.debug !== "boolean") {
@@ -342,6 +423,9 @@ const config = {
342
423
  if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
343
424
  throw new Error(`Invalid config: cacheableRoutes must be an string array`);
344
425
  }
426
+ if (!Array.isArray(config2.excludeRoutes) || config2.excludeRoutes.some((item) => typeof item !== "string")) {
427
+ throw new Error(`Invalid config: excludeRoutes must be a string array`);
428
+ }
345
429
  if (typeof config2.provider !== "string") {
346
430
  throw new Error(`Invalid config: provider must be a string`);
347
431
  }
@@ -382,6 +466,9 @@ const config = {
382
466
  if (typeof config2.autoPurgeCache !== "boolean") {
383
467
  throw new Error(`Invalid config: autoPurgeCache must be a boolean`);
384
468
  }
469
+ if (typeof config2.autoPurgeCacheOnStart !== "boolean") {
470
+ throw new Error(`Invalid config: autoPurgeCacheOnStart must be a boolean`);
471
+ }
385
472
  }
386
473
  };
387
474
  const contentTypes = {};
@@ -591,6 +678,7 @@ class RedisCacheProvider {
591
678
  this.cacheGetTimeoutInMs = Number(
592
679
  this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
593
680
  );
681
+ this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
594
682
  if (redisClusterNodes.length) {
595
683
  const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
596
684
  if (!redisClusterOptions["redisOptions"]) {
@@ -640,8 +728,9 @@ class RedisCacheProvider {
640
728
  async del(key) {
641
729
  if (!this.ready) return null;
642
730
  try {
643
- loggy.info(`Redis PURGING KEY: ${key}`);
644
- await this.client.del(key);
731
+ const relativeKey = key.slice(this.keyPrefix.length);
732
+ loggy.info(`Redis PURGING KEY: ${relativeKey}`);
733
+ await this.client.del(relativeKey);
645
734
  return true;
646
735
  } catch (error) {
647
736
  loggy.error(`Redis del error: ${error}`);
@@ -651,7 +740,7 @@ class RedisCacheProvider {
651
740
  async keys() {
652
741
  if (!this.ready) return null;
653
742
  try {
654
- const keys = await this.client.keys("*");
743
+ const keys = await this.client.keys(`${this.keyPrefix}*`);
655
744
  return keys;
656
745
  } catch (error) {
657
746
  loggy.error(`Redis keys error: ${error}`);
@@ -661,6 +750,14 @@ class RedisCacheProvider {
661
750
  async reset() {
662
751
  if (!this.ready) return null;
663
752
  try {
753
+ if (this.keyPrefix) {
754
+ loggy.info(`Redis FLUSHING NAMESPACE: ${this.keyPrefix}`);
755
+ const keys = await this.keys();
756
+ if (!keys) return null;
757
+ const toDelete = keys.filter((key) => key.startsWith(this.keyPrefix));
758
+ await Promise.all(toDelete.map((key) => this.del(key)));
759
+ return true;
760
+ }
664
761
  loggy.info(`Redis FLUSHING ALL KEYS`);
665
762
  await this.client.flushdb();
666
763
  return true;
@@ -69,6 +69,7 @@ const bootstrap = ({ strapi: strapi2 }) => {
69
69
  try {
70
70
  const cacheService = strapi2.plugin("strapi-cache").services.service;
71
71
  const autoPurgeCache = strapi2.plugin("strapi-cache").config("autoPurgeCache");
72
+ const autoPurgeCacheOnStart = strapi2.plugin("strapi-cache").config("autoPurgeCacheOnStart");
72
73
  const cacheStore = cacheService.getCacheInstance();
73
74
  if (!cacheStore) {
74
75
  loggy.error("Plugin could not be initialized");
@@ -91,6 +92,13 @@ const bootstrap = ({ strapi: strapi2 }) => {
91
92
  }
92
93
  });
93
94
  }
95
+ if (autoPurgeCacheOnStart) {
96
+ cacheStore.reset().then(() => {
97
+ loggy.info("Cache purged successfully");
98
+ }).catch((error) => {
99
+ loggy.error(`Error purging cache on start: ${error.message}`);
100
+ });
101
+ }
94
102
  } catch (error) {
95
103
  loggy.error("Plugin could not be initialized");
96
104
  return;
@@ -175,6 +183,7 @@ function getCacheHeaderConfig() {
175
183
  const middleware$1 = async (ctx, next) => {
176
184
  const cacheService = strapi.plugin("strapi-cache").services.service;
177
185
  const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
186
+ const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
178
187
  const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
179
188
  const cacheStore = cacheService.getCacheInstance();
180
189
  const { url } = ctx.request;
@@ -182,6 +191,12 @@ const middleware$1 = async (ctx, next) => {
182
191
  const cacheEntry = await cacheStore.get(key);
183
192
  const cacheControlHeader = ctx.request.headers["cache-control"];
184
193
  const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
194
+ const routeIsExcluded = excludeRoutes.some((route) => url.startsWith(route));
195
+ if (routeIsExcluded) {
196
+ loggy.info(`Route excluded from cache: ${url}`);
197
+ await next();
198
+ return;
199
+ }
185
200
  const routeIsCachable = cacheableRoutes.some((route) => url.startsWith(route)) || cacheableRoutes.length === 0 && url.startsWith("/api");
186
201
  const authorizationHeader = ctx.request.headers["authorization"];
187
202
  if (authorizationHeader && !cacheAuthorizedRequests) {
@@ -189,12 +204,32 @@ const middleware$1 = async (ctx, next) => {
189
204
  await next();
190
205
  return;
191
206
  }
207
+ const middlewaresConfig = strapi.config.get("middlewares");
208
+ const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
209
+ const corsConfig = corsMiddleware?.config;
210
+ const origin = ctx?.request?.headers?.origin;
211
+ let allowedOrigins = corsConfig?.origin ?? "*";
212
+ if (typeof allowedOrigins === "string") {
213
+ allowedOrigins = [allowedOrigins];
214
+ }
192
215
  if (cacheEntry && !noCache) {
193
216
  loggy.info(`HIT with key: ${key}`);
194
217
  ctx.status = 200;
195
218
  ctx.body = cacheEntry.body;
196
219
  if (cacheHeaders) {
197
220
  ctx.set(cacheEntry.headers);
221
+ }
222
+ if (corsMiddleware) {
223
+ loggy.info("CORS middleware is set, checking allowed origins");
224
+ if (allowedOrigins.includes(origin)) {
225
+ loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
226
+ ctx.set("Access-Control-Allow-Origin", origin);
227
+ } else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
228
+ loggy.info("No origin header or * in allowed origins, setting to *");
229
+ ctx.set("Access-Control-Allow-Origin", "*");
230
+ }
231
+ } else {
232
+ loggy.info("No CORS middleware set, setting to request origin or *");
198
233
  ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
199
234
  }
200
235
  return;
@@ -208,15 +243,27 @@ const middleware$1 = async (ctx, next) => {
208
243
  cacheHeadersAllowList,
209
244
  cacheHeadersDenyList
210
245
  );
246
+ let setCache = true;
247
+ if (corsMiddleware) {
248
+ if (allowedOrigins.includes(origin)) ;
249
+ else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
250
+ else {
251
+ setCache = false;
252
+ }
253
+ }
211
254
  if (ctx.body instanceof Stream) {
212
255
  const buf = await streamToBuffer(ctx.body);
213
256
  const contentEncoding = ctx.response.headers["content-encoding"];
214
257
  const decompressed = await decompressBuffer(buf, contentEncoding);
215
258
  const responseText = decodeBufferToText(decompressed);
216
- await cacheStore.set(key, { body: responseText, headers: headersToStore });
259
+ if (setCache) {
260
+ await cacheStore.set(key, { body: responseText, headers: headersToStore });
261
+ }
217
262
  ctx.body = buf;
218
263
  } else {
219
- await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
264
+ if (setCache) {
265
+ await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
266
+ }
220
267
  }
221
268
  }
222
269
  };
@@ -255,12 +302,32 @@ const middleware = async (ctx, next) => {
255
302
  await next();
256
303
  return;
257
304
  }
305
+ const middlewaresConfig = strapi.config.get("middlewares");
306
+ const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
307
+ const corsConfig = corsMiddleware?.config;
308
+ const origin = ctx?.request?.headers?.origin;
309
+ let allowedOrigins = corsConfig?.origin ?? "*";
310
+ if (typeof allowedOrigins === "string") {
311
+ allowedOrigins = [allowedOrigins];
312
+ }
258
313
  if (cacheEntry && !noCache) {
259
314
  loggy.info(`HIT with key: ${key}`);
260
315
  ctx.status = 200;
261
316
  ctx.body = cacheEntry.body;
262
317
  if (cacheHeaders) {
263
318
  ctx.set(cacheEntry.headers);
319
+ }
320
+ if (corsMiddleware) {
321
+ loggy.info("CORS middleware is set, checking allowed origins");
322
+ if (allowedOrigins.includes(origin)) {
323
+ loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
324
+ ctx.set("Access-Control-Allow-Origin", origin);
325
+ } else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
326
+ loggy.info("No origin header or * in allowed origins, setting to *");
327
+ ctx.set("Access-Control-Allow-Origin", "*");
328
+ }
329
+ } else {
330
+ loggy.info("No CORS middleware set, setting to request origin or *");
264
331
  ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
265
332
  }
266
333
  return;
@@ -280,15 +347,27 @@ const middleware = async (ctx, next) => {
280
347
  cacheHeadersAllowList,
281
348
  cacheHeadersDenyList
282
349
  );
350
+ let setCache = true;
351
+ if (corsMiddleware) {
352
+ if (allowedOrigins.includes(origin)) ;
353
+ else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
354
+ else {
355
+ setCache = false;
356
+ }
357
+ }
283
358
  if (ctx.body instanceof Stream) {
284
359
  const buf = await streamToBuffer(ctx.body);
285
360
  const contentEncoding = ctx.response.headers["content-encoding"];
286
361
  const decompressed = await decompressBuffer(buf, contentEncoding);
287
362
  const responseText = decodeBufferToText(decompressed);
288
- await cacheStore.set(key, { body: responseText, headers: headersToStore });
363
+ if (setCache) {
364
+ await cacheStore.set(key, { body: responseText, headers: headersToStore });
365
+ }
289
366
  ctx.body = buf;
290
367
  } else {
291
- await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
368
+ if (setCache) {
369
+ await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
370
+ }
292
371
  }
293
372
  }
294
373
  };
@@ -309,6 +388,7 @@ const config = {
309
388
  allowStale: false,
310
389
  cacheableRoutes: [],
311
390
  provider: "memory",
391
+ excludeRoutes: [],
312
392
  redisConfig: env("REDIS_URL"),
313
393
  redisClusterNodes: [],
314
394
  redisClusterOptions: {},
@@ -317,7 +397,8 @@ const config = {
317
397
  cacheHeadersAllowList: [],
318
398
  cacheAuthorizedRequests: false,
319
399
  cacheGetTimeoutInMs: 1e3,
320
- autoPurgeCache: true
400
+ autoPurgeCache: true,
401
+ autoPurgeCacheOnStart: true
321
402
  }),
322
403
  validator: (config2) => {
323
404
  if (typeof config2.debug !== "boolean") {
@@ -338,6 +419,9 @@ const config = {
338
419
  if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
339
420
  throw new Error(`Invalid config: cacheableRoutes must be an string array`);
340
421
  }
422
+ if (!Array.isArray(config2.excludeRoutes) || config2.excludeRoutes.some((item) => typeof item !== "string")) {
423
+ throw new Error(`Invalid config: excludeRoutes must be a string array`);
424
+ }
341
425
  if (typeof config2.provider !== "string") {
342
426
  throw new Error(`Invalid config: provider must be a string`);
343
427
  }
@@ -378,6 +462,9 @@ const config = {
378
462
  if (typeof config2.autoPurgeCache !== "boolean") {
379
463
  throw new Error(`Invalid config: autoPurgeCache must be a boolean`);
380
464
  }
465
+ if (typeof config2.autoPurgeCacheOnStart !== "boolean") {
466
+ throw new Error(`Invalid config: autoPurgeCacheOnStart must be a boolean`);
467
+ }
381
468
  }
382
469
  };
383
470
  const contentTypes = {};
@@ -587,6 +674,7 @@ class RedisCacheProvider {
587
674
  this.cacheGetTimeoutInMs = Number(
588
675
  this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
589
676
  );
677
+ this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
590
678
  if (redisClusterNodes.length) {
591
679
  const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
592
680
  if (!redisClusterOptions["redisOptions"]) {
@@ -636,8 +724,9 @@ class RedisCacheProvider {
636
724
  async del(key) {
637
725
  if (!this.ready) return null;
638
726
  try {
639
- loggy.info(`Redis PURGING KEY: ${key}`);
640
- await this.client.del(key);
727
+ const relativeKey = key.slice(this.keyPrefix.length);
728
+ loggy.info(`Redis PURGING KEY: ${relativeKey}`);
729
+ await this.client.del(relativeKey);
641
730
  return true;
642
731
  } catch (error) {
643
732
  loggy.error(`Redis del error: ${error}`);
@@ -647,7 +736,7 @@ class RedisCacheProvider {
647
736
  async keys() {
648
737
  if (!this.ready) return null;
649
738
  try {
650
- const keys = await this.client.keys("*");
739
+ const keys = await this.client.keys(`${this.keyPrefix}*`);
651
740
  return keys;
652
741
  } catch (error) {
653
742
  loggy.error(`Redis keys error: ${error}`);
@@ -657,6 +746,14 @@ class RedisCacheProvider {
657
746
  async reset() {
658
747
  if (!this.ready) return null;
659
748
  try {
749
+ if (this.keyPrefix) {
750
+ loggy.info(`Redis FLUSHING NAMESPACE: ${this.keyPrefix}`);
751
+ const keys = await this.keys();
752
+ if (!keys) return null;
753
+ const toDelete = keys.filter((key) => key.startsWith(this.keyPrefix));
754
+ await Promise.all(toDelete.map((key) => this.del(key)));
755
+ return true;
756
+ }
660
757
  loggy.info(`Redis FLUSHING ALL KEYS`);
661
758
  await this.client.flushdb();
662
759
  return true;
@@ -9,6 +9,7 @@ declare const _default: {
9
9
  allowStale: boolean;
10
10
  cacheableRoutes: any[];
11
11
  provider: string;
12
+ excludeRoutes: any[];
12
13
  redisConfig: any;
13
14
  redisClusterNodes: any[];
14
15
  redisClusterOptions: {};
@@ -18,6 +19,7 @@ declare const _default: {
18
19
  cacheAuthorizedRequests: boolean;
19
20
  cacheGetTimeoutInMs: number;
20
21
  autoPurgeCache: boolean;
22
+ autoPurgeCacheOnStart: boolean;
21
23
  };
22
24
  validator: (config: any) => void;
23
25
  };
@@ -18,6 +18,7 @@ declare const _default: {
18
18
  allowStale: boolean;
19
19
  cacheableRoutes: any[];
20
20
  provider: string;
21
+ excludeRoutes: any[];
21
22
  redisConfig: any;
22
23
  redisClusterNodes: any[];
23
24
  redisClusterOptions: {};
@@ -27,6 +28,7 @@ declare const _default: {
27
28
  cacheAuthorizedRequests: boolean;
28
29
  cacheGetTimeoutInMs: number;
29
30
  autoPurgeCache: boolean;
31
+ autoPurgeCacheOnStart: boolean;
30
32
  };
31
33
  validator: (config: any) => void;
32
34
  };
@@ -5,6 +5,7 @@ export declare class RedisCacheProvider implements CacheProvider {
5
5
  private initialized;
6
6
  private client;
7
7
  private cacheGetTimeoutInMs;
8
+ private keyPrefix;
8
9
  constructor(strapi: Core.Strapi);
9
10
  init(): void;
10
11
  get ready(): boolean;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.7-rc.2",
2
+ "version": "1.5.8",
3
3
  "keywords": [
4
4
  "strapi cache",
5
5
  "strapi rest cache",
@@ -42,8 +42,7 @@
42
42
  "ioredis": "^5.6.1",
43
43
  "lru-cache": "^11.1.0",
44
44
  "raw-body": "^3.0.0",
45
- "react-intl": "^7.1.10",
46
- "strapi-cache": "file:.yalc/strapi-cache"
45
+ "react-intl": "^7.1.10"
47
46
  },
48
47
  "devDependencies": {
49
48
  "@strapi/sdk-plugin": "^5.3.2",