@directus/api 26.0.1 → 27.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/cli/utils/create-db-connection.js +3 -5
  2. package/dist/controllers/files.js +1 -1
  3. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +2 -2
  4. package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -2
  5. package/dist/database/get-ast-from-query/utils/get-related-collection.js +2 -2
  6. package/dist/database/helpers/fn/types.js +2 -1
  7. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -0
  8. package/dist/database/helpers/schema/dialects/mssql.js +6 -0
  9. package/dist/database/helpers/schema/dialects/mysql.d.ts +0 -1
  10. package/dist/database/helpers/schema/dialects/mysql.js +0 -11
  11. package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -0
  12. package/dist/database/helpers/schema/dialects/oracle.js +6 -0
  13. package/dist/database/helpers/schema/types.d.ts +2 -1
  14. package/dist/database/helpers/schema/types.js +6 -4
  15. package/dist/database/index.d.ts +0 -6
  16. package/dist/database/index.js +12 -28
  17. package/dist/database/migrations/20210225A-add-relations-sort-field.js +3 -1
  18. package/dist/database/migrations/20210510A-restructure-relations.js +8 -8
  19. package/dist/database/migrations/20210924A-add-collection-organization.js +6 -1
  20. package/dist/database/migrations/20210927A-replace-fields-group.js +3 -1
  21. package/dist/database/migrations/20211118A-add-notifications.js +3 -1
  22. package/dist/database/migrations/20211211A-add-shares.js +7 -1
  23. package/dist/database/migrations/20230823A-add-content-versioning.js +3 -1
  24. package/dist/database/migrations/20240909A-separate-comments.js +3 -1
  25. package/dist/database/run-ast/lib/apply-query/add-join.d.ts +54 -0
  26. package/dist/database/run-ast/lib/apply-query/add-join.js +86 -0
  27. package/dist/database/run-ast/lib/apply-query/aggregate.d.ts +3 -0
  28. package/dist/database/run-ast/lib/apply-query/aggregate.js +24 -0
  29. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +8 -0
  30. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.js +20 -0
  31. package/dist/database/run-ast/lib/apply-query/filter/index.d.ts +8 -0
  32. package/dist/database/run-ast/lib/apply-query/filter/index.js +151 -0
  33. package/dist/database/run-ast/lib/apply-query/filter/operator.d.ts +3 -0
  34. package/dist/database/run-ast/lib/apply-query/filter/operator.js +175 -0
  35. package/dist/database/run-ast/lib/apply-query/filter/validate-operator.d.ts +2 -0
  36. package/dist/database/run-ast/lib/apply-query/filter/validate-operator.js +18 -0
  37. package/dist/database/run-ast/lib/apply-query/get-filter-path.d.ts +1 -0
  38. package/dist/database/run-ast/lib/apply-query/get-filter-path.js +13 -0
  39. package/dist/database/run-ast/lib/apply-query/get-operation.d.ts +7 -0
  40. package/dist/database/run-ast/lib/apply-query/get-operation.js +19 -0
  41. package/dist/database/run-ast/lib/apply-query/index.d.ts +20 -0
  42. package/dist/database/run-ast/lib/apply-query/index.js +92 -0
  43. package/dist/database/run-ast/lib/apply-query/join-filter-with-cases.d.ts +2 -0
  44. package/dist/database/run-ast/lib/apply-query/join-filter-with-cases.js +12 -0
  45. package/dist/database/run-ast/lib/apply-query/mock.d.ts +3 -0
  46. package/dist/database/run-ast/lib/apply-query/mock.js +4 -0
  47. package/dist/database/run-ast/lib/apply-query/pagination.d.ts +3 -0
  48. package/dist/database/run-ast/lib/apply-query/pagination.js +11 -0
  49. package/dist/database/run-ast/lib/apply-query/search.d.ts +4 -0
  50. package/dist/database/run-ast/lib/apply-query/search.js +78 -0
  51. package/dist/database/run-ast/lib/apply-query/sort.d.ts +19 -0
  52. package/dist/database/run-ast/lib/apply-query/sort.js +87 -0
  53. package/dist/database/run-ast/lib/get-db-query.js +7 -5
  54. package/dist/database/run-ast/run-ast.js +1 -1
  55. package/dist/database/run-ast/utils/apply-case-when.js +1 -1
  56. package/dist/database/run-ast/utils/get-column-pre-processor.js +2 -2
  57. package/dist/{utils → database/run-ast/utils}/get-column.js +1 -1
  58. package/dist/database/run-ast/utils/get-field-alias.js +1 -1
  59. package/dist/database/run-ast/utils/remove-temporary-fields.js +1 -1
  60. package/dist/database/seeds/01-collections.yaml +2 -2
  61. package/dist/database/seeds/04-fields.yaml +2 -2
  62. package/dist/database/seeds/05-activity.yaml +1 -1
  63. package/dist/database/seeds/08-permissions.yaml +1 -1
  64. package/dist/database/seeds/09-presets.yaml +1 -1
  65. package/dist/database/seeds/10-relations.yaml +8 -8
  66. package/dist/database/seeds/11-revisions.yaml +1 -1
  67. package/dist/database/seeds/run.js +8 -1
  68. package/dist/flows.js +8 -1
  69. package/dist/metrics/lib/create-metrics.js +13 -7
  70. package/dist/operations/condition/index.js +7 -4
  71. package/dist/operations/exec/index.js +2 -1
  72. package/dist/permissions/utils/default-permission.d.ts +7 -0
  73. package/dist/permissions/utils/default-permission.js +7 -0
  74. package/dist/services/collections.js +22 -18
  75. package/dist/services/fields.js +2 -5
  76. package/dist/services/payload.d.ts +6 -6
  77. package/dist/services/payload.js +47 -13
  78. package/dist/services/server.js +7 -2
  79. package/dist/services/specifications.js +2 -2
  80. package/dist/utils/get-relation-info.d.ts +1 -1
  81. package/dist/utils/get-relation-info.js +2 -4
  82. package/dist/websocket/handlers/items.js +5 -0
  83. package/package.json +23 -21
  84. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +0 -2
  85. package/dist/database/get-ast-from-query/utils/get-relation.js +0 -7
  86. package/dist/utils/apply-query.d.ts +0 -46
  87. package/dist/utils/apply-query.js +0 -771
  88. /package/dist/{utils → database/run-ast/utils}/apply-function-to-column-name.d.ts +0 -0
  89. /package/dist/{utils → database/run-ast/utils}/apply-function-to-column-name.js +0 -0
  90. /package/dist/{utils → database/run-ast/utils}/get-column.d.ts +0 -0
