@directus/api 29.1.1 → 31.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.
Files changed (105) hide show
  1. package/dist/app.js +5 -0
  2. package/dist/auth/drivers/oauth2.js +17 -3
  3. package/dist/auth/drivers/openid.js +17 -3
  4. package/dist/constants.d.ts +1 -1
  5. package/dist/constants.js +9 -1
  6. package/dist/controllers/items.js +3 -4
  7. package/dist/controllers/mcp.d.ts +2 -0
  8. package/dist/controllers/mcp.js +33 -0
  9. package/dist/controllers/users.js +17 -7
  10. package/dist/controllers/versions.js +3 -2
  11. package/dist/database/errors/dialects/mssql.d.ts +1 -1
  12. package/dist/database/errors/dialects/mssql.js +18 -10
  13. package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
  14. package/dist/database/migrations/20250813A-add-mcp.js +18 -0
  15. package/dist/database/run-ast/README.md +46 -0
  16. package/dist/mailer.js +3 -3
  17. package/dist/mcp/define.d.ts +2 -0
  18. package/dist/mcp/define.js +3 -0
  19. package/dist/mcp/index.d.ts +1 -0
  20. package/dist/mcp/index.js +1 -0
  21. package/dist/mcp/schema.d.ts +485 -0
  22. package/dist/mcp/schema.js +219 -0
  23. package/dist/mcp/server.d.ts +97 -0
  24. package/dist/mcp/server.js +310 -0
  25. package/dist/mcp/tools/assets.d.ts +3 -0
  26. package/dist/mcp/tools/assets.js +54 -0
  27. package/dist/mcp/tools/collections.d.ts +84 -0
  28. package/dist/mcp/tools/collections.js +90 -0
  29. package/dist/mcp/tools/fields.d.ts +101 -0
  30. package/dist/mcp/tools/fields.js +157 -0
  31. package/dist/mcp/tools/files.d.ts +235 -0
  32. package/dist/mcp/tools/files.js +103 -0
  33. package/dist/mcp/tools/flows.d.ts +323 -0
  34. package/dist/mcp/tools/flows.js +85 -0
  35. package/dist/mcp/tools/folders.d.ts +95 -0
  36. package/dist/mcp/tools/folders.js +96 -0
  37. package/dist/mcp/tools/index.d.ts +15 -0
  38. package/dist/mcp/tools/index.js +29 -0
  39. package/dist/mcp/tools/items.d.ts +87 -0
  40. package/dist/mcp/tools/items.js +141 -0
  41. package/dist/mcp/tools/operations.d.ts +171 -0
  42. package/dist/mcp/tools/operations.js +77 -0
  43. package/dist/mcp/tools/prompts/assets.md +8 -0
  44. package/dist/mcp/tools/prompts/collections.md +336 -0
  45. package/dist/mcp/tools/prompts/fields.md +521 -0
  46. package/dist/mcp/tools/prompts/files.md +180 -0
  47. package/dist/mcp/tools/prompts/flows.md +495 -0
  48. package/dist/mcp/tools/prompts/folders.md +34 -0
  49. package/dist/mcp/tools/prompts/index.d.ts +16 -0
  50. package/dist/mcp/tools/prompts/index.js +19 -0
  51. package/dist/mcp/tools/prompts/items.md +317 -0
  52. package/dist/mcp/tools/prompts/operations.md +721 -0
  53. package/dist/mcp/tools/prompts/relations.md +386 -0
  54. package/dist/mcp/tools/prompts/schema.md +130 -0
  55. package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
  56. package/dist/mcp/tools/prompts/system-prompt.md +44 -0
  57. package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
  58. package/dist/mcp/tools/relations.d.ts +73 -0
  59. package/dist/mcp/tools/relations.js +93 -0
  60. package/dist/mcp/tools/schema.d.ts +54 -0
  61. package/dist/mcp/tools/schema.js +317 -0
  62. package/dist/mcp/tools/system.d.ts +3 -0
  63. package/dist/mcp/tools/system.js +22 -0
  64. package/dist/mcp/tools/trigger-flow.d.ts +8 -0
  65. package/dist/mcp/tools/trigger-flow.js +48 -0
  66. package/dist/mcp/transport.d.ts +13 -0
  67. package/dist/mcp/transport.js +18 -0
  68. package/dist/mcp/types.d.ts +56 -0
  69. package/dist/mcp/types.js +1 -0
  70. package/dist/middleware/respond.js +2 -2
  71. package/dist/services/authentication.js +36 -0
  72. package/dist/services/fields.js +4 -4
  73. package/dist/services/graphql/index.d.ts +2 -2
  74. package/dist/services/graphql/index.js +6 -5
  75. package/dist/services/graphql/resolvers/query.js +4 -39
  76. package/dist/services/items.js +38 -12
  77. package/dist/services/payload.d.ts +7 -3
  78. package/dist/services/payload.js +70 -12
  79. package/dist/services/server.js +1 -0
  80. package/dist/services/tfa.d.ts +1 -1
  81. package/dist/services/tfa.js +20 -5
  82. package/dist/services/versions.d.ts +6 -4
  83. package/dist/services/versions.js +84 -25
  84. package/dist/types/auth.d.ts +2 -1
  85. package/dist/utils/deep-map-response.d.ts +17 -0
  86. package/dist/utils/deep-map-response.js +61 -0
  87. package/dist/utils/get-relation-info.d.ts +1 -2
  88. package/dist/utils/permissions-cacheable.d.ts +8 -0
  89. package/dist/utils/{permissions-cachable.js → permissions-cacheable.js} +8 -6
  90. package/dist/utils/transaction.d.ts +1 -1
  91. package/dist/utils/transaction.js +18 -2
  92. package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
  93. package/dist/utils/versioning/deep-map-with-schema.js +81 -0
  94. package/dist/utils/versioning/handle-version.d.ts +3 -0
  95. package/dist/utils/versioning/handle-version.js +96 -0
  96. package/dist/utils/versioning/merge-version-data.d.ts +2 -0
  97. package/dist/utils/versioning/merge-version-data.js +10 -0
  98. package/dist/utils/versioning/split-recursive.d.ts +4 -0
  99. package/dist/utils/versioning/split-recursive.js +27 -0
  100. package/package.json +31 -30
  101. package/dist/middleware/merge-content-versions.d.ts +0 -2
  102. package/dist/middleware/merge-content-versions.js +0 -26
  103. package/dist/utils/merge-version-data.d.ts +0 -3
  104. package/dist/utils/merge-version-data.js +0 -134
  105. package/dist/utils/permissions-cachable.d.ts +0 -8
