@directus/api 23.3.1 → 24.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.
@@ -175,9 +175,11 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
175
175
  // user that is about to be updated
176
176
  let emitPayload = {
177
177
  auth_data: userPayload.auth_data,
178
- // Make sure a user's role gets updated if his openid group or role mapping changes
179
- role: role,
180
178
  };
179
+ // Make sure a user's role gets updated if their openid group or role mapping changes
180
+ if (this.config['roleMapping']) {
181
+ emitPayload['role'] = role;
182
+ }
181
183
  if (syncUserInfo) {
182
184
  emitPayload = {
183
185
  ...emitPayload,
@@ -18,8 +18,8 @@ export declare const SUPPORTED_IMAGE_METADATA_FORMATS: string[];
18
18
  /** Resumable uploads */
19
19
  export declare const RESUMABLE_UPLOADS: {
20
20
  ENABLED: boolean;
21
- CHUNK_SIZE: number;
22
- MAX_SIZE: number;
21
+ CHUNK_SIZE: number | null;
22
+ MAX_SIZE: number | null;
23
23
  EXPIRATION_TIME: number;
24
24
  SCHEDULE: string;
25
25
  };
@@ -35,7 +35,7 @@ export const multipartHandler = (req, res, next) => {
35
35
  headers,
36
36
  defParamCharset: 'utf8',
37
37
  limits: {
38
- fileSize: env['FILES_MAX_UPLOAD_SIZE'] ? bytes.parse(env['FILES_MAX_UPLOAD_SIZE']) : undefined,
38
+ fileSize: bytes.parse(env['FILES_MAX_UPLOAD_SIZE']) ?? undefined,
39
39
  },
40
40
  });
41
41
  const savedFiles = [];
@@ -1,6 +1,7 @@
1
1
  import type { Knex } from 'knex';
2
2
  import { SchemaHelper, type SortRecord, type Sql } from '../types.js';
3
3
  export declare class SchemaHelperMSSQL extends SchemaHelper {
4
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
4
5
  applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void;
5
6
  applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void;
6
7
  formatUUID(uuid: string): string;
@@ -1,6 +1,10 @@
1
+ import { getDefaultIndexName } from '../../../../utils/get-default-index-name.js';
1
2
  import { SchemaHelper } from '../types.js';
2
3
  import { prepQueryParams } from '../utils/prep-query-params.js';
3
4
  export class SchemaHelperMSSQL extends SchemaHelper {
5
+ generateIndexName(type, collection, fields) {
6
+ return getDefaultIndexName(type, collection, fields, { maxLength: 128 });
7
+ }
4
8
  applyLimit(rootQuery, limit) {
5
9
  // The ORDER BY clause is invalid in views, inline functions, derived tables, subqueries,
6
10
  // and common table expressions, unless TOP, OFFSET or FOR XML is also specified.
@@ -1,6 +1,7 @@
1
1
  import type { Knex } from 'knex';
2
2
  import { SchemaHelper, type SortRecord } from '../types.js';
3
3
  export declare class SchemaHelperMySQL extends SchemaHelper {
4
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
4
5
  applyMultiRelationalSort(knex: Knex, dbQuery: Knex.QueryBuilder, table: string, primaryKey: string, orderByString: string, orderByFields: Knex.Raw[]): Knex.QueryBuilder;
5
6
  getDatabaseSize(): Promise<number | null>;
6
7
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
@@ -1,8 +1,12 @@
1
1
  import { useEnv } from '@directus/env';
2
+ import { getDefaultIndexName } from '../../../../utils/get-default-index-name.js';
2
3
  import { getDatabaseVersion } from '../../../index.js';
3
4
  import { SchemaHelper } from '../types.js';
4
5
  const env = useEnv();
5
6
  export class SchemaHelperMySQL extends SchemaHelper {
7
+ generateIndexName(type, collection, fields) {
8
+ return getDefaultIndexName(type, collection, fields, { maxLength: 64 });
9
+ }
6
10
  applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields) {
7
11
  if (getDatabaseVersion()?.startsWith('5.7')) {
8
12
  dbQuery.orderByRaw(`?? asc, ${orderByString}`, [`${table}.${primaryKey}`, ...orderByFields]);
@@ -4,6 +4,7 @@ import type { Knex } from 'knex';
4
4
  import type { Options, SortRecord, Sql } from '../types.js';
5
5
  import { SchemaHelper } from '../types.js';
6
6
  export declare class SchemaHelperOracle extends SchemaHelper {
7
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
7
8
  changeToType(table: string, column: string, type: (typeof KNEX_TYPES)[number], options?: Options): Promise<void>;
8
9
  castA2oPrimaryKey(): string;
9
10
  preRelationChange(relation: Partial<Relation>): void;
@@ -1,6 +1,19 @@
1
+ import crypto from 'node:crypto';
2
+ import { getDefaultIndexName } from '../../../../utils/get-default-index-name.js';
1
3
  import { SchemaHelper } from '../types.js';
2
4
  import { prepQueryParams } from '../utils/prep-query-params.js';
3
5
  export class SchemaHelperOracle extends SchemaHelper {
6
+ generateIndexName(type, collection, fields) {
7
+ // Backwards compatibility with oracle requires no hash added to the name.
8
+ let indexName = getDefaultIndexName(type, collection, fields, { maxLength: Infinity });
9
+ // Knex generates a hash of the name if it is above the allowed value
10
+ // https://github.com/knex/knex/blob/master/lib/dialects/oracle/utils.js#L20
11
+ if (indexName.length > 128) {
12
+ // generates the sha1 of the name and encode it with base64
13
+ indexName = crypto.createHash('sha1').update(indexName).digest('base64').replace('=', '');
14
+ }
15
+ return indexName;
16
+ }
4
17
  async changeToType(table, column, type, options = {}) {
5
18
  await this.changeToTypeByCopy(table, column, type, options);
6
19
  }
@@ -1,6 +1,7 @@
1
1
  import type { Knex } from 'knex';
2
2
  import { SchemaHelper, type SortRecord, type Sql } from '../types.js';
3
3
  export declare class SchemaHelperPostgres extends SchemaHelper {
4
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
4
5
  getDatabaseSize(): Promise<number | null>;
5
6
  prepQueryParams(queryParams: Sql): Sql;
6
7
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], hasRelationalSort: boolean): void;
@@ -1,8 +1,12 @@
1
1
  import { useEnv } from '@directus/env';
2
+ import { getDefaultIndexName } from '../../../../utils/get-default-index-name.js';
2
3
  import { SchemaHelper } from '../types.js';
3
4
  import { prepQueryParams } from '../utils/prep-query-params.js';
4
5
  const env = useEnv();
5
6
  export class SchemaHelperPostgres extends SchemaHelper {
7
+ generateIndexName(type, collection, fields) {
8
+ return getDefaultIndexName(type, collection, fields, { maxLength: 63 });
9
+ }
6
10
  async getDatabaseSize() {
7
11
  try {
8
12
  const result = await this.knex.select(this.knex.raw(`pg_database_size(?) as size;`, [env['DB_DATABASE']]));
@@ -1,5 +1,6 @@
1
1
  import { SchemaHelper } from '../types.js';
2
2
  export declare class SchemaHelperSQLite extends SchemaHelper {
3
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
3
4
  preColumnChange(): Promise<boolean>;
4
5
  postColumnChange(): Promise<void>;
5
6
  getDatabaseSize(): Promise<number | null>;
@@ -1,5 +1,9 @@
1
+ import { getDefaultIndexName } from '../../../../utils/get-default-index-name.js';
1
2
  import { SchemaHelper } from '../types.js';
2
3
  export class SchemaHelperSQLite extends SchemaHelper {
4
+ generateIndexName(type, collection, fields) {
5
+ return getDefaultIndexName(type, collection, fields, { maxLength: Infinity });
6
+ }
3
7
  async preColumnChange() {
4
8
  const foreignCheckStatus = (await this.knex.raw('PRAGMA foreign_keys'))[0].foreign_keys === 1;
5
9
  if (foreignCheckStatus) {
@@ -19,6 +19,7 @@ export type SortRecord = {
19
19
  export declare abstract class SchemaHelper extends DatabaseHelper {
20
20
  isOneOfClients(clients: DatabaseClient[]): boolean;
21
21
  changeNullable(table: string, column: string, nullable: boolean): Promise<void>;
22
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
22
23
  changeToType(table: string, column: string, type: (typeof KNEX_TYPES)[number], options?: Options): Promise<void>;
23
24
  protected changeToTypeByCopy(table: string, column: string, type: (typeof KNEX_TYPES)[number], options: Options): Promise<void>;
24
25
  preColumnChange(): Promise<boolean>;
@@ -1,3 +1,4 @@
1
+ import { getDefaultIndexName } from '../../../utils/get-default-index-name.js';
1
2
  import { getDatabaseClient } from '../../index.js';
2
3
  import { DatabaseHelper } from '../types.js';
3
4
  export class SchemaHelper extends DatabaseHelper {
@@ -14,6 +15,9 @@ export class SchemaHelper extends DatabaseHelper {
14
15
  }
15
16
  });
16
17
  }
18
+ generateIndexName(type, collection, fields) {
19
+ return getDefaultIndexName(type, collection, fields);
20
+ }
17
21
  async changeToType(table, column, type, options = {}) {
18
22
  await this.knex.schema.alterTable(table, (builder) => {
19
23
  const b = type === 'string' ? builder.string(column, options.length) : builder[type](column);
@@ -7,17 +7,27 @@ export function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode,
7
7
  const parentItems = clone(toArray(parentItem));
8
8
  if (nestedNode.type === 'm2o') {
9
9
  const parentsByForeignKey = new Map();
10
- parentItems.forEach((parentItem) => {
10
+ // default all nested nodes to null
11
+ for (const parentItem of parentItems) {
11
12
  const relationKey = parentItem[nestedNode.relation.field];
12
13
  if (!parentsByForeignKey.has(relationKey)) {
13
14
  parentsByForeignKey.set(relationKey, []);
14
15
  }
15
16
  parentItem[nestedNode.fieldKey] = null;
16
17
  parentsByForeignKey.get(relationKey).push(parentItem);
17
- });
18
- const nestPrimaryKeyField = schema.collections[nestedNode.relation.related_collection].primary;
18
+ }
19
+ const nestedPrimaryKeyField = schema.collections[nestedNode.relation.related_collection].primary;
20
+ // populate nested items where applicable
19
21
  for (const nestedItem of nestedItems) {
20
- const nestedPK = nestedItem[nestPrimaryKeyField];
22
+ const nestedPK = nestedItem[nestedPrimaryKeyField];
23
+ // user has no access to the nestedItem PK field
24
+ if (nestedPK === null) {
25
+ continue;
26
+ }
27
+ // Existing M2O record (i.e. valid nested PK) but not linked to this (or any) parent item
28
+ if (!parentsByForeignKey.has(nestedPK)) {
29
+ continue;
30
+ }
21
31
  for (const parentItem of parentsByForeignKey.get(nestedPK)) {
22
32
  parentItem[nestedNode.fieldKey] = nestedItem;
23
33
  }
@@ -29,30 +39,44 @@ export function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode,
29
39
  const parentRelationField = nestedNode.fieldKey;
30
40
  const nestedParentKeyField = nestedNode.relation.field;
31
41
  const parentsByPrimaryKey = new Map();
32
- parentItems.forEach((parentItem) => {
33
- if (!parentItem[parentRelationField])
42
+ for (const parentItem of parentItems) {
43
+ if (!parentItem[parentRelationField]) {
34
44
  parentItem[parentRelationField] = [];
35
- const parentPrimaryKey = parentItem[parentPrimaryKeyField];
45
+ }
46
+ let parentPrimaryKey = parentItem[parentPrimaryKeyField];
47
+ // null if the user has no access to parent PK
48
+ if (parentPrimaryKey === null) {
49
+ continue;
50
+ }
51
+ else {
52
+ // ensure key access is type agnostic
53
+ parentPrimaryKey = parentPrimaryKey.toString();
54
+ }
36
55
  if (parentsByPrimaryKey.has(parentPrimaryKey)) {
37
56
  throw new Error(`Duplicate parent primary key '${parentPrimaryKey}' of '${parentCollectionName}' when merging o2m nested items`);
38
57
  }
39
58
  parentsByPrimaryKey.set(parentPrimaryKey, parentItem);
40
- });
59
+ }
41
60
  const toAddToAllParents = [];
42
- nestedItems.forEach((nestedItem) => {
43
- if (nestedItem === null)
44
- return;
61
+ for (const nestedItem of nestedItems) {
62
+ if (nestedItem === null) {
63
+ continue;
64
+ }
45
65
  if (Array.isArray(nestedItem[nestedParentKeyField])) {
46
66
  toAddToAllParents.push(nestedItem); // TODO explain this odd case
47
- return; // Avoids adding the nestedItem twice
67
+ continue; // Avoids adding the nestedItem twice
48
68
  }
49
69
  const parentPrimaryKey = nestedItem[nestedParentKeyField]?.[parentPrimaryKeyField] ?? nestedItem[nestedParentKeyField];
50
- const parentItem = parentsByPrimaryKey.get(parentPrimaryKey);
70
+ if (parentPrimaryKey === null) {
71
+ continue;
72
+ }
73
+ const parentItem = parentsByPrimaryKey.get(parentPrimaryKey.toString());
74
+ // null if the user has no access to parent PK
51
75
  if (!parentItem) {
52
- throw new Error(`Missing parentItem '${nestedItem[nestedParentKeyField]}' of '${parentCollectionName}' when merging o2m nested items`);
76
+ continue;
53
77
  }
54
78
  parentItem[parentRelationField].push(nestedItem);
55
- });
79
+ }
56
80
  for (const [index, parentItem] of parentItems.entries()) {
57
81
  if (fieldAllowed === false || (isArray(fieldAllowed) && !fieldAllowed[index])) {
58
82
  parentItem[nestedNode.fieldKey] = null;
@@ -17,7 +17,8 @@ export const respond = asyncHandler(async (req, res) => {
17
17
  if (env['CACHE_VALUE_MAX_SIZE'] !== false) {
18
18
  const valueSize = res.locals['payload'] ? stringByteSize(JSON.stringify(res.locals['payload'])) : 0;
19
19
  const maxSize = parseBytesConfiguration(env['CACHE_VALUE_MAX_SIZE']);
20
- exceedsMaxSize = valueSize > maxSize;
20
+ if (maxSize !== null)
21
+ exceedsMaxSize = valueSize > maxSize;
21
22
  }
22
23
  if ((req.method.toLowerCase() === 'get' || req.originalUrl?.startsWith('/graphql')) &&
23
24
  req.originalUrl?.startsWith('/auth') === false &&
@@ -101,7 +101,7 @@ export class CollectionsService {
101
101
  await trx.schema.createTable(payload.collection, (table) => {
102
102
  for (const field of payload.fields) {
103
103
  if (field.type && ALIAS_TYPES.includes(field.type) === false) {
104
- fieldsService.addColumnToTable(table, field);
104
+ fieldsService.addColumnToTable(table, payload.collection, field);
105
105
  }
106
106
  }
107
107
  });
@@ -30,5 +30,5 @@ export declare class FieldsService {
30
30
  updateField(collection: string, field: RawField, opts?: MutationOptions): Promise<string>;
31
31
  updateFields(collection: string, fields: RawField[], opts?: MutationOptions): Promise<string[]>;
32
32
  deleteField(collection: string, field: string, opts?: MutationOptions): Promise<void>;
33
- addColumnToTable(table: Knex.CreateTableBuilder, field: RawField | Field, existing?: Column | null): void;
33
+ addColumnToTable(table: Knex.CreateTableBuilder, collection: string, field: RawField | Field, existing?: Column | null): void;
34
34
  }
@@ -289,11 +289,11 @@ export class FieldsService {
289
289
  : field;
290
290
  if (hookAdjustedField.type && ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
291
291
  if (table) {
292
- this.addColumnToTable(table, hookAdjustedField);
292
+ this.addColumnToTable(table, collection, hookAdjustedField);
293
293
  }
294
294
  else {
295
295
  await trx.schema.alterTable(collection, (table) => {
296
- this.addColumnToTable(table, hookAdjustedField);
296
+ this.addColumnToTable(table, collection, hookAdjustedField);
297
297
  });
298
298
  }
299
299
  }
@@ -397,7 +397,7 @@ export class FieldsService {
397
397
  await trx.schema.alterTable(collection, async (table) => {
398
398
  if (!hookAdjustedField.schema)
399
399
  return;
400
- this.addColumnToTable(table, field, existingColumn);
400
+ this.addColumnToTable(table, collection, field, existingColumn);
401
401
  });
402
402
  });
403
403
  }
@@ -583,7 +583,17 @@ export class FieldsService {
583
583
  .update({ group: null })
584
584
  .where({ group: metaRow.field, collection: metaRow.collection });
585
585
  }
586
- await trx('directus_fields').delete().where({ collection, field });
586
+ const itemsService = new ItemsService('directus_fields', {
587
+ knex: trx,
588
+ accountability: this.accountability,
589
+ schema: this.schema,
590
+ });
591
+ await itemsService.deleteByQuery({
592
+ filter: {
593
+ collection: { _eq: collection },
594
+ field: { _eq: field },
595
+ },
596
+ }, { emitEvents: false });
587
597
  });
588
598
  const actionEvent = {
589
599
  event: 'fields.delete',
@@ -623,7 +633,7 @@ export class FieldsService {
623
633
  }
624
634
  }
625
635
  }
626
- addColumnToTable(table, field, existing = null) {
636
+ addColumnToTable(table, collection, field, existing = null) {
627
637
  let column;
628
638
  // Don't attempt to add a DB column for alias / corrupt fields
629
639
  if (field.type === 'alias' || field.type === 'unknown')
@@ -718,21 +728,23 @@ export class FieldsService {
718
728
  }
719
729
  else if (!existing?.is_primary_key) {
720
730
  // primary key will already have unique/index constraints
731
+ const uniqueIndexName = this.helpers.schema.generateIndexName('unique', collection, field.field);
721
732
  if (field.schema?.is_unique === true) {
722
733
  if (!existing || existing.is_unique === false) {
723
- column.unique();
734
+ column.unique({ indexName: uniqueIndexName });
724
735
  }
725
736
  }
726
737
  else if (field.schema?.is_unique === false) {
727
738
  if (existing && existing.is_unique === true) {
728
- table.dropUnique([field.field]);
739
+ table.dropUnique([field.field], uniqueIndexName);
729
740
  }
730
741
  }
742
+ const indexName = this.helpers.schema.generateIndexName('index', collection, field.field);
731
743
  if (field.schema?.is_indexed === true && !existing?.is_indexed) {
732
- column.index();
744
+ column.index(indexName);
733
745
  }
734
746
  else if (field.schema?.is_indexed === false && existing?.is_indexed) {
735
- table.dropIndex([field.field]);
747
+ table.dropIndex([field.field], indexName);
736
748
  }
737
749
  }
738
750
  if (existing) {
@@ -5,8 +5,8 @@ import { DataStore, Upload } from '@tus/utils';
5
5
  export type TusDataStoreConfig = {
6
6
  constants: {
7
7
  ENABLED: boolean;
8
- CHUNK_SIZE: number;
9
- MAX_SIZE: number;
8
+ CHUNK_SIZE: number | null;
9
+ MAX_SIZE: number | null;
10
10
  EXPIRATION_TIME: number;
11
11
  SCHEDULE: string;
12
12
  };
@@ -17,8 +17,8 @@ export type TusDataStoreConfig = {
17
17
  accountability: Accountability | undefined;
18
18
  };
19
19
  export declare class TusDataStore extends DataStore {
20
- protected chunkSize: number;
21
- protected maxSize: number;
20
+ protected chunkSize: number | undefined;
21
+ protected maxSize: number | undefined;
22
22
  protected expirationTime: number;
23
23
  protected location: string;
24
24
  protected storageDriver: TusDriver;
@@ -17,8 +17,10 @@ export class TusDataStore extends DataStore {
17
17
  accountability;
18
18
  constructor(config) {
19
19
  super();
20
- this.chunkSize = config.constants.CHUNK_SIZE;
21
- this.maxSize = config.constants.MAX_SIZE;
20
+ if (config.constants.CHUNK_SIZE !== null)
21
+ this.chunkSize = config.constants.CHUNK_SIZE;
22
+ if (config.constants.MAX_SIZE !== null)
23
+ this.maxSize = config.constants.MAX_SIZE;
22
24
  this.expirationTime = config.constants.EXPIRATION_TIME;
23
25
  this.location = config.location;
24
26
  this.storageDriver = config.driver;
@@ -37,7 +37,7 @@ export async function createTusServer(context) {
37
37
  path: '/files/tus',
38
38
  datastore: store,
39
39
  locker: getTusLocker(),
40
- maxSize: RESUMABLE_UPLOADS.MAX_SIZE,
40
+ ...(RESUMABLE_UPLOADS.MAX_SIZE !== null && { maxSize: RESUMABLE_UPLOADS.MAX_SIZE }),
41
41
  async onUploadFinish(req, res, upload) {
42
42
  const service = new ItemsService('directus_files', {
43
43
  schema: req.schema,
@@ -21,5 +21,5 @@ type GetColumnOptions = OriginalCollectionName | (FunctionColumnOptions & Origin
21
21
  * @param options Optional parameters
22
22
  * @returns Knex raw instance
23
23
  */
24
- export declare function getColumn(knex: Knex, table: string, column: string, alias: (string | false) | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
24
+ export declare function getColumn(knex: Knex, table: string, column: string, alias: string | false | undefined, schema: SchemaOverview, options?: GetColumnOptions): Knex.Raw;
25
25
  export {};
@@ -1,10 +1,13 @@
1
+ export type GetDefaultIndexNameOptions = {
2
+ maxLength?: number;
3
+ };
1
4
  /**
2
5
  * Generate an index name for a given collection + fields combination.
3
6
  *
4
- * Is based on the default index name generation of knex, but limits the index to a maximum of 64
5
- * characters (the max length for MySQL and MariaDB).
7
+ * Based on the default index name generation of knex, with the caveat that it limits the index to options.maxLength
8
+ * which defaults to 60 characters.
6
9
  *
7
10
  * @see
8
11
  * https://github.com/knex/knex/blob/fff6eb15d7088d4198650a2c6e673dedaf3b8f36/lib/schema/tablecompiler.js#L282-L297
9
12
  */
10
- export declare function getDefaultIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
13
+ export declare function getDefaultIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[], options?: GetDefaultIndexNameOptions): string;
@@ -2,20 +2,21 @@ import { getSimpleHash } from '@directus/utils';
2
2
  /**
3
3
  * Generate an index name for a given collection + fields combination.
4
4
  *
5
- * Is based on the default index name generation of knex, but limits the index to a maximum of 64
6
- * characters (the max length for MySQL and MariaDB).
5
+ * Based on the default index name generation of knex, with the caveat that it limits the index to options.maxLength
6
+ * which defaults to 60 characters.
7
7
  *
8
8
  * @see
9
9
  * https://github.com/knex/knex/blob/fff6eb15d7088d4198650a2c6e673dedaf3b8f36/lib/schema/tablecompiler.js#L282-L297
10
10
  */
11
- export function getDefaultIndexName(type, collection, fields) {
11
+ export function getDefaultIndexName(type, collection, fields, options) {
12
+ const maxLength = options?.maxLength ?? 60;
12
13
  if (!Array.isArray(fields))
13
14
  fields = fields ? [fields] : [];
14
15
  const table = collection.replace(/\.|-/g, '_');
15
16
  const indexName = (table + '_' + fields.join('_') + '_' + type).toLowerCase();
16
- if (indexName.length <= 60)
17
+ if (indexName.length <= maxLength)
17
18
  return indexName;
18
19
  const suffix = `__${getSimpleHash(indexName)}_${type}`;
19
- const prefix = indexName.substring(0, 60 - suffix.length);
20
+ const prefix = indexName.substring(0, maxLength - suffix.length);
20
21
  return `${prefix}${suffix}`;
21
22
  }
package/license CHANGED
@@ -1,7 +1,7 @@
1
1
  Licensor: Monospace, Inc.
2
2
 
3
3
  Licensed Work: Directus
4
- The Licensed Work is Copyright © 2024 Monospace, Inc.
4
+ The Licensed Work is Copyright © 2025 Monospace, Inc.
5
5
 
6
6
  Additional Use Grant: You may use the Licensed Work in production as long as
7
7
  your Total Finances do not exceed US $5,000,000 for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "23.3.1",
3
+ "version": "24.0.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -59,127 +59,126 @@
59
59
  ],
60
60
  "dependencies": {
61
61
  "@authenio/samlify-node-xmllint": "2.0.0",
62
- "@aws-sdk/client-ses": "3.668.0",
62
+ "@aws-sdk/client-ses": "3.723.0",
63
63
  "@godaddy/terminus": "4.12.1",
64
64
  "@rollup/plugin-alias": "5.1.1",
65
- "@rollup/plugin-node-resolve": "15.3.0",
65
+ "@rollup/plugin-node-resolve": "15.3.1",
66
66
  "@rollup/plugin-virtual": "3.0.2",
67
- "@tus/file-store": "1.5.0",
68
- "@tus/server": "1.9.0",
69
- "@tus/utils": "0.4.0",
70
- "@types/cookie": "0.6.0",
67
+ "@tus/server": "1.10.0",
68
+ "@tus/utils": "0.5.0",
71
69
  "argon2": "0.41.1",
72
70
  "async": "3.2.6",
73
- "axios": "1.7.7",
71
+ "axios": "1.7.9",
74
72
  "busboy": "1.6.0",
75
73
  "bytes": "3.1.2",
76
74
  "camelcase": "8.0.0",
77
- "chalk": "5.3.0",
75
+ "chalk": "5.4.1",
78
76
  "chokidar": "3.6.0",
79
77
  "commander": "12.1.0",
80
78
  "content-disposition": "0.5.4",
81
- "cookie": "0.7.2",
79
+ "cookie": "1.0.2",
82
80
  "cookie-parser": "1.4.7",
83
81
  "cors": "2.8.5",
84
82
  "cron-parser": "4.9.0",
85
83
  "date-fns": "4.1.0",
86
84
  "deep-diff": "1.0.2",
87
85
  "destroy": "1.2.0",
88
- "dotenv": "16.4.5",
86
+ "dotenv": "16.4.7",
89
87
  "encodeurl": "2.0.0",
90
88
  "eventemitter2": "6.4.9",
91
89
  "execa": "8.0.1",
92
90
  "exif-reader": "2.0.1",
93
- "express": "4.20.0",
91
+ "express": "4.21.2",
94
92
  "flat": "6.0.1",
95
93
  "fs-extra": "11.2.0",
96
94
  "glob-to-regexp": "0.4.1",
97
- "graphql": "16.9.0",
95
+ "graphql": "16.10.0",
98
96
  "graphql-compose": "9.0.11",
99
- "graphql-ws": "5.16.0",
97
+ "graphql-ws": "5.16.1",
100
98
  "helmet": "8.0.0",
101
99
  "icc": "3.0.0",
102
- "inquirer": "12.0.0",
103
- "ioredis": "5.4.1",
100
+ "inquirer": "12.3.0",
101
+ "ioredis": "5.4.2",
104
102
  "ip-matching": "2.1.2",
105
- "isolated-vm": "5.0.1",
103
+ "isolated-vm": "5.0.3",
106
104
  "joi": "17.13.3",
107
105
  "js-yaml": "4.1.0",
108
106
  "js2xmlparser": "5.0.0",
109
107
  "json2csv": "5.0.7",
110
108
  "jsonwebtoken": "9.0.2",
111
- "keyv": "5.1.0",
109
+ "keyv": "5.2.3",
112
110
  "knex": "3.1.0",
113
111
  "ldapjs": "2.3.3",
114
- "liquidjs": "10.17.0",
112
+ "liquidjs": "10.20.1",
115
113
  "lodash-es": "4.17.21",
116
- "marked": "14.1.2",
114
+ "marked": "15.0.6",
117
115
  "micromustache": "8.0.3",
118
116
  "mime-types": "2.1.35",
119
- "minimatch": "9.0.5",
117
+ "minimatch": "10.0.1",
120
118
  "mnemonist": "0.39.8",
121
119
  "ms": "2.1.3",
122
- "nanoid": "5.0.7",
120
+ "nanoid": "5.0.9",
123
121
  "node-machine-id": "1.1.12",
124
122
  "node-schedule": "2.1.1",
125
- "nodemailer": "6.9.15",
123
+ "nodemailer": "6.9.16",
126
124
  "object-hash": "3.0.0",
127
125
  "openapi3-ts": "4.4.0",
128
- "openid-client": "5.7.0",
129
- "ora": "8.1.0",
126
+ "openid-client": "5.7.1",
127
+ "ora": "8.1.1",
130
128
  "otplib": "12.0.1",
131
- "p-limit": "6.1.0",
129
+ "p-limit": "6.2.0",
132
130
  "p-queue": "8.0.1",
133
- "papaparse": "5.4.1",
134
- "pino": "9.4.0",
131
+ "papaparse": "5.5.0",
132
+ "pino": "9.6.0",
135
133
  "pino-http": "10.3.0",
136
134
  "pino-http-print": "3.1.0",
137
- "pino-pretty": "11.2.2",
138
- "qs": "6.13.0",
139
- "rate-limiter-flexible": "5.0.3",
140
- "rollup": "4.17.2",
135
+ "pino-pretty": "13.0.0",
136
+ "qs": "6.13.1",
137
+ "rate-limiter-flexible": "5.0.4",
138
+ "rollup": "4.30.1",
141
139
  "samlify": "2.8.10",
142
- "sanitize-html": "2.13.1",
140
+ "sanitize-html": "2.14.0",
143
141
  "sharp": "0.33.5",
144
142
  "snappy": "7.2.2",
145
- "stream-json": "1.8.0",
143
+ "stream-json": "1.9.1",
146
144
  "tar": "7.4.3",
147
- "tsx": "4.19.1",
145
+ "tsx": "4.19.2",
148
146
  "wellknown": "0.5.0",
149
147
  "ws": "8.18.0",
150
- "zod": "3.23.8",
148
+ "zod": "3.24.1",
151
149
  "zod-validation-error": "3.4.0",
152
- "@directus/app": "13.3.8",
153
- "@directus/constants": "12.0.1",
154
- "@directus/errors": "1.0.1",
155
- "@directus/extensions": "2.0.6",
156
- "@directus/env": "4.1.0",
157
- "@directus/extensions-registry": "2.0.6",
158
- "@directus/extensions-sdk": "12.1.4",
159
- "@directus/memory": "2.0.6",
160
- "@directus/pressure": "2.0.5",
161
- "@directus/schema": "12.1.1",
150
+ "@directus/constants": "13.0.0",
151
+ "@directus/app": "13.5.0",
152
+ "@directus/env": "5.0.0",
153
+ "@directus/errors": "2.0.0",
154
+ "@directus/extensions": "3.0.0",
155
+ "@directus/extensions-sdk": "13.0.0",
156
+ "@directus/extensions-registry": "3.0.0",
157
+ "@directus/format-title": "12.0.0",
158
+ "@directus/memory": "3.0.0",
159
+ "@directus/pressure": "3.0.0",
160
+ "@directus/schema": "13.0.0",
162
161
  "@directus/specs": "11.1.0",
163
- "@directus/storage": "11.0.1",
164
- "@directus/format-title": "11.0.0",
165
- "@directus/storage-driver-azure": "11.1.2",
166
- "@directus/storage-driver-cloudinary": "11.1.2",
167
- "@directus/storage-driver-gcs": "11.1.2",
168
- "@directus/storage-driver-local": "11.0.1",
169
- "@directus/storage-driver-s3": "11.0.5",
170
- "@directus/system-data": "2.1.2",
171
- "@directus/utils": "12.0.5",
172
- "@directus/storage-driver-supabase": "2.1.2",
173
- "@directus/validation": "1.0.5",
174
- "directus": "11.3.4"
162
+ "@directus/storage": "12.0.0",
163
+ "@directus/storage-driver-azure": "12.0.0",
164
+ "@directus/storage-driver-gcs": "12.0.0",
165
+ "@directus/storage-driver-local": "12.0.0",
166
+ "@directus/storage-driver-cloudinary": "12.0.0",
167
+ "@directus/storage-driver-supabase": "3.0.0",
168
+ "@directus/system-data": "3.0.0",
169
+ "@directus/utils": "13.0.0",
170
+ "@directus/storage-driver-s3": "12.0.0",
171
+ "@directus/validation": "2.0.0",
172
+ "directus": "11.4.0"
175
173
  },
176
174
  "devDependencies": {
177
- "@ngneat/falso": "7.2.0",
175
+ "@directus/tsconfig": "3.0.0",
176
+ "@ngneat/falso": "7.3.0",
178
177
  "@types/async": "3.2.24",
179
178
  "@types/busboy": "1.5.4",
180
- "@types/bytes": "3.1.4",
179
+ "@types/bytes": "3.1.5",
181
180
  "@types/content-disposition": "0.5.8",
182
- "@types/cookie-parser": "1.4.7",
181
+ "@types/cookie-parser": "1.4.8",
183
182
  "@types/cors": "2.8.17",
184
183
  "@types/deep-diff": "1.0.5",
185
184
  "@types/destroy": "1.0.3",
@@ -196,38 +195,37 @@
196
195
  "@types/lodash-es": "4.17.12",
197
196
  "@types/mime-types": "2.1.4",
198
197
  "@types/ms": "0.7.34",
199
- "@types/node": "18.19.55",
198
+ "@types/node": "22.10.5",
200
199
  "@types/node-schedule": "2.1.7",
201
- "@types/nodemailer": "6.4.16",
200
+ "@types/nodemailer": "6.4.17",
202
201
  "@types/object-hash": "3.0.6",
203
- "@types/papaparse": "5.3.14",
204
- "@types/qs": "6.9.16",
202
+ "@types/papaparse": "5.3.15",
203
+ "@types/qs": "6.9.17",
205
204
  "@types/sanitize-html": "2.13.0",
206
- "@types/stream-json": "1.7.7",
205
+ "@types/stream-json": "1.7.8",
207
206
  "@types/wellknown": "0.5.8",
208
- "@types/ws": "8.5.12",
209
- "@vitest/coverage-v8": "2.1.2",
207
+ "@types/ws": "8.5.13",
208
+ "@vitest/coverage-v8": "2.1.8",
210
209
  "copyfiles": "2.4.1",
211
210
  "form-data": "4.0.1",
212
211
  "get-port": "7.1.0",
213
212
  "knex-mock-client": "3.0.2",
214
- "typescript": "5.6.3",
215
- "vitest": "2.1.2",
216
- "@directus/random": "1.0.0",
217
- "@directus/tsconfig": "2.0.0",
218
- "@directus/types": "12.2.2"
213
+ "typescript": "5.7.3",
214
+ "vitest": "2.1.8",
215
+ "@directus/random": "2.0.0",
216
+ "@directus/types": "13.0.0"
219
217
  },
220
218
  "optionalDependencies": {
221
219
  "@keyv/redis": "3.0.1",
222
- "mysql2": "3.11.3",
220
+ "mysql2": "3.12.0",
223
221
  "nodemailer-mailgun-transport": "2.1.5",
224
- "oracledb": "6.6.0",
225
- "pg": "8.13.0",
222
+ "oracledb": "6.7.1",
223
+ "pg": "8.13.1",
226
224
  "sqlite3": "5.1.7",
227
225
  "tedious": "18.6.1"
228
226
  },
229
227
  "engines": {
230
- "node": ">=18.17.0"
228
+ "node": ">=22"
231
229
  },
232
230
  "scripts": {
233
231
  "build": "tsc --project tsconfig.prod.json && copyfiles \"src/**/*.{yaml,liquid}\" -u 1 dist",