@directus/api 27.0.2 → 28.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/auth/drivers/oauth2.js +4 -4
  2. package/dist/auth/drivers/openid.d.ts +2 -1
  3. package/dist/auth/drivers/openid.js +78 -43
  4. package/dist/database/errors/dialects/mssql.d.ts +2 -1
  5. package/dist/database/errors/dialects/mssql.js +124 -120
  6. package/dist/database/errors/dialects/mysql.d.ts +2 -1
  7. package/dist/database/errors/dialects/mysql.js +112 -108
  8. package/dist/database/errors/dialects/postgres.d.ts +2 -1
  9. package/dist/database/errors/dialects/postgres.js +75 -71
  10. package/dist/database/errors/dialects/sqlite.d.ts +2 -1
  11. package/dist/database/errors/dialects/sqlite.js +6 -5
  12. package/dist/database/errors/translate.d.ts +2 -1
  13. package/dist/database/errors/translate.js +5 -5
  14. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +5 -3
  15. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +26 -16
  16. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +2 -1
  17. package/dist/database/get-ast-from-query/lib/parse-fields.js +5 -4
  18. package/dist/database/migrations/20250609A-license-banner.d.ts +3 -0
  19. package/dist/database/migrations/20250609A-license-banner.js +14 -0
  20. package/dist/database/migrations/20250613A-add-project-id.d.ts +3 -0
  21. package/dist/database/migrations/20250613A-add-project-id.js +26 -0
  22. package/dist/extensions/lib/get-extensions-settings.js +14 -8
  23. package/dist/extensions/manager.js +26 -0
  24. package/dist/flows.d.ts +5 -1
  25. package/dist/flows.js +61 -4
  26. package/dist/operations/condition/index.js +1 -1
  27. package/dist/permissions/utils/get-permissions-for-share.js +2 -0
  28. package/dist/permissions/utils/merge-fields.d.ts +1 -0
  29. package/dist/permissions/utils/merge-fields.js +29 -0
  30. package/dist/permissions/utils/merge-permissions.js +3 -14
  31. package/dist/services/fields.js +3 -3
  32. package/dist/services/graphql/resolvers/mutation.js +1 -1
  33. package/dist/services/graphql/resolvers/query.js +1 -1
  34. package/dist/services/graphql/resolvers/system.js +4 -4
  35. package/dist/services/graphql/schema/parse-query.d.ts +1 -1
  36. package/dist/services/graphql/schema/parse-query.js +8 -1
  37. package/dist/services/graphql/schema/read.js +4 -4
  38. package/dist/services/graphql/subscription.js +1 -1
  39. package/dist/services/graphql/utils/filter-replace-m2a.d.ts +3 -0
  40. package/dist/services/graphql/utils/filter-replace-m2a.js +59 -0
  41. package/dist/services/items.js +2 -2
  42. package/dist/services/payload.js +2 -2
  43. package/dist/services/relations.d.ts +1 -1
  44. package/dist/services/relations.js +5 -2
  45. package/dist/services/specifications.js +6 -2
  46. package/dist/services/users.js +3 -0
  47. package/dist/telemetry/lib/get-report.js +4 -1
  48. package/dist/telemetry/types/report.d.ts +4 -0
  49. package/dist/telemetry/utils/get-project-id.d.ts +2 -0
  50. package/dist/telemetry/utils/get-project-id.js +4 -0
  51. package/dist/utils/get-ip-from-req.d.ts +2 -1
  52. package/dist/utils/get-ip-from-req.js +29 -2
  53. package/dist/utils/get-schema.js +1 -1
  54. package/dist/utils/is-url-allowed.js +1 -1
  55. package/dist/utils/sanitize-query.js +6 -0
  56. package/dist/utils/validate-query.js +1 -0
  57. package/dist/websocket/controllers/base.d.ts +2 -2
  58. package/dist/websocket/controllers/base.js +33 -5
  59. package/dist/websocket/types.d.ts +1 -0
  60. package/dist/websocket/utils/items.d.ts +1 -1
  61. package/package.json +27 -24
  62. package/dist/utils/map-values-deep.d.ts +0 -1
  63. package/dist/utils/map-values-deep.js +0 -25