@@ -0,0 +1,78 @@
1
+ import { NUMERIC_TYPES } from '@directus/constants';
2
+ import { isIn } from '@directus/utils';
3
+ import { getCases } from '../../../../permissions/modules/process-ast/lib/get-cases.js';
4
+ import { isValidUuid } from '../../../../utils/is-valid-uuid.js';
5
+ import { parseNumericString } from '../../../../utils/parse-numeric-string.js';
6
+ import { getHelpers } from '../../../helpers/index.js';
7
+ import { applyFilter } from './filter/index.js';
8
+ export function applySearch(knex, schema, dbQuery, searchQuery, collection, aliasMap, permissions) {
9
+ const { number: numberHelper } = getHelpers(knex);
10
+ const allowedFields = new Set(permissions.filter((p) => p.collection === collection).flatMap((p) => p.fields ?? []));
11
+ let fields = Object.entries(schema.collections[collection].fields);
12
+ const { cases, caseMap } = getCases(collection, permissions, []);
13
+ // Add field restrictions if non-admin and "everything" is not allowed
14
+ if (cases.length !== 0 && !allowedFields.has('*')) {
15
+ fields = fields.filter((field) => allowedFields.has(field[0]));
16
+ }
17
+ dbQuery.andWhere(function (queryBuilder) {
18
+ let needsFallbackCondition = true;
19
+ fields.forEach(([name, field]) => {
20
+ // only account for when cases when full access is not given
21
+ const whenCases = allowedFields.has('*') ? [] : (caseMap[name] ?? []).map((caseIndex) => cases[caseIndex]);
22
+ const fieldType = getFieldType(field);
23
+ if (fieldType !== null) {
24
+ needsFallbackCondition = false;
25
+ }
26
+ else {
27
+ return;
28
+ }
29
+ if (cases.length !== 0 && whenCases?.length !== 0) {
30
+ queryBuilder.orWhere((subQuery) => {
31
+ addSearchCondition(subQuery, name, fieldType, 'and');
32
+ applyFilter(knex, schema, subQuery, { _or: whenCases }, collection, aliasMap, cases, permissions);
33
+ });
34
+ }
35
+ else {
36
+ addSearchCondition(queryBuilder, name, fieldType, 'or');
37
+ }
38
+ });
39
+ if (needsFallbackCondition) {
40
+ queryBuilder.orWhereRaw('1 = 0');
41
+ }
42
+ });
43
+ function addSearchCondition(queryBuilder, name, fieldType, logical) {
44
+ if (fieldType === null) {
45
+ return;
46
+ }
47
+ if (fieldType === 'string') {
48
+ queryBuilder[logical].whereRaw(`LOWER(??) LIKE ?`, [`${collection}.${name}`, `%${searchQuery.toLowerCase()}%`]);
49
+ }
50
+ else if (fieldType === 'numeric') {
51
+ numberHelper.addSearchCondition(queryBuilder, collection, name, parseNumericString(searchQuery), logical);
52
+ }
53
+ else if (fieldType === 'uuid') {
54
+ queryBuilder[logical].where({ [`${collection}.${name}`]: searchQuery });
55
+ }
56
+ }
57
+ function getFieldType(field) {
58
+ if (['text', 'string'].includes(field.type)) {
59
+ return 'string';
60
+ }
61
+ if (isNumericField(field)) {
62
+ const number = parseNumericString(searchQuery);
63
+ if (number === null) {
64
+ return null;
65
+ }
66
+ if (numberHelper.isNumberValid(number, field)) {
67
+ return 'numeric';
68
+ }
69
+ }
70
+ if (field.type === 'uuid' && isValidUuid(searchQuery)) {
71
+ return 'uuid';
72
+ }
73
+ return null;
74
+ }
75
+ }
76
+ function isNumericField(field) {
77
+ return isIn(field.type, NUMERIC_TYPES);
78
+ }
@@ -0,0 +1,19 @@
1
+ import type { Aggregate, SchemaOverview } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ import type { AliasMap } from '../../../../utils/get-column-path.js';
4
+ export type ColumnSortRecord = {
5
+ order: 'asc' | 'desc';
6
+ column: string;
7
+ };
8
+ export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, sort: string[], aggregate: Aggregate | null | undefined, collection: string, aliasMap: AliasMap, returnRecords?: boolean): {
9
+ sortRecords: {
10
+ order: "asc" | "desc";
11
+ column: any;
12
+ }[];
13
+ hasJoins: boolean;
14
+ hasMultiRelationalSort: boolean;
15
+ } | {
16
+ hasJoins: boolean;
17
+ hasMultiRelationalSort: boolean;
18
+ sortRecords?: never;
19
+ };
@@ -0,0 +1,87 @@
1
+ import { getColumnPath } from '../../../../utils/get-column-path.js';
2
+ import { getColumn } from '../../utils/get-column.js';
3
+ import { getRelationInfo } from '../../../../utils/get-relation-info.js';
4
+ import { addJoin } from './add-join.js';
5
+ export function applySort(knex, schema, rootQuery, sort, aggregate, collection, aliasMap, returnRecords = false) {
6
+ const relations = schema.relations;
7
+ let hasJoins = false;
8
+ let hasMultiRelationalSort = false;
9
+ const sortRecords = sort.map((sortField) => {
10
+ const column = sortField.split('.');
11
+ let order = 'asc';
12
+ if (sortField.startsWith('-')) {
13
+ order = 'desc';
14
+ }
15
+ if (column[0].startsWith('-')) {
16
+ column[0] = column[0].substring(1);
17
+ }
18
+ // Is the column name one of the aggregate functions used in the query if there is any?
19
+ if (Object.keys(aggregate ?? {}).includes(column[0])) {
20
+ // If so, return the column name without the order prefix
21
+ const operation = column[0];
22
+ // Get the field for the aggregate function
23
+ const field = column[1];
24
+ // If the operation is countAll there is no field.
25
+ if (operation === 'countAll') {
26
+ return {
27
+ order,
28
+ column: 'countAll',
29
+ };
30
+ }
31
+ // If the operation is a root count there is no field.
32
+ if (operation === 'count' && (field === '*' || !field)) {
33
+ return {
34
+ order,
35
+ column: 'count',
36
+ };
37
+ }
38
+ // Return the column name with the operation and field name
39
+ return {
40
+ order,
41
+ column: returnRecords ? column[0] : `${operation}->${field}`,
42
+ };
43
+ }
44
+ if (column.length === 1) {
45
+ const pathRoot = column[0].split(':')[0];
46
+ const { relation, relationType } = getRelationInfo(relations, collection, pathRoot);
47
+ if (!relation || ['m2o', 'a2o'].includes(relationType ?? '')) {
48
+ return {
49
+ order,
50
+ column: returnRecords ? column[0] : getColumn(knex, collection, column[0], false, schema),
51
+ };
52
+ }
53
+ }
54
+ const { hasMultiRelational, isJoinAdded } = addJoin({
55
+ path: column,
56
+ collection,
57
+ aliasMap,
58
+ rootQuery,
59
+ schema,
60
+ knex,
61
+ });
62
+ const { columnPath } = getColumnPath({
63
+ path: column,
64
+ collection,
65
+ aliasMap,
66
+ relations,
67
+ schema,
68
+ });
69
+ const [alias, field] = columnPath.split('.');
70
+ if (!hasJoins) {
71
+ hasJoins = isJoinAdded;
72
+ }
73
+ if (!hasMultiRelationalSort) {
74
+ hasMultiRelationalSort = hasMultiRelational;
75
+ }
76
+ return {
77
+ order,
78
+ column: returnRecords ? columnPath : getColumn(knex, alias, field, false, schema),
79
+ };
80
+ });
81
+ if (returnRecords)
82
+ return { sortRecords, hasJoins, hasMultiRelationalSort };
83
+ // Clears the order if any, eg: from MSSQL offset
84
+ rootQuery.clear('order');
85
+ rootQuery.orderBy(sortRecords);
86
+ return { hasJoins, hasMultiRelationalSort };
87
+ }
@@ -1,14 +1,16 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { cloneDeep } from 'lodash-es';
3
- import applyQuery, { applyLimit, applySort, generateAlias } from '../../../utils/apply-query.js';
3
+ import { applySort } from './apply-query/sort.js';
4
4
  import { getCollectionFromAlias } from '../../../utils/get-collection-from-alias.js';
