@directus/api 14.0.0 → 14.0.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/dist/auth/drivers/oauth2.js +8 -3
- package/dist/auth/drivers/openid.js +7 -3
- package/dist/cli/index.js +2 -6
- package/dist/cli/load-extensions.d.ts +1 -0
- package/dist/cli/load-extensions.js +19 -0
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +10 -1
- package/dist/database/system-data/fields/users.yaml +2 -0
- package/dist/emitter.d.ts +2 -2
- package/dist/extensions/manager.js +11 -11
- package/dist/middleware/respond.js +10 -10
- package/dist/middleware/validate-batch.js +2 -0
- package/dist/server.js +2 -1
- package/dist/services/extensions.js +16 -1
- package/dist/services/files.js +4 -4
- package/dist/services/versions.js +6 -0
- package/dist/utils/delete-from-require-cache.js +8 -1
- package/dist/utils/redact-object.d.ts +1 -1
- package/dist/utils/redact-object.js +37 -24
- package/dist/utils/sanitize-query.js +1 -0
- package/dist/worker-pool.js +8 -0
- package/package.json +9 -9
|
@@ -54,8 +54,9 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
54
54
|
return generators.codeVerifier();
|
|
55
55
|
}
|
|
56
56
|
generateAuthUrl(codeVerifier, prompt = false) {
|
|
57
|
+
const { plainCodeChallenge } = this.config;
|
|
57
58
|
try {
|
|
58
|
-
const codeChallenge = generators.codeChallenge(codeVerifier);
|
|
59
|
+
const codeChallenge = plainCodeChallenge ? codeVerifier : generators.codeChallenge(codeVerifier);
|
|
59
60
|
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
|
|
60
61
|
return this.client.authorizationUrl({
|
|
61
62
|
scope: this.config['scope'] ?? 'email',
|
|
@@ -63,7 +64,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
63
64
|
prompt: prompt ? 'consent' : undefined,
|
|
64
65
|
...paramsConfig,
|
|
65
66
|
code_challenge: codeChallenge,
|
|
66
|
-
code_challenge_method: 'S256',
|
|
67
|
+
code_challenge_method: plainCodeChallenge ? 'plain' : 'S256',
|
|
67
68
|
// Some providers require state even with PKCE
|
|
68
69
|
state: codeChallenge,
|
|
69
70
|
});
|
|
@@ -85,10 +86,14 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
|
85
86
|
logger.warn('[OAuth2] No code, codeVerifier or state in payload');
|
|
86
87
|
throw new InvalidCredentialsError();
|
|
87
88
|
}
|
|
89
|
+
const { plainCodeChallenge } = this.config;
|
|
88
90
|
let tokenSet;
|
|
89
91
|
let userInfo;
|
|
90
92
|
try {
|
|
91
|
-
|
|
93
|
+
const codeChallenge = plainCodeChallenge
|
|
94
|
+
? payload['codeVerifier']
|
|
95
|
+
: generators.codeChallenge(payload['codeVerifier']);
|
|
96
|
+
tokenSet = await this.client.oauthCallback(this.redirectUrl, { code: payload['code'], state: payload['state'] }, { code_verifier: payload['codeVerifier'], state: codeChallenge });
|
|
92
97
|
userInfo = await this.client.userinfo(tokenSet.access_token);
|
|
93
98
|
}
|
|
94
99
|
catch (e) {
|
|
@@ -64,9 +64,10 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
64
64
|
return generators.codeVerifier();
|
|
65
65
|
}
|
|
66
66
|
async generateAuthUrl(codeVerifier, prompt = false) {
|
|
67
|
+
const { plainCodeChallenge } = this.config;
|
|
67
68
|
try {
|
|
68
69
|
const client = await this.client;
|
|
69
|
-
const codeChallenge = generators.codeChallenge(codeVerifier);
|
|
70
|
+
const codeChallenge = plainCodeChallenge ? codeVerifier : generators.codeChallenge(codeVerifier);
|
|
70
71
|
const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
|
|
71
72
|
return client.authorizationUrl({
|
|
72
73
|
scope: this.config['scope'] ?? 'openid profile email',
|
|
@@ -74,7 +75,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
74
75
|
prompt: prompt ? 'consent' : undefined,
|
|
75
76
|
...paramsConfig,
|
|
76
77
|
code_challenge: codeChallenge,
|
|
77
|
-
code_challenge_method: 'S256',
|
|
78
|
+
code_challenge_method: plainCodeChallenge ? 'plain' : 'S256',
|
|
78
79
|
// Some providers require state even with PKCE
|
|
79
80
|
state: codeChallenge,
|
|
80
81
|
nonce: codeChallenge,
|
|
@@ -97,11 +98,14 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
|
97
98
|
logger.warn('[OpenID] No code, codeVerifier or state in payload');
|
|
98
99
|
throw new InvalidCredentialsError();
|
|
99
100
|
}
|
|
101
|
+
const { plainCodeChallenge } = this.config;
|
|
100
102
|
let tokenSet;
|
|
101
103
|
let userInfo;
|
|
102
104
|
try {
|
|
103
105
|
const client = await this.client;
|
|
104
|
-
const codeChallenge =
|
|
106
|
+
const codeChallenge = plainCodeChallenge
|
|
107
|
+
? payload['codeVerifier']
|
|
108
|
+
: generators.codeChallenge(payload['codeVerifier']);
|
|
105
109
|
tokenSet = await client.callback(this.redirectUrl, { code: payload['code'], state: payload['state'], iss: payload['iss'] }, { code_verifier: payload['codeVerifier'], state: codeChallenge, nonce: codeChallenge });
|
|
106
110
|
userInfo = tokenSet.claims();
|
|
107
111
|
if (client.issuer.metadata['userinfo_endpoint']) {
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Command, Option } from 'commander';
|
|
2
|
-
import { isInstalled } from '../database/index.js';
|
|
3
2
|
import emitter from '../emitter.js';
|
|
4
|
-
import { getExtensionManager } from '../extensions/index.js';
|
|
5
3
|
import { startServer } from '../server.js';
|
|
6
4
|
import * as pkg from '../utils/package.js';
|
|
7
5
|
import bootstrap from './commands/bootstrap/index.js';
|
|
@@ -16,12 +14,10 @@ import keyGenerate from './commands/security/key.js';
|
|
|
16
14
|
import secretGenerate from './commands/security/secret.js';
|
|
17
15
|
import usersCreate from './commands/users/create.js';
|
|
18
16
|
import usersPasswd from './commands/users/passwd.js';
|
|
17
|
+
import { loadExtensions } from './load-extensions.js';
|
|
19
18
|
export async function createCli() {
|
|
20
19
|
const program = new Command();
|
|
21
|
-
|
|
22
|
-
const extensionManager = getExtensionManager();
|
|
23
|
-
await extensionManager.initialize({ schedule: false, watch: false });
|
|
24
|
-
}
|
|
20
|
+
await loadExtensions();
|
|
25
21
|
await emitter.emitInit('cli.before', { program });
|
|
26
22
|
program.name('directus').usage('[command] [options]');
|
|
27
23
|
program.version(pkg.version, '-v, --version');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const loadExtensions: () => Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { isInstalled, validateMigrations } from '../database/index.js';
|
|
2
|
+
import { getEnv } from '../env.js';
|
|
3
|
+
import { getExtensionManager } from '../extensions/index.js';
|
|
4
|
+
import logger from '../logger.js';
|
|
5
|
+
export const loadExtensions = async () => {
|
|
6
|
+
const env = getEnv();
|
|
7
|
+
if (!('DB_CLIENT' in env))
|
|
8
|
+
return;
|
|
9
|
+
const installed = await isInstalled();
|
|
10
|
+
if (!installed)
|
|
11
|
+
return;
|
|
12
|
+
const migrationsValid = await validateMigrations();
|
|
13
|
+
if (!migrationsValid) {
|
|
14
|
+
logger.info('Skipping CLI extensions initialization due to outstanding migrations.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const extensionManager = getExtensionManager();
|
|
18
|
+
await extensionManager.initialize({ schedule: false, watch: false });
|
|
19
|
+
};
|
|
@@ -99,7 +99,16 @@
|
|
|
99
99
|
- preferences_divider
|
|
100
100
|
- avatar
|
|
101
101
|
- language
|
|
102
|
-
-
|
|
102
|
+
- appearance
|
|
103
|
+
- theme_light
|
|
104
|
+
- theme_dark
|
|
105
|
+
- theme_light_overrides
|
|
106
|
+
- theme_dark_overrides
|
|
103
107
|
- tfa_secret
|
|
104
108
|
- status
|
|
105
109
|
- role
|
|
110
|
+
|
|
111
|
+
# This is a temporary allowed field to help people migrate from
|
|
112
|
+
# 10.6 to 10.7 and should be removed in 10.8
|
|
113
|
+
# @TODO remove
|
|
114
|
+
- theme
|
package/dist/emitter.d.ts
CHANGED
|
@@ -8,10 +8,10 @@ export declare class Emitter {
|
|
|
8
8
|
emitFilter<T>(event: string | string[], payload: T, meta: Record<string, any>, context?: EventContext | null): Promise<T>;
|
|
9
9
|
emitAction(event: string | string[], meta: Record<string, any>, context?: EventContext | null): void;
|
|
10
10
|
emitInit(event: string, meta: Record<string, any>): Promise<void>;
|
|
11
|
-
onFilter(event: string, handler: FilterHandler): void;
|
|
11
|
+
onFilter<T = unknown>(event: string, handler: FilterHandler<T>): void;
|
|
12
12
|
onAction(event: string, handler: ActionHandler): void;
|
|
13
13
|
onInit(event: string, handler: InitHandler): void;
|
|
14
|
-
offFilter(event: string, handler: FilterHandler): void;
|
|
14
|
+
offFilter<T = unknown>(event: string, handler: FilterHandler<T>): void;
|
|
15
15
|
offAction(event: string, handler: ActionHandler): void;
|
|
16
16
|
offInit(event: string, handler: InitHandler): void;
|
|
17
17
|
offAll(): void;
|
|
@@ -38,11 +38,12 @@ const virtual = virtualDefault;
|
|
|
38
38
|
const alias = aliasDefault;
|
|
39
39
|
const nodeResolve = nodeResolveDefault;
|
|
40
40
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const defaultOptions = {
|
|
42
|
+
schedule: true,
|
|
43
|
+
watch: env['EXTENSIONS_AUTO_RELOAD'] && env['NODE_ENV'] !== 'development',
|
|
44
|
+
};
|
|
41
45
|
export class ExtensionManager {
|
|
42
|
-
options =
|
|
43
|
-
schedule: true,
|
|
44
|
-
watch: env['EXTENSIONS_AUTO_RELOAD'] && env['NODE_ENV'] !== 'development',
|
|
45
|
-
};
|
|
46
|
+
options = defaultOptions;
|
|
46
47
|
/**
|
|
47
48
|
* Whether or not the extensions have been read from disk and registered into the system
|
|
48
49
|
*/
|
|
@@ -105,12 +106,10 @@ export class ExtensionManager {
|
|
|
105
106
|
* @param {boolean} options.watch - Whether or not to watch the local extensions folder for changes
|
|
106
107
|
*/
|
|
107
108
|
async initialize(options = {}) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.options.watch = options.watch;
|
|
113
|
-
}
|
|
109
|
+
this.options = {
|
|
110
|
+
...defaultOptions,
|
|
111
|
+
...options,
|
|
112
|
+
};
|
|
114
113
|
const wasWatcherInitialized = this.watcher !== null;
|
|
115
114
|
if (this.options.watch && !wasWatcherInitialized) {
|
|
116
115
|
this.initializeWatcher();
|
|
@@ -567,7 +566,8 @@ export class ExtensionManager {
|
|
|
567
566
|
*/
|
|
568
567
|
registerEndpoint(config, name) {
|
|
569
568
|
const endpointRegistrationCallback = typeof config === 'function' ? config : config.handler;
|
|
570
|
-
const
|
|
569
|
+
const nameWithoutType = name.includes(':') ? name.split(':')[0] : name;
|
|
570
|
+
const routeName = typeof config === 'function' ? nameWithoutType : config.id;
|
|
571
571
|
const scopedRouter = express.Router();
|
|
572
572
|
this.endpointRouter.use(`/${routeName}`, scopedRouter);
|
|
573
573
|
endpointRegistrationCallback(scopedRouter, {
|
|
@@ -19,6 +19,16 @@ export const respond = asyncHandler(async (req, res) => {
|
|
|
19
19
|
const maxSize = parseBytesConfiguration(env['CACHE_VALUE_MAX_SIZE']);
|
|
20
20
|
exceedsMaxSize = valueSize > maxSize;
|
|
21
21
|
}
|
|
22
|
+
if (req.sanitizedQuery.version &&
|
|
23
|
+
req.collection &&
|
|
24
|
+
(req.singleton || req.params['pk']) &&
|
|
25
|
+
'data' in res.locals['payload']) {
|
|
26
|
+
const versionsService = new VersionsService({ accountability: req.accountability ?? null, schema: req.schema });
|
|
27
|
+
const saves = await versionsService.getVersionSaves(req.sanitizedQuery.version, req.collection, req.params['pk']);
|
|
28
|
+
if (saves) {
|
|
29
|
+
assign(res.locals['payload'].data, ...saves);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
22
32
|
if ((req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
|
|
23
33
|
env['CACHE_ENABLED'] === true &&
|
|
24
34
|
cache &&
|
|
@@ -41,16 +51,6 @@ export const respond = asyncHandler(async (req, res) => {
|
|
|
41
51
|
res.setHeader('Cache-Control', 'no-cache');
|
|
42
52
|
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
43
53
|
}
|
|
44
|
-
if (req.sanitizedQuery.version &&
|
|
45
|
-
req.collection &&
|
|
46
|
-
(req.singleton || req.params['pk']) &&
|
|
47
|
-
'data' in res.locals['payload']) {
|
|
48
|
-
const versionsService = new VersionsService({ accountability: req.accountability ?? null, schema: req.schema });
|
|
49
|
-
const saves = await versionsService.getVersionSaves(req.sanitizedQuery.version, req.collection, req.params['pk']);
|
|
50
|
-
if (saves) {
|
|
51
|
-
assign(res.locals['payload'].data, ...saves);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
54
|
if (req.sanitizedQuery.export) {
|
|
55
55
|
const exportService = new ExportService({ accountability: req.accountability ?? null, schema: req.schema });
|
|
56
56
|
let filename = '';
|
|
@@ -2,6 +2,7 @@ import Joi from 'joi';
|
|
|
2
2
|
import { InvalidPayloadError } from '@directus/errors';
|
|
3
3
|
import asyncHandler from '../utils/async-handler.js';
|
|
4
4
|
import { sanitizeQuery } from '../utils/sanitize-query.js';
|
|
5
|
+
import { validateQuery } from '../utils/validate-query.js';
|
|
5
6
|
export const validateBatch = (scope) => asyncHandler(async (req, _res, next) => {
|
|
6
7
|
if (req.method.toLowerCase() === 'get') {
|
|
7
8
|
req.body = {};
|
|
@@ -18,6 +19,7 @@ export const validateBatch = (scope) => asyncHandler(async (req, _res, next) =>
|
|
|
18
19
|
// In reads, the query in the body should override the query params for searching
|
|
19
20
|
if (scope === 'read' && req.body.query) {
|
|
20
21
|
req.sanitizedQuery = sanitizeQuery(req.body.query, req.accountability);
|
|
22
|
+
validateQuery(req.sanitizedQuery);
|
|
21
23
|
}
|
|
22
24
|
// Every cRUD action has either keys or query
|
|
23
25
|
let batchSchema = Joi.object().keys({
|
package/dist/server.js
CHANGED
|
@@ -10,9 +10,9 @@ import emitter from './emitter.js';
|
|
|
10
10
|
import env from './env.js';
|
|
11
11
|
import logger from './logger.js';
|
|
12
12
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
13
|
+
import { toBoolean } from './utils/to-boolean.js';
|
|
13
14
|
import { createSubscriptionController, createWebSocketController, getSubscriptionController, getWebSocketController, } from './websocket/controllers/index.js';
|
|
14
15
|
import { startWebSocketHandlers } from './websocket/handlers/index.js';
|
|
15
|
-
import { toBoolean } from './utils/to-boolean.js';
|
|
16
16
|
export let SERVER_ONLINE = true;
|
|
17
17
|
export async function createServer() {
|
|
18
18
|
const server = http.createServer(await createApp());
|
|
@@ -116,6 +116,7 @@ export async function startServer() {
|
|
|
116
116
|
server
|
|
117
117
|
.listen(port, host, () => {
|
|
118
118
|
logger.info(`Server started at http://${host}:${port}`);
|
|
119
|
+
process.send?.('ready');
|
|
119
120
|
emitter.emitAction('server.start', { server }, {
|
|
120
121
|
database: getDatabase(),
|
|
121
122
|
schema: null,
|
|
@@ -89,7 +89,22 @@ export class ExtensionsService {
|
|
|
89
89
|
let bundleName = null;
|
|
90
90
|
let name = meta.name;
|
|
91
91
|
if (name.includes('/')) {
|
|
92
|
-
|
|
92
|
+
const parts = name.split('/');
|
|
93
|
+
// NPM packages can have an optional organization scope in the format
|
|
94
|
+
// `@<org>/<package>`. This is limited to a single `/`.
|
|
95
|
+
//
|
|
96
|
+
// `foo` -> extension
|
|
97
|
+
// `foo/bar` -> bundle
|
|
98
|
+
// `@rijk/foo` -> extension
|
|
99
|
+
// `@rijk/foo/bar -> bundle
|
|
100
|
+
const hasOrg = parts.at(0).startsWith('@');
|
|
101
|
+
if (hasOrg && parts.length > 2) {
|
|
102
|
+
name = parts.pop();
|
|
103
|
+
bundleName = parts.join('/');
|
|
104
|
+
}
|
|
105
|
+
else if (hasOrg === false) {
|
|
106
|
+
[bundleName, name] = parts;
|
|
107
|
+
}
|
|
93
108
|
}
|
|
94
109
|
let schema;
|
|
95
110
|
if (bundleName) {
|
package/dist/services/files.js
CHANGED
|
@@ -29,23 +29,23 @@ export class FilesService extends ItemsService {
|
|
|
29
29
|
*/
|
|
30
30
|
async uploadOne(stream, data, primaryKey, opts) {
|
|
31
31
|
const storage = await getStorage();
|
|
32
|
-
let existingFile =
|
|
32
|
+
let existingFile = null;
|
|
33
33
|
if (primaryKey !== undefined) {
|
|
34
34
|
existingFile =
|
|
35
35
|
(await this.knex
|
|
36
36
|
.select('folder', 'filename_download')
|
|
37
37
|
.from('directus_files')
|
|
38
38
|
.where({ id: primaryKey })
|
|
39
|
-
.first()) ??
|
|
39
|
+
.first()) ?? null;
|
|
40
40
|
}
|
|
41
|
-
const payload = { ...existingFile, ...clone(data) };
|
|
41
|
+
const payload = { ...(existingFile ?? {}), ...clone(data) };
|
|
42
42
|
if ('folder' in payload === false) {
|
|
43
43
|
const settings = await this.knex.select('storage_default_folder').from('directus_settings').first();
|
|
44
44
|
if (settings?.storage_default_folder) {
|
|
45
45
|
payload.folder = settings.storage_default_folder;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
if (primaryKey !== undefined) {
|
|
48
|
+
if (existingFile !== null && primaryKey !== undefined) {
|
|
49
49
|
await this.updateOne(primaryKey, payload, { emitEvents: false });
|
|
50
50
|
// If the file you're uploading already exists, we'll consider this upload a replace. In that case, we'll
|
|
51
51
|
// delete the previously saved file and thumbnails to ensure they're generated fresh
|
|
@@ -3,8 +3,10 @@ import { InvalidPayloadError, UnprocessableContentError } from '@directus/errors
|
|
|
3
3
|
import Joi from 'joi';
|
|
4
4
|
import { assign, pick } from 'lodash-es';
|
|
5
5
|
import objectHash from 'object-hash';
|
|
6
|
+
import { getCache } from '../cache.js';
|
|
6
7
|
import getDatabase from '../database/index.js';
|
|
7
8
|
import emitter from '../emitter.js';
|
|
9
|
+
import { shouldClearCache } from '../utils/should-clear-cache.js';
|
|
8
10
|
import { ActivityService } from './activity.js';
|
|
9
11
|
import { AuthorizationService } from './authorization.js';
|
|
10
12
|
import { ItemsService } from './items.js';
|
|
@@ -188,6 +190,10 @@ export class VersionsService extends ItemsService {
|
|
|
188
190
|
data: revisionDelta,
|
|
189
191
|
delta: revisionDelta,
|
|
190
192
|
});
|
|
193
|
+
const { cache } = getCache();
|
|
194
|
+
if (shouldClearCache(cache, undefined, version['collection'])) {
|
|
195
|
+
cache.clear();
|
|
196
|
+
}
|
|
191
197
|
return data;
|
|
192
198
|
}
|
|
193
199
|
async promote(version, mainHash, fields) {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
+
import logger from '../logger.js';
|
|
2
3
|
const require = createRequire(import.meta.url);
|
|
3
4
|
export function deleteFromRequireCache(modulePath) {
|
|
4
|
-
|
|
5
|
+
try {
|
|
6
|
+
const moduleCachePath = require.resolve(modulePath);
|
|
7
|
+
delete require.cache[moduleCachePath];
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
logger.trace(`Module cache not found for ${modulePath}, skipped cache delete.`);
|
|
11
|
+
}
|
|
5
12
|
}
|
|
@@ -19,5 +19,5 @@ export declare function redactObject(input: UnknownObject, redact: {
|
|
|
19
19
|
/**
|
|
20
20
|
* Replace values and extract Error objects for use with JSON.stringify()
|
|
21
21
|
*/
|
|
22
|
-
export declare function getReplacer(replacement: Replacement, values?: Values): (_key: string, value: unknown) =>
|
|
22
|
+
export declare function getReplacer(replacement: Replacement, values?: Values): (_key: string, value: unknown) => any;
|
|
23
23
|
export {};
|
|
@@ -84,31 +84,44 @@ export function getReplacer(replacement, values) {
|
|
|
84
84
|
const filteredValues = values
|
|
85
85
|
? Object.entries(values).filter(([_k, v]) => typeof v === 'string' && v.length > 0)
|
|
86
86
|
: [];
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
const replacer = (seen) => {
|
|
88
|
+
return function (_key, value) {
|
|
89
|
+
if (value instanceof Error) {
|
|
90
|
+
return {
|
|
91
|
+
name: value.name,
|
|
92
|
+
message: value.message,
|
|
93
|
+
stack: value.stack,
|
|
94
|
+
cause: value.cause,
|
|
95
|
+
};
|
|
93
96
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
97
|
+
if (value !== null && typeof value === 'object') {
|
|
98
|
+
if (seen.has(value)) {
|
|
99
|
+
return '[Circular]';
|
|
100
|
+
}
|
|
101
|
+
seen.add(value);
|
|
102
|
+
const newValue = Array.isArray(value) ? [] : {};
|
|
103
|
+
for (const [key2, value2] of Object.entries(value)) {
|
|
104
|
+
if (typeof value2 === 'string') {
|
|
105
|
+
newValue[key2] = value2;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
newValue[key2] = replacer(seen)(key2, value2);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
seen.delete(value);
|
|
112
|
+
return newValue;
|
|
110
113
|
}
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
if (!values || filteredValues.length === 0 || typeof value !== 'string')
|
|
115
|
+
return value;
|
|
116
|
+
let finalValue = value;
|
|
117
|
+
for (const [redactKey, valueToRedact] of filteredValues) {
|
|
118
|
+
if (finalValue.includes(valueToRedact)) {
|
|
119
|
+
finalValue = finalValue.replace(new RegExp(valueToRedact, 'g'), replacement(redactKey));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return finalValue;
|
|
123
|
+
};
|
|
113
124
|
};
|
|
125
|
+
const seen = new WeakSet();
|
|
126
|
+
return replacer(seen);
|
|
114
127
|
}
|
package/dist/worker-pool.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
1
2
|
import Tinypool from 'tinypool';
|
|
2
3
|
let workerPool;
|
|
3
4
|
export function getWorkerPool() {
|
|
@@ -6,6 +7,13 @@ export function getWorkerPool() {
|
|
|
6
7
|
minThreads: 0,
|
|
7
8
|
maxQueue: 'auto',
|
|
8
9
|
});
|
|
10
|
+
// TODO Workaround currently required for failing CPU count on ARM in Tinypool,
|
|
11
|
+
// remove again once fixed upstream
|
|
12
|
+
if (workerPool.options.maxThreads === 0) {
|
|
13
|
+
const availableParallelism = os.availableParallelism();
|
|
14
|
+
workerPool.options.maxThreads = availableParallelism;
|
|
15
|
+
workerPool.options.maxQueue = availableParallelism ** 2;
|
|
16
|
+
}
|
|
9
17
|
}
|
|
10
18
|
return workerPool;
|
|
11
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.2",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -144,23 +144,23 @@
|
|
|
144
144
|
"ws": "8.14.2",
|
|
145
145
|
"zod": "3.22.4",
|
|
146
146
|
"zod-validation-error": "1.0.1",
|
|
147
|
-
"@directus/app": "10.
|
|
147
|
+
"@directus/app": "10.12.0",
|
|
148
148
|
"@directus/constants": "11.0.1",
|
|
149
|
+
"@directus/extensions": "0.1.1",
|
|
149
150
|
"@directus/errors": "0.2.0",
|
|
150
|
-
"@directus/extensions": "
|
|
151
|
-
"@directus/extensions-sdk": "10.1.13",
|
|
151
|
+
"@directus/extensions-sdk": "10.1.14",
|
|
152
152
|
"@directus/pressure": "1.0.12",
|
|
153
153
|
"@directus/schema": "11.0.0",
|
|
154
|
-
"@directus/specs": "10.2.
|
|
154
|
+
"@directus/specs": "10.2.1",
|
|
155
155
|
"@directus/storage": "10.0.7",
|
|
156
156
|
"@directus/storage-driver-azure": "10.0.13",
|
|
157
|
-
"@directus/storage-driver-cloudinary": "10.0.13",
|
|
158
157
|
"@directus/storage-driver-gcs": "10.0.13",
|
|
159
|
-
"@directus/storage-driver-
|
|
158
|
+
"@directus/storage-driver-cloudinary": "10.0.13",
|
|
160
159
|
"@directus/storage-driver-s3": "10.0.13",
|
|
160
|
+
"@directus/storage-driver-local": "10.0.13",
|
|
161
161
|
"@directus/storage-driver-supabase": "1.0.5",
|
|
162
|
-
"@directus/
|
|
163
|
-
"@directus/
|
|
162
|
+
"@directus/validation": "0.0.8",
|
|
163
|
+
"@directus/utils": "11.0.1"
|
|
164
164
|
},
|
|
165
165
|
"devDependencies": {
|
|
166
166
|
"@ngneat/falso": "6.4.0",
|