@directus/api 24.0.0 → 25.0.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 +10 -4
- package/dist/auth/drivers/oauth2.js +2 -3
- package/dist/auth/drivers/openid.js +2 -3
- package/dist/cache.d.ts +2 -2
- package/dist/cache.js +20 -7
- package/dist/controllers/assets.js +2 -2
- package/dist/controllers/metrics.d.ts +2 -0
- package/dist/controllers/metrics.js +33 -0
- package/dist/controllers/server.js +1 -1
- package/dist/database/get-ast-from-query/lib/parse-fields.js +4 -3
- package/dist/database/helpers/index.d.ts +1 -3
- package/dist/database/helpers/index.js +1 -3
- package/dist/database/helpers/number/dialects/mssql.d.ts +2 -2
- package/dist/database/helpers/number/dialects/mssql.js +3 -3
- package/dist/database/helpers/number/dialects/oracle.d.ts +2 -2
- package/dist/database/helpers/number/dialects/oracle.js +2 -2
- package/dist/database/helpers/number/dialects/sqlite.d.ts +2 -2
- package/dist/database/helpers/number/dialects/sqlite.js +2 -2
- package/dist/database/helpers/number/types.d.ts +2 -2
- package/dist/database/helpers/number/types.js +2 -2
- package/dist/database/helpers/schema/dialects/oracle.d.ts +6 -1
- package/dist/database/helpers/schema/dialects/oracle.js +15 -0
- package/dist/database/helpers/schema/types.d.ts +3 -1
- package/dist/database/helpers/schema/types.js +9 -0
- package/dist/database/index.js +3 -0
- package/dist/metrics/index.d.ts +1 -0
- package/dist/metrics/index.js +1 -0
- package/dist/metrics/lib/create-metrics.d.ts +15 -0
- package/dist/metrics/lib/create-metrics.js +239 -0
- package/dist/metrics/lib/use-metrics.d.ts +17 -0
- package/dist/metrics/lib/use-metrics.js +15 -0
- package/dist/metrics/types/metric.d.ts +1 -0
- package/dist/metrics/types/metric.js +1 -0
- package/dist/middleware/respond.js +7 -1
- package/dist/operations/condition/index.js +7 -2
- package/dist/operations/mail/index.d.ts +6 -3
- package/dist/operations/mail/index.js +2 -2
- package/dist/schedules/metrics.d.ts +7 -0
- package/dist/schedules/metrics.js +44 -0
- package/dist/services/assets.d.ts +6 -1
- package/dist/services/assets.js +8 -6
- package/dist/services/fields.js +23 -39
- package/dist/services/files.js +6 -5
- package/dist/services/graphql/errors/format.d.ts +6 -0
- package/dist/services/graphql/errors/format.js +14 -0
- package/dist/services/graphql/index.d.ts +5 -53
- package/dist/services/graphql/index.js +5 -2720
- package/dist/services/graphql/resolvers/mutation.d.ts +4 -0
- package/dist/services/graphql/resolvers/mutation.js +74 -0
- package/dist/services/graphql/resolvers/query.d.ts +8 -0
- package/dist/services/graphql/resolvers/query.js +87 -0
- package/dist/services/graphql/resolvers/system-admin.d.ts +5 -0
- package/dist/services/graphql/resolvers/system-admin.js +236 -0
- package/dist/services/graphql/resolvers/system-global.d.ts +7 -0
- package/dist/services/graphql/resolvers/system-global.js +435 -0
- package/dist/services/graphql/resolvers/system.d.ts +11 -0
- package/dist/services/graphql/resolvers/system.js +554 -0
- package/dist/services/graphql/schema/get-types.d.ts +12 -0
- package/dist/services/graphql/schema/get-types.js +223 -0
- package/dist/services/graphql/schema/index.d.ts +32 -0
- package/dist/services/graphql/schema/index.js +190 -0
- package/dist/services/graphql/schema/parse-args.d.ts +9 -0
- package/dist/services/graphql/schema/parse-args.js +35 -0
- package/dist/services/graphql/schema/parse-query.d.ts +7 -0
- package/dist/services/graphql/schema/parse-query.js +98 -0
- package/dist/services/graphql/schema/read.d.ts +12 -0
- package/dist/services/graphql/schema/read.js +653 -0
- package/dist/services/graphql/schema/write.d.ts +9 -0
- package/dist/services/graphql/schema/write.js +142 -0
- package/dist/services/graphql/subscription.d.ts +1 -1
- package/dist/services/graphql/subscription.js +7 -6
- package/dist/services/graphql/utils/aggrgate-query.d.ts +6 -0
- package/dist/services/graphql/utils/aggrgate-query.js +32 -0
- package/dist/services/graphql/utils/replace-fragments.d.ts +6 -0
- package/dist/services/graphql/utils/replace-fragments.js +21 -0
- package/dist/services/graphql/utils/replace-funcs.d.ts +5 -0
- package/dist/services/graphql/utils/replace-funcs.js +21 -0
- package/dist/services/graphql/utils/sanitize-gql-schema.d.ts +1 -1
- package/dist/services/graphql/utils/sanitize-gql-schema.js +5 -5
- package/dist/services/items.js +0 -2
- package/dist/services/meta.js +25 -84
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +23 -1
- package/dist/utils/apply-query.d.ts +1 -1
- package/dist/utils/apply-query.js +58 -21
- package/dist/utils/freeze-schema.d.ts +3 -0
- package/dist/utils/freeze-schema.js +31 -0
- package/dist/utils/get-accountability-for-token.js +1 -0
- package/dist/utils/get-milliseconds.js +1 -1
- package/dist/utils/get-schema.js +11 -6
- package/dist/utils/permissions-cachable.d.ts +8 -0
- package/dist/utils/permissions-cachable.js +38 -0
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/websocket/messages.d.ts +6 -6
- package/package.json +23 -20
- package/dist/database/helpers/nullable-update/dialects/default.d.ts +0 -3
- package/dist/database/helpers/nullable-update/dialects/default.js +0 -3
- package/dist/database/helpers/nullable-update/dialects/oracle.d.ts +0 -12
- package/dist/database/helpers/nullable-update/dialects/oracle.js +0 -16
- package/dist/database/helpers/nullable-update/index.d.ts +0 -7
- package/dist/database/helpers/nullable-update/index.js +0 -7
- package/dist/database/helpers/nullable-update/types.d.ts +0 -7
- package/dist/database/helpers/nullable-update/types.js +0 -12
package/dist/app.js
CHANGED
|
@@ -9,8 +9,8 @@ import { createRequire } from 'node:module';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import qs from 'qs';
|
|
11
11
|
import { registerAuthProviders } from './auth.js';
|
|
12
|
-
import activityRouter from './controllers/activity.js';
|
|
13
12
|
import accessRouter from './controllers/access.js';
|
|
13
|
+
import activityRouter from './controllers/activity.js';
|
|
14
14
|
import assetsRouter from './controllers/assets.js';
|
|
15
15
|
import authRouter from './controllers/auth.js';
|
|
16
16
|
import collectionsRouter from './controllers/collections.js';
|
|
@@ -23,6 +23,7 @@ import flowsRouter from './controllers/flows.js';
|
|
|
23
23
|
import foldersRouter from './controllers/folders.js';
|
|
24
24
|
import graphqlRouter from './controllers/graphql.js';
|
|
25
25
|
import itemsRouter from './controllers/items.js';
|
|
26
|
+
import metricsRouter from './controllers/metrics.js';
|
|
26
27
|
import notFoundHandler from './controllers/not-found.js';
|
|
27
28
|
import notificationsRouter from './controllers/notifications.js';
|
|
28
29
|
import operationsRouter from './controllers/operations.js';
|
|
@@ -43,9 +44,6 @@ import usersRouter from './controllers/users.js';
|
|
|
43
44
|
import utilsRouter from './controllers/utils.js';
|
|
44
45
|
import versionsRouter from './controllers/versions.js';
|
|
45
46
|
import webhooksRouter from './controllers/webhooks.js';
|
|
46
|
-
import retentionSchedule from './schedules/retention.js';
|
|
47
|
-
import telemetrySchedule from './schedules/telemetry.js';
|
|
48
|
-
import tusSchedule from './schedules/tus.js';
|
|
49
47
|
import { isInstalled, validateDatabaseConnection, validateDatabaseExtensions, validateMigrations, } from './database/index.js';
|
|
50
48
|
import emitter from './emitter.js';
|
|
51
49
|
import { getExtensionManager } from './extensions/index.js';
|
|
@@ -60,6 +58,10 @@ import rateLimiterGlobal from './middleware/rate-limiter-global.js';
|
|
|
60
58
|
import rateLimiter from './middleware/rate-limiter-ip.js';
|
|
61
59
|
import sanitizeQuery from './middleware/sanitize-query.js';
|
|
62
60
|
import schema from './middleware/schema.js';
|
|
61
|
+
import metricsSchedule from './schedules/metrics.js';
|
|
62
|
+
import retentionSchedule from './schedules/retention.js';
|
|
63
|
+
import telemetrySchedule from './schedules/telemetry.js';
|
|
64
|
+
import tusSchedule from './schedules/tus.js';
|
|
63
65
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
64
66
|
import { Url } from './utils/url.js';
|
|
65
67
|
import { validateStorage } from './utils/validate-storage.js';
|
|
@@ -223,6 +225,9 @@ export default async function createApp() {
|
|
|
223
225
|
app.use('/flows', flowsRouter);
|
|
224
226
|
app.use('/folders', foldersRouter);
|
|
225
227
|
app.use('/items', itemsRouter);
|
|
228
|
+
if (env['METRICS_ENABLED'] === true) {
|
|
229
|
+
app.use('/metrics', metricsRouter);
|
|
230
|
+
}
|
|
226
231
|
app.use('/notifications', notificationsRouter);
|
|
227
232
|
app.use('/operations', operationsRouter);
|
|
228
233
|
app.use('/panels', panelsRouter);
|
|
@@ -251,6 +256,7 @@ export default async function createApp() {
|
|
|
251
256
|
await retentionSchedule();
|
|
252
257
|
await telemetrySchedule();
|
|
253
258
|
await tusSchedule();
|
|
259
|
+
await metricsSchedule();
|
|
254
260
|
await emitter.emitInit('app.after', { app });
|
|
255
261
|
return app;
|
|
256
262
|
}
|
|
@@ -19,6 +19,7 @@ import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
|
19
19
|
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
20
20
|
import { getSecret } from '../../utils/get-secret.js';
|
|
21
21
|
import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js';
|
|
22
|
+
import { verifyJWT } from '../../utils/jwt.js';
|
|
22
23
|
import { Url } from '../../utils/url.js';
|
|
23
24
|
import { LocalAuthDriver } from './local.js';
|
|
24
25
|
export class OAuth2AuthDriver extends LocalAuthDriver {
|
|
@@ -254,9 +255,7 @@ export function createOAuth2AuthRouter(providerName) {
|
|
|
254
255
|
const logger = useLogger();
|
|
255
256
|
let tokenData;
|
|
256
257
|
try {
|
|
257
|
-
tokenData =
|
|
258
|
-
issuer: 'directus',
|
|
259
|
-
});
|
|
258
|
+
tokenData = verifyJWT(req.cookies[`oauth2.${providerName}`], getSecret());
|
|
260
259
|
}
|
|
261
260
|
catch (e) {
|
|
262
261
|
logger.warn(e, `[OAuth2] Couldn't verify OAuth2 cookie`);
|
|
@@ -19,6 +19,7 @@ import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
|
|
19
19
|
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
|
20
20
|
import { getSecret } from '../../utils/get-secret.js';
|
|
21
21
|
import { isLoginRedirectAllowed } from '../../utils/is-login-redirect-allowed.js';
|
|
22
|
+
import { verifyJWT } from '../../utils/jwt.js';
|
|
22
23
|
import { Url } from '../../utils/url.js';
|
|
23
24
|
import { LocalAuthDriver } from './local.js';
|
|
24
25
|
export class OpenIDAuthDriver extends LocalAuthDriver {
|
|
@@ -306,9 +307,7 @@ export function createOpenIDAuthRouter(providerName) {
|
|
|
306
307
|
const logger = useLogger();
|
|
307
308
|
let tokenData;
|
|
308
309
|
try {
|
|
309
|
-
tokenData =
|
|
310
|
-
issuer: 'directus',
|
|
311
|
-
});
|
|
310
|
+
tokenData = verifyJWT(req.cookies[`openid.${providerName}`], getSecret());
|
|
312
311
|
}
|
|
313
312
|
catch (e) {
|
|
314
313
|
logger.warn(e, `[OpenID] Couldn't verify OpenID cookie`);
|
package/dist/cache.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare function clearSystemCache(opts?: {
|
|
|
13
13
|
}): Promise<void>;
|
|
14
14
|
export declare function setSystemCache(key: string, value: any, ttl?: number): Promise<void>;
|
|
15
15
|
export declare function getSystemCache(key: string): Promise<Record<string, any>>;
|
|
16
|
-
export declare function
|
|
17
|
-
export declare function
|
|
16
|
+
export declare function setMemorySchemaCache(schema: SchemaOverview): void;
|
|
17
|
+
export declare function getMemorySchemaCache(): Readonly<SchemaOverview> | undefined;
|
|
18
18
|
export declare function setCacheValue(cache: Keyv, key: string, value: Record<string, any> | Record<string, any>[], ttl?: number): Promise<void>;
|
|
19
19
|
export declare function getCacheValue(cache: Keyv, key: string): Promise<any>;
|
package/dist/cache.js
CHANGED
|
@@ -9,14 +9,16 @@ import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
|
9
9
|
import { getMilliseconds } from './utils/get-milliseconds.js';
|
|
10
10
|
import { validateEnv } from './utils/validate-env.js';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
|
+
import { freezeSchema, unfreezeSchema } from './utils/freeze-schema.js';
|
|
12
13
|
const logger = useLogger();
|
|
13
14
|
const env = useEnv();
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
15
16
|
let cache = null;
|
|
16
17
|
let systemCache = null;
|
|
17
|
-
let localSchemaCache = null;
|
|
18
18
|
let lockCache = null;
|
|
19
19
|
let messengerSubscribed = false;
|
|
20
|
+
let localSchemaCache = null;
|
|
21
|
+
let memorySchemaCache = null;
|
|
20
22
|
const messenger = useBus();
|
|
21
23
|
if (redisConfigAvailable() && !messengerSubscribed) {
|
|
22
24
|
messengerSubscribed = true;
|
|
@@ -25,6 +27,7 @@ if (redisConfigAvailable() && !messengerSubscribed) {
|
|
|
25
27
|
await cache.clear();
|
|
26
28
|
}
|
|
27
29
|
await localSchemaCache?.clear();
|
|
30
|
+
memorySchemaCache = null;
|
|
28
31
|
});
|
|
29
32
|
}
|
|
30
33
|
export function getCache() {
|
|
@@ -61,6 +64,7 @@ export async function clearSystemCache(opts) {
|
|
|
61
64
|
await lockCache.delete('system-cache-lock');
|
|
62
65
|
}
|
|
63
66
|
await localSchemaCache.clear();
|
|
67
|
+
memorySchemaCache = null;
|
|
64
68
|
// Since a lot of cached permission function rely on the schema it needs to be cleared as well
|
|
65
69
|
await clearPermissionCache();
|
|
66
70
|
messenger.publish('schemaChanged', { autoPurgeCache: opts?.autoPurgeCache });
|
|
@@ -75,13 +79,22 @@ export async function getSystemCache(key) {
|
|
|
75
79
|
const { systemCache } = getCache();
|
|
76
80
|
return await getCacheValue(systemCache, key);
|
|
77
81
|
}
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
export function setMemorySchemaCache(schema) {
|
|
83
|
+
if (Object.isFrozen(schema)) {
|
|
84
|
+
memorySchemaCache = schema;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
memorySchemaCache = freezeSchema(schema);
|
|
88
|
+
}
|
|
81
89
|
}
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
|
|
90
|
+
export function getMemorySchemaCache() {
|
|
91
|
+
if (env['CACHE_SCHEMA_FREEZE_ENABLED']) {
|
|
92
|
+
return memorySchemaCache ?? undefined;
|
|
93
|
+
}
|
|
94
|
+
else if (memorySchemaCache) {
|
|
95
|
+
return unfreezeSchema(memorySchemaCache);
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
85
98
|
}
|
|
86
99
|
export async function setCacheValue(cache, key, value, ttl) {
|
|
87
100
|
const compressed = await compress(value);
|
|
@@ -151,7 +151,7 @@ asyncHandler(async (req, res) => {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
-
const { stream, file, stat } = await service.getAsset(id, { transformationParams, acceptFormat }, range);
|
|
154
|
+
const { stream, file, stat } = await service.getAsset(id, { transformationParams, acceptFormat }, range, true);
|
|
155
155
|
const filename = req.params['filename'] ?? file.filename_download;
|
|
156
156
|
res.attachment(filename);
|
|
157
157
|
res.setHeader('Content-Type', file.type);
|
|
@@ -180,7 +180,7 @@ asyncHandler(async (req, res) => {
|
|
|
180
180
|
res.setHeader('Content-Length', stat.size);
|
|
181
181
|
return res.end();
|
|
182
182
|
}
|
|
183
|
-
stream
|
|
183
|
+
(await stream())
|
|
184
184
|
.on('error', (error) => {
|
|
185
185
|
logger.error(error, `Couldn't stream file ${file.id} to the client`);
|
|
186
186
|
if (!res.headersSent) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError } from '@directus/errors';
|
|
3
|
+
import { Router } from 'express';
|
|
4
|
+
import { useMetrics } from '../metrics/index.js';
|
|
5
|
+
import asyncHandler from '../utils/async-handler.js';
|
|
6
|
+
const env = useEnv();
|
|
7
|
+
const router = Router();
|
|
8
|
+
const metrics = useMetrics();
|
|
9
|
+
router.get('/', asyncHandler(async (req, _res, next) => {
|
|
10
|
+
if (req.accountability?.admin === true) {
|
|
11
|
+
return next();
|
|
12
|
+
}
|
|
13
|
+
// support Bearer Token of type `Metrics`
|
|
14
|
+
const metricTokens = env['METRICS_TOKENS'];
|
|
15
|
+
if (!req.headers || !req.headers.authorization || !metricTokens) {
|
|
16
|
+
throw new ForbiddenError();
|
|
17
|
+
}
|
|
18
|
+
const parts = req.headers.authorization.split(' ');
|
|
19
|
+
if (parts.length !== 2 || parts[0].toLowerCase() !== 'metrics') {
|
|
20
|
+
throw new ForbiddenError();
|
|
21
|
+
}
|
|
22
|
+
if (metricTokens.find((mt) => mt.toString() === parts[1]) !== undefined) {
|
|
23
|
+
return next();
|
|
24
|
+
}
|
|
25
|
+
throw new ForbiddenError();
|
|
26
|
+
}), asyncHandler(async (_req, res) => {
|
|
27
|
+
res.set('Content-Type', 'text/plain');
|
|
28
|
+
// Don't cache anything by default
|
|
29
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
30
|
+
res.setHeader('Vary', 'Origin, Cache-Control');
|
|
31
|
+
return res.send(await metrics?.readAll());
|
|
32
|
+
}));
|
|
33
|
+
export default router;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { RouteNotFoundError } from '@directus/errors';
|
|
1
2
|
import { format } from 'date-fns';
|
|
2
3
|
import { Router } from 'express';
|
|
3
|
-
import { RouteNotFoundError } from '@directus/errors';
|
|
4
4
|
import { respond } from '../middleware/respond.js';
|
|
5
5
|
import { ServerService } from '../services/server.js';
|
|
6
6
|
import { SpecificationService } from '../services/specifications.js';
|
|
@@ -169,10 +169,11 @@ export async function parseFields(options, context) {
|
|
|
169
169
|
continue;
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
const childQuery = { ...options.query };
|
|
172
173
|
// update query alias for children parseFields
|
|
173
174
|
const deepAlias = getDeepQuery(options.deep?.[fieldKey] || {})?.['alias'];
|
|
174
|
-
if
|
|
175
|
-
|
|
175
|
+
// reset alias to empty if none are present
|
|
176
|
+
childQuery.alias = isEmpty(deepAlias) ? {} : deepAlias;
|
|
176
177
|
child = {
|
|
177
178
|
type: relationType,
|
|
178
179
|
name: relatedCollection,
|
|
@@ -184,7 +185,7 @@ export async function parseFields(options, context) {
|
|
|
184
185
|
children: await parseFields({
|
|
185
186
|
parentCollection: relatedCollection,
|
|
186
187
|
fields: nestedFields,
|
|
187
|
-
query:
|
|
188
|
+
query: childQuery,
|
|
188
189
|
deep: options.deep?.[fieldKey] || {},
|
|
189
190
|
accountability: options.accountability,
|
|
190
191
|
}, context),
|
|
@@ -3,17 +3,15 @@ import type { Knex } from 'knex';
|
|
|
3
3
|
import * as dateHelpers from './date/index.js';
|
|
4
4
|
import * as fnHelpers from './fn/index.js';
|
|
5
5
|
import * as geometryHelpers from './geometry/index.js';
|
|
6
|
+
import * as numberHelpers from './number/index.js';
|
|
6
7
|
import * as schemaHelpers from './schema/index.js';
|
|
7
8
|
import * as sequenceHelpers from './sequence/index.js';
|
|
8
|
-
import * as numberHelpers from './number/index.js';
|
|
9
|
-
import * as nullableUpdateHelper from './nullable-update/index.js';
|
|
10
9
|
export declare function getHelpers(database: Knex): {
|
|
11
10
|
date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
|
|
12
11
|
st: geometryHelpers.postgres | geometryHelpers.mssql | geometryHelpers.mysql | geometryHelpers.sqlite | geometryHelpers.oracle | geometryHelpers.redshift;
|
|
13
12
|
schema: schemaHelpers.cockroachdb | schemaHelpers.mssql | schemaHelpers.mysql | schemaHelpers.postgres | schemaHelpers.sqlite | schemaHelpers.oracle | schemaHelpers.redshift;
|
|
14
13
|
sequence: sequenceHelpers.mysql | sequenceHelpers.postgres;
|
|
15
14
|
number: numberHelpers.cockroachdb | numberHelpers.mssql | numberHelpers.postgres | numberHelpers.sqlite | numberHelpers.oracle;
|
|
16
|
-
nullableUpdate: nullableUpdateHelper.postgres | nullableUpdateHelper.oracle;
|
|
17
15
|
};
|
|
18
16
|
export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.postgres | fnHelpers.mssql | fnHelpers.mysql | fnHelpers.sqlite | fnHelpers.oracle;
|
|
19
17
|
export type Helpers = ReturnType<typeof getHelpers>;
|
|
@@ -2,10 +2,9 @@ import { getDatabaseClient } from '../index.js';
|
|
|
2
2
|
import * as dateHelpers from './date/index.js';
|
|
3
3
|
import * as fnHelpers from './fn/index.js';
|
|
4
4
|
import * as geometryHelpers from './geometry/index.js';
|
|
5
|
+
import * as numberHelpers from './number/index.js';
|
|
5
6
|
import * as schemaHelpers from './schema/index.js';
|
|
6
7
|
import * as sequenceHelpers from './sequence/index.js';
|
|
7
|
-
import * as numberHelpers from './number/index.js';
|
|
8
|
-
import * as nullableUpdateHelper from './nullable-update/index.js';
|
|
9
8
|
export function getHelpers(database) {
|
|
10
9
|
const client = getDatabaseClient(database);
|
|
11
10
|
return {
|
|
@@ -14,7 +13,6 @@ export function getHelpers(database) {
|
|
|
14
13
|
schema: new schemaHelpers[client](database),
|
|
15
14
|
sequence: new sequenceHelpers[client](database),
|
|
16
15
|
number: new numberHelpers[client](database),
|
|
17
|
-
nullableUpdate: new nullableUpdateHelper[client](database),
|
|
18
16
|
};
|
|
19
17
|
}
|
|
20
18
|
export function getFunctions(database, schema) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { NumericValue } from '@directus/types';
|
|
1
2
|
import type { Knex } from 'knex';
|
|
2
3
|
import { NumberDatabaseHelper, type NumberInfo } from '../types.js';
|
|
3
|
-
import type { NumericValue } from '@directus/types';
|
|
4
4
|
export declare class NumberHelperMSSQL extends NumberDatabaseHelper {
|
|
5
|
-
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
5
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue, logical: 'and' | 'or'): Knex.QueryBuilder;
|
|
6
6
|
isNumberValid(value: NumericValue, info: NumberInfo): boolean;
|
|
7
7
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { NumberDatabaseHelper } from '../types.js';
|
|
1
2
|
import { maybeStringifyBigInt } from '../utils/maybe-stringify-big-int.js';
|
|
2
3
|
import { numberInRange } from '../utils/number-in-range.js';
|
|
3
|
-
import { NumberDatabaseHelper } from '../types.js';
|
|
4
4
|
export class NumberHelperMSSQL extends NumberDatabaseHelper {
|
|
5
|
-
addSearchCondition(dbQuery, collection, name, value) {
|
|
6
|
-
return dbQuery.
|
|
5
|
+
addSearchCondition(dbQuery, collection, name, value, logical) {
|
|
6
|
+
return dbQuery[logical].where({ [`${collection}.${name}`]: maybeStringifyBigInt(value) });
|
|
7
7
|
}
|
|
8
8
|
isNumberValid(value, info) {
|
|
9
9
|
return numberInRange(value, info);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { NumericValue } from '@directus/types';
|
|
1
2
|
import type { Knex } from 'knex';
|
|
2
3
|
import { NumberDatabaseHelper } from '../types.js';
|
|
3
|
-
import type { NumericValue } from '@directus/types';
|
|
4
4
|
export declare class NumberHelperOracle extends NumberDatabaseHelper {
|
|
5
|
-
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
5
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue, logical: 'and' | 'or'): Knex.QueryBuilder;
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NumberDatabaseHelper } from '../types.js';
|
|
2
2
|
import { maybeStringifyBigInt } from '../utils/maybe-stringify-big-int.js';
|
|
3
3
|
export class NumberHelperOracle extends NumberDatabaseHelper {
|
|
4
|
-
addSearchCondition(dbQuery, collection, name, value) {
|
|
5
|
-
return dbQuery.
|
|
4
|
+
addSearchCondition(dbQuery, collection, name, value, logical) {
|
|
5
|
+
return dbQuery[logical].where({ [`${collection}.${name}`]: maybeStringifyBigInt(value) });
|
|
6
6
|
}
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { NumericValue } from '@directus/types';
|
|
1
2
|
import type { Knex } from 'knex';
|
|
2
3
|
import { NumberDatabaseHelper } from '../types.js';
|
|
3
|
-
import type { NumericValue } from '@directus/types';
|
|
4
4
|
export declare class NumberHelperSQLite extends NumberDatabaseHelper {
|
|
5
|
-
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
5
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue, logical: 'and' | 'or'): Knex.QueryBuilder;
|
|
6
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NumberDatabaseHelper } from '../types.js';
|
|
2
2
|
import { maybeStringifyBigInt } from '../utils/maybe-stringify-big-int.js';
|
|
3
3
|
export class NumberHelperSQLite extends NumberDatabaseHelper {
|
|
4
|
-
addSearchCondition(dbQuery, collection, name, value) {
|
|
5
|
-
return dbQuery.
|
|
4
|
+
addSearchCondition(dbQuery, collection, name, value, logical) {
|
|
5
|
+
return dbQuery[logical].where({ [`${collection}.${name}`]: maybeStringifyBigInt(value) });
|
|
6
6
|
}
|
|
7
7
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import type { NumericType, NumericValue } from '@directus/types';
|
|
1
2
|
import type { Knex } from 'knex';
|
|
2
3
|
import { DatabaseHelper } from '../types.js';
|
|
3
|
-
import type { NumericType, NumericValue } from '@directus/types';
|
|
4
4
|
export type NumberInfo = {
|
|
5
5
|
type: NumericType;
|
|
6
6
|
precision: number | null;
|
|
7
7
|
scale: number | null;
|
|
8
8
|
};
|
|
9
9
|
export declare abstract class NumberDatabaseHelper extends DatabaseHelper {
|
|
10
|
-
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue): Knex.QueryBuilder;
|
|
10
|
+
addSearchCondition(dbQuery: Knex.QueryBuilder, collection: string, name: string, value: NumericValue, logical: 'and' | 'or'): Knex.QueryBuilder;
|
|
11
11
|
isNumberValid(_value: NumericValue, _info: NumberInfo): boolean;
|
|
12
12
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DatabaseHelper } from '../types.js';
|
|
2
2
|
export class NumberDatabaseHelper extends DatabaseHelper {
|
|
3
|
-
addSearchCondition(dbQuery, collection, name, value) {
|
|
4
|
-
return dbQuery.
|
|
3
|
+
addSearchCondition(dbQuery, collection, name, value, logical) {
|
|
4
|
+
return dbQuery[logical].where({ [`${collection}.${name}`]: value });
|
|
5
5
|
}
|
|
6
6
|
isNumberValid(_value, _info) {
|
|
7
7
|
return true;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { KNEX_TYPES } from '@directus/constants';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Column } from '@directus/schema';
|
|
3
|
+
import type { Field, RawField, Relation, Type } from '@directus/types';
|
|
3
4
|
import type { Knex } from 'knex';
|
|
4
5
|
import type { Options, SortRecord, Sql } from '../types.js';
|
|
5
6
|
import { SchemaHelper } from '../types.js';
|
|
@@ -10,6 +11,10 @@ export declare class SchemaHelperOracle extends SchemaHelper {
|
|
|
10
11
|
preRelationChange(relation: Partial<Relation>): void;
|
|
11
12
|
processFieldType(field: Field): Type;
|
|
12
13
|
getDatabaseSize(): Promise<number | null>;
|
|
14
|
+
/**
|
|
15
|
+
* Oracle throws an error when overwriting the nullable option for an existing column with the same value.
|
|
16
|
+
*/
|
|
17
|
+
setNullable(column: Knex.ColumnBuilder, field: RawField | Field, existing: Column | null): void;
|
|
13
18
|
prepQueryParams(queryParams: Sql): Sql;
|
|
14
19
|
prepBindings(bindings: Knex.Value[]): any;
|
|
15
20
|
addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], _hasRelationalSort: boolean): void;
|
|
@@ -52,6 +52,21 @@ export class SchemaHelperOracle extends SchemaHelper {
|
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Oracle throws an error when overwriting the nullable option for an existing column with the same value.
|
|
57
|
+
*/
|
|
58
|
+
setNullable(column, field, existing) {
|
|
59
|
+
if (!existing) {
|
|
60
|
+
super.setNullable(column, field, existing);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (field.schema?.is_nullable === false && existing.is_nullable === true) {
|
|
64
|
+
column.notNullable();
|
|
65
|
+
}
|
|
66
|
+
else if (field.schema?.is_nullable === true && existing.is_nullable === false) {
|
|
67
|
+
column.nullable();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
55
70
|
prepQueryParams(queryParams) {
|
|
56
71
|
return prepQueryParams(queryParams, { format: (index) => `:${index + 1}` });
|
|
57
72
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { KNEX_TYPES } from '@directus/constants';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Column } from '@directus/schema';
|
|
3
|
+
import type { Field, RawField, Relation, Type } from '@directus/types';
|
|
3
4
|
import type { Knex } from 'knex';
|
|
4
5
|
import type { DatabaseClient } from '../../../types/index.js';
|
|
5
6
|
import { DatabaseHelper } from '../types.js';
|
|
@@ -25,6 +26,7 @@ export declare abstract class SchemaHelper extends DatabaseHelper {
|
|
|
25
26
|
preColumnChange(): Promise<boolean>;
|
|
26
27
|
postColumnChange(): Promise<void>;
|
|
27
28
|
preRelationChange(_relation: Partial<Relation>): void;
|
|
29
|
+
setNullable(column: Knex.ColumnBuilder, field: RawField | Field, existing: Column | null): void;
|
|
28
30
|
processFieldType(field: Field): Type;
|
|
29
31
|
constraintName(existingName: string): string;
|
|
30
32
|
applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void;
|
|
@@ -66,6 +66,15 @@ export class SchemaHelper extends DatabaseHelper {
|
|
|
66
66
|
preRelationChange(_relation) {
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
+
setNullable(column, field, existing) {
|
|
70
|
+
const isNullable = field.schema?.is_nullable ?? existing?.is_nullable ?? true;
|
|
71
|
+
if (isNullable) {
|
|
72
|
+
column.nullable();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
column.notNullable();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
69
78
|
processFieldType(field) {
|
|
70
79
|
return field.type;
|
|
71
80
|
}
|
package/dist/database/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { performance } from 'perf_hooks';
|
|
|
11
11
|
import { promisify } from 'util';
|
|
12
12
|
import { getExtensionsPath } from '../extensions/lib/get-extensions-path.js';
|
|
13
13
|
import { useLogger } from '../logger/index.js';
|
|
14
|
+
import { useMetrics } from '../metrics/index.js';
|
|
14
15
|
import { getConfigFromEnv } from '../utils/get-config-from-env.js';
|
|
15
16
|
import { validateEnv } from '../utils/validate-env.js';
|
|
16
17
|
import { getHelpers } from './helpers/index.js';
|
|
@@ -25,6 +26,7 @@ export function getDatabase() {
|
|
|
25
26
|
}
|
|
26
27
|
const env = useEnv();
|
|
27
28
|
const logger = useLogger();
|
|
29
|
+
const metrics = useMetrics();
|
|
28
30
|
const { client, version, searchPath, connectionString, pool: poolConfig = {}, ...connectionConfig } = getConfigFromEnv('DB_', ['DB_EXCLUDE_TABLES']);
|
|
29
31
|
const requiredEnvVars = ['DB_CLIENT'];
|
|
30
32
|
switch (client) {
|
|
@@ -148,6 +150,7 @@ export function getDatabase() {
|
|
|
148
150
|
if (time) {
|
|
149
151
|
delta = performance.now() - time;
|
|
150
152
|
times.delete(queryInfo.__knexUid);
|
|
153
|
+
metrics?.getDatabaseResponseMetric()?.observe(delta);
|
|
151
154
|
}
|
|
152
155
|
// eslint-disable-next-line no-nested-ternary
|
|
153
156
|
const bindings = queryInfo.bindings
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/use-metrics.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib/use-metrics.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MetricObjectWithValues, MetricValue } from 'prom-client';
|
|
2
|
+
import { Counter, Histogram } from 'prom-client';
|
|
3
|
+
export declare function createMetrics(): {
|
|
4
|
+
getDatabaseErrorMetric: () => Counter | null;
|
|
5
|
+
getDatabaseResponseMetric: () => Histogram | null;
|
|
6
|
+
getCacheErrorMetric: () => Counter | null;
|
|
7
|
+
getRedisErrorMetric: () => Counter | null;
|
|
8
|
+
getStorageErrorMetric: (location: string) => Counter | null;
|
|
9
|
+
aggregate: (data: {
|
|
10
|
+
pid: number;
|
|
11
|
+
metrics: MetricObjectWithValues<MetricValue<string>>[];
|
|
12
|
+
}) => Promise<void>;
|
|
13
|
+
generate: () => Promise<void>;
|
|
14
|
+
readAll: () => Promise<string>;
|
|
15
|
+
};
|