strapi-cache 1.11.1 → 1.12.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,13 +72,14 @@ 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 + GraphQL requests; receives koa ctx
75
+ // keyGenerator: (ctx, defaultKey) => `custom:${defaultKey}`, // (Optional) custom cache key for REST + GraphQL requests; receives koa ctx and the generated default key
76
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
77
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`.
78
78
  provider: 'memory', // Cache provider ('memory', 'redis' or 'valkey')
79
79
  redisConfig: env('REDIS_URL', 'redis://localhost:6379'), // Redis/Valkey config: string or object. See https://github.com/redis/ioredis (Redis) or https://github.com/valkey-io/iovalkey (Valkey)
80
80
  redisClusterNodes: [], // If provided any cluster node (this list is not empty), initialize cluster client. Each object must have keys 'host' and 'port'
81
81
  redisClusterOptions: {}, // Options for ioredis redis cluster client. redisOptions key is taken from redisConfig parameter above if not set here. See https://github.com/redis/ioredis for references
82
+ redisScanDeleteCount: 100, // COUNT hint for SCAN when purging keys (Redis/Valkey only)
82
83
  cacheHeaders: true, // Plugin also stores response headers in the cache (set to false if you don't want to cache headers)
83
84
  cacheHeadersDenyList: ['access-control-allow-origin', 'content-encoding'], // Headers to exclude from the cache (must be lowercase, if empty array, no headers are excluded, cacheHeaders must be true)
84
85
  cacheHeadersAllowList: ['content-type', 'content-security-policy'], // Headers to include in the cache (must be lowercase, if empty array, all headers are cached, cacheHeaders must be true)
@@ -97,32 +98,32 @@ Full configuration example:
97
98
 
98
99
  Possible configuration keys are listed below; omitted keys keep the plugin defaults.
99
100
 
100
- | Key | Description | Possible values |
101
- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
102
- | `debug` | Log cache decisions and operations to the server console | `true` or `false` (default: `false`) |
103
- | `provider` | Where entries are stored | `'memory'`, `'redis'`, or `'valkey'` (default: `'memory'`) |
104
- | `redisConfig` | Redis/Valkey connection: URL string or client options passed to ioredis/iovalkey | String or object; **required** when `provider` is `'redis'` or `'valkey'`. Default: value of `REDIS_URL` from the environment |
105
- | `redisClusterNodes` | Seed nodes for Redis cluster mode; non-empty list switches to a cluster client | Array of `{ host: string, port: number }` (default: `[]`) |
106
- | `redisClusterOptions` | Options for the cluster client (e.g. `scaleReads`); `redisOptions` often come from `redisConfig` | Object (default: `{}`) |
107
- | `redisScanDeleteCount` | `COUNT` hint for `SCAN` when purging keys (Redis/Valkey) | Positive number (default: `100`) |
108
- | `max` | Maximum number of entries (in-memory provider only) | Positive integer (default: `1000`) |
109
- | `ttl` | Time-to-live for each entry, in milliseconds | Non-negative number (default: `3600000`, i.e. 1 hour) |
110
- | `size` | Approximate max total size in bytes (in-memory provider only) | Positive integer (default: `10485760`, i.e. 10 MB) |
111
- | `allowStale` | Whether stale entries may be returned (in-memory provider only) | `true` or `false` (default: `false`) |
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}`; for graphql, if set, the ctx gets additional fields: `rootFields` and `operationName` which may be useful for invalidation logic | Function `(ctx) => string` (default: unset) |
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` |
115
- | `excludeRoutes` | URLs starting with any of these prefixes are **never** cached; evaluated before `cacheableRoutes` / entities | Array of path prefix strings (default: `[]`) |
116
- | `cacheHeaders` | Store and replay response headers with the body | `true` or `false` (default: `true`) |
117
- | `cacheHeadersDenyList` | Header names (lowercase) to strip when `cacheHeaders` is `true` | Array of strings (default: `[]`) |
118
- | `cacheHeadersAllowList` | If non-empty, only these header names (lowercase) are stored; if empty, all headers are stored (subject to deny list) | Array of strings (default: `[]`) |
119
- | `cacheAuthorizedRequests` | Whether to cache requests that include an `Authorization` header | `true` or `false` (default: `false`) |
120
- | `cacheGetTimeoutInMs` | Max time to wait for a cache read before treating it as a miss | Milliseconds (default: `1000`) |
121
- | `autoPurgeCache` | Invalidate relevant REST cache entries after content create/update/delete | `true` or `false` (default: `true`) |
122
- | `autoPurgeGraphQL` | Invalidate GraphQL cache after content create/update/delete | `true` or `false` (default: `false` if omitted; set `true` to enable) |
123
- | `autoPurgeCacheOnStart` | Clear the cache when Strapi starts | `true` or `false` (default: `true`) |
124
- | `disableAdminPopups` | Turn off admin UI notifications for cache actions | `true` or `false` (default: `false`) |
125
- | `disableAdminButtons` | Hide manual purge controls in the admin (list and edit views), either globally or for specific REST content type paths | `true`, `false`, or an array of paths such as `['/api/products', '/api/tags']` (default: `false`) |
101
+ | Key | Description | Possible values |
102
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
103
+ | `debug` | Log cache decisions and operations to the server console | `true` or `false` (default: `false`) |
104
+ | `provider` | Where entries are stored | `'memory'`, `'redis'`, or `'valkey'` (default: `'memory'`) |
105
+ | `redisConfig` | Redis/Valkey connection: URL string or client options passed to ioredis/iovalkey | String or object; **required** when `provider` is `'redis'` or `'valkey'`. Default: value of `REDIS_URL` from the environment |
106
+ | `redisClusterNodes` | Seed nodes for Redis cluster mode; non-empty list switches to a cluster client | Array of `{ host: string, port: number }` (default: `[]`) |
107
+ | `redisClusterOptions` | Options for the cluster client (e.g. `scaleReads`); `redisOptions` often come from `redisConfig` | Object (default: `{}`) |
108
+ | `redisScanDeleteCount` | `COUNT` hint for `SCAN` when purging keys (Redis/Valkey) | Positive number (default: `100`) |
109
+ | `max` | Maximum number of entries (in-memory provider only) | Positive integer (default: `1000`) |
110
+ | `ttl` | Time-to-live for each entry, in milliseconds | Non-negative number (default: `3600000`, i.e. 1 hour) |
111
+ | `size` | Approximate max total size in bytes (in-memory provider only) | Positive integer (default: `10485760`, i.e. 10 MB) |
112
+ | `allowStale` | Whether stale entries may be returned (in-memory provider only) | `true` or `false` (default: `false`) |
113
+ | `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”) |
114
+ | `keyGenerator` | Custom function to build cache keys; receives Koa `ctx` and the generated default key (for REST: `${method}:${url}`, for GraphQL: a hash-based default key); when omitted, the plugin uses the generated default key; for GraphQL, if set, the ctx gets additional fields: `rootFields` and `operationName` which may be useful for invalidation logic | Function `(ctx, defaultKey) => string` (default: unset) |
115
+ | `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` |
116
+ | `excludeRoutes` | URLs starting with any of these prefixes are **never** cached; evaluated before `cacheableRoutes` / entities | Array of path prefix strings (default: `[]`) |
117
+ | `cacheHeaders` | Store and replay response headers with the body | `true` or `false` (default: `true`) |
118
+ | `cacheHeadersDenyList` | Header names (lowercase) to strip when `cacheHeaders` is `true` | Array of strings (default: `[]`) |
119
+ | `cacheHeadersAllowList` | If non-empty, only these header names (lowercase) are stored; if empty, all headers are stored (subject to deny list) | Array of strings (default: `[]`) |
120
+ | `cacheAuthorizedRequests` | Whether to cache requests that include an `Authorization` header | `true` or `false` (default: `false`) |
121
+ | `cacheGetTimeoutInMs` | Max time to wait for a cache read before treating it as a miss | Milliseconds (default: `1000`) |
122
+ | `autoPurgeCache` | Invalidate relevant REST cache entries after content create/update/delete | `true` or `false` (default: `true`) |
123
+ | `autoPurgeGraphQL` | Invalidate GraphQL cache after content create/update/delete | `true` or `false` (default: `false` if omitted; set `true` to enable) |
124
+ | `autoPurgeCacheOnStart` | Clear the cache when Strapi starts | `true` or `false` (default: `true`) |
125
+ | `disableAdminPopups` | Turn off admin UI notifications for cache actions | `true` or `false` (default: `false`) |
126
+ | `disableAdminButtons` | Hide manual purge controls in the admin (list and edit views), either globally or for specific REST content type paths | `true`, `false`, or an array of paths such as `['/api/products', '/api/tags']` (default: `false`) |
126
127
 