@@ -1,6 +1,6 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { ErrorCode, InvalidCredentialsError, InvalidPayloadError, InvalidProviderConfigError, InvalidProviderError, InvalidTokenError, isDirectusError, ServiceUnavailableError, } from '@directus/errors';
3
- import { parseJSON } from '@directus/utils';
3
+ import { parseJSON, toArray } from '@directus/utils';
4
4
  import express, { Router } from 'express';
5
5
  import { flatten } from 'flat';
6
6
  import jwt from 'jsonwebtoken';
@@ -126,8 +126,8 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
126
126
  }
127
127
  let role = this.config['defaultRoleId'];
128
128
  const groupClaimName = this.config['groupClaimName'] ?? 'groups';
129
- const groups = userInfo[groupClaimName];
130
- if (Array.isArray(groups)) {
129
+ const groups = userInfo[groupClaimName] ? toArray(userInfo[groupClaimName]) : [];
130
+ if (groups.length > 0) {
131
131
  for (const key in this.roleMap) {
132
132
  if (groups.includes(key)) {
133
133
  // Overwrite default role if user is member of a group specified in roleMap
@@ -136,7 +136,7 @@ export class OAuth2AuthDriver extends LocalAuthDriver {
136
136
  }
137
137
  }
138
138
  }
139
- else {
139
+ else if (Object.keys(this.roleMap).length > 0) {
140
140
  logger.debug(`[OAuth2] Configured group claim with name "${groupClaimName}" does not exist or is empty.`);
141
141
  }
142
142
  // Flatten response to support dot indexes
@@ -5,12 +5,13 @@ import type { AuthDriverOptions, User } from '../../types/index.js';
5
5
  import type { RoleMap } from '../../types/rolemap.js';
6
6
  import { LocalAuthDriver } from './local.js';
7
7
  export declare class OpenIDAuthDriver extends LocalAuthDriver {
8
- client: Promise<Client>;
8
+ client: null | Client;
9
9
  redirectUrl: string;
10
10
  usersService: UsersService;
11
11
  config: Record<string, any>;
12
12
  roleMap: RoleMap;
13
13
  constructor(options: AuthDriverOptions, config: Record<string, any>);
14
+ private getClient;
14
15
  generateCodeVerifier(): string;
15
16
  generateAuthUrl(codeVerifier: string, prompt?: boolean): Promise<string>;
16
17
  private fetchUserId;
@@ -1,10 +1,10 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { ErrorCode, InvalidCredentialsError, InvalidPayloadError, InvalidProviderConfigError, InvalidProviderError, InvalidTokenError, isDirectusError, ServiceUnavailableError, } from '@directus/errors';
3
- import { parseJSON } from '@directus/utils';
3
+ import { parseJSON, toArray } from '@directus/utils';
4
4
  import express, { Router } from 'express';
5
5
  import { flatten } from 'flat';
6
6
  import jwt from 'jsonwebtoken';
7
- import { errors, generators, Issuer } from 'openid-client';
7
+ import { custom, errors, generators, Issuer } from 'openid-client';
8
8
  import { getAuthProvider } from '../../auth.js';
9
9
  import { REFRESH_COOKIE_OPTIONS, SESSION_COOKIE_OPTIONS } from '../../constants.js';
10
10
  import getDatabase from '../../database/index.js';
@@ -32,23 +32,15 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
32
32
  super(options, config);
33
33
  const env = useEnv();
34
34
  const logger = useLogger();
35
- const { issuerUrl, clientId, clientSecret, ...additionalConfig } = config;
36
- if (!issuerUrl || !clientId || !clientSecret || !additionalConfig['provider']) {
35
+ const { issuerUrl, clientId, clientSecret, provider, issuerDiscoveryMustSucceed } = config;
36
+ if (!issuerUrl || !clientId || !clientSecret || !provider) {
37
37
  logger.error('Invalid provider config');
38
- throw new InvalidProviderConfigError({ provider: additionalConfig['provider'] });
38
+ throw new InvalidProviderConfigError({ provider });
39
39
  }
40
- const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', additionalConfig['provider'], 'callback');
41
- // extract client overrides/options excluding CLIENT_ID and CLIENT_SECRET as they are passed directly
42
- const clientOptionsOverrides = getConfigFromEnv(`AUTH_${config['provider'].toUpperCase()}_CLIENT_`, {
43
- omitKey: [
44
- `AUTH_${config['provider'].toUpperCase()}_CLIENT_ID`,
45
- `AUTH_${config['provider'].toUpperCase()}_CLIENT_SECRET`,
46
- ],
47
- type: 'underscore',
48
- });
40
+ const redirectUrl = new Url(env['PUBLIC_URL']).addPath('auth', 'login', provider, 'callback');
49
41
  this.redirectUrl = redirectUrl.toString();
50
42
  this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
51
- this.config = additionalConfig;
43
+ this.config = config;
52
44
  this.roleMap = {};
53
45
  const roleMapping = this.config['roleMapping'];
54
46
  if (roleMapping) {
@@ -60,29 +52,62 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
60
52
  logger.error("[OpenID] Expected a JSON-Object as role mapping, got an Array instead. Make sure you declare the variable with 'json:' prefix.");
61
53
  throw new InvalidProviderError();
62
54
  }
63
- this.client = new Promise((resolve, reject) => {
64
- Issuer.discover(issuerUrl)
65
- .then((issuer) => {
66
- const supportedTypes = issuer.metadata['response_types_supported'];
67
- if (!supportedTypes?.includes('code')) {
68
- logger.error('OpenID provider does not support required code flow');
69
- reject(new InvalidProviderConfigError({
70
- provider: additionalConfig['provider'],
71
- }));
72
- }
73
- resolve(new issuer.Client({
74
- client_id: clientId,
75
- client_secret: clientSecret,
76
- redirect_uris: [this.redirectUrl],
77
- response_types: ['code'],
78
- ...clientOptionsOverrides,
79
- }));
80
- })
81
- .catch((e) => {
82
- logger.error(e, '[OpenID] Failed to fetch provider config');
55
+ this.client = null;
56
+ // preload client
57
+ this.getClient().catch((e) => {
58
+ logger.error(e, '[OpenID] Failed to fetch provider config');
59
+ if (issuerDiscoveryMustSucceed !== false) {
60
+ logger.error(`AUTH_${provider.toUpperCase()}_ISSUER_DISCOVERY_MUST_SUCCEED is enabled and discovery failed, exiting`);
83
61
  process.exit(1);
62
+ }
63
+ });
64
+ }
65
+ async getClient() {
66
+ if (this.client)
67
+ return this.client;
68
+ const logger = useLogger();
69
+ const { issuerUrl, clientId, clientSecret, provider } = this.config;
70
+ // extract client http overrides/options
71
+ const clientHttpOptions = getConfigFromEnv(`AUTH_${provider.toUpperCase()}_CLIENT_HTTP_`);
72
+ if (clientHttpOptions) {
73
+ Issuer[custom.http_options] = (_, options) => {
74
+ return {
75
+ ...options,
76
+ ...clientHttpOptions,
77
+ };
78
+ };
79
+ }
80
+ const issuer = await Issuer.discover(issuerUrl);
81
+ const supportedTypes = issuer.metadata['response_types_supported'];
82
+ if (!supportedTypes?.includes('code')) {
83
+ logger.error('OpenID provider does not support required code flow');
84
+ throw new InvalidProviderConfigError({
85
+ provider,
84
86
  });
87
+ }
88
+ // extract client overrides/options excluding CLIENT_ID and CLIENT_SECRET as they are passed directly
89
+ const clientOptionsOverrides = getConfigFromEnv(`AUTH_${provider.toUpperCase()}_CLIENT_`, {
90
+ omitKey: [`AUTH_${provider.toUpperCase()}_CLIENT_ID`, `AUTH_${provider.toUpperCase()}_CLIENT_SECRET`],
91
+ omitPrefix: [`AUTH_${provider.toUpperCase()}_CLIENT_HTTP_`],
92
+ type: 'underscore',
93
+ });
94
+ const client = new issuer.Client({
95
+ client_id: clientId,
96
+ client_secret: clientSecret,
97
+ redirect_uris: [this.redirectUrl],
98
+ response_types: ['code'],
99
+ ...clientOptionsOverrides,
85
100
  });
101
+ if (clientHttpOptions) {
102
+ client[custom.http_options] = (_, options) => {
103
+ return {
104
+ ...options,
105
+ ...clientHttpOptions,
106
+ };
107
+ };
108
+ }
109
+ this.client = client;
110
+ return client;
86
111
  }
87
112
  generateCodeVerifier() {
88
113
  return generators.codeVerifier();
@@ -90,7 +115,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
90
115
  async generateAuthUrl(codeVerifier, prompt = false) {
91
116
  const { plainCodeChallenge } = this.config;
92
117
  try {
93
- const client = await this.client;
118
+ const client = await this.getClient();
94
119
  const codeChallenge = plainCodeChallenge ? codeVerifier : generators.codeChallenge(codeVerifier);
95
120
  const paramsConfig = typeof this.config['params'] === 'object' ? this.config['params'] : {};
96
121
  return client.authorizationUrl({
@@ -127,7 +152,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
127
152
  let tokenSet;
128
153
  let userInfo;
129
154
  try {
130
- const client = await this.client;
155
+ const client = await this.getClient();
131
156
  const codeChallenge = plainCodeChallenge
132
157
  ? payload['codeVerifier']
133
158
  : generators.codeChallenge(payload['codeVerifier']);
@@ -145,8 +170,8 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
145
170
  }
146
171
  let role = this.config['defaultRoleId'];
147
172
  const groupClaimName = this.config['groupClaimName'] ?? 'groups';
148
- const groups = userInfo[groupClaimName];
149
- if (Array.isArray(groups)) {
173
+ const groups = userInfo[groupClaimName] ? toArray(userInfo[groupClaimName]) : [];
174
+ if (groups.length > 0) {
150
175
  for (const key in this.roleMap) {
151
176
  if (groups.includes(key)) {
152
177
  // Overwrite default role if user is member of a group specified in roleMap
@@ -155,7 +180,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
155
180
  }
156
181
  }
157
182
  }
158
- else {
183
+ else if (Object.keys(this.roleMap).length > 0) {
159
184
  logger.debug(`[OpenID] Configured group claim with name "${groupClaimName}" does not exist or is empty.`);
160
185
  }
161
186
  // Flatten response to support dot indexes
@@ -248,7 +273,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
248
273
  }
249
274
  if (authData?.['refreshToken']) {
250
275
  try {
251
- const client = await this.client;
276
+ const client = await this.getClient();
252
277
  const tokenSet = await client.refresh(authData['refreshToken']);
253
278
  // Update user refreshToken if provided
254
279
  if (tokenSet.refresh_token) {
@@ -305,12 +330,21 @@ export function createOpenIDAuthRouter(providerName) {
305
330
  httpOnly: true,
306
331
  sameSite: 'lax',
307
332
  });
308
- return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
333
+ try {
334
+ return res.redirect(await provider.generateAuthUrl(codeVerifier, prompt));
335
+ }
336
+ catch {
337
+ return res.redirect(new Url(env['PUBLIC_URL'])
338
+ .addPath('admin', 'login')
339
+ .setQuery('reason', ErrorCode.ServiceUnavailable)
340
+ .toString());
341
+ }
309
342
  }), respond);
310
343
  router.post('/callback', express.urlencoded({ extended: false }), (req, res) => {
311
344
  res.redirect(303, `./callback?${new URLSearchParams(req.body)}`);
312
345
  }, respond);
313
346
  router.get('/callback', asyncHandler(async (req, res, next) => {
347
+ const env = useEnv();
314
348
  const logger = useLogger();
315
349
  let tokenData;
316
350
  try {
@@ -318,7 +352,8 @@ export function createOpenIDAuthRouter(providerName) {
318
352
  }
319
353
  catch (e) {
320
354
  logger.warn(e, `[OpenID] Couldn't verify OpenID cookie`);
321
- throw new InvalidCredentialsError();
355
+ const url = new Url(env['PUBLIC_URL']).addPath('admin', 'login');
356
+ return res.redirect(`${url.toString()}?reason=${ErrorCode.InvalidCredentials}`);
322
357
  }
323
358
  const { verifier, redirect, prompt } = tokenData;
324
359
  const accountability = createDefaultAccountability({ ip: getIPFromReq(req) });
@@ -1,2 +1,3 @@
1
1
  import type { MSSQLError } from './types.js';
2
- export declare function extractError(error: MSSQLError): Promise<MSSQLError | Error>;
2
+ import type { Item } from '@directus/types';
3
+ export declare function extractError(error: MSSQLError, data: Partial<Item>): Promise<MSSQLError | Error>;
@@ -8,135 +8,139 @@ var MSSQLErrorCodes;
8
8
  MSSQLErrorCodes[MSSQLErrorCodes["UNIQUE_VIOLATION"] = 2601] = "UNIQUE_VIOLATION";
9
9
  MSSQLErrorCodes[MSSQLErrorCodes["VALUE_LIMIT_VIOLATION"] = 2628] = "VALUE_LIMIT_VIOLATION";
10
10
  })(MSSQLErrorCodes || (MSSQLErrorCodes = {}));
11
- export async function extractError(error) {
11
+ export async function extractError(error, data) {
12
12
  switch (error.number) {
13
13
  case MSSQLErrorCodes.UNIQUE_VIOLATION:
14
14
  case 2627:
15
- return await uniqueViolation(error);
15
+ return await uniqueViolation();
16
16
  case MSSQLErrorCodes.NUMERIC_VALUE_OUT_OF_RANGE:
17
- return numericValueOutOfRange(error);
17
+ return numericValueOutOfRange();
18
18
  case MSSQLErrorCodes.VALUE_LIMIT_VIOLATION:
19
- return valueLimitViolation(error);
19
+ return valueLimitViolation();
20
20
  case MSSQLErrorCodes.NOT_NULL_VIOLATION:
21
- return notNullViolation(error);
21
+ return notNullViolation();
22
22
  case MSSQLErrorCodes.FOREIGN_KEY_VIOLATION:
23
- return foreignKeyViolation(error);
23
+ return foreignKeyViolation();
24
24
  }
25
25
  return error;
26
- }
27
- async function uniqueViolation(error) {
28
- /**
29
- * NOTE:
30
- * SQL Server doesn't return the name of the offending column when a unique constraint is thrown:
31
- *
32
- * insert into [articles] ([unique]) values (@p0)
33
- * - Violation of UNIQUE KEY constraint 'UQ__articles__5A062640242004EB'.
34
- * Cannot insert duplicate key in object 'dbo.articles'. The duplicate key value is (rijk).
35
- *
36
- * While it's not ideal, the best next thing we can do is extract the column name from
37
- * information_schema when this happens
38
- */
39
- const betweenQuotes = /'([^']+)'/g;
40
- const betweenParens = /\(([^)]+)\)/g;
41
- const quoteMatches = error.message.match(betweenQuotes);
42
- const parenMatches = error.message.match(betweenParens);
43
- if (!quoteMatches || !parenMatches)
44
- return error;
45
- const keyName = quoteMatches[1].slice(1, -1);
46
- let collection = quoteMatches[0].slice(1, -1);
47
- let field = null;
48
- if (keyName) {
49
- const database = getDatabase();
50
- const constraintUsage = await database
51
- .select('sys.columns.name as field', database.raw('OBJECT_NAME(??) as collection', ['sys.columns.object_id']))
52
- .from('sys.indexes')
53
- .innerJoin('sys.index_columns', (join) => {
54
- join
55
- .on('sys.indexes.object_id', '=', 'sys.index_columns.object_id')
56
- .andOn('sys.indexes.index_id', '=', 'sys.index_columns.index_id');
57
- })
58
- .innerJoin('sys.columns', (join) => {
59
- join
60
- .on('sys.index_columns.object_id', '=', 'sys.columns.object_id')
61
- .andOn('sys.index_columns.column_id', '=', 'sys.columns.column_id');
62
- })
63
- .where('sys.indexes.name', '=', keyName)
64
- .first();
65
- collection = constraintUsage?.collection;
66
- field = constraintUsage?.field;
26
+ async function uniqueViolation() {
27
+ /**
28
+ * NOTE:
29
+ * SQL Server doesn't return the name of the offending column when a unique constraint is thrown:
30
+ *
31
+ * insert into [articles] ([unique]) values (@p0)
32
+ * - Violation of UNIQUE KEY constraint 'UQ__articles__5A062640242004EB'.
33
+ * Cannot insert duplicate key in object 'dbo.articles'. The duplicate key value is (rijk).
34
+ *
35
+ * While it's not ideal, the best next thing we can do is extract the column name from
36
+ * information_schema when this happens
37
+ */
38
+ const betweenQuotes = /'([^']+)'/g;
39
+ const betweenParens = /\(([^)]+)\)/g;
40
+ const quoteMatches = error.message.match(betweenQuotes);
41
+ const parenMatches = error.message.match(betweenParens);
42
+ if (!quoteMatches || !parenMatches)
43
+ return error;
44
+ const keyName = quoteMatches[1].slice(1, -1);
45
+ let collection = quoteMatches[0].slice(1, -1);
46
+ let field = null;
47
+ if (keyName) {
48
+ const database = getDatabase();
49
+ const constraintUsage = await database
50
+ .select('sys.columns.name as field', database.raw('OBJECT_NAME(??) as collection', ['sys.columns.object_id']))
51
+ .from('sys.indexes')
52
+ .innerJoin('sys.index_columns', (join) => {
53
+ join
54
+ .on('sys.indexes.object_id', '=', 'sys.index_columns.object_id')
55
+ .andOn('sys.indexes.index_id', '=', 'sys.index_columns.index_id');
56
+ })
57
+ .innerJoin('sys.columns', (join) => {
58
+ join
59
+ .on('sys.index_columns.object_id', '=', 'sys.columns.object_id')
60
+ .andOn('sys.index_columns.column_id', '=', 'sys.columns.column_id');
61
+ })
62
+ .where('sys.indexes.name', '=', keyName)
63
+ .first();
64
+ collection = constraintUsage?.collection;
65
+ field = constraintUsage?.field;
66
+ }
67
+ return new RecordNotUniqueError({
68
+ collection,
69
+ field,
70
+ value: field ? data[field] : null,
71
+ });
67
72
  }
68
- return new RecordNotUniqueError({
69
- collection,
70
- field,
71
- });
72
- }
73
- function numericValueOutOfRange(error) {
74
- const betweenBrackets = /\[([^\]]+)\]/g;
75
- const bracketMatches = error.message.match(betweenBrackets);
76
- if (!bracketMatches)
77
- return error;
78
- const collection = bracketMatches[0].slice(1, -1);
79
- /**
80
- * NOTE
81
- * MS SQL Doesn't return the offending column name in the error, nor any other identifying information
82
- * we can use to extract the column name :(
83
- *
84
- * insert into [test1] ([small]) values (@p0)
85
- * - Arithmetic overflow error for data type tinyint, value = 50000.
86
- */
87
- const field = null;
88
- return new ValueOutOfRangeError({
89
- collection,
90
- field,
91
- });
92
- }
93
- function valueLimitViolation(error) {
94
- const betweenBrackets = /\[([^\]]+)\]/g;
95
- const betweenQuotes = /'([^']+)'/g;
96
- const bracketMatches = error.message.match(betweenBrackets);
97
- const quoteMatches = error.message.match(betweenQuotes);
98
- if (!bracketMatches || !quoteMatches)
99
- return error;
100
- const collection = bracketMatches[0].slice(1, -1);
101
- const field = quoteMatches[1].slice(1, -1);
102
- return new ValueTooLongError({
103
- collection,
104
- field,
105
- });
106
- }
107
- function notNullViolation(error) {
108
- const betweenBrackets = /\[([^\]]+)\]/g;
109
- const betweenQuotes = /'([^']+)'/g;
110
- const bracketMatches = error.message.match(betweenBrackets);
111
- const quoteMatches = error.message.match(betweenQuotes);
112
- if (!bracketMatches || !quoteMatches)
113
- return error;
114
- const collection = bracketMatches[0].slice(1, -1);
115
- const field = quoteMatches[0].slice(1, -1);
116
- if (error.message.includes('Cannot insert the value NULL into column')) {
117
- return new ContainsNullValuesError({ collection, field });
73
+ function numericValueOutOfRange() {
74
+ const betweenBrackets = /\[([^\]]+)\]/g;
75
+ const bracketMatches = error.message.match(betweenBrackets);
76
+ if (!bracketMatches)
77
+ return error;
78
+ const collection = bracketMatches[0].slice(1, -1);
79
+ /**
80
+ * NOTE
81
+ * MS SQL Doesn't return the offending column name in the error, nor any other identifying information
82
+ * we can use to extract the column name :(
83
+ *
84
+ * insert into [test1] ([small]) values (@p0)
85
+ * - Arithmetic overflow error for data type tinyint, value = 50000.
86
+ */
87
+ const field = null;
88
+ return new ValueOutOfRangeError({
89
+ collection,
90
+ field,
91
+ value: field ? data[field] : null,
92
+ });
93
+ }
94
+ function valueLimitViolation() {
95
+ const betweenBrackets = /\[([^\]]+)\]/g;
96
+ const betweenQuotes = /'([^']+)'/g;
97
+ const bracketMatches = error.message.match(betweenBrackets);
98
+ const quoteMatches = error.message.match(betweenQuotes);
99
+ if (!bracketMatches || !quoteMatches)
100
+ return error;
101
+ const collection = bracketMatches[0].slice(1, -1);
102
+ const field = quoteMatches[1].slice(1, -1);
103
+ return new ValueTooLongError({
104
+ collection,
105
+ field,
106
+ value: field ? data[field] : null,
107
+ });
108
+ }
109
+ function notNullViolation() {
110
+ const betweenBrackets = /\[([^\]]+)\]/g;
111
+ const betweenQuotes = /'([^']+)'/g;
112
+ const bracketMatches = error.message.match(betweenBrackets);
113
+ const quoteMatches = error.message.match(betweenQuotes);
114
+ if (!bracketMatches || !quoteMatches)
115
+ return error;
116
+ const collection = bracketMatches[0].slice(1, -1);
117
+ const field = quoteMatches[0].slice(1, -1);
118
+ if (error.message.includes('Cannot insert the value NULL into column')) {
119
+ return new ContainsNullValuesError({ collection, field });
120
+ }
121
+ return new NotNullViolationError({
122
+ collection,
123
+ field,
124
+ });
125
+ }
126
+ function foreignKeyViolation() {
127
+ const betweenUnderscores = /__(.+)__/g;
128
+ const betweenParens = /\(([^)]+)\)/g;
129
+ // NOTE:
130
+ // Seeing that MS SQL doesn't return the offending column name, we have to extract it from the
131
+ // foreign key constraint name as generated by the database. This'll probably fail if you have
132
+ // custom names for whatever reason.
133
+ const underscoreMatches = error.message.match(betweenUnderscores);
134
+ const parenMatches = error.message.match(betweenParens);
135
+ if (!underscoreMatches || !parenMatches)
136
+ return error;
137
+ const underscoreParts = underscoreMatches[0].split('__');
138
+ const collection = underscoreParts[1];
139
+ const field = underscoreParts[2];
140
+ return new InvalidForeignKeyError({
141
+ collection,
142
+ field,
143
+ value: field ? data[field] : null,
144
+ });
118
145
  }
119
- return new NotNullViolationError({
120
- collection,
121
- field,
122
- });
123
- }
124
- function foreignKeyViolation(error) {
125
- const betweenUnderscores = /__(.+)__/g;
126
- const betweenParens = /\(([^)]+)\)/g;
127
- // NOTE:
128
- // Seeing that MS SQL doesn't return the offending column name, we have to extract it from the
129
- // foreign key constraint name as generated by the database. This'll probably fail if you have
130
- // custom names for whatever reason.
131
- const underscoreMatches = error.message.match(betweenUnderscores);
132
- const parenMatches = error.message.match(betweenParens);
133
- if (!underscoreMatches || !parenMatches)
134
- return error;
135
- const underscoreParts = underscoreMatches[0].split('__');
136
- const collection = underscoreParts[1];
137
- const field = underscoreParts[2];
138
- return new InvalidForeignKeyError({
139
- collection,
140
- field,
141
- });
142
146
  }
@@ -1,2 +1,3 @@
1
1
  import type { MySQLError } from './types.js';
2
- export declare function extractError(error: MySQLError): MySQLError | Error;
2
+ import type { Item } from '@directus/types';
3
+ export declare function extractError(error: MySQLError, data: Partial<Item>): MySQLError | Error;