5
- import { getColumn } from '../../../utils/get-column.js';
5
+ import { getColumn } from '../utils/get-column.js';
6
6
  import { getHelpers } from '../../helpers/index.js';
7
7
  import { applyCaseWhen } from '../utils/apply-case-when.js';
8
8
  import { getColumnPreprocessor } from '../utils/get-column-pre-processor.js';
9
9
  import { getNodeAlias } from '../utils/get-field-alias.js';
10
10
  import { getInnerQueryColumnPreProcessor } from '../utils/get-inner-query-column-pre-processor.js';
11
11
  import { withPreprocessBindings } from '../utils/with-preprocess-bindings.js';
12
+ import applyQuery, { generateAlias } from './apply-query/index.js';
13
+ import { applyLimit } from './apply-query/pagination.js';
12
14
  export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, permissionsOnly }, { knex, schema }) {
13
15
  const aliasMap = Object.create(null);
14
16
  const env = useEnv();
@@ -46,12 +48,12 @@ export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissi
46
48
  return dbQuery;
47
49
  }
48
50
  const primaryKey = schema.collections[table].primary;
49
- let dbQuery = knex.from(table);
51
+ const dbQuery = knex.from(table);
50
52
  let sortRecords;
51
53
  const innerQuerySortRecords = [];
