directus 9.21.0 → 9.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/app.js +9 -5
  2. package/dist/app.test.d.ts +1 -0
  3. package/dist/auth/drivers/openid.js +3 -1
  4. package/dist/cli/commands/bootstrap/index.js +2 -2
  5. package/dist/cli/commands/schema/apply.js +0 -2
  6. package/dist/cli/commands/schema/snapshot.js +0 -2
  7. package/dist/cli/commands/security/secret.js +2 -2
  8. package/dist/cli/utils/create-env/env-stub.liquid +3 -3
  9. package/dist/cli/utils/create-env/index.js +5 -8
  10. package/dist/constants.d.ts +1 -0
  11. package/dist/constants.js +2 -1
  12. package/dist/controllers/assets.js +9 -7
  13. package/dist/controllers/files.js +2 -1
  14. package/dist/controllers/utils.js +2 -2
  15. package/dist/database/helpers/fn/dialects/mssql.js +2 -1
  16. package/dist/database/helpers/fn/dialects/mysql.js +3 -2
  17. package/dist/database/helpers/fn/dialects/oracle.js +2 -1
  18. package/dist/database/helpers/fn/dialects/postgres.js +2 -1
  19. package/dist/database/helpers/fn/dialects/sqlite.js +2 -1
  20. package/dist/database/helpers/fn/types.d.ts +1 -0
  21. package/dist/database/helpers/fn/types.js +5 -4
  22. package/dist/database/helpers/index.d.ts +1 -1
  23. package/dist/database/helpers/schema/dialects/mssql.d.ts +6 -0
  24. package/dist/database/helpers/schema/dialects/mssql.js +14 -0
  25. package/dist/database/helpers/schema/dialects/mysql.d.ts +5 -0
  26. package/dist/database/helpers/schema/dialects/mysql.js +19 -0
  27. package/dist/database/helpers/schema/dialects/oracle.d.ts +1 -0
  28. package/dist/database/helpers/schema/dialects/oracle.js +3 -0
  29. package/dist/database/helpers/schema/index.d.ts +2 -2
  30. package/dist/database/helpers/schema/index.js +4 -4
  31. package/dist/database/helpers/schema/types.d.ts +5 -0
  32. package/dist/database/helpers/schema/types.js +13 -0
  33. package/dist/database/index.d.ts +6 -0
  34. package/dist/database/index.js +20 -1
  35. package/dist/database/migrations/20211007A-update-presets.js +2 -2
  36. package/dist/database/migrations/run.js +7 -31
  37. package/dist/database/run-ast.js +132 -6
  38. package/dist/database/system-data/fields/index.js +2 -1
  39. package/dist/env.js +3 -2
  40. package/dist/exceptions/range-not-satisfiable.d.ts +1 -1
  41. package/dist/extensions.d.ts +7 -1
  42. package/dist/extensions.js +41 -15
  43. package/dist/logger.js +27 -1
  44. package/dist/middleware/schema.js +1 -1
  45. package/dist/operations/request/index.d.ts +1 -2
  46. package/dist/operations/request/index.js +2 -2
  47. package/dist/services/assets.d.ts +4 -3
  48. package/dist/services/assets.js +13 -11
  49. package/dist/services/authentication.js +4 -3
  50. package/dist/services/authorization.js +1 -1
  51. package/dist/services/collections.js +7 -7
  52. package/dist/services/fields.d.ts +3 -2
  53. package/dist/services/fields.js +40 -20
  54. package/dist/services/files.d.ts +4 -3
  55. package/dist/services/files.js +92 -68
  56. package/dist/services/flows.d.ts +0 -2
  57. package/dist/services/flows.js +0 -14
  58. package/dist/services/flows.test.d.ts +1 -0
  59. package/dist/services/graphql/index.d.ts +5 -1
  60. package/dist/services/graphql/index.js +29 -31
  61. package/dist/services/import-export.d.ts +4 -3
  62. package/dist/services/items.js +7 -1
  63. package/dist/services/meta.js +2 -2
  64. package/dist/services/operations.d.ts +0 -2
  65. package/dist/services/operations.js +0 -12
  66. package/dist/services/operations.test.d.ts +1 -0
  67. package/dist/services/permissions.d.ts +0 -5
  68. package/dist/services/permissions.js +0 -25
  69. package/dist/services/permissions.test.d.ts +1 -0
  70. package/dist/services/relations.d.ts +2 -2
  71. package/dist/services/relations.js +24 -16
  72. package/dist/services/roles.js +0 -3
  73. package/dist/services/roles.test.d.ts +1 -0
  74. package/dist/services/server.js +8 -6
  75. package/dist/services/shares.js +2 -2
  76. package/dist/services/specifications.js +12 -1
  77. package/dist/services/users.js +10 -4
  78. package/dist/services/webhooks.d.ts +0 -2
  79. package/dist/services/webhooks.js +0 -10
  80. package/dist/services/webhooks.test.d.ts +1 -0
  81. package/dist/storage/get-storage-driver.d.ts +3 -0
  82. package/dist/storage/get-storage-driver.js +20 -0
  83. package/dist/storage/get-storage-driver.test.d.ts +1 -0
  84. package/dist/storage/index.d.ts +5 -0
  85. package/dist/storage/index.js +20 -0
  86. package/dist/storage/index.test.d.ts +1 -0
  87. package/dist/storage/register-drivers.d.ts +2 -0
  88. package/dist/storage/register-drivers.js +22 -0
  89. package/dist/storage/register-drivers.test.d.ts +1 -0
  90. package/dist/storage/register-locations.d.ts +2 -0
  91. package/dist/storage/register-locations.js +17 -0
  92. package/dist/storage/register-locations.test.d.ts +1 -0
  93. package/dist/utils/apply-query.d.ts +27 -3
  94. package/dist/utils/apply-query.js +180 -127
  95. package/dist/utils/apply-snapshot.js +32 -13
  96. package/dist/utils/dynamic-import.d.ts +1 -0
  97. package/dist/utils/dynamic-import.js +7 -0
  98. package/dist/utils/get-collection-from-alias.d.ts +6 -0
  99. package/dist/utils/get-collection-from-alias.js +15 -0
  100. package/dist/utils/get-collection-from-alias.test.d.ts +1 -0
  101. package/dist/utils/get-column-path.d.ts +14 -8
  102. package/dist/utils/get-column-path.js +24 -7
  103. package/dist/utils/get-column.d.ts +8 -1
  104. package/dist/utils/get-column.js +10 -3
  105. package/dist/utils/get-config-from-env.js +3 -2
  106. package/dist/utils/get-default-value.d.ts +1 -1
  107. package/dist/utils/get-schema.d.ts +6 -2
  108. package/dist/utils/get-schema.js +1 -1
  109. package/dist/utils/get-snapshot.js +1 -1
  110. package/dist/utils/parse-image-metadata.d.ts +3 -0
  111. package/dist/utils/parse-image-metadata.js +73 -0
  112. package/dist/utils/track.js +2 -2
  113. package/dist/utils/validate-env.js +3 -2
  114. package/dist/webhooks.js +2 -2
  115. package/package.json +18 -22
  116. package/dist/storage.d.ts +0 -3
  117. package/dist/storage.js +0 -61
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { StorageManager } from '@directus/storage';
2
+ export declare const registerDrivers: (storage: StorageManager) => Promise<void>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDrivers = void 0;
4
+ const env_1 = require("../env");
5
+ const get_storage_driver_1 = require("./get-storage-driver");
6
+ const registerDrivers = async (storage) => {
7
+ const env = (0, env_1.getEnv)();
8
+ const usedDrivers = [];
9
+ for (const [key, value] of Object.entries(env)) {
10
+ if ((key.startsWith('STORAGE_') && key.endsWith('_DRIVER')) === false)
11
+ continue;
12
+ if (value && usedDrivers.includes(value) === false)
13
+ usedDrivers.push(value);
14
+ }
15
+ for (const driverName of usedDrivers) {
16
+ const storageDriver = await (0, get_storage_driver_1.getStorageDriver)(driverName);
17
+ if (storageDriver) {
18
+ storage.registerDriver(driverName, storageDriver);
19
+ }
20
+ }
21
+ };
22
+ exports.registerDrivers = registerDrivers;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { StorageManager } from '@directus/storage';
2
+ export declare const registerLocations: (storage: StorageManager) => Promise<void>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerLocations = void 0;
4
+ const utils_1 = require("@directus/shared/utils");
5
+ const env_1 = require("../env");
6
+ const get_config_from_env_1 = require("../utils/get-config-from-env");
7
+ const registerLocations = async (storage) => {
8
+ const env = (0, env_1.getEnv)();
9
+ const locations = (0, utils_1.toArray)(env.STORAGE_LOCATIONS);
10
+ locations.forEach((location) => {
11
+ location = location.trim();
12
+ const driverConfig = (0, get_config_from_env_1.getConfigFromEnv)(`STORAGE_${location.toUpperCase()}_`);
13
+ const { driver, ...options } = driverConfig;
14
+ storage.registerLocation(location, { driver, options });
15
+ });
16
+ };
17
+ exports.registerLocations = registerLocations;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,10 +1,34 @@
1
1
  import { Aggregate, Filter, Query, SchemaOverview } from '@directus/shared/types';
