directus 9.23.3 → 9.24.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/dist/app.js +15 -15
- package/dist/auth/drivers/ldap.js +22 -22
- package/dist/auth/drivers/local.js +7 -7
- package/dist/auth/drivers/oauth2.js +27 -25
- package/dist/auth/drivers/openid.js +32 -30
- package/dist/auth/drivers/saml.js +10 -10
- package/dist/auth.js +4 -3
- package/dist/cache.js +16 -11
- package/dist/cli/commands/bootstrap/index.js +5 -4
- package/dist/cli/utils/create-db-connection.js +1 -1
- package/dist/cli/utils/create-env/index.js +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +6 -5
- package/dist/controllers/activity.js +10 -10
- package/dist/controllers/assets.js +19 -18
- package/dist/controllers/auth.js +16 -16
- package/dist/controllers/collections.js +11 -11
- package/dist/controllers/dashboards.js +11 -11
- package/dist/controllers/extensions.js +3 -3
- package/dist/controllers/fields.js +17 -17
- package/dist/controllers/files.js +18 -17
- package/dist/controllers/flows.js +13 -13
- package/dist/controllers/folders.js +11 -11
- package/dist/controllers/graphql.js +6 -6
- package/dist/controllers/items.js +19 -19
- package/dist/controllers/notifications.js +11 -11
- package/dist/controllers/operations.js +11 -11
- package/dist/controllers/panels.js +11 -11
- package/dist/controllers/permissions.js +11 -11
- package/dist/controllers/presets.js +11 -11
- package/dist/controllers/relations.js +11 -11
- package/dist/controllers/revisions.js +3 -3
- package/dist/controllers/roles.js +11 -11
- package/dist/controllers/schema.js +5 -5
- package/dist/controllers/server.js +7 -7
- package/dist/controllers/settings.js +2 -2
- package/dist/controllers/shares.js +14 -14
- package/dist/controllers/users.js +16 -16
- package/dist/controllers/utils.js +7 -7
- package/dist/controllers/webhooks.js +11 -11
- package/dist/database/helpers/fn/types.d.ts +0 -1
- package/dist/database/helpers/fn/types.js +0 -2
- package/dist/database/helpers/index.d.ts +3 -3
- package/dist/database/index.js +5 -5
- package/dist/database/migrations/20210805B-change-image-metadata-structure.js +15 -15
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast.js +6 -6
- package/dist/database/system-data/collections/index.js +2 -2
- package/dist/database/system-data/fields/index.js +3 -3
- package/dist/env.js +1 -1
- package/dist/exceptions/database/dialects/mssql.js +2 -2
- package/dist/exceptions/database/dialects/mysql.js +6 -6
- package/dist/exceptions/database/record-not-unique.d.ts +1 -1
- package/dist/extensions.js +10 -10
- package/dist/flows.js +33 -31
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +32 -32
- package/dist/mailer.js +16 -16
- package/dist/messenger.js +4 -4
- package/dist/middleware/authenticate.d.ts +1 -1
- package/dist/middleware/authenticate.js +2 -2
- package/dist/middleware/cache.js +11 -11
- package/dist/middleware/collection-exists.js +4 -4
- package/dist/middleware/cors.js +8 -8
- package/dist/middleware/error-handler.js +2 -2
- package/dist/middleware/extract-token.js +3 -3
- package/dist/middleware/get-permissions.js +1 -1
- package/dist/middleware/graphql.js +12 -6
- package/dist/middleware/rate-limiter-global.js +5 -5
- package/dist/middleware/rate-limiter-ip.js +2 -2
- package/dist/middleware/respond.js +16 -16
- package/dist/middleware/sanitize-query.js +1 -1
- package/dist/middleware/schema.js +1 -1
- package/dist/middleware/use-collection.js +1 -1
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/exec/index.js +2 -2
- package/dist/rate-limiter.js +1 -1
- package/dist/request/validate-ip.js +2 -2
- package/dist/server.js +4 -4
- package/dist/services/activity.js +14 -14
- package/dist/services/assets.js +6 -6
- package/dist/services/authentication.js +9 -9
- package/dist/services/collections.js +9 -9
- package/dist/services/fields.js +5 -5
- package/dist/services/files.js +18 -18
- package/dist/services/graphql/index.js +170 -116
- package/dist/services/import-export.js +6 -6
- package/dist/services/items.js +6 -6
- package/dist/services/mail/index.js +5 -5
- package/dist/services/meta.js +1 -0
- package/dist/services/notifications.js +4 -4
- package/dist/services/relations.js +4 -4
- package/dist/services/revisions.js +3 -3
- package/dist/services/roles.js +5 -5
- package/dist/services/server.js +27 -27
- package/dist/services/shares.js +9 -9
- package/dist/services/specifications.js +5 -3
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +24 -27
- package/dist/storage/register-locations.js +1 -1
- package/dist/utils/apply-diff.js +12 -12
- package/dist/utils/apply-query.js +3 -2
- package/dist/utils/dynamic-import.js +1 -1
- package/dist/utils/generate-hash.js +1 -1
- package/dist/utils/get-ast-from-query.js +2 -2
- package/dist/utils/get-auth-providers.js +1 -1
- package/dist/utils/get-cache-headers.js +3 -3
- package/dist/utils/get-collection-from-alias.js +1 -0
- package/dist/utils/get-column-path.js +2 -1
- package/dist/utils/get-default-value.js +1 -1
- package/dist/utils/get-ip-from-req.js +2 -2
- package/dist/utils/get-permissions.js +11 -11
- package/dist/utils/get-schema.js +5 -5
- package/dist/utils/get-snapshot-diff.js +1 -1
- package/dist/utils/is-url-allowed.js +5 -2
- package/dist/utils/parse-image-metadata.js +3 -3
- package/dist/utils/reduce-schema.js +5 -5
- package/dist/utils/sanitize-query.js +26 -26
- package/dist/utils/should-skip-cache.js +13 -4
- package/dist/utils/strip-function.js +1 -1
- package/dist/utils/telemetry.d.ts +1 -0
- package/dist/utils/telemetry.js +30 -0
- package/dist/utils/validate-keys.js +1 -1
- package/dist/utils/validate-query.js +1 -1
- package/dist/utils/validate-storage.js +8 -8
- package/dist/webhooks.js +2 -2
- package/package.json +13 -13
- package/dist/utils/redact-header-cookies.d.ts +0 -1
- package/dist/utils/redact-header-cookies.js +0 -11
- package/dist/utils/track.d.ts +0 -1
- package/dist/utils/track.js +0 -81
- /package/dist/{utils/redact-header-cookies.test.d.ts → logger.test.d.ts} +0 -0
|
@@ -17,10 +17,10 @@ exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
17
17
|
let operationName = null;
|
|
18
18
|
let document;
|
|
19
19
|
if (req.method === 'GET') {
|
|
20
|
-
query = req.query
|
|
21
|
-
if (req.query
|
|
20
|
+
query = req.query['query'] || null;
|
|
21
|
+
if (req.query['variables']) {
|
|
22
22
|
try {
|
|
23
|
-
variables = (0, utils_1.parseJSON)(req.query
|
|
23
|
+
variables = (0, utils_1.parseJSON)(req.query['variables']);
|
|
24
24
|
}
|
|
25
25
|
catch {
|
|
26
26
|
throw new exceptions_1.InvalidQueryException(`Variables are invalid JSON.`);
|
|
@@ -29,7 +29,7 @@ exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
29
29
|
else {
|
|
30
30
|
variables = {};
|
|
31
31
|
}
|
|
32
|
-
operationName = req.query
|
|
32
|
+
operationName = req.query['operationName'] || null;
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
35
|
query = req.body.query || null;
|
|
@@ -56,8 +56,14 @@ exports.parseGraphQL = (0, async_handler_1.default)(async (req, res, next) => {
|
|
|
56
56
|
}
|
|
57
57
|
// Prevent caching responses when mutations are made
|
|
58
58
|
if (operationAST?.operation === 'mutation') {
|
|
59
|
-
res.locals
|
|
59
|
+
res.locals['cache'] = false;
|
|
60
60
|
}
|
|
61
|
-
res.locals
|
|
61
|
+
res.locals['graphqlParams'] = {
|
|
62
|
+
document,
|
|
63
|
+
query,
|
|
64
|
+
variables,
|
|
65
|
+
operationName,
|
|
66
|
+
contextValue: { req, res },
|
|
67
|
+
};
|
|
62
68
|
return next();
|
|
63
69
|
});
|
|
@@ -13,7 +13,7 @@ const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
|
13
13
|
const validate_env_1 = require("../utils/validate-env");
|
|
14
14
|
const RATE_LIMITER_GLOBAL_KEY = 'global-rate-limit';
|
|
15
15
|
let checkRateLimit = (_req, _res, next) => next();
|
|
16
|
-
if (env_1.default
|
|
16
|
+
if (env_1.default['RATE_LIMITER_GLOBAL_ENABLED'] === true) {
|
|
17
17
|
(0, validate_env_1.validateEnv)(['RATE_LIMITER_GLOBAL_STORE', 'RATE_LIMITER_GLOBAL_DURATION', 'RATE_LIMITER_GLOBAL_POINTS']);
|
|
18
18
|
validateConfiguration();
|
|
19
19
|
exports.rateLimiterGlobal = (0, rate_limiter_1.createRateLimiter)('RATE_LIMITER_GLOBAL');
|
|
@@ -26,7 +26,7 @@ if (env_1.default.RATE_LIMITER_GLOBAL_ENABLED === true) {
|
|
|
26
26
|
throw rateLimiterRes;
|
|
27
27
|
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
|
|
28
28
|
throw new index_1.HitRateLimitException(`Too many requests, retry after ${(0, ms_1.default)(rateLimiterRes.msBeforeNext)}.`, {
|
|
29
|
-
limit: +env_1.default
|
|
29
|
+
limit: +env_1.default['RATE_LIMITER_GLOBAL_POINTS'],
|
|
30
30
|
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -35,12 +35,12 @@ if (env_1.default.RATE_LIMITER_GLOBAL_ENABLED === true) {
|
|
|
35
35
|
}
|
|
36
36
|
exports.default = checkRateLimit;
|
|
37
37
|
function validateConfiguration() {
|
|
38
|
-
if (env_1.default
|
|
38
|
+
if (env_1.default['RATE_LIMITER_ENABLED'] !== true) {
|
|
39
39
|
logger_1.default.error(`The IP based rate limiter needs to be enabled when using the global rate limiter.`);
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
|
-
const globalPointsPerSec = Number(env_1.default
|
|
43
|
-
const regularPointsPerSec = Number(env_1.default
|
|
42
|
+
const globalPointsPerSec = Number(env_1.default['RATE_LIMITER_GLOBAL_POINTS']) / Math.max(Number(env_1.default['RATE_LIMITER_GLOBAL_DURATION']), 1);
|
|
43
|
+
const regularPointsPerSec = Number(env_1.default['RATE_LIMITER_POINTS']) / Math.max(Number(env_1.default['RATE_LIMITER_DURATION']), 1);
|
|
44
44
|
if (globalPointsPerSec <= regularPointsPerSec) {
|
|
45
45
|
logger_1.default.error(`The global rate limiter needs to allow more requests per second than the IP based rate limiter.`);
|
|
46
46
|
process.exit(1);
|
|
@@ -12,7 +12,7 @@ const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
|
12
12
|
const get_ip_from_req_1 = require("../utils/get-ip-from-req");
|
|
13
13
|
const validate_env_1 = require("../utils/validate-env");
|
|
14
14
|
let checkRateLimit = (_req, _res, next) => next();
|
|
15
|
-
if (env_1.default
|
|
15
|
+
if (env_1.default['RATE_LIMITER_ENABLED'] === true) {
|
|
16
16
|
(0, validate_env_1.validateEnv)(['RATE_LIMITER_STORE', 'RATE_LIMITER_DURATION', 'RATE_LIMITER_POINTS']);
|
|
17
17
|
exports.rateLimiter = (0, rate_limiter_1.createRateLimiter)('RATE_LIMITER');
|
|
18
18
|
checkRateLimit = (0, async_handler_1.default)(async (req, res, next) => {
|
|
@@ -24,7 +24,7 @@ if (env_1.default.RATE_LIMITER_ENABLED === true) {
|
|
|
24
24
|
throw rateLimiterRes;
|
|
25
25
|
res.set('Retry-After', String(Math.round(rateLimiterRes.msBeforeNext / 1000)));
|
|
26
26
|
throw new exceptions_1.HitRateLimitException(`Too many requests, retry after ${(0, ms_1.default)(rateLimiterRes.msBeforeNext)}.`, {
|
|
27
|
-
limit: +env_1.default
|
|
27
|
+
limit: +env_1.default['RATE_LIMITER_POINTS'],
|
|
28
28
|
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext),
|
|
29
29
|
});
|
|
30
30
|
}
|
|
@@ -18,26 +18,26 @@ const get_string_byte_size_1 = require("../utils/get-string-byte-size");
|
|
|
18
18
|
exports.respond = (0, async_handler_1.default)(async (req, res) => {
|
|
19
19
|
const { cache } = (0, cache_1.getCache)();
|
|
20
20
|
let exceedsMaxSize = false;
|
|
21
|
-
if (env_1.default
|
|
22
|
-
const valueSize = res.locals
|
|
23
|
-
const maxSize = (0, bytes_1.parse)(env_1.default
|
|
21
|
+
if (env_1.default['CACHE_VALUE_MAX_SIZE'] !== false) {
|
|
22
|
+
const valueSize = res.locals['payload'] ? (0, get_string_byte_size_1.stringByteSize)(JSON.stringify(res.locals['payload'])) : 0;
|
|
23
|
+
const maxSize = (0, bytes_1.parse)(env_1.default['CACHE_VALUE_MAX_SIZE']);
|
|
24
24
|
exceedsMaxSize = valueSize > maxSize;
|
|
25
25
|
}
|
|
26
26
|
if ((req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
|
|
27
|
-
env_1.default
|
|
27
|
+
env_1.default['CACHE_ENABLED'] === true &&
|
|
28
28
|
cache &&
|
|
29
29
|
!req.sanitizedQuery.export &&
|
|
30
|
-
res.locals
|
|
30
|
+
res.locals['cache'] !== false &&
|
|
31
31
|
exceedsMaxSize === false) {
|
|
32
32
|
const key = (0, get_cache_key_1.getCacheKey)(req);
|
|
33
33
|
try {
|
|
34
|
-
await (0, cache_1.setCacheValue)(cache, key, res.locals
|
|
35
|
-
await (0, cache_1.setCacheValue)(cache, `${key}__expires_at`, { exp: Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
34
|
+
await (0, cache_1.setCacheValue)(cache, key, res.locals['payload'], (0, get_milliseconds_1.getMilliseconds)(env_1.default['CACHE_TTL']));
|
|
35
|
+
await (0, cache_1.setCacheValue)(cache, `${key}__expires_at`, { exp: Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default['CACHE_TTL'], 0) });
|
|
36
36
|
}
|
|
37
37
|
catch (err) {
|
|
38
38
|
logger_1.default.warn(err, `[cache] Couldn't set key ${key}. ${err}`);
|
|
39
39
|
}
|
|
40
|
-
res.setHeader('Cache-Control', (0, get_cache_headers_1.getCacheControlHeader)(req, (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
40
|
+
res.setHeader('Cache-Control', (0, get_cache_headers_1.getCacheControlHeader)(req, (0, get_milliseconds_1.getMilliseconds)(env_1.default['CACHE_TTL']), true, true));
|
|
41
41
|
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
@@ -58,29 +58,29 @@ exports.respond = (0, async_handler_1.default)(async (req, res) => {
|
|
|
58
58
|
if (req.sanitizedQuery.export === 'json') {
|
|
59
59
|
res.attachment(`${filename}.json`);
|
|
60
60
|
res.set('Content-Type', 'application/json');
|
|
61
|
-
return res.status(200).send(exportService.transform(res.locals
|
|
61
|
+
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'json'));
|
|
62
62
|
}
|
|
63
63
|
if (req.sanitizedQuery.export === 'xml') {
|
|
64
64
|
res.attachment(`${filename}.xml`);
|
|
65
65
|
res.set('Content-Type', 'text/xml');
|
|
66
|
-
return res.status(200).send(exportService.transform(res.locals
|
|
66
|
+
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'xml'));
|
|
67
67
|
}
|
|
68
68
|
if (req.sanitizedQuery.export === 'csv') {
|
|
69
69
|
res.attachment(`${filename}.csv`);
|
|
70
70
|
res.set('Content-Type', 'text/csv');
|
|
71
|
-
return res.status(200).send(exportService.transform(res.locals
|
|
71
|
+
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'csv'));
|
|
72
72
|
}
|
|
73
73
|
if (req.sanitizedQuery.export === 'yaml') {
|
|
74
74
|
res.attachment(`${filename}.yaml`);
|
|
75
75
|
res.set('Content-Type', 'text/yaml');
|
|
76
|
-
return res.status(200).send(exportService.transform(res.locals
|
|
76
|
+
return res.status(200).send(exportService.transform(res.locals['payload']?.data, 'yaml'));
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
if (Buffer.isBuffer(res.locals
|
|
80
|
-
return res.end(res.locals
|
|
79
|
+
if (Buffer.isBuffer(res.locals['payload'])) {
|
|
80
|
+
return res.end(res.locals['payload']);
|
|
81
81
|
}
|
|
82
|
-
else if (res.locals
|
|
83
|
-
return res.json(res.locals
|
|
82
|
+
else if (res.locals['payload']) {
|
|
83
|
+
return res.json(res.locals['payload']);
|
|
84
84
|
}
|
|
85
85
|
else {
|
|
86
86
|
return res.status(204).end();
|
|
@@ -11,7 +11,7 @@ const sanitizeQueryMiddleware = (req, _res, next) => {
|
|
|
11
11
|
if (!req.query)
|
|
12
12
|
return;
|
|
13
13
|
req.sanitizedQuery = (0, sanitize_query_1.sanitizeQuery)({
|
|
14
|
-
fields: req.query
|
|
14
|
+
fields: req.query['fields'] || '*',
|
|
15
15
|
...req.query,
|
|
16
16
|
}, req.accountability || null);
|
|
17
17
|
Object.freeze(req.sanitizedQuery);
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
7
7
|
const get_schema_1 = require("../utils/get-schema");
|
|
8
|
-
const schema = (0, async_handler_1.default)(async (req,
|
|
8
|
+
const schema = (0, async_handler_1.default)(async (req, _res, next) => {
|
|
9
9
|
req.schema = await (0, get_schema_1.getSchema)();
|
|
10
10
|
return next();
|
|
11
11
|
});
|
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
7
|
-
const useCollection = (collection) => (0, async_handler_1.default)(async (req,
|
|
7
|
+
const useCollection = (collection) => (0, async_handler_1.default)(async (req, _res, next) => {
|
|
8
8
|
req.collection = collection;
|
|
9
9
|
next();
|
|
10
10
|
});
|
|
@@ -9,7 +9,7 @@ const exceptions_1 = require("../exceptions");
|
|
|
9
9
|
const exceptions_2 = require("@directus/shared/exceptions");
|
|
10
10
|
const async_handler_1 = __importDefault(require("../utils/async-handler"));
|
|
11
11
|
const sanitize_query_1 = require("../utils/sanitize-query");
|
|
12
|
-
const validateBatch = (scope) => (0, async_handler_1.default)(async (req,
|
|
12
|
+
const validateBatch = (scope) => (0, async_handler_1.default)(async (req, _res, next) => {
|
|
13
13
|
if (req.method.toLowerCase() === 'get') {
|
|
14
14
|
req.body = {};
|
|
15
15
|
return next();
|
|
@@ -6,10 +6,10 @@ const node_module_1 = require("node:module");
|
|
|
6
6
|
exports.default = (0, utils_1.defineOperationApi)({
|
|
7
7
|
id: 'exec',
|
|
8
8
|
handler: async ({ code }, { data, env }) => {
|
|
9
|
-
const allowedModules = env
|
|
9
|
+
const allowedModules = env['FLOWS_EXEC_ALLOWED_MODULES'] ? (0, utils_1.toArray)(env['FLOWS_EXEC_ALLOWED_MODULES']) : [];
|
|
10
10
|
const allowedModulesBuiltIn = [];
|
|
11
11
|
const allowedModulesExternal = [];
|
|
12
|
-
const allowedEnv = data
|
|
12
|
+
const allowedEnv = data['$env'] ?? {};
|
|
13
13
|
const opts = {
|
|
14
14
|
eval: false,
|
|
15
15
|
wasm: false,
|
package/dist/rate-limiter.js
CHANGED
|
@@ -9,7 +9,7 @@ const rate_limiter_flexible_1 = require("rate-limiter-flexible");
|
|
|
9
9
|
const env_1 = __importDefault(require("./env"));
|
|
10
10
|
const get_config_from_env_1 = require("./utils/get-config-from-env");
|
|
11
11
|
function createRateLimiter(configPrefix = 'RATE_LIMITER', configOverrides) {
|
|
12
|
-
switch (env_1.default
|
|
12
|
+
switch (env_1.default['RATE_LIMITER_STORE']) {
|
|
13
13
|
case 'redis':
|
|
14
14
|
return new rate_limiter_flexible_1.RateLimiterRedis(getConfig('redis', configPrefix, configOverrides));
|
|
15
15
|
case 'memcache':
|
|
@@ -8,10 +8,10 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
8
8
|
const env_1 = require("../env");
|
|
9
9
|
const validateIP = async (ip, url) => {
|
|
10
10
|
const env = (0, env_1.getEnv)();
|
|
11
|
-
if (env
|
|
11
|
+
if (env['IMPORT_IP_DENY_LIST'].includes(ip)) {
|
|
12
12
|
throw new Error(`Requested URL "${url}" resolves to a denied IP address`);
|
|
13
13
|
}
|
|
14
|
-
if (env
|
|
14
|
+
if (env['IMPORT_IP_DENY_LIST'].includes('0.0.0.0')) {
|
|
15
15
|
const networkInterfaces = node_os_1.default.networkInterfaces();
|
|
16
16
|
for (const networkInfo of Object.values(networkInterfaces)) {
|
|
17
17
|
if (!networkInfo)
|
package/dist/server.js
CHANGED
|
@@ -106,7 +106,7 @@ async function createServer() {
|
|
|
106
106
|
(0, terminus_1.createTerminus)(server, terminusOptions);
|
|
107
107
|
return server;
|
|
108
108
|
async function beforeShutdown() {
|
|
109
|
-
if (env_1.default
|
|
109
|
+
if (env_1.default['NODE_ENV'] !== 'development') {
|
|
110
110
|
logger_1.default.info('Shutting down...');
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -121,7 +121,7 @@ async function createServer() {
|
|
|
121
121
|
schema: null,
|
|
122
122
|
accountability: null,
|
|
123
123
|
});
|
|
124
|
-
if (env_1.default
|
|
124
|
+
if (env_1.default['NODE_ENV'] !== 'development') {
|
|
125
125
|
logger_1.default.info('Directus shut down OK. Bye bye!');
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -129,8 +129,8 @@ async function createServer() {
|
|
|
129
129
|
exports.createServer = createServer;
|
|
130
130
|
async function startServer() {
|
|
131
131
|
const server = await createServer();
|
|
132
|
-
const host = env_1.default
|
|
133
|
-
const port = env_1.default
|
|
132
|
+
const host = env_1.default['HOST'];
|
|
133
|
+
const port = env_1.default['PORT'];
|
|
134
134
|
server
|
|
135
135
|
.listen(port, host, () => {
|
|
136
136
|
(0, update_check_1.default)(package_json_1.default)
|
|
@@ -26,9 +26,9 @@ class ActivityService extends items_1.ItemsService {
|
|
|
26
26
|
this.usersService = new users_1.UsersService({ schema: this.schema });
|
|
27
27
|
}
|
|
28
28
|
async createOne(data, opts) {
|
|
29
|
-
if (data
|
|
29
|
+
if (data['action'] === types_1.Action.COMMENT && typeof data['comment'] === 'string') {
|
|
30
30
|
const usersRegExp = new RegExp(/@[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/gi);
|
|
31
|
-
const mentions = (0, lodash_1.uniq)(data
|
|
31
|
+
const mentions = (0, lodash_1.uniq)(data['comment'].match(usersRegExp) ?? []);
|
|
32
32
|
const sender = await this.usersService.readOne(this.accountability.user, {
|
|
33
33
|
fields: ['id', 'first_name', 'last_name', 'email'],
|
|
34
34
|
});
|
|
@@ -39,24 +39,24 @@ class ActivityService extends items_1.ItemsService {
|
|
|
39
39
|
});
|
|
40
40
|
const accountability = {
|
|
41
41
|
user: userID,
|
|
42
|
-
role: user
|
|
43
|
-
admin: user
|
|
44
|
-
app: user
|
|
42
|
+
role: user['role']?.id ?? null,
|
|
43
|
+
admin: user['role']?.admin_access ?? null,
|
|
44
|
+
app: user['role']?.app_access ?? null,
|
|
45
45
|
};
|
|
46
46
|
accountability.permissions = await (0, get_permissions_1.getPermissions)(accountability, this.schema);
|
|
47
47
|
const authorizationService = new authorization_1.AuthorizationService({ schema: this.schema, accountability });
|
|
48
48
|
const usersService = new users_1.UsersService({ schema: this.schema, accountability });
|
|
49
49
|
try {
|
|
50
|
-
await authorizationService.checkAccess('read', data
|
|
50
|
+
await authorizationService.checkAccess('read', data['collection'], data['item']);
|
|
51
51
|
const templateData = await usersService.readByQuery({
|
|
52
52
|
fields: ['id', 'first_name', 'last_name', 'email'],
|
|
53
53
|
filter: { id: { _in: mentions.map((mention) => mention.substring(1)) } },
|
|
54
54
|
});
|
|
55
55
|
const userPreviews = templateData.reduce((acc, user) => {
|
|
56
|
-
acc[user
|
|
56
|
+
acc[user['id']] = `<em>${(0, user_name_1.userName)(user)}</em>`;
|
|
57
57
|
return acc;
|
|
58
58
|
}, {});
|
|
59
|
-
let comment = data
|
|
59
|
+
let comment = data['comment'];
|
|
60
60
|
for (const mention of mentions) {
|
|
61
61
|
const uuid = mention.substring(1);
|
|
62
62
|
// We only match on UUIDs in the first place. This is just an extra sanity check
|
|
@@ -72,17 +72,17 @@ ${(0, user_name_1.userName)(sender)} has mentioned you in a comment:
|
|
|
72
72
|
|
|
73
73
|
${comment}
|
|
74
74
|
|
|
75
|
-
<a href="${new url_1.Url(env_1.default
|
|
76
|
-
.addPath('admin', 'content', data
|
|
75
|
+
<a href="${new url_1.Url(env_1.default['PUBLIC_URL'])
|
|
76
|
+
.addPath('admin', 'content', data['collection'], data['item'])
|
|
77
77
|
.toString()}">Click here to view.</a>
|
|
78
78
|
`;
|
|
79
79
|
await this.notificationsService.createOne({
|
|
80
80
|
recipient: userID,
|
|
81
|
-
sender: sender
|
|
82
|
-
subject: `You were mentioned in ${data
|
|
81
|
+
sender: sender['id'],
|
|
82
|
+
subject: `You were mentioned in ${data['collection']}`,
|
|
83
83
|
message,
|
|
84
|
-
collection: data
|
|
85
|
-
item: data
|
|
84
|
+
collection: data['collection'],
|
|
85
|
+
item: data['item'],
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
catch (err) {
|
package/dist/services/assets.js
CHANGED
|
@@ -131,24 +131,24 @@ class AssetsService {
|
|
|
131
131
|
const { width, height } = file;
|
|
132
132
|
if (!width ||
|
|
133
133
|
!height ||
|
|
134
|
-
width > env_1.default
|
|
135
|
-
height > env_1.default
|
|
134
|
+
width > env_1.default['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'] ||
|
|
135
|
+
height > env_1.default['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION']) {
|
|
136
136
|
throw new exceptions_1.IllegalAssetTransformation(`Image is too large to be transformed, or image size couldn't be determined.`);
|
|
137
137
|
}
|
|
138
138
|
const { queue, process } = sharp_1.default.counters();
|
|
139
|
-
if (queue + process > env_1.default
|
|
139
|
+
if (queue + process > env_1.default['ASSETS_TRANSFORM_MAX_CONCURRENT']) {
|
|
140
140
|
throw new service_unavailable_1.ServiceUnavailableException('Server too busy', {
|
|
141
141
|
service: 'files',
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
const readStream = await storage.location(file.storage).read(file.filename_disk, range);
|
|
145
145
|
const transformer = (0, sharp_1.default)({
|
|
146
|
-
limitInputPixels: Math.pow(env_1.default
|
|
146
|
+
limitInputPixels: Math.pow(env_1.default['ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION'], 2),
|
|
147
147
|
sequentialRead: true,
|
|
148
|
-
failOn: env_1.default
|
|
148
|
+
failOn: env_1.default['ASSETS_INVALID_IMAGE_SENSITIVITY_LEVEL'],
|
|
149
149
|
});
|
|
150
150
|
transformer.timeout({
|
|
151
|
-
seconds: (0, lodash_1.clamp)(Math.round((0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
151
|
+
seconds: (0, lodash_1.clamp)(Math.round((0, get_milliseconds_1.getMilliseconds)(env_1.default['ASSETS_TRANSFORM_TIMEOUT'], 0) / 1000), 1, 3600),
|
|
152
152
|
});
|
|
153
153
|
if (transforms.find((transform) => transform[0] === 'rotate') === undefined)
|
|
154
154
|
transformer.rotate();
|
|
@@ -40,7 +40,7 @@ class AuthenticationService {
|
|
|
40
40
|
*/
|
|
41
41
|
async login(providerName = constants_1.DEFAULT_AUTH_PROVIDER, payload, otp) {
|
|
42
42
|
const { nanoid } = await import('nanoid');
|
|
43
|
-
const STALL_TIME = env_1.default
|
|
43
|
+
const STALL_TIME = env_1.default['LOGIN_STALL_TIME'];
|
|
44
44
|
const timeStart = perf_hooks_1.performance.now();
|
|
45
45
|
const provider = (0, auth_1.getAuthProvider)(providerName);
|
|
46
46
|
let userId;
|
|
@@ -150,12 +150,12 @@ class AuthenticationService {
|
|
|
150
150
|
schema: this.schema,
|
|
151
151
|
accountability: this.accountability,
|
|
152
152
|
});
|
|
153
|
-
const accessToken = jsonwebtoken_1.default.sign(customClaims, env_1.default
|
|
154
|
-
expiresIn: env_1.default
|
|
153
|
+
const accessToken = jsonwebtoken_1.default.sign(customClaims, env_1.default['SECRET'], {
|
|
154
|
+
expiresIn: env_1.default['ACCESS_TOKEN_TTL'],
|
|
155
155
|
issuer: 'directus',
|
|
156
156
|
});
|
|
157
157
|
const refreshToken = nanoid(64);
|
|
158
|
-
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
158
|
+
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default['REFRESH_TOKEN_TTL'], 0));
|
|
159
159
|
await this.knex('directus_sessions').insert({
|
|
160
160
|
token: refreshToken,
|
|
161
161
|
user: user.id,
|
|
@@ -185,7 +185,7 @@ class AuthenticationService {
|
|
|
185
185
|
return {
|
|
186
186
|
accessToken,
|
|
187
187
|
refreshToken,
|
|
188
|
-
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
188
|
+
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default['ACCESS_TOKEN_TTL']),
|
|
189
189
|
id: user.id,
|
|
190
190
|
};
|
|
191
191
|
}
|
|
@@ -280,12 +280,12 @@ class AuthenticationService {
|
|
|
280
280
|
schema: this.schema,
|
|
281
281
|
accountability: this.accountability,
|
|
282
282
|
});
|
|
283
|
-
const accessToken = jsonwebtoken_1.default.sign(customClaims, env_1.default
|
|
284
|
-
expiresIn: env_1.default
|
|
283
|
+
const accessToken = jsonwebtoken_1.default.sign(customClaims, env_1.default['SECRET'], {
|
|
284
|
+
expiresIn: env_1.default['ACCESS_TOKEN_TTL'],
|
|
285
285
|
issuer: 'directus',
|
|
286
286
|
});
|
|
287
287
|
const newRefreshToken = nanoid(64);
|
|
288
|
-
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
288
|
+
const refreshTokenExpiration = new Date(Date.now() + (0, get_milliseconds_1.getMilliseconds)(env_1.default['REFRESH_TOKEN_TTL'], 0));
|
|
289
289
|
await this.knex('directus_sessions')
|
|
290
290
|
.update({
|
|
291
291
|
token: newRefreshToken,
|
|
@@ -298,7 +298,7 @@ class AuthenticationService {
|
|
|
298
298
|
return {
|
|
299
299
|
accessToken,
|
|
300
300
|
refreshToken: newRefreshToken,
|
|
301
|
-
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default
|
|
301
|
+
expires: (0, get_milliseconds_1.getMilliseconds)(env_1.default['ACCESS_TOKEN_TTL']),
|
|
302
302
|
id: record.user_id,
|
|
303
303
|
};
|
|
304
304
|
}
|
|
@@ -157,7 +157,7 @@ class CollectionsService {
|
|
|
157
157
|
return payload.collection;
|
|
158
158
|
}
|
|
159
159
|
finally {
|
|
160
|
-
if (this.cache && env_1.default
|
|
160
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
161
161
|
await this.cache.clear();
|
|
162
162
|
}
|
|
163
163
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -198,7 +198,7 @@ class CollectionsService {
|
|
|
198
198
|
return collections;
|
|
199
199
|
}
|
|
200
200
|
finally {
|
|
201
|
-
if (this.cache && env_1.default
|
|
201
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
202
202
|
await this.cache.clear();
|
|
203
203
|
}
|
|
204
204
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -270,8 +270,8 @@ class CollectionsService {
|
|
|
270
270
|
});
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
-
if (env_1.default
|
|
274
|
-
return collections.filter((collection) => env_1.default
|
|
273
|
+
if (env_1.default['DB_EXCLUDE_TABLES']) {
|
|
274
|
+
return collections.filter((collection) => env_1.default['DB_EXCLUDE_TABLES'].includes(collection.collection) === false);
|
|
275
275
|
}
|
|
276
276
|
return collections;
|
|
277
277
|
}
|
|
@@ -342,7 +342,7 @@ class CollectionsService {
|
|
|
342
342
|
return collectionKey;
|
|
343
343
|
}
|
|
344
344
|
finally {
|
|
345
|
-
if (this.cache && env_1.default
|
|
345
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
346
346
|
await this.cache.clear();
|
|
347
347
|
}
|
|
348
348
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -390,7 +390,7 @@ class CollectionsService {
|
|
|
390
390
|
});
|
|
391
391
|
}
|
|
392
392
|
finally {
|
|
393
|
-
if (this.cache && env_1.default
|
|
393
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
394
394
|
await this.cache.clear();
|
|
395
395
|
}
|
|
396
396
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -432,7 +432,7 @@ class CollectionsService {
|
|
|
432
432
|
return collectionKeys;
|
|
433
433
|
}
|
|
434
434
|
finally {
|
|
435
|
-
if (this.cache && env_1.default
|
|
435
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
436
436
|
await this.cache.clear();
|
|
437
437
|
}
|
|
438
438
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -535,7 +535,7 @@ class CollectionsService {
|
|
|
535
535
|
return collectionKey;
|
|
536
536
|
}
|
|
537
537
|
finally {
|
|
538
|
-
if (this.cache && env_1.default
|
|
538
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
539
539
|
await this.cache.clear();
|
|
540
540
|
}
|
|
541
541
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -576,7 +576,7 @@ class CollectionsService {
|
|
|
576
576
|
return collectionKeys;
|
|
577
577
|
}
|
|
578
578
|
finally {
|
|
579
|
-
if (this.cache && env_1.default
|
|
579
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
580
580
|
await this.cache.clear();
|
|
581
581
|
}
|
|
582
582
|
if (opts?.autoPurgeSystemCache !== false) {
|
package/dist/services/fields.js
CHANGED
|
@@ -298,7 +298,7 @@ class FieldsService {
|
|
|
298
298
|
if (runPostColumnChange) {
|
|
299
299
|
await this.helpers.schema.postColumnChange();
|
|
300
300
|
}
|
|
301
|
-
if (this.cache && env_1.default
|
|
301
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
302
302
|
await this.cache.clear();
|
|
303
303
|
}
|
|
304
304
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -393,7 +393,7 @@ class FieldsService {
|
|
|
393
393
|
if (runPostColumnChange) {
|
|
394
394
|
await this.helpers.schema.postColumnChange();
|
|
395
395
|
}
|
|
396
|
-
if (this.cache && env_1.default
|
|
396
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
397
397
|
await this.cache.clear();
|
|
398
398
|
}
|
|
399
399
|
if (opts?.autoPurgeSystemCache !== false) {
|
|
@@ -478,10 +478,10 @@ class FieldsService {
|
|
|
478
478
|
.first();
|
|
479
479
|
const collectionMetaUpdates = {};
|
|
480
480
|
if (collectionMeta?.archive_field === field) {
|
|
481
|
-
collectionMetaUpdates
|
|
481
|
+
collectionMetaUpdates['archive_field'] = null;
|
|
482
482
|
}
|
|
483
483
|
if (collectionMeta?.sort_field === field) {
|
|
484
|
-
collectionMetaUpdates
|
|
484
|
+
collectionMetaUpdates['sort_field'] = null;
|
|
485
485
|
}
|
|
486
486
|
if (Object.keys(collectionMetaUpdates).length > 0) {
|
|
487
487
|
await trx('directus_collections').update(collectionMetaUpdates).where({ collection });
|
|
@@ -523,7 +523,7 @@ class FieldsService {
|
|
|
523
523
|
if (runPostColumnChange) {
|
|
524
524
|
await this.helpers.schema.postColumnChange();
|
|
525
525
|
}
|
|
526
|
-
if (this.cache && env_1.default
|
|
526
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
527
527
|
await this.cache.clear();
|
|
528
528
|
}
|
|
529
529
|
if (opts?.autoPurgeSystemCache !== false) {
|
package/dist/services/files.js
CHANGED
|
@@ -79,12 +79,12 @@ class FilesService extends items_1.ItemsService {
|
|
|
79
79
|
if (['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/tiff'].includes(payload.type)) {
|
|
80
80
|
const stream = await storage.location(data.storage).read(payload.filename_disk);
|
|
81
81
|
const { height, width, description, title, tags, metadata } = await this.getMetadata(stream);
|
|
82
|
-
payload.height
|
|
83
|
-
payload.width
|
|
84
|
-
payload.description
|
|
85
|
-
payload.title
|
|
86
|
-
payload.tags
|
|
87
|
-
payload.metadata
|
|
82
|
+
payload.height ??= height ?? null;
|
|
83
|
+
payload.width ??= width ?? null;
|
|
84
|
+
payload.description ??= description ?? null;
|
|
85
|
+
payload.title ??= title ?? null;
|
|
86
|
+
payload.tags ??= tags ?? null;
|
|
87
|
+
payload.metadata ??= metadata ?? null;
|
|
88
88
|
}
|
|
89
89
|
// We do this in a service without accountability. Even if you don't have update permissions to the file,
|
|
90
90
|
// we still want to be able to set the extracted values from the file on create
|
|
@@ -93,7 +93,7 @@ class FilesService extends items_1.ItemsService {
|
|
|
93
93
|
schema: this.schema,
|
|
94
94
|
});
|
|
95
95
|
await sudoService.updateOne(primaryKey, payload, { emitEvents: false });
|
|
96
|
-
if (this.cache && env_1.default
|
|
96
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
97
97
|
await this.cache.clear();
|
|
98
98
|
}
|
|
99
99
|
if (opts?.emitEvents !== false) {
|
|
@@ -112,7 +112,7 @@ class FilesService extends items_1.ItemsService {
|
|
|
112
112
|
/**
|
|
113
113
|
* Extract metadata from a buffer's content
|
|
114
114
|
*/
|
|
115
|
-
async getMetadata(stream, allowList = env_1.default
|
|
115
|
+
async getMetadata(stream, allowList = env_1.default['FILE_METADATA_ALLOW_LIST']) {
|
|
116
116
|
return new Promise((resolve, reject) => {
|
|
117
117
|
(0, promises_1.pipeline)(stream, (0, sharp_1.default)().metadata(async (err, sharpMetadata) => {
|
|
118
118
|
if (err) {
|
|
@@ -176,14 +176,14 @@ class FilesService extends items_1.ItemsService {
|
|
|
176
176
|
logger_1.default.warn(err);
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
-
if (fullMetadata?.iptc?.Caption && typeof fullMetadata.iptc
|
|
180
|
-
metadata.description = fullMetadata.iptc?.Caption;
|
|
179
|
+
if (fullMetadata?.iptc?.['Caption'] && typeof fullMetadata.iptc['Caption'] === 'string') {
|
|
180
|
+
metadata.description = fullMetadata.iptc?.['Caption'];
|
|
181
181
|
}
|
|
182
|
-
if (fullMetadata?.iptc?.Headline && typeof fullMetadata.iptc
|
|
183
|
-
metadata.title = fullMetadata.iptc
|
|
182
|
+
if (fullMetadata?.iptc?.['Headline'] && typeof fullMetadata.iptc['Headline'] === 'string') {
|
|
183
|
+
metadata.title = fullMetadata.iptc['Headline'];
|
|
184
184
|
}
|
|
185
|
-
if (fullMetadata?.iptc?.Keywords) {
|
|
186
|
-
metadata.tags = fullMetadata.iptc
|
|
185
|
+
if (fullMetadata?.iptc?.['Keywords']) {
|
|
186
|
+
metadata.tags = fullMetadata.iptc['Keywords'];
|
|
187
187
|
}
|
|
188
188
|
if (allowList === '*' || allowList?.[0] === '*') {
|
|
189
189
|
metadata.metadata = fullMetadata;
|
|
@@ -229,7 +229,7 @@ class FilesService extends items_1.ItemsService {
|
|
|
229
229
|
const filename = decodeURI(path_1.default.basename(parsedURL.pathname));
|
|
230
230
|
const payload = {
|
|
231
231
|
filename_download: filename,
|
|
232
|
-
storage: (0, utils_1.toArray)(env_1.default
|
|
232
|
+
storage: (0, utils_1.toArray)(env_1.default['STORAGE_LOCATIONS'])[0],
|
|
233
233
|
type: fileResponse.headers['content-type'],
|
|
234
234
|
title: (0, format_title_1.default)(filename),
|
|
235
235
|
...(body || {}),
|
|
@@ -265,13 +265,13 @@ class FilesService extends items_1.ItemsService {
|
|
|
265
265
|
}
|
|
266
266
|
await super.deleteMany(keys);
|
|
267
267
|
for (const file of files) {
|
|
268
|
-
const disk = storage.location(file
|
|
268
|
+
const disk = storage.location(file['storage']);
|
|
269
269
|
// Delete file + thumbnails
|
|
270
|
-
for await (const filepath of disk.list(file
|
|
270
|
+
for await (const filepath of disk.list(file['id'])) {
|
|
271
271
|
await disk.delete(filepath);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
|
-
if (this.cache && env_1.default
|
|
274
|
+
if (this.cache && env_1.default['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
|
|
275
275
|
await this.cache.clear();
|
|
276
276
|
}
|
|
277
277
|
return keys;
|