@directus/api 33.3.1 → 34.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/ai/chat/lib/create-ui-stream.js +2 -1
  2. package/dist/ai/chat/lib/transform-file-parts.d.ts +12 -0
  3. package/dist/ai/chat/lib/transform-file-parts.js +36 -0
  4. package/dist/ai/files/adapters/anthropic.d.ts +3 -0
  5. package/dist/ai/files/adapters/anthropic.js +25 -0
  6. package/dist/ai/files/adapters/google.d.ts +3 -0
  7. package/dist/ai/files/adapters/google.js +58 -0
  8. package/dist/ai/files/adapters/index.d.ts +3 -0
  9. package/dist/ai/files/adapters/index.js +3 -0
  10. package/dist/ai/files/adapters/openai.d.ts +3 -0
  11. package/dist/ai/files/adapters/openai.js +22 -0
  12. package/dist/ai/files/controllers/upload.d.ts +2 -0
  13. package/dist/ai/files/controllers/upload.js +101 -0
  14. package/dist/ai/files/lib/fetch-provider.d.ts +1 -0
  15. package/dist/ai/files/lib/fetch-provider.js +23 -0
  16. package/dist/ai/files/lib/upload-to-provider.d.ts +4 -0
  17. package/dist/ai/files/lib/upload-to-provider.js +26 -0
  18. package/dist/ai/files/router.d.ts +1 -0
  19. package/dist/ai/files/router.js +5 -0
  20. package/dist/ai/files/types.d.ts +5 -0
  21. package/dist/ai/files/types.js +1 -0
  22. package/dist/ai/providers/anthropic-file-support.d.ts +12 -0
  23. package/dist/ai/providers/anthropic-file-support.js +94 -0
  24. package/dist/ai/providers/registry.js +3 -6
  25. package/dist/ai/tools/fields/index.d.ts +3 -3
  26. package/dist/ai/tools/fields/index.js +9 -3
  27. package/dist/ai/tools/flows/index.d.ts +16 -16
  28. package/dist/ai/tools/schema.d.ts +8 -8
  29. package/dist/ai/tools/schema.js +2 -2
  30. package/dist/app.js +10 -1
  31. package/dist/auth/drivers/oauth2.js +10 -4
  32. package/dist/auth/drivers/openid.js +10 -4
  33. package/dist/auth/drivers/saml.js +20 -10
  34. package/dist/auth/utils/resolve-login-redirect.d.ts +11 -0
  35. package/dist/auth/utils/resolve-login-redirect.js +62 -0
  36. package/dist/controllers/deployment-webhooks.d.ts +2 -0
  37. package/dist/controllers/deployment-webhooks.js +95 -0
  38. package/dist/controllers/deployment.js +61 -165
  39. package/dist/controllers/files.js +2 -1
  40. package/dist/controllers/server.js +32 -26
  41. package/dist/controllers/tus.js +33 -2
  42. package/dist/controllers/utils.js +18 -0
  43. package/dist/database/get-ast-from-query/lib/parse-fields.js +52 -26
  44. package/dist/database/helpers/date/dialects/oracle.js +2 -0
  45. package/dist/database/helpers/date/dialects/sqlite.js +2 -0
  46. package/dist/database/helpers/date/types.d.ts +1 -1
  47. package/dist/database/helpers/date/types.js +3 -1
  48. package/dist/database/helpers/fn/dialects/mssql.d.ts +1 -0
  49. package/dist/database/helpers/fn/dialects/mssql.js +21 -0
  50. package/dist/database/helpers/fn/dialects/mysql.d.ts +2 -0
  51. package/dist/database/helpers/fn/dialects/mysql.js +30 -0
  52. package/dist/database/helpers/fn/dialects/oracle.d.ts +1 -0
  53. package/dist/database/helpers/fn/dialects/oracle.js +21 -0
  54. package/dist/database/helpers/fn/dialects/postgres.d.ts +14 -0
  55. package/dist/database/helpers/fn/dialects/postgres.js +40 -0
  56. package/dist/database/helpers/fn/dialects/sqlite.d.ts +1 -0
  57. package/dist/database/helpers/fn/dialects/sqlite.js +12 -0
  58. package/dist/database/helpers/fn/json/parse-function.d.ts +19 -0
  59. package/dist/database/helpers/fn/json/parse-function.js +66 -0
  60. package/dist/database/helpers/fn/types.d.ts +8 -0
  61. package/dist/database/helpers/fn/types.js +19 -0
  62. package/dist/database/helpers/schema/dialects/mysql.d.ts +1 -0
  63. package/dist/database/helpers/schema/dialects/mysql.js +11 -0
  64. package/dist/database/helpers/schema/types.d.ts +1 -0
  65. package/dist/database/helpers/schema/types.js +3 -0
  66. package/dist/database/index.js +2 -1
  67. package/dist/database/migrations/20260211A-add-deployment-webhooks.d.ts +3 -0
  68. package/dist/database/migrations/20260211A-add-deployment-webhooks.js +37 -0
  69. package/dist/database/run-ast/lib/apply-query/aggregate.js +4 -4
  70. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  71. package/dist/database/run-ast/lib/apply-query/filter/operator.js +17 -7
  72. package/dist/database/run-ast/lib/parse-current-level.js +8 -1
  73. package/dist/database/run-ast/run-ast.js +11 -1
  74. package/dist/database/run-ast/utils/apply-function-to-column-name.js +7 -1
  75. package/dist/database/run-ast/utils/get-column.js +13 -2
  76. package/dist/deployment/deployment.d.ts +25 -2
  77. package/dist/deployment/drivers/netlify.d.ts +6 -2
  78. package/dist/deployment/drivers/netlify.js +114 -12
  79. package/dist/deployment/drivers/vercel.d.ts +5 -2
  80. package/dist/deployment/drivers/vercel.js +84 -5
  81. package/dist/deployment.d.ts +5 -0
  82. package/dist/deployment.js +34 -0
  83. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +1 -1
  84. package/dist/permissions/utils/get-unaliased-field-key.js +9 -1
  85. package/dist/request/is-denied-ip.js +24 -23
  86. package/dist/services/authentication.js +27 -22
  87. package/dist/services/collections.js +1 -0
  88. package/dist/services/deployment-projects.d.ts +31 -2
  89. package/dist/services/deployment-projects.js +109 -5
  90. package/dist/services/deployment-runs.d.ts +19 -1
  91. package/dist/services/deployment-runs.js +86 -0
  92. package/dist/services/deployment.d.ts +44 -3
  93. package/dist/services/deployment.js +263 -15
  94. package/dist/services/files/utils/get-metadata.js +6 -6
  95. package/dist/services/files.d.ts +3 -1
  96. package/dist/services/files.js +26 -3
  97. package/dist/services/graphql/resolvers/query.js +23 -6
  98. package/dist/services/graphql/resolvers/system.js +35 -27
  99. package/dist/services/payload.d.ts +6 -0
  100. package/dist/services/payload.js +27 -2
  101. package/dist/services/server.js +1 -1
  102. package/dist/services/users.js +6 -1
  103. package/dist/test-utils/README.md +112 -0
  104. package/dist/test-utils/controllers.d.ts +65 -0
  105. package/dist/test-utils/controllers.js +100 -0
  106. package/dist/test-utils/database.d.ts +1 -1
  107. package/dist/test-utils/database.js +3 -1
  108. package/dist/utils/get-field-relational-depth.d.ts +13 -0
  109. package/dist/utils/get-field-relational-depth.js +22 -0
  110. package/dist/utils/parse-value.d.ts +4 -0
  111. package/dist/utils/parse-value.js +11 -0
  112. package/dist/utils/sanitize-query.js +3 -2
  113. package/dist/utils/split-fields.d.ts +4 -0
  114. package/dist/utils/split-fields.js +32 -0
  115. package/dist/utils/validate-query.js +2 -1
  116. package/package.json +36 -36
  117. package/dist/auth/utils/is-login-redirect-allowed.d.ts +0 -7
  118. package/dist/auth/utils/is-login-redirect-allowed.js +0 -39
