@directus/api 30.0.0 → 32.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 (197) hide show
  1. package/dist/app.js +7 -0
  2. package/dist/auth/auth.d.ts +2 -1
  3. package/dist/auth/auth.js +7 -2
  4. package/dist/auth/drivers/ldap.d.ts +0 -2
  5. package/dist/auth/drivers/ldap.js +9 -7
  6. package/dist/auth/drivers/oauth2.d.ts +0 -2
  7. package/dist/auth/drivers/oauth2.js +28 -11
  8. package/dist/auth/drivers/openid.d.ts +0 -2
  9. package/dist/auth/drivers/openid.js +28 -11
  10. package/dist/auth/drivers/saml.d.ts +0 -2
  11. package/dist/auth/drivers/saml.js +5 -5
  12. package/dist/auth.js +1 -2
  13. package/dist/cli/commands/bootstrap/index.js +12 -33
  14. package/dist/cli/commands/init/index.js +1 -1
  15. package/dist/cli/commands/schema/apply.d.ts +4 -0
  16. package/dist/cli/commands/schema/apply.js +26 -3
  17. package/dist/controllers/collections.js +7 -2
  18. package/dist/controllers/fields.js +31 -8
  19. package/dist/controllers/mcp.d.ts +2 -0
  20. package/dist/controllers/mcp.js +33 -0
  21. package/dist/controllers/server.js +26 -1
  22. package/dist/controllers/settings.js +9 -2
  23. package/dist/controllers/users.js +17 -7
  24. package/dist/controllers/versions.js +3 -2
  25. package/dist/database/errors/dialects/mssql.d.ts +1 -1
  26. package/dist/database/errors/dialects/mssql.js +18 -10
  27. package/dist/database/helpers/fn/types.js +3 -3
  28. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
  29. package/dist/database/helpers/schema/dialects/cockroachdb.js +13 -0
  30. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
  31. package/dist/database/helpers/schema/dialects/mssql.js +23 -0
  32. package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
  33. package/dist/database/helpers/schema/dialects/mysql.js +25 -0
  34. package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
  35. package/dist/database/helpers/schema/dialects/oracle.js +13 -0
  36. package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
  37. package/dist/database/helpers/schema/dialects/postgres.js +13 -0
  38. package/dist/database/helpers/schema/types.d.ts +5 -0
  39. package/dist/database/helpers/schema/types.js +6 -0
  40. package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
  41. package/dist/database/migrations/20250813A-add-mcp.js +18 -0
  42. package/dist/database/migrations/20251012A-add-field-searchable.d.ts +3 -0
  43. package/dist/database/migrations/20251012A-add-field-searchable.js +10 -0
  44. package/dist/database/migrations/20251014A-add-project-owner.d.ts +3 -0
  45. package/dist/database/migrations/20251014A-add-project-owner.js +37 -0
  46. package/dist/database/migrations/20251028A-add-retention-indexes.d.ts +3 -0
  47. package/dist/database/migrations/20251028A-add-retention-indexes.js +42 -0
  48. package/dist/database/run-ast/README.md +46 -0
  49. package/dist/database/run-ast/lib/apply-query/add-join.js +2 -2
  50. package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
  51. package/dist/database/run-ast/lib/apply-query/index.d.ts +0 -1
  52. package/dist/database/run-ast/lib/apply-query/index.js +4 -6
  53. package/dist/database/run-ast/lib/apply-query/search.js +2 -0
  54. package/dist/database/run-ast/lib/get-db-query.js +7 -6
  55. package/dist/database/run-ast/utils/generate-alias.d.ts +6 -0
  56. package/dist/database/run-ast/utils/generate-alias.js +57 -0
  57. package/dist/flows.js +1 -0
  58. package/dist/mcp/define.d.ts +2 -0
  59. package/dist/mcp/define.js +3 -0
  60. package/dist/mcp/index.d.ts +1 -0
  61. package/dist/mcp/index.js +1 -0
  62. package/dist/mcp/schema.d.ts +485 -0
  63. package/dist/mcp/schema.js +219 -0
  64. package/dist/mcp/server.d.ts +103 -0
  65. package/dist/mcp/server.js +310 -0
  66. package/dist/mcp/tools/assets.d.ts +3 -0
  67. package/dist/mcp/tools/assets.js +54 -0
  68. package/dist/mcp/tools/collections.d.ts +84 -0
  69. package/dist/mcp/tools/collections.js +90 -0
  70. package/dist/mcp/tools/fields.d.ts +101 -0
  71. package/dist/mcp/tools/fields.js +157 -0
  72. package/dist/mcp/tools/files.d.ts +235 -0
  73. package/dist/mcp/tools/files.js +103 -0
  74. package/dist/mcp/tools/flows.d.ts +323 -0
  75. package/dist/mcp/tools/flows.js +85 -0
  76. package/dist/mcp/tools/folders.d.ts +95 -0
  77. package/dist/mcp/tools/folders.js +96 -0
  78. package/dist/mcp/tools/index.d.ts +15 -0
  79. package/dist/mcp/tools/index.js +29 -0
  80. package/dist/mcp/tools/items.d.ts +87 -0
  81. package/dist/mcp/tools/items.js +141 -0
  82. package/dist/mcp/tools/operations.d.ts +171 -0
  83. package/dist/mcp/tools/operations.js +77 -0
  84. package/dist/mcp/tools/prompts/assets.md +8 -0
  85. package/dist/mcp/tools/prompts/collections.md +336 -0
  86. package/dist/mcp/tools/prompts/fields.md +521 -0
  87. package/dist/mcp/tools/prompts/files.md +180 -0
  88. package/dist/mcp/tools/prompts/flows.md +495 -0
  89. package/dist/mcp/tools/prompts/folders.md +34 -0
  90. package/dist/mcp/tools/prompts/index.d.ts +16 -0
  91. package/dist/mcp/tools/prompts/index.js +19 -0
  92. package/dist/mcp/tools/prompts/items.md +317 -0
  93. package/dist/mcp/tools/prompts/operations.md +721 -0
  94. package/dist/mcp/tools/prompts/relations.md +386 -0
  95. package/dist/mcp/tools/prompts/schema.md +130 -0
  96. package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
  97. package/dist/mcp/tools/prompts/system-prompt.md +44 -0
  98. package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
  99. package/dist/mcp/tools/relations.d.ts +73 -0
  100. package/dist/mcp/tools/relations.js +93 -0
  101. package/dist/mcp/tools/schema.d.ts +54 -0
  102. package/dist/mcp/tools/schema.js +317 -0
  103. package/dist/mcp/tools/system.d.ts +3 -0
  104. package/dist/mcp/tools/system.js +22 -0
  105. package/dist/mcp/tools/trigger-flow.d.ts +8 -0
  106. package/dist/mcp/tools/trigger-flow.js +48 -0
  107. package/dist/mcp/transport.d.ts +13 -0
  108. package/dist/mcp/transport.js +18 -0
  109. package/dist/mcp/types.d.ts +56 -0
  110. package/dist/mcp/types.js +1 -0
  111. package/dist/metrics/lib/create-metrics.js +16 -25
  112. package/dist/middleware/collection-exists.js +2 -2
  113. package/dist/operations/mail/index.js +3 -1
  114. package/dist/operations/mail/rate-limiter.d.ts +1 -0
  115. package/dist/operations/mail/rate-limiter.js +29 -0
  116. package/dist/permissions/modules/process-payload/process-payload.js +3 -10
  117. package/dist/permissions/modules/validate-access/validate-access.js +2 -3
  118. package/dist/schedules/metrics.js +6 -2
  119. package/dist/schedules/project.d.ts +4 -0
  120. package/dist/schedules/project.js +27 -0
  121. package/dist/services/authentication.js +36 -0
  122. package/dist/services/collections.d.ts +3 -3
  123. package/dist/services/collections.js +16 -1
  124. package/dist/services/fields.d.ts +21 -5
  125. package/dist/services/fields.js +109 -32
  126. package/dist/services/graphql/resolvers/query.js +1 -1
  127. package/dist/services/graphql/resolvers/system-admin.js +49 -5
  128. package/dist/services/graphql/schema/parse-query.js +8 -8
  129. package/dist/services/graphql/utils/aggregate-query.d.ts +1 -1
  130. package/dist/services/graphql/utils/aggregate-query.js +5 -1
  131. package/dist/services/graphql/utils/filter-replace-m2a.js +2 -1
  132. package/dist/services/import-export.d.ts +9 -1
  133. package/dist/services/import-export.js +287 -101
  134. package/dist/services/items.d.ts +1 -1
  135. package/dist/services/items.js +50 -24
  136. package/dist/services/mail/index.js +2 -0
  137. package/dist/services/mail/rate-limiter.d.ts +1 -0
  138. package/dist/services/mail/rate-limiter.js +29 -0
  139. package/dist/services/meta.js +28 -24
  140. package/dist/services/payload.d.ts +7 -3
  141. package/dist/services/payload.js +26 -12
  142. package/dist/services/schema.js +4 -1
  143. package/dist/services/server.d.ts +1 -0
  144. package/dist/services/server.js +15 -18
  145. package/dist/services/settings.d.ts +2 -1
  146. package/dist/services/settings.js +15 -0
  147. package/dist/services/tfa.d.ts +1 -1
  148. package/dist/services/tfa.js +20 -5
  149. package/dist/services/tus/server.js +14 -9
  150. package/dist/services/versions.d.ts +6 -4
  151. package/dist/services/versions.js +84 -25
  152. package/dist/telemetry/lib/get-report.js +4 -4
  153. package/dist/telemetry/lib/send-report.d.ts +6 -1
  154. package/dist/telemetry/lib/send-report.js +3 -1
  155. package/dist/telemetry/types/report.d.ts +17 -1
  156. package/dist/telemetry/utils/get-settings.d.ts +9 -0
  157. package/dist/telemetry/utils/get-settings.js +14 -0
  158. package/dist/test-utils/README.md +760 -0
  159. package/dist/test-utils/cache.d.ts +51 -0
  160. package/dist/test-utils/cache.js +59 -0
  161. package/dist/test-utils/database.d.ts +48 -0
  162. package/dist/test-utils/database.js +52 -0
  163. package/dist/test-utils/emitter.d.ts +35 -0
  164. package/dist/test-utils/emitter.js +38 -0
  165. package/dist/test-utils/fields-service.d.ts +28 -0
  166. package/dist/test-utils/fields-service.js +36 -0
  167. package/dist/test-utils/items-service.d.ts +23 -0
  168. package/dist/test-utils/items-service.js +37 -0
  169. package/dist/test-utils/knex.d.ts +164 -0
  170. package/dist/test-utils/knex.js +268 -0
  171. package/dist/test-utils/schema.d.ts +26 -0
  172. package/dist/test-utils/schema.js +35 -0
  173. package/dist/types/auth.d.ts +2 -3
  174. package/dist/utils/apply-diff.js +15 -0
  175. package/dist/utils/create-admin.d.ts +11 -0
  176. package/dist/utils/create-admin.js +50 -0
  177. package/dist/utils/get-schema.js +5 -3
  178. package/dist/utils/get-snapshot-diff.js +49 -5
  179. package/dist/utils/get-snapshot.js +13 -7
  180. package/dist/utils/sanitize-schema.d.ts +11 -4
  181. package/dist/utils/sanitize-schema.js +9 -6
  182. package/dist/utils/schedule.js +15 -19
  183. package/dist/utils/validate-diff.js +31 -0
  184. package/dist/utils/validate-snapshot.js +7 -0
  185. package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
  186. package/dist/utils/versioning/deep-map-with-schema.js +81 -0
  187. package/dist/utils/versioning/handle-version.d.ts +2 -2
  188. package/dist/utils/versioning/handle-version.js +47 -43
  189. package/dist/utils/versioning/split-recursive.d.ts +4 -0
  190. package/dist/utils/versioning/split-recursive.js +27 -0
  191. package/dist/websocket/controllers/hooks.js +12 -20
  192. package/dist/websocket/messages.d.ts +3 -3
  193. package/package.json +65 -66
  194. package/dist/cli/utils/defaults.d.ts +0 -4
  195. package/dist/cli/utils/defaults.js +0 -17
  196. package/dist/telemetry/utils/get-project-id.d.ts +0 -2
  197. package/dist/telemetry/utils/get-project-id.js +0 -4
