directus 9.21.2 → 9.22.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 (103) hide show
  1. package/dist/app.js +9 -5
  2. package/dist/app.test.d.ts +1 -0
  3. package/dist/cli/commands/bootstrap/index.js +2 -2
  4. package/dist/cli/commands/security/secret.js +2 -2
  5. package/dist/cli/utils/create-env/env-stub.liquid +3 -3
  6. package/dist/cli/utils/create-env/index.js +5 -8
  7. package/dist/constants.d.ts +1 -0
  8. package/dist/constants.js +2 -1
  9. package/dist/controllers/assets.js +9 -7
  10. package/dist/controllers/files.js +2 -1
  11. package/dist/controllers/utils.js +2 -2
  12. package/dist/database/helpers/fn/dialects/mssql.js +2 -1
  13. package/dist/database/helpers/fn/dialects/mysql.js +2 -1
  14. package/dist/database/helpers/fn/dialects/oracle.js +2 -1
  15. package/dist/database/helpers/fn/dialects/postgres.js +2 -1
  16. package/dist/database/helpers/fn/dialects/sqlite.js +2 -1
  17. package/dist/database/helpers/fn/types.d.ts +1 -0
  18. package/dist/database/helpers/fn/types.js +5 -4
  19. package/dist/database/helpers/index.d.ts +1 -1
  20. package/dist/database/helpers/schema/dialects/mssql.d.ts +6 -0
  21. package/dist/database/helpers/schema/dialects/mssql.js +14 -0
  22. package/dist/database/helpers/schema/dialects/mysql.d.ts +5 -0
  23. package/dist/database/helpers/schema/dialects/mysql.js +19 -0
  24. package/dist/database/helpers/schema/dialects/oracle.d.ts +1 -0
  25. package/dist/database/helpers/schema/dialects/oracle.js +3 -0
  26. package/dist/database/helpers/schema/index.d.ts +2 -2
  27. package/dist/database/helpers/schema/index.js +4 -4
  28. package/dist/database/helpers/schema/types.d.ts +5 -0
  29. package/dist/database/helpers/schema/types.js +13 -0
  30. package/dist/database/index.d.ts +6 -0
  31. package/dist/database/index.js +20 -1
  32. package/dist/database/migrations/20211007A-update-presets.js +2 -2
  33. package/dist/database/migrations/run.js +7 -31
  34. package/dist/database/run-ast.js +132 -6
  35. package/dist/database/system-data/fields/index.js +2 -1
  36. package/dist/env.js +3 -2
  37. package/dist/exceptions/range-not-satisfiable.d.ts +1 -1
  38. package/dist/extensions.d.ts +7 -1
  39. package/dist/extensions.js +41 -15
  40. package/dist/logger.js +27 -1
  41. package/dist/operations/request/index.d.ts +1 -2
  42. package/dist/operations/request/index.js +2 -2
  43. package/dist/services/assets.d.ts +4 -3
  44. package/dist/services/assets.js +13 -11
  45. package/dist/services/authentication.js +4 -3
  46. package/dist/services/authorization.js +1 -1
  47. package/dist/services/files.d.ts +4 -3
  48. package/dist/services/files.js +92 -68
  49. package/dist/services/flows.d.ts +0 -2
  50. package/dist/services/flows.js +0 -14
  51. package/dist/services/flows.test.d.ts +1 -0
  52. package/dist/services/graphql/index.d.ts +5 -1
  53. package/dist/services/graphql/index.js +29 -31
  54. package/dist/services/import-export.d.ts +4 -3
  55. package/dist/services/items.js +7 -1
  56. package/dist/services/meta.js +2 -2
  57. package/dist/services/operations.d.ts +0 -2
  58. package/dist/services/operations.js +0 -12
  59. package/dist/services/operations.test.d.ts +1 -0
  60. package/dist/services/permissions.d.ts +0 -5
  61. package/dist/services/permissions.js +0 -25
  62. package/dist/services/permissions.test.d.ts +1 -0
  63. package/dist/services/roles.js +0 -3
  64. package/dist/services/roles.test.d.ts +1 -0
  65. package/dist/services/server.js +8 -6
  66. package/dist/services/shares.js +2 -2
  67. package/dist/services/specifications.js +12 -1
  68. package/dist/services/webhooks.d.ts +0 -2
  69. package/dist/services/webhooks.js +0 -10
  70. package/dist/services/webhooks.test.d.ts +1 -0
  71. package/dist/storage/get-storage-driver.d.ts +3 -0
  72. package/dist/storage/get-storage-driver.js +20 -0
  73. package/dist/storage/get-storage-driver.test.d.ts +1 -0
  74. package/dist/storage/index.d.ts +5 -0
  75. package/dist/storage/index.js +20 -0
  76. package/dist/storage/index.test.d.ts +1 -0
  77. package/dist/storage/register-drivers.d.ts +2 -0
  78. package/dist/storage/register-drivers.js +22 -0
  79. package/dist/storage/register-drivers.test.d.ts +1 -0
  80. package/dist/storage/register-locations.d.ts +2 -0
  81. package/dist/storage/register-locations.js +17 -0
  82. package/dist/storage/register-locations.test.d.ts +1 -0
  83. package/dist/utils/apply-query.d.ts +27 -3
  84. package/dist/utils/apply-query.js +180 -127
  85. package/dist/utils/dynamic-import.d.ts +1 -0
  86. package/dist/utils/dynamic-import.js +7 -0
  87. package/dist/utils/get-collection-from-alias.d.ts +6 -0
  88. package/dist/utils/get-collection-from-alias.js +15 -0
  89. package/dist/utils/get-collection-from-alias.test.d.ts +1 -0
  90. package/dist/utils/get-column-path.d.ts +14 -8
  91. package/dist/utils/get-column-path.js +24 -7
  92. package/dist/utils/get-column.d.ts +8 -1
  93. package/dist/utils/get-column.js +10 -3
  94. package/dist/utils/get-config-from-env.js +3 -2
  95. package/dist/utils/get-default-value.d.ts +1 -1
  96. package/dist/utils/parse-image-metadata.d.ts +3 -0
  97. package/dist/utils/parse-image-metadata.js +73 -0
  98. package/dist/utils/track.js +2 -2
  99. package/dist/utils/validate-env.js +3 -2
  100. package/dist/webhooks.js +2 -2
  101. package/package.json +17 -11
  102. package/dist/storage.d.ts +0 -3
  103. package/dist/storage.js +0 -61
