strapi-cache 1.9.0 → 1.10.0

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
@@ -72,6 +72,7 @@ Full configuration example:
72
72
  size: 1024 * 1024 * 1024, // Maximum size of the cache (1 GB) (only for memory cache)
73
73
  allowStale: false, // Allow stale cache items (only for memory cache)
74
74
  cacheableRoutes: ['/api/products', '/api/categories'], // Caches routes which start with these paths (if empty array, all '/api' routes are cached)
75
+ keyGenerator: (ctx) => `${ctx.request.method}:${ctx.request.url}`, // Optional custom cache key for REST requests; receives koa ctx
75
76
  // cacheableEntities: ['products', 'categories'], // (Optional) Specify which entities to cache. When set, only these entities will be cached (ignores cacheableRoutes). If not set (undefined), cacheableRoutes logic is used
76
77
  excludeRoutes: ['/api/products/private'], // Exclude routes which start with these paths from being cached (takes precedence over cacheableRoutes). **Note:** `excludeRoutes` takes precedence over `cacheableRoutes`.
77
78
  provider: 'memory', // Cache provider ('memory', 'redis' or 'valkey')
@@ -109,6 +110,7 @@ Possible configuration keys are listed below; omitted keys keep the plugin defau
109
110
  | `size` | Approximate max total size in bytes (in-memory provider only) | Positive integer (default: `10485760`, i.e. 10 MB) |
110
111
  | `allowStale` | Whether stale entries may be returned (in-memory provider only) | `true` or `false` (default: `false`) |
111
112
  | `cacheableRoutes` | Only URLs starting with one of these paths are cached; if empty, every URL under the REST API prefix matches | Array of path prefix strings (default: `[]` meaning “all API routes”) |
113
+ | `keyGenerator` | Custom function to build REST cache keys; receives Koa `ctx`; when omitted, default key is `${method}:${url}` | Function `(ctx) => string` (default: unset) |
112
114
  | `cacheableEntities` | If non-empty, only these API “entity” segments are cached; **when set, this drives eligibility instead of** `cacheableRoutes` | Array of strings (e.g. collection/table names), or omit / leave empty to use `cacheableRoutes` |
113
115
  | `excludeRoutes` | URLs starting with any of these prefixes are **never** cached; evaluated before `cacheableRoutes` / entities | Array of path prefix strings (default: `[]`) |
114
116
  | `cacheHeaders` | Store and replay response headers with the body | `true` or `false` (default: `true`) |
@@ -152,7 +152,13 @@ const bootstrap = ({ strapi: strapi2 }) => {
152
152
  loggy.info("Plugin initialized");
153
153
  strapi2.admin.services.permission.actionProvider.registerMany(actions);
154
154
  };