2
2
  import { Knex } from 'knex';
3
+ import { AliasMap } from './get-column-path';
4
+ export declare const generateAlias: () => string;
3
5
  /**
4
6
  * Apply the Query to a given Knex query builder instance
5
7
  */
6
- export default function applyQuery(knex: Knex, collection: string, dbQuery: Knex.QueryBuilder, query: Query, schema: SchemaOverview, subQuery?: boolean): Knex.QueryBuilder;
7
- export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, rootSort: string[], collection: string, subQuery?: boolean): void;
8
- export declare function applyFilter(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, rootFilter: Filter, collection: string, subQuery?: boolean): Knex.QueryBuilder<any, any>;
8
+ export default function applyQuery(knex: Knex, collection: string, dbQuery: Knex.QueryBuilder, query: Query, schema: SchemaOverview, options?: {
9
+ aliasMap?: AliasMap;
10
+ isInnerQuery?: boolean;
11
+ hasMultiRelationalSort?: boolean;
12
+ }): {
13
+ query: Knex.QueryBuilder<any, any>;
14
+ hasMultiRelationalFilter: boolean;
15
+ };
16
+ export type ColumnSortRecord = {
17
+ order: 'asc' | 'desc';
18
+ column: string;
19
+ };
20
+ export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, rootSort: string[], collection: string, aliasMap: AliasMap, returnRecords?: boolean): {
21
+ sortRecords: {
22
+ order: "asc" | "desc";
23
+ column: any;
24
+ }[];
25
+ hasMultiRelationalSort: boolean;
26
+ } | undefined;
27
+ export declare function applyLimit(rootQuery: Knex.QueryBuilder, limit: any): void;
28
+ export declare function applyOffset(knex: Knex, rootQuery: Knex.QueryBuilder, offset: any): void;
29
+ export declare function applyFilter(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, rootFilter: Filter, collection: string, aliasMap: AliasMap): {
30
+ query: Knex.QueryBuilder<any, any>;
31
+ hasMultiRelationalFilter: boolean;
32
+ };
9
33
  export declare function applySearch(schema: SchemaOverview, dbQuery: Knex.QueryBuilder, searchQuery: string, collection: string): Promise<void>;
