@directus/api 23.3.2 → 24.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 (43) hide show
  1. package/dist/constants.d.ts +2 -2
  2. package/dist/controllers/files.js +1 -1
  3. package/dist/database/get-ast-from-query/lib/parse-fields.js +4 -3
  4. package/dist/database/helpers/index.d.ts +1 -3
  5. package/dist/database/helpers/index.js +1 -3
  6. package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -0
  7. package/dist/database/helpers/schema/dialects/mssql.js +4 -0
  8. package/dist/database/helpers/schema/dialects/mysql.d.ts +1 -0
  9. package/dist/database/helpers/schema/dialects/mysql.js +4 -0
  10. package/dist/database/helpers/schema/dialects/oracle.d.ts +7 -1
  11. package/dist/database/helpers/schema/dialects/oracle.js +28 -0
  12. package/dist/database/helpers/schema/dialects/postgres.d.ts +1 -0
  13. package/dist/database/helpers/schema/dialects/postgres.js +4 -0
  14. package/dist/database/helpers/schema/dialects/sqlite.d.ts +1 -0
  15. package/dist/database/helpers/schema/dialects/sqlite.js +4 -0
  16. package/dist/database/helpers/schema/types.d.ts +4 -1
  17. package/dist/database/helpers/schema/types.js +13 -0
  18. package/dist/database/run-ast/utils/merge-with-parent-items.js +39 -15
  19. package/dist/middleware/respond.js +2 -1
  20. package/dist/operations/mail/index.d.ts +6 -3
  21. package/dist/operations/mail/index.js +2 -2
  22. package/dist/services/collections.js +1 -1
  23. package/dist/services/fields.d.ts +1 -1
  24. package/dist/services/fields.js +37 -41
  25. package/dist/services/files.js +6 -5
  26. package/dist/services/tus/data-store.d.ts +4 -4
  27. package/dist/services/tus/data-store.js +4 -2
  28. package/dist/services/tus/server.js +1 -1
  29. package/dist/utils/get-column.d.ts +1 -1
  30. package/dist/utils/get-default-index-name.d.ts +6 -3
  31. package/dist/utils/get-default-index-name.js +6 -5
  32. package/dist/utils/get-schema.js +1 -1
  33. package/dist/utils/sanitize-schema.d.ts +1 -1
  34. package/license +1 -1
  35. package/package.json +74 -76
  36. package/dist/database/helpers/nullable-update/dialects/default.d.ts +0 -3
  37. package/dist/database/helpers/nullable-update/dialects/default.js +0 -3
  38. package/dist/database/helpers/nullable-update/dialects/oracle.d.ts +0 -12
  39. package/dist/database/helpers/nullable-update/dialects/oracle.js +0 -16
  40. package/dist/database/helpers/nullable-update/index.d.ts +0 -7
  41. package/dist/database/helpers/nullable-update/index.js +0 -7
  42. package/dist/database/helpers/nullable-update/types.d.ts +0 -7
  43. package/dist/database/helpers/nullable-update/types.js +0 -12
@@ -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 = [];
@@ -169,10 +169,11 @@ export async function parseFields(options, context) {
169
169
  continue;
170
170
  }
171
171
  }
172
+ const childQuery = { ...options.query };
172
173
  // update query alias for children parseFields
173
174
  const deepAlias = getDeepQuery(options.deep?.[fieldKey] || {})?.['alias'];
