1
import type { Alterations, Item, SchemaOverview } from '@directus/types';
2
import { isObject } from '@directus/utils';
4
import { cloneDeep } from 'lodash-es';
6
const alterationSchema = Joi.object({
7
create: Joi.array().items(Joi.object().unknown()),
8
update: Joi.array().items(Joi.object().unknown()),
9
delete: Joi.array().items(Joi.string(), Joi.number()),
12
export function mergeVersionsRaw(item: Item, versionData: Partial<Item>[]) {
13
const result = cloneDeep(item);
15
for (const versionRecord of versionData) {
16
for (const key of Object.keys(versionRecord)) {
17
result[key] = versionRecord[key];
24
export function mergeVersionsRecursive(
28
schema: SchemaOverview,
30
if (versionData.length === 0) return item;
32
return recursiveMerging(item, versionData, collection, schema) as Item;
35
function recursiveMerging(data: Item, versionData: unknown[], collection: string, schema: SchemaOverview): unknown {
36
const result = cloneDeep(data);
37
const relations = getRelations(collection, schema);
39
for (const versionRecord of versionData) {
40
if (!isObject(versionRecord)) {
44
for (const key of Object.keys(data)) {
45
if (key in versionRecord === false) {
49
const currentValue: unknown = data[key];
50
const newValue: unknown = versionRecord[key];
52
if (typeof newValue !== 'object' || newValue === null) {
54
result[key] = newValue;
58
if (key in relations === false) {
60
if (isManyToAnyCollection(collection, schema) && key === 'item') {
61
const item = addMissingKeys(isObject(currentValue) ? currentValue : {}, newValue);
62
result[key] = recursiveMerging(item, [newValue], data['collection'], schema);
65
result[key] = newValue;
71
const { error } = alterationSchema.validate(newValue);
74
if (typeof newValue === 'object' && key in relations) {
75
const newItem = !currentValue || typeof currentValue !== 'object' ? newValue : currentValue;
76
result[key] = recursiveMerging(newItem, [newValue], relations[key]!, schema);
82
const alterations = newValue as Alterations;
83
const currentPrimaryKeyField = schema.collections[collection]!.primary;
84
const relatedPrimaryKeyField = schema.collections[relations[key]!]!.primary;
86
const mergedRelation: Item[] = [];
88
if (Array.isArray(currentValue)) {
89
if (alterations.delete.length > 0) {
90
for (const currentItem of currentValue) {
91
const currentId = typeof currentItem === 'object' ? currentItem[currentPrimaryKeyField] : currentItem;
93
if (alterations.delete.includes(currentId) === false) {
94
mergedRelation.push(currentItem);
98
mergedRelation.push(...currentValue);
101
if (alterations.update.length > 0) {
102
for (const updatedItem of alterations.update) {
104
const itemIndex = mergedRelation.findIndex(
105
(currentItem) => currentItem[relatedPrimaryKeyField] === updatedItem[currentPrimaryKeyField],
108
if (itemIndex === -1) {
110
const pkIndex = mergedRelation.findIndex(
111
(currentItem) => currentItem === updatedItem[currentPrimaryKeyField],
114
if (pkIndex === -1) {
116
mergedRelation.push(updatedItem);
118
mergedRelation[pkIndex] = updatedItem;
124
const item = addMissingKeys(mergedRelation[itemIndex]!, updatedItem);
126
mergedRelation[itemIndex] = recursiveMerging(item, [updatedItem], relations[key]!, schema) as Item;
131
if (alterations.create.length > 0) {
132
for (const createdItem of alterations.create) {
133
const item = addMissingKeys({}, createdItem);
134
mergedRelation.push(recursiveMerging(item, [createdItem], relations[key]!, schema) as Item);
138
result[key] = mergedRelation;
145
function addMissingKeys(item: Item, edits: Item) {
146
const result: Item = { ...item };
148
for (const key of Object.keys(edits)) {
149
if (key in item === false) {
157
function isManyToAnyCollection(collection: string, schema: SchemaOverview) {
158
const relation = schema.relations.find(
159
(relation) => relation.collection === collection && relation.meta?.many_collection === collection,
162
if (!relation || !relation.meta?.one_field || !relation.related_collection) return false;
165
schema.collections[relation.related_collection]?.fields[relation.meta.one_field]?.special.includes('m2a'),
169
function getRelations(collection: string, schema: SchemaOverview) {
170
return schema.relations.reduce(
171
(result, relation) => {
172
if (relation.related_collection === collection && relation.meta?.one_field) {
173
result[relation.meta.one_field] = relation.collection;
176
if (relation.collection === collection && relation.related_collection) {
177
result[relation.field] = relation.related_collection;
182
{} as Record<string, string>,