10
34
  export declare function applyAggregate(dbQuery: Knex.QueryBuilder, aggregate: Aggregate, collection: string): void;
@@ -3,9 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.applySort = void 0;
6
+ exports.applyAggregate = exports.applySearch = exports.applyFilter = exports.applyOffset = exports.applyLimit = exports.applySort = exports.generateAlias = void 0;
7
7
  const lodash_1 = require("lodash");
8
- const nanoid_1 = require("nanoid");
9
8
  const uuid_validate_1 = __importDefault(require("uuid-validate"));
10
9
  const helpers_1 = require("../database/helpers");
11
10
  const invalid_query_1 = require("../exceptions/invalid-query");
@@ -14,19 +13,24 @@ const get_column_path_1 = require("./get-column-path");
14
13
  const get_relation_info_1 = require("./get-relation-info");
15
14
  const utils_1 = require("@directus/shared/utils");
16
15
  const strip_function_1 = require("./strip-function");
17
- const generateAlias = (0, nanoid_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
16
+ // @ts-ignore
17
+ const non_secure_1 = require("nanoid/non-secure");
18
+ exports.generateAlias = (0, non_secure_1.customAlphabet)('abcdefghijklmnopqrstuvwxyz', 5);
18
19
  /**
19
20
  * Apply the Query to a given Knex query builder instance
20
21
  */
21
- function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false) {
22
- if (query.sort) {
23
- applySort(knex, schema, dbQuery, query.sort, collection, subQuery);
22
+ function applyQuery(knex, collection, dbQuery, query, schema, options) {
23
+ var _a;
24
+ const aliasMap = (_a = options === null || options === void 0 ? void 0 : options.aliasMap) !== null && _a !== void 0 ? _a : Object.create(null);
25
+ let hasMultiRelationalFilter = false;
26
+ if (query.sort && !(options === null || options === void 0 ? void 0 : options.isInnerQuery) && !(options === null || options === void 0 ? void 0 : options.hasMultiRelationalSort)) {
27
+ applySort(knex, schema, dbQuery, query.sort, collection, aliasMap);
24
28
  }
25
- if (typeof query.limit === 'number' && query.limit !== -1) {
26
- dbQuery.limit(query.limit);
29
+ if (!(options === null || options === void 0 ? void 0 : options.hasMultiRelationalSort)) {
30
+ applyLimit(dbQuery, query.limit);
27
31
  }
28
32
  if (query.offset) {
29
- dbQuery.offset(query.offset);
33
+ applyOffset(knex, dbQuery, query.offset);
30
34
  }
31
35
  if (query.page && query.limit && query.limit !== -1) {
32
36
  dbQuery.offset(query.limit * (query.page - 1));
@@ -41,15 +45,18 @@ function applyQuery(knex, collection, dbQuery, query, schema, subQuery = false)
41
45
  applyAggregate(dbQuery, query.aggregate, collection);
42
46
  }
43
47
  if (query.filter) {
44
- applyFilter(knex, schema, dbQuery, query.filter, collection, subQuery);
48
+ hasMultiRelationalFilter = applyFilter(knex, schema, dbQuery, query.filter, collection, aliasMap).hasMultiRelationalFilter;
45
49
  }
46
- return dbQuery;
50
+ return { query: dbQuery, hasMultiRelationalFilter };
47
51
  }
48
52
  exports.default = applyQuery;
49
- function addJoin({ path, collection, aliasMap, rootQuery, subQuery, schema, relations, knex }) {
53
+ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, knex }) {
54
+ let hasMultiRelational = false;
50
55
  path = (0, lodash_1.clone)(path);
51
56
  followRelation(path);
52
- function followRelation(pathParts, parentCollection = collection, parentAlias) {
57
+ return hasMultiRelational;
58
+ function followRelation(pathParts, parentCollection = collection, parentFields) {
59
+ var _a, _b, _c;
53
60
  /**
54
61
  * For A2M fields, the path can contain an optional collection scope <field>:<scope>
55
62
  */
@@ -58,104 +65,135 @@ function addJoin({ path, collection, aliasMap, rootQuery, subQuery, schema, rela
58
65
  if (!relation) {
59
66
  return;
60
67
  }
61
- const alias = generateAlias();
62
- (0, lodash_1.set)(aliasMap, parentAlias ? [parentAlias, ...pathParts] : pathParts, alias);
63
- if (relationType === 'm2o') {
64
- rootQuery.leftJoin({ [alias]: relation.related_collection }, `${parentAlias || parentCollection}.${relation.field}`, `${alias}.${schema.collections[relation.related_collection].primary}`);
65
- }
66
- if (relationType === 'a2o') {
67
- const pathScope = pathParts[0].split(':')[1];
68
- if (!pathScope) {
69
- throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
70
- }
71
- rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
72
- joinClause
73
- .onVal(relation.meta.one_collection_field, '=', pathScope)
74
- .andOn(`${parentAlias || parentCollection}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${alias}.${schema.collections[pathScope].primary}`));
75
- });
76
- }
77
- if (relationType === 'o2a') {
78
- rootQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
79
- joinClause
80
- .onVal(relation.meta.one_collection_field, '=', parentCollection)
81
- .andOn(`${alias}.${relation.field}`, '=', knex.raw(`CAST(?? AS CHAR(255))`, `${parentAlias || parentCollection}.${schema.collections[parentCollection].primary}`));
82
- });
83
- }
84
- // Still join o2m relations when in subquery OR when the o2m relation is not at the root level
85
- if (relationType === 'o2m' && (subQuery === true || parentAlias !== undefined)) {
86
- rootQuery.leftJoin({ [alias]: relation.collection }, `${parentAlias || parentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
87
- }
88
- if (relationType === 'm2o' || subQuery === true || (relationType === 'o2m' && parentAlias !== undefined)) {
89
- let parent;
68
+ const existingAlias = parentFields
69
+ ? (_a = aliasMap[`${parentFields}.${pathParts[0]}`]) === null || _a === void 0 ? void 0 : _a.alias
70
+ : (_b = aliasMap[pathParts[0]]) === null || _b === void 0 ? void 0 : _b.alias;
71
+ if (!existingAlias) {
72
+ const alias = (0, exports.generateAlias)();
73
+ const aliasKey = parentFields ? `${parentFields}.${pathParts[0]}` : pathParts[0];
74
+ const aliasedParentCollection = ((_c = aliasMap[parentFields !== null && parentFields !== void 0 ? parentFields : '']) === null || _c === void 0 ? void 0 : _c.alias) || parentCollection;
75
+ aliasMap[aliasKey] = { alias, collection: '' };
90
76
  if (relationType === 'm2o') {
91
- parent = relation.related_collection;
77
+ rootQuery.leftJoin({ [alias]: relation.related_collection }, `${aliasedParentCollection}.${relation.field}`, `${alias}.${schema.collections[relation.related_collection].primary}`);
78
+ aliasMap[aliasKey].collection = relation.related_collection;
92
79
  }
93
80
  else if (relationType === 'a2o') {
94
81
  const pathScope = pathParts[0].split(':')[1];
95
82
  if (!pathScope) {
96
83
  throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
97
84
  }
98
- parent = pathScope;
85
+ rootQuery.leftJoin({ [alias]: pathScope }, (joinClause) => {
86
+ joinClause
87
+ .onVal(relation.meta.one_collection_field, '=', pathScope)
88
+ .andOn(`${aliasedParentCollection}.${relation.field}`, '=', knex.raw((0, helpers_1.getHelpers)(knex).schema.castA2oPrimaryKey(), `${alias}.${schema.collections[pathScope].primary}`));
89
+ });
90
+ aliasMap[aliasKey].collection = pathScope;
99
91
  }
100
- else {
101
- parent = relation.collection;
92
+ else if (relationType === 'o2a') {
93
+ rootQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
94
+ joinClause
95
+ .onVal(relation.meta.one_collection_field, '=', parentCollection)
96
+ .andOn(`${alias}.${relation.field}`, '=', knex.raw((0, helpers_1.getHelpers)(knex).schema.castA2oPrimaryKey(), `${aliasedParentCollection}.${schema.collections[parentCollection].primary}`));
97
+ });
98
+ aliasMap[aliasKey].collection = relation.collection;
99
+ hasMultiRelational = true;
102
100
  }
103
- pathParts.shift();
104
- if (pathParts.length) {
105
- followRelation(pathParts, parent, alias);
101
+ else if (relationType === 'o2m') {
102
+ rootQuery.leftJoin({ [alias]: relation.collection }, `${aliasedParentCollection}.${schema.collections[relation.related_collection].primary}`, `${alias}.${relation.field}`);
103
+ aliasMap[aliasKey].collection = relation.collection;
104
+ hasMultiRelational = true;
106
105
  }
107
106
  }
107
+ let parent;
108
+ if (relationType === 'm2o') {
109
+ parent = relation.related_collection;
110
+ }
111
+ else if (relationType === 'a2o') {
112
+ const pathScope = pathParts[0].split(':')[1];
113
+ if (!pathScope) {
114
+ throw new invalid_query_1.InvalidQueryException(`You have to provide a collection scope when sorting or filtering on a many-to-any item`);
115
+ }
116
+ parent = pathScope;
117
+ }
118
+ else {
119
+ parent = relation.collection;
120
+ }
121
+ if (pathParts.length > 1) {
122
+ followRelation(pathParts.slice(1), parent, `${parentFields ? parentFields + '.' : ''}${pathParts[0]}`);
123
+ }
108
124
  }
109
125
  }
110
- function applySort(knex, schema, rootQuery, rootSort, collection, subQuery = false) {
126
+ function applySort(knex, schema, rootQuery, rootSort, collection, aliasMap, returnRecords = false) {
111
127
  const relations = schema.relations;
112
- const aliasMap = {};
113
- rootQuery.orderBy(rootSort.map((sortField) => {
128
+ let hasMultiRelationalSort = false;
129
+ const sortRecords = rootSort.map((sortField) => {
114
130
  const column = sortField.split('.');
115
131
  let order = 'asc';
116
- if (column.length > 1) {
117
- if (sortField.startsWith('-')) {
118
- order = 'desc';
119
- }
120
- if (column[0].startsWith('-')) {
121
- column[0] = column[0].substring(1);
122
- }
123
- addJoin({
124
- path: column,
125
- collection,
126
- aliasMap,
127
- rootQuery,
128
- subQuery,
129
- schema,
130
- relations,
131
- knex,
132
- });
133
- const { columnPath } = (0, get_column_path_1.getColumnPath)({ path: column, collection, aliasMap, relations });
134
- const [alias, field] = columnPath.split('.');
135
- return {
136
- order,
137
- column: (0, get_column_1.getColumn)(knex, alias, field, false, schema),
138
- };
139
- }
140
- let col = column[0];
141
132
  if (sortField.startsWith('-')) {
142
- col = column[0].substring(1);
143
133
  order = 'desc';
144
134
  }
135
+ if (column[0].startsWith('-')) {
136
+ column[0] = column[0].substring(1);
137
+ }
138
+ if (column.length === 1) {
139
+ const pathRoot = column[0].split(':')[0];
140
+ const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, collection, pathRoot);
141
+ if (!relation || ['m2o', 'a2o'].includes(relationType !== null && relationType !== void 0 ? relationType : '')) {
142
+ return {
143
+ order,
144
+ column: returnRecords ? column[0] : (0, get_column_1.getColumn)(knex, collection, column[0], false, schema),
145
+ };
146
+ }
147
+ }
148
+ const hasMultiRelational = addJoin({
149
+ path: column,
150
+ collection,
151
+ aliasMap,
152
+ rootQuery,
153
+ schema,
154
+ relations,
155
+ knex,
156
+ });
157
+ const { columnPath } = (0, get_column_path_1.getColumnPath)({
158
+ path: column,
159
+ collection,
160
+ aliasMap,
161
+ relations,
162
+ schema,
163
+ });
164
+ const [alias, field] = columnPath.split('.');
165
+ if (!hasMultiRelationalSort) {
166
+ hasMultiRelationalSort = hasMultiRelational;
167
+ }
145
168
  return {
146
169
  order,
147
- column: (0, get_column_1.getColumn)(knex, collection, col, false, schema),
170
+ column: returnRecords ? columnPath : (0, get_column_1.getColumn)(knex, alias, field, false, schema),
148
171
  };
149
- }));
172
+ });
173
+ if (returnRecords)
174
+ return { sortRecords, hasMultiRelationalSort };
175
+ rootQuery.orderBy(sortRecords);
150
176
  }
151
177
  exports.applySort = applySort;
152
- function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery = false) {
178
+ function applyLimit(rootQuery, limit) {
179
+ if (typeof limit === 'number' && limit !== -1) {
180
+ rootQuery.limit(limit);
181
+ }
182
+ }
183
+ exports.applyLimit = applyLimit;
184
+ function applyOffset(knex, rootQuery, offset) {
185
+ if (typeof offset === 'number') {
186
+ (0, helpers_1.getHelpers)(knex).schema.applyOffset(rootQuery, offset);
187
+ }
188
+ }
189
+ exports.applyOffset = applyOffset;
190
+ function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap) {
153
191
  const helpers = (0, helpers_1.getHelpers)(knex);
154
192
  const relations = schema.relations;
155
- const aliasMap = {};
193
+ let hasMultiRelationalFilter = false;
156
194
  addJoins(rootQuery, rootFilter, collection);
157
195
  addWhereClauses(knex, rootQuery, rootFilter, collection);
158
- return rootQuery;
196
+ return { query: rootQuery, hasMultiRelationalFilter };
159
197
  function addJoins(dbQuery, filter, collection) {
160
198
  for (const [key, value] of Object.entries(filter)) {
161
199
  if (key === '_or' || key === '_and') {
@@ -169,22 +207,25 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
169
207
  continue;
170
208
  }
171
209
  const filterPath = getFilterPath(key, value);
172
- if (filterPath.length > 1) {
173
- addJoin({
210
+ if (filterPath.length > 1 ||
211
+ (!(key.includes('(') && key.includes(')')) && schema.collections[collection].fields[key].type === 'alias')) {
212
+ const hasMultiRelational = addJoin({
174
213
  path: filterPath,
175
214
  collection,
176
215
  knex,
177
216
  schema,
178
217
  relations,
179
- subQuery,
180
218
  rootQuery,
181
219
  aliasMap,
182
220
  });
221
+ if (!hasMultiRelationalFilter) {
222
+ hasMultiRelationalFilter = hasMultiRelational;
223
+ }
183
224
  }
184
225
  }
185
226
  }
186
227
  function addWhereClauses(knex, dbQuery, filter, collection, logical = 'and') {
187
- var _a, _b;
228
+ var _a;
188
229
  for (const [key, value] of Object.entries(filter)) {
189
230
  if (key === '_or' || key === '_and') {
190
231
  // If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
@@ -207,47 +248,58 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
207
248
  const pathRoot = filterPath[0].split(':')[0];
208
249
  const { relation, relationType } = (0, get_relation_info_1.getRelationInfo)(relations, collection, pathRoot);
209
250
  const { operator: filterOperator, value: filterValue } = getOperation(key, value);
210
- if (relationType === 'm2o' || relationType === 'a2o' || relationType === null) {
211
- if (filterPath.length > 1) {
212
- const { columnPath, targetCollection } = (0, get_column_path_1.getColumnPath)({ path: filterPath, collection, relations, aliasMap });
213
- if (!columnPath)
214
- continue;
215
- const { type, special } = validateFilterField(schema.collections[targetCollection].fields, (0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1]), targetCollection);
216
- validateFilterOperator(type, filterOperator, special);
217
- applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
218
- }
219
- else {
220
- const { type, special } = validateFilterField(schema.collections[collection].fields, (0, strip_function_1.stripFunction)(filterPath[0]), collection);
221
- validateFilterOperator(type, filterOperator, special);
222
- applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
223
- }
224
- }
225
- else if (subQuery === false || filterPath.length > 1) {
251
+ if (filterPath.length > 1 ||
252
+ (!(key.includes('(') && key.includes(')')) && schema.collections[collection].fields[key].type === 'alias')) {
226
253
  if (!relation)
227
254
  continue;
228
- let pkField = `${collection}.${schema.collections[relation.related_collection].primary}`;
229
- if (relationType === 'o2a') {
230
- pkField = knex.raw(`CAST(?? AS CHAR(255))`, [pkField]);
231
- }
232
- const subQueryBuilder = (filter) => (subQueryKnex) => {
233
- const field = relation.field;
234
- const collection = relation.collection;
235
- const column = `${collection}.${field}`;
236
- subQueryKnex
237
- .select({ [field]: column })
238
- .from(collection)
239
- .whereNotNull(column);
240
- applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
241
- };
242
- if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
243
- dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
255
+ if (relationType === 'o2m' || relationType === 'o2a') {
256
+ let pkField = `${collection}.${schema.collections[relation.related_collection].primary}`;
257
+ if (relationType === 'o2a') {
258
+ pkField = knex.raw((0, helpers_1.getHelpers)(knex).schema.castA2oPrimaryKey(), [pkField]);
259
+ }
260
+ const subQueryBuilder = (filter) => (subQueryKnex) => {
261
+ const field = relation.field;
262
+ const collection = relation.collection;
263
+ const column = `${collection}.${field}`;
264
+ subQueryKnex
265
+ .select({ [field]: column })
266
+ .from(collection)
267
+ .whereNotNull(column);
268
+ applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema);
269
+ };
270
+ const childKey = (_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0];
271
+ if (childKey === '_none') {
272
+ dbQuery[logical].whereNotIn(pkField, subQueryBuilder(Object.values(value)[0]));
273
+ continue;
274
+ }
275
+ else if (childKey === '_some') {
276
+ dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
277
+ continue;
278
+ }
244
279
  }
245
- else if (((_b = Object.keys(value)) === null || _b === void 0 ? void 0 : _b[0]) === '_some') {
246
- dbQuery[logical].whereIn(pkField, subQueryBuilder(Object.values(value)[0]));
280
+ if (filterPath.includes('_none') || filterPath.includes('_some')) {
281
+ throw new invalid_query_1.InvalidQueryException(`"${filterPath.includes('_none') ? '_none' : '_some'}" can only be used with top level relational alias field`);
247
282
  }
248
- else {
249
- dbQuery[logical].whereIn(pkField, subQueryBuilder(value));
283
+ const { columnPath, targetCollection, addNestedPkField } = (0, get_column_path_1.getColumnPath)({
284
+ path: filterPath,
285
+ collection,
286
+ relations,
287
+ aliasMap,
288
+ schema,
289
+ });
290
+ if (addNestedPkField) {
291
+ filterPath.push(addNestedPkField);
250
292
  }
293
+ if (!columnPath)
294
+ continue;
295
+ const { type, special } = validateFilterField(schema.collections[targetCollection].fields, (0, strip_function_1.stripFunction)(filterPath[filterPath.length - 1]), targetCollection);
296
+ validateFilterOperator(type, filterOperator, special);
297
+ applyFilterToQuery(columnPath, filterOperator, filterValue, logical, targetCollection);
298
+ }
299
+ else {
300
+ const { type, special } = validateFilterField(schema.collections[collection].fields, (0, strip_function_1.stripFunction)(filterPath[0]), collection);
301
+ validateFilterOperator(type, filterOperator, special);
302
+ applyFilterToQuery(`${collection}.${filterPath[0]}`, filterOperator, filterValue, logical);
251
303
  }
252
304
  }
253
305
  function validateFilterField(fields, key, collection = 'unknown') {
@@ -271,7 +323,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
271
323
  function applyFilterToQuery(key, operator, compareValue, logical = 'and', originalCollectionName) {
272
324
  const [table, column] = key.split('.');
273
325
  // Is processed through Knex.Raw, so should be safe to string-inject into these where queries
274
- const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema);
326
+ const selectionRaw = (0, get_column_1.getColumn)(knex, table, column, false, schema, { originalCollectionName });
275
327
  // Knex supports "raw" in the columnName parameter, but isn't typed as such. Too bad..
276
328
  // See https://github.com/knex/knex/issues/4518 @TODO remove as any once knex is updated
277
329
  // These operators don't rely on a value, and can thus be used without one (eg `?filter[field][_null]`)
@@ -498,16 +550,17 @@ function applyAggregate(dbQuery, aggregate, collection) {
498
550
  exports.applyAggregate = applyAggregate;
499
551
  function getFilterPath(key, value) {
500
552
  const path = [key];
501
- if (typeof Object.keys(value)[0] === 'string' && Object.keys(value)[0].startsWith('_') === true) {
553
+ const childKey = Object.keys(value)[0];
554
+ if (typeof childKey === 'string' && childKey.startsWith('_') === true && !['_none', '_some'].includes(childKey)) {
502
555
  return path;
503
556
  }
504
557
  if ((0, lodash_1.isPlainObject)(value)) {
505
- path.push(...getFilterPath(Object.keys(value)[0], Object.values(value)[0]));
558
+ path.push(...getFilterPath(childKey, Object.values(value)[0]));
506
559
  }
507
560
  return path;
508
561
  }
509
562
  function getOperation(key, value) {
510
- if (key.startsWith('_') && key !== '_and' && key !== '_or') {
563
+ if (key.startsWith('_') && !['_and', '_or', '_none', '_some'].includes(key)) {
511
564
  return { operator: key, value };
512
565
  }
513
566
  else if ((0, lodash_1.isPlainObject)(value) === false) {