52
54
  let hasMultiRelationalSort;
53
55
  if (queryCopy.sort) {
54
- const sortResult = applySort(knex, schema, dbQuery, queryCopy, table, aliasMap, true);
56
+ const sortResult = applySort(knex, schema, dbQuery, queryCopy.sort, queryCopy.aggregate, table, aliasMap, true);
55
57
  if (sortResult) {
56
58
  sortRecords = sortResult.sortRecords;
57
59
  hasMultiRelationalSort = sortResult.hasMultiRelationalSort;
@@ -115,7 +117,7 @@ export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissi
115
117
  innerQuerySortRecords.push({ alias: sortAlias, order: sortRecord.order, column: orderByColumn });
116
118
  });
117
119
  if (hasMultiRelationalSort) {
118
- dbQuery = helpers.schema.applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields);
120
+ dbQuery.rowNumber(knex.ref('directus_row_number').toQuery(), knex.raw(`partition by ?? order by ${orderByString}`, [`${table}.${primaryKey}`, ...orderByFields]));
119
121
  // Start order by with directus_row_number. The directus_row_number is derived from a window function that
120
122
  // is ordered by the sort fields within every primary key partition. That ensures that the result with the
121
123
  // row number = 1 is the top-most row of every partition, according to the selected sort fields.
