@directus/api 25.0.1 → 26.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/app.js +3 -3
- package/dist/auth/drivers/oauth2.d.ts +2 -0
- package/dist/auth/drivers/oauth2.js +40 -2
- package/dist/auth/drivers/openid.js +8 -1
- package/dist/controllers/access.js +2 -2
- package/dist/controllers/comments.js +2 -2
- package/dist/controllers/dashboards.js +2 -2
- package/dist/controllers/files.js +2 -2
- package/dist/controllers/flows.js +2 -2
- package/dist/controllers/folders.js +2 -2
- package/dist/controllers/items.js +2 -2
- package/dist/controllers/notifications.js +2 -2
- package/dist/controllers/operations.js +2 -2
- package/dist/controllers/panels.js +2 -2
- package/dist/controllers/permissions.js +2 -2
- package/dist/controllers/policies.js +2 -2
- package/dist/controllers/presets.js +2 -2
- package/dist/controllers/roles.js +2 -2
- package/dist/controllers/shares.js +2 -2
- package/dist/controllers/translations.js +2 -2
- package/dist/controllers/users.js +2 -2
- package/dist/controllers/utils.js +8 -3
- package/dist/controllers/versions.js +2 -2
- package/dist/controllers/webhooks.js +1 -1
- package/dist/database/helpers/capabilities/dialects/default.d.ts +3 -0
- package/dist/database/helpers/capabilities/dialects/default.js +3 -0
- package/dist/database/helpers/capabilities/dialects/mysql.d.ts +4 -0
- package/dist/database/helpers/capabilities/dialects/mysql.js +9 -0
- package/dist/database/helpers/capabilities/dialects/postgres.d.ts +5 -0
- package/dist/database/helpers/capabilities/dialects/postgres.js +14 -0
- package/dist/database/helpers/capabilities/index.d.ts +7 -0
- package/dist/database/helpers/capabilities/index.js +7 -0
- package/dist/database/helpers/capabilities/types.d.ts +11 -0
- package/dist/database/helpers/capabilities/types.js +15 -0
- package/dist/database/helpers/index.d.ts +2 -0
- package/dist/database/helpers/index.js +2 -0
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -2
- package/dist/database/helpers/schema/dialects/cockroachdb.js +0 -4
- package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -2
- package/dist/database/helpers/schema/dialects/postgres.js +0 -4
- package/dist/database/index.js +1 -1
- package/dist/database/migrations/20250224A-visual-editor.d.ts +3 -0
- package/dist/database/migrations/20250224A-visual-editor.js +35 -0
- package/dist/database/run-ast/lib/get-db-query.js +16 -4
- package/dist/logger/index.js +3 -3
- package/dist/middleware/sanitize-query.js +17 -7
- package/dist/middleware/validate-batch.js +1 -1
- package/dist/operations/item-delete/index.js +1 -1
- package/dist/operations/item-read/index.js +1 -1
- package/dist/operations/item-update/index.js +1 -1
- package/dist/permissions/lib/fetch-permissions.js +6 -4
- package/dist/permissions/modules/process-ast/utils/context-has-dynamic-variables.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/context-has-dynamic-variables.js +3 -0
- package/dist/permissions/modules/process-payload/process-payload.d.ts +1 -0
- package/dist/permissions/modules/process-payload/process-payload.js +19 -4
- package/dist/permissions/types.d.ts +2 -1
- package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +3 -2
- package/dist/permissions/utils/extract-required-dynamic-variable-context.js +24 -5
- package/dist/permissions/utils/fetch-dynamic-variable-data.d.ts +9 -0
- package/dist/permissions/utils/{fetch-dynamic-variable-context.js → fetch-dynamic-variable-data.js} +13 -12
- package/dist/rate-limiter.js +1 -1
- package/dist/services/assets.js +12 -2
- package/dist/services/authentication.js +2 -2
- package/dist/services/collections.js +8 -2
- package/dist/services/graphql/resolvers/get-collection-type.d.ts +3 -0
- package/dist/services/graphql/resolvers/get-collection-type.js +34 -0
- package/dist/services/graphql/resolvers/get-field-type.d.ts +3 -0
- package/dist/services/graphql/resolvers/get-field-type.js +51 -0
- package/dist/services/graphql/resolvers/get-relation-type.d.ts +3 -0
- package/dist/services/graphql/resolvers/get-relation-type.js +39 -0
- package/dist/services/graphql/resolvers/mutation.js +1 -1
- package/dist/services/graphql/resolvers/query.js +4 -4
- package/dist/services/graphql/resolvers/system-admin.d.ts +2 -2
- package/dist/services/graphql/resolvers/system-admin.js +207 -199
- package/dist/services/graphql/resolvers/system.d.ts +1 -7
- package/dist/services/graphql/resolvers/system.js +12 -113
- package/dist/services/graphql/schema/index.js +1 -1
- package/dist/services/graphql/schema/parse-query.d.ts +2 -2
- package/dist/services/graphql/schema/parse-query.js +6 -6
- package/dist/services/graphql/schema/read.d.ts +2 -2
- package/dist/services/graphql/schema/read.js +86 -2
- package/dist/services/graphql/schema-cache.d.ts +2 -2
- package/dist/services/graphql/schema-cache.js +1 -3
- package/dist/services/graphql/subscription.d.ts +3 -3
- package/dist/services/graphql/subscription.js +3 -3
- package/dist/services/graphql/utils/{aggrgate-query.d.ts → aggregate-query.d.ts} +2 -2
- package/dist/services/graphql/utils/{aggrgate-query.js → aggregate-query.js} +3 -3
- package/dist/services/items.d.ts +1 -0
- package/dist/services/items.js +30 -16
- package/dist/services/payload.d.ts +1 -0
- package/dist/services/payload.js +32 -17
- package/dist/services/shares.js +1 -1
- package/dist/services/specifications.js +10 -5
- package/dist/services/tus/lockers.d.ts +1 -1
- package/dist/services/tus/lockers.js +6 -5
- package/dist/services/tus/server.js +24 -0
- package/dist/services/users.js +1 -0
- package/dist/types/services.d.ts +2 -0
- package/dist/utils/apply-query.d.ts +1 -0
- package/dist/utils/apply-query.js +21 -12
- package/dist/utils/generate-hash.js +1 -1
- package/dist/utils/get-config-from-env.d.ts +6 -1
- package/dist/utils/get-config-from-env.js +16 -11
- package/dist/utils/get-graphql-type.js +3 -1
- package/dist/utils/is-login-redirect-allowed.js +2 -0
- package/dist/utils/redact-object.js +5 -1
- package/dist/utils/sanitize-query.d.ts +5 -2
- package/dist/utils/sanitize-query.js +34 -9
- package/dist/websocket/controllers/base.d.ts +2 -2
- package/dist/websocket/handlers/items.js +4 -4
- package/dist/websocket/handlers/subscribe.js +2 -2
- package/dist/websocket/messages.d.ts +7 -7
- package/dist/websocket/messages.js +1 -1
- package/package.json +59 -59
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +0 -8
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { toBoolean } from '@directus/utils';
|
|
3
3
|
import { GraphQLBoolean, GraphQLEnumType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString, } from 'graphql';
|
|
4
|
-
import { GraphQLJSON,
|
|
4
|
+
import { GraphQLJSON, SchemaComposer, toInputObjectType } from 'graphql-compose';
|
|
5
5
|
import getDatabase from '../../../database/index.js';
|
|
6
6
|
import { fetchAccountabilityCollectionAccess } from '../../../permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js';
|
|
7
7
|
import { fetchAccountabilityPolicyGlobals } from '../../../permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js';
|
|
8
|
-
import { getGraphQLType } from '../../../utils/get-graphql-type.js';
|
|
9
8
|
import { CollectionsService } from '../../collections.js';
|
|
10
9
|
import { FieldsService } from '../../fields.js';
|
|
11
10
|
import { FilesService } from '../../files.js';
|
|
@@ -18,6 +17,9 @@ import { GraphQLService } from '../index.js';
|
|
|
18
17
|
import { generateSchema } from '../schema/index.js';
|
|
19
18
|
import { getQuery } from '../schema/parse-query.js';
|
|
20
19
|
import { replaceFragmentsInSelections } from '../utils/replace-fragments.js';
|
|
20
|
+
import { getCollectionType } from './get-collection-type.js';
|
|
21
|
+
import { getFieldType } from './get-field-type.js';
|
|
22
|
+
import { getRelationType } from './get-relation-type.js';
|
|
21
23
|
import { resolveSystemAdmin } from './system-admin.js';
|
|
22
24
|
import { globalResolvers } from './system-global.js';
|
|
23
25
|
const env = useEnv();
|
|
@@ -187,47 +189,8 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
187
189
|
},
|
|
188
190
|
},
|
|
189
191
|
});
|
|
190
|
-
const Collection = schemaComposer.createObjectTC({
|
|
191
|
-
name: 'directus_collections',
|
|
192
|
-
});
|
|
193
|
-
const Field = schemaComposer.createObjectTC({
|
|
194
|
-
name: 'directus_fields',
|
|
195
|
-
});
|
|
196
|
-
const Relation = schemaComposer.createObjectTC({
|
|
197
|
-
name: 'directus_relations',
|
|
198
|
-
});
|
|
199
|
-
const Extension = schemaComposer.createObjectTC({
|
|
200
|
-
name: 'directus_extensions',
|
|
201
|
-
});
|
|
202
|
-
const composers = {
|
|
203
|
-
Collection,
|
|
204
|
-
Field,
|
|
205
|
-
Relation,
|
|
206
|
-
Extension,
|
|
207
|
-
};
|
|
208
192
|
if ('directus_collections' in schema.read.collections) {
|
|
209
|
-
Collection
|
|
210
|
-
collection: GraphQLString,
|
|
211
|
-
meta: schemaComposer.createObjectTC({
|
|
212
|
-
name: 'directus_collections_meta',
|
|
213
|
-
fields: Object.values(schema.read.collections['directus_collections'].fields).reduce((acc, field) => {
|
|
214
|
-
acc[field.field] = {
|
|
215
|
-
type: field.nullable
|
|
216
|
-
? getGraphQLType(field.type, field.special)
|
|
217
|
-
: new GraphQLNonNull(getGraphQLType(field.type, field.special)),
|
|
218
|
-
description: field.note,
|
|
219
|
-
};
|
|
220
|
-
return acc;
|
|
221
|
-
}, {}),
|
|
222
|
-
}),
|
|
223
|
-
schema: schemaComposer.createObjectTC({
|
|
224
|
-
name: 'directus_collections_schema',
|
|
225
|
-
fields: {
|
|
226
|
-
name: GraphQLString,
|
|
227
|
-
comment: GraphQLString,
|
|
228
|
-
},
|
|
229
|
-
}),
|
|
230
|
-
});
|
|
193
|
+
const Collection = getCollectionType(schemaComposer, schema, 'read');
|
|
231
194
|
schemaComposer.Query.addFields({
|
|
232
195
|
collections: {
|
|
233
196
|
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Collection.getType()))),
|
|
@@ -255,45 +218,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
255
218
|
});
|
|
256
219
|
}
|
|
257
220
|
if ('directus_fields' in schema.read.collections) {
|
|
258
|
-
Field
|
|
259
|
-
collection: GraphQLString,
|
|
260
|
-
field: GraphQLString,
|
|
261
|
-
type: GraphQLString,
|
|
262
|
-
meta: schemaComposer.createObjectTC({
|
|
263
|
-
name: 'directus_fields_meta',
|
|
264
|
-
fields: Object.values(schema.read.collections['directus_fields'].fields).reduce((acc, field) => {
|
|
265
|
-
acc[field.field] = {
|
|
266
|
-
type: field.nullable
|
|
267
|
-
? getGraphQLType(field.type, field.special)
|
|
268
|
-
: new GraphQLNonNull(getGraphQLType(field.type, field.special)),
|
|
269
|
-
description: field.note,
|
|
270
|
-
};
|
|
271
|
-
return acc;
|
|
272
|
-
}, {}),
|
|
273
|
-
}),
|
|
274
|
-
schema: schemaComposer.createObjectTC({
|
|
275
|
-
name: 'directus_fields_schema',
|
|
276
|
-
fields: {
|
|
277
|
-
name: GraphQLString,
|
|
278
|
-
table: GraphQLString,
|
|
279
|
-
data_type: GraphQLString,
|
|
280
|
-
default_value: GraphQLString,
|
|
281
|
-
max_length: GraphQLInt,
|
|
282
|
-
numeric_precision: GraphQLInt,
|
|
283
|
-
numeric_scale: GraphQLInt,
|
|
284
|
-
is_generated: GraphQLBoolean,
|
|
285
|
-
generation_expression: GraphQLString,
|
|
286
|
-
is_indexed: GraphQLBoolean,
|
|
287
|
-
is_nullable: GraphQLBoolean,
|
|
288
|
-
is_unique: GraphQLBoolean,
|
|
289
|
-
is_primary_key: GraphQLBoolean,
|
|
290
|
-
has_auto_increment: GraphQLBoolean,
|
|
291
|
-
foreign_key_column: GraphQLString,
|
|
292
|
-
foreign_key_table: GraphQLString,
|
|
293
|
-
comment: GraphQLString,
|
|
294
|
-
},
|
|
295
|
-
}),
|
|
296
|
-
});
|
|
221
|
+
const Field = getFieldType(schemaComposer, schema, 'read');
|
|
297
222
|
schemaComposer.Query.addFields({
|
|
298
223
|
fields: {
|
|
299
224
|
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Field.getType()))),
|
|
@@ -335,33 +260,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
335
260
|
});
|
|
336
261
|
}
|
|
337
262
|
if ('directus_relations' in schema.read.collections) {
|
|
338
|
-
Relation
|
|
339
|
-
collection: GraphQLString,
|
|
340
|
-
field: GraphQLString,
|
|
341
|
-
related_collection: GraphQLString,
|
|
342
|
-
schema: schemaComposer.createObjectTC({
|
|
343
|
-
name: 'directus_relations_schema',
|
|
344
|
-
fields: {
|
|
345
|
-
table: new GraphQLNonNull(GraphQLString),
|
|
346
|
-
column: new GraphQLNonNull(GraphQLString),
|
|
347
|
-
foreign_key_table: new GraphQLNonNull(GraphQLString),
|
|
348
|
-
foreign_key_column: new GraphQLNonNull(GraphQLString),
|
|
349
|
-
constraint_name: GraphQLString,
|
|
350
|
-
on_update: new GraphQLNonNull(GraphQLString),
|
|
351
|
-
on_delete: new GraphQLNonNull(GraphQLString),
|
|
352
|
-
},
|
|
353
|
-
}),
|
|
354
|
-
meta: schemaComposer.createObjectTC({
|
|
355
|
-
name: 'directus_relations_meta',
|
|
356
|
-
fields: Object.values(schema.read.collections['directus_relations'].fields).reduce((acc, field) => {
|
|
357
|
-
acc[field.field] = {
|
|
358
|
-
type: getGraphQLType(field.type, field.special),
|
|
359
|
-
description: field.note,
|
|
360
|
-
};
|
|
361
|
-
return acc;
|
|
362
|
-
}, {}),
|
|
363
|
-
}),
|
|
364
|
-
});
|
|
263
|
+
const Relation = getRelationType(schemaComposer, schema, 'read');
|
|
365
264
|
schemaComposer.Query.addFields({
|
|
366
265
|
relations: {
|
|
367
266
|
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Relation.getType()))),
|
|
@@ -402,7 +301,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
402
301
|
},
|
|
403
302
|
});
|
|
404
303
|
}
|
|
405
|
-
resolveSystemAdmin(gql,
|
|
304
|
+
resolveSystemAdmin(gql, schema, schemaComposer);
|
|
406
305
|
if ('directus_users' in schema.read.collections) {
|
|
407
306
|
schemaComposer.Query.addFields({
|
|
408
307
|
users_me: {
|
|
@@ -412,7 +311,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
412
311
|
return null;
|
|
413
312
|
const service = new UsersService({ schema: gql.schema, accountability: gql.accountability });
|
|
414
313
|
const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
415
|
-
const query = getQuery(args, selections || [], info.variableValues, gql.accountability);
|
|
314
|
+
const query = await getQuery(args, selections || [], info.variableValues, gql.schema, gql.accountability);
|
|
416
315
|
return await service.readOne(gql.accountability.user, query);
|
|
417
316
|
},
|
|
418
317
|
},
|
|
@@ -450,7 +349,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
450
349
|
schema: gql.schema,
|
|
451
350
|
});
|
|
452
351
|
const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
453
|
-
const query = getQuery(args, selections || [], info.variableValues, gql.accountability);
|
|
352
|
+
const query = await getQuery(args, selections || [], info.variableValues, gql.schema, gql.accountability);
|
|
454
353
|
query.limit = -1;
|
|
455
354
|
const roles = await service.readMany(gql.accountability.roles, query);
|
|
456
355
|
return roles;
|
|
@@ -498,7 +397,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
498
397
|
await service.updateOne(gql.accountability.user, args['data']);
|
|
499
398
|
if ('directus_users' in ReadCollectionTypes) {
|
|
500
399
|
const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
501
|
-
const query = getQuery(args, selections || [], info.variableValues, gql.accountability);
|
|
400
|
+
const query = await getQuery(args, selections || [], info.variableValues, gql.schema, gql.accountability);
|
|
502
401
|
return await service.readOne(gql.accountability.user, query);
|
|
503
402
|
}
|
|
504
403
|
return true;
|
|
@@ -522,7 +421,7 @@ export function injectSystemResolvers(gql, schemaComposer, { CreateCollectionTyp
|
|
|
522
421
|
const primaryKey = await service.importOne(args['url'], args['data']);
|
|
523
422
|
if ('directus_files' in ReadCollectionTypes) {
|
|
524
423
|
const selections = replaceFragmentsInSelections(info.fieldNodes[0]?.selectionSet?.selections, info.fragments);
|
|
525
|
-
const query = getQuery(args, selections || [], info.variableValues, gql.accountability);
|
|
424
|
+
const query = await getQuery(args, selections || [], info.variableValues, gql.schema, gql.accountability);
|
|
526
425
|
return await service.readOne(primaryKey, query);
|
|
527
426
|
}
|
|
528
427
|
return true;
|
|
@@ -89,7 +89,7 @@ export async function generateSchema(gql, type = 'schema') {
|
|
|
89
89
|
action: 'delete',
|
|
90
90
|
}, { schema: gql.schema, knex: gql.knex }),
|
|
91
91
|
};
|
|
92
|
-
const { ReadCollectionTypes, VersionCollectionTypes } = getReadableTypes(gql, schemaComposer, schema, inconsistentFields);
|
|
92
|
+
const { ReadCollectionTypes, VersionCollectionTypes } = await getReadableTypes(gql, schemaComposer, schema, inconsistentFields);
|
|
93
93
|
const { CreateCollectionTypes, UpdateCollectionTypes, DeleteCollectionTypes } = getWritableTypes(gql, schemaComposer, schema, inconsistentFields, ReadCollectionTypes);
|
|
94
94
|
const CollectionTypes = {
|
|
95
95
|
CreateCollectionTypes,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { Accountability, Query } from '@directus/types';
|
|
1
|
+
import type { Accountability, Query, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { GraphQLResolveInfo, SelectionNode } from 'graphql';
|
|
3
3
|
/**
|
|
4
4
|
* Get a Directus Query object from the parsed arguments (rawQuery) and GraphQL AST selectionSet. Converts SelectionSet into
|
|
5
5
|
* Directus' `fields` query for use in the resolver. Also applies variables where appropriate.
|
|
6
6
|
*/
|
|
7
|
-
export declare function getQuery(rawQuery: Query, selections: readonly SelectionNode[], variableValues: GraphQLResolveInfo['variableValues'], accountability?: Accountability | null): Query
|
|
7
|
+
export declare function getQuery(rawQuery: Query, selections: readonly SelectionNode[], variableValues: GraphQLResolveInfo['variableValues'], schema: SchemaOverview, accountability?: Accountability | null): Promise<Query>;
|
|
@@ -7,8 +7,8 @@ import { parseArgs } from './parse-args.js';
|
|
|
7
7
|
* Get a Directus Query object from the parsed arguments (rawQuery) and GraphQL AST selectionSet. Converts SelectionSet into
|
|
8
8
|
* Directus' `fields` query for use in the resolver. Also applies variables where appropriate.
|
|
9
9
|
*/
|
|
10
|
-
export function getQuery(rawQuery, selections, variableValues, accountability) {
|
|
11
|
-
const query = sanitizeQuery(rawQuery, accountability);
|
|
10
|
+
export async function getQuery(rawQuery, selections, variableValues, schema, accountability) {
|
|
11
|
+
const query = await sanitizeQuery(rawQuery, schema, accountability);
|
|
12
12
|
const parseAliases = (selections) => {
|
|
13
13
|
const aliases = {};
|
|
14
14
|
for (const selection of selections) {
|
|
@@ -20,7 +20,7 @@ export function getQuery(rawQuery, selections, variableValues, accountability) {
|
|
|
20
20
|
}
|
|
21
21
|
return aliases;
|
|
22
22
|
};
|
|
23
|
-
const parseFields = (selections, parent) => {
|
|
23
|
+
const parseFields = async (selections, parent) => {
|
|
24
24
|
const fields = [];
|
|
25
25
|
for (let selection of selections) {
|
|
26
26
|
if ((selection.kind === 'Field' || selection.kind === 'InlineFragment') !== true)
|
|
@@ -70,7 +70,7 @@ export function getQuery(rawQuery, selections, variableValues, accountability) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
children = parseFields(selection.selectionSet.selections, currentAlias ?? current);
|
|
73
|
+
children = await parseFields(selection.selectionSet.selections, currentAlias ?? current);
|
|
74
74
|
}
|
|
75
75
|
fields.push(...children);
|
|
76
76
|
}
|
|
@@ -82,14 +82,14 @@ export function getQuery(rawQuery, selections, variableValues, accountability) {
|
|
|
82
82
|
if (!query.deep)
|
|
83
83
|
query.deep = {};
|
|
84
84
|
const args = parseArgs(selection.arguments, variableValues);
|
|
85
|
-
set(query.deep, currentAlias ?? current, merge({}, get(query.deep, currentAlias ?? current), mapKeys(sanitizeQuery(args, accountability), (_value, key) => `_${key}`)));
|
|
85
|
+
set(query.deep, currentAlias ?? current, merge({}, get(query.deep, currentAlias ?? current), mapKeys(await sanitizeQuery(args, schema, accountability), (_value, key) => `_${key}`)));
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
return uniq(fields);
|
|
90
90
|
};
|
|
91
91
|
query.alias = parseAliases(selections);
|
|
92
|
-
query.fields = parseFields(selections);
|
|
92
|
+
query.fields = await parseFields(selections);
|
|
93
93
|
if (query.filter)
|
|
94
94
|
query.filter = replaceFuncs(query.filter);
|
|
95
95
|
query.deep = replaceFuncs(query.deep);
|
|
@@ -5,8 +5,8 @@ import { type InconsistentFields, type Schema } from './index.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Create readable types and attach resolvers for each. Also prepares full filter argument structures
|
|
7
7
|
*/
|
|
8
|
-
export declare function getReadableTypes(gql: GraphQLService, schemaComposer: SchemaComposer, schema: Schema, inconsistentFields: InconsistentFields): {
|
|
8
|
+
export declare function getReadableTypes(gql: GraphQLService, schemaComposer: SchemaComposer, schema: Schema, inconsistentFields: InconsistentFields): Promise<{
|
|
9
9
|
ReadCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
|
10
10
|
VersionCollectionTypes: Record<string, ObjectTypeComposer<any, any>>;
|
|
11
11
|
ReadableCollectionFilterTypes: Record<string, InputTypeComposer<any>>;
|
|
12
|
-
}
|
|
12
|
+
}>;
|
|
@@ -14,12 +14,75 @@ import { getTypes } from './get-types.js';
|
|
|
14
14
|
/**
|
|
15
15
|
* Create readable types and attach resolvers for each. Also prepares full filter argument structures
|
|
16
16
|
*/
|
|
17
|
-
export function getReadableTypes(gql, schemaComposer, schema, inconsistentFields) {
|
|
17
|
+
export async function getReadableTypes(gql, schemaComposer, schema, inconsistentFields) {
|
|
18
18
|
const { CollectionTypes: ReadCollectionTypes, VersionTypes: VersionCollectionTypes } = getTypes(schemaComposer, gql.scope, schema, inconsistentFields, 'read');
|
|
19
19
|
const ReadableCollectionFilterTypes = {};
|
|
20
|
+
const ReadableCollectionQuantifierFilterTypes = {};
|
|
20
21
|
const AggregatedFunctions = {};
|
|
21
22
|
const AggregatedFields = {};
|
|
22
23
|
const AggregateMethods = {};
|
|
24
|
+
const IDFilterOperators = schemaComposer.createInputTC({
|
|
25
|
+
name: 'id_filter_operators',
|
|
26
|
+
fields: {
|
|
27
|
+
_eq: {
|
|
28
|
+
type: GraphQLID,
|
|
29
|
+
},
|
|
30
|
+
_neq: {
|
|
31
|
+
type: GraphQLID,
|
|
32
|
+
},
|
|
33
|
+
_contains: {
|
|
34
|
+
type: GraphQLID,
|
|
35
|
+
},
|
|
36
|
+
_icontains: {
|
|
37
|
+
type: GraphQLID,
|
|
38
|
+
},
|
|
39
|
+
_ncontains: {
|
|
40
|
+
type: GraphQLID,
|
|
41
|
+
},
|
|
42
|
+
_starts_with: {
|
|
43
|
+
type: GraphQLID,
|
|
44
|
+
},
|
|
45
|
+
_nstarts_with: {
|
|
46
|
+
type: GraphQLID,
|
|
47
|
+
},
|
|
48
|
+
_istarts_with: {
|
|
49
|
+
type: GraphQLID,
|
|
50
|
+
},
|
|
51
|
+
_nistarts_with: {
|
|
52
|
+
type: GraphQLID,
|
|
53
|
+
},
|
|
54
|
+
_ends_with: {
|
|
55
|
+
type: GraphQLID,
|
|
56
|
+
},
|
|
57
|
+
_nends_with: {
|
|
58
|
+
type: GraphQLID,
|
|
59
|
+
},
|
|
60
|
+
_iends_with: {
|
|
61
|
+
type: GraphQLID,
|
|
62
|
+
},
|
|
63
|
+
_niends_with: {
|
|
64
|
+
type: GraphQLID,
|
|
65
|
+
},
|
|
66
|
+
_in: {
|
|
67
|
+
type: new GraphQLList(GraphQLID),
|
|
68
|
+
},
|
|
69
|
+
_nin: {
|
|
70
|
+
type: new GraphQLList(GraphQLID),
|
|
71
|
+
},
|
|
72
|
+
_null: {
|
|
73
|
+
type: GraphQLBoolean,
|
|
74
|
+
},
|
|
75
|
+
_nnull: {
|
|
76
|
+
type: GraphQLBoolean,
|
|
77
|
+
},
|
|
78
|
+
_empty: {
|
|
79
|
+
type: GraphQLBoolean,
|
|
80
|
+
},
|
|
81
|
+
_nempty: {
|
|
82
|
+
type: GraphQLBoolean,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
23
86
|
const StringFilterOperators = schemaComposer.createInputTC({
|
|
24
87
|
name: 'string_filter_operators',
|
|
25
88
|
fields: {
|
|
@@ -356,6 +419,9 @@ export function getReadableTypes(gql, schemaComposer, schema, inconsistentFields
|
|
|
356
419
|
case GraphQLHash:
|
|
357
420
|
filterOperatorType = HashFilterOperators;
|
|
358
421
|
break;
|
|
422
|
+
case GraphQLID:
|
|
423
|
+
filterOperatorType = IDFilterOperators;
|
|
424
|
+
break;
|
|
359
425
|
default:
|
|
360
426
|
filterOperatorType = StringFilterOperators;
|
|
361
427
|
}
|
|
@@ -591,10 +657,21 @@ export function getReadableTypes(gql, schemaComposer, schema, inconsistentFields
|
|
|
591
657
|
});
|
|
592
658
|
}
|
|
593
659
|
}
|
|
660
|
+
for (const collection in ReadableCollectionFilterTypes) {
|
|
661
|
+
const quantifier_collection = ReadableCollectionFilterTypes[collection]?.clone(`${collection}_quantifier_filter`);
|
|
662
|
+
quantifier_collection?.addFields({
|
|
663
|
+
_some: ReadableCollectionFilterTypes[collection],
|
|
664
|
+
_none: ReadableCollectionFilterTypes[collection],
|
|
665
|
+
});
|
|
666
|
+
ReadableCollectionQuantifierFilterTypes[collection] = quantifier_collection;
|
|
667
|
+
}
|
|
594
668
|
for (const relation of schema.read.relations) {
|
|
595
669
|
if (relation.related_collection) {
|
|
596
670
|
if (SYSTEM_DENY_LIST.includes(relation.related_collection))
|
|
597
671
|
continue;
|
|
672
|
+
ReadableCollectionQuantifierFilterTypes[relation.collection]?.addFields({
|
|
673
|
+
[relation.field]: ReadableCollectionFilterTypes[relation.related_collection],
|
|
674
|
+
});
|
|
598
675
|
ReadableCollectionFilterTypes[relation.collection]?.addFields({
|
|
599
676
|
[relation.field]: ReadableCollectionFilterTypes[relation.related_collection],
|
|
600
677
|
});
|
|
@@ -617,8 +694,11 @@ export function getReadableTypes(gql, schemaComposer, schema, inconsistentFields
|
|
|
617
694
|
},
|
|
618
695
|
});
|
|
619
696
|
if (relation.meta?.one_field) {
|
|
697
|
+
ReadableCollectionQuantifierFilterTypes[relation.related_collection]?.addFields({
|
|
698
|
+
[relation.meta.one_field]: ReadableCollectionQuantifierFilterTypes[relation.collection],
|
|
699
|
+
});
|
|
620
700
|
ReadableCollectionFilterTypes[relation.related_collection]?.addFields({
|
|
621
|
-
[relation.meta.one_field]:
|
|
701
|
+
[relation.meta.one_field]: ReadableCollectionQuantifierFilterTypes[relation.collection],
|
|
622
702
|
});
|
|
623
703
|
ReadCollectionTypes[relation.related_collection]?.addFieldArgs(relation.meta.one_field, {
|
|
624
704
|
filter: ReadableCollectionFilterTypes[relation.collection],
|
|
@@ -641,8 +721,12 @@ export function getReadableTypes(gql, schemaComposer, schema, inconsistentFields
|
|
|
641
721
|
}
|
|
642
722
|
}
|
|
643
723
|
else if (relation.meta?.one_allowed_collections) {
|
|
724
|
+
ReadableCollectionQuantifierFilterTypes[relation.collection]?.removeField('item');
|
|
644
725
|
ReadableCollectionFilterTypes[relation.collection]?.removeField('item');
|
|
645
726
|
for (const collection of relation.meta.one_allowed_collections) {
|
|
727
|
+
ReadableCollectionQuantifierFilterTypes[relation.collection]?.addFields({
|
|
728
|
+
[`item__${collection}`]: ReadableCollectionFilterTypes[collection],
|
|
729
|
+
});
|
|
646
730
|
ReadableCollectionFilterTypes[relation.collection]?.addFields({
|
|
647
731
|
[`item__${collection}`]: ReadableCollectionFilterTypes[collection],
|
|
648
732
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { GraphQLSchema } from 'graphql';
|
|
2
|
-
import
|
|
3
|
-
export declare const cache:
|
|
2
|
+
import { LRUMap } from 'mnemonist';
|
|
3
|
+
export declare const cache: LRUMap<string, string | GraphQLSchema>;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { useEnv } from '@directus/env';
|
|
2
2
|
import { GraphQLSchema } from 'graphql';
|
|
3
|
-
import
|
|
3
|
+
import { LRUMap } from 'mnemonist';
|
|
4
4
|
import { useBus } from '../../bus/index.js';
|
|
5
|
-
// Workaround for misaligned types in mnemonist package exports
|
|
6
|
-
const LRUMap = LRUMapDefault;
|
|
7
5
|
const env = useEnv();
|
|
8
6
|
const bus = useBus();
|
|
9
7
|
export const cache = new LRUMap(Number(env['GRAPHQL_SCHEMA_CACHE_CAPACITY'] ?? 100));
|
|
@@ -2,19 +2,19 @@ import type { GraphQLService } from './index.js';
|
|
|
2
2
|
import type { GraphQLResolveInfo } from 'graphql';
|
|
3
3
|
export declare function bindPubSub(): void;
|
|
4
4
|
export declare function createSubscriptionGenerator(gql: GraphQLService, event: string): (_x: unknown, _y: unknown, _z: unknown, request: GraphQLResolveInfo) => AsyncGenerator<{
|
|
5
|
-
[
|
|
5
|
+
[event]: {
|
|
6
6
|
key: string | number;
|
|
7
7
|
data: null;
|
|
8
8
|
event: "delete";
|
|
9
9
|
};
|
|
10
10
|
} | {
|
|
11
|
-
[
|
|
11
|
+
[event]: {
|
|
12
12
|
key: string | number;
|
|
13
13
|
data: any;
|
|
14
14
|
event: "create";
|
|
15
15
|
};
|
|
16
16
|
} | {
|
|
17
|
-
[
|
|
17
|
+
[event]: {
|
|
18
18
|
key: string | number;
|
|
19
19
|
data: any;
|
|
20
20
|
event: "update";
|
|
@@ -12,7 +12,7 @@ export function bindPubSub() {
|
|
|
12
12
|
}
|
|
13
13
|
export function createSubscriptionGenerator(gql, event) {
|
|
14
14
|
return async function* (_x, _y, _z, request) {
|
|
15
|
-
const fields = parseFields(gql, request);
|
|
15
|
+
const fields = await parseFields(gql, request);
|
|
16
16
|
const args = parseArguments(request);
|
|
17
17
|
for await (const payload of messages.subscribe(event)) {
|
|
18
18
|
const eventData = payload;
|
|
@@ -79,7 +79,7 @@ function createPubSub(emitter) {
|
|
|
79
79
|
},
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
-
function parseFields(gql, request) {
|
|
82
|
+
async function parseFields(gql, request) {
|
|
83
83
|
const selections = request.fieldNodes[0]?.selectionSet?.selections ?? [];
|
|
84
84
|
const dataSelections = selections.reduce((result, selection) => {
|
|
85
85
|
if (selection.kind === 'Field' &&
|
|
@@ -89,7 +89,7 @@ function parseFields(gql, request) {
|
|
|
89
89
|
}
|
|
90
90
|
return result;
|
|
91
91
|
}, []);
|
|
92
|
-
const { fields } = getQuery({}, dataSelections, request.variableValues, gql.accountability);
|
|
92
|
+
const { fields } = await getQuery({}, dataSelections, request.variableValues, gql.schema, gql.accountability);
|
|
93
93
|
return fields ?? [];
|
|
94
94
|
}
|
|
95
95
|
function parseArguments(request) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { Accountability, Query } from '@directus/types';
|
|
1
|
+
import type { Accountability, Query, SchemaOverview } from '@directus/types';
|
|
2
2
|
import type { SelectionNode } from 'graphql';
|
|
3
3
|
/**
|
|
4
4
|
* Resolve the aggregation query based on the requested aggregated fields
|
|
5
5
|
*/
|
|
6
|
-
export declare function getAggregateQuery(rawQuery: Query, selections: readonly SelectionNode[], accountability?: Accountability | null): Query
|
|
6
|
+
export declare function getAggregateQuery(rawQuery: Query, selections: readonly SelectionNode[], schema: SchemaOverview, accountability?: Accountability | null): Promise<Query>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { replaceFuncs } from './replace-funcs.js';
|
|
2
1
|
import { sanitizeQuery } from '../../../utils/sanitize-query.js';
|
|
3
2
|
import { validateQuery } from '../../../utils/validate-query.js';
|
|
3
|
+
import { replaceFuncs } from './replace-funcs.js';
|
|
4
4
|
/**
|
|
5
5
|
* Resolve the aggregation query based on the requested aggregated fields
|
|
6
6
|
*/
|
|
7
|
-
export function getAggregateQuery(rawQuery, selections, accountability) {
|
|
8
|
-
const query = sanitizeQuery(rawQuery, accountability);
|
|
7
|
+
export async function getAggregateQuery(rawQuery, selections, schema, accountability) {
|
|
8
|
+
const query = await sanitizeQuery(rawQuery, schema, accountability);
|
|
9
9
|
query.aggregate = {};
|
|
10
10
|
for (let aggregationGroup of selections) {
|
|
11
11
|
if ((aggregationGroup.kind === 'Field') !== true)
|
package/dist/services/items.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare class ItemsService<Item extends AnyItem = AnyItem, Collection ext
|
|
|
18
18
|
eventScope: string;
|
|
19
19
|
schema: SchemaOverview;
|
|
20
20
|
cache: Keyv<any> | null;
|
|
21
|
+
nested: string[];
|
|
21
22
|
constructor(collection: Collection, options: AbstractServiceOptions);
|
|
22
23
|
/**
|
|
23
24
|
* Create a fork of the current service, allowing instantiation with different options.
|
package/dist/services/items.js
CHANGED
|
@@ -26,6 +26,7 @@ export class ItemsService {
|
|
|
26
26
|
eventScope;
|
|
27
27
|
schema;
|
|
28
28
|
cache;
|
|
29
|
+
nested;
|
|
29
30
|
constructor(collection, options) {
|
|
30
31
|
this.collection = collection;
|
|
31
32
|
this.knex = options.knex || getDatabase();
|
|
@@ -33,6 +34,7 @@ export class ItemsService {
|
|
|
33
34
|
this.eventScope = isSystemCollection(this.collection) ? this.collection.substring(9) : 'items';
|
|
34
35
|
this.schema = options.schema;
|
|
35
36
|
this.cache = getCache().cache;
|
|
37
|
+
this.nested = options.nested ?? [];
|
|
36
38
|
return this;
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
@@ -43,7 +45,13 @@ export class ItemsService {
|
|
|
43
45
|
// ItemsService expects `collection` and `options` as parameters,
|
|
44
46
|
// while the other services only expect `options`
|
|
45
47
|
const isItemsService = Service.length === 2;
|
|
46
|
-
const newOptions = {
|
|
48
|
+
const newOptions = {
|
|
49
|
+
knex: this.knex,
|
|
50
|
+
accountability: this.accountability,
|
|
51
|
+
schema: this.schema,
|
|
52
|
+
nested: this.nested,
|
|
53
|
+
...options,
|
|
54
|
+
};
|
|
47
55
|
if (isItemsService) {
|
|
48
56
|
return new ItemsService(this.collection, newOptions);
|
|
49
57
|
}
|
|
@@ -95,19 +103,15 @@ export class ItemsService {
|
|
|
95
103
|
.filter((field) => field.alias === true)
|
|
96
104
|
.map((field) => field.field);
|
|
97
105
|
const payload = cloneDeep(data);
|
|
106
|
+
let actionHookPayload = payload;
|
|
98
107
|
const nestedActionEvents = [];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
/**
|
|
109
|
+
* By wrapping the logic in a transaction, we make sure we automatically roll back all the
|
|
110
|
+
* changes in the DB if any of the parts contained within throws an error. This also means
|
|
111
|
+
* that any errors thrown in any nested relational changes will bubble up and cancel the whole
|
|
112
|
+
* update tree
|
|
113
|
+
*/
|
|
103
114
|
const primaryKey = await transaction(this.knex, async (trx) => {
|
|
104
|
-
const serviceOptions = {
|
|
105
|
-
accountability: this.accountability,
|
|
106
|
-
knex: trx,
|
|
107
|
-
schema: this.schema,
|
|
108
|
-
};
|
|
109
|
-
// We're creating new services instances so they can use the transaction as their Knex interface
|
|
110
|
-
const payloadService = new PayloadService(this.collection, serviceOptions);
|
|
111
115
|
// Run all hooks that are attached to this event so the end user has the chance to augment the
|
|
112
116
|
// item that is about to be saved
|
|
113
117
|
const payloadAfterHooks = opts.emitEvents !== false
|
|
@@ -127,6 +131,7 @@ export class ItemsService {
|
|
|
127
131
|
action: 'create',
|
|
128
132
|
collection: this.collection,
|
|
129
133
|
payload: payloadAfterHooks,
|
|
134
|
+
nested: this.nested,
|
|
130
135
|
}, {
|
|
131
136
|
knex: trx,
|
|
132
137
|
schema: this.schema,
|
|
@@ -135,6 +140,15 @@ export class ItemsService {
|
|
|
135
140
|
if (opts.preMutationError) {
|
|
136
141
|
throw opts.preMutationError;
|
|
137
142
|
}
|
|
143
|
+
// Ensure the action hook payload has the post filter hook + preset changes
|
|
144
|
+
actionHookPayload = payloadWithPresets;
|
|
145
|
+
// We're creating new services instances so they can use the transaction as their Knex interface
|
|
146
|
+
const payloadService = new PayloadService(this.collection, {
|
|
147
|
+
accountability: this.accountability,
|
|
148
|
+
knex: trx,
|
|
149
|
+
schema: this.schema,
|
|
150
|
+
nested: this.nested,
|
|
151
|
+
});
|
|
138
152
|
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
139
153
|
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
140
154
|
const payloadWithoutAliases = pick(payloadWithA2O, without(fields, ...aliases));
|
|
@@ -190,7 +204,7 @@ export class ItemsService {
|
|
|
190
204
|
primaryKey = result.id;
|
|
191
205
|
// Set the primary key on the input item, in order for the "after" event hook to be able
|
|
192
206
|
// to read from it
|
|
193
|
-
|
|
207
|
+
actionHookPayload[primaryKeyField] = primaryKey;
|
|
194
208
|
}
|
|
195
209
|
// At this point, the primary key is guaranteed to be set.
|
|
196
210
|
primaryKey = primaryKey;
|
|
@@ -260,7 +274,7 @@ export class ItemsService {
|
|
|
260
274
|
? ['items.create', `${this.collection}.items.create`]
|
|
261
275
|
: `${this.eventScope}.create`,
|
|
262
276
|
meta: {
|
|
263
|
-
payload,
|
|
277
|
+
payload: actionHookPayload,
|
|
264
278
|
key: primaryKey,
|
|
265
279
|
collection: this.collection,
|
|
266
280
|
},
|
|
@@ -450,8 +464,6 @@ export class ItemsService {
|
|
|
450
464
|
* Uses `this.updateMany` under the hood.
|
|
451
465
|
*/
|
|
452
466
|
async updateOne(key, data, opts) {
|
|
453
|
-
const primaryKeyField = this.schema.collections[this.collection].primary;
|
|
454
|
-
validateKeys(this.schema, this.collection, primaryKeyField, key);
|
|
455
467
|
await this.updateMany([key], data, opts);
|
|
456
468
|
return key;
|
|
457
469
|
}
|
|
@@ -553,6 +565,7 @@ export class ItemsService {
|
|
|
553
565
|
action: 'update',
|
|
554
566
|
collection: this.collection,
|
|
555
567
|
payload: payloadAfterHooks,
|
|
568
|
+
nested: this.nested,
|
|
556
569
|
}, {
|
|
557
570
|
knex: this.knex,
|
|
558
571
|
schema: this.schema,
|
|
@@ -566,6 +579,7 @@ export class ItemsService {
|
|
|
566
579
|
accountability: this.accountability,
|
|
567
580
|
knex: trx,
|
|
568
581
|
schema: this.schema,
|
|
582
|
+
nested: this.nested,
|
|
569
583
|
});
|
|
570
584
|
const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
|
|
571
585
|
const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
|
|
@@ -29,6 +29,7 @@ export declare class PayloadService {
|
|
|
29
29
|
helpers: Helpers;
|
|
30
30
|
collection: string;
|
|
31
31
|
schema: SchemaOverview;
|
|
32
|
+
nested: string[];
|
|
32
33
|
constructor(collection: string, options: AbstractServiceOptions);
|
|
33
34
|
transformers: Transformers;
|
|
34
35
|
processValues(action: Action, payloads: Partial<Item>[]): Promise<Partial<Item>[]>;
|