155
- const generateCacheKey = (context) => {
155
+ const generateCacheKey = (context, keyGenerator) => {
156
+ if (typeof keyGenerator === "function") {
157
+ const customKey = keyGenerator(context);
158
+ if (typeof customKey === "string") {
159
+ return customKey;
160
+ }
161
+ }
156
162
  const { url } = context.request;
157
163
  const { method } = context.request;
158
164
  return `${method}:${url}`;
@@ -238,10 +244,11 @@ const middleware$1 = async (ctx, next) => {
238
244
  const cacheableEntities = strapi.plugin("strapi-cache").config("cacheableEntities");
239
245
  const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
240
246
  const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
247
+ const keyGenerator = strapi.plugin("strapi-cache").config("keyGenerator");
241
248
  const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
242
249
  const cacheStore = cacheService.getCacheInstance();
243
250
  const { url } = ctx.request;
244
- const key = generateCacheKey(ctx);
251
+ const key = generateCacheKey(ctx, keyGenerator);
245
252
  const cacheEntry = await cacheStore.get(key);
246
253
  const cacheControlHeader = ctx.request.headers["cache-control"];
247
254
  const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
@@ -533,6 +540,7 @@ const config = {
533
540
  size: 1024 * 1024 * 10,
534
541
  allowStale: false,
535
542
  cacheableRoutes: [],
543
+ keyGenerator: void 0,
536
544
  provider: "memory",
537
545
  excludeRoutes: [],
538
546
  redisConfig: env("REDIS_URL"),
@@ -568,6 +576,9 @@ const config = {
568
576
  if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
569
577
  throw new Error(`Invalid config: cacheableRoutes must be an string array`);
570
578
  }
579
+ if (config2.keyGenerator !== void 0 && typeof config2.keyGenerator !== "function") {
580
+ throw new Error(`Invalid config: keyGenerator must be a function`);
581
+ }
571
582
  if (config2.cacheableEntities !== void 0 && (!Array.isArray(config2.cacheableEntities) || config2.cacheableEntities.some((item) => typeof item !== "string"))) {
572
583
  throw new Error(`Invalid config: cacheableEntities must be a string array`);
573
584
  }
@@ -148,7 +148,13 @@ const bootstrap = ({ strapi: strapi2 }) => {
148
148
  loggy.info("Plugin initialized");
149
149
  strapi2.admin.services.permission.actionProvider.registerMany(actions);
150
150
  };
151
- const generateCacheKey = (context) => {
151
+ const generateCacheKey = (context, keyGenerator) => {
152
+ if (typeof keyGenerator === "function") {
153
+ const customKey = keyGenerator(context);
154
+ if (typeof customKey === "string") {
155
+ return customKey;
156
+ }
157
+ }
152
158
  const { url } = context.request;
153
159
  const { method } = context.request;
154
160
  return `${method}:${url}`;
@@ -234,10 +240,11 @@ const middleware$1 = async (ctx, next) => {
234
240
  const cacheableEntities = strapi.plugin("strapi-cache").config("cacheableEntities");
235
241
  const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
236
242
  const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
243
+ const keyGenerator = strapi.plugin("strapi-cache").config("keyGenerator");
237
244
  const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
238
245
  const cacheStore = cacheService.getCacheInstance();
239
246
  const { url } = ctx.request;
240
- const key = generateCacheKey(ctx);
247
+ const key = generateCacheKey(ctx, keyGenerator);
241
248
  const cacheEntry = await cacheStore.get(key);
242
249
  const cacheControlHeader = ctx.request.headers["cache-control"];
243
250
  const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
@@ -529,6 +536,7 @@ const config = {
529
536
  size: 1024 * 1024 * 10,
530
537
  allowStale: false,
531
538
  cacheableRoutes: [],
539
+ keyGenerator: void 0,
532
540
  provider: "memory",
533
541
  excludeRoutes: [],
534
542
  redisConfig: env("REDIS_URL"),
@@ -564,6 +572,9 @@ const config = {
564
572
  if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
565
573
  throw new Error(`Invalid config: cacheableRoutes must be an string array`);
566
574
  }
575
+ if (config2.keyGenerator !== void 0 && typeof config2.keyGenerator !== "function") {
576
+ throw new Error(`Invalid config: keyGenerator must be a function`);
577
+ }
567
578
  if (config2.cacheableEntities !== void 0 && (!Array.isArray(config2.cacheableEntities) || config2.cacheableEntities.some((item) => typeof item !== "string"))) {
568
579
  throw new Error(`Invalid config: cacheableEntities must be a string array`);
569
580
  }
@@ -8,6 +8,7 @@ declare const _default: {
8
8
  size: number;
9
9
  allowStale: boolean;
10
10
  cacheableRoutes: any[];
11
+ keyGenerator: any;
11
12
  provider: string;
12
13
  excludeRoutes: any[];
13
14
  redisConfig: any;
@@ -17,6 +17,7 @@ declare const _default: {
17
17
  size: number;
18
18
  allowStale: boolean;
19
19
  cacheableRoutes: any[];
20
+ keyGenerator: any;
20
21
  provider: string;
21
22
  excludeRoutes: any[];
22
23
  redisConfig: any;
@@ -1,6 +1,7 @@
1
1
  import { Core } from '@strapi/strapi';
2
2
  import { Context } from 'koa';
3
- export declare const generateCacheKey: (context: Context) => string;
3
+ export type CacheKeyGenerator = (context: Context) => string;
4
+ export declare const generateCacheKey: (context: Context, keyGenerator?: CacheKeyGenerator) => string;
4
5
  export declare const generateGraphqlCacheKey: (payload: string, method?: 'GET' | 'POST', rootFields?: string[], strapi?: Core.Strapi) => string;
5
6
  export declare const escapeRegExp: (s: string) => string;
6
7
  export declare const generateEntityKey: (url: string, restApiPrefix: string) => string;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.9.0",
2
+ "version": "1.10.0",
3
3
  "keywords": [
4
4
  "strapi cache",
5
5
  "strapi rest cache",
@@ -35,7 +35,7 @@
35
35
  "test:ts:front": "run -T tsc -p admin/tsconfig.json",
36
36
  "test:ts:back": "run -T tsc -p server/tsconfig.json",
37
37
  "test": "vitest run --exclude 'test/integration/**'",
38
- "test:integration": "npm run build && rm -rf playground/.cache && mkdir -p playground/.yalc/strapi-cache && cp -r dist package.json LICENSE README.md playground/.yalc/strapi-cache/ && cd playground && npm install && npm run build && cd .. && NODE_ENV=test jest --config jest.integration.config.js --runInBand --forceExit --detectOpenHandles",
38
+ "test:integration": "npm run build && rm -rf playground/.cache playground/node_modules playground/.yalc && mkdir -p playground/.yalc/strapi-cache && cp -r dist package.json LICENSE README.md playground/.yalc/strapi-cache/ && node -e \"const fs=require('fs');const p='playground/.yalc/strapi-cache/package.json';const pkg=JSON.parse(fs.readFileSync(p,'utf8'));delete pkg.devDependencies;delete pkg.optionalDependencies;delete pkg.scripts;fs.writeFileSync(p,JSON.stringify(pkg,null,2));\" && cd playground && npm ci --ignore-scripts && npm rebuild better-sqlite3 @swc/core sharp --foreground-scripts && npm run build && cd .. && NODE_ENV=test jest --config jest.integration.config.js --runInBand --forceExit --detectOpenHandles",
39
39
  "test:all": "npm test -- --run && npm run test:integration"
40
40
  },
41
41
  "dependencies": {