strapi-cache 1.6.2 → 1.7.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 +3 -1
- package/dist/_chunks/de-C_gFyhHL.mjs +18 -0
- package/dist/_chunks/de-l6MDDAXu.js +18 -0
- package/dist/_chunks/en-D9w03LyX.js +18 -0
- package/dist/_chunks/en-bK2OKwtN.mjs +18 -0
- package/dist/_chunks/index-BInrGFOe.mjs +52 -0
- package/dist/_chunks/index-BjBoZhpS.js +52 -0
- package/dist/_chunks/index-BwjcxGep.mjs +388 -0
- package/dist/_chunks/index-Dwrl1jfJ.js +387 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/PurgeModal/index.d.ts +2 -1
- package/dist/admin/src/hooks/useCacheOperations.d.ts +1 -0
- package/dist/server/index.js +848 -0
- package/dist/server/index.mjs +846 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +27 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/controller.d.ts +10 -0
- package/dist/server/src/controllers/index.d.ts +11 -0
- package/dist/server/src/index.d.ts +75 -0
- package/dist/server/src/middlewares/cache.d.ts +3 -0
- package/dist/server/src/middlewares/graphql.d.ts +2 -0
- package/dist/server/src/middlewares/index.d.ts +6 -0
- package/dist/server/src/permissions.d.ts +6 -0
- package/dist/server/src/policies/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/index.d.ts +19 -0
- package/dist/server/src/routes/purge.d.ts +14 -0
- package/dist/server/src/services/index.d.ts +8 -0
- package/dist/server/src/services/memory/provider.d.ts +17 -0
- package/dist/server/src/services/memory/service.d.ts +6 -0
- package/dist/server/src/services/redis/provider.d.ts +18 -0
- package/dist/server/src/services/redis/service.d.ts +6 -0
- package/dist/server/src/services/resolver.d.ts +3 -0
- package/dist/server/src/types/cache.types.d.ts +18 -0
- package/dist/server/src/utils/body.d.ts +7 -0
- package/dist/server/src/utils/header.d.ts +10 -0
- package/dist/server/src/utils/invalidateCache.d.ts +4 -0
- package/dist/server/src/utils/key.d.ts +4 -0
- package/dist/server/src/utils/log.d.ts +5 -0
- package/dist/server/src/utils/withTimeout.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const crypto = require("crypto");
|
|
3
|
+
const Stream = require("stream");
|
|
4
|
+
const zlib = require("zlib");
|
|
5
|
+
const rawBody = require("raw-body");
|
|
6
|
+
const lruCache = require("lru-cache");
|
|
7
|
+
const ioredis = require("ioredis");
|
|
8
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
9
|
+
const Stream__default = /* @__PURE__ */ _interopDefault(Stream);
|
|
10
|
+
const rawBody__default = /* @__PURE__ */ _interopDefault(rawBody);
|
|
11
|
+
const loggy = {
|
|
12
|
+
info: (msg) => {
|
|
13
|
+
const shouldDebug = strapi.plugin("strapi-cache").config("debug") ?? false;
|
|
14
|
+
if (!shouldDebug) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
strapi.log.info(`[STRAPI CACHE] ${msg}`);
|
|
18
|
+
},
|
|
19
|
+
error: (msg) => {
|
|
20
|
+
const shouldDebug = strapi.plugin("strapi-cache").config("debug") ?? false;
|
|
21
|
+
if (!shouldDebug) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
strapi.log.error(`[STRAPI CACHE] ${msg}`);
|
|
25
|
+
},
|
|
26
|
+
warn: (msg) => {
|
|
27
|
+
const shouldDebug = strapi.plugin("strapi-cache").config("debug") ?? false;
|
|
28
|
+
if (!shouldDebug) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
strapi.log.warn(`[STRAPI CACHE] ${msg}`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
async function invalidateCache(event, cacheStore, strapi2) {
|
|
35
|
+
const { model } = event;
|
|
36
|
+
const uid = model.uid;
|
|
37
|
+
const restApiPrefix = strapi2.config.get("api.rest.prefix", "/api");
|
|
38
|
+
try {
|
|
39
|
+
const contentType = strapi2.contentType(uid);
|
|
40
|
+
if (!contentType || !contentType.kind) {
|
|
41
|
+
loggy.info(`Content type ${uid} not found`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const pluralName = contentType.kind === "singleType" ? contentType.info.singularName : contentType.info.pluralName;
|
|
45
|
+
const apiPath = `${restApiPrefix}/${pluralName}`;
|
|
46
|
+
const regex = new RegExp(`^.*:${apiPath}(/.*)?(\\?.*)?$`);
|
|
47
|
+
await cacheStore.clearByRegexp([regex]);
|
|
48
|
+
loggy.info(`Invalidated cache for ${apiPath}`);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
loggy.error("Cache invalidation error:");
|
|
51
|
+
loggy.error(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function invalidateGraphqlCache(cacheStore) {
|
|
55
|
+
try {
|
|
56
|
+
const graphqlRegex = new RegExp(`^POST:/graphql(:.*)?$`);
|
|
57
|
+
await cacheStore.clearByRegexp([graphqlRegex]);
|
|
58
|
+
loggy.info(`Invalidated cache for ${graphqlRegex}`);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
loggy.error("Cache invalidation error:");
|
|
61
|
+
loggy.error(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const actions = [
|
|
65
|
+
{
|
|
66
|
+
section: "plugins",
|
|
67
|
+
displayName: "Purge Cache",
|
|
68
|
+
uid: "purge-cache",
|
|
69
|
+
pluginName: "strapi-cache"
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
const bootstrap = ({ strapi: strapi2 }) => {
|
|
73
|
+
loggy.info("Initializing");
|
|
74
|
+
try {
|
|
75
|
+
const cacheService = strapi2.plugin("strapi-cache").services.service;
|
|
76
|
+
const autoPurgeCache = strapi2.plugin("strapi-cache").config("autoPurgeCache");
|
|
77
|
+
const autoPurgeCacheOnStart = strapi2.plugin("strapi-cache").config("autoPurgeCacheOnStart");
|
|
78
|
+
const cacheStore = cacheService.getCacheInstance();
|
|
79
|
+
if (!cacheStore) {
|
|
80
|
+
loggy.error("Plugin could not be initialized");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
cacheStore.init();
|
|
84
|
+
if (autoPurgeCache) {
|
|
85
|
+
strapi2.db.lifecycles.subscribe({
|
|
86
|
+
async afterCreate(event) {
|
|
87
|
+
await invalidateCache(event, cacheStore, strapi2);
|
|
88
|
+
await invalidateGraphqlCache(cacheStore);
|
|
89
|
+
},
|
|
90
|
+
async afterUpdate(event) {
|
|
91
|
+
await invalidateCache(event, cacheStore, strapi2);
|
|
92
|
+
await invalidateGraphqlCache(cacheStore);
|
|
93
|
+
},
|
|
94
|
+
async afterDelete(event) {
|
|
95
|
+
await invalidateCache(event, cacheStore, strapi2);
|
|
96
|
+
await invalidateGraphqlCache(cacheStore);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (autoPurgeCacheOnStart) {
|
|
101
|
+
cacheStore.reset().then(() => {
|
|
102
|
+
loggy.info("Cache purged successfully");
|
|
103
|
+
}).catch((error) => {
|
|
104
|
+
loggy.error(`Error purging cache on start: ${error.message}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
loggy.error("Plugin could not be initialized");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
loggy.info("Plugin initialized");
|
|
112
|
+
strapi2.admin.services.permission.actionProvider.registerMany(actions);
|
|
113
|
+
};
|
|
114
|
+
const generateCacheKey = (context) => {
|
|
115
|
+
const { url } = context.request;
|
|
116
|
+
const { method } = context.request;
|
|
117
|
+
return `${method}:${url}`;
|
|
118
|
+
};
|
|
119
|
+
const generateGraphqlCacheKey = (payload) => {
|
|
120
|
+
const hash = crypto.createHash("sha256").update(payload).digest("base64url");
|
|
121
|
+
return `POST:/graphql:${hash}`;
|
|
122
|
+
};
|
|
123
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
124
|
+
const streamToBuffer = (stream) => {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const chunks = [];
|
|
127
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
128
|
+
stream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
129
|
+
stream.on("error", reject);
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
const decompressBuffer = async (buffer, encoding) => {
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
let decompressStream;
|
|
135
|
+
switch (encoding) {
|
|
136
|
+
case "gzip":
|
|
137
|
+
decompressStream = zlib.createGunzip();
|
|
138
|
+
break;
|
|
139
|
+
case "br":
|
|
140
|
+
decompressStream = zlib.createBrotliDecompress();
|
|
141
|
+
break;
|
|
142
|
+
case "deflate":
|
|
143
|
+
decompressStream = zlib.createInflate();
|
|
144
|
+
break;
|
|
145
|
+
default:
|
|
146
|
+
return resolve(buffer);
|
|
147
|
+
}
|
|
148
|
+
const chunks = [];
|
|
149
|
+
decompressStream.on("data", (chunk) => chunks.push(chunk));
|
|
150
|
+
decompressStream.on("end", () => resolve(Buffer.concat(chunks)));
|
|
151
|
+
decompressStream.on("error", reject);
|
|
152
|
+
decompressStream.end(buffer);
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
const decodeBufferToText = (buffer) => {
|
|
156
|
+
const decoder = new TextDecoder("utf-8");
|
|
157
|
+
return decoder.decode(buffer);
|
|
158
|
+
};
|
|
159
|
+
function getHeadersToStore(ctx, cacheHeaders, cacheHeadersAllowList = [], cacheHeadersDenyList = []) {
|
|
160
|
+
let headersToStore = null;
|
|
161
|
+
if (cacheHeaders) {
|
|
162
|
+
let headers = ctx.response.headers;
|
|
163
|
+
if (cacheHeadersAllowList.length) {
|
|
164
|
+
headers = Object.fromEntries(
|
|
165
|
+
Object.entries(headers).filter(([key]) => cacheHeadersAllowList.includes(key.toLowerCase()))
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (cacheHeadersDenyList.length) {
|
|
169
|
+
headers = Object.fromEntries(
|
|
170
|
+
Object.entries(headers).filter(([key]) => !cacheHeadersDenyList.includes(key.toLowerCase()))
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
headersToStore = headers;
|
|
174
|
+
}
|
|
175
|
+
return headersToStore;
|
|
176
|
+
}
|
|
177
|
+
function getCacheHeaderConfig() {
|
|
178
|
+
const cacheHeaders = strapi.plugin("strapi-cache").config("cacheHeaders");
|
|
179
|
+
const cacheHeadersDenyList = strapi.plugin("strapi-cache").config("cacheHeadersDenyList");
|
|
180
|
+
const cacheHeadersAllowList = strapi.plugin("strapi-cache").config("cacheHeadersAllowList");
|
|
181
|
+
const cacheAuthorizedRequests = strapi.plugin("strapi-cache").config("cacheAuthorizedRequests");
|
|
182
|
+
return {
|
|
183
|
+
cacheHeaders,
|
|
184
|
+
cacheHeadersDenyList,
|
|
185
|
+
cacheHeadersAllowList,
|
|
186
|
+
cacheAuthorizedRequests
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const middleware$1 = async (ctx, next) => {
|
|
190
|
+
const cacheService = strapi.plugin("strapi-cache").services.service;
|
|
191
|
+
const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
|
|
192
|
+
const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
|
|
193
|
+
const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
|
|
194
|
+
const cacheStore = cacheService.getCacheInstance();
|
|
195
|
+
const { url } = ctx.request;
|
|
196
|
+
const key = generateCacheKey(ctx);
|
|
197
|
+
const cacheEntry = await cacheStore.get(key);
|
|
198
|
+
const cacheControlHeader = ctx.request.headers["cache-control"];
|
|
199
|
+
const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
|
|
200
|
+
const restApiPrefix = strapi.config.get("api.rest.prefix", "/api");
|
|
201
|
+
const routeIsExcluded = excludeRoutes.some((route) => url.startsWith(route));
|
|
202
|
+
if (routeIsExcluded) {
|
|
203
|
+
loggy.info(`Route excluded from cache: ${url}`);
|
|
204
|
+
await next();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const routeIsCachable = cacheableRoutes.some((route) => url.startsWith(route)) || cacheableRoutes.length === 0 && url.startsWith(restApiPrefix);
|
|
208
|
+
const authorizationHeader = ctx.request.headers["authorization"];
|
|
209
|
+
if (authorizationHeader && !cacheAuthorizedRequests) {
|
|
210
|
+
loggy.info(`Authorized request bypassing cache: ${key}`);
|
|
211
|
+
await next();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const middlewaresConfig = strapi.config.get("middlewares");
|
|
215
|
+
const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
|
|
216
|
+
const corsConfig = corsMiddleware?.config;
|
|
217
|
+
const origin = ctx?.request?.headers?.origin;
|
|
218
|
+
let allowedOrigins = corsConfig?.origin ?? "*";
|
|
219
|
+
if (typeof allowedOrigins === "string") {
|
|
220
|
+
allowedOrigins = [allowedOrigins];
|
|
221
|
+
}
|
|
222
|
+
if (cacheEntry && !noCache) {
|
|
223
|
+
loggy.info(`HIT with key: ${key}`);
|
|
224
|
+
ctx.status = 200;
|
|
225
|
+
ctx.body = cacheEntry.body;
|
|
226
|
+
if (cacheHeaders) {
|
|
227
|
+
ctx.set(cacheEntry.headers);
|
|
228
|
+
}
|
|
229
|
+
if (corsMiddleware) {
|
|
230
|
+
loggy.info("CORS middleware is set, checking allowed origins");
|
|
231
|
+
if (allowedOrigins.includes(origin)) {
|
|
232
|
+
loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
|
|
233
|
+
ctx.set("Access-Control-Allow-Origin", origin);
|
|
234
|
+
} else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
|
|
235
|
+
loggy.info("No origin header or * in allowed origins, setting to *");
|
|
236
|
+
ctx.set("Access-Control-Allow-Origin", "*");
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
loggy.info("No CORS middleware set, setting to request origin or *");
|
|
240
|
+
ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await next();
|
|
245
|
+
if (ctx.method === "GET" && ctx.status >= 200 && ctx.status < 300 && routeIsCachable) {
|
|
246
|
+
loggy.info(`MISS with key: ${key}`);
|
|
247
|
+
const headersToStore = getHeadersToStore(
|
|
248
|
+
ctx,
|
|
249
|
+
cacheHeaders,
|
|
250
|
+
cacheHeadersAllowList,
|
|
251
|
+
cacheHeadersDenyList
|
|
252
|
+
);
|
|
253
|
+
let setCache = true;
|
|
254
|
+
if (corsMiddleware) {
|
|
255
|
+
if (allowedOrigins.includes(origin)) ;
|
|
256
|
+
else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
|
|
257
|
+
else {
|
|
258
|
+
setCache = false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (ctx.body instanceof Stream__default.default) {
|
|
262
|
+
const buf = await streamToBuffer(ctx.body);
|
|
263
|
+
const contentEncoding = ctx.response.headers["content-encoding"];
|
|
264
|
+
const decompressed = await decompressBuffer(buf, contentEncoding);
|
|
265
|
+
const responseText = decodeBufferToText(decompressed);
|
|
266
|
+
if (setCache) {
|
|
267
|
+
await cacheStore.set(key, { body: responseText, headers: headersToStore });
|
|
268
|
+
}
|
|
269
|
+
ctx.body = buf;
|
|
270
|
+
} else {
|
|
271
|
+
if (setCache) {
|
|
272
|
+
await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
const middleware = async (ctx, next) => {
|
|
278
|
+
const cacheService = strapi.plugin("strapi-cache").services.service;
|
|
279
|
+
const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
|
|
280
|
+
const cacheStore = cacheService.getCacheInstance();
|
|
281
|
+
const { url } = ctx.request;
|
|
282
|
+
const originalReq = ctx.req;
|
|
283
|
+
const bodyBuffer = await rawBody__default.default(originalReq);
|
|
284
|
+
const body = bodyBuffer.toString();
|
|
285
|
+
const clonedReq = new Stream.Readable();
|
|
286
|
+
clonedReq.push(bodyBuffer);
|
|
287
|
+
clonedReq.push(null);
|
|
288
|
+
clonedReq.headers = { ...originalReq.headers };
|
|
289
|
+
clonedReq.method = originalReq.method;
|
|
290
|
+
clonedReq.url = originalReq.url;
|
|
291
|
+
clonedReq.httpVersion = originalReq.httpVersion;
|
|
292
|
+
clonedReq.socket = originalReq.socket;
|
|
293
|
+
clonedReq.connection = originalReq.connection;
|
|
294
|
+
ctx.req = clonedReq;
|
|
295
|
+
ctx.request.req = clonedReq;
|
|
296
|
+
const isIntrospectionQuery = body.includes("IntrospectionQuery");
|
|
297
|
+
if (isIntrospectionQuery) {
|
|
298
|
+
loggy.info("Skipping cache for introspection query");
|
|
299
|
+
await next();
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const key = generateGraphqlCacheKey(body);
|
|
303
|
+
const cacheEntry = await cacheStore.get(key);
|
|
304
|
+
const cacheControlHeader = ctx.request.headers["cache-control"];
|
|
305
|
+
const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
|
|
306
|
+
const authorizationHeader = ctx.request.headers["authorization"];
|
|
307
|
+
if (authorizationHeader && !cacheAuthorizedRequests) {
|
|
308
|
+
loggy.info(`Authorized request bypassing cache: ${key}`);
|
|
309
|
+
await next();
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const middlewaresConfig = strapi.config.get("middlewares");
|
|
313
|
+
const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
|
|
314
|
+
const corsConfig = corsMiddleware?.config;
|
|
315
|
+
const origin = ctx?.request?.headers?.origin;
|
|
316
|
+
let allowedOrigins = corsConfig?.origin ?? "*";
|
|
317
|
+
if (typeof allowedOrigins === "string") {
|
|
318
|
+
allowedOrigins = [allowedOrigins];
|
|
319
|
+
}
|
|
320
|
+
if (cacheEntry && !noCache) {
|
|
321
|
+
loggy.info(`HIT with key: ${key}`);
|
|
322
|
+
ctx.status = 200;
|
|
323
|
+
ctx.body = cacheEntry.body;
|
|
324
|
+
if (cacheHeaders) {
|
|
325
|
+
ctx.set(cacheEntry.headers);
|
|
326
|
+
}
|
|
327
|
+
if (corsMiddleware) {
|
|
328
|
+
loggy.info("CORS middleware is set, checking allowed origins");
|
|
329
|
+
if (allowedOrigins.includes(origin)) {
|
|
330
|
+
loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
|
|
331
|
+
ctx.set("Access-Control-Allow-Origin", origin);
|
|
332
|
+
} else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
|
|
333
|
+
loggy.info("No origin header or * in allowed origins, setting to *");
|
|
334
|
+
ctx.set("Access-Control-Allow-Origin", "*");
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
loggy.info("No CORS middleware set, setting to request origin or *");
|
|
338
|
+
ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
await next();
|
|
343
|
+
if (ctx.method === "POST" && ctx.status >= 200 && ctx.status < 300 && url.startsWith("/graphql")) {
|
|
344
|
+
loggy.info(`MISS with key: ${key}`);
|
|
345
|
+
const headers = ctx.request.headers;
|
|
346
|
+
const authorizationHeader2 = headers["authorization"];
|
|
347
|
+
if (authorizationHeader2 && !cacheAuthorizedRequests) {
|
|
348
|
+
loggy.info(`Authorized request not caching: ${key}`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const headersToStore = getHeadersToStore(
|
|
352
|
+
ctx,
|
|
353
|
+
cacheHeaders,
|
|
354
|
+
cacheHeadersAllowList,
|
|
355
|
+
cacheHeadersDenyList
|
|
356
|
+
);
|
|
357
|
+
let setCache = true;
|
|
358
|
+
if (corsMiddleware) {
|
|
359
|
+
if (allowedOrigins.includes(origin)) ;
|
|
360
|
+
else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
|
|
361
|
+
else {
|
|
362
|
+
setCache = false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (ctx.body instanceof Stream__default.default) {
|
|
366
|
+
const buf = await streamToBuffer(ctx.body);
|
|
367
|
+
const contentEncoding = ctx.response.headers["content-encoding"];
|
|
368
|
+
const decompressed = await decompressBuffer(buf, contentEncoding);
|
|
369
|
+
const responseText = decodeBufferToText(decompressed);
|
|
370
|
+
if (setCache) {
|
|
371
|
+
await cacheStore.set(key, { body: responseText, headers: headersToStore });
|
|
372
|
+
}
|
|
373
|
+
ctx.body = buf;
|
|
374
|
+
} else {
|
|
375
|
+
if (setCache) {
|
|
376
|
+
await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
const middlewares = {
|
|
382
|
+
graphql: middleware,
|
|
383
|
+
cache: middleware$1
|
|
384
|
+
};
|
|
385
|
+
const register = ({ strapi: strapi2 }) => {
|
|
386
|
+
strapi2.server.use(middlewares.cache);
|
|
387
|
+
strapi2.server.use(middlewares.graphql);
|
|
388
|
+
};
|
|
389
|
+
const config = {
|
|
390
|
+
default: ({ env }) => ({
|
|
391
|
+
debug: false,
|
|
392
|
+
max: 1e3,
|
|
393
|
+
ttl: 1e3 * 60 * 60,
|
|
394
|
+
size: 1024 * 1024 * 10,
|
|
395
|
+
allowStale: false,
|
|
396
|
+
cacheableRoutes: [],
|
|
397
|
+
provider: "memory",
|
|
398
|
+
excludeRoutes: [],
|
|
399
|
+
redisConfig: env("REDIS_URL"),
|
|
400
|
+
redisClusterNodes: [],
|
|
401
|
+
redisClusterOptions: {},
|
|
402
|
+
cacheHeaders: true,
|
|
403
|
+
cacheHeadersDenyList: [],
|
|
404
|
+
cacheHeadersAllowList: [],
|
|
405
|
+
cacheAuthorizedRequests: false,
|
|
406
|
+
cacheGetTimeoutInMs: 1e3,
|
|
407
|
+
autoPurgeCache: true,
|
|
408
|
+
autoPurgeCacheOnStart: true,
|
|
409
|
+
disableAdminPopups: false
|
|
410
|
+
}),
|
|
411
|
+
validator: (config2) => {
|
|
412
|
+
if (typeof config2.debug !== "boolean") {
|
|
413
|
+
throw new Error(`Invalid config: debug must be a boolean`);
|
|
414
|
+
}
|
|
415
|
+
if (typeof config2.max !== "number") {
|
|
416
|
+
throw new Error(`Invalid config: max must be a number`);
|
|
417
|
+
}
|
|
418
|
+
if (typeof config2.ttl !== "number") {
|
|
419
|
+
throw new Error(`Invalid config: ttl must be a number`);
|
|
420
|
+
}
|
|
421
|
+
if (typeof config2.size !== "number") {
|
|
422
|
+
throw new Error(`Invalid config: size must be a number`);
|
|
423
|
+
}
|
|
424
|
+
if (typeof config2.allowStale !== "boolean") {
|
|
425
|
+
throw new Error(`Invalid config: allowStale must be a boolean`);
|
|
426
|
+
}
|
|
427
|
+
if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
|
|
428
|
+
throw new Error(`Invalid config: cacheableRoutes must be an string array`);
|
|
429
|
+
}
|
|
430
|
+
if (!Array.isArray(config2.excludeRoutes) || config2.excludeRoutes.some((item) => typeof item !== "string")) {
|
|
431
|
+
throw new Error(`Invalid config: excludeRoutes must be a string array`);
|
|
432
|
+
}
|
|
433
|
+
if (typeof config2.provider !== "string") {
|
|
434
|
+
throw new Error(`Invalid config: provider must be a string`);
|
|
435
|
+
}
|
|
436
|
+
if (config2.provider !== "memory" && config2.provider !== "redis") {
|
|
437
|
+
throw new Error(`Invalid config: provider must be 'memory' or 'redis'`);
|
|
438
|
+
}
|
|
439
|
+
if (config2.provider === "redis") {
|
|
440
|
+
if (!config2.redisConfig) {
|
|
441
|
+
throw new Error(`Invalid config: redisConfig must be set when using redis provider`);
|
|
442
|
+
}
|
|
443
|
+
if (typeof config2.redisConfig !== "string" && typeof config2.redisConfig !== "object") {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Invalid config: redisConfig must be a string or object when using redis provider`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (!Array.isArray(config2.redisClusterNodes) || config2.redisClusterNodes.some((item) => !("host" in item && "port" in item))) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
`Invalid config: redisClusterNodes must be as a list of objects with keys 'host' and 'port'`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (typeof config2.redisClusterOptions !== "object") {
|
|
454
|
+
throw new Error(`Invalid config: redisClusterOptions must be an object`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (typeof config2.cacheHeaders !== "boolean") {
|
|
458
|
+
throw new Error(`Invalid config: cacheHeaders must be a boolean`);
|
|
459
|
+
}
|
|
460
|
+
if (!Array.isArray(config2.cacheHeadersDenyList) || config2.cacheHeadersDenyList.some((item) => typeof item !== "string")) {
|
|
461
|
+
throw new Error(`Invalid config: cacheHeadersDenyList must be an string array`);
|
|
462
|
+
}
|
|
463
|
+
if (!Array.isArray(config2.cacheHeadersAllowList) || config2.cacheHeadersAllowList.some((item) => typeof item !== "string")) {
|
|
464
|
+
throw new Error(`Invalid config: cacheHeadersAllowList must be an string array`);
|
|
465
|
+
}
|
|
466
|
+
if (typeof config2.cacheAuthorizedRequests !== "boolean") {
|
|
467
|
+
throw new Error(`Invalid config: cacheAuthorizedRequests must be a boolean`);
|
|
468
|
+
}
|
|
469
|
+
if (typeof config2.cacheGetTimeoutInMs !== "number") {
|
|
470
|
+
throw new Error(`Invalid config: cacheGetTimeoutInMs must be a number`);
|
|
471
|
+
}
|
|
472
|
+
if (typeof config2.autoPurgeCache !== "boolean") {
|
|
473
|
+
throw new Error(`Invalid config: autoPurgeCache must be a boolean`);
|
|
474
|
+
}
|
|
475
|
+
if (typeof config2.autoPurgeCacheOnStart !== "boolean") {
|
|
476
|
+
throw new Error(`Invalid config: autoPurgeCacheOnStart must be a boolean`);
|
|
477
|
+
}
|
|
478
|
+
if (typeof config2.disableAdminPopups !== "boolean") {
|
|
479
|
+
throw new Error(`Invalid config: disableAdminPopups must be a boolean`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const contentTypes = {};
|
|
484
|
+
const controller = ({ strapi: strapi2 }) => ({
|
|
485
|
+
async purgeCache(ctx) {
|
|
486
|
+
const service2 = strapi2.plugin("strapi-cache").service("service");
|
|
487
|
+
await service2.getCacheInstance().reset();
|
|
488
|
+
ctx.body = {
|
|
489
|
+
message: "Cache purged successfully"
|
|
490
|
+
};
|
|
491
|
+
},
|
|
492
|
+
async purgeCacheByKey(ctx) {
|
|
493
|
+
const { key } = ctx.request.body;
|
|
494
|
+
if (!key || typeof key !== "string" || key.trim() === "") {
|
|
495
|
+
ctx.status = 400;
|
|
496
|
+
ctx.body = {
|
|
497
|
+
error: "Key is required and must be a non-empty string"
|
|
498
|
+
};
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const service2 = strapi2.plugin("strapi-cache").service("service");
|
|
502
|
+
const regex = new RegExp(escapeRegExp(key));
|
|
503
|
+
await service2.getCacheInstance().clearByRegexp([regex]);
|
|
504
|
+
ctx.body = {
|
|
505
|
+
message: `Cache purged successfully for key: ${key}`
|
|
506
|
+
};
|
|
507
|
+
},
|
|
508
|
+
async config(ctx) {
|
|
509
|
+
try {
|
|
510
|
+
const config2 = {
|
|
511
|
+
cacheableRoutes: strapi2.plugin("strapi-cache").config("cacheableRoutes") ?? [],
|
|
512
|
+
disableAdminPopups: strapi2.plugin("strapi-cache").config("disableAdminPopups") ?? false
|
|
513
|
+
};
|
|
514
|
+
ctx.body = config2;
|
|
515
|
+
} catch (error) {
|
|
516
|
+
console.error("Error constructing config:", error);
|
|
517
|
+
ctx.status = 500;
|
|
518
|
+
ctx.body = { error: "Configuration not available" };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const controllers = {
|
|
523
|
+
controller
|
|
524
|
+
};
|
|
525
|
+
const policies = {};
|
|
526
|
+
const purgeRoute = [
|
|
527
|
+
{
|
|
528
|
+
method: "POST",
|
|
529
|
+
path: "/purge-cache",
|
|
530
|
+
handler: "controller.purgeCache",
|
|
531
|
+
config: {
|
|
532
|
+
policies: [
|
|
533
|
+
"admin::isAuthenticatedAdmin",
|
|
534
|
+
{
|
|
535
|
+
name: "plugin::content-manager.hasPermissions",
|
|
536
|
+
config: {
|
|
537
|
+
actions: ["plugin::strapi-cache.purge-cache"]
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
method: "POST",
|
|
545
|
+
path: "/purge-cache/key",
|
|
546
|
+
handler: "controller.purgeCacheByKey",
|
|
547
|
+
config: {
|
|
548
|
+
policies: [
|
|
549
|
+
"admin::isAuthenticatedAdmin",
|
|
550
|
+
{
|
|
551
|
+
name: "plugin::content-manager.hasPermissions",
|
|
552
|
+
config: {
|
|
553
|
+
actions: ["plugin::strapi-cache.purge-cache"]
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
method: "GET",
|
|
561
|
+
path: "/config",
|
|
562
|
+
handler: "controller.config",
|
|
563
|
+
config: {
|
|
564
|
+
policies: [
|
|
565
|
+
"admin::isAuthenticatedAdmin",
|
|
566
|
+
{
|
|
567
|
+
name: "plugin::content-manager.hasPermissions",
|
|
568
|
+
config: {
|
|
569
|
+
actions: ["plugin::strapi-cache.purge-cache"]
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
];
|
|
576
|
+
const routes = {
|
|
577
|
+
"purge-route": {
|
|
578
|
+
type: "admin",
|
|
579
|
+
routes: purgeRoute
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const withTimeout = (callback, ms) => {
|
|
583
|
+
let timeout = null;
|
|
584
|
+
return Promise.race([
|
|
585
|
+
callback().then((result) => {
|
|
586
|
+
if (timeout) {
|
|
587
|
+
clearTimeout(timeout);
|
|
588
|
+
}
|
|
589
|
+
return result;
|
|
590
|
+
}),
|
|
591
|
+
new Promise((_, reject) => {
|
|
592
|
+
timeout = setTimeout(() => {
|
|
593
|
+
reject(new Error("timeout"));
|
|
594
|
+
}, ms);
|
|
595
|
+
})
|
|
596
|
+
]);
|
|
597
|
+
};
|
|
598
|
+
class InMemoryCacheProvider {
|
|
599
|
+
constructor(strapi2) {
|
|
600
|
+
this.strapi = strapi2;
|
|
601
|
+
this.initialized = false;
|
|
602
|
+
}
|
|
603
|
+
init() {
|
|
604
|
+
if (this.initialized) {
|
|
605
|
+
loggy.error("Provider already initialized");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
this.initialized = true;
|
|
609
|
+
const max = Number(this.strapi.plugin("strapi-cache").config("max"));
|
|
610
|
+
const ttl = Number(this.strapi.plugin("strapi-cache").config("ttl"));
|
|
611
|
+
const size = Number(this.strapi.plugin("strapi-cache").config("size"));
|
|
612
|
+
const allowStale = Boolean(this.strapi.plugin("strapi-cache").config("allowStale"));
|
|
613
|
+
this.provider = new lruCache.LRUCache({
|
|
614
|
+
max,
|
|
615
|
+
ttl,
|
|
616
|
+
size,
|
|
617
|
+
allowStale
|
|
618
|
+
});
|
|
619
|
+
this.cacheGetTimeoutInMs = Number(
|
|
620
|
+
this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
|
|
621
|
+
);
|
|
622
|
+
loggy.info("Provider initialized");
|
|
623
|
+
}
|
|
624
|
+
get ready() {
|
|
625
|
+
if (!this.initialized) {
|
|
626
|
+
loggy.info("Provider not initialized");
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
async get(key) {
|
|
632
|
+
if (!this.ready) return null;
|
|
633
|
+
return withTimeout(
|
|
634
|
+
() => new Promise((resolve) => {
|
|
635
|
+
resolve(this.provider.get(key));
|
|
636
|
+
}),
|
|
637
|
+
this.cacheGetTimeoutInMs
|
|
638
|
+
).catch((error) => {
|
|
639
|
+
loggy.error(`Error during get: ${error?.message || error}`);
|
|
640
|
+
return null;
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
async set(key, val) {
|
|
644
|
+
if (!this.ready) return null;
|
|
645
|
+
try {
|
|
646
|
+
return this.provider.set(key, val);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
loggy.error(`Error during set: ${error}`);
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async del(key) {
|
|
653
|
+
if (!this.ready) return null;
|
|
654
|
+
try {
|
|
655
|
+
loggy.info(`PURGING KEY: ${key}`);
|
|
656
|
+
return this.provider.delete(key);
|
|
657
|
+
} catch (error) {
|
|
658
|
+
loggy.error(`Error during delete: ${error}`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
async keys() {
|
|
663
|
+
if (!this.ready) return null;
|
|
664
|
+
try {
|
|
665
|
+
return Array.from(this.provider.keys());
|
|
666
|
+
} catch (error) {
|
|
667
|
+
loggy.error(`Error fetching keys: ${error}`);
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async reset() {
|
|
672
|
+
if (!this.ready) return null;
|
|
673
|
+
try {
|
|
674
|
+
const allKeys = await this.keys();
|
|
675
|
+
if (!allKeys) return null;
|
|
676
|
+
loggy.info(`PURGING ALL KEYS: ${allKeys.length}`);
|
|
677
|
+
await Promise.all(allKeys.map((key) => this.del(key)));
|
|
678
|
+
return true;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
loggy.error(`Error during reset: ${error}`);
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async clearByRegexp(regExps) {
|
|
685
|
+
const keys = await this.keys();
|
|
686
|
+
if (!keys) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const matches = keys.filter((key) => regExps.some((re) => re.test(key)));
|
|
690
|
+
await Promise.all(matches.map((key) => this.del(key)));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
class RedisCacheProvider {
|
|
694
|
+
constructor(strapi2) {
|
|
695
|
+
this.strapi = strapi2;
|
|
696
|
+
this.initialized = false;
|
|
697
|
+
}
|
|
698
|
+
init() {
|
|
699
|
+
if (this.initialized) {
|
|
700
|
+
loggy.error("Redis provider already initialized");
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
try {
|
|
704
|
+
const redisConfig = this.strapi.plugin("strapi-cache").config("redisConfig") || "redis://localhost:6379";
|
|
705
|
+
const redisClusterNodes = this.strapi.plugin("strapi-cache").config("redisClusterNodes");
|
|
706
|
+
this.cacheGetTimeoutInMs = Number(
|
|
707
|
+
this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
|
|
708
|
+
);
|
|
709
|
+
this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
|
|
710
|
+
if (redisClusterNodes.length) {
|
|
711
|
+
const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
|
|
712
|
+
if (!redisClusterOptions["redisOptions"]) {
|
|
713
|
+
redisClusterOptions.redisOptions = redisConfig;
|
|
714
|
+
}
|
|
715
|
+
this.client = new ioredis.Redis.Cluster(redisClusterNodes, redisClusterOptions);
|
|
716
|
+
} else {
|
|
717
|
+
this.client = new ioredis.Redis(redisConfig);
|
|
718
|
+
}
|
|
719
|
+
this.initialized = true;
|
|
720
|
+
loggy.info("Redis provider initialized");
|
|
721
|
+
} catch (error) {
|
|
722
|
+
loggy.error(error);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
get ready() {
|
|
726
|
+
if (!this.initialized) {
|
|
727
|
+
loggy.info("Redis provider not initialized");
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
async get(key) {
|
|
733
|
+
if (!this.ready) return null;
|
|
734
|
+
return withTimeout(() => this.client.get(key), this.cacheGetTimeoutInMs).then((data) => data ? JSON.parse(data) : null).catch((error) => {
|
|
735
|
+
loggy.error(`Redis get error: ${error?.message || error}`);
|
|
736
|
+
return null;
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
async set(key, val) {
|
|
740
|
+
if (!this.ready) return null;
|
|
741
|
+
try {
|
|
742
|
+
const ttlInMs = Number(this.strapi.plugin("strapi-cache").config("ttl"));
|
|
743
|
+
const ttlInS = Number((ttlInMs / 1e3).toFixed());
|
|
744
|
+
const serialized = JSON.stringify(val);
|
|
745
|
+
if (ttlInS > 0) {
|
|
746
|
+
await this.client.set(key, serialized, "EX", ttlInS);
|
|
747
|
+
} else {
|
|
748
|
+
await this.client.set(key, serialized);
|
|
749
|
+
}
|
|
750
|
+
return val;
|
|
751
|
+
} catch (error) {
|
|
752
|
+
loggy.error(`Redis set error: ${error}`);
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async del(key) {
|
|
757
|
+
if (!this.ready) return null;
|
|
758
|
+
try {
|
|
759
|
+
const relativeKey = key.slice(this.keyPrefix.length);
|
|
760
|
+
loggy.info(`Redis PURGING KEY: ${relativeKey}`);
|
|
761
|
+
await this.client.del(relativeKey);
|
|
762
|
+
return true;
|
|
763
|
+
} catch (error) {
|
|
764
|
+
loggy.error(`Redis del error: ${error}`);
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async keys() {
|
|
769
|
+
if (!this.ready) return null;
|
|
770
|
+
try {
|
|
771
|
+
const keys = await this.client.keys(`${this.keyPrefix}*`);
|
|
772
|
+
return keys;
|
|
773
|
+
} catch (error) {
|
|
774
|
+
loggy.error(`Redis keys error: ${error}`);
|
|
775
|
+
return null;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
async reset() {
|
|
779
|
+
if (!this.ready) return null;
|
|
780
|
+
try {
|
|
781
|
+
if (this.keyPrefix) {
|
|
782
|
+
loggy.info(`Redis FLUSHING NAMESPACE: ${this.keyPrefix}`);
|
|
783
|
+
const keys = await this.keys();
|
|
784
|
+
if (!keys) return null;
|
|
785
|
+
const toDelete = keys.filter((key) => key.startsWith(this.keyPrefix));
|
|
786
|
+
await Promise.all(toDelete.map((key) => this.del(key)));
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
loggy.info(`Redis FLUSHING ALL KEYS`);
|
|
790
|
+
await this.client.flushdb();
|
|
791
|
+
return true;
|
|
792
|
+
} catch (error) {
|
|
793
|
+
loggy.error(`Redis reset error: ${error}`);
|
|
794
|
+
return null;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async clearByRegexp(regExps) {
|
|
798
|
+
const keys = await this.keys();
|
|
799
|
+
if (!keys) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const toDelete = keys.filter((key) => regExps.some((re) => re.test(key)));
|
|
803
|
+
await Promise.all(toDelete.map((key) => this.del(key)));
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const resolveCacheProvider = (strapi2) => {
|
|
807
|
+
const providerType = strapi2.plugin("strapi-cache").config("provider") || "memory";
|
|
808
|
+
loggy.info(`Selected cache provider: ${providerType}`);
|
|
809
|
+
let instance;
|
|
810
|
+
switch (providerType) {
|
|
811
|
+
case "redis":
|
|
812
|
+
instance = new RedisCacheProvider(strapi2);
|
|
813
|
+
break;
|
|
814
|
+
default:
|
|
815
|
+
instance = new InMemoryCacheProvider(strapi2);
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
return instance;
|
|
819
|
+
};
|
|
820
|
+
const service = ({ strapi: strapi2 }) => {
|
|
821
|
+
let instance = null;
|
|
822
|
+
return {
|
|
823
|
+
getCacheInstance() {
|
|
824
|
+
if (!instance) {
|
|
825
|
+
loggy.info("Initializing cache service from provider config...");
|
|
826
|
+
instance = resolveCacheProvider(strapi2);
|
|
827
|
+
}
|
|
828
|
+
return instance;
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
const services = {
|
|
833
|
+
service
|
|
834
|
+
};
|
|
835
|
+
const index = {
|
|
836
|
+
register,
|
|
837
|
+
bootstrap,
|
|
838
|
+
destroy() {
|
|
839
|
+
},
|
|
840
|
+
config,
|
|
841
|
+
controllers,
|
|
842
|
+
routes,
|
|
843
|
+
services,
|
|
844
|
+
contentTypes,
|
|
845
|
+
policies,
|
|
846
|
+
middlewares
|
|
847
|
+
};
|
|
848
|
+
module.exports = index;
|