174
- if (!isEmpty(deepAlias))
175
- options.query.alias = deepAlias;
175
+ // reset alias to empty if none are present
176
+ childQuery.alias = isEmpty(deepAlias) ? {} : deepAlias;
176
177
  child = {
177
178
  type: relationType,
178
179
  name: relatedCollection,
@@ -184,7 +185,7 @@ export async function parseFields(options, context) {
184
185
  children: await parseFields({
185
186
  parentCollection: relatedCollection,
186
187
  fields: nestedFields,
187
- query: options.query,
188
+ query: childQuery,
188
189
  deep: options.deep?.[fieldKey] || {},
189
190
  accountability: options.accountability,
190
191
  }, context),
@@ -3,17 +3,15 @@ import type { Knex } from 'knex';
3
3
  import * as dateHelpers from './date/index.js';
4
4
  import * as fnHelpers from './fn/index.js';
5
5
  import * as geometryHelpers from './geometry/index.js';
6
+ import * as numberHelpers from './number/index.js';
6
7
  import * as schemaHelpers from './schema/index.js';
7
8
  import * as sequenceHelpers from './sequence/index.js';
8
- import * as numberHelpers from './number/index.js';
9
- import * as nullableUpdateHelper from './nullable-update/index.js';
10
9
  export declare function getHelpers(database: Knex): {
11
10
  date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
12
11
  st: geometryHelpers.postgres | geometryHelpers.mssql | geometryHelpers.mysql | geometryHelpers.sqlite | geometryHelpers.oracle | geometryHelpers.redshift;
13
12
  schema: schemaHelpers.cockroachdb | schemaHelpers.mssql | schemaHelpers.mysql | schemaHelpers.postgres | schemaHelpers.sqlite | schemaHelpers.oracle | schemaHelpers.redshift;
14
13
  sequence: sequenceHelpers.mysql | sequenceHelpers.postgres;
15
14
  number: numberHelpers.cockroachdb | numberHelpers.mssql | numberHelpers.postgres | numberHelpers.sqlite | numberHelpers.oracle;
16
- nullableUpdate: nullableUpdateHelper.postgres | nullableUpdateHelper.oracle;
17
15
  };
18
16
  export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.postgres | fnHelpers.mssql | fnHelpers.mysql | fnHelpers.sqlite | fnHelpers.oracle;
19
17
  export type Helpers = ReturnType<typeof getHelpers>;
@@ -2,10 +2,9 @@ import { getDatabaseClient } from '../index.js';
2
2
  import * as dateHelpers from './date/index.js';
3
3
  import * as fnHelpers from './fn/index.js';
4
4
  import * as geometryHelpers from './geometry/index.js';
5
+ import * as numberHelpers from './number/index.js';
5
6
  import * as schemaHelpers from './schema/index.js';
6
7
  import * as sequenceHelpers from './sequence/index.js';
7
- import * as numberHelpers from './number/index.js';
8
- import * as nullableUpdateHelper from './nullable-update/index.js';
9
8
  export function getHelpers(database) {
10
9
  const client = getDatabaseClient(database);
11
10
  return {
@@ -14,7 +13,6 @@ export function getHelpers(database) {
14
13
  schema: new schemaHelpers[client](database),
15
14
  sequence: new sequenceHelpers[client](database),
16
15
  number: new numberHelpers[client](database),
17
- nullableUpdate: new nullableUpdateHelper[client](database),
18
16
  };
19
17
  }
20
18
  export function getFunctions(database, schema) {
@@ -1,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]);
@@ -1,14 +1,20 @@
1
1
  import type { KNEX_TYPES } from '@directus/constants';
2
- import type { Field, Relation, Type } from '@directus/types';
2
+ import type { Column } from '@directus/schema';
3
+ import type { Field, RawField, Relation, Type } from '@directus/types';
3
4
  import type { Knex } from 'knex';
4
5
  import type { Options, SortRecord, Sql } from '../types.js';
5
6
  import { SchemaHelper } from '../types.js';
6
7
  export declare class SchemaHelperOracle extends SchemaHelper {
8
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
7
9
  changeToType(table: string, column: string, type: (typeof KNEX_TYPES)[number], options?: Options): Promise<void>;
8
10
  castA2oPrimaryKey(): string;
9
11
  preRelationChange(relation: Partial<Relation>): void;
10
12
  processFieldType(field: Field): Type;
11
13
  getDatabaseSize(): Promise<number | null>;
14
+ /**
15
+ * Oracle throws an error when overwriting the nullable option for an existing column with the same value.
16
+ */
17
+ setNullable(column: Knex.ColumnBuilder, field: RawField | Field, existing: Column | null): void;
12
18
  prepQueryParams(queryParams: Sql): Sql;
13
19
  prepBindings(bindings: Knex.Value[]): any;
14
20
  addInnerSortFieldsToGroupBy(groupByFields: (string | Knex.Raw)[], sortRecords: SortRecord[], _hasRelationalSort: boolean): 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
  }
@@ -39,6 +52,21 @@ export class SchemaHelperOracle extends SchemaHelper {
39
52
  return null;
40
53
  }
41
54
  }
55
+ /**
56
+ * Oracle throws an error when overwriting the nullable option for an existing column with the same value.
57
+ */
58
+ setNullable(column, field, existing) {
59
+ if (!existing) {
60
+ super.setNullable(column, field, existing);
61
+ return;
62
+ }
63
+ if (field.schema?.is_nullable === false && existing.is_nullable === true) {
64
+ column.notNullable();
65
+ }
66
+ else if (field.schema?.is_nullable === true && existing.is_nullable === false) {
67
+ column.nullable();
68
+ }
69
+ }
42
70
  prepQueryParams(queryParams) {
43
71
  return prepQueryParams(queryParams, { format: (index) => `:${index + 1}` });
44
72
  }
@@ -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) {
@@ -1,5 +1,6 @@
1
1
  import type { KNEX_TYPES } from '@directus/constants';
2
- import type { Field, Relation, Type } from '@directus/types';
2
+ import type { Column } from '@directus/schema';
3
+ import type { Field, RawField, Relation, Type } from '@directus/types';
3
4
  import type { Knex } from 'knex';
4
5
  import type { DatabaseClient } from '../../../types/index.js';
5
6
  import { DatabaseHelper } from '../types.js';
@@ -19,11 +20,13 @@ export type SortRecord = {
19
20
  export declare abstract class SchemaHelper extends DatabaseHelper {
20
21
  isOneOfClients(clients: DatabaseClient[]): boolean;
21
22
  changeNullable(table: string, column: string, nullable: boolean): Promise<void>;
23
+ generateIndexName(type: 'unique' | 'foreign' | 'index', collection: string, fields: string | string[]): string;
22
24
  changeToType(table: string, column: string, type: (typeof KNEX_TYPES)[number], options?: Options): Promise<void>;
23
25
  protected changeToTypeByCopy(table: string, column: string, type: (typeof KNEX_TYPES)[number], options: Options): Promise<void>;
24
26
  preColumnChange(): Promise<boolean>;
25
27
  postColumnChange(): Promise<void>;
26
28
  preRelationChange(_relation: Partial<Relation>): void;
29
+ setNullable(column: Knex.ColumnBuilder, field: RawField | Field, existing: Column | null): void;
27
30
  processFieldType(field: Field): Type;
28
31
  constraintName(existingName: string): string;
29
32
  applyLimit(rootQuery: Knex.QueryBuilder, limit: number): void;
@@ -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);
@@ -62,6 +66,15 @@ export class SchemaHelper extends DatabaseHelper {
62
66
  preRelationChange(_relation) {
63
67
  return;
64
68
  }
69
+ setNullable(column, field, existing) {
70
+ const isNullable = field.schema?.is_nullable ?? existing?.is_nullable ?? true;
71
+ if (isNullable) {
72
+ column.nullable();
73
+ }
74
+ else {
75
+ column.notNullable();
76
+ }
77
+ }
65
78
  processFieldType(field) {
66
79
  return field.type;
67
80
  }
@@ -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 &&
@@ -1,10 +1,13 @@
1
1
  export type Options = {
2
- body?: string;
3
- template?: string;
4
- data?: Record<string, any>;
5
2
  to: string;
6
3
  type: 'wysiwyg' | 'markdown' | 'template';
7
4
  subject: string;
5
+ body?: string;
6
+ template?: string;
7
+ data?: Record<string, any>;
8
+ cc?: string;
9
+ bcc?: string;
10
+ replyTo?: string;
8
11
  };
9
12
  declare const _default: import("@directus/extensions").OperationApiConfig<Options>;
10
13
  export default _default;
@@ -5,9 +5,9 @@ import { useLogger } from '../../logger/index.js';
5
5
  const logger = useLogger();
6
6
  export default defineOperationApi({
7
7
  id: 'mail',
8
- handler: async ({ body, template, data, to, type, subject }, { accountability, database, getSchema }) => {
8
+ handler: async ({ body, template, data, to, type, subject, cc, bcc, replyTo }, { accountability, database, getSchema }) => {
9
9
  const mailService = new MailService({ schema: await getSchema({ database }), accountability, knex: database });
10
- const mailObject = { to, subject };
10
+ const mailObject = { to, subject, cc, bcc, replyTo };
11
11
  const safeBody = typeof body !== 'string' ? JSON.stringify(body) : body;
12
12
  if (type === 'template') {
13
13
  mailObject.template = {
@@ -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')
@@ -664,7 +674,17 @@ export class FieldsService {
664
674
  else {
665
675
  throw new InvalidPayloadError({ reason: `Illegal type passed: "${field.type}"` });
666
676
  }
667
- const setDefaultValue = (defaultValue) => {
677
+ /**
678
+ * The column nullability must be set on every alter or it will be dropped
679
+ * This is due to column.alter() not being incremental per https://knexjs.org/guide/schema-builder.html#alter
680
+ */
681
+ this.helpers.schema.setNullable(column, field, existing);
682
+ /**
683
+ * The default value must be set on every alter or it will be dropped
684
+ * This is due to column.alter() not being incremental per https://knexjs.org/guide/schema-builder.html#alter
685
+ */
686
+ const defaultValue = field.schema?.default_value !== undefined ? field.schema?.default_value : existing?.default_value;
687
+ if (defaultValue !== undefined) {
668
688
  const newDefaultValueIsString = typeof defaultValue === 'string';
669
689
  const newDefaultIsNowFunction = newDefaultValueIsString && defaultValue.toLowerCase() === 'now()';
670
690
  const newDefaultIsCurrentTimestamp = newDefaultValueIsString && defaultValue === 'CURRENT_TIMESTAMP';
@@ -684,34 +704,6 @@ export class FieldsService {
684
704
  else {
685
705
  column.defaultTo(defaultValue);
686
706
  }
687
- };
688
- // for a new item, set the default value and nullable as provided without any further considerations
689
- if (!existing) {
690
- if (field.schema?.default_value !== undefined) {
691
- setDefaultValue(field.schema.default_value);
692
- }
693
- if (field.schema?.is_nullable || field.schema?.is_nullable === undefined) {
694
- column.nullable();
695
- }
696
- else {
697
- column.notNullable();
698
- }
699
- }
700
- else {
701
- // for an existing item: if nullable option changed, we have to provide the default values as well and actually vice versa
702
- // see https://knexjs.org/guide/schema-builder.html#alter
703
- // To overwrite a nullable option with the same value this is not possible for Oracle though, hence the DB helper
704
- if (field.schema?.default_value !== undefined || field.schema?.is_nullable !== undefined) {
705
- this.helpers.nullableUpdate.updateNullableValue(column, field, existing);
706
- let defaultValue = null;
707
- if (field.schema?.default_value !== undefined) {
708
- defaultValue = field.schema.default_value;
709
- }
710
- else if (existing.default_value !== undefined) {
711
- defaultValue = existing.default_value;
712
- }
713
- setDefaultValue(defaultValue);
714
- }
715
707
  }
716
708
  if (field.schema?.is_primary_key) {
717
709
  column.primary().notNullable();
@@ -720,19 +712,23 @@ export class FieldsService {
720
712
  // primary key will already have unique/index constraints
721
713
  if (field.schema?.is_unique === true) {
722
714
  if (!existing || existing.is_unique === false) {
723
- column.unique();
715
+ column.unique({ indexName: this.helpers.schema.generateIndexName('unique', collection, field.field) });
724
716
  }
725
717
  }
726
718
  else if (field.schema?.is_unique === false) {
727
- if (existing && existing.is_unique === true) {
728
- table.dropUnique([field.field]);
719
+ if (existing?.is_unique === true) {
720
+ table.dropUnique([field.field], this.helpers.schema.generateIndexName('unique', collection, field.field));
729
721
  }
730
722
  }
731
- if (field.schema?.is_indexed === true && !existing?.is_indexed) {
732
- column.index();
723
+ if (field.schema?.is_indexed === true) {
724
+ if (!existing || existing.is_indexed === false) {
725
+ column.index(this.helpers.schema.generateIndexName('index', collection, field.field));
726
+ }
733
727
  }
734
- else if (field.schema?.is_indexed === false && existing?.is_indexed) {
735
- table.dropIndex([field.field]);
728
+ else if (field.schema?.is_indexed === false) {
729
+ if (existing?.is_indexed === true) {
730
+ table.dropIndex([field.field], this.helpers.schema.generateIndexName('index', collection, field.field));
731
+ }
736
732
  }
737
733
  }
738
734
  if (existing) {
@@ -1,5 +1,5 @@
1
1
  import { useEnv } from '@directus/env';
2
- import { ContentTooLargeError, ForbiddenError, InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
2
+ import { ContentTooLargeError, InvalidPayloadError, ServiceUnavailableError } from '@directus/errors';
3
3
  import formatTitle from '@directus/format-title';
4
4
  import { toArray } from '@directus/utils';
5
5
  import encodeURL from 'encodeurl';
@@ -211,10 +211,11 @@ export class FilesService extends ItemsService {
211
211
  */
212
212
  async deleteMany(keys) {
213
213
  const storage = await getStorage();
214
- const files = await super.readMany(keys, { fields: ['id', 'storage', 'filename_disk'], limit: -1 });
215
- if (!files) {
216
- throw new ForbiddenError();
217
- }
214
+ const sudoFilesItemsService = new FilesService({
215
+ knex: this.knex,
216
+ schema: this.schema,
217
+ });
218
+ const files = await sudoFilesItemsService.readMany(keys, { fields: ['id', 'storage', 'filename_disk'], limit: -1 });
218
219
  await super.deleteMany(keys);
219
220
  for (const file of files) {
220
221
  const disk = storage.location(file['storage']);
@@ -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
  }
@@ -44,10 +44,10 @@ export async function getSchema(options, attempt = 0) {
44
44
  const subscription = new Promise((resolve, reject) => {
45
45
  bus.subscribe(messageKey, busListener).catch(reject);
46
46
  function busListener(options) {
47
+ cleanup();
47
48
  if (options.schema === null) {
48
49
  return reject();
49
50
  }
50
- cleanup();
51
51
  setLocalSchemaCache(options.schema).catch(reject);
52
52
  resolve(options.schema);
53
53
  }
@@ -16,7 +16,7 @@ export declare function sanitizeCollection(collection: Collection | undefined):
16
16
  * @returns sanitized field
17
17
  */
18
18
  export declare function sanitizeField(field: Field | undefined, sanitizeAllSchema?: boolean): Partial<Field> | undefined;
19
- export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "numeric_precision" | "data_type" | "default_value" | "max_length" | "numeric_scale" | "is_nullable" | "is_unique" | "is_indexed" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
19
+ export declare function sanitizeColumn(column: Column): Pick<Column, "table" | "name" | "data_type" | "default_value" | "max_length" | "numeric_precision" | "numeric_scale" | "is_nullable" | "is_unique" | "is_indexed" | "is_primary_key" | "is_generated" | "generation_expression" | "has_auto_increment" | "foreign_key_table" | "foreign_key_column">;
20
20
  /**
21
21
  * Pick certain database vendor specific relation properties that should be compared when performing diff
22
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.2",
3
+ "version": "24.0.1",
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
131
  "papaparse": "5.4.1",
134
- "pino": "9.4.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.4.0",
153
- "@directus/constants": "12.0.1",
154
- "@directus/env": "4.1.0",
155
- "@directus/errors": "1.0.1",
156
- "@directus/extensions": "2.0.6",
157
- "@directus/extensions-registry": "2.0.6",
158
- "@directus/extensions-sdk": "12.1.4",
159
- "@directus/format-title": "11.0.0",
160
- "@directus/memory": "2.0.6",
161
- "@directus/pressure": "2.0.5",
162
- "@directus/schema": "12.1.1",
150
+ "@directus/app": "13.6.0",
151
+ "@directus/constants": "13.0.0",
152
+ "@directus/env": "5.0.0",
153
+ "@directus/errors": "2.0.0",
154
+ "@directus/extensions": "3.0.1",
155
+ "@directus/extensions-registry": "3.0.1",
156
+ "@directus/extensions-sdk": "13.0.1",
157
+ "@directus/format-title": "12.0.0",
158
+ "@directus/memory": "3.0.0",
159
+ "@directus/schema": "13.0.0",
163
160
  "@directus/specs": "11.1.0",
164
- "@directus/storage-driver-azure": "11.1.2",
165
- "@directus/storage": "11.0.1",
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/storage-driver-supabase": "2.1.2",
171
- "@directus/system-data": "2.1.2",
172
- "@directus/validation": "1.0.5",
173
- "@directus/utils": "12.0.5",
174
- "directus": "11.3.5"
161
+ "@directus/storage-driver-azure": "12.0.0",
162
+ "@directus/pressure": "3.0.0",
163
+ "@directus/storage": "12.0.0",
164
+ "@directus/storage-driver-cloudinary": "12.0.0",
165
+ "@directus/storage-driver-local": "12.0.0",
166
+ "@directus/storage-driver-gcs": "12.0.0",
167
+ "@directus/storage-driver-s3": "12.0.0",
168
+ "@directus/storage-driver-supabase": "3.0.0",
169
+ "@directus/utils": "13.0.0",
170
+ "@directus/system-data": "3.0.0",
171
+ "@directus/validation": "2.0.0",
172
+ "directus": "11.4.1"
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",
@@ -1,3 +0,0 @@
1
- import { NullableFieldUpdateHelper } from '../types.js';
2
- export declare class NullableFieldUpdateHelperDefault extends NullableFieldUpdateHelper {
3
- }
@@ -1,3 +0,0 @@
1
- import { NullableFieldUpdateHelper } from '../types.js';
2
- export class NullableFieldUpdateHelperDefault extends NullableFieldUpdateHelper {
3
- }
@@ -1,12 +0,0 @@
1
- import type { Column } from '@directus/schema';
2
- import type { Field, RawField } from '@directus/types';
3
- import type { Knex } from 'knex';
4
- import { NullableFieldUpdateHelper } from '../types.js';
5
- /**
6
- * Oracle throws an error when overwriting the nullable option with same value.
7
- * Therefore we need to check if the nullable option has changed and only then apply it.
8
- * The default value can be set regardless of the previous value.
9
- */
10
- export declare class NullableFieldUpdateHelperOracle extends NullableFieldUpdateHelper {
11
- updateNullableValue(column: Knex.ColumnBuilder, field: RawField | Field, existing: Column): void;
12
- }
@@ -1,16 +0,0 @@
1
- import { NullableFieldUpdateHelper } from '../types.js';
2
- /**
3
- * Oracle throws an error when overwriting the nullable option with same value.
4
- * Therefore we need to check if the nullable option has changed and only then apply it.
5
- * The default value can be set regardless of the previous value.
6
- */
7
- export class NullableFieldUpdateHelperOracle extends NullableFieldUpdateHelper {
8
- updateNullableValue(column, field, existing) {
9
- if (field.schema?.is_nullable === false && existing.is_nullable === true) {
10
- column.notNullable();
11
- }
12
- else if (field.schema?.is_nullable === true && existing.is_nullable === false) {
13
- column.nullable();
14
- }
15
- }
16
- }
@@ -1,7 +0,0 @@
1
- export { NullableFieldUpdateHelperOracle as oracle } from './dialects/oracle.js';
2
- export { NullableFieldUpdateHelperDefault as postgres } from './dialects/default.js';
3
- export { NullableFieldUpdateHelperDefault as mysql } from './dialects/default.js';
4
- export { NullableFieldUpdateHelperDefault as cockroachdb } from './dialects/default.js';
5
- export { NullableFieldUpdateHelperDefault as redshift } from './dialects/default.js';
6
- export { NullableFieldUpdateHelperDefault as sqlite } from './dialects/default.js';
7
- export { NullableFieldUpdateHelperDefault as mssql } from './dialects/default.js';
@@ -1,7 +0,0 @@
1
- export { NullableFieldUpdateHelperOracle as oracle } from './dialects/oracle.js';
2
- export { NullableFieldUpdateHelperDefault as postgres } from './dialects/default.js';
3
- export { NullableFieldUpdateHelperDefault as mysql } from './dialects/default.js';
4
- export { NullableFieldUpdateHelperDefault as cockroachdb } from './dialects/default.js';
5
- export { NullableFieldUpdateHelperDefault as redshift } from './dialects/default.js';
6
- export { NullableFieldUpdateHelperDefault as sqlite } from './dialects/default.js';
7
- export { NullableFieldUpdateHelperDefault as mssql } from './dialects/default.js';
@@ -1,7 +0,0 @@
1
- import type { Knex } from 'knex';
2
- import { DatabaseHelper } from '../types.js';
3
- import type { Column } from '@directus/schema';
4
- import type { Field, RawField } from '@directus/types';
5
- export declare class NullableFieldUpdateHelper extends DatabaseHelper {
6
- updateNullableValue(column: Knex.ColumnBuilder, field: RawField | Field, existing: Column): void;
7
- }
@@ -1,12 +0,0 @@
1
- import { DatabaseHelper } from '../types.js';
2
- export class NullableFieldUpdateHelper extends DatabaseHelper {
3
- updateNullableValue(column, field, existing) {
4
- const isNullable = field.schema?.is_nullable ?? existing?.is_nullable ?? true;
5
- if (isNullable) {
6
- column.nullable();
7
- }
8
- else {
9
- column.notNullable();
10
- }
11
- }
12
- }