@directus/api 18.2.1 → 19.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.
Files changed (90) hide show
  1. package/dist/app.js +0 -3
  2. package/dist/auth/drivers/ldap.js +1 -1
  3. package/dist/auth/drivers/local.js +1 -1
  4. package/dist/auth/drivers/oauth2.js +1 -1
  5. package/dist/auth/drivers/openid.js +1 -1
  6. package/dist/cache.js +1 -1
  7. package/dist/cli/utils/create-env/env-stub.liquid +2 -2
  8. package/dist/controllers/activity.js +1 -1
  9. package/dist/controllers/assets.js +9 -12
  10. package/dist/controllers/auth.js +7 -6
  11. package/dist/controllers/collections.js +1 -2
  12. package/dist/controllers/dashboards.js +1 -2
  13. package/dist/controllers/extensions.js +30 -0
  14. package/dist/controllers/fields.js +1 -3
  15. package/dist/controllers/flows.js +1 -2
  16. package/dist/controllers/folders.js +1 -2
  17. package/dist/controllers/items.js +3 -4
  18. package/dist/controllers/notifications.js +1 -2
  19. package/dist/controllers/operations.js +1 -2
  20. package/dist/controllers/panels.js +1 -2
  21. package/dist/controllers/presets.js +1 -2
  22. package/dist/controllers/roles.js +1 -2
  23. package/dist/controllers/translations.js +1 -2
  24. package/dist/controllers/users.js +1 -2
  25. package/dist/controllers/webhooks.js +10 -74
  26. package/dist/database/migrations/20240122A-add-report-url-fields.d.ts +3 -0
  27. package/dist/database/migrations/20240122A-add-report-url-fields.js +14 -0
  28. package/dist/database/migrations/20240204A-marketplace.js +17 -5
  29. package/dist/database/migrations/20240305A-change-useragent-type.d.ts +3 -0
  30. package/dist/database/migrations/20240305A-change-useragent-type.js +19 -0
  31. package/dist/database/migrations/20240311A-deprecate-webhooks.d.ts +13 -0
  32. package/dist/database/migrations/20240311A-deprecate-webhooks.js +125 -0
  33. package/dist/database/run-ast.js +4 -3
  34. package/dist/extensions/manager.d.ts +1 -0
  35. package/dist/extensions/manager.js +4 -1
  36. package/dist/middleware/authenticate.js +1 -1
  37. package/dist/services/activity.d.ts +2 -1
  38. package/dist/services/authorization.d.ts +2 -2
  39. package/dist/services/collections.d.ts +1 -1
  40. package/dist/services/collections.js +8 -7
  41. package/dist/services/extensions.d.ts +3 -0
  42. package/dist/services/extensions.js +42 -10
  43. package/dist/services/fields.d.ts +2 -1
  44. package/dist/services/fields.js +37 -7
  45. package/dist/services/files.d.ts +2 -2
  46. package/dist/services/flows.d.ts +2 -2
  47. package/dist/services/graphql/index.d.ts +2 -2
  48. package/dist/services/graphql/index.js +5 -0
  49. package/dist/services/import-export.js +4 -3
  50. package/dist/services/items.d.ts +2 -2
  51. package/dist/services/items.js +9 -8
  52. package/dist/services/notifications.d.ts +2 -2
  53. package/dist/services/operations.d.ts +2 -2
  54. package/dist/services/payload.d.ts +2 -2
  55. package/dist/services/permissions/index.d.ts +2 -2
  56. package/dist/services/relations.js +10 -3
  57. package/dist/services/revisions.d.ts +2 -1
  58. package/dist/services/roles.d.ts +2 -2
  59. package/dist/services/roles.js +2 -1
  60. package/dist/services/shares.d.ts +2 -1
  61. package/dist/services/shares.js +1 -1
  62. package/dist/services/tfa.d.ts +2 -1
  63. package/dist/services/tfa.js +1 -1
  64. package/dist/services/users.d.ts +2 -2
  65. package/dist/services/users.js +3 -2
  66. package/dist/services/utils.d.ts +2 -2
  67. package/dist/services/utils.js +2 -2
  68. package/dist/services/versions.d.ts +1 -1
  69. package/dist/services/webhooks.d.ts +8 -4
  70. package/dist/services/webhooks.js +15 -12
  71. package/dist/types/items.d.ts +1 -8
  72. package/dist/types/services.d.ts +1 -2
  73. package/dist/utils/apply-diff.js +2 -1
  74. package/dist/utils/get-ast-from-query.js +1 -1
  75. package/dist/utils/get-auth-providers.d.ts +3 -1
  76. package/dist/utils/get-auth-providers.js +15 -4
  77. package/dist/utils/get-cache-headers.js +0 -3
  78. package/dist/utils/get-schema.d.ts +1 -1
  79. package/dist/utils/get-schema.js +52 -29
  80. package/dist/utils/merge-version-data.js +1 -1
  81. package/dist/utils/transaction.d.ts +9 -0
  82. package/dist/utils/transaction.js +15 -0
  83. package/dist/utils/validate-keys.d.ts +1 -2
  84. package/dist/websocket/controllers/base.d.ts +1 -3
  85. package/dist/websocket/controllers/base.js +12 -3
  86. package/dist/websocket/utils/items.d.ts +1 -1
  87. package/license +1 -1
  88. package/package.json +39 -37
  89. package/dist/webhooks.d.ts +0 -4
  90. package/dist/webhooks.js +0 -80
