@directus/api 14.0.0 → 14.0.1
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/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 +5 -1
- package/dist/database/system-data/fields/users.yaml +2 -0
- package/dist/emitter.d.ts +2 -2
- package/dist/extensions/manager.js +9 -10
- package/dist/middleware/respond.js +10 -10
- package/dist/server.js +2 -1
- package/dist/services/extensions.js +16 -1
- package/dist/services/versions.js +6 -0
- package/dist/utils/redact-object.d.ts +1 -1
- package/dist/utils/redact-object.js +37 -24
- package/dist/worker-pool.js +8 -0
- package/package.json +5 -5
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
|
+
};
|
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();
|
|
@@ -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 = '';
|
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) {
|
|
@@ -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) {
|
|
@@ -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.1",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -144,14 +144,14 @@
|
|
|
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.11.0",
|
|
148
148
|
"@directus/constants": "11.0.1",
|
|
149
149
|
"@directus/errors": "0.2.0",
|
|
150
|
-
"@directus/extensions": "0.1.
|
|
151
|
-
"@directus/extensions-sdk": "10.1.
|
|
150
|
+
"@directus/extensions": "0.1.1",
|
|
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
157
|
"@directus/storage-driver-cloudinary": "10.0.13",
|