@@ -49,7 +49,7 @@ export async function runAst(originalAST, schema, accountability, options) {
49
49
  return null;
50
50
  // Run the items through the special transforms
51
51
  const payloadService = new PayloadService(collection, { knex, schema });
52
- let items = await payloadService.processValues('read', rawItems, query.alias ?? {});
52
+ let items = await payloadService.processValues('read', rawItems, query.alias ?? {}, query.aggregate ?? {});
53
53
  if (!items || (Array.isArray(items) && items.length === 0))
54
54
  return items;
55
55
  // Apply the `_in` filters to the nested collection batches
@@ -1,4 +1,4 @@
1
- import { applyFilter } from '../../../utils/apply-query.js';
1
+ import { applyFilter } from '../lib/apply-query/filter/index.js';
2
2
  export function applyCaseWhen({ columnCases, table, aliasMap, cases, column, alias, permissions }, { knex, schema }) {
3
3
  const caseQuery = knex.queryBuilder();
4
4
  applyFilter(knex, schema, caseQuery, { _or: columnCases }, table, aliasMap, cases, permissions);
@@ -1,5 +1,5 @@
1
- import { joinFilterWithCases } from '../../../utils/apply-query.js';
2
- import { getColumn } from '../../../utils/get-column.js';
1
+ import { joinFilterWithCases } from '../lib/apply-query/join-filter-with-cases.js';
2
+ import { getColumn } from './get-column.js';
3
3
  import { parseFilterKey } from '../../../utils/parse-filter-key.js';
4
4
  import { getHelpers } from '../../helpers/index.js';
5
5
  import { applyCaseWhen } from './apply-case-when.js';
@@ -1,6 +1,6 @@
1
1
  import { REGEX_BETWEEN_PARENS } from '@directus/constants';
2
2
  import { getFunctionsForType } from '@directus/utils';
3
- import { getFunctions } from '../database/helpers/index.js';
3
+ import { getFunctions } from '../../helpers/index.js';
4
4
  import { InvalidQueryError } from '@directus/errors';
5
5
  import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { applyFunctionToColumnName } from '../../../utils/apply-function-to-column-name.js';
1
+ import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
2
2
  export function getNodeAlias(node) {
3
3
  return applyFunctionToColumnName(node.fieldKey);
4
4
  }
@@ -1,6 +1,6 @@
1
1
  import { toArray } from '@directus/utils';
2
2
  import { cloneDeep, pick } from 'lodash-es';
3
- import { applyFunctionToColumnName } from '../../../utils/apply-function-to-column-name.js';
3
+ import { applyFunctionToColumnName } from './apply-function-to-column-name.js';
4
4
  export function removeTemporaryFields(schema, rawItem, ast, primaryKeyField, parentItem) {
5
5
  const rawItems = cloneDeep(toArray(rawItem));
6
6
  const items = [];
@@ -3,7 +3,7 @@ table: directus_collections
3
3
  columns:
4
4
  collection:
5
5
  type: string
6
- length: 64
6
+ length: 'MAX_TABLE_NAME_LENGTH'
7
7
  primary: true
8
8
  nullable: false
9
9
  icon:
@@ -39,4 +39,4 @@ columns:
39
39
  length: 255
40
40
  sort_field:
41
41
  type: string
42
- length: 64
42
+ length: 'MAX_COLUMN_NAME_LENGTH'
@@ -5,14 +5,14 @@ columns:
5
5
  increments: true
6
6
  collection:
7
7
  type: string
8
- length: 64
8
+ length: 'MAX_TABLE_NAME_LENGTH'
9
9
  nullable: false
10
10
  references:
11
11
  table: directus_collections
12
12
  column: collection
13
13
  field:
14
14
  type: string
15
- length: 64
15
+ length: 'MAX_COLUMN_NAME_LENGTH'
16
16
  nullable: false
17
17
  special:
18
18
  type: string
@@ -22,7 +22,7 @@ columns:
22
22
  length: 255
23
23
  collection:
24
24
  type: string
25
- length: 64
25
+ length: 'MAX_TABLE_NAME_LENGTH'
26
26
  nullable: false
27
27
  references:
28
28
  table: directus_collections
@@ -10,7 +10,7 @@ columns:
10
10
  column: id
11
11
  collection:
12
12
  type: string
13
- length: 64
13
+ length: 'MAX_TABLE_NAME_LENGTH'
14
14
  nullable: false
15
15
  references:
16
16
  table: directus_collections
@@ -18,7 +18,7 @@ columns:
18
18
  column: id
19
19
  collection:
20
20
  type: string
21
- length: 64
21
+ length: 'MAX_TABLE_NAME_LENGTH'
22
22
  references:
23
23
  table: directus_collections
24
24
  column: collection
@@ -5,36 +5,36 @@ columns:
5
5
  increments: true
6
6
  many_collection:
7
7
  type: string
8
- length: 64
8
+ length: 'MAX_TABLE_NAME_LENGTH'
9
9
  nullable: false
10
10
  references:
11
11
  table: directus_collections
12
12
  column: collection
13
13
  many_field:
14
14
  type: string
15
- length: 64
15
+ length: 'MAX_COLUMN_NAME_LENGTH'
16
16
  nullable: false
17
17
  many_primary:
18
18
  type: string
19
- length: 64
19
+ length: 'MAX_COLUMN_NAME_LENGTH'
20
20
  nullable: false
21
21
  one_collection:
22
22
  type: string
23
- length: 64
23
+ length: 'MAX_TABLE_NAME_LENGTH'
24
24
  references:
25
25
  table: directus_collections
26
26
  column: collection
27
27
  one_field:
28
28
  type: string
29
- length: 64
29
+ length: 'MAX_COLUMN_NAME_LENGTH'
30
30
  one_primary:
31
31
  type: string
32
- length: 64
32
+ length: 'MAX_COLUMN_NAME_LENGTH'
33
33
  one_collection_field:
34
34
  type: string
35
- length: 64
35
+ length: 'MAX_COLUMN_NAME_LENGTH'
36
36
  one_allowed_collections:
37
37
  type: text
38
38
  junction_field:
39
39
  type: string
40
- length: 64
40
+ length: 'MAX_COLUMN_NAME_LENGTH'
@@ -12,7 +12,7 @@ columns:
12
12
  column: id
13
13
  collection:
14
14
  type: string
15
- length: 64
15
+ length: 'MAX_TABLE_NAME_LENGTH'
16
16
  nullable: false
17
17
  references:
18
18
  table: directus_collections
@@ -24,7 +24,14 @@ export default async function runSeed(database) {
24
24
  if (columnInfo.type === 'alias' || columnInfo.type === 'unknown')
25
25
  return;
26
26
  if (columnInfo.type === 'string') {
27
- column = tableBuilder.string(columnName, columnInfo.length);
27
+ let length = columnInfo.length;
28
+ if (length === 'MAX_TABLE_NAME_LENGTH') {
29
+ length = helpers.schema.getTableNameMaxLength();
30
+ }
31
+ else if (length === 'MAX_COLUMN_NAME_LENGTH') {
32
+ length = helpers.schema.getColumnNameMaxLength();
33
+ }
34
+ column = tableBuilder.string(columnName, Number(length));
28
35
  }
29
36
  else if (columnInfo.increments) {
30
37
  column = tableBuilder.increments();
package/dist/flows.js CHANGED
@@ -297,6 +297,12 @@ class FlowManager {
297
297
  });
298
298
  }
299
299
  }
300
+ if ((flow.trigger === 'manual' || flow.trigger === 'webhook') &&
301
+ flow.options['async'] !== true &&
302
+ flow.options['error_on_reject'] === true &&
303
+ lastOperationStatus === 'reject') {
304
+ throw keyedData[LAST_KEY];
305
+ }
300
306
  if (flow.trigger === 'event' && flow.options['type'] === 'filter' && lastOperationStatus === 'reject') {
301
307
  throw keyedData[LAST_KEY];
302
308
  }
@@ -315,8 +321,9 @@ class FlowManager {
315
321
  return { successor: null, status: 'unknown', data: null, options: null };
316
322
  }
317
323
  const handler = this.operations.get(operation.type);
318
- const options = applyOptionsData(operation.options, keyedData);
324
+ let options = operation.options;
319
325
  try {
326
+ options = applyOptionsData(options, keyedData);
320
327
  let result = await handler(options, {
321
328
  services,
322
329
  env: useEnv(),
@@ -7,6 +7,7 @@ import pm2 from 'pm2';
7
7
  import { AggregatorRegistry, Counter, Histogram, register } from 'prom-client';
8
8
  import { getCache } from '../../cache.js';
9
9
  import { hasDatabaseConnection } from '../../database/index.js';
10
+ import { useLogger } from '../../logger/index.js';
10
11
  import { redisConfigAvailable, useRedis } from '../../redis/index.js';
11
12
  import { getStorage } from '../../storage/index.js';
12
13
  const isPM2 = 'PM2_HOME' in process.env;
@@ -15,6 +16,7 @@ const listApps = promisify(pm2.list.bind(pm2));
15
16
  const sendDataToProcessId = promisify(pm2.sendDataToProcessId.bind(pm2));
16
17
  export function createMetrics() {
17
18
  const env = useEnv();
19
+ const logger = useLogger();
18
20
  const services = env['METRICS_SERVICES'] ?? [];
19
21
  const aggregates = new Map();
20
22
  /**
@@ -54,8 +56,8 @@ export function createMetrics() {
54
56
  }
55
57
  await Promise.allSettled(syncs);
56
58
  }
57
- catch {
58
- // ignore
59
+ catch (error) {
60
+ logger.error(error);
59
61
  }
60
62
  }
61
63
  }
@@ -215,11 +217,15 @@ export function createMetrics() {
215
217
  try {
216
218
  await disk.write(`metric-${checkId}`, Readable.from(['check']));
217
219
  const fileStream = await disk.read(`metric-${checkId}`);
218
- await new Promise((resolve) => fileStream.on('data', async () => {
219
- fileStream.destroy();
220
- await disk.delete(`metric-${checkId}`);
221
- return resolve(null);
222
- }));
220
+ fileStream.on('data', async () => {
221
+ try {
222
+ fileStream.destroy();
223
+ await disk.delete(`metric-${checkId}`);
224
+ }
225
+ catch (error) {
226
+ logger.error(error);
227
+ }
228
+ });
223
229
  }
224
230
  catch {
225
231
  metric.inc();
@@ -1,12 +1,15 @@
1
1
  import { defineOperationApi } from '@directus/extensions';
2
- import { validatePayload } from '@directus/utils';
2
+ import { parseFilter, validatePayload } from '@directus/utils';
3
3
  import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
4
4
  export default defineOperationApi({
5
5
  id: 'condition',
6
- handler: ({ filter }, { data }) => {
7
- const errors = validatePayload(filter, data, { requireAll: true });
6
+ handler: ({ filter }, { data, accountability }) => {
7
+ const parsedFilter = parseFilter(filter, accountability);
8
+ if (!parsedFilter) {
9
+ return null;
10
+ }
11
+ const errors = validatePayload(parsedFilter, data, { requireAll: true });
8
12
  if (errors.length > 0) {
9
- // sanitize and format errors
10
13
  const validationErrors = errors
11
14
  .map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))
12
15
  .flat();
@@ -1,5 +1,6 @@
1
1
  import { defineOperationApi } from '@directus/extensions';
2
2
  import { createRequire } from 'node:module';
3
+ import { sieveFunctions } from '@directus/utils';
3
4
  const require = createRequire(import.meta.url);
4
5
  const ivm = require('isolated-vm');
5
6
  /**
@@ -31,7 +32,7 @@ export default defineOperationApi({
31
32
  }, { copy: true });
32
33
  // Run the operation once to define the module.exports function
33
34
  await context.eval(code, { timeout: scriptTimeoutMs });
34
- const inputData = new ivm.ExternalCopy({ data });
35
+ const inputData = new ivm.ExternalCopy({ data: sieveFunctions(data) });
35
36
  const resultRef = await context.evalClosure(`return module.exports($0.data)`, [inputData.copyInto()], {
36
37
  result: { reference: true, promise: true },
37
38
  timeout: scriptTimeoutMs,
@@ -0,0 +1,7 @@
1
+ export declare const DEFUAULT_PERMISSION: {
2
+ policy: null;
3
+ permissions: null;
4
+ validation: null;
5
+ presets: null;
6
+ fields: null;
7
+ };
@@ -0,0 +1,7 @@
1
+ export const DEFUAULT_PERMISSION = {
2
+ policy: null,
3
+ permissions: null,
4
+ validation: null,
5
+ presets: null,
6
+ fields: null,
7
+ };
@@ -70,25 +70,29 @@ export class CollectionsService {
70
70
  if ('fields' in payload && !Array.isArray(payload.fields)) {
71
71
  throw new InvalidPayloadError({ reason: `"fields" must be an array` });
72
72
  }
73
- // Directus heavily relies on the primary key of a collection, so we have to make sure that
74
- // every collection that is created has a primary key. If no primary key field is created
75
- // while making the collection, we default to an auto incremented id named `id`
73
+ /**
74
+ * Directus heavily relies on the primary key of a collection, so we have to make sure that
75
+ * every collection that is created has a primary key. If no primary key field is created
76
+ * while making the collection, we default to an auto incremented id named `id`
77
+ */
78
+ const injectedPrimaryKeyField = {
79
+ field: 'id',
80
+ type: 'integer',
81
+ meta: {
82
+ hidden: true,
83
+ interface: 'numeric',
84
+ readonly: true,
85
+ },
86
+ schema: {
87
+ is_primary_key: true,
88
+ has_auto_increment: true,
89
+ },
90
+ };
76
91
  if (!payload.fields || payload.fields.length === 0) {
77
- payload.fields = [
78
- {
79
- field: 'id',
80
- type: 'integer',
81
- meta: {
82
- hidden: true,
83
- interface: 'numeric',
84
- readonly: true,
85
- },
86
- schema: {
87
- is_primary_key: true,
88
- has_auto_increment: true,
89
- },
90
- },
91
- ];
92
+ payload.fields = [injectedPrimaryKeyField];
93
+ }
94
+ else if (!payload.fields.some((f) => f.schema?.is_primary_key === true || f.schema?.has_auto_increment === true)) {
95
+ payload.fields = [injectedPrimaryKeyField, ...payload.fields];
92
96
  }
93
97
  // Ensure that every field meta has the field/collection fields filled correctly
94
98
  payload.fields = payload.fields.map((field) => {
@@ -2,7 +2,7 @@ import { DEFAULT_NUMERIC_PRECISION, DEFAULT_NUMERIC_SCALE, KNEX_TYPES, REGEX_BET
2
2
  import { useEnv } from '@directus/env';
3
3
  import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
4
4
  import { createInspector } from '@directus/schema';
5
- import { addFieldFlag, toArray } from '@directus/utils';
5
+ import { addFieldFlag, getRelations, toArray } from '@directus/utils';
6
6
  import { isEqual, isNil, merge } from 'lodash-es';
7
7
  import { clearSystemCache, getCache, getCacheValue, setCacheValue } from '../cache.js';
8
8
  import { ALIAS_TYPES, ALLOWED_DB_DEFAULT_FUNCTIONS } from '../constants.js';
@@ -511,10 +511,7 @@ export class FieldsService {
511
511
  });
512
512
  }
513
513
  await transaction(this.knex, async (trx) => {
514
- const relations = this.schema.relations.filter((relation) => {
515
- return ((relation.collection === collection && relation.field === field) ||
516
- (relation.related_collection === collection && relation.meta?.one_field === field));
517
- });
514
+ const relations = getRelations(this.schema.relations, collection, field);
518
515
  const relationsService = new RelationsService({
519
516
  knex: trx,
520
517
  accountability: this.accountability,