@@ -1,6 +1,7 @@
1
1
  import { Action } from '@directus/constants';
2
2
  import { useEnv } from '@directus/env';
3
3
  import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
4
+ import { isSystemCollection } from '@directus/system-data';
4
5
  import { assign, clone, cloneDeep, omit, pick, without } from 'lodash-es';
5
6
  import { getCache } from '../cache.js';
6
7
  import { translateDatabaseError } from '../database/errors/translate.js';
@@ -10,10 +11,10 @@ import runAST from '../database/run-ast.js';
10
11
  import emitter from '../emitter.js';
11
12
  import getASTFromQuery from '../utils/get-ast-from-query.js';
12
13
  import { shouldClearCache } from '../utils/should-clear-cache.js';
14
+ import { transaction } from '../utils/transaction.js';
13
15
  import { validateKeys } from '../utils/validate-keys.js';
14
16
  import { AuthorizationService } from './authorization.js';
15
17
  import { PayloadService } from './payload.js';
16
- import { isSystemCollection } from '@directus/system-data';
17
18
  const env = useEnv();
18
19
  export class ItemsService {
19
20
  collection;
@@ -82,7 +83,7 @@ export class ItemsService {
82
83
  // changes in the DB if any of the parts contained within throws an error. This also means
83
84
  // that any errors thrown in any nested relational changes will bubble up and cancel the whole
84
85
  // update tree
85
- const primaryKey = await this.knex.transaction(async (trx) => {
86
+ const primaryKey = await transaction(this.knex, async (trx) => {
86
87
  // We're creating new services instances so they can use the transaction as their Knex interface
87
88
  const payloadService = new PayloadService(this.collection, {
88
89
  accountability: this.accountability,
@@ -255,11 +256,11 @@ export class ItemsService {
255
256
  async createMany(data, opts = {}) {
256
257
  if (!opts.mutationTracker)
257
258
  opts.mutationTracker = this.createMutationTracker();
258
- const { primaryKeys, nestedActionEvents } = await this.knex.transaction(async (trx) => {
259
+ const { primaryKeys, nestedActionEvents } = await transaction(this.knex, async (knex) => {
259
260
  const service = new ItemsService(this.collection, {
260
261
  accountability: this.accountability,
261
262
  schema: this.schema,
262
- knex: trx,
263
+ knex: knex,
263
264
  });
264
265
  const primaryKeys = [];
265
266
  const nestedActionEvents = [];
@@ -418,7 +419,7 @@ export class ItemsService {
418
419
  const primaryKeyField = this.schema.collections[this.collection].primary;
419
420
  const keys = [];
420
421
  try {
421
- await this.knex.transaction(async (trx) => {
422
+ await transaction(this.knex, async (trx) => {
422
423
  const service = new ItemsService(this.collection, {
423
424
  accountability: this.accountability,
424
425
  knex: trx,
@@ -488,7 +489,7 @@ export class ItemsService {
488
489
  if (opts.preMutationError) {
489
490
  throw opts.preMutationError;
490
491
  }
491
- await this.knex.transaction(async (trx) => {
492
+ await transaction(this.knex, async (trx) => {
492
493
  const payloadService = new PayloadService(this.collection, {
493
494
  accountability: this.accountability,
494
495
  knex: trx,
@@ -629,7 +630,7 @@ export class ItemsService {
629
630
  async upsertMany(payloads, opts = {}) {
630
631
  if (!opts.mutationTracker)
631
632
  opts.mutationTracker = this.createMutationTracker();
632
- const primaryKeys = await this.knex.transaction(async (trx) => {
633
+ const primaryKeys = await transaction(this.knex, async (trx) => {
633
634
  const service = new ItemsService(this.collection, {
634
635
  accountability: this.accountability,
635
636
  schema: this.schema,
@@ -697,7 +698,7 @@ export class ItemsService {
697
698
  accountability: this.accountability,
698
699
  });
699
700
  }
700
- await this.knex.transaction(async (trx) => {
701
+ await transaction(this.knex, async (trx) => {
701
702
  await trx(this.collection).whereIn(primaryKeyField, keys).delete();
702
703
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
703
704
  const activityService = new ActivityService({
@@ -1,5 +1,5 @@
1
- import type { Notification } from '@directus/types';
2
- import type { AbstractServiceOptions, MutationOptions, PrimaryKey } from '../types/index.js';
1
+ import type { Notification, PrimaryKey } from '@directus/types';
2
+ import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
3
  import { ItemsService } from './items.js';
4
4
  import { MailService } from './mail/index.js';
5
5
  import { UsersService } from './users.js';
@@ -1,5 +1,5 @@
1
- import type { OperationRaw } from '@directus/types';
2
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types/index.js';
1
+ import type { Item, OperationRaw, PrimaryKey } from '@directus/types';
2
+ import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
3
  import { ItemsService } from './items.js';
4
4
  export declare class OperationsService extends ItemsService<OperationRaw> {
5
5
  constructor(options: AbstractServiceOptions);
@@ -1,7 +1,7 @@
1
- import type { Accountability, SchemaOverview } from '@directus/types';
1
+ import type { Accountability, Item, PrimaryKey, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  import type { Helpers } from '../database/helpers/index.js';
4
- import type { AbstractServiceOptions, ActionEventParams, Item, MutationOptions, PrimaryKey } from '../types/index.js';
4
+ import type { AbstractServiceOptions, ActionEventParams, MutationOptions } from '../types/index.js';
5
5
  type Action = 'create' | 'read' | 'update';
6
6
  type Transformers = {
7
7
  [type: string]: (context: {
@@ -1,6 +1,6 @@
1
- import type { ItemPermissions, PermissionsAction, Query } from '@directus/types';
1
+ import type { Item, ItemPermissions, PermissionsAction, PrimaryKey, Query } from '@directus/types';
2
2
  import type Keyv from 'keyv';
3
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../../types/index.js';
3
+ import type { AbstractServiceOptions, MutationOptions } from '../../types/index.js';
4
4
  import type { QueryOptions } from '../items.js';
5
5
  import { ItemsService } from '../items.js';
6
6
  export declare class PermissionsService extends ItemsService {
@@ -8,6 +8,7 @@ import getDatabase, { getSchemaInspector } from '../database/index.js';
8
8
  import emitter from '../emitter.js';
9
9
  import { getDefaultIndexName } from '../utils/get-default-index-name.js';
10
10
  import { getSchema } from '../utils/get-schema.js';
11
+ import { transaction } from '../utils/transaction.js';
11
12
  import { ItemsService } from './items.js';
12
13
  import { PermissionsService } from './permissions/index.js';
13
14
  export class RelationsService {
@@ -150,7 +151,7 @@ export class RelationsService {
150
151
  many_field: relation.field,
151
152
  one_collection: relation.related_collection || null,
152
153
  };
153
- await this.knex.transaction(async (trx) => {
154
+ await transaction(this.knex, async (trx) => {
154
155
  if (relation.related_collection) {
155
156
  await trx.schema.alterTable(relation.collection, async (table) => {
156
157
  this.alterType(table, relation, fieldSchema.nullable);
@@ -161,6 +162,9 @@ export class RelationsService {
161
162
  if (relation.schema?.on_delete) {
162
163
  builder.onDelete(relation.schema.on_delete);
163
164
  }
165
+ if (relation.schema?.on_update) {
166
+ builder.onUpdate(relation.schema.on_update);
167
+ }
164
168
  });
165
169
  }
166
170
  const relationsItemService = new ItemsService('directus_relations', {
@@ -218,7 +222,7 @@ export class RelationsService {
218
222
  this.helpers.schema.preRelationChange(relation);
219
223
  const nestedActionEvents = [];
220
224
  try {
221
- await this.knex.transaction(async (trx) => {
225
+ await transaction(this.knex, async (trx) => {
222
226
  if (existingRelation.related_collection) {
223
227
  await trx.schema.alterTable(collection, async (table) => {
224
228
  let constraintName = getDefaultIndexName('foreign', collection, field);
@@ -236,6 +240,9 @@ export class RelationsService {
236
240
  if (relation.schema?.on_delete) {
237
241
  builder.onDelete(relation.schema.on_delete);
238
242
  }
243
+ if (relation.schema?.on_update) {
244
+ builder.onUpdate(relation.schema.on_update);
245
+ }
239
246
  });
240
247
  }
241
248
  const relationsItemService = new ItemsService('directus_relations', {
@@ -302,7 +309,7 @@ export class RelationsService {
302
309
  const runPostColumnChange = await this.helpers.schema.preColumnChange();
303
310
  const nestedActionEvents = [];
304
311
  try {
305
- await this.knex.transaction(async (trx) => {
312
+ await transaction(this.knex, async (trx) => {
306
313
  const existingConstraints = await this.schemaInspector.foreignKeys();
307
314
  const constraintNames = existingConstraints.map((key) => key.constraint_name);
308
315
  if (existingRelation.schema?.constraint_name &&
@@ -1,4 +1,5 @@
1
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types/index.js';
1
+ import type { Item, PrimaryKey } from '@directus/types';
2
+ import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
2
3
  import { ItemsService } from './items.js';
3
4
  export declare class RevisionsService extends ItemsService {
4
5
  constructor(options: AbstractServiceOptions);
@@ -1,5 +1,5 @@
1
- import type { Query } from '@directus/types';
2
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types/index.js';
1
+ import type { Item, PrimaryKey, Query } from '@directus/types';
2
+ import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
3
  import { ItemsService } from './items.js';
4
4
  export declare class RolesService extends ItemsService {
5
5
  constructor(options: AbstractServiceOptions);
@@ -1,5 +1,6 @@
1
1
  import { ForbiddenError, InvalidPayloadError, UnprocessableContentError } from '@directus/errors';
2
2
  import { getMatch } from 'ip-matching';
3
+ import { transaction } from '../utils/transaction.js';
3
4
  import { ItemsService } from './items.js';
4
5
  import { PermissionsService } from './permissions/index.js';
5
6
  import { PresetsService } from './presets.js';
@@ -217,7 +218,7 @@ export class RolesService extends ItemsService {
217
218
  catch (err) {
218
219
  opts.preMutationError = err;
219
220
  }
220
- await this.knex.transaction(async (trx) => {
221
+ await transaction(this.knex, async (trx) => {
221
222
  const itemsService = new ItemsService('directus_roles', {
222
223
  knex: trx,
223
224
  accountability: this.accountability,
@@ -1,4 +1,5 @@
1
- import type { AbstractServiceOptions, Item, LoginResult, MutationOptions, PrimaryKey } from '../types/index.js';
1
+ import type { Item, PrimaryKey } from '@directus/types';
2
+ import type { AbstractServiceOptions, LoginResult, MutationOptions } from '../types/index.js';
2
3
  import { AuthorizationService } from './authorization.js';
3
4
  import { ItemsService } from './items.js';
4
5
  export declare class SharesService extends ItemsService {
@@ -2,6 +2,7 @@ import { useEnv } from '@directus/env';
2
2
  import { ForbiddenError, InvalidCredentialsError } from '@directus/errors';
3
3
  import argon2 from 'argon2';
4
4
  import jwt from 'jsonwebtoken';
5
+ import { useLogger } from '../logger.js';
5
6
  import { getMilliseconds } from '../utils/get-milliseconds.js';
6
7
  import { md } from '../utils/md.js';
7
8
  import { Url } from '../utils/url.js';
@@ -10,7 +11,6 @@ import { AuthorizationService } from './authorization.js';
10
11
  import { ItemsService } from './items.js';
11
12
  import { MailService } from './mail/index.js';
12
13
  import { UsersService } from './users.js';
13
- import { useLogger } from '../logger.js';
14
14
  const env = useEnv();
15
15
  const logger = useLogger();
16
16
  export class SharesService extends ItemsService {
@@ -1,5 +1,6 @@
1
+ import type { PrimaryKey } from '@directus/types';
1
2
  import type { Knex } from 'knex';
2
- import type { AbstractServiceOptions, PrimaryKey } from '../types/index.js';
3
+ import type { AbstractServiceOptions } from '../types/index.js';
3
4
  import { ItemsService } from './items.js';
4
5
  export declare class TFAService {
5
6
  knex: Knex;
@@ -1,6 +1,6 @@
1
+ import { InvalidPayloadError } from '@directus/errors';
1
2
  import { authenticator } from 'otplib';
2
3
  import getDatabase from '../database/index.js';
3
- import { InvalidPayloadError } from '@directus/errors';
4
4
  import { ItemsService } from './items.js';
5
5
  export class TFAService {
6
6
  knex;
@@ -1,5 +1,5 @@
1
- import type { Query } from '@directus/types';
2
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey } from '../types/index.js';
1
+ import type { Item, PrimaryKey, Query } from '@directus/types';
2
+ import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
3
  import { ItemsService } from './items.js';
4
4
  export declare class UsersService extends ItemsService {
5
5
  constructor(options: AbstractServiceOptions);
@@ -7,14 +7,15 @@ import jwt from 'jsonwebtoken';
7
7
  import { cloneDeep, isEmpty } from 'lodash-es';
8
8
  import { performance } from 'perf_hooks';
9
9
  import getDatabase from '../database/index.js';
10
+ import { useLogger } from '../logger.js';
10
11
  import isUrlAllowed from '../utils/is-url-allowed.js';
11
12
  import { verifyJWT } from '../utils/jwt.js';
12
13
  import { stall } from '../utils/stall.js';
14
+ import { transaction } from '../utils/transaction.js';
13
15
  import { Url } from '../utils/url.js';
14
16
  import { ItemsService } from './items.js';
15
17
  import { MailService } from './mail/index.js';
16
18
  import { SettingsService } from './settings.js';
17
- import { useLogger } from '../logger.js';
18
19
  const env = useEnv();
19
20
  const logger = useLogger();
20
21
  export class UsersService extends ItemsService {
@@ -195,7 +196,7 @@ export class UsersService extends ItemsService {
195
196
  opts.mutationTracker = this.createMutationTracker();
196
197
  const primaryKeyField = this.schema.collections[this.collection].primary;
197
198
  const keys = [];
198
- await this.knex.transaction(async (trx) => {
199
+ await transaction(this.knex, async (trx) => {
199
200
  const service = new UsersService({
200
201
  accountability: this.accountability,
201
202
  knex: trx,
@@ -1,6 +1,6 @@
1
- import type { Accountability, SchemaOverview } from '@directus/types';
1
+ import type { Accountability, PrimaryKey, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
- import type { AbstractServiceOptions, PrimaryKey } from '../types/index.js';
3
+ import type { AbstractServiceOptions } from '../types/index.js';
4
4
  export declare class UtilsService {
5
5
  knex: Knex;
6
6
  accountability: Accountability | null;
@@ -1,8 +1,8 @@
1
+ import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
2
+ import { systemCollectionRows } from '@directus/system-data';
1
3
  import { flushCaches, getCache } from '../cache.js';
2
4
  import getDatabase from '../database/index.js';
3
- import { systemCollectionRows } from '@directus/system-data';
4
5
  import emitter from '../emitter.js';
5
- import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
6
6
  import { shouldClearCache } from '../utils/should-clear-cache.js';
7
7
  export class UtilsService {
8
8
  knex;
@@ -17,5 +17,5 @@ export declare class VersionsService extends ItemsService {
17
17
  createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
18
18
  updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
19
19
  save(key: PrimaryKey, data: Partial<Item>): Promise<Partial<Item>>;
20
- promote(version: PrimaryKey, mainHash: string, fields?: string[]): Promise<import("../types/items.js").PrimaryKey>;
20
+ promote(version: PrimaryKey, mainHash: string, fields?: string[]): Promise<PrimaryKey>;
21
21
  }
@@ -1,11 +1,15 @@
1
+ import { type DirectusError } from '@directus/errors';
1
2
  import type { Bus } from '@directus/memory';
2
- import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey, Webhook } from '../types/index.js';
3
+ import type { PrimaryKey } from '@directus/types';
4
+ import type { AbstractServiceOptions, MutationOptions, Webhook } from '../types/index.js';
3
5
  import { ItemsService } from './items.js';
4
6
  export declare class WebhooksService extends ItemsService<Webhook> {
5
7
  messenger: Bus;
8
+ errorDeprecation: DirectusError;
6
9
  constructor(options: AbstractServiceOptions);
7
- createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
8
- createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
9
- updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
10
+ createOne(): Promise<PrimaryKey>;
11
+ createMany(): Promise<PrimaryKey[]>;
12
+ updateBatch(): Promise<PrimaryKey[]>;
13
+ updateMany(): Promise<PrimaryKey[]>;
10
14
  deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]>;
11
15
  }
@@ -1,25 +1,28 @@
1
+ import { ErrorCode, createError } from '@directus/errors';
1
2
  import { useBus } from '../bus/index.js';
3
+ import { useLogger } from '../logger.js';
2
4
  import { ItemsService } from './items.js';
5
+ const logger = useLogger();
3
6
  export class WebhooksService extends ItemsService {
4
7
  messenger;
8
+ errorDeprecation;
5
9
  constructor(options) {
6
10
  super('directus_webhooks', options);
7
11
  this.messenger = useBus();
12
+ this.errorDeprecation = new (createError(ErrorCode.MethodNotAllowed, 'Webhooks are deprecated, use Flows instead', 405))();
13
+ logger.warn('Webhooks are deprecated and the WebhooksService will be removed in an upcoming release. Creating/Updating Webhooks is disabled, use Flows instead');
8
14
  }
9
- async createOne(data, opts) {
10
- const result = await super.createOne(data, opts);
11
- this.messenger.publish('webhooks', { type: 'reload' });
12
- return result;
15
+ async createOne() {
16
+ throw this.errorDeprecation;
13
17
  }
14
- async createMany(data, opts) {
15
- const result = await super.createMany(data, opts);
16
- this.messenger.publish('webhooks', { type: 'reload' });
17
- return result;
18
+ async createMany() {
19
+ throw this.errorDeprecation;
18
20
  }
19
- async updateMany(keys, data, opts) {
20
- const result = await super.updateMany(keys, data, opts);
21
- this.messenger.publish('webhooks', { type: 'reload' });
22
- return result;
21
+ async updateBatch() {
22
+ throw this.errorDeprecation;
23
+ }
24
+ async updateMany() {
25
+ throw this.errorDeprecation;
23
26
  }
24
27
  async deleteMany(keys, opts) {
25
28
  const result = await super.deleteMany(keys, opts);
@@ -1,13 +1,6 @@
1
1
  import type { DirectusError } from '@directus/errors';
2
- import type { EventContext } from '@directus/types';
2
+ import type { EventContext, PrimaryKey } from '@directus/types';
3
3
  import type { MutationTracker } from '../services/items.js';
4
- export type Item = Record<string, any>;
5
- export type PrimaryKey = string | number;
6
- export type Alterations<T extends Item = Item, K extends keyof T | undefined = undefined> = {
7
- create: Partial<T>[];
8
- update: (K extends keyof T ? Partial<T> & Pick<T, K> : Partial<T>)[];
9
- delete: (K extends keyof T ? T[K] : PrimaryKey)[];
10
- };
11
4
  export type MutationOptions = {
12
5
  /**
13
6
  * Callback function that's fired whenever a revision is made in the mutation
@@ -1,6 +1,5 @@
1
- import type { Accountability, Query, SchemaOverview } from '@directus/types';
1
+ import type { Accountability, Item, PrimaryKey, Query, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
- import type { Item, PrimaryKey } from './items.js';
4
3
  export type AbstractServiceOptions = {
5
4
  knex?: Knex | undefined;
6
5
  accountability?: Accountability | null | undefined;
@@ -9,6 +9,7 @@ import { CollectionsService } from '../services/collections.js';
9
9
  import { FieldsService } from '../services/fields.js';
10
10
  import { RelationsService } from '../services/relations.js';
11
11
  import { DiffKind } from '../types/index.js';
12
+ import { transaction } from '../utils/transaction.js';
12
13
  import { getSchema } from './get-schema.js';
13
14
  const logger = useLogger();
14
15
  export async function applyDiff(currentSnapshot, snapshotDiff, options) {
@@ -22,7 +23,7 @@ export async function applyDiff(currentSnapshot, snapshotDiff, options) {
22
23
  bypassLimits: true,
23
24
  };
24
25
  const runPostColumnChange = await helpers.schema.preColumnChange();
25
- await database.transaction(async (trx) => {
26
+ await transaction(database, async (trx) => {
26
27
  const collectionsService = new CollectionsService({ knex: trx, schema });
27
28
  const getNestedCollectionsToCreate = (currentLevelCollection) => snapshotDiff.collections.filter(({ diff }) => diff[0].rhs?.meta?.group === currentLevelCollection);
28
29
  const getNestedCollectionsToDelete = (currentLevelCollection) => snapshotDiff.collections.filter(({ diff }) => diff[0].lhs?.meta?.group === currentLevelCollection);
@@ -73,7 +73,7 @@ export default async function getASTFromQuery(collection, query, schema, options
73
73
  for (const fieldKey of fields) {
74
74
  let name = fieldKey;
75
75
  if (query.alias) {
76
- // check for field alias (is is one of the key)
76
+ // check for field alias (is one of the key)
77
77
  if (name in query.alias) {
78
78
  name = query.alias[fieldKey];
79
79
  }
@@ -4,5 +4,7 @@ interface AuthProvider {
4
4
  icon?: string;
5
5
  label?: string;
6
6
  }
7
- export declare function getAuthProviders(): AuthProvider[];
7
+ export declare function getAuthProviders({ sessionOnly }?: {
8
+ sessionOnly: boolean;
9
+ }): AuthProvider[];
8
10
  export {};
@@ -1,10 +1,21 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { toArray } from '@directus/utils';
3
- export function getAuthProviders() {
3
+ export function getAuthProviders({ sessionOnly } = { sessionOnly: false }) {
4
4
  const env = useEnv();
5
- return toArray(env['AUTH_PROVIDERS'])
6
- .filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`])
7
- .map((provider) => ({
5
+ let providers = toArray(env['AUTH_PROVIDERS']).filter((provider) => provider && env[`AUTH_${provider.toUpperCase()}_DRIVER`]);
6
+ if (sessionOnly) {
7
+ providers = providers.filter((provider) => {
8
+ const driver = env[`AUTH_${provider.toUpperCase()}_DRIVER`];
9
+ // only the following 3 drivers require a mode selection
10
+ if (['oauth2', 'openid', 'saml'].includes(driver)) {
11
+ const mode = env[`AUTH_${provider.toUpperCase()}_MODE`];
12
+ // if mode is not defined it defaults to session
13
+ return !mode || mode === 'session';
14
+ }
15
+ return true;
16
+ });
17
+ }
18
+ return providers.map((provider) => ({
8
19
  name: provider,
9
20
  label: env[`AUTH_${provider.toUpperCase()}_LABEL`],
10
21
  driver: env[`AUTH_${provider.toUpperCase()}_DRIVER`],
@@ -16,9 +16,6 @@ export function getCacheControlHeader(req, ttl, globalCacheSettings, personalize
16
16
  // When the resource / current request shouldn't be cached
17
17
  if (ttl === undefined || ttl < 0)
18
18
  return 'no-cache';
19
- // When the API cache can invalidate at any moment
20
- if (globalCacheSettings && env['CACHE_AUTO_PURGE'] === true)
21
- return 'no-cache';
22
19
  const headerValues = [];
23
20
  // When caching depends on the authentication status of the users
24
21
  if (personalized) {
@@ -7,4 +7,4 @@ export declare function getSchema(options?: {
7
7
  * Used to ensure schema snapshot/apply is not using outdated schema
8
8
  */
9
9
  bypassCache?: boolean;
10
- }): Promise<SchemaOverview>;
10
+ }, attempt?: number): Promise<SchemaOverview>;
@@ -1,47 +1,70 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { createInspector } from '@directus/schema';
3
+ import { systemCollectionRows } from '@directus/system-data';
3
4
  import { parseJSON, toArray } from '@directus/utils';
4
5
  import { mapValues } from 'lodash-es';
6
+ import { useBus } from '../bus/index.js';
5
7
  import { getSchemaCache, setSchemaCache } from '../cache.js';
6
8
  import { ALIAS_TYPES } from '../constants.js';
7
9
  import getDatabase from '../database/index.js';
10
+ import { useLock } from '../lock/index.js';
8
11
  import { useLogger } from '../logger.js';
9
12
  import { RelationsService } from '../services/relations.js';
10
13
  import getDefaultValue from './get-default-value.js';
11
- import getLocalType from './get-local-type.js';
12
- import { systemCollectionRows } from '@directus/system-data';
13
14
  import { getSystemFieldRowsWithAuthProviders } from './get-field-system-rows.js';
15
+ import getLocalType from './get-local-type.js';
14
16
  const logger = useLogger();
15
- export async function getSchema(options) {
17
+ export async function getSchema(options, attempt = 0) {
18
+ const MAX_ATTEMPTS = 3;
16
19
  const env = useEnv();
17
- const database = options?.database || getDatabase();
18
- const schemaInspector = createInspector(database);
19
- let result;
20
- if (!options?.bypassCache && env['CACHE_SCHEMA'] !== false) {
21
- let cachedSchema;
22
- try {
23
- cachedSchema = await getSchemaCache();
24
- }
25
- catch (err) {
26
- logger.warn(err, `[schema-cache] Couldn't retrieve cache. ${err}`);
27
- }
28
- if (cachedSchema) {
29
- result = cachedSchema;
30
- }
31
- else {
32
- result = await getDatabaseSchema(database, schemaInspector);
33
- try {
34
- await setSchemaCache(result);
35
- }
36
- catch (err) {
37
- logger.warn(err, `[schema-cache] Couldn't save cache. ${err}`);
38
- }
39
- }
20
+ if (attempt >= MAX_ATTEMPTS) {
21
+ throw new Error(`Failed to get Schema information: hit infinite loop`);
40
22
  }
41
- else {
42
- result = await getDatabaseSchema(database, schemaInspector);
23
+ if (options?.bypassCache || env['CACHE_SCHEMA'] === false) {
24
+ const database = options?.database || getDatabase();
25
+ const schemaInspector = createInspector(database);
26
+ return await getDatabaseSchema(database, schemaInspector);
27
+ }
28
+ const cached = await getSchemaCache();
29
+ if (cached) {
30
+ return cached;
31
+ }
32
+ const lock = useLock();
33
+ const bus = useBus();
34
+ const lockKey = 'schemaCache--preparing';
35
+ const messageKey = 'schemaCache--done';
36
+ const processId = await lock.increment(lockKey);
37
+ const currentProcessShouldHandleOperation = processId === 1;
38
+ if (currentProcessShouldHandleOperation === false) {
39
+ logger.trace('Schema cache is prepared in another process, waiting for result.');
40
+ return new Promise((resolve) => {
41
+ const TIMEOUT = 10000;
42
+ let timeout;
43
+ const callback = async () => {
44
+ if (timeout)
45
+ clearTimeout(timeout);
46
+ const schema = await getSchema(options, attempt + 1);
47
+ resolve(schema);
48
+ bus.unsubscribe(messageKey, callback);
49
+ };
50
+ bus.subscribe(messageKey, callback);
51
+ timeout = setTimeout(async () => {
52
+ logger.trace('Did not receive schema callback message in time. Pulling schema...');
53
+ callback();
54
+ }, TIMEOUT);
55
+ });
56
+ }
57
+ try {
58
+ const database = options?.database || getDatabase();
59
+ const schemaInspector = createInspector(database);
60
+ const schema = await getDatabaseSchema(database, schemaInspector);
61
+ await setSchemaCache(schema);
62
+ return schema;
63
+ }
64
+ finally {
65
+ await lock.delete(lockKey);
66
+ bus.publish(messageKey, { ready: true });
43
67
  }
44
- return result;
45
68
  }
46
69
  async function getDatabaseSchema(database, schemaInspector) {
47
70
  const env = useEnv();
@@ -1,5 +1,5 @@
1
- import Joi from 'joi';
2
1
  import { isObject } from '@directus/utils';
2
+ import Joi from 'joi';
3
3
  import { cloneDeep } from 'lodash-es';
4
4
  const alterationSchema = Joi.object({
5
5
  create: Joi.array().items(Joi.object().unknown()),
@@ -0,0 +1,9 @@
1
+ import type { Knex } from 'knex';
2
+ /**
3
+ * Execute the given handler within the current transaction or a newly created one
4
+ * if the current knex state isn't a transaction yet.
5
+ *
6
+ * Can be used to ensure the handler is run within a transaction,
7
+ * while preventing nested transactions.
8
+ */
9
+ export declare const transaction: <T = unknown>(knex: Knex, handler: (knex: Knex) => Promise<T>) => Promise<T>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Execute the given handler within the current transaction or a newly created one
3
+ * if the current knex state isn't a transaction yet.
4
+ *
5
+ * Can be used to ensure the handler is run within a transaction,
6
+ * while preventing nested transactions.
7
+ */
8
+ export const transaction = (knex, handler) => {
9
+ if (knex.isTransaction) {
10
+ return handler(knex);
11
+ }
12
+ else {
13
+ return knex.transaction((trx) => handler(trx));
14
+ }
15
+ };
@@ -1,5 +1,4 @@
1
- import type { SchemaOverview } from '@directus/types';
2
- import type { PrimaryKey } from '../types/index.js';
1
+ import type { PrimaryKey, SchemaOverview } from '@directus/types';
3
2
  /**
4
3
  * Validate keys based on its type
5
4
  */