@@ -17,6 +17,7 @@ This directory contains mock implementations for commonly used modules in servic
17
17
  - **[files-service.ts](#files-servicets)** - FilesService mocks
18
18
  - **[folders-service.ts](#folders-servicets)** - FoldersService mocks
19
19
  - **[test-helpers.ts](#test-helpersts)** - Test data factory functions
20
+ - **[controllers.ts](#controllersts)** - Controller/router testing helpers
20
21
 
21
22
  ## Quick Start
22
23
 
@@ -521,6 +522,116 @@ const buildTreeSpy = vi.spyOn(FoldersService.prototype, 'buildTree').mockResolve
521
522
 
522
523
  ---
523
524
 
525
+ ### controllers.ts
526
+
527
+ Provides helpers for extracting route handlers from Express routers and creating mock Express request/response objects
528
+ for controller tests.
529
+
530
+ #### `getRouteHandler(router, method, path)`
531
+
532
+ Extracts the middleware/handler stack for a specific route from an Express router.
533
+
534
+ **Parameters:**
535
+
536
+ - `router`: The Express `Router` instance
537
+ - `method`: HTTP method (`'GET'`, `'POST'`, `'PATCH'`, `'DELETE'`, etc.)
538
+ - `path`: Route path (e.g. `'/'`, `'/:id'`)
539
+
540
+ **Returns:** Array of `{ handle: (...args) => any }` layers for the matched route
541
+
542
+ **Throws:** If no matching route is found
543
+
544
+ **Example:**
545
+
546
+ ```typescript
547
+ import { default as router } from './tus.js';
548
+ import { getRouteHandler } from '../test-utils/controllers.js';
549
+
550
+ const [checkAccess, handler] = getRouteHandler(router, 'POST', '/');
551
+ await checkAccess?.handle(req, res, next);
552
+ ```
553
+
554
+ #### `createMockRequest(overrides?)`
555
+
556
+ Creates a mock Express Request pre-populated with common Directus properties (`accountability`, `schema`,
557
+ `sanitizedQuery`, etc.).
558
+
559
+ **Parameters:**
560
+
561
+ - `overrides` (optional): Properties to merge into the mock request
562
+
563
+ **Returns:** Mock `Request` object
564
+
565
+ **Example:**
566
+
567
+ ```typescript
568
+ import { createMockRequest } from '../test-utils/controllers.js';
569
+
570
+ // Minimal request
571
+ const req = createMockRequest({ schema });
572
+
573
+ // With accountability and custom header
574
+ const req = createMockRequest({
575
+ method: 'POST',
576
+ accountability,
577
+ schema,
578
+ header: vi.fn().mockReturnValue('some-value'),
579
+ });
580
+ ```
581
+
582
+ #### `createMockResponse(overrides?)`
583
+
584
+ Creates a mock Express Response with chainable methods (`status`, `json`, `send`, `set`, `end`).
585
+
586
+ **Parameters:**
587
+
588
+ - `overrides` (optional): Properties to merge into the mock response
589
+
590
+ **Returns:** Mock `Response` object
591
+
592
+ **Example:**
593
+
594
+ ```typescript
595
+ import { createMockResponse } from '../test-utils/controllers.js';
596
+
597
+ const res = createMockResponse();
598
+ ```
599
+
600
+ #### Full Controller Test Example
601
+
602
+ ```typescript
603
+ import { SchemaBuilder } from '@directus/schema-builder';
604
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
605
+ import { createMockRequest, createMockResponse, getRouteHandler } from '../test-utils/controllers.js';
606
+ import { default as router } from './controller.js';
607
+
608
+ const schema = new SchemaBuilder()
609
+ .collection('collection', (c) => {
610
+ c.field('id').integer().primary();
611
+ c.field('title').string();
612
+ })
613
+ .build();
614
+
615
+ describe('controller', () => {
616
+ beforeEach(() => {
617
+ vi.clearAllMocks();
618
+ });
619
+
620
+ test('validates access on POST', async () => {
621
+ const req = createMockRequest({ method: 'POST', accountability, schema });
622
+ const res = createMockResponse();
623
+ const next = vi.fn();
624
+
625
+ const [firstHandler] = getRouteHandler(router, 'POST', '/');
626
+ await firstHandler?.handle(req, res, next);
627
+
628
+ expect(next).toHaveBeenCalled();
629
+ });
630
+ });
631
+ ```
632
+
633
+ ---
634
+
524
635
  ## Common Patterns
525
636
 
526
637
  ### Full Service Test Setup
@@ -737,6 +848,7 @@ See these files for complete examples:
737
848
 
738
849
  - [collections.test.ts](../services/collections.test.ts) - Full service test with schema operations
739
850
  - [fields.test.ts](../services/fields.test.ts) - Complex service test with field management
851
+ - [tus.test.ts](../controllers/tus.test.ts) - Controller test with route handler extraction and access validation
740
852
 
741
853
  ---
742
854
 
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Controller testing utilities
3
+ * Provides helpers for extracting route handlers and creating mock Express request/response objects
4
+ */
5
+ import type { Request, Response, Router } from 'express';
6
+ /**
7
+ * Get a route handler stack from an Express router
8
+ *
9
+ * @param router The Express router instance to search
10
+ * @param method HTTP method (GET, POST, PATCH, DELETE, etc.)
11
+ * @param path Route path (e.g. '/', '/:id')
12
+ * @returns Array of middleware/handler layers for the matched route
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { default as router } from './controller.js';
17
+
18
+ * const [firstHandler, secondHandler] = getRouteHandler(router, 'POST', '/');
19
+ * await firstHandler?.handle(req, res, next);
20
+ * ```
21
+ */
22
+ export declare function getRouteHandler(router: Router, method: string, path: string): Array<{
23
+ handle: (...args: any[]) => any;
24
+ }>;
25
+ /**
26
+ * Creates a mock Express Request with common Directus properties.
27
+ *
28
+ * @param overrides Properties to merge into the mock request
29
+ * @returns Mock Express Request object
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Basic usage
34
+ * const req = createMockRequest({ method: 'POST', accountability });
35
+ *
36
+ * // With custom schema
37
+ * const req = createMockRequest({
38
+ * method: 'PATCH',
39
+ * params: { id: 'file-1' },
40
+ * schema: mySchema,
41
+ * });
42
+ *
43
+ * // With custom header mock
44
+ * const req = createMockRequest({
45
+ * header: vi.fn().mockReturnValue('some-header-value'),
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function createMockRequest(overrides?: Partial<Request>): Request;
50
+ /**
51
+ * Creates a mock Express Response with chainable methods.
52
+ *
53
+ * @param overrides Properties to merge into the mock response
54
+ * @returns Mock Express Response object
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * // Basic usage
59
+ * const res = createMockResponse();
60
+ *
61
+ * // With custom locals
62
+ * const res = createMockResponse({ locals: { payload: { data: [] } } });
63
+ * ```
64
+ */
65
+ export declare function createMockResponse(overrides?: Partial<Response>): Response;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Controller testing utilities
3
+ * Provides helpers for extracting route handlers and creating mock Express request/response objects
4
+ */
5
+ import { vi } from 'vitest';
6
+ /**
7
+ * Get a route handler stack from an Express router
8
+ *
9
+ * @param router The Express router instance to search
10
+ * @param method HTTP method (GET, POST, PATCH, DELETE, etc.)
11
+ * @param path Route path (e.g. '/', '/:id')
12
+ * @returns Array of middleware/handler layers for the matched route
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { default as router } from './controller.js';
17
+
18
+ * const [firstHandler, secondHandler] = getRouteHandler(router, 'POST', '/');
19
+ * await firstHandler?.handle(req, res, next);
20
+ * ```
21
+ */
22
+ export function getRouteHandler(router, method, path) {
23
+ const stack = router.stack;
24
+ const layer = stack.find((l) => l.route?.path === path && l.route?.methods[method.toLowerCase()]);
25
+ if (!layer)
26
+ throw new Error(`No route found for ${method} ${path}`);
27
+ return layer.route.stack;
28
+ }
29
+ /**
30
+ * Creates a mock Express Request with common Directus properties.
31
+ *
32
+ * @param overrides Properties to merge into the mock request
33
+ * @returns Mock Express Request object
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Basic usage
38
+ * const req = createMockRequest({ method: 'POST', accountability });
39
+ *
40
+ * // With custom schema
41
+ * const req = createMockRequest({
42
+ * method: 'PATCH',
43
+ * params: { id: 'file-1' },
44
+ * schema: mySchema,
45
+ * });
46
+ *
47
+ * // With custom header mock
48
+ * const req = createMockRequest({
49
+ * header: vi.fn().mockReturnValue('some-header-value'),
50
+ * });
51
+ * ```
52
+ */
53
+ export function createMockRequest(overrides = {}) {
54
+ const headerFn = vi.fn().mockReturnValue(undefined);
55
+ return {
56
+ method: 'GET',
57
+ headers: {},
58
+ params: {},
59
+ body: {},
60
+ header: headerFn,
61
+ get: headerFn,
62
+ is: vi.fn().mockReturnValue(false),
63
+ token: null,
64
+ collection: '',
65
+ singleton: false,
66
+ accountability: undefined,
67
+ sanitizedQuery: {},
68
+ schema: {
69
+ collections: {},
70
+ relations: [],
71
+ },
72
+ ...overrides,
73
+ };
74
+ }
75
+ /**
76
+ * Creates a mock Express Response with chainable methods.
77
+ *
78
+ * @param overrides Properties to merge into the mock response
79
+ * @returns Mock Express Response object
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Basic usage
84
+ * const res = createMockResponse();
85
+ *
86
+ * // With custom locals
87
+ * const res = createMockResponse({ locals: { payload: { data: [] } } });
88
+ * ```
89
+ */
90
+ export function createMockResponse(overrides = {}) {
91
+ return {
92
+ locals: {},
93
+ status: vi.fn().mockReturnThis(),
94
+ json: vi.fn().mockReturnThis(),
95
+ send: vi.fn().mockReturnThis(),
96
+ set: vi.fn().mockReturnThis(),
97
+ end: vi.fn().mockReturnThis(),
98
+ ...overrides,
99
+ };
100
+ }
@@ -24,7 +24,7 @@ import type { DatabaseClient } from '@directus/types';
24
24
  * ```
25
25
  */
26
26
  export declare function mockDatabase(client?: DatabaseClient): {
27
- default: import("vitest").Mock<(...args: any[]) => any>;
27
+ default: import("vitest").Mock<() => import("vitest").MockedFunction<import("knex").Knex<any, unknown[]>>>;
28
28
  getDatabaseClient: import("vitest").Mock<(...args: any[]) => any>;
29
29
  getSchemaInspector: import("vitest").Mock<(...args: any[]) => any>;
30
30
  };
@@ -3,6 +3,7 @@
3
3
  * Provides simplified mocks for src/database/index module used in service testing
4
4
  */
5
5
  import { vi } from 'vitest';
6
+ import { createMockKnex } from './knex.js';
6
7
  /**
7
8
  * Creates a standard database mock for service tests
8
9
  * This matches the pattern used across all service test files
@@ -24,8 +25,9 @@ import { vi } from 'vitest';
24
25
  * ```
25
26
  */
26
27
  export function mockDatabase(client = 'postgres') {
28
+ const { db } = createMockKnex();
27
29
  return {
28
- default: vi.fn(),
30
+ default: vi.fn(() => db),
29
31
  getDatabaseClient: vi.fn().mockReturnValue(client),
30
32
  getSchemaInspector: vi.fn(),
31
33
  };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Counts the number of relational segments in a field path. Handles function syntax
3
+ * (e.g. json(), year()) by counting relational segments in the prefix and in the first argument
4
+ * separately, while ignoring subsequent arguments (e.g. json paths).
5
+ *
6
+ * @example
7
+ * getFieldRelationalDepth('a.b.c') // 3
8
+ * getFieldRelationalDepth('year(user.date_created)') // 2
9
+ * getFieldRelationalDepth('category_id.json(metadata, a.b.c)') // 2
10
+ * getFieldRelationalDepth('json(a.b.field, some.path)') // 3
11
+ * getFieldRelationalDepth('json(metadata, path.to.value)') // 1
12
+ */
13
+ export declare function getFieldRelationalDepth(field: string): number;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Counts the number of relational segments in a field path. Handles function syntax
3
+ * (e.g. json(), year()) by counting relational segments in the prefix and in the first argument
4
+ * separately, while ignoring subsequent arguments (e.g. json paths).
5
+ *
6
+ * @example
7
+ * getFieldRelationalDepth('a.b.c') // 3
8
+ * getFieldRelationalDepth('year(user.date_created)') // 2
9
+ * getFieldRelationalDepth('category_id.json(metadata, a.b.c)') // 2
10
+ * getFieldRelationalDepth('json(a.b.field, some.path)') // 3
11
+ * getFieldRelationalDepth('json(metadata, path.to.value)') // 1
12
+ */
13
+ export function getFieldRelationalDepth(field) {
14
+ const openParenIndex = field.indexOf('(');
15
+ if (openParenIndex === -1) {
16
+ return field.split('.').length;
17
+ }
18
+ const functionDepth = field.slice(0, openParenIndex).split('.').length - 1;
19
+ const commaIndex = field.indexOf(',', openParenIndex);
20
+ const fieldDepth = field.slice(openParenIndex, commaIndex).split('.').length;
21
+ return functionDepth + fieldDepth;
22
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Parse a value that might be a JSON string, returning a typed result or fallback.
3
+ */
4
+ export declare function parseValue<T>(value: unknown, fallback: T): T;
@@ -0,0 +1,11 @@
1
+ import { parseJSON } from '@directus/utils';
2
+ /**
3
+ * Parse a value that might be a JSON string, returning a typed result or fallback.
4
+ */
5
+ export function parseValue(value, fallback) {
6
+ if (!value)
7
+ return fallback;
8
+ if (typeof value === 'string')
9
+ return parseJSON(value);
10
+ return value;
11
+ }
@@ -9,6 +9,7 @@ import { contextHasDynamicVariables } from '../permissions/modules/process-ast/u
9
9
  import { extractRequiredDynamicVariableContext } from '../permissions/utils/extract-required-dynamic-variable-context.js';
10
10
  import { fetchDynamicVariableData } from '../permissions/utils/fetch-dynamic-variable-data.js';
11
11
  import { Meta } from '../types/index.js';
12
+ import { splitFields } from './split-fields.js';
12
13
  /**
13
14
  * Sanitize the query parameters and parse them where necessary.
14
15
  */
@@ -81,7 +82,7 @@ function sanitizeFields(rawFields) {
81
82
  return null;
82
83
  let fields = [];
83
84
  if (typeof rawFields === 'string') {
84
- fields = rawFields.split(',');
85
+ fields = splitFields(rawFields);
85
86
  }
86
87
  else if (Array.isArray(rawFields)) {
87
88
  fields = rawFields;
@@ -90,7 +91,7 @@ function sanitizeFields(rawFields) {
90
91
  throw new InvalidQueryError({ reason: '"fields" must be a string or array' });
91
92
  }
92
93
  // Case where array item includes CSV (fe fields[]=id,name):
93
- fields = flatten(fields.map((field) => (field.includes(',') ? field.split(',') : field)));
94
+ fields = flatten(fields.map((field) => (field.includes(',') ? splitFields(field) : field)));
94
95
  fields = fields.map((field) => field.trim());
95
96
  return fields;
96
97
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Parenthesis aware splitting of fields allowing for `json(a, b)` field functions
3
+ */
4
+ export declare function splitFields(input: string): string[];
@@ -0,0 +1,32 @@
1
+ import { InvalidQueryError } from '@directus/errors';
2
+ /**
3
+ * Parenthesis aware splitting of fields allowing for `json(a, b)` field functions
4
+ */
5
+ export function splitFields(input) {
6
+ const fields = [];
7
+ let current = '';
8
+ let depth = 0;
9
+ for (const char of input) {
10
+ if (char === '(') {
11
+ depth++;
12
+ if (depth > 1) {
13
+ throw new InvalidQueryError({ reason: 'Nested functions are not supported in "fields"' });
14
+ }
15
+ }
16
+ else if (char === ')') {
17
+ depth--;
18
+ }
19
+ if (char === ',' && depth === 0) {
20
+ fields.push(current);
21
+ current = '';
22
+ }
23
+ else {
24
+ current += char;
25
+ }
26
+ }
27
+ if (depth !== 0) {
28
+ throw new InvalidQueryError({ reason: 'Missing closing parenthesis in "fields"' });
29
+ }
30
+ fields.push(current);
31
+ return fields;
32
+ }
@@ -4,6 +4,7 @@ import Joi from 'joi';
4
4
  import { isPlainObject, uniq } from 'lodash-es';
5
5
  import { stringify } from 'wellknown';
6
6
  import { calculateFieldDepth } from './calculate-field-depth.js';
7
+ import { getFieldRelationalDepth } from './get-field-relational-depth.js';
7
8
  const env = useEnv();
8
9
  const querySchema = Joi.object({
9
10
  fields: Joi.array().items(Joi.string()),
@@ -186,7 +187,7 @@ function validateRelationalDepth(query) {
186
187
  }
187
188
  fields = uniq(fields);
188
189
  for (const field of fields) {
189
- if (field.split('.').length > maxRelationalDepth) {
190
+ if (getFieldRelationalDepth(field) > maxRelationalDepth) {
190
191
  throw new InvalidQueryError({ reason: 'Max relational depth exceeded' });
191
192
  }
192
193
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "33.3.1",
3
+ "version": "34.0.1",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -58,10 +58,10 @@
58
58
  "dist"
59
59
  ],
60
60
  "dependencies": {
61
- "@ai-sdk/anthropic": "3.0.9",
62
- "@ai-sdk/google": "3.0.6",
63
- "@ai-sdk/openai": "3.0.7",
64
- "@ai-sdk/openai-compatible": "2.0.4",
61
+ "@ai-sdk/anthropic": "3.0.58",
62
+ "@ai-sdk/google": "3.0.43",
63
+ "@ai-sdk/openai": "3.0.41",
64
+ "@ai-sdk/openai-compatible": "2.0.35",
65
65
  "@authenio/samlify-node-xmllint": "2.0.0",
66
66
  "@aws-sdk/client-sesv2": "3.928.0",
67
67
  "@godaddy/terminus": "4.12.1",
@@ -72,12 +72,12 @@
72
72
  "@rollup/plugin-virtual": "3.0.2",
73
73
  "@tus/server": "2.3.0",
74
74
  "@tus/utils": "0.6.0",
75
- "ai": "6.0.25",
75
+ "ai": "6.0.116",
76
76
  "archiver": "7.0.1",
77
77
  "argon2": "0.44.0",
78
78
  "async": "3.2.6",
79
79
  "async-mutex": "0.5.0",
80
- "axios": "1.12.2",
80
+ "axios": "1.13.5",
81
81
  "busboy": "1.6.0",
82
82
  "bytes": "3.1.2",
83
83
  "camelcase": "8.0.0",
@@ -118,12 +118,12 @@
118
118
  "keyv": "5.5.3",
119
119
  "knex": "3.1.0",
120
120
  "ldapts": "8.1.3",
121
- "liquidjs": "10.24.0",
122
- "lodash-es": "4.17.21",
121
+ "liquidjs": "10.25.0",
122
+ "lodash-es": "4.17.23",
123
123
  "marked": "16.4.1",
124
124
  "micromustache": "8.0.3",
125
125
  "mime-types": "3.0.1",
126
- "minimatch": "10.1.1",
126
+ "minimatch": "10.2.3",
127
127
  "mnemonist": "0.40.3",
128
128
  "ms": "2.1.3",
129
129
  "nanoid": "5.1.6",
@@ -144,48 +144,48 @@
144
144
  "pm2": "6.0.14",
145
145
  "prom-client": "15.1.3",
146
146
  "proxy-addr": "2.0.7",
147
- "qs": "6.14.1",
147
+ "qs": "6.14.2",
148
148
  "rate-limiter-flexible": "7.2.0",
149
149
  "rolldown": "1.0.0-beta.31",
150
- "rollup": "4.52.5",
150
+ "rollup": "4.59.0",
151
151
  "samlify": "2.10.2",
152
152
  "sanitize-filename": "1.6.3",
153
153
  "sanitize-html": "2.17.0",
154
154
  "sharp": "0.34.5",
155
155
  "snappy": "7.3.3",
156
156
  "stream-json": "1.9.1",
157
- "tar": "7.5.4",
157
+ "tar": "7.5.8",
158
158
  "tsx": "4.20.6",
159
159
  "uuid": "11.1.0",
160
160
  "wellknown": "0.5.0",
161
161
  "ws": "8.18.3",
162
162
  "zod": "4.1.12",
163
163
  "zod-validation-error": "4.0.2",
164
- "@directus/ai": "1.1.0",
165
- "@directus/app": "15.4.0",
166
- "@directus/constants": "14.1.0",
167
- "@directus/env": "5.5.3",
164
+ "@directus/ai": "1.3.0",
165
+ "@directus/constants": "14.2.0",
166
+ "@directus/app": "15.5.1",
167
+ "@directus/env": "5.6.1",
168
168
  "@directus/errors": "2.2.0",
169
- "@directus/extensions": "3.0.19",
170
- "@directus/extensions-registry": "3.0.19",
171
- "@directus/extensions-sdk": "17.0.9",
172
- "@directus/memory": "3.1.2",
173
- "@directus/schema": "13.0.5",
169
+ "@directus/extensions": "3.0.21",
170
+ "@directus/extensions-registry": "3.0.21",
174
171
  "@directus/format-title": "12.1.1",
175
- "@directus/schema-builder": "0.0.14",
176
- "@directus/specs": "12.0.0",
177
- "@directus/pressure": "3.0.17",
172
+ "@directus/memory": "3.1.4",
173
+ "@directus/pressure": "3.0.19",
174
+ "@directus/schema": "13.0.5",
175
+ "@directus/schema-builder": "0.0.16",
176
+ "@directus/extensions-sdk": "17.0.11",
177
+ "@directus/specs": "12.0.1",
178
178
  "@directus/storage": "12.0.3",
179
- "@directus/storage-driver-azure": "12.0.17",
180
- "@directus/storage-driver-cloudinary": "12.0.17",
181
- "@directus/storage-driver-s3": "12.1.3",
179
+ "@directus/storage-driver-gcs": "12.0.19",
180
+ "@directus/storage-driver-cloudinary": "12.0.19",
181
+ "@directus/storage-driver-s3": "12.1.5",
182
182
  "@directus/storage-driver-local": "12.0.3",
183
- "@directus/storage-driver-supabase": "3.0.17",
184
- "@directus/system-data": "4.1.0",
185
- "@directus/storage-driver-gcs": "12.0.17",
186
- "@directus/utils": "13.2.2",
187
- "directus": "11.15.4",
188
- "@directus/validation": "2.0.17"
183
+ "@directus/system-data": "4.3.0",
184
+ "@directus/storage-driver-supabase": "3.0.19",
185
+ "@directus/validation": "2.0.19",
186
+ "@directus/utils": "13.3.1",
187
+ "directus": "11.16.1",
188
+ "@directus/storage-driver-azure": "12.0.19"
189
189
  },
190
190
  "devDependencies": {
191
191
  "@directus/tsconfig": "3.0.0",
@@ -228,8 +228,8 @@
228
228
  "knex-mock-client": "3.0.2",
229
229
  "typescript": "5.9.3",
230
230
  "vitest": "3.2.4",
231
- "@directus/schema-builder": "0.0.14",
232
- "@directus/types": "14.2.1"
231
+ "@directus/schema-builder": "0.0.16",
232
+ "@directus/types": "14.3.1"
233
233
  },
234
234
  "optionalDependencies": {
235
235
  "@keyv/redis": "3.0.1",
@@ -1,7 +0,0 @@
1
- /**
2
- * Checks if the defined redirect after successful SSO login is in the allow list
3
- * @param provider SSO provider name
4
- * @param redirect URL to redirect to after login
5
- * @returns True if the redirect is allowed, false otherwise
6
- */
7
- export declare function isLoginRedirectAllowed(provider: string, redirect: unknown): boolean;
@@ -1,39 +0,0 @@
1
- import { useEnv } from '@directus/env';
2
- import { toArray } from '@directus/utils';
3
- import { useLogger } from '../../logger/index.js';
4
- import isUrlAllowed from '../../utils/is-url-allowed.js';
5
- /**
6
- * Checks if the defined redirect after successful SSO login is in the allow list
7
- * @param provider SSO provider name
8
- * @param redirect URL to redirect to after login
9
- * @returns True if the redirect is allowed, false otherwise
10
- */
11
- export function isLoginRedirectAllowed(provider, redirect) {
12
- if (!redirect)
13
- return true; // empty redirect
14
- if (typeof redirect !== 'string')
15
- return false; // invalid type
16
- const env = useEnv();
17
- const publicUrl = env['PUBLIC_URL'];
18
- if (!URL.canParse(redirect)) {
19
- if (!redirect.startsWith('//')) {
20
- // should be a relative path like `/admin/test`
21
- return true;
22
- }
23
- // domain without protocol `//example.com/test`
24
- return false;
25
- }
26
- const envKey = `AUTH_${provider.toUpperCase()}_REDIRECT_ALLOW_LIST`;
27
- if (envKey in env) {
28
- if (isUrlAllowed(redirect, [...toArray(env[envKey]), publicUrl]))
29
- return true;
30
- }
31
- if (URL.canParse(publicUrl) === false) {
32
- useLogger().error('Invalid PUBLIC_URL for login redirect');
33
- return false;
34
- }
35
- const { protocol: redirectProtocol, host: redirectHost } = new URL(redirect);
36
- const { protocol: publicProtocol, host: publicHost } = new URL(publicUrl);
37
- // allow redirects to the defined PUBLIC_URL (protocol + host including port)
38
- return `${redirectProtocol}//${redirectHost}` === `${publicProtocol}//${publicHost}`;
39
- }