@@ -1,7 +1,5 @@
1
1
  import { parseFilterFunctionPath } from '@directus/utils';
2
2
  import { omit } from 'lodash-es';
3
- import { mergeVersionsRaw, mergeVersionsRecursive } from '../../../utils/merge-version-data.js';
4
- import { VersionsService } from '../../versions.js';
5
3
  import { parseArgs } from '../schema/parse-args.js';
6
4
  import { getQuery } from '../schema/parse-query.js';
7
5
  import { getAggregateQuery } from '../utils/aggregate-query.js';
@@ -19,7 +17,6 @@ export async function resolveQuery(gql, info) {
19
17
  return null;
20
18
  const args = parseArgs(info.fieldNodes[0].arguments || [], info.variableValues);
21
19
  let query;
22
- let versionRaw = false;
23
20
  const isAggregate = collection.endsWith('_aggregated') && collection in gql.schema.collections === false;
24
21
  if (isAggregate) {
25
22
  query = await getAggregateQuery(args, selections, gql.schema, gql.accountability);
@@ -32,50 +29,18 @@ export async function resolveQuery(gql, info) {
32
29
  }
33
30
  if (collection.endsWith('_by_version') && collection in gql.schema.collections === false) {
34
31
  collection = collection.slice(0, -11);
35
- versionRaw = true;
32
+ query.versionRaw = true;
36
33
  }
37
34
  }
38
- if (args['id']) {
39
- query.filter = {
40
- _and: [
41
- query.filter || {},
42
- {
43
- [gql.schema.collections[collection].primary]: {
44
- _eq: args['id'],
45
- },
46
- },
47
- ],
48
- };
49
- query.limit = 1;
50
- }
51
35
  // Transform count(a.b.c) into a.b.count(c)
52
36
  if (query.fields?.length) {
53
37
  for (let fieldIndex = 0; fieldIndex < query.fields.length; fieldIndex++) {
54
38
  query.fields[fieldIndex] = parseFilterFunctionPath(query.fields[fieldIndex]);
55
39
  }
56
40
  }
57
- const result = await gql.read(collection, query);
58
- if (args['version']) {
59
- const versionsService = new VersionsService({ accountability: gql.accountability, schema: gql.schema });
60
- const saves = await versionsService.getVersionSaves(args['version'], collection, args['id']);
61
- if (saves) {
62
- if (gql.schema.collections[collection].singleton) {
63
- return versionRaw
64
- ? mergeVersionsRaw(result, saves)
65
- : mergeVersionsRecursive(result, saves, collection, gql.schema);
66
- }
67
- else {
68
- if (result?.[0] === undefined)
69
- return null;
70
- return versionRaw
71
- ? mergeVersionsRaw(result[0], saves)
72
- : mergeVersionsRecursive(result[0], saves, collection, gql.schema);
73
- }
74
- }
75
- }
76
- if (args['id']) {
77
- return result?.[0] || null;
78
- }
41
+ const result = await gql.read(collection, query, args['id']);
42
+ if (args['id'])
43
+ return result;
79
44
  if (query.group) {
80
45
  // for every entry in result add a group field based on query.group;
81
46
  const aggregateKeys = Object.keys(query.aggregate ?? {});
@@ -18,6 +18,7 @@ import { shouldClearCache } from '../utils/should-clear-cache.js';
18
18
  import { transaction } from '../utils/transaction.js';
19
19
  import { validateKeys } from '../utils/validate-keys.js';
20
20
  import { validateUserCountIntegrity } from '../utils/validate-user-count-integrity.js';
21
+ import { handleVersion } from '../utils/versioning/handle-version.js';
21
22
  import { PayloadService } from './payload.js';
22
23
  const env = useEnv();
23
24
  export class ItemsService {
@@ -96,8 +97,6 @@ export class ItemsService {
96
97
  if (!opts.bypassLimits) {
97
98
  opts.mutationTracker.trackMutations(1);
98
99
  }
99
- const { ActivityService } = await import('./activity.js');
100
- const { RevisionsService } = await import('./revisions.js');
101
100
  const primaryKeyField = this.schema.collections[this.collection].primary;
102
101
  const fields = Object.keys(this.schema.collections[this.collection].fields);
103
102
  const aliases = Object.values(this.schema.collections[this.collection].fields)
@@ -149,6 +148,7 @@ export class ItemsService {
149
148
  knex: trx,
150
149
  schema: this.schema,
151
150
  nested: this.nested,
151
+ overwriteDefaults: opts.overwriteDefaults,
152
152
  });
153
153
  const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
154
154
  const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
@@ -226,7 +226,11 @@ export class ItemsService {
226
226
  }
227
227
  }
228
228
  // If this is an authenticated action, and accountability tracking is enabled, save activity row
229
- if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
229
+ if (opts.skipTracking !== true &&
230
+ this.accountability &&
231
+ this.schema.collections[this.collection].accountability !== null) {
232
+ const { ActivityService } = await import('./activity.js');
233
+ const { RevisionsService } = await import('./revisions.js');
230
234
  const activityService = new ActivityService({
231
235
  knex: trx,
232
236
  schema: this.schema,
@@ -267,6 +271,9 @@ export class ItemsService {
267
271
  if (autoIncrementSequenceNeedsToBeReset) {
268
272
  await getHelpers(trx).sequence.resetAutoIncrementSequence(this.collection, primaryKeyField);
269
273
  }
274
+ if (opts.onItemCreate) {
275
+ opts.onItemCreate(this.collection, primaryKey);
276
+ }
270
277
  return primaryKey;
271
278
  });
272
279
  if (opts.emitEvents !== false) {
@@ -332,6 +339,7 @@ export class ItemsService {
332
339
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
333
340
  bypassEmitAction: (params) => nestedActionEvents.push(params),
334
341
  mutationTracker: opts.mutationTracker,
342
+ overwriteDefaults: opts.overwriteDefaults?.[index],
335
343
  bypassAutoIncrementSequenceReset,
336
344
  });
337
345
  primaryKeys.push(primaryKey);
@@ -427,7 +435,13 @@ export class ItemsService {
427
435
  validateKeys(this.schema, this.collection, primaryKeyField, key);
428
436
  const filterWithKey = assign({}, query.filter, { [primaryKeyField]: { _eq: key } });
429
437
  const queryWithKey = assign({}, query, { filter: filterWithKey });
430
- const results = await this.readByQuery(queryWithKey, opts);
438
+ let results = [];
439
+ if (query.version) {
440
+ results = [await handleVersion(this, key, queryWithKey, opts)];
441
+ }
442
+ else {
443
+ results = await this.readByQuery(queryWithKey, opts);
444
+ }
431
445
  if (results.length === 0) {
432
446
  throw new ForbiddenError();
433
447
  }
@@ -485,13 +499,15 @@ export class ItemsService {
485
499
  await transaction(this.knex, async (knex) => {
486
500
  const service = this.fork({ knex });
487
501
  let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
488
- for (const item of data) {
502
+ for (const index in data) {
503
+ const item = data[index];
489
504
  const primaryKey = item[primaryKeyField];
490
505
  if (!primaryKey)
491
506
  throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
492
507
  const combinedOpts = {
493
508
  autoPurgeCache: false,
494
509
  ...opts,
510
+ overwriteDefaults: opts.overwriteDefaults?.[index],
495
511
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
496
512
  };
497
513
  keys.push(await service.updateOne(primaryKey, omit(item, primaryKeyField), combinedOpts));
@@ -522,8 +538,6 @@ export class ItemsService {
522
538
  if (!opts.bypassLimits) {
523
539
  opts.mutationTracker.trackMutations(keys.length);
524
540
  }
525
- const { ActivityService } = await import('./activity.js');
526
- const { RevisionsService } = await import('./revisions.js');
527
541
  const primaryKeyField = this.schema.collections[this.collection].primary;
528
542
  validateKeys(this.schema, this.collection, primaryKeyField, keys);
529
543
  const fields = Object.keys(this.schema.collections[this.collection].fields);
@@ -581,6 +595,7 @@ export class ItemsService {
581
595
  knex: trx,
582
596
  schema: this.schema,
583
597
  nested: this.nested,
598
+ overwriteDefaults: opts.overwriteDefaults,
584
599
  });
585
600
  const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
586
601
  const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
@@ -616,7 +631,11 @@ export class ItemsService {
616
631
  }
617
632
  }
618
633
  // If this is an authenticated action, and accountability tracking is enabled, save activity row
619
- if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
634
+ if (opts.skipTracking !== true &&
635
+ this.accountability &&
636
+ this.schema.collections[this.collection].accountability !== null) {
637
+ const { ActivityService } = await import('./activity.js');
638
+ const { RevisionsService } = await import('./revisions.js');
620
639
  const activityService = new ActivityService({
621
640
  knex: trx,
622
641
  schema: this.schema,
@@ -738,8 +757,13 @@ export class ItemsService {
738
757
  const primaryKeys = await transaction(this.knex, async (knex) => {
739
758
  const service = this.fork({ knex });
740
759
  const primaryKeys = [];
741
- for (const payload of payloads) {
742
- const primaryKey = await service.upsertOne(payload, { ...(opts || {}), autoPurgeCache: false });
760
+ for (const index in payloads) {
761
+ const payload = payloads[index];
762
+ const primaryKey = await service.upsertOne(payload, {
763
+ ...(opts || {}),
764
+ overwriteDefaults: opts.overwriteDefaults?.[index],
765
+ autoPurgeCache: false,
766
+ });
743
767
  primaryKeys.push(primaryKey);
744
768
  }
745
769
  return primaryKeys;
@@ -780,7 +804,6 @@ export class ItemsService {
780
804
  if (!opts.bypassLimits) {
781
805
  opts.mutationTracker.trackMutations(keys.length);
782
806
  }
783
- const { ActivityService } = await import('./activity.js');
784
807
  const primaryKeyField = this.schema.collections[this.collection].primary;
785
808
  validateKeys(this.schema, this.collection, primaryKeyField, keys);
786
809
  if (this.accountability) {
@@ -816,7 +839,10 @@ export class ItemsService {
816
839
  await validateUserCountIntegrity({ flags: opts.userIntegrityCheckFlags, knex: trx });
817
840
  }
818
841
  }
819
- if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
842
+ if (opts.skipTracking !== true &&
843
+ this.accountability &&
844
+ this.schema.collections[this.collection].accountability !== null) {
845
+ const { ActivityService } = await import('./activity.js');
820
846
  const activityService = new ActivityService({
821
847
  knex: trx,
822
848
  schema: this.schema,
@@ -1,4 +1,4 @@
1
- import type { AbstractServiceOptions, Accountability, Aggregate, FieldOverview, Item, MutationOptions, PayloadAction, PayloadServiceProcessRelationResult, PrimaryKey, SchemaOverview } from '@directus/types';
1
+ import type { AbstractServiceOptions, Accountability, Aggregate, DefaultOverwrite, FieldOverview, Item, MutationOptions, PayloadAction, PayloadServiceProcessRelationResult, PrimaryKey, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  import type { Helpers } from '../database/helpers/index.js';
4
4
  type Transformers = {
@@ -9,6 +9,7 @@ type Transformers = {
9
9
  accountability: Accountability | null;
10
10
  specials: string[];
11
11
  helpers: Helpers;
12
+ overwriteDefaults: DefaultOverwrite | undefined;
12
13
  }) => Promise<any>;
13
14
  };
14
15
  /**
@@ -22,7 +23,10 @@ export declare class PayloadService {
22
23
  collection: string;
23
24
  schema: SchemaOverview;
24
25
  nested: string[];
25
- constructor(collection: string, options: AbstractServiceOptions);
26
+ overwriteDefaults: DefaultOverwrite | undefined;
27
+ constructor(collection: string, options: AbstractServiceOptions & {
28
+ overwriteDefaults?: DefaultOverwrite | undefined;
29
+ });
26
30
  transformers: Transformers;
27
31
  processValues(action: PayloadAction, payloads: Partial<Item>[]): Promise<Partial<Item>[]>;
28
32
  processValues(action: PayloadAction, payload: Partial<Item>): Promise<Partial<Item>>;
@@ -62,6 +66,6 @@ export declare class PayloadService {
62
66
  * Transforms the input partial payload to match the output structure, to have consistency
63
67
  * between delta and data
64
68
  */
65
- prepareDelta(data: Partial<Item>): Promise<string | null>;
69
+ prepareDelta(delta: Partial<Item>): Promise<string | null>;
66
70
  }
67
71
  export {};
@@ -21,6 +21,7 @@ export class PayloadService {
21
21
  collection;
22
22
  schema;
23
23
  nested;
24
+ overwriteDefaults;
24
25
  constructor(collection, options) {
25
26
  this.accountability = options.accountability || null;
26
27
  this.knex = options.knex || getDatabase();
@@ -28,6 +29,7 @@ export class PayloadService {
28
29
  this.collection = collection;
29
30
  this.schema = options.schema;
30
31
  this.nested = options.nested ?? [];
32
+ this.overwriteDefaults = options.overwriteDefaults;
31
33
  return this;
32
34
  }
33
35
  transformers = {
@@ -77,14 +79,14 @@ export class PayloadService {
77
79
  return value ? '**********' : null;
78
80
  return value;
79
81
  },
80
- async 'user-created'({ action, value, accountability }) {
82
+ async 'user-created'({ action, value, accountability, overwriteDefaults }) {
81
83
  if (action === 'create')
82
- return accountability?.user || null;
84
+ return (overwriteDefaults ? overwriteDefaults._user : accountability?.user) ?? null;
83
85
  return value;
84
86
  },
85
- async 'user-updated'({ action, value, accountability }) {
87
+ async 'user-updated'({ action, value, accountability, overwriteDefaults }) {
86
88
  if (action === 'update')
87
- return accountability?.user || null;
89
+ return (overwriteDefaults ? overwriteDefaults._user : accountability?.user) ?? null;
88
90
  return value;
89
91
  },
90
92
  async 'role-created'({ action, value, accountability }) {
@@ -97,14 +99,14 @@ export class PayloadService {
97
99
  return accountability?.role || null;
98
100
  return value;
99
101
  },
100
- async 'date-created'({ action, value, helpers }) {
102
+ async 'date-created'({ action, value, helpers, overwriteDefaults }) {
101
103
  if (action === 'create')
102
- return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
104
+ return new Date(overwriteDefaults ? overwriteDefaults._date : helpers.date.writeTimestamp(new Date().toISOString()));
103
105
  return value;
104
106
  },
105
- async 'date-updated'({ action, value, helpers }) {
107
+ async 'date-updated'({ action, value, helpers, overwriteDefaults }) {
106
108
  if (action === 'update')
107
- return new Date(helpers.date.writeTimestamp(new Date().toISOString()));
109
+ return new Date(overwriteDefaults ? overwriteDefaults._date : helpers.date.writeTimestamp(new Date().toISOString()));
108
110
  return value;
109
111
  },
110
112
  async 'cast-csv'({ action, value }) {
@@ -212,6 +214,7 @@ export class PayloadService {
212
214
  accountability,
213
215
  specials: fieldSpecials,
214
216
  helpers: this.helpers,
217
+ overwriteDefaults: this.overwriteDefaults,
215
218
  });
216
219
  }
217
220
  }
@@ -402,6 +405,11 @@ export class PayloadService {
402
405
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
403
406
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
404
407
  emitEvents: opts?.emitEvents,
408
+ autoPurgeCache: opts?.autoPurgeCache,
409
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
410
+ skipTracking: opts?.skipTracking,
411
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
412
+ onItemCreate: opts?.onItemCreate,
405
413
  mutationTracker: opts?.mutationTracker,
406
414
  });
407
415
  }
@@ -412,6 +420,11 @@ export class PayloadService {
412
420
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
413
421
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
414
422
  emitEvents: opts?.emitEvents,
423
+ autoPurgeCache: opts?.autoPurgeCache,
424
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
425
+ skipTracking: opts?.skipTracking,
426
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
427
+ onItemCreate: opts?.onItemCreate,
415
428
  mutationTracker: opts?.mutationTracker,
416
429
  });
417
430
  }
@@ -468,6 +481,11 @@ export class PayloadService {
468
481
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
469
482
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
470
483
  emitEvents: opts?.emitEvents,
484
+ autoPurgeCache: opts?.autoPurgeCache,
485
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
486
+ skipTracking: opts?.skipTracking,
487
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
488
+ onItemCreate: opts?.onItemCreate,
471
489
  mutationTracker: opts?.mutationTracker,
472
490
  });
473
491
  }
@@ -478,6 +496,11 @@ export class PayloadService {
478
496
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
479
497
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
480
498
  emitEvents: opts?.emitEvents,
499
+ autoPurgeCache: opts?.autoPurgeCache,
500
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
501
+ skipTracking: opts?.skipTracking,
502
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.field],
503
+ onItemCreate: opts?.onItemCreate,
481
504
  mutationTracker: opts?.mutationTracker,
482
505
  });
483
506
  }
@@ -570,6 +593,11 @@ export class PayloadService {
570
593
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
571
594
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
572
595
  emitEvents: opts?.emitEvents,
596
+ autoPurgeCache: opts?.autoPurgeCache,
597
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
598
+ skipTracking: opts?.skipTracking,
599
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field],
600
+ onItemCreate: opts?.onItemCreate,
573
601
  mutationTracker: opts?.mutationTracker,
574
602
  })));
575
603
  const query = {
@@ -596,6 +624,11 @@ export class PayloadService {
596
624
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
597
625
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
598
626
  emitEvents: opts?.emitEvents,
627
+ autoPurgeCache: opts?.autoPurgeCache,
628
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
629
+ skipTracking: opts?.skipTracking,
630
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field],
631
+ onItemCreate: opts?.onItemCreate,
599
632
  mutationTracker: opts?.mutationTracker,
600
633
  });
601
634
  }
@@ -605,6 +638,11 @@ export class PayloadService {
605
638
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
606
639
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
607
640
  emitEvents: opts?.emitEvents,
641
+ autoPurgeCache: opts?.autoPurgeCache,
642
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
643
+ skipTracking: opts?.skipTracking,
644
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field],
645
+ onItemCreate: opts?.onItemCreate,
608
646
  mutationTracker: opts?.mutationTracker,
609
647
  });
610
648
  }
@@ -648,12 +686,17 @@ export class PayloadService {
648
686
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
649
687
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
650
688
  emitEvents: opts?.emitEvents,
689
+ autoPurgeCache: opts?.autoPurgeCache,
690
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
691
+ skipTracking: opts?.skipTracking,
692
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['create'],
693
+ onItemCreate: opts?.onItemCreate,
651
694
  mutationTracker: opts?.mutationTracker,
652
695
  });
653
696
  }
654
697
  if (alterations.update) {
655
- for (const item of alterations.update) {
656
- const { [relatedPrimaryKeyField]: key, ...record } = item;
698
+ for (const index in alterations.update) {
699
+ const { [relatedPrimaryKeyField]: key, ...record } = alterations.update[index];
657
700
  const existingRecord = await this.knex
658
701
  .select(relatedPrimaryKeyField, relation.field)
659
702
  .from(relation.collection)
@@ -667,6 +710,11 @@ export class PayloadService {
667
710
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
668
711
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
669
712
  emitEvents: opts?.emitEvents,
713
+ autoPurgeCache: opts?.autoPurgeCache,
714
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
715
+ skipTracking: opts?.skipTracking,
716
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['update'][index],
717
+ onItemCreate: opts?.onItemCreate,
670
718
  mutationTracker: opts?.mutationTracker,
671
719
  });
672
720
  }
@@ -694,6 +742,11 @@ export class PayloadService {
694
742
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
695
743
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
696
744
  emitEvents: opts?.emitEvents,
745
+ autoPurgeCache: opts?.autoPurgeCache,
746
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
747
+ skipTracking: opts?.skipTracking,
748
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['delete'],
749
+ onItemCreate: opts?.onItemCreate,
697
750
  mutationTracker: opts?.mutationTracker,
698
751
  });
699
752
  }
@@ -703,6 +756,11 @@ export class PayloadService {
703
756
  onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
704
757
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
705
758
  emitEvents: opts?.emitEvents,
759
+ autoPurgeCache: opts?.autoPurgeCache,
760
+ autoPurgeSystemCache: opts?.autoPurgeSystemCache,
761
+ skipTracking: opts?.skipTracking,
762
+ overwriteDefaults: opts?.overwriteDefaults?.[relation.meta.one_field]?.['delete'],
763
+ onItemCreate: opts?.onItemCreate,
706
764
  mutationTracker: opts?.mutationTracker,
707
765
  });
708
766
  }
@@ -715,8 +773,8 @@ export class PayloadService {
715
773
  * Transforms the input partial payload to match the output structure, to have consistency
716
774
  * between delta and data
717
775
  */
718
- async prepareDelta(data) {
719
- let payload = cloneDeep(data);
776
+ async prepareDelta(delta) {
777
+ let payload = cloneDeep(delta);
720
778
  for (const key in payload) {
721
779
  if (payload[key]?.isRawInstance) {
722
780
  payload[key] = payload[key].bindings[0];
@@ -53,6 +53,7 @@ export class ServerService {
53
53
  ],
54
54
  });
55
55
  info['project'] = projectInfo;
56
+ info['mcp_enabled'] = toBoolean(env['MCP_ENABLED'] ?? true);
56
57
  if (this.accountability?.user) {
57
58
  if (env['RATE_LIMITER_ENABLED']) {
58
59
  info['rateLimit'] = {
@@ -6,7 +6,7 @@ export declare class TFAService {
6
6
  itemsService: ItemsService;
7
7
  constructor(options: AbstractServiceOptions);
8
8
  verifyOTP(key: PrimaryKey, otp: string, secret?: string): Promise<boolean>;
9
- generateTFA(key: PrimaryKey): Promise<Record<string, string>>;
9
+ generateTFA(key: PrimaryKey, requiresPassword?: boolean): Promise<Record<string, string>>;
10
10
  enableTFA(key: PrimaryKey, otp: string, secret: string): Promise<void>;
11
11
  disableTFA(key: PrimaryKey): Promise<void>;
12
12
  }
@@ -2,6 +2,7 @@ import { InvalidPayloadError } from '@directus/errors';
2
2
  import { authenticator } from 'otplib';
3
3
  import getDatabase from '../database/index.js';
4
4
  import { ItemsService } from './items.js';
5
+ import { DEFAULT_AUTH_PROVIDER } from '../constants.js';
5
6
  export class TFAService {
6
7
  knex;
7
8
  itemsService;
@@ -19,26 +20,40 @@ export class TFAService {
19
20
  }
20
21
  return authenticator.check(otp, user.tfa_secret);
21
22
  }
22
- async generateTFA(key) {
23
- const user = await this.knex.select('email', 'tfa_secret').from('directus_users').where({ id: key }).first();
23
+ async generateTFA(key, requiresPassword = true) {
24
+ const user = await this.knex
25
+ .select('email', 'tfa_secret', 'provider', 'external_identifier')
26
+ .from('directus_users')
27
+ .where({ id: key })
28
+ .first();
24
29
  if (user?.tfa_secret !== null) {
25
30
  throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
26
31
  }
27
- if (!user?.email) {
32
+ // Only require email for non-OAuth users
33
+ if (user?.provider === DEFAULT_AUTH_PROVIDER && !user?.email) {
28
34
  throw new InvalidPayloadError({ reason: 'User must have a valid email to enable TFA' });
29
35
  }
36
+ if (!requiresPassword && user?.provider === DEFAULT_AUTH_PROVIDER) {
37
+ throw new InvalidPayloadError({ reason: 'This method is only available for OAuth users' });
38
+ }
30
39
  const secret = authenticator.generateSecret();
31
40
  const project = await this.knex.select('project_name').from('directus_settings').limit(1).first();
41
+ // For OAuth users without email, use external_identifier as fallback
42
+ const accountName = user.email || user.external_identifier || `user_${key}`;
32
43
  return {
33
44
  secret,
34
- url: authenticator.keyuri(user.email, project?.project_name || 'Directus', secret),
45
+ url: authenticator.keyuri(accountName, project?.project_name || 'Directus', secret),
35
46
  };
36
47
  }
37
48
  async enableTFA(key, otp, secret) {
38
- const user = await this.knex.select('tfa_secret').from('directus_users').where({ id: key }).first();
49
+ const user = await this.knex.select('tfa_secret', 'provider').from('directus_users').where({ id: key }).first();
50
+ const requiresPassword = user?.['provider'] === DEFAULT_AUTH_PROVIDER;
39
51
  if (user?.tfa_secret !== null) {
40
52
  throw new InvalidPayloadError({ reason: 'TFA Secret is already set for this user' });
41
53
  }
54
+ if (!requiresPassword && user?.provider === DEFAULT_AUTH_PROVIDER) {
55
+ throw new InvalidPayloadError({ reason: 'This method is only available for OAuth users' });
56
+ }
42
57
  if (!authenticator.check(otp, secret)) {
43
58
  throw new InvalidPayloadError({ reason: `"otp" is invalid` });
44
59
  }
@@ -1,6 +1,6 @@
1
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey, Query } from '@directus/types';
1
+ import type { AbstractServiceOptions, ContentVersion, Item, MutationOptions, PrimaryKey, Query, QueryOptions } from '@directus/types';
2
2
  import { ItemsService } from './items.js';
3
- export declare class VersionsService extends ItemsService {
3
+ export declare class VersionsService extends ItemsService<ContentVersion> {
4
4
  constructor(options: AbstractServiceOptions);
5
5
  private validateCreateData;
6
6
  getMainItem(collection: string, item: PrimaryKey, query?: Query): Promise<Item>;
@@ -8,10 +8,12 @@ export declare class VersionsService extends ItemsService {
8
8
  outdated: boolean;
9
9
  mainHash: string;
10
10
  }>;
11
- getVersionSaves(key: string, collection: string, item: string | undefined): Promise<Partial<Item>[] | null>;
11
+ getVersionSave(key: string, collection: string, item: string, mapDelta?: boolean): Promise<ContentVersion | undefined>;
12
12
  createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
13
+ readOne(key: PrimaryKey, query?: Query, opts?: QueryOptions): Promise<ContentVersion>;
13
14
  createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
14
15
  updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
15
- save(key: PrimaryKey, data: Partial<Item>): Promise<Partial<Item>>;
16
+ save(key: PrimaryKey, delta: Partial<Item>): Promise<Partial<Item>>;
16
17
  promote(version: PrimaryKey, mainHash: string, fields?: string[]): Promise<PrimaryKey>;
18
+ private mapDelta;
17
19
  }