@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.
- package/dist/app.js +7 -0
- package/dist/auth/auth.d.ts +2 -1
- package/dist/auth/auth.js +7 -2
- package/dist/auth/drivers/ldap.d.ts +0 -2
- package/dist/auth/drivers/ldap.js +9 -7
- package/dist/auth/drivers/oauth2.d.ts +0 -2
- package/dist/auth/drivers/oauth2.js +28 -11
- package/dist/auth/drivers/openid.d.ts +0 -2
- package/dist/auth/drivers/openid.js +28 -11
- package/dist/auth/drivers/saml.d.ts +0 -2
- package/dist/auth/drivers/saml.js +5 -5
- package/dist/auth.js +1 -2
- package/dist/cli/commands/bootstrap/index.js +12 -33
- package/dist/cli/commands/init/index.js +1 -1
- package/dist/cli/commands/schema/apply.d.ts +4 -0
- package/dist/cli/commands/schema/apply.js +26 -3
- package/dist/controllers/collections.js +7 -2
- package/dist/controllers/fields.js +31 -8
- package/dist/controllers/mcp.d.ts +2 -0
- package/dist/controllers/mcp.js +33 -0
- package/dist/controllers/server.js +26 -1
- package/dist/controllers/settings.js +9 -2
- package/dist/controllers/users.js +17 -7
- package/dist/controllers/versions.js +3 -2
- package/dist/database/errors/dialects/mssql.d.ts +1 -1
- package/dist/database/errors/dialects/mssql.js +18 -10
- package/dist/database/helpers/fn/types.js +3 -3
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +13 -0
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mssql.js +23 -0
- package/dist/database/helpers/schema/dialects/mysql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mysql.js +25 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/oracle.js +13 -0
- package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/postgres.js +13 -0
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +6 -0
- package/dist/database/migrations/20250813A-add-mcp.d.ts +3 -0
- package/dist/database/migrations/20250813A-add-mcp.js +18 -0
- package/dist/database/migrations/20251012A-add-field-searchable.d.ts +3 -0
- package/dist/database/migrations/20251012A-add-field-searchable.js +10 -0
- package/dist/database/migrations/20251014A-add-project-owner.d.ts +3 -0
- package/dist/database/migrations/20251014A-add-project-owner.js +37 -0
- package/dist/database/migrations/20251028A-add-retention-indexes.d.ts +3 -0
- package/dist/database/migrations/20251028A-add-retention-indexes.js +42 -0
- package/dist/database/run-ast/README.md +46 -0
- package/dist/database/run-ast/lib/apply-query/add-join.js +2 -2
- package/dist/database/run-ast/lib/apply-query/filter/get-filter-type.d.ts +2 -2
- package/dist/database/run-ast/lib/apply-query/index.d.ts +0 -1
- package/dist/database/run-ast/lib/apply-query/index.js +4 -6
- package/dist/database/run-ast/lib/apply-query/search.js +2 -0
- package/dist/database/run-ast/lib/get-db-query.js +7 -6
- package/dist/database/run-ast/utils/generate-alias.d.ts +6 -0
- package/dist/database/run-ast/utils/generate-alias.js +57 -0
- package/dist/flows.js +1 -0
- package/dist/mcp/define.d.ts +2 -0
- package/dist/mcp/define.js +3 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/schema.d.ts +485 -0
- package/dist/mcp/schema.js +219 -0
- package/dist/mcp/server.d.ts +103 -0
- package/dist/mcp/server.js +310 -0
- package/dist/mcp/tools/assets.d.ts +3 -0
- package/dist/mcp/tools/assets.js +54 -0
- package/dist/mcp/tools/collections.d.ts +84 -0
- package/dist/mcp/tools/collections.js +90 -0
- package/dist/mcp/tools/fields.d.ts +101 -0
- package/dist/mcp/tools/fields.js +157 -0
- package/dist/mcp/tools/files.d.ts +235 -0
- package/dist/mcp/tools/files.js +103 -0
- package/dist/mcp/tools/flows.d.ts +323 -0
- package/dist/mcp/tools/flows.js +85 -0
- package/dist/mcp/tools/folders.d.ts +95 -0
- package/dist/mcp/tools/folders.js +96 -0
- package/dist/mcp/tools/index.d.ts +15 -0
- package/dist/mcp/tools/index.js +29 -0
- package/dist/mcp/tools/items.d.ts +87 -0
- package/dist/mcp/tools/items.js +141 -0
- package/dist/mcp/tools/operations.d.ts +171 -0
- package/dist/mcp/tools/operations.js +77 -0
- package/dist/mcp/tools/prompts/assets.md +8 -0
- package/dist/mcp/tools/prompts/collections.md +336 -0
- package/dist/mcp/tools/prompts/fields.md +521 -0
- package/dist/mcp/tools/prompts/files.md +180 -0
- package/dist/mcp/tools/prompts/flows.md +495 -0
- package/dist/mcp/tools/prompts/folders.md +34 -0
- package/dist/mcp/tools/prompts/index.d.ts +16 -0
- package/dist/mcp/tools/prompts/index.js +19 -0
- package/dist/mcp/tools/prompts/items.md +317 -0
- package/dist/mcp/tools/prompts/operations.md +721 -0
- package/dist/mcp/tools/prompts/relations.md +386 -0
- package/dist/mcp/tools/prompts/schema.md +130 -0
- package/dist/mcp/tools/prompts/system-prompt-description.md +1 -0
- package/dist/mcp/tools/prompts/system-prompt.md +44 -0
- package/dist/mcp/tools/prompts/trigger-flow.md +214 -0
- package/dist/mcp/tools/relations.d.ts +73 -0
- package/dist/mcp/tools/relations.js +93 -0
- package/dist/mcp/tools/schema.d.ts +54 -0
- package/dist/mcp/tools/schema.js +317 -0
- package/dist/mcp/tools/system.d.ts +3 -0
- package/dist/mcp/tools/system.js +22 -0
- package/dist/mcp/tools/trigger-flow.d.ts +8 -0
- package/dist/mcp/tools/trigger-flow.js +48 -0
- package/dist/mcp/transport.d.ts +13 -0
- package/dist/mcp/transport.js +18 -0
- package/dist/mcp/types.d.ts +56 -0
- package/dist/mcp/types.js +1 -0
- package/dist/metrics/lib/create-metrics.js +16 -25
- package/dist/middleware/collection-exists.js +2 -2
- package/dist/operations/mail/index.js +3 -1
- package/dist/operations/mail/rate-limiter.d.ts +1 -0
- package/dist/operations/mail/rate-limiter.js +29 -0
- package/dist/permissions/modules/process-payload/process-payload.js +3 -10
- package/dist/permissions/modules/validate-access/validate-access.js +2 -3
- package/dist/schedules/metrics.js +6 -2
- package/dist/schedules/project.d.ts +4 -0
- package/dist/schedules/project.js +27 -0
- package/dist/services/authentication.js +36 -0
- package/dist/services/collections.d.ts +3 -3
- package/dist/services/collections.js +16 -1
- package/dist/services/fields.d.ts +21 -5
- package/dist/services/fields.js +109 -32
- package/dist/services/graphql/resolvers/query.js +1 -1
- package/dist/services/graphql/resolvers/system-admin.js +49 -5
- package/dist/services/graphql/schema/parse-query.js +8 -8
- package/dist/services/graphql/utils/aggregate-query.d.ts +1 -1
- package/dist/services/graphql/utils/aggregate-query.js +5 -1
- package/dist/services/graphql/utils/filter-replace-m2a.js +2 -1
- package/dist/services/import-export.d.ts +9 -1
- package/dist/services/import-export.js +287 -101
- package/dist/services/items.d.ts +1 -1
- package/dist/services/items.js +50 -24
- package/dist/services/mail/index.js +2 -0
- package/dist/services/mail/rate-limiter.d.ts +1 -0
- package/dist/services/mail/rate-limiter.js +29 -0
- package/dist/services/meta.js +28 -24
- package/dist/services/payload.d.ts +7 -3
- package/dist/services/payload.js +26 -12
- package/dist/services/schema.js +4 -1
- package/dist/services/server.d.ts +1 -0
- package/dist/services/server.js +15 -18
- package/dist/services/settings.d.ts +2 -1
- package/dist/services/settings.js +15 -0
- package/dist/services/tfa.d.ts +1 -1
- package/dist/services/tfa.js +20 -5
- package/dist/services/tus/server.js +14 -9
- package/dist/services/versions.d.ts +6 -4
- package/dist/services/versions.js +84 -25
- package/dist/telemetry/lib/get-report.js +4 -4
- package/dist/telemetry/lib/send-report.d.ts +6 -1
- package/dist/telemetry/lib/send-report.js +3 -1
- package/dist/telemetry/types/report.d.ts +17 -1
- package/dist/telemetry/utils/get-settings.d.ts +9 -0
- package/dist/telemetry/utils/get-settings.js +14 -0
- package/dist/test-utils/README.md +760 -0
- package/dist/test-utils/cache.d.ts +51 -0
- package/dist/test-utils/cache.js +59 -0
- package/dist/test-utils/database.d.ts +48 -0
- package/dist/test-utils/database.js +52 -0
- package/dist/test-utils/emitter.d.ts +35 -0
- package/dist/test-utils/emitter.js +38 -0
- package/dist/test-utils/fields-service.d.ts +28 -0
- package/dist/test-utils/fields-service.js +36 -0
- package/dist/test-utils/items-service.d.ts +23 -0
- package/dist/test-utils/items-service.js +37 -0
- package/dist/test-utils/knex.d.ts +164 -0
- package/dist/test-utils/knex.js +268 -0
- package/dist/test-utils/schema.d.ts +26 -0
- package/dist/test-utils/schema.js +35 -0
- package/dist/types/auth.d.ts +2 -3
- package/dist/utils/apply-diff.js +15 -0
- package/dist/utils/create-admin.d.ts +11 -0
- package/dist/utils/create-admin.js +50 -0
- package/dist/utils/get-schema.js +5 -3
- package/dist/utils/get-snapshot-diff.js +49 -5
- package/dist/utils/get-snapshot.js +13 -7
- package/dist/utils/sanitize-schema.d.ts +11 -4
- package/dist/utils/sanitize-schema.js +9 -6
- package/dist/utils/schedule.js +15 -19
- package/dist/utils/validate-diff.js +31 -0
- package/dist/utils/validate-snapshot.js +7 -0
- package/dist/utils/versioning/deep-map-with-schema.d.ts +23 -0
- package/dist/utils/versioning/deep-map-with-schema.js +81 -0
- package/dist/utils/versioning/handle-version.d.ts +2 -2
- package/dist/utils/versioning/handle-version.js +47 -43
- package/dist/utils/versioning/split-recursive.d.ts +4 -0
- package/dist/utils/versioning/split-recursive.js +27 -0
- package/dist/websocket/controllers/hooks.js +12 -20
- package/dist/websocket/messages.d.ts +3 -3
- package/package.json +65 -66
- package/dist/cli/utils/defaults.d.ts +0 -4
- package/dist/cli/utils/defaults.js +0 -17
- package/dist/telemetry/utils/get-project-id.d.ts +0 -2
- 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,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,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,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 {
|
|
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 =
|
|
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" | "
|
|
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" | "
|
|
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 {
|
|
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 {
|
|
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
|
|
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_${
|
|
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 =
|
|
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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './server.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './server.js';
|