strapi-cache 1.8.1 → 1.8.2-rc.2

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
@@ -17,7 +17,7 @@ Boost your API performance with automatic in-memory or Redis caching for REST an
17
17
  - ♻️ **LRU (Least Recently Used) caching strategy**
18
18
  - 🔧 Simple integration with Strapi config
19
19
  - 📦 Lightweight with zero overhead
20
- - 🗄️ **Supports in-memory and Redis caching**
20
+ - 🗄️ **Supports in-memory, Redis and Valkey caching**
21
21
 
22
22
  ---
23
23
 
@@ -52,9 +52,9 @@ In your Strapi project, navigate to `config/plugins.js` and add the following co
52
52
  cacheableRoutes: ['/api/products', '/api/categories'], // Caches routes which start with these paths (if empty array, all '/api' routes are cached)
53
53
  // 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
54
54
  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`.
55
- provider: 'memory', // Cache provider ('memory' or 'redis')
56
- 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)
57
- 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
55
+ provider: 'memory', // Cache provider ('memory', 'redis' or 'valkey')
56
+ 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)
57
+ redisClusterNodes: [], // If provided any cluster node (this list is not empty), initialize cluster client. Each object must have keys 'host' and 'port'
58
58
  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
59
59
  cacheHeaders: true, // Plugin also stores response headers in the cache (set to false if you don't want to cache headers)
60
60
  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)
@@ -83,8 +83,8 @@ All of these routes are protected by the policies `admin::isAuthenticatedAdmin`
83
83
 
84
84
  ## 🗂️ How It Works
85
85
 
86
- - **Storage**: The plugin keeps cached data in memory or Redis, depending on the configuration.
87
- - **Packages**: Uses [lru-cache](https://github.com/isaacs/node-lru-cache) for in-memory cache. Uses [ioredis](https://github.com/redis/ioredis) for Redis caching.
86
+ - **Storage**: The plugin keeps cached data in memory, Redis or Valkey, depending on the configuration.
87
+ - **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.
88
88
  - **Automatic Invalidation**: Cache is cleared automatically when content is updated, deleted, or created. (GraphQL cache clears on any content update.)
89
89
  - **`no-cache` Header Support**: Respects the `no-cache` header, letting you skip the cache by setting `Cache-Control: no-cache` in your request.
90
90
  - **Default Cached Requests**: By default, caches all GET requests to `/api` (or whatever prefix you defined) and POST requests to `/graphql`. You can customize which routes or entities to cache using `cacheableRoutes` or `cacheableEntities` config options.
@@ -3,12 +3,13 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const designSystem = require("@strapi/design-system");
5
5
  const reactIntl = require("react-intl");
6
- const index = require("./index-D_ssKKxu.js");
6
+ const admin = require("@strapi/strapi/admin");
7
+ const index = require("./index-EMYe2zF8.js");
7
8
  const react = require("react");
8
9
  const SettingsPage = () => {
9
10
  const { formatMessage } = reactIntl.useIntl();
10
11
  const [keyToUse, setKeyToUse] = react.useState("");
11
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "20px" }, children: [
12
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: index.pluginPermissions.viewSettings, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "20px" }, children: [
12
13
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", as: "h1", children: formatMessage({
13
14
  id: "strapi-cache.name",
14
15
  defaultMessage: "Strapi Cache Settings"
@@ -47,6 +48,6 @@ const SettingsPage = () => {
47
48
  }
48
49
  ),
49
50
  /* @__PURE__ */ jsxRuntime.jsx(index.PurgeModal, { buttonText: "Purge All", isPurgeAll: true, isSettingsPage: true })
50
- ] });
51
+ ] }) });
51
52
  };
52
53
  exports.default = SettingsPage;
@@ -73,12 +73,14 @@ const useCacheConfig = (enabled = true) => {
73
73
  };
74
74
  };
75
75
  const pluginPermissions = {
76
- purge: [{ action: "plugin::strapi-cache.purge-cache", subject: null }]
76
+ purge: [{ action: "plugin::strapi-cache.purge-cache", subject: null }],
77
+ viewSettings: [{ action: "plugin::strapi-cache.view-settings", subject: null }]
77
78
  };
78
79
  const useCachePermissions = () => {
79
80
  const { allowedActions } = useRBAC(pluginPermissions);
80
81
  return {
81
82
  canPurgeCache: allowedActions.canPurgeCache,
83
+ canViewSettings: allowedActions.canViewSettings,
82
84
  allowedActions
83
85
  };
84
86
  };
@@ -371,8 +373,8 @@ const index = {
371
373
  },
372
374
  id: "settings",
373
375
  to: `${PLUGIN_ID}/settings`,
374
- Component: () => import("./index-CkLhx2ik.mjs"),
375
- permissions: []
376
+ Component: () => import("./index-DLoQ9I8J.mjs"),
377
+ permissions: pluginPermissions.viewSettings
376
378
  }
377
379
  ]
378
380
  );
@@ -392,5 +394,6 @@ const index = {
392
394
  };
393
395
  export {
394
396
  PurgeModal as P,
395
- index as i
397
+ index as i,
398
+ pluginPermissions as p
396
399
  };
@@ -1,12 +1,13 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { Typography, TextInput } from "@strapi/design-system";
3
3
  import { useIntl } from "react-intl";
4
- import { P as PurgeModal } from "./index-BwuX8jJl.mjs";
4
+ import { Page } from "@strapi/strapi/admin";
5
+ import { p as pluginPermissions, P as PurgeModal } from "./index-BovuTRdq.mjs";
5
6
  import { useState } from "react";
6
7
  const SettingsPage = () => {
7
8
  const { formatMessage } = useIntl();
8
9
  const [keyToUse, setKeyToUse] = useState("");
9
- return /* @__PURE__ */ jsxs("div", { style: { padding: "20px" }, children: [
10
+ return /* @__PURE__ */ jsx(Page.Protect, { permissions: pluginPermissions.viewSettings, children: /* @__PURE__ */ jsxs("div", { style: { padding: "20px" }, children: [
10
11
  /* @__PURE__ */ jsx(Typography, { variant: "alpha", as: "h1", children: formatMessage({
11
12
  id: "strapi-cache.name",
12
13
  defaultMessage: "Strapi Cache Settings"
@@ -45,7 +46,7 @@ const SettingsPage = () => {
45
46
  }
46
47
  ),
47
48
  /* @__PURE__ */ jsx(PurgeModal, { buttonText: "Purge All", isPurgeAll: true, isSettingsPage: true })
48
- ] });
49
+ ] }) });
49
50
  };
50
51
  export {
51
52
  SettingsPage as default
@@ -74,12 +74,14 @@ const useCacheConfig = (enabled = true) => {
74
74
  };
75
75
  };
76
76
  const pluginPermissions = {
77
- purge: [{ action: "plugin::strapi-cache.purge-cache", subject: null }]
77
+ purge: [{ action: "plugin::strapi-cache.purge-cache", subject: null }],
78
+ viewSettings: [{ action: "plugin::strapi-cache.view-settings", subject: null }]
78
79
  };
79
80
  const useCachePermissions = () => {
80
81
  const { allowedActions } = admin.useRBAC(pluginPermissions);
81
82
  return {
82
83
  canPurgeCache: allowedActions.canPurgeCache,
84
+ canViewSettings: allowedActions.canViewSettings,
83
85
  allowedActions
84
86
  };
85
87
  };
@@ -372,8 +374,8 @@ const index = {
372
374
  },
373
375
  id: "settings",
374
376
  to: `${PLUGIN_ID}/settings`,
375
- Component: () => Promise.resolve().then(() => require("./index-B_MAAg0W.js")),
376
- permissions: []
377
+ Component: () => Promise.resolve().then(() => require("./index-Bdo3ZcJs.js")),
378
+ permissions: pluginPermissions.viewSettings
377
379
  }
378
380
  ]
379
381
  );
@@ -393,3 +395,4 @@ const index = {
393
395
  };
394
396
  exports.PurgeModal = PurgeModal;
395
397
  exports.index = index;
398
+ exports.pluginPermissions = pluginPermissions;
@@ -1,3 +1,3 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-D_ssKKxu.js");
2
+ const index = require("../_chunks/index-EMYe2zF8.js");
3
3
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-BwuX8jJl.mjs";
1
+ import { i } from "../_chunks/index-BovuTRdq.mjs";
2
2
  export {
3
3
  i as default
4
4
  };
@@ -1,4 +1,5 @@
1
1
  export declare const useCachePermissions: () => {
2
2
  canPurgeCache: boolean;
3
+ canViewSettings: boolean;
3
4
  allowedActions: import("@strapi/strapi/admin").AllowedActions;
4
5
  };
@@ -3,4 +3,8 @@ export declare const pluginPermissions: {
3
3
  action: string;
4
4
  subject: null;
5
5
  }[];
6
+ viewSettings: {
7
+ action: string;
8
+ subject: null;
9
+ }[];
6
10
  };
@@ -5,6 +5,7 @@ const zlib = require("zlib");
5
5
  const rawBody = require("raw-body");
6
6
  const lruCache = require("lru-cache");
7
7
  const ioredis = require("ioredis");
8
+ const iovalkey = require("iovalkey");
8
9
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
9
10
  const Stream__default = /* @__PURE__ */ _interopDefault(Stream);
10
11
  const rawBody__default = /* @__PURE__ */ _interopDefault(rawBody);
@@ -91,6 +92,12 @@ const actions = [
91
92
  displayName: "Purge Cache",
92
93
  uid: "purge-cache",
93
94
  pluginName: "strapi-cache"
95
+ },
96
+ {
97
+ section: "plugins",
98
+ displayName: "View settings",
99
+ uid: "view-settings",
100
+ pluginName: "strapi-cache"
94
101
  }
95
102
  ];
96
103
  const bootstrap = ({ strapi: strapi2 }) => {
@@ -567,16 +574,18 @@ const config = {
567
574
  if (typeof config2.provider !== "string") {
568
575
  throw new Error(`Invalid config: provider must be a string`);
569
576
  }
570
- if (config2.provider !== "memory" && config2.provider !== "redis") {
571
- throw new Error(`Invalid config: provider must be 'memory' or 'redis'`);
577
+ if (config2.provider !== "memory" && config2.provider !== "redis" && config2.provider !== "valkey") {
578
+ throw new Error(`Invalid config: provider must be 'memory', 'redis' or 'valkey'`);
572
579
  }
573
- if (config2.provider === "redis") {
580
+ if (config2.provider === "redis" || config2.provider === "valkey") {
574
581
  if (!config2.redisConfig) {
575
- throw new Error(`Invalid config: redisConfig must be set when using redis provider`);
582
+ throw new Error(
583
+ `Invalid config: redisConfig must be set when using redis or valkey provider`
584
+ );
576
585
  }
577
586
  if (typeof config2.redisConfig !== "string" && typeof config2.redisConfig !== "object") {
578
587
  throw new Error(
579
- `Invalid config: redisConfig must be a string or object when using redis provider`
588
+ `Invalid config: redisConfig must be a string or object when using redis or valkey provider`
580
589
  );
581
590
  }
582
591
  if (!Array.isArray(config2.redisClusterNodes) || config2.redisClusterNodes.some((item) => !("host" in item && "port" in item))) {
@@ -705,7 +714,10 @@ const purgeRoute = [
705
714
  {
706
715
  name: "plugin::content-manager.hasPermissions",
707
716
  config: {
708
- actions: ["plugin::strapi-cache.purge-cache"]
717
+ actions: [
718
+ "plugin::strapi-cache.purge-cache",
719
+ "plugin::strapi-cache.view-settings"
720
+ ]
709
721
  }
710
722
  }
711
723
  ]
@@ -840,23 +852,41 @@ class RedisCacheProvider {
840
852
  return;
841
853
  }
842
854
  try {
855
+ const provider = this.strapi.plugin("strapi-cache").config("provider") || "redis";
843
856
  const redisConfig = this.strapi.plugin("strapi-cache").config("redisConfig") || "redis://localhost:6379";
844
857
  const redisClusterNodes = this.strapi.plugin("strapi-cache").config("redisClusterNodes");
845
858
  this.cacheGetTimeoutInMs = Number(
846
859
  this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
847
860
  );
848
861
  this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
849
- if (redisClusterNodes.length) {
850
- const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
851
- if (!redisClusterOptions["redisOptions"]) {
852
- redisClusterOptions.redisOptions = redisConfig;
862
+ if (provider === "valkey") {
863
+ if (redisClusterNodes.length) {
864
+ const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions") ?? {};
865
+ const clusterOptions = { ...redisClusterOptions };
866
+ if (!clusterOptions["redisOptions"]) {
867
+ clusterOptions["redisOptions"] = redisConfig;
868
+ }
869
+ this.client = new iovalkey.Cluster(
870
+ redisClusterNodes,
871
+ clusterOptions
872
+ );
873
+ } else {
874
+ this.client = new iovalkey.Redis(redisConfig);
853
875
  }
854
- this.client = new ioredis.Redis.Cluster(redisClusterNodes, redisClusterOptions);
876
+ loggy.info("Valkey provider initialized");
855
877
  } else {
856
- this.client = new ioredis.Redis(redisConfig);
878
+ if (redisClusterNodes.length) {
879
+ const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
880
+ if (!redisClusterOptions["redisOptions"]) {
881
+ redisClusterOptions.redisOptions = redisConfig;
882
+ }
883
+ this.client = new ioredis.Redis.Cluster(redisClusterNodes, redisClusterOptions);
884
+ } else {
885
+ this.client = new ioredis.Redis(redisConfig);
886
+ }
887
+ loggy.info("Redis provider initialized");
857
888
  }
858
889
  this.initialized = true;
859
- loggy.info("Redis provider initialized");
860
890
  } catch (error) {
861
891
  loggy.error(error);
862
892
  }
@@ -960,6 +990,7 @@ const resolveCacheProvider = (strapi2) => {
960
990
  let instance;
961
991
  switch (providerType) {
962
992
  case "redis":
993
+ case "valkey":
963
994
  instance = new RedisCacheProvider(strapi2);
964
995
  break;
965
996
  default:
@@ -3,7 +3,8 @@ import Stream, { Readable } from "stream";
3
3
  import { createInflate, createBrotliDecompress, createGunzip } from "zlib";
4
4
  import rawBody from "raw-body";
5
5
  import { LRUCache } from "lru-cache";
6
- import { Redis } from "ioredis";
6
+ import { Redis as Redis$1 } from "ioredis";
7
+ import { Cluster, Redis } from "iovalkey";
7
8
  const loggy = {
8
9
  info: (msg) => {
9
10
  const shouldDebug = strapi.plugin("strapi-cache").config("debug") ?? false;
@@ -87,6 +88,12 @@ const actions = [
87
88
  displayName: "Purge Cache",
88
89
  uid: "purge-cache",
89
90
  pluginName: "strapi-cache"
91
+ },
92
+ {
93
+ section: "plugins",
94
+ displayName: "View settings",
95
+ uid: "view-settings",
96
+ pluginName: "strapi-cache"
90
97
  }
91
98
  ];
92
99
  const bootstrap = ({ strapi: strapi2 }) => {
@@ -563,16 +570,18 @@ const config = {
563
570
  if (typeof config2.provider !== "string") {
564
571
  throw new Error(`Invalid config: provider must be a string`);
565
572
  }
566
- if (config2.provider !== "memory" && config2.provider !== "redis") {
567
- throw new Error(`Invalid config: provider must be 'memory' or 'redis'`);
573
+ if (config2.provider !== "memory" && config2.provider !== "redis" && config2.provider !== "valkey") {
574
+ throw new Error(`Invalid config: provider must be 'memory', 'redis' or 'valkey'`);
568
575
  }
569
- if (config2.provider === "redis") {
576
+ if (config2.provider === "redis" || config2.provider === "valkey") {
570
577
  if (!config2.redisConfig) {
571
- throw new Error(`Invalid config: redisConfig must be set when using redis provider`);
578
+ throw new Error(
579
+ `Invalid config: redisConfig must be set when using redis or valkey provider`
580
+ );
572
581
  }
573
582
  if (typeof config2.redisConfig !== "string" && typeof config2.redisConfig !== "object") {
574
583
  throw new Error(
575
- `Invalid config: redisConfig must be a string or object when using redis provider`
584
+ `Invalid config: redisConfig must be a string or object when using redis or valkey provider`
576
585
  );
577
586
  }
578
587
  if (!Array.isArray(config2.redisClusterNodes) || config2.redisClusterNodes.some((item) => !("host" in item && "port" in item))) {
@@ -701,7 +710,10 @@ const purgeRoute = [
701
710
  {
702
711
  name: "plugin::content-manager.hasPermissions",
703
712
  config: {
704
- actions: ["plugin::strapi-cache.purge-cache"]
713
+ actions: [
714
+ "plugin::strapi-cache.purge-cache",
715
+ "plugin::strapi-cache.view-settings"
716
+ ]
705
717
  }
706
718
  }
707
719
  ]
@@ -836,23 +848,41 @@ class RedisCacheProvider {
836
848
  return;
837
849
  }
838
850
  try {
851
+ const provider = this.strapi.plugin("strapi-cache").config("provider") || "redis";
839
852
  const redisConfig = this.strapi.plugin("strapi-cache").config("redisConfig") || "redis://localhost:6379";
840
853
  const redisClusterNodes = this.strapi.plugin("strapi-cache").config("redisClusterNodes");
841
854
  this.cacheGetTimeoutInMs = Number(
842
855
  this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
843
856
  );
844
857
  this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
845
- if (redisClusterNodes.length) {
846
- const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
847
- if (!redisClusterOptions["redisOptions"]) {
848
- redisClusterOptions.redisOptions = redisConfig;
858
+ if (provider === "valkey") {
859
+ if (redisClusterNodes.length) {
860
+ const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions") ?? {};
861
+ const clusterOptions = { ...redisClusterOptions };
862
+ if (!clusterOptions["redisOptions"]) {
863
+ clusterOptions["redisOptions"] = redisConfig;
864
+ }
865
+ this.client = new Cluster(
866
+ redisClusterNodes,
867
+ clusterOptions
868
+ );
869
+ } else {
870
+ this.client = new Redis(redisConfig);
849
871
  }
850
- this.client = new Redis.Cluster(redisClusterNodes, redisClusterOptions);
872
+ loggy.info("Valkey provider initialized");
851
873
  } else {
852
- this.client = new Redis(redisConfig);
874
+ if (redisClusterNodes.length) {
875
+ const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
876
+ if (!redisClusterOptions["redisOptions"]) {
877
+ redisClusterOptions.redisOptions = redisConfig;
878
+ }
879
+ this.client = new Redis$1.Cluster(redisClusterNodes, redisClusterOptions);
880
+ } else {
881
+ this.client = new Redis$1(redisConfig);
882
+ }
883
+ loggy.info("Redis provider initialized");
853
884
  }
854
885
  this.initialized = true;
855
- loggy.info("Redis provider initialized");
856
886
  } catch (error) {
857
887
  loggy.error(error);
858
888
  }
@@ -956,6 +986,7 @@ const resolveCacheProvider = (strapi2) => {
956
986
  let instance;
957
987
  switch (providerType) {
958
988
  case "redis":
989
+ case "valkey":
959
990
  instance = new RedisCacheProvider(strapi2);
960
991
  break;
961
992
  default:
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.8.1",
2
+ "version": "1.8.2-rc.2",
3
3
  "keywords": [
4
4
  "strapi cache",
5
5
  "strapi rest cache",
@@ -42,6 +42,7 @@
42
42
  "@strapi/design-system": "^2.0.1",
43
43
  "@strapi/icons": "^2.0.1",
44
44
  "ioredis": "^5.6.1",
45
+ "iovalkey": "^0.3.3",
45
46
  "lru-cache": "^11.1.0",
46
47
  "raw-body": "^3.0.0",
47
48
  "react-intl": "^7.1.10"