package/dist/app.js CHANGED
@@ -31,7 +31,6 @@ const express_1 = __importDefault(require("express"));
31
31
  const fs_extra_1 = __importDefault(require("fs-extra"));
32
32
  const path_1 = __importDefault(require("path"));
33
33
  const qs_1 = __importDefault(require("qs"));
34
- const helmet_1 = __importDefault(require("helmet"));
35
34
  const activity_1 = __importDefault(require("./controllers/activity"));
36
35
  const assets_1 = __importDefault(require("./controllers/assets"));
37
36
  const auth_1 = __importDefault(require("./controllers/auth"));
@@ -87,6 +86,7 @@ const url_1 = require("./utils/url");
87
86
  const get_config_from_env_1 = require("./utils/get-config-from-env");
88
87
  const lodash_1 = require("lodash");
89
88
  async function createApp() {
89
+ const helmet = await import('helmet');
90
90
  (0, validate_env_1.validateEnv)(['KEY', 'SECRET']);
91
91
  if (!new url_1.Url(env_1.default.PUBLIC_URL).isAbsolute()) {
92
92
  logger_1.default.warn('PUBLIC_URL should be a full URL');
@@ -111,7 +111,7 @@ async function createApp() {
111
111
  app.disable('x-powered-by');
112
112
  app.set('trust proxy', env_1.default.IP_TRUST_PROXY);
113
113
  app.set('query parser', (str) => qs_1.default.parse(str, { depth: 10 }));
114
- app.use(helmet_1.default.contentSecurityPolicy((0, lodash_1.merge)({
114
+ app.use(helmet.contentSecurityPolicy((0, lodash_1.merge)({
115
115
  useDefaults: true,
116
116
  directives: {
117
117
  // Unsafe-eval is required for vue3 / vue-i18n / app extensions
@@ -130,7 +130,7 @@ async function createApp() {
130
130
  },
131
131
  }, (0, get_config_from_env_1.getConfigFromEnv)('CONTENT_SECURITY_POLICY_'))));
132
132
  if (env_1.default.HSTS_ENABLED) {
133
- app.use(helmet_1.default.hsts((0, get_config_from_env_1.getConfigFromEnv)('HSTS_', ['HSTS_ENABLED'])));
133
+ app.use(helmet.hsts((0, get_config_from_env_1.getConfigFromEnv)('HSTS_', ['HSTS_ENABLED'])));
134
134
  }
135
135
  await emitter_1.default.emitInit('app.before', { app });
136
136
  await emitter_1.default.emitInit('middlewares.before', { app });
@@ -170,13 +170,17 @@ async function createApp() {
170
170
  if (env_1.default.SERVE_APP) {
171
171
  const adminPath = require.resolve('@directus/app');
172
172
  const adminUrl = new url_1.Url(env_1.default.PUBLIC_URL).addPath('admin');
173
+ const embeds = extensionManager.getEmbeds();
173
174
  // Set the App's base path according to the APIs public URL
174
175
  const html = await fs_extra_1.default.readFile(adminPath, 'utf8');
175
- const htmlWithBase = html.replace(/<base \/>/, `<base href="${adminUrl.toString({ rootRelative: true })}/" />`);
176
+ const htmlWithVars = html
177
+ .replace(/<base \/>/, `<base href="${adminUrl.toString({ rootRelative: true })}/" />`)
178
+ .replace(/<embed-head \/>/, embeds.head)
179
+ .replace(/<embed-body \/>/, embeds.body);
176
180
  const sendHtml = (_req, res) => {
177
181
  res.setHeader('Cache-Control', 'no-cache');
178
182
  res.setHeader('Vary', 'Origin, Cache-Control');
179
- res.send(htmlWithBase);
183
+ res.send(htmlWithVars);
180
184
  };
181
185
  const setStaticHeaders = (res) => {
182
186
  res.setHeader('Cache-Control', 'max-age=31536000, immutable');
@@ -0,0 +1 @@
1
+ export {};
@@ -26,7 +26,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- const nanoid_1 = require("nanoid");
30
29
  const run_1 = __importDefault(require("../../../database/migrations/run"));
31
30
  const run_2 = __importDefault(require("../../../database/seeds/run"));
32
31
  const env_1 = __importDefault(require("../../../env"));
@@ -78,6 +77,7 @@ async function waitForDatabase(database) {
78
77
  await (0, database_1.validateDatabaseConnection)(database);
79
78
  }
80
79
  async function createDefaultAdmin(schema) {
80
+ const { nanoid } = await import('nanoid');
81
81
  logger_1.default.info('Setting up first admin role...');
82
82
  const rolesService = new services_1.RolesService({ schema });
83
83
  const role = await rolesService.createOne(defaults_1.defaultAdminRole);
@@ -90,7 +90,7 @@ async function createDefaultAdmin(schema) {
90
90
  }
91
91
  let adminPassword = env_1.default.ADMIN_PASSWORD;
92
92
  if (!adminPassword) {
93
- adminPassword = (0, nanoid_1.nanoid)(12);
93
+ adminPassword = nanoid(12);
94
94
  logger_1.default.info(`No admin password provided. Defaulting to "${adminPassword}"`);
95
95
  }
96
96
  await usersService.createOne({ email: adminEmail, password: adminPassword, role, ...defaults_1.defaultAdminUser });
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const nanoid_1 = require("nanoid");
4
3
  async function generateSecret() {
5
- process.stdout.write((0, nanoid_1.nanoid)(32));
4
+ const { nanoid } = await import('nanoid');
5
+ process.stdout.write(nanoid(32));
6
6
  process.exit(0);
7
7
  }
8
8
  exports.default = generateSecret;
@@ -149,8 +149,8 @@ CACHE_ENABLED=false
149
149
  # memory | redis | memcache
150
150
  CACHE_STORE=memory
151
151
 
152
- # How long assets will be cached for in the browser. Sets the max-age value of the Cache-Control header ["30m"]
153
- ASSETS_CACHE_TTL="30m"
152
+ # How long assets will be cached for in the browser. Sets the max-age value of the Cache-Control header ["30d"]
153
+ ASSETS_CACHE_TTL="30d"
154
154
 
155
155
  # CACHE_REDIS="redis://@127.0.0.1:5105"
156
156
  # CACHE_MEMCACHE="localhost:5109"
@@ -227,7 +227,7 @@ CORS_METHODS=GET,POST,PATCH,DELETE
227
227
  # Value for the Access-Control-Allow-Headers header [Content-Type,Authorization]
228
228
  CORS_ALLOWED_HEADERS=Content-Type,Authorization
229
229
 
230
- # Value for the Access-Control-Expose-Headers header [Content-R
230
+ # Value for the Access-Control-Expose-Headers header [Content-Range]
231
231
  CORS_EXPOSED_HEADERS=Content-Range
232
232
 
233
233
  # Whether or not to send the Access-Control-Allow-Credentials header [true]
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const fs_1 = __importDefault(require("fs"));
7
7
  const liquidjs_1 = require("liquidjs");
8
- const nanoid_1 = require("nanoid");
9
8
  const path_1 = __importDefault(require("path"));
10
9
  const util_1 = require("util");
11
10
  const uuid_1 = require("uuid");
@@ -16,15 +15,13 @@ const open = (0, util_1.promisify)(fs_1.default.open);
16
15
  const liquidEngine = new liquidjs_1.Liquid({
17
16
  extname: '.liquid',
18
17
  });
19
- const defaults = {
20
- security: {
21
- KEY: (0, uuid_1.v4)(),
22
- SECRET: (0, nanoid_1.nanoid)(32),
23
- },
24
- };
25
18
  async function createEnv(client, credentials, directory) {
19
+ const { nanoid } = await import('nanoid');
26
20
  const config = {
27
- ...defaults,
21
+ security: {
22
+ KEY: (0, uuid_1.v4)(),
23
+ SECRET: nanoid(32),
24
+ },
28
25
  database: {
29
26
  DB_CLIENT: client,
30
27
  },
@@ -15,3 +15,4 @@ export declare const COOKIE_OPTIONS: {
15
15
  sameSite: "lax" | "strict" | "none";
16
16
  };
17
17
  export declare const ROBOTSTXT: string;
18
+ export declare const OAS_REQUIRED_SCHEMAS: string[];
package/dist/constants.js CHANGED
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  var _a;
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ROBOTSTXT = exports.COOKIE_OPTIONS = exports.UUID_REGEX = exports.GENERATE_SPECIAL = exports.COLUMN_TRANSFORMS = exports.DEFAULT_AUTH_PROVIDER = exports.ALIAS_TYPES = exports.FILTER_VARIABLES = exports.ASSET_TRANSFORM_QUERY_KEYS = exports.SYSTEM_ASSET_ALLOW_LIST = void 0;
7
+ exports.OAS_REQUIRED_SCHEMAS = exports.ROBOTSTXT = exports.COOKIE_OPTIONS = exports.UUID_REGEX = exports.GENERATE_SPECIAL = exports.COLUMN_TRANSFORMS = exports.DEFAULT_AUTH_PROVIDER = exports.ALIAS_TYPES = exports.FILTER_VARIABLES = exports.ASSET_TRANSFORM_QUERY_KEYS = exports.SYSTEM_ASSET_ALLOW_LIST = void 0;
8
8
  const env_1 = __importDefault(require("./env"));
9
9
  const ms_1 = __importDefault(require("ms"));
10
10
  exports.SYSTEM_ASSET_ALLOW_LIST = [
@@ -60,3 +60,4 @@ exports.ROBOTSTXT = `
60
60
  User-agent: *
61
61
  Disallow: /
62
62
  `.trim();
63
+ exports.OAS_REQUIRED_SCHEMAS = ['Query', 'x-metadata'];
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const utils_1 = require("@directus/shared/utils");
7
7
  const express_1 = require("express");
8
- const helmet_1 = __importDefault(require("helmet"));
9
8
  const lodash_1 = require("lodash");
10
9
  const ms_1 = __importDefault(require("ms"));
11
10
  const constants_1 = require("../constants");
@@ -92,12 +91,15 @@ router.get('/:pk/:filename?',
92
91
  return next();
93
92
  throw new exceptions_1.InvalidQueryException(`Dynamic asset generation has been disabled for this project.`);
94
93
  }
95
- }), helmet_1.default.contentSecurityPolicy((0, lodash_1.merge)({
96
- useDefaults: false,
97
- directives: {
98
- defaultSrc: ['none'],
99
- },
100
- }, (0, get_config_from_env_1.getConfigFromEnv)('ASSETS_CONTENT_SECURITY_POLICY'))),
94
+ }), (0, async_handler_1.default)(async (req, res, next) => {
95
+ const helmet = await import('helmet');
96
+ return helmet.contentSecurityPolicy((0, lodash_1.merge)({
97
+ useDefaults: false,
98
+ directives: {
99
+ defaultSrc: ['none'],
100
+ },
101
+ }, (0, get_config_from_env_1.getConfigFromEnv)('ASSETS_CONTENT_SECURITY_POLICY')))(req, res, next);
102
+ }),
101
103
  // Return file
102
104
  (0, async_handler_1.default)(async (req, res) => {
103
105
  var _a, _b, _c;
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.multipartHandler = void 0;
7
- const format_title_1 = __importDefault(require("@directus/format-title"));
8
7
  const utils_1 = require("@directus/shared/utils");
9
8
  const busboy_1 = __importDefault(require("busboy"));
10
9
  const express_1 = __importDefault(require("express"));
@@ -17,6 +16,8 @@ const use_collection_1 = __importDefault(require("../middleware/use-collection")
17
16
  const validate_batch_1 = require("../middleware/validate-batch");
18
17
  const services_1 = require("../services");
19
18
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
19
+ // @ts-ignore
20
+ const format_title_1 = __importDefault(require("@directus/format-title"));
20
21
  const router = express_1.default.Router();
21
22
  router.use((0, use_collection_1.default)('directus_files'));
22
23
  const multipartHandler = (req, res, next) => {
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const argon2_1 = __importDefault(require("argon2"));
7
7
  const express_1 = require("express");
8
8
  const joi_1 = __importDefault(require("joi"));
9
- const nanoid_1 = require("nanoid");
10
9
  const exceptions_1 = require("../exceptions");
11
10
  const collection_exists_1 = __importDefault(require("../middleware/collection-exists"));
12
11
  const respond_1 = require("../middleware/respond");
@@ -18,9 +17,10 @@ const generate_hash_1 = require("../utils/generate-hash");
18
17
  const router = (0, express_1.Router)();
19
18
  router.get('/random/string', (0, async_handler_1.default)(async (req, res) => {
20
19
  var _a;
20
+ const { nanoid } = await import('nanoid');
21
21
  if (req.query && req.query.length && Number(req.query.length) > 500)
22
22
  throw new exceptions_1.InvalidQueryException(`"length" can't be more than 500 characters`);
23
- const string = (0, nanoid_1.nanoid)(((_a = req.query) === null || _a === void 0 ? void 0 : _a.length) ? Number(req.query.length) : 32);
23
+ const string = nanoid(((_a = req.query) === null || _a === void 0 ? void 0 : _a.length) ? Number(req.query.length) : 32);
24
24
  return res.json({ data: string });
25
25
  }));
26
26
  router.post('/hash/generate', (0, async_handler_1.default)(async (req, res) => {
@@ -35,7 +35,8 @@ class FnHelperMSSQL extends types_1.FnHelper {
35
35
  }
36
36
  count(table, column, options) {
37
37
  var _a, _b, _c, _d, _e;
38
- const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[table]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
38
+ const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
39
+ const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[collectionName]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
39
40
  if (type === 'json') {
40
41
  return this.knex.raw(`(SELECT COUNT(*) FROM OPENJSON(??.??, '$'))`, [table, column]);
41
42
  }
@@ -35,7 +35,8 @@ class FnHelperMySQL extends types_1.FnHelper {
35
35
  }
36
36
  count(table, column, options) {
37
37
  var _a, _b, _c, _d, _e;
38
- const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[table]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
38
+ const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
39
+ const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[collectionName]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
39
40
  if (type === 'json') {
40
41
  return this.knex.raw('JSON_LENGTH(??.??)', [table, column]);
41
42
  }
@@ -35,7 +35,8 @@ class FnHelperOracle extends types_1.FnHelper {
35
35
  }
36
36
  count(table, column, options) {
37
37
  var _a, _b, _c, _d, _e;
38
- const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[table]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
38
+ const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
39
+ const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[collectionName]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
39
40
  if (type === 'json') {
40
41
  return this.knex.raw("json_value(??.??, '$.size()')", [table, column]);
41
42
  }
@@ -35,7 +35,8 @@ class FnHelperPostgres extends types_1.FnHelper {
35
35
  }
36
36
  count(table, column, options) {
37
37
  var _a, _b, _c, _d, _e;
38
- const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[table]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
38
+ const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
39
+ const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[collectionName]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
39
40
  if (type === 'json') {
40
41
  const { dbType } = this.schema.collections[table].fields[column];
41
42
  return this.knex.raw(dbType === 'jsonb' ? 'jsonb_array_length(??.??)' : 'json_array_length(??.??)', [
@@ -59,7 +59,8 @@ class FnHelperSQLite extends types_1.FnHelper {
59
59
  }
60
60
  count(table, column, options) {
61
61
  var _a, _b, _c, _d, _e;
62
- const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[table]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
62
+ const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
63
+ const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[collectionName]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
63
64
  if (type === 'json') {
64
65
  return this.knex.raw(`json_array_length(??.??, '$')`, [table, column]);
65
66
  }
@@ -4,6 +4,7 @@ import { DatabaseHelper } from '../types';
4
4
  export type FnHelperOptions = {
5
5
  type?: string;
6
6
  query?: Query;
7
+ originalCollectionName?: string;
7
8
  };
8
9
  export declare abstract class FnHelper extends DatabaseHelper {
9
10
  protected knex: Knex;
@@ -12,17 +12,18 @@ class FnHelper extends types_1.DatabaseHelper {
12
12
  }
13
13
  _relationalCount(table, column, options) {
14
14
  var _a;
15
- const relation = this.schema.relations.find((relation) => { var _a; return relation.related_collection === table && ((_a = relation === null || relation === void 0 ? void 0 : relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === column; });
16
- const currentPrimary = this.schema.collections[table].primary;
15
+ const collectionName = (options === null || options === void 0 ? void 0 : options.originalCollectionName) || table;
16
+ const relation = this.schema.relations.find((relation) => { var _a; return relation.related_collection === collectionName && ((_a = relation === null || relation === void 0 ? void 0 : relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === column; });
17
+ const currentPrimary = this.schema.collections[collectionName].primary;
17
18
  if (!relation) {
18
- throw new Error(`Field ${table}.${column} isn't a nested relational collection`);
19
+ throw new Error(`Field ${collectionName}.${column} isn't a nested relational collection`);
19
20
  }
20
21
  let countQuery = this.knex
21
22
  .count('*')
22
23
  .from(relation.collection)
23
24
  .where(relation.field, '=', this.knex.raw(`??.??`, [table, currentPrimary]));
24
25
  if ((_a = options === null || options === void 0 ? void 0 : options.query) === null || _a === void 0 ? void 0 : _a.filter) {
25
- countQuery = (0, apply_query_1.applyFilter)(this.knex, this.schema, countQuery, options.query.filter, relation.collection, false);
26
+ countQuery = (0, apply_query_1.applyFilter)(this.knex, this.schema, countQuery, options.query.filter, relation.collection, {}).query;
26
27
  }
27
28
  return this.knex.raw('(' + countQuery.toQuery() + ')');
28
29
  }
@@ -7,7 +7,7 @@ import * as schemaHelpers from './schema';
7
7
  export declare function getHelpers(database: Knex): {
8
8
  date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
9
9
  st: geometryHelpers.sqlite | geometryHelpers.postgres | geometryHelpers.mysql | geometryHelpers.oracle | geometryHelpers.mssql | geometryHelpers.redshift;
10
- schema: schemaHelpers.sqlite | schemaHelpers.postgres | schemaHelpers.cockroachdb | schemaHelpers.oracle;
10
+ schema: schemaHelpers.sqlite | schemaHelpers.postgres | schemaHelpers.mysql | schemaHelpers.cockroachdb | schemaHelpers.oracle | schemaHelpers.mssql;
11
11
  };
12
12
  export declare function getFunctions(database: Knex, schema: SchemaOverview): fnHelpers.sqlite | fnHelpers.postgres | fnHelpers.mysql | fnHelpers.oracle | fnHelpers.mssql;
13
13
  export type Helpers = ReturnType<typeof getHelpers>;
@@ -0,0 +1,6 @@
1
+ import { Knex } from 'knex';
2
+ import { SchemaHelper } from '../types';
3
+ export declare class SchemaHelperMSSQL extends SchemaHelper {
4
+ applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void;
5
+ formatUUID(uuid: string): string;
6
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchemaHelperMSSQL = void 0;
4
+ const types_1 = require("../types");
5
+ class SchemaHelperMSSQL extends types_1.SchemaHelper {
6
+ applyOffset(rootQuery, offset) {
7
+ rootQuery.offset(offset);
8
+ rootQuery.orderBy(1);
9
+ }
10
+ formatUUID(uuid) {
11
+ return uuid.toUpperCase();
12
+ }
13
+ }
14
+ exports.SchemaHelperMSSQL = SchemaHelperMSSQL;
@@ -0,0 +1,5 @@
1
+ import { Knex } from 'knex';
2
+ import { SchemaHelper } from '../types';
3
+ export declare class SchemaHelperMySQL extends SchemaHelper {
4
+ applyMultiRelationalSort(knex: Knex, dbQuery: Knex.QueryBuilder, table: string, primaryKey: string, orderByString: string, orderByFields: Knex.Raw[]): Knex.QueryBuilder;
5
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SchemaHelperMySQL = void 0;
4
+ const database_1 = require("../../../../database");
5
+ const types_1 = require("../types");
6
+ class SchemaHelperMySQL extends types_1.SchemaHelper {
7
+ applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields) {
8
+ var _a;
9
+ if ((_a = (0, database_1.getDatabaseVersion)()) === null || _a === void 0 ? void 0 : _a.startsWith('5.7')) {
10
+ dbQuery.orderByRaw(`?? asc, ${orderByString.slice(9)}`, [`${table}.${primaryKey}`, ...orderByFields]);
11
+ dbQuery = knex
12
+ .select(knex.raw(`??, ( @rank := IF ( @cur_id = deep.${primaryKey}, @rank + 1, 1 ) ) AS directus_row_number, ( @cur_id := deep.${primaryKey} ) AS current_id`, 'deep.*'))
13
+ .from(knex.raw('? as ??, (SELECT @rank := 0, @cur_id := null) vars', [dbQuery, 'deep']));
14
+ return dbQuery;
15
+ }
16
+ return super.applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields);
17
+ }
18
+ }
19
+ exports.SchemaHelperMySQL = SchemaHelperMySQL;
@@ -2,4 +2,5 @@ import { KNEX_TYPES } from '@directus/shared/constants';
2
2
  import { Options, SchemaHelper } from '../types';
3
3
  export declare class SchemaHelperOracle extends SchemaHelper {
4
4
  changeToType(table: string, column: string, type: typeof KNEX_TYPES[number], options?: Options): Promise<void>;
5
+ castA2oPrimaryKey(): string;
5
6
  }
@@ -6,5 +6,8 @@ class SchemaHelperOracle extends types_1.SchemaHelper {
6
6
  async changeToType(table, column, type, options = {}) {
7
7
  await this.changeToTypeByCopy(table, column, type, options);
8
8
  }
9
+ castA2oPrimaryKey() {
10
+ return 'CAST(?? AS VARCHAR2(255))';
11
+ }
9
12
  }
10
13
  exports.SchemaHelperOracle = SchemaHelperOracle;
@@ -3,5 +3,5 @@ export { SchemaHelperCockroachDb as cockroachdb } from './dialects/cockroachdb';
3
3
  export { SchemaHelperDefault as redshift } from './dialects/default';
4
4
  export { SchemaHelperOracle as oracle } from './dialects/oracle';
5
5
  export { SchemaHelperSQLite as sqlite } from './dialects/sqlite';
6
- export { SchemaHelperDefault as mysql } from './dialects/default';
7
- export { SchemaHelperDefault as mssql } from './dialects/default';
6
+ export { SchemaHelperMySQL as mysql } from './dialects/mysql';
7
+ export { SchemaHelperMSSQL as mssql } from './dialects/mssql';
@@ -11,7 +11,7 @@ var oracle_1 = require("./dialects/oracle");
11
11
  Object.defineProperty(exports, "oracle", { enumerable: true, get: function () { return oracle_1.SchemaHelperOracle; } });
12
12
  var sqlite_1 = require("./dialects/sqlite");
13
13
  Object.defineProperty(exports, "sqlite", { enumerable: true, get: function () { return sqlite_1.SchemaHelperSQLite; } });
14
- var default_3 = require("./dialects/default");
15
- Object.defineProperty(exports, "mysql", { enumerable: true, get: function () { return default_3.SchemaHelperDefault; } });
16
- var default_4 = require("./dialects/default");
17
- Object.defineProperty(exports, "mssql", { enumerable: true, get: function () { return default_4.SchemaHelperDefault; } });
14
+ var mysql_1 = require("./dialects/mysql");
15
+ Object.defineProperty(exports, "mysql", { enumerable: true, get: function () { return mysql_1.SchemaHelperMySQL; } });
16
+ var mssql_1 = require("./dialects/mssql");
17
+ Object.defineProperty(exports, "mssql", { enumerable: true, get: function () { return mssql_1.SchemaHelperMSSQL; } });
@@ -1,5 +1,6 @@
1
1
  import { DatabaseHelper } from '../types';
2
2
  import { KNEX_TYPES } from '@directus/shared/constants';
3
+ import { Knex } from 'knex';
3
4
  type Clients = 'mysql' | 'postgres' | 'cockroachdb' | 'sqlite' | 'oracle' | 'mssql' | 'redshift';
4
5
  export type Options = {
5
6
  nullable?: boolean;
@@ -14,5 +15,9 @@ export declare abstract class SchemaHelper extends DatabaseHelper {
14
15
  preColumnChange(): Promise<boolean>;
15
16
  postColumnChange(): Promise<void>;
16
17
  constraintName(existingName: string): string;
18
+ applyOffset(rootQuery: Knex.QueryBuilder, offset: number): void;
19
+ castA2oPrimaryKey(): string;
20
+ applyMultiRelationalSort(knex: Knex, dbQuery: Knex.QueryBuilder, table: string, primaryKey: string, orderByString: string, orderByFields: Knex.Raw[]): Knex.QueryBuilder;
21
+ formatUUID(uuid: string): string;
17
22
  }
18
23
  export {};
@@ -67,5 +67,18 @@ class SchemaHelper extends types_1.DatabaseHelper {
67
67
  // reference issue #14873
68
68
  return existingName;
69
69
  }
70
+ applyOffset(rootQuery, offset) {
71
+ rootQuery.offset(offset);
72
+ }
73
+ castA2oPrimaryKey() {
74
+ return 'CAST(?? AS CHAR(255))';
75
+ }
76
+ applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields) {
77
+ dbQuery.rowNumber(knex.ref('directus_row_number').toQuery(), knex.raw(`partition by ??${orderByString}`, [`${table}.${primaryKey}`, ...orderByFields]));
78
+ return dbQuery;
79
+ }
80
+ formatUUID(uuid) {
81
+ return uuid; // no-op by defaut
82
+ }
70
83
  }
71
84
  exports.SchemaHelper = SchemaHelper;
@@ -2,6 +2,12 @@ import SchemaInspector from '@directus/schema';
2
2
  import { Knex } from 'knex';
3
3
  export default function getDatabase(): Knex;
4
4
  export declare function getSchemaInspector(): ReturnType<typeof SchemaInspector>;
5
+ /**
6
+ * Get database version. Value currently exists for MySQL only.
7
+ *
8
+ * @returns Cached database version
9
+ */
10
+ export declare function getDatabaseVersion(): string | null;
5
11
  export declare function hasDatabaseConnection(database?: Knex): Promise<boolean>;
6
12
  export declare function validateDatabaseConnection(database?: Knex): Promise<void>;
7
13
  export declare function getDatabaseClient(database?: Knex): 'mysql' | 'postgres' | 'cockroachdb' | 'sqlite' | 'oracle' | 'mssql' | 'redshift';
@@ -3,7 +3,7 @@ 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.validateDatabaseExtensions = exports.validateMigrations = exports.isInstalled = exports.getDatabaseClient = exports.validateDatabaseConnection = exports.hasDatabaseConnection = exports.getSchemaInspector = void 0;
6
+ exports.validateDatabaseExtensions = exports.validateMigrations = exports.isInstalled = exports.getDatabaseClient = exports.validateDatabaseConnection = exports.hasDatabaseConnection = exports.getDatabaseVersion = exports.getSchemaInspector = void 0;
7
7
  const schema_1 = __importDefault(require("@directus/schema"));
8
8
  const knex_1 = require("knex");
9
9
  const perf_hooks_1 = require("perf_hooks");
@@ -18,6 +18,7 @@ const util_1 = require("util");
18
18
  const helpers_1 = require("./helpers");
19
19
  let database = null;
20
20
  let inspector = null;
21
+ let databaseVersion = null;
21
22
  function getDatabase() {
22
23
  if (database) {
23
24
  return database;
@@ -95,6 +96,15 @@ function getDatabase() {
95
96
  callback(null, conn);
96
97
  };
97
98
  }
99
+ if (client === 'mysql') {
100
+ poolConfig.afterCreate = async (conn, callback) => {
101
+ logger_1.default.trace('Retrieving database version');
102
+ const run = (0, util_1.promisify)(conn.query.bind(conn));
103
+ const version = await run('SELECT @@version;');
104
+ databaseVersion = version[0]['@@version'];
105
+ callback(null, conn);
106
+ };
107
+ }
98
108
  if (client === 'mssql') {
99
109
  // This brings MS SQL in line with the other DB vendors. We shouldn't do any automatic
100
110
  // timezone conversion on the database level, especially not when other database vendors don't
@@ -125,6 +135,15 @@ function getSchemaInspector() {
125
135
  return inspector;
126
136
  }
127
137
  exports.getSchemaInspector = getSchemaInspector;
138
+ /**
139
+ * Get database version. Value currently exists for MySQL only.
140
+ *
141
+ * @returns Cached database version
142
+ */
143
+ function getDatabaseVersion() {
144
+ return databaseVersion;
145
+ }
146
+ exports.getDatabaseVersion = getDatabaseVersion;
128
147
  async function hasDatabaseConnection(database) {
129
148
  database = database !== null && database !== void 0 ? database : getDatabase();
130
149
  try {
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.down = exports.up = void 0;
4
4
  const utils_1 = require("@directus/shared/utils");
5
- const nanoid_1 = require("nanoid");
6
5
  async function up(knex) {
7
6
  var _a;
8
7
  await knex.schema.alterTable('directus_presets', (table) => {
@@ -54,6 +53,7 @@ async function up(knex) {
54
53
  exports.up = up;
55
54
  async function down(knex) {
56
55
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
56
+ const { nanoid } = await import('nanoid');
57
57
  await knex.schema.alterTable('directus_presets', (table) => {
58
58
  table.json('filters');
59
59
  });
@@ -73,7 +73,7 @@ async function down(knex) {
73
73
  if (!field || !operator || !value)
74
74
  continue;
75
75
  oldFilters.push({
76
- key: (0, nanoid_1.nanoid)(),
76
+ key: nanoid(),
77
77
  field,
78
78
  operator: operator.substring(1),
79
79
  value,