127
128
  ## 🔍 Routes
128
129
 
@@ -139,7 +140,7 @@ All of these routes are protected by the policies `admin::isAuthenticatedAdmin`
139
140
 
140
141
  - **Storage**: The plugin keeps cached data in memory, Redis or Valkey, depending on the configuration.
141
142
  - **Packages**: Uses [lru-cache](https://github.com/isaacs/node-lru-cache) for in-memory cache. Uses [ioredis](https://github.com/redis/ioredis) for Redis and [iovalkey](https://github.com/valkey-io/iovalkey) for Valkey caching.
142
- - **Automatic Invalidation**: When `autoPurgeCache` is enabled (default), relevant REST cache entries are invalidated on content create, update, or delete. When `autoPurgeGraphQL` is enabled, GraphQL cache is invalidated the same way (it is off unless you set it in config).
143
+ - **Automatic Invalidation**: When `autoPurgeCache` is enabled (default), relevant REST cache entries are invalidated on content create, update, or delete. REST invalidation honors `excludeRoutes`, `cacheableEntities`, and `cacheableRoutes`, and runs in the background so content writes do not wait for cache scans to finish. When `autoPurgeGraphQL` is enabled, GraphQL cache is invalidated the same way (it is off unless you set it in config).
143
144
  - **`no-cache` Header Support**: Respects the `no-cache` header, letting you skip the cache by setting `Cache-Control: no-cache` in your request.
144
145
  - **Default Cached Requests**: By default, caches all GET requests to `/api` (or whatever prefix you defined) and POST requests to `/graphql` or predefined graphql route from graphql plugin config. You can customize which routes or entities to cache using `cacheableRoutes` or `cacheableEntities` config options.
145
146
 
@@ -1,2 +1,3 @@
1
- declare const PluginIcon: () => import("react/jsx-runtime").JSX.Element;
1
+ /// <reference types="react" />
2
+ declare const PluginIcon: () => import("react").JSX.Element;
2
3
  export { PluginIcon };
@@ -1,2 +1,3 @@
1
- declare function PurgeCacheButton(): import("react/jsx-runtime").JSX.Element | null;
1
+ /// <reference types="react" />
2
+ declare function PurgeCacheButton(): import("react").JSX.Element | null;
2
3
  export default PurgeCacheButton;
@@ -1,2 +1,3 @@
1
- declare function PurgeEntityButton(): import("react/jsx-runtime").JSX.Element | null;
1
+ /// <reference types="react" />
2
+ declare function PurgeEntityButton(): import("react").JSX.Element | null;
2
3
  export default PurgeEntityButton;
@@ -1,3 +1,4 @@
1
+ /// <reference types="react" />
1
2
  export type PurgeProps = {
2
3
  buttonText: string;
3
4
  buttonWidth?: string;
@@ -6,5 +7,5 @@ export type PurgeProps = {
6
7
  isSettingsPage?: boolean;
7
8
  isPurgeAll?: boolean;
8
9
  };
9
- declare function PurgeModal({ buttonText, keyToUse, buttonWidth, contentTypeName, isSettingsPage, isPurgeAll, }: PurgeProps): import("react/jsx-runtime").JSX.Element | null;
10
+ declare function PurgeModal({ buttonText, keyToUse, buttonWidth, contentTypeName, isSettingsPage, isPurgeAll, }: PurgeProps): import("react").JSX.Element | null;
10
11
  export default PurgeModal;
@@ -1,2 +1,3 @@
1
- declare const SettingsPage: () => import("react/jsx-runtime").JSX.Element;
1
+ /// <reference types="react" />
2
+ declare const SettingsPage: () => import("react").JSX.Element;
2
3
  export default SettingsPage;
@@ -39,11 +39,8 @@ async function invalidateCache(event, cacheStore, strapi2) {
39
39
  const uid = model.uid;
40
40
  const restApiPrefix = strapi2.config.get("api.rest.prefix", "/api");
41
41
  const cacheableEntities = strapi2.plugin("strapi-cache").config("cacheableEntities");
42
- const entityIsCacheable = cacheableEntities?.length ? cacheableEntities.includes(model.tableName) : true;
43
- if (!entityIsCacheable) {
44
- loggy.info(`Not invalidated. ${model.tableName} is not cacheable.`);
45
- return;
46
- }
42
+ const cacheableRoutes = strapi2.plugin("strapi-cache").config("cacheableRoutes") ?? [];
43
+ const excludeRoutes = strapi2.plugin("strapi-cache").config("excludeRoutes") ?? [];
47
44
  try {
48
45
  const contentType = strapi2.contentType(uid);
49
46
  if (!contentType || !contentType.kind) {
@@ -52,7 +49,20 @@ async function invalidateCache(event, cacheStore, strapi2) {
52
49
  }
53
50
  const pluralName = contentType.kind === "singleType" ? contentType.info.singularName : contentType.info.pluralName;
54
51
  const apiPath = `${restApiPrefix}/${pluralName}`;
55
- const regex = new RegExp(`^.*:${apiPath}(/.*)?(\\?.*)?$`);
52
+ const entityNames = [
53
+ model.tableName,
54
+ contentType.info.singularName,
55
+ contentType.info.pluralName
56
+ ].filter(Boolean);
57
+ const entityIsCacheable = cacheableEntities?.length ? entityNames.some((name) => cacheableEntities.includes(name)) : void 0;
58
+ const routeIsExcluded = excludeRoutes.some((route) => apiPath.startsWith(route));
59
+ const routeIsCacheable = cacheableRoutes.some((route) => apiPath.startsWith(route)) || cacheableRoutes.length === 0 && apiPath.startsWith(restApiPrefix);
60
+ const isCacheable = !routeIsExcluded && (entityIsCacheable ?? routeIsCacheable);
61
+ if (!isCacheable) {
62
+ loggy.info(`Not invalidated. ${apiPath} is not cacheable.`);
63
+ return;
64
+ }
65
+ const regex = new RegExp(`^.*:${escapeRegex(apiPath)}(/.*)?(\\?.*)?$`);
56
66
  await cacheStore.clearByRegexp([regex]);
57
67
  loggy.info(`Invalidated cache for ${apiPath}`);
58
68
  } catch (error) {
@@ -104,6 +114,12 @@ const actions = [
104
114
  pluginName: "strapi-cache"
105
115
  }
106
116
  ];
117
+ const runInBackground = (label, task) => {
118
+ void Promise.resolve().then(task).catch((error) => {
119
+ loggy.error(`${label} error:`);
120
+ loggy.error(error);
121
+ });
122
+ };
107
123
  const bootstrap = ({ strapi: strapi2 }) => {
108
124
  loggy.info("Initializing");
109
125
  try {
@@ -119,27 +135,36 @@ const bootstrap = ({ strapi: strapi2 }) => {
119
135
  cacheStore.init();
120
136
  if (autoPurgeCache) {
121
137
  strapi2.db.lifecycles.subscribe({
122
- async afterCreate(event) {
123
- await invalidateCache(event, cacheStore, strapi2);
138
+ afterCreate(event) {
139
+ runInBackground("Cache invalidation", () => invalidateCache(event, cacheStore, strapi2));
124
140
  },
125
- async afterUpdate(event) {
126
- await invalidateCache(event, cacheStore, strapi2);
141
+ afterUpdate(event) {
142
+ runInBackground("Cache invalidation", () => invalidateCache(event, cacheStore, strapi2));
127
143
  },
128
- async afterDelete(event) {
129
- await invalidateCache(event, cacheStore, strapi2);
144
+ afterDelete(event) {
145
+ runInBackground("Cache invalidation", () => invalidateCache(event, cacheStore, strapi2));
130
146
  }
131
147
  });
132
148
  }
133
149
  if (autoPurgeGraphQL) {
134
150
  strapi2.db.lifecycles.subscribe({
135
- async afterCreate(event) {
136
- await invalidateGraphqlCache(event, cacheStore, strapi2);
151
+ afterCreate(event) {
152
+ runInBackground(
153
+ "GraphQL cache invalidation",
154
+ () => invalidateGraphqlCache(event, cacheStore, strapi2)
155
+ );
137
156
  },
138
- async afterUpdate(event) {
139
- await invalidateGraphqlCache(event, cacheStore, strapi2);
157
+ afterUpdate(event) {
158
+ runInBackground(
159
+ "GraphQL cache invalidation",
160
+ () => invalidateGraphqlCache(event, cacheStore, strapi2)
161
+ );
140
162
  },
141
- async afterDelete(event) {
142
- await invalidateGraphqlCache(event, cacheStore, strapi2);
163
+ afterDelete(event) {
164
+ runInBackground(
165
+ "GraphQL cache invalidation",
166
+ () => invalidateGraphqlCache(event, cacheStore, strapi2)
167
+ );
143
168
  }
144
169
  });
145
170
  }
@@ -157,24 +182,24 @@ const bootstrap = ({ strapi: strapi2 }) => {
157
182
  loggy.info("Plugin initialized");
158
183
  strapi2.admin.services.permission.actionProvider.registerMany(actions);
159
184
  };
160
- const getCustomCacheKey = (context, keyGenerator) => {
185
+ const getCustomCacheKey = (context, keyGenerator, fallbackKey) => {
161
186
  if (typeof keyGenerator !== "function") {
162
187
  return void 0;
163
188
  }
164
- const customKey = keyGenerator(context);
189
+ const customKey = keyGenerator(context, fallbackKey);
165
190
  if (typeof customKey === "string") {
166
191
  return customKey;
167
192
  }
168
193
  return void 0;
169
194
  };
170
- const resolveGraphqlCacheKey = (context, fallbackKey, keyGenerator) => getCustomCacheKey(context, keyGenerator) ?? fallbackKey;
195
+ const resolveGraphqlCacheKey = (context, fallbackKey, keyGenerator) => getCustomCacheKey(context, keyGenerator, fallbackKey) ?? fallbackKey;
171
196
  const generateCacheKey = (context, keyGenerator) => {
172
- const customKey = getCustomCacheKey(context, keyGenerator);
197
+ const { url } = context.request;
198
+ const { method } = context.request;
199
+ const customKey = getCustomCacheKey(context, keyGenerator, `${method}:${url}`);
173
200
  if (customKey !== void 0) {
174
201
  return customKey;
175
202
  }
176
- const { url } = context.request;
177
- const { method } = context.request;
178
203
  return `${method}:${url}`;
179
204
  };
180
205
  const generateGraphqlCacheKey = (payload, method = "POST", rootFields = [], strapi2) => {
@@ -35,11 +35,8 @@ async function invalidateCache(event, cacheStore, strapi2) {
35
35
  const uid = model.uid;
36
36
  const restApiPrefix = strapi2.config.get("api.rest.prefix", "/api");
37
37
  const cacheableEntities = strapi2.plugin("strapi-cache").config("cacheableEntities");
38
- const entityIsCacheable = cacheableEntities?.length ? cacheableEntities.includes(model.tableName) : true;
39
- if (!entityIsCacheable) {
40
- loggy.info(`Not invalidated. ${model.tableName} is not cacheable.`);
41
- return;
42
- }
38
+ const cacheableRoutes = strapi2.plugin("strapi-cache").config("cacheableRoutes") ?? [];
39
+ const excludeRoutes = strapi2.plugin("strapi-cache").config("excludeRoutes") ?? [];
43
40
  try {
44
41
  const contentType = strapi2.contentType(uid);
45
42
  if (!contentType || !contentType.kind) {
@@ -48,7 +45,20 @@ async function invalidateCache(event, cacheStore, strapi2) {
48
45
  }
49
46
  const pluralName = contentType.kind === "singleType" ? contentType.info.singularName : contentType.info.pluralName;
50
47
  const apiPath = `${restApiPrefix}/${pluralName}`;
51
- const regex = new RegExp(`^.*:${apiPath}(/.*)?(\\?.*)?$`);
48
+ const entityNames = [
49
+ model.tableName,
50
+ contentType.info.singularName,
51
+ contentType.info.pluralName
52
+ ].filter(Boolean);
53
+ const entityIsCacheable = cacheableEntities?.length ? entityNames.some((name) => cacheableEntities.includes(name)) : void 0;
54
+ const routeIsExcluded = excludeRoutes.some((route) => apiPath.startsWith(route));
55
+ const routeIsCacheable = cacheableRoutes.some((route) => apiPath.startsWith(route)) || cacheableRoutes.length === 0 && apiPath.startsWith(restApiPrefix);
56
+ const isCacheable = !routeIsExcluded && (entityIsCacheable ?? routeIsCacheable);
57
+ if (!isCacheable) {
58
+ loggy.info(`Not invalidated. ${apiPath} is not cacheable.`);
59
+ return;
60
+ }
61
+ const regex = new RegExp(`^.*:${escapeRegex(apiPath)}(/.*)?(\\?.*)?$`);
52
62
  await cacheStore.clearByRegexp([regex]);
53
63
  loggy.info(`Invalidated cache for ${apiPath}`);
54
64
  } catch (error) {
@@ -100,6 +110,12 @@ const actions = [
100
110
  pluginName: "strapi-cache"
101
111
  }
102
112
  ];
113
+ const runInBackground = (label, task) => {
114
+ void Promise.resolve().then(task).catch((error) => {
115
+ loggy.error(`${label} error:`);
116
+ loggy.error(error);
117
+ });
118
+ };
103
119
  const bootstrap = ({ strapi: strapi2 }) => {
104
120
  loggy.info("Initializing");
105
121
  try {
@@ -115,27 +131,36 @@ const bootstrap = ({ strapi: strapi2 }) => {
115
131
  cacheStore.init();
116
132
  if (autoPurgeCache) {
117
133
  strapi2.db.lifecycles.subscribe({
118
- async afterCreate(event) {
119
- await invalidateCache(event, cacheStore, strapi2);
134
+ afterCreate(event) {
135
+ runInBackground("Cache invalidation", () => invalidateCache(event, cacheStore, strapi2));
120
136
  },
121
- async afterUpdate(event) {
122
- await invalidateCache(event, cacheStore, strapi2);
137
+ afterUpdate(event) {
138
+ runInBackground("Cache invalidation", () => invalidateCache(event, cacheStore, strapi2));
123
139
  },
124
- async afterDelete(event) {
125
- await invalidateCache(event, cacheStore, strapi2);
140
+ afterDelete(event) {
141
+ runInBackground("Cache invalidation", () => invalidateCache(event, cacheStore, strapi2));
126
142
  }
127
143
  });
128
144
  }
129
145
  if (autoPurgeGraphQL) {
130
146
  strapi2.db.lifecycles.subscribe({
131
- async afterCreate(event) {
132
- await invalidateGraphqlCache(event, cacheStore, strapi2);
147
+ afterCreate(event) {
148
+ runInBackground(
149
+ "GraphQL cache invalidation",
150
+ () => invalidateGraphqlCache(event, cacheStore, strapi2)
151
+ );
133
152
  },
134
- async afterUpdate(event) {
135
- await invalidateGraphqlCache(event, cacheStore, strapi2);
153
+ afterUpdate(event) {
154
+ runInBackground(
155
+ "GraphQL cache invalidation",
156
+ () => invalidateGraphqlCache(event, cacheStore, strapi2)
157
+ );
136
158
  },
137
- async afterDelete(event) {
138
- await invalidateGraphqlCache(event, cacheStore, strapi2);
159
+ afterDelete(event) {
160
+ runInBackground(
161
+ "GraphQL cache invalidation",
162
+ () => invalidateGraphqlCache(event, cacheStore, strapi2)
163
+ );
139
164
  }
140
165
  });
141
166
  }
@@ -153,24 +178,24 @@ const bootstrap = ({ strapi: strapi2 }) => {
153
178
  loggy.info("Plugin initialized");
154
179
  strapi2.admin.services.permission.actionProvider.registerMany(actions);
155
180
  };
156
- const getCustomCacheKey = (context, keyGenerator) => {
181
+ const getCustomCacheKey = (context, keyGenerator, fallbackKey) => {
157
182
  if (typeof keyGenerator !== "function") {
158
183
  return void 0;
159
184
  }
160
- const customKey = keyGenerator(context);
185
+ const customKey = keyGenerator(context, fallbackKey);
161
186
  if (typeof customKey === "string") {
162
187
  return customKey;
163
188
  }
164
189
  return void 0;
165
190
  };
166
- const resolveGraphqlCacheKey = (context, fallbackKey, keyGenerator) => getCustomCacheKey(context, keyGenerator) ?? fallbackKey;
191
+ const resolveGraphqlCacheKey = (context, fallbackKey, keyGenerator) => getCustomCacheKey(context, keyGenerator, fallbackKey) ?? fallbackKey;
167
192
  const generateCacheKey = (context, keyGenerator) => {
168
- const customKey = getCustomCacheKey(context, keyGenerator);
193
+ const { url } = context.request;
194
+ const { method } = context.request;
195
+ const customKey = getCustomCacheKey(context, keyGenerator, `${method}:${url}`);
169
196
  if (customKey !== void 0) {
170
197
  return customKey;
171
198
  }
172
- const { url } = context.request;
173
- const { method } = context.request;
174
199
  return `${method}:${url}`;
175
200
  };
176
201
  const generateGraphqlCacheKey = (payload, method = "POST", rootFields = [], strapi2) => {
@@ -1,7 +1,7 @@
1
1
  import { Core } from '@strapi/strapi';
2
2
  import { Context } from 'koa';
3
- export type CacheKeyGenerator = (context: Context) => string;
4
- export declare const getCustomCacheKey: (context: Context, keyGenerator?: CacheKeyGenerator) => string;
3
+ export type CacheKeyGenerator = (context: Context, defaultKey?: string) => string;
4
+ export declare const getCustomCacheKey: (context: Context, keyGenerator?: CacheKeyGenerator, fallbackKey?: string) => string;
5
5
  export declare const resolveGraphqlCacheKey: (context: Context, fallbackKey: string, keyGenerator?: CacheKeyGenerator) => string;
6
6
  export declare const generateCacheKey: (context: Context, keyGenerator?: CacheKeyGenerator) => string;
7
7
  export declare const generateGraphqlCacheKey: (payload: string, method?: 'GET' | 'POST', rootFields?: string[], strapi?: Core.Strapi) => string;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.11.1",
2
+ "version": "1.12.0",
3
3
  "keywords": [
4
4
  "strapi cache",
5
5
  "strapi rest cache",
@@ -39,8 +39,8 @@
39
39
  "test:all": "npm test -- --run && npm run test:integration"
40
40
  },
41
41
  "dependencies": {
42
- "@strapi/design-system": "^2.0.1",
43
- "@strapi/icons": "^2.0.1",
42
+ "@strapi/design-system": "2.2.1",
43
+ "@strapi/icons": "2.2.1",
44
44
  "ioredis": "^5.6.1",
45
45
  "lru-cache": "^11.1.0",
46
46
  "raw-body": "^3.0.0",
@@ -52,8 +52,8 @@
52
52
  "@strapi/typescript-utils": "^5.0.0",
53
53
  "@types/jest": "^30.0.0",
54
54
  "@types/koa": "^2.15.0",
55
- "@types/react": "^19.1.0",
56
- "@types/react-dom": "^19.1.1",
55
+ "@types/react": "^18.3.0",
56
+ "@types/react-dom": "^18.3.0",
57
57
  "@types/supertest": "^6.0.3",
58
58
  "better-sqlite3": "^12.6.2",
59
59
  "jest": "^30.2.0",