@@ -0,0 +1,18 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('directus_settings', (table) => {
3
+ table.boolean('mcp_enabled').defaultTo(false).notNullable();
4
+ table.boolean('mcp_allow_deletes').defaultTo(false).notNullable();
5
+ table.string('mcp_prompts_collection').defaultTo(null).nullable();
6
+ table.boolean('mcp_system_prompt_enabled').defaultTo(true).notNullable();
7
+ table.text('mcp_system_prompt').defaultTo(null).nullable();
8
+ });
9
+ }
10
+ export async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('mcp_enabled');
13
+ table.dropColumn('mcp_allow_deletes');
14
+ table.dropColumn('mcp_prompts_collection');
15
+ table.dropColumn('mcp_system_prompt_enabled');
16
+ table.dropColumn('mcp_system_prompt');
17
+ });
18
+ }
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,10 @@
1
+ export async function up(knex) {
2
+ await knex.schema.alterTable('directus_fields', (table) => {
3
+ table.boolean('searchable').defaultTo(true).notNullable();
4
+ });
5
+ }
6
+ export async function down(knex) {
7
+ await knex.schema.alterTable('directus_fields', (table) => {
8
+ table.dropColumn('searchable');
9
+ });
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,37 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { toBoolean } from '@directus/utils';
3
+ import { SettingsService } from '../../services/settings.js';
4
+ import { getSchema } from '../../utils/get-schema.js';
5
+ import { email } from 'zod';
6
+ export async function up(knex) {
7
+ await knex.schema.alterTable('directus_settings', (table) => {
8
+ table.string('project_owner');
9
+ table.string('project_usage');
10
+ table.string('org_name');
11
+ table.boolean('product_updates');
12
+ table.string('project_status');
13
+ table.dropColumn('accepted_terms');
14
+ });
15
+ const env = useEnv();
16
+ const settingsService = new SettingsService({ schema: await getSchema() });
17
+ if (email().safeParse(env['PROJECT_OWNER']).success) {
18
+ await settingsService.setOwner({
19
+ project_owner: env['PROJECT_OWNER'],
20
+ org_name: null,
21
+ project_usage: null,
22
+ product_updates: false,
23
+ });
24
+ }
25
+ }
26
+ export async function down(knex) {
27
+ const env = useEnv();
28
+ const acceptedTerms = toBoolean(env['ACCEPT_TERMS']);
29
+ await knex.schema.alterTable('directus_settings', (table) => {
30
+ table.dropColumn('project_owner');
31
+ table.dropColumn('project_usage');
32
+ table.dropColumn('org_name');
33
+ table.dropColumn('product_updates');
34
+ table.dropColumn('project_status');
35
+ table.boolean('accepted_terms').defaultTo(acceptedTerms);
36
+ });
37
+ }
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,42 @@
1
+ import { FieldsService } from '../../services/fields.js';
2
+ import { getSchema } from '../../utils/get-schema.js';
3
+ import { transaction } from '../../utils/transaction.js';
4
+ import { getHelpers } from '../helpers/index.js';
5
+ import { getDatabaseClient } from '../index.js';
6
+ const RETENTION_INDEXES = [
7
+ { collection: 'directus_activity', field: 'timestamp', ignore: [] },
8
+ // MySQL is ignored because it already has an index on revisions.parent
9
+ { collection: 'directus_revisions', field: 'parent', ignore: ['mysql'] },
10
+ ];
11
+ export async function up(knex) {
12
+ const client = getDatabaseClient(knex);
13
+ const helpers = getHelpers(knex);
14
+ const schema = await getSchema();
15
+ const service = new FieldsService({ knex, schema });
16
+ for (const { collection, field, ignore } of RETENTION_INDEXES) {
17
+ if (ignore.includes(client))
18
+ continue;
19
+ const existingColumn = await service.columnInfo(collection, field);
20
+ if (!existingColumn.is_indexed) {
21
+ await helpers.schema.createIndex(collection, field, { attemptConcurrentIndex: true });
22
+ }
23
+ }
24
+ }
25
+ export async function down(knex) {
26
+ const client = getDatabaseClient(knex);
27
+ const helpers = getHelpers(knex);
28
+ const schema = await getSchema();
29
+ const service = new FieldsService({ knex, schema });
30
+ for (const { collection, field, ignore } of RETENTION_INDEXES) {
31
+ if (ignore.includes(client))
32
+ continue;
33
+ const existingColumn = await service.columnInfo(collection, field);
34
+ if (existingColumn.is_indexed) {
35
+ await transaction(knex, async (trx) => {
36
+ await trx.schema.alterTable(collection, async (table) => {
37
+ table.dropIndex([field], helpers.schema.generateIndexName('index', collection, field));
38
+ });
39
+ });
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,46 @@
1
+ # Internal RunAst Documentation
2
+
3
+ As the `runAst()` function is a crucial and complex part of Directus, this document intends to get you started by
4
+ providing a high level overview.
5
+
6
+ ## AST
7
+
8
+ When a request is made through REST, GQL or WebSockets, we internally turn this request into an AST before this AST
9
+ get's translated into one or more SQL requests which are then send to the database.
10
+
11
+ On the root level of an AST is the root node which then references a list of child nodes. Each child node can be either
12
+ a primitive field (FieldNode), a relational field (M2ONode, O2MNode, A2MNode) or a function field (FunctionFieldNode).
13
+ Relational fields can also have additional children and nested queries.
14
+
15
+ ### FieldNode
16
+
17
+ Describes a primitive field and is also a leaf node of the AST.
18
+
19
+ ### Relational Fields
20
+
21
+ The M2ONode, O2MNode and A2MNode all will recursively call the `runAst()` function and the resulting data will then be
22
+ injected into the data of the level above.
23
+
24
+ ### FunctionFieldNode
25
+
26
+ The only case where a function field node get's inserted into the ast, is for the count(o2m_relation) case, all other
27
+ functions are kept as FieldNode's with the name containing the function.
28
+
29
+ ## RunAst()
30
+
31
+ The `runAst()` does translates the AST into an executable SQL request, requests the appropiate data from the database
32
+ and then returns a single or a list of items as the result.
33
+
34
+ ### GetDBQuery()
35
+
36
+ The RootNode and relational fields have each their own query parameters. These parameters need to be applied to the SQL
37
+ query. This is done by the `getDBQuery()` function which internally uses `applyQuery()`.
38
+
39
+ <!-- TODO: Describe in larger detail what the GetDBQuery exactly does, i.e. why and when does it need an inner query, why does it sometimes call applyQuery() multiple times in different places -->
40
+
41
+ #### ApplyQuery()
42
+
43
+ `applyQuery()` takes the query and modifies the SQL request so that filters, sorts and all the other variants of query
44
+ parameters are applied.A special behaviour is filtering, sorting or other parameters on nested fields. To make this
45
+ possible, the `addJoin()` joins the required tables and then registers itself into the aliasMap so that the next time
46
+ the same collection should be joined, instead it will just reuse the already existing join.
@@ -2,7 +2,7 @@ import { InvalidQueryError } from '@directus/errors';
2
2
  import { clone } from 'lodash-es';
3
3
  import { getRelationInfo } from '../../../../utils/get-relation-info.js';
4
4
  import { getHelpers } from '../../../helpers/index.js';
5
- import { generateAlias } from './index.js';
5
+ import { generateJoinAlias } from '../../utils/generate-alias.js';
6
6
  export function addJoin({ path, collection, aliasMap, rootQuery, schema, knex }) {
7
7
  let hasMultiRelational = false;
8
8
  let isJoinAdded = false;
@@ -22,7 +22,7 @@ export function addJoin({ path, collection, aliasMap, rootQuery, schema, knex })
22
22
  ? aliasMap[`${parentFields}.${pathParts[0]}`]?.alias
23
23
  : aliasMap[pathParts[0]]?.alias;
24
24
  if (!existingAlias) {
25
- const alias = generateAlias();
25
+ const alias = generateJoinAlias(parentCollection, pathParts, relationType, parentFields);
26
26
  const aliasKey = parentFields ? `${parentFields}.${pathParts[0]}` : pathParts[0];
27
27
  const aliasedParentCollection = aliasMap[parentFields ?? '']?.alias || parentCollection;
28
28
  aliasMap[aliasKey] = { alias, collection: '' };
@@ -1,8 +1,8 @@
1
1
  import type { FieldOverview } from '@directus/types';
2
2
  export declare function getFilterType(fields: Record<string, FieldOverview>, key: string, collection?: string): {
3
- type: "string" | "boolean" | "json" | "text" | "integer" | "float" | "alias" | "uuid" | "binary" | "time" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
3
+ type: "string" | "boolean" | "binary" | "time" | "text" | "integer" | "float" | "alias" | "uuid" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "json" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
4
4
  special?: never;
5
5
  } | {
6
- type: "string" | "boolean" | "json" | "text" | "integer" | "float" | "alias" | "uuid" | "binary" | "time" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
6
+ type: "string" | "boolean" | "binary" | "time" | "text" | "integer" | "float" | "alias" | "uuid" | "dateTime" | "timestamp" | "bigInteger" | "date" | "decimal" | "json" | "hash" | "csv" | "geometry" | "geometry.Point" | "geometry.LineString" | "geometry.Polygon" | "geometry.MultiPoint" | "geometry.MultiLineString" | "geometry.MultiPolygon" | "unknown";
7
7
  special: string[];
8
8
  };
@@ -1,7 +1,6 @@
1
1
  import type { Filter, Permission, Query, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  import type { AliasMap } from '../../../../utils/get-column-path.js';
4
- export declare const generateAlias: (size?: number) => string;
5
4
  type ApplyQueryOptions = {
6
5
  aliasMap?: AliasMap;
7
6
  isInnerQuery?: boolean;
@@ -1,14 +1,12 @@
1
- import { customAlphabet } from 'nanoid/non-secure';
2
1
  import { getHelpers } from '../../../helpers/index.js';
3
2
  import { applyCaseWhen } from '../../utils/apply-case-when.js';
4
3
  import { getColumn } from '../../utils/get-column.js';
5
- import { applyLimit, applyOffset } from './pagination.js';
6
- import { joinFilterWithCases } from './join-filter-with-cases.js';
7
- import { applySort } from './sort.js';
4
+ import { applyAggregate } from './aggregate.js';
8
5
  import { applyFilter } from './filter/index.js';
6
+ import { joinFilterWithCases } from './join-filter-with-cases.js';
7
+ import { applyLimit, applyOffset } from './pagination.js';
9
8
  import { applySearch } from './search.js';
10
- import { applyAggregate } from './aggregate.js';
11
- export const generateAlias = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
9
+ import { applySort } from './sort.js';
12
10
  /**
13
11
  * Apply the Query to a given Knex query builder instance
14
12
  */
@@ -9,6 +9,8 @@ export function applySearch(knex, schema, dbQuery, searchQuery, collection, alia
9
9
  const { number: numberHelper } = getHelpers(knex);
10
10
  const allowedFields = new Set(permissions.filter((p) => p.collection === collection).flatMap((p) => p.fields ?? []));
11
11
  let fields = Object.entries(schema.collections[collection].fields);
12
+ // filter out fields that are not searchable
13
+ fields = fields.filter(([_name, field]) => field.searchable !== false && field.special.includes('conceal') !== true);
12
14
  const { cases, caseMap } = getCases(collection, permissions, []);
13
15
  // Add field restrictions if non-admin and "everything" is not allowed
14
16
  if (cases.length !== 0 && !allowedFields.has('*')) {
@@ -1,16 +1,17 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { cloneDeep } from 'lodash-es';
3
- import { applySort } from './apply-query/sort.js';
4
3
  import { getCollectionFromAlias } from '../../../utils/get-collection-from-alias.js';
5
- import { getColumn } from '../utils/get-column.js';
6
4
  import { getHelpers } from '../../helpers/index.js';
7
5
  import { applyCaseWhen } from '../utils/apply-case-when.js';
6
+ import { generateQueryAlias } from '../utils/generate-alias.js';
8
7
  import { getColumnPreprocessor } from '../utils/get-column-pre-processor.js';
8
+ import { getColumn } from '../utils/get-column.js';
9
9
  import { getNodeAlias } from '../utils/get-field-alias.js';
10
10
  import { getInnerQueryColumnPreProcessor } from '../utils/get-inner-query-column-pre-processor.js';
11
11
  import { withPreprocessBindings } from '../utils/with-preprocess-bindings.js';
12
- import applyQuery, { generateAlias } from './apply-query/index.js';
12
+ import applyQuery from './apply-query/index.js';
13
13
  import { applyLimit } from './apply-query/pagination.js';
14
+ import { applySort } from './apply-query/sort.js';
14
15
  export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissions, permissionsOnly }, { knex, schema }) {
15
16
  const aliasMap = Object.create(null);
16
17
  const env = useEnv();
@@ -95,11 +96,11 @@ export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissi
95
96
  if (needsInnerQuery) {
96
97
  let orderByString = '';
97
98
  const orderByFields = [];
98
- sortRecords.map((sortRecord) => {
99
+ sortRecords.map((sortRecord, index) => {
99
100
  if (orderByString.length !== 0) {
100
101
  orderByString += ', ';
101
102
  }
102
- const sortAlias = `sort_${generateAlias()}`;
103
+ const sortAlias = generateQueryAlias(table, queryCopy, `sort_${index}_${sortRecord.column}_${sortRecord.order}`);
103
104
  let orderByColumn;
104
105
  if (sortRecord.column.includes('.')) {
105
106
  const [alias, field] = sortRecord.column.split('.');
@@ -146,7 +147,7 @@ export function getDBQuery({ table, fieldNodes, o2mNodes, query, cases, permissi
146
147
  }
147
148
  if (!needsInnerQuery)
148
149
  return dbQuery;
149
- const innerCaseWhenAliasPrefix = generateAlias();
150
+ const innerCaseWhenAliasPrefix = generateQueryAlias(table, queryCopy, 'inner_case_when');
150
151
  if (hasCaseWhen) {
151
152
  /* If there are cases, we need to employ a trick in order to evaluate the case/when structure in the inner query,
152
153
  while passing the result of the evaluation to the outer query. The case/when needs to be evaluated in the inner
@@ -0,0 +1,6 @@
1
+ import type { Query } from '@directus/types';
2
+ import type { FnHelperOptions } from '../../helpers/fn/types.js';
3
+ export declare function generateAlias(context?: string): string;
4
+ export declare function generateQueryAlias(table: string, query: Query, path?: string): string;
5
+ export declare function generateRelationalQueryAlias(table: string, column: string, collectionName: string, options?: FnHelperOptions): string;
6
+ export declare function generateJoinAlias(collection: string, path: string[], relationType: string | null, parentFields?: string): string;
@@ -0,0 +1,57 @@
1
+ import { getSimpleHash } from '@directus/utils';
2
+ import { customAlphabet } from 'nanoid/non-secure';
3
+ // Fallback to original random alias generator
4
+ const generateRandomAlias = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
5
+ // Context-aware alias generator
6
+ export function generateAlias(context = '') {
7
+ if (context) {
8
+ return generateDeterministicAlias(context);
9
+ }
10
+ return generateRandomAlias();
11
+ }
12
+ // Create a deterministic alias for general query contexts
13
+ export function generateQueryAlias(table, query, path = '') {
14
+ const context = JSON.stringify({
15
+ table,
16
+ path,
17
+ sort: query.sort,
18
+ group: query.group,
19
+ aggregate: query.aggregate,
20
+ // Exclude: limit, offset, page, search, filter - these are execution parameters
21
+ // that don't affect the underlying query structure requiring aliases
22
+ });
23
+ return generateDeterministicAlias(context);
24
+ }
25
+ // Create a deterministic context for relational count aliases
26
+ export function generateRelationalQueryAlias(table, column, collectionName, options) {
27
+ const context = JSON.stringify({
28
+ table,
29
+ column,
30
+ collectionName,
31
+ filter: options?.relationalCountOptions?.query?.filter,
32
+ });
33
+ return generateDeterministicAlias(context);
34
+ }
35
+ // Create a deterministic alias for join contexts
36
+ export function generateJoinAlias(collection, path, relationType, parentFields = '') {
37
+ const context = JSON.stringify({
38
+ collection,
39
+ path: path.join('.'),
40
+ relationType,
41
+ parentFields,
42
+ });
43
+ return generateDeterministicAlias(context);
44
+ }
45
+ // Generate deterministic alias based on context
46
+ function generateDeterministicAlias(context = '') {
47
+ const hash = getSimpleHash(context);
48
+ const alphabet = 'abcdefghijklmnopqrstuvwxyz';
49
+ let result = '';
50
+ let num = parseInt(hash, 16);
51
+ // Generate 5 character alias
52
+ for (let i = 0; i < 5; i++) {
53
+ result += alphabet[num % alphabet.length];
54
+ num = Math.floor(num / alphabet.length);
55
+ }
56
+ return result;
57
+ }
package/dist/flows.js CHANGED
@@ -291,6 +291,7 @@ class FlowManager {
291
291
  [ACCOUNTABILITY_KEY]: context?.['accountability'] ?? null,
292
292
  [ENV_KEY]: this.envs,
293
293
  };
294
+ context['flow'] ??= flow;
294
295
  let nextOperation = flow.operation;
295
296
  let lastOperationStatus = 'unknown';
296
297
  const steps = [];
@@ -0,0 +1,2 @@
1
+ import type { ToolConfig } from './types.js';
2
+ export declare function defineTool<Args>(tool: ToolConfig<Args>): ToolConfig<Args>;
@@ -0,0 +1,3 @@
1
+ export function defineTool(tool) {
2
+ return tool;
3
+ }
@@ -0,0 +1 @@
1
+ export * from './server.js';
@@ -0,0 +1 @@
1
+ export * from './server.js';