@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
|
@@ -6,8 +6,6 @@ import { pick } from 'lodash-es';
|
|
|
6
6
|
* @returns sanitized collection
|
|
7
7
|
*/
|
|
8
8
|
export function sanitizeCollection(collection) {
|
|
9
|
-
if (!collection)
|
|
10
|
-
return collection;
|
|
11
9
|
return pick(collection, ['collection', 'fields', 'meta', 'schema.name']);
|
|
12
10
|
}
|
|
13
11
|
/**
|
|
@@ -18,8 +16,6 @@ export function sanitizeCollection(collection) {
|
|
|
18
16
|
* @returns sanitized field
|
|
19
17
|
*/
|
|
20
18
|
export function sanitizeField(field, sanitizeAllSchema = false) {
|
|
21
|
-
if (!field)
|
|
22
|
-
return field;
|
|
23
19
|
const defaultPaths = ['collection', 'field', 'type', 'meta', 'name', 'children'];
|
|
24
20
|
const pickedPaths = sanitizeAllSchema
|
|
25
21
|
? defaultPaths
|
|
@@ -71,8 +67,6 @@ export function sanitizeColumn(column) {
|
|
|
71
67
|
* @returns sanitized relation
|
|
72
68
|
*/
|
|
73
69
|
export function sanitizeRelation(relation) {
|
|
74
|
-
if (!relation)
|
|
75
|
-
return relation;
|
|
76
70
|
return pick(relation, [
|
|
77
71
|
'collection',
|
|
78
72
|
'field',
|
|
@@ -87,3 +81,12 @@ export function sanitizeRelation(relation) {
|
|
|
87
81
|
'schema.on_delete',
|
|
88
82
|
]);
|
|
89
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Pick certain specific system field properties that should be compared when performing diff
|
|
86
|
+
*
|
|
87
|
+
* @param field field to sanitize
|
|
88
|
+
* @returns sanitized system field
|
|
89
|
+
*/
|
|
90
|
+
export function sanitizeSystemField(field) {
|
|
91
|
+
return pick(field, ['collection', 'field', 'schema.is_indexed']);
|
|
92
|
+
}
|
package/dist/utils/schedule.js
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import schedule from 'node-schedule';
|
|
1
|
+
import { CronJob, validateCronExpression } from 'cron';
|
|
3
2
|
import { SynchronizedClock } from '../synchronization.js';
|
|
4
3
|
export function validateCron(rule) {
|
|
5
|
-
|
|
6
|
-
cron.parseExpression(rule);
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
return true;
|
|
4
|
+
return validateCronExpression(rule).valid;
|
|
12
5
|
}
|
|
13
6
|
export function scheduleSynchronizedJob(id, rule, cb) {
|
|
14
7
|
const clock = new SynchronizedClock(`${id}:${rule}`);
|
|
15
|
-
const job =
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
const job = CronJob.from({
|
|
9
|
+
cronTime: rule,
|
|
10
|
+
onTick: async (fireDate) => {
|
|
11
|
+
// Get next execution time for synchronization
|
|
12
|
+
const nextDate = job.nextDate();
|
|
13
|
+
const nextTimestamp = nextDate.toMillis();
|
|
14
|
+
const wasSet = await clock.set(nextTimestamp);
|
|
15
|
+
if (wasSet) {
|
|
16
|
+
await cb(fireDate);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
start: true,
|
|
24
20
|
});
|
|
25
21
|
const stop = async () => {
|
|
26
|
-
job.
|
|
22
|
+
job.stop();
|
|
27
23
|
await clock.reset();
|
|
28
24
|
};
|
|
29
25
|
return { stop };
|
|
@@ -31,6 +31,13 @@ const applyJoiSchema = Joi.object({
|
|
|
31
31
|
diff: Joi.array().items(deepDiffSchema).required(),
|
|
32
32
|
}))
|
|
33
33
|
.required(),
|
|
34
|
+
systemFields: Joi.array()
|
|
35
|
+
.items(Joi.object({
|
|
36
|
+
collection: Joi.string().required(),
|
|
37
|
+
field: Joi.string().required(),
|
|
38
|
+
diff: Joi.array().items(deepDiffSchema).required(),
|
|
39
|
+
}))
|
|
40
|
+
.required(),
|
|
34
41
|
relations: Joi.array()
|
|
35
42
|
.items(Joi.object({
|
|
36
43
|
collection: Joi.string().required(),
|
|
@@ -53,6 +60,7 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
|
|
|
53
60
|
// No changes to apply
|
|
54
61
|
if (applyDiff.diff.collections.length === 0 &&
|
|
55
62
|
applyDiff.diff.fields.length === 0 &&
|
|
63
|
+
applyDiff.diff.systemFields.length === 0 &&
|
|
56
64
|
applyDiff.diff.relations.length === 0) {
|
|
57
65
|
return false;
|
|
58
66
|
}
|
|
@@ -97,6 +105,29 @@ export function validateApplyDiff(applyDiff, currentSnapshotWithHash) {
|
|
|
97
105
|
}
|
|
98
106
|
}
|
|
99
107
|
}
|
|
108
|
+
for (const diffSystemField of applyDiff.diff.systemFields) {
|
|
109
|
+
const systemField = `${diffSystemField.collection}.${diffSystemField.field}`;
|
|
110
|
+
if (!diffSystemField.diff[0])
|
|
111
|
+
continue;
|
|
112
|
+
if (diffSystemField.diff[0].kind !== DiffKind.EDIT) {
|
|
113
|
+
let action = 'update array'; // DiffKind.ARRAY
|
|
114
|
+
if (diffSystemField.diff[0].kind === DiffKind.NEW) {
|
|
115
|
+
action = 'create';
|
|
116
|
+
}
|
|
117
|
+
else if (diffSystemField.diff[0].kind === DiffKind.DELETE) {
|
|
118
|
+
action = 'delete';
|
|
119
|
+
}
|
|
120
|
+
throw new InvalidPayloadError({
|
|
121
|
+
reason: `Provided diff is trying to ${action} field "${systemField}" but this action is not supported. Please generate a new diff and try again`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const pathString = diffSystemField.diff[0].path?.join('.') ?? '';
|
|
125
|
+
if (pathString.length === 0 || pathString !== 'schema.is_indexed') {
|
|
126
|
+
throw new InvalidPayloadError({
|
|
127
|
+
reason: `Provided diff is trying to alter property "${pathString}" on "${systemField}" but currently only "schema.is_indexed" is supported. Please generate a new diff and try again`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
100
131
|
for (const diffRelation of applyDiff.diff.relations) {
|
|
101
132
|
let relation = `${diffRelation.collection}.${diffRelation.field}`;
|
|
102
133
|
if (diffRelation.related_collection)
|
|
@@ -33,6 +33,13 @@ const snapshotJoiSchema = Joi.object({
|
|
|
33
33
|
.valid(...TYPES, ...ALIAS_TYPES)
|
|
34
34
|
.allow(null),
|
|
35
35
|
})),
|
|
36
|
+
systemFields: Joi.array().items(Joi.object({
|
|
37
|
+
collection: Joi.string(),
|
|
38
|
+
field: Joi.string(),
|
|
39
|
+
schema: Joi.object({
|
|
40
|
+
is_indexed: Joi.bool(),
|
|
41
|
+
}),
|
|
42
|
+
})),
|
|
36
43
|
relations: Joi.array().items(Joi.object({
|
|
37
44
|
collection: Joi.string(),
|
|
38
45
|
field: Joi.string(),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CollectionOverview, FieldOverview, Relation, SchemaOverview } from '@directus/types';
|
|
2
|
+
import { type RelationInfo } from '../get-relation-info.js';
|
|
3
|
+
/**
|
|
4
|
+
* Allows to deep map the data like a response or delta changes with collection, field and relation context for each entry.
|
|
5
|
+
* Bottom to Top depth first mapping of values.
|
|
6
|
+
*/
|
|
7
|
+
export declare function deepMapWithSchema(object: Record<string, any>, callback: (entry: [key: string | number, value: unknown], context: {
|
|
8
|
+
collection: CollectionOverview;
|
|
9
|
+
field: FieldOverview;
|
|
10
|
+
relation: Relation | null;
|
|
11
|
+
leaf: boolean;
|
|
12
|
+
relationType: RelationInfo['relationType'] | null;
|
|
13
|
+
object: Record<string, any>;
|
|
14
|
+
}) => [key: string | number, value: unknown] | undefined, context: {
|
|
15
|
+
schema: SchemaOverview;
|
|
16
|
+
collection: string;
|
|
17
|
+
relationInfo?: RelationInfo;
|
|
18
|
+
}, options?: {
|
|
19
|
+
/** If set to true, non-existent fields will be included in the mapping and will have a value of undefined */
|
|
20
|
+
mapNonExistentFields?: boolean;
|
|
21
|
+
/** If set to true, it will map the "create", "update" and "delete" syntax for o2m relations found in deltas */
|
|
22
|
+
detailedUpdateSyntax?: boolean;
|
|
23
|
+
}): any;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isPlainObject } from 'lodash-es';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { getRelationInfo } from '../get-relation-info.js';
|
|
4
|
+
import { InvalidQueryError } from '@directus/errors';
|
|
5
|
+
/**
|
|
6
|
+
* Allows to deep map the data like a response or delta changes with collection, field and relation context for each entry.
|
|
7
|
+
* Bottom to Top depth first mapping of values.
|
|
8
|
+
*/
|
|
9
|
+
export function deepMapWithSchema(object, callback, context, options) {
|
|
10
|
+
const collection = context.schema.collections[context.collection];
|
|
11
|
+
assert(isPlainObject(object) && typeof object === 'object' && object !== null, `DeepMapResponse only works on objects, received ${JSON.stringify(object)}`);
|
|
12
|
+
let fields;
|
|
13
|
+
if (options?.mapNonExistentFields) {
|
|
14
|
+
fields = Object.entries(collection.fields);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
fields = Object.keys(object).map((key) => [key, collection.fields[key]]);
|
|
18
|
+
}
|
|
19
|
+
return Object.fromEntries(fields
|
|
20
|
+
.map(([key, field]) => {
|
|
21
|
+
let value = object[key];
|
|
22
|
+
if (!field)
|
|
23
|
+
return [key, value];
|
|
24
|
+
const relationInfo = getRelationInfo(context.schema.relations, collection.collection, field.field);
|
|
25
|
+
let leaf = true;
|
|
26
|
+
if (relationInfo.relation && typeof value === 'object' && value !== null && isPlainObject(object)) {
|
|
27
|
+
switch (relationInfo.relationType) {
|
|
28
|
+
case 'm2o':
|
|
29
|
+
value = deepMapWithSchema(value, callback, {
|
|
30
|
+
schema: context.schema,
|
|
31
|
+
collection: relationInfo.relation.related_collection,
|
|
32
|
+
relationInfo,
|
|
33
|
+
}, options);
|
|
34
|
+
leaf = false;
|
|
35
|
+
break;
|
|
36
|
+
case 'o2m': {
|
|
37
|
+
function map(childValue) {
|
|
38
|
+
if (isPlainObject(childValue) && typeof childValue === 'object' && childValue !== null) {
|
|
39
|
+
leaf = false;
|
|
40
|
+
return deepMapWithSchema(childValue, callback, {
|
|
41
|
+
schema: context.schema,
|
|
42
|
+
collection: relationInfo.relation.collection,
|
|
43
|
+
relationInfo,
|
|
44
|
+
}, options);
|
|
45
|
+
}
|
|
46
|
+
else
|
|
47
|
+
return childValue;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
value = value.map(map);
|
|
51
|
+
}
|
|
52
|
+
else if (options?.detailedUpdateSyntax && isPlainObject(value)) {
|
|
53
|
+
value = {
|
|
54
|
+
create: value['create']?.map(map),
|
|
55
|
+
update: value['update']?.map(map),
|
|
56
|
+
delete: value['delete']?.map(map),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case 'a2o': {
|
|
62
|
+
const related_collection = object[relationInfo.relation.meta.one_collection_field];
|
|
63
|
+
if (!related_collection) {
|
|
64
|
+
throw new InvalidQueryError({
|
|
65
|
+
reason: `When selecting '${collection.collection}.${field.field}', the field '${collection.collection}.${relationInfo.relation.meta.one_collection_field}' has to be selected when using versioning and m2a relations `,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
value = deepMapWithSchema(value, callback, {
|
|
69
|
+
schema: context.schema,
|
|
70
|
+
collection: related_collection,
|
|
71
|
+
relationInfo,
|
|
72
|
+
}, options);
|
|
73
|
+
leaf = false;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return callback([key, value], { collection, field, ...relationInfo, leaf, object });
|
|
79
|
+
})
|
|
80
|
+
.filter((f) => f));
|
|
81
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { PrimaryKey, Query, QueryOptions } from '@directus/types';
|
|
2
2
|
import type { ItemsService as ItemsServiceType } from '../../services/index.js';
|
|
3
|
-
export declare function handleVersion(self: ItemsServiceType, key: PrimaryKey, queryWithKey: Query, opts?: QueryOptions): Promise<
|
|
3
|
+
export declare function handleVersion(self: ItemsServiceType, key: PrimaryKey, queryWithKey: Query, opts?: QueryOptions): Promise<any>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ForbiddenError } from '@directus/errors';
|
|
2
|
-
import { deepMapResponse } from '../deep-map-response.js';
|
|
3
2
|
import { transaction } from '../transaction.js';
|
|
4
|
-
import {
|
|
3
|
+
import { deepMapWithSchema } from './deep-map-with-schema.js';
|
|
4
|
+
import { splitRecursive } from './split-recursive.js';
|
|
5
5
|
export async function handleVersion(self, key, queryWithKey, opts) {
|
|
6
6
|
const { VersionsService } = await import('../../services/versions.js');
|
|
7
7
|
const { ItemsService } = await import('../../services/items.js');
|
|
@@ -15,19 +15,21 @@ export async function handleVersion(self, key, queryWithKey, opts) {
|
|
|
15
15
|
accountability: self.accountability,
|
|
16
16
|
knex: self.knex,
|
|
17
17
|
});
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
return [originalData[0]];
|
|
21
|
-
return [mergeVersionsRaw(originalData[0], versionData)];
|
|
18
|
+
const version = await versionsService.getVersionSave(queryWithKey.version, self.collection, key);
|
|
19
|
+
return Object.assign(originalData[0], version?.delta);
|
|
22
20
|
}
|
|
23
|
-
let
|
|
21
|
+
let result;
|
|
24
22
|
const versionsService = new VersionsService({
|
|
25
23
|
schema: self.schema,
|
|
26
24
|
accountability: self.accountability,
|
|
27
25
|
knex: self.knex,
|
|
28
26
|
});
|
|
29
27
|
const createdIDs = {};
|
|
30
|
-
const
|
|
28
|
+
const version = await versionsService.getVersionSave(queryWithKey.version, self.collection, key, false);
|
|
29
|
+
if (!version) {
|
|
30
|
+
throw new ForbiddenError();
|
|
31
|
+
}
|
|
32
|
+
const { delta } = version;
|
|
31
33
|
await transaction(self.knex, async (trx) => {
|
|
32
34
|
const itemsServiceAdmin = new ItemsService(self.collection, {
|
|
33
35
|
schema: self.schema,
|
|
@@ -36,57 +38,59 @@ export async function handleVersion(self, key, queryWithKey, opts) {
|
|
|
36
38
|
},
|
|
37
39
|
knex: trx,
|
|
38
40
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
if (delta) {
|
|
42
|
+
const { rawDelta, defaultOverwrites } = splitRecursive(delta);
|
|
43
|
+
await itemsServiceAdmin.updateOne(key, rawDelta, {
|
|
41
44
|
emitEvents: false,
|
|
42
45
|
autoPurgeCache: false,
|
|
43
46
|
skipTracking: true,
|
|
47
|
+
overwriteDefaults: defaultOverwrites,
|
|
44
48
|
onItemCreate: (collection, pk) => {
|
|
45
49
|
if (collection in createdIDs === false)
|
|
46
50
|
createdIDs[collection] = [];
|
|
47
51
|
createdIDs[collection].push(pk);
|
|
48
52
|
},
|
|
49
53
|
});
|
|
50
|
-
}
|
|
54
|
+
}
|
|
51
55
|
const itemsServiceUser = new ItemsService(self.collection, {
|
|
52
56
|
schema: self.schema,
|
|
53
57
|
accountability: self.accountability,
|
|
54
58
|
knex: trx,
|
|
55
59
|
});
|
|
56
|
-
|
|
60
|
+
result = (await itemsServiceUser.readByQuery(queryWithKey, opts))[0];
|
|
57
61
|
await trx.rollback();
|
|
58
62
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
else if (context.relationType === 'o2m' && Array.isArray(value)) {
|
|
69
|
-
const ids = createdIDs[context.relation.collection];
|
|
70
|
-
return [
|
|
71
|
-
key,
|
|
72
|
-
value.map((val) => {
|
|
73
|
-
const match = ids?.find((id) => String(id) === String(val));
|
|
74
|
-
if (match) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
return val;
|
|
78
|
-
}),
|
|
79
|
-
];
|
|
63
|
+
if (!result) {
|
|
64
|
+
throw new ForbiddenError();
|
|
65
|
+
}
|
|
66
|
+
return deepMapWithSchema(result, ([key, value], context) => {
|
|
67
|
+
if (context.relationType === 'm2o' || context.relationType === 'a2o') {
|
|
68
|
+
const ids = createdIDs[context.relation.related_collection];
|
|
69
|
+
const match = ids?.find((id) => String(id) === String(value));
|
|
70
|
+
if (match) {
|
|
71
|
+
return [key, null];
|
|
80
72
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
}
|
|
74
|
+
else if (context.relationType === 'o2m' && Array.isArray(value)) {
|
|
75
|
+
const ids = createdIDs[context.relation.collection];
|
|
76
|
+
return [
|
|
77
|
+
key,
|
|
78
|
+
value.map((val) => {
|
|
79
|
+
const match = ids?.find((id) => String(id) === String(val));
|
|
80
|
+
if (match) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return val;
|
|
84
|
+
}),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
if (context.field.field === context.collection.primary) {
|
|
88
|
+
const ids = createdIDs[context.collection.collection];
|
|
89
|
+
const match = ids?.find((id) => String(id) === String(value));
|
|
90
|
+
if (match) {
|
|
91
|
+
return [key, null];
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
91
|
-
return results;
|
|
93
|
+
}
|
|
94
|
+
return [key, value];
|
|
95
|
+
}, { collection: self.collection, schema: self.schema });
|
|
92
96
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isPlainObject } from 'lodash-es';
|
|
2
|
+
export function splitRecursive(object) {
|
|
3
|
+
if (isPlainObject(object) && typeof object === 'object' && object !== null) {
|
|
4
|
+
const { _user, _date, ...rest } = object;
|
|
5
|
+
const defaultOverwrites = { _user, _date };
|
|
6
|
+
for (const key in rest) {
|
|
7
|
+
const { rawDelta, defaultOverwrites: innerDefaultOverwrites } = splitRecursive(rest[key]);
|
|
8
|
+
rest[key] = rawDelta;
|
|
9
|
+
if (innerDefaultOverwrites)
|
|
10
|
+
defaultOverwrites[key] = innerDefaultOverwrites;
|
|
11
|
+
}
|
|
12
|
+
return { rawDelta: rest, defaultOverwrites };
|
|
13
|
+
}
|
|
14
|
+
else if (Array.isArray(object)) {
|
|
15
|
+
const rest = [];
|
|
16
|
+
const defaultOverwrites = [];
|
|
17
|
+
for (const key in object) {
|
|
18
|
+
const { rawDelta, defaultOverwrites: innerDefaultOverwrites } = splitRecursive(object[key]);
|
|
19
|
+
rest[key] = rawDelta;
|
|
20
|
+
if (innerDefaultOverwrites)
|
|
21
|
+
defaultOverwrites[key] = innerDefaultOverwrites;
|
|
22
|
+
}
|
|
23
|
+
object.map((value) => splitRecursive(value));
|
|
24
|
+
return { rawDelta: rest, defaultOverwrites };
|
|
25
|
+
}
|
|
26
|
+
return { rawDelta: object, defaultOverwrites: undefined };
|
|
27
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { systemCollectionNames } from '@directus/system-data';
|
|
1
2
|
import { useBus } from '../../bus/index.js';
|
|
2
3
|
import emitter from '../../emitter.js';
|
|
3
4
|
let actionsRegistered = false;
|
|
@@ -7,26 +8,17 @@ export function registerWebSocketEvents() {
|
|
|
7
8
|
actionsRegistered = true;
|
|
8
9
|
registerActionHooks([
|
|
9
10
|
'items',
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'presets',
|
|
22
|
-
'revisions',
|
|
23
|
-
'roles',
|
|
24
|
-
'settings',
|
|
25
|
-
'shares',
|
|
26
|
-
'translations',
|
|
27
|
-
'users',
|
|
28
|
-
'versions',
|
|
29
|
-
'webhooks',
|
|
11
|
+
...systemCollectionNames
|
|
12
|
+
.filter((collection) => ![
|
|
13
|
+
'directus_migrations',
|
|
14
|
+
// manually registered below
|
|
15
|
+
'directus_fields',
|
|
16
|
+
'directus_relations',
|
|
17
|
+
'directus_files',
|
|
18
|
+
// excluded for security reasons
|
|
19
|
+
'directus_sessions',
|
|
20
|
+
].includes(collection))
|
|
21
|
+
.map((collection) => collection.replace('directus_', '')),
|
|
30
22
|
]);
|
|
31
23
|
registerFieldsHooks();
|
|
32
24
|
registerFilesHooks();
|
|
@@ -12,7 +12,7 @@ export declare const WebSocketResponse: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
12
12
|
code: z.ZodString;
|
|
13
13
|
message: z.ZodString;
|
|
14
14
|
}, z.core.$loose>;
|
|
15
|
-
}, z.core.$loose>]>;
|
|
15
|
+
}, z.core.$loose>], "status">;
|
|
16
16
|
export type WebSocketResponse = z.infer<typeof WebSocketResponse>;
|
|
17
17
|
export declare const ConnectionParams: z.ZodObject<{
|
|
18
18
|
access_token: z.ZodOptional<z.ZodString>;
|
|
@@ -49,7 +49,7 @@ export declare const WebSocketSubscribeMessage: z.ZodDiscriminatedUnion<[z.ZodOb
|
|
|
49
49
|
}, z.core.$loose>, z.ZodObject<{
|
|
50
50
|
uid: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
51
51
|
type: z.ZodLiteral<"unsubscribe">;
|
|
52
|
-
}, z.core.$loose>]>;
|
|
52
|
+
}, z.core.$loose>], "type">;
|
|
53
53
|
export type WebSocketSubscribeMessage = z.infer<typeof WebSocketSubscribeMessage>;
|
|
54
54
|
export declare const WebSocketLogsMessage: z.ZodUnion<readonly [z.ZodObject<{
|
|
55
55
|
type: z.ZodLiteral<"subscribe">;
|
|
@@ -108,7 +108,7 @@ export declare const WebSocketEvent: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
108
108
|
collection: z.ZodString;
|
|
109
109
|
payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
110
110
|
keys: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
111
|
-
}, z.core.$strip>]>;
|
|
111
|
+
}, z.core.$strip>], "action">;
|
|
112
112
|
export type WebSocketEvent = z.infer<typeof WebSocketEvent>;
|
|
113
113
|
export declare const AuthMode: z.ZodUnion<readonly [z.ZodLiteral<"public">, z.ZodLiteral<"handshake">, z.ZodLiteral<"strict">]>;
|
|
114
114
|
export type AuthMode = z.infer<typeof AuthMode>;
|