@strapi/core 5.43.0 → 5.45.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/loaders/plugins/index.d.ts.map +1 -1
- package/dist/loaders/plugins/index.js +15 -5
- package/dist/loaders/plugins/index.js.map +1 -1
- package/dist/loaders/plugins/index.mjs +15 -5
- package/dist/loaders/plugins/index.mjs.map +1 -1
- package/dist/package.json.js +16 -16
- package/dist/package.json.mjs +16 -16
- package/dist/services/document-service/entries.d.ts +1 -1
- package/dist/services/document-service/entries.js +2 -1
- package/dist/services/document-service/entries.js.map +1 -1
- package/dist/services/document-service/entries.mjs +2 -1
- package/dist/services/document-service/entries.mjs.map +1 -1
- package/dist/services/document-service/internationalization.d.ts.map +1 -1
- package/dist/services/document-service/internationalization.js +43 -2
- package/dist/services/document-service/internationalization.js.map +1 -1
- package/dist/services/document-service/internationalization.mjs +43 -2
- package/dist/services/document-service/internationalization.mjs.map +1 -1
- package/dist/services/document-service/repository.d.ts.map +1 -1
- package/dist/services/document-service/repository.js +16 -7
- package/dist/services/document-service/repository.js.map +1 -1
- package/dist/services/document-service/repository.mjs +13 -4
- package/dist/services/document-service/repository.mjs.map +1 -1
- package/dist/services/document-service/transform/relations/utils/map-relation.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/utils/map-relation.js +4 -0
- package/dist/services/document-service/transform/relations/utils/map-relation.js.map +1 -1
- package/dist/services/document-service/transform/relations/utils/map-relation.mjs +4 -0
- package/dist/services/document-service/transform/relations/utils/map-relation.mjs.map +1 -1
- package/dist/services/document-service/utils/populate.d.ts.map +1 -1
- package/dist/services/document-service/utils/populate.js +1 -7
- package/dist/services/document-service/utils/populate.js.map +1 -1
- package/dist/services/document-service/utils/populate.mjs +1 -7
- package/dist/services/document-service/utils/populate.mjs.map +1 -1
- package/dist/services/document-service/utils/self-referential-relations.d.ts +37 -0
- package/dist/services/document-service/utils/self-referential-relations.d.ts.map +1 -0
- package/dist/services/document-service/utils/self-referential-relations.js +111 -0
- package/dist/services/document-service/utils/self-referential-relations.js.map +1 -0
- package/dist/services/document-service/utils/self-referential-relations.mjs +108 -0
- package/dist/services/document-service/utils/self-referential-relations.mjs.map +1 -0
- package/dist/services/document-service/utils/unidirectional-relations.d.ts +5 -5
- package/dist/services/document-service/utils/unidirectional-relations.d.ts.map +1 -1
- package/dist/services/document-service/utils/unidirectional-relations.js +5 -3
- package/dist/services/document-service/utils/unidirectional-relations.js.map +1 -1
- package/dist/services/document-service/utils/unidirectional-relations.mjs +5 -3
- package/dist/services/document-service/utils/unidirectional-relations.mjs.map +1 -1
- package/package.json +16 -16
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fp = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Preserves self-referential relations during publish/discard operations.
|
|
7
|
+
*
|
|
8
|
+
* When publishing or discarding a draft, self-referential relations (where both sides
|
|
9
|
+
* of the relation belong to the same content type) are lost because:
|
|
10
|
+
*
|
|
11
|
+
* 1. The old entry is deleted
|
|
12
|
+
* 2. A new entry is created with relations resolved via documentId → entity ID mapping
|
|
13
|
+
* 3. At mapping time, the old entity is already deleted and the new one doesn't exist yet
|
|
14
|
+
* 4. The relation is silently dropped
|
|
15
|
+
*
|
|
16
|
+
* This utility:
|
|
17
|
+
* 1. Captures self-referential join table rows before deletion
|
|
18
|
+
* 2. Remaps old entity IDs to new entity IDs after creation
|
|
19
|
+
* 3. Inserts the remapped relations
|
|
20
|
+
*/ /**
|
|
21
|
+
* Loads self-referential relations from source entries before they are deleted/recreated.
|
|
22
|
+
*/ const load = async (uid, sourceEntries)=>{
|
|
23
|
+
const updates = [];
|
|
24
|
+
const dbModel = strapi.db.metadata.get(uid);
|
|
25
|
+
await strapi.db.transaction(async ({ trx })=>{
|
|
26
|
+
for (const attribute of Object.values(dbModel.attributes)){
|
|
27
|
+
if (attribute.type !== 'relation' || attribute.target !== uid) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Bidirectional inverse side (e.g. `children` mappedBy `parent`) shares the same physical
|
|
31
|
+
// join table as the owning attribute; processing both would duplicate rows and inserts.
|
|
32
|
+
if (attribute.mappedBy) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const joinTable = attribute.joinTable;
|
|
36
|
+
if (!joinTable) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const { name: sourceColumnName } = joinTable.joinColumn;
|
|
40
|
+
const { name: targetColumnName } = joinTable.inverseJoinColumn;
|
|
41
|
+
const sourceIds = sourceEntries.map((entry)=>String(entry.id));
|
|
42
|
+
// Load relations where both source and target are among the entries being processed.
|
|
43
|
+
// These are the self-referential relations that would be lost during the
|
|
44
|
+
// delete-and-recreate cycle.
|
|
45
|
+
const selfRelations = await strapi.db.getConnection().select('*').from(joinTable.name).whereIn(sourceColumnName, sourceIds).whereIn(targetColumnName, sourceIds).transacting(trx);
|
|
46
|
+
if (selfRelations.length > 0) {
|
|
47
|
+
updates.push({
|
|
48
|
+
joinTable,
|
|
49
|
+
relations: selfRelations
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return updates;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Syncs self-referential relations by remapping old entry IDs to new entry IDs
|
|
58
|
+
* and inserting the remapped relations into the join table.
|
|
59
|
+
*/ const sync = async (sourceEntries, targetEntries, relationData)=>{
|
|
60
|
+
if (relationData.length === 0) return;
|
|
61
|
+
const targetEntriesByLocale = fp.keyBy('locale', targetEntries);
|
|
62
|
+
// Keys stringified for object lookup; values keep the original DB type so PostgreSQL integer columns receive integers, not strings
|
|
63
|
+
const idMapping = sourceEntries.reduce((acc, sourceEntry)=>{
|
|
64
|
+
const targetEntry = targetEntriesByLocale[sourceEntry.locale];
|
|
65
|
+
if (!targetEntry) return acc;
|
|
66
|
+
acc[String(sourceEntry.id)] = targetEntry.id;
|
|
67
|
+
return acc;
|
|
68
|
+
}, {});
|
|
69
|
+
const batchSize = strapi.db.dialect.getBatchInsertSize();
|
|
70
|
+
await strapi.db.transaction(async ({ trx })=>{
|
|
71
|
+
for (const { joinTable, relations } of relationData){
|
|
72
|
+
const sourceColumn = joinTable.joinColumn.name;
|
|
73
|
+
const targetColumn = joinTable.inverseJoinColumn.name;
|
|
74
|
+
const newRelations = relations.map((relation)=>{
|
|
75
|
+
const oldSourceId = String(relation[sourceColumn]);
|
|
76
|
+
const oldTargetId = String(relation[targetColumn]);
|
|
77
|
+
const newSourceId = idMapping[oldSourceId];
|
|
78
|
+
const newTargetId = idMapping[oldTargetId];
|
|
79
|
+
// Both sides must map to new entries
|
|
80
|
+
if (!newSourceId || !newTargetId) return null;
|
|
81
|
+
return {
|
|
82
|
+
...fp.omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),
|
|
83
|
+
[sourceColumn]: newSourceId,
|
|
84
|
+
[targetColumn]: newTargetId
|
|
85
|
+
};
|
|
86
|
+
}).filter(Boolean);
|
|
87
|
+
const pairKey = (r)=>`${String(r[sourceColumn])}:${String(r[targetColumn])}`;
|
|
88
|
+
const seenPairs = new Set();
|
|
89
|
+
const deduped = newRelations.filter((r)=>{
|
|
90
|
+
const key = pairKey(r);
|
|
91
|
+
if (seenPairs.has(key)) return false;
|
|
92
|
+
seenPairs.add(key);
|
|
93
|
+
return true;
|
|
94
|
+
});
|
|
95
|
+
if (deduped.length === 0) continue;
|
|
96
|
+
const newSourceIds = [
|
|
97
|
+
...new Set(deduped.map((r)=>String(r[sourceColumn])))
|
|
98
|
+
];
|
|
99
|
+
const existingRows = await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).select(sourceColumn, targetColumn);
|
|
100
|
+
const existingSet = new Set(existingRows.map((r)=>pairKey(r)));
|
|
101
|
+
const toInsert = deduped.filter((r)=>!existingSet.has(pairKey(r)));
|
|
102
|
+
if (toInsert.length > 0) {
|
|
103
|
+
await trx.batchInsert(joinTable.name, toInsert, batchSize);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
exports.load = load;
|
|
110
|
+
exports.sync = sync;
|
|
111
|
+
//# sourceMappingURL=self-referential-relations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-referential-relations.js","sources":["../../../../src/services/document-service/utils/self-referential-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\nimport type { Data, UID } from '@strapi/types';\nimport type { JoinTable } from '@strapi/database';\n\ninterface VersionEntry {\n id: Data.ID;\n locale: string;\n}\n\ninterface RelationData {\n joinTable: JoinTable;\n relations: Record<string, unknown>[];\n}\n\n/**\n * Preserves self-referential relations during publish/discard operations.\n *\n * When publishing or discarding a draft, self-referential relations (where both sides\n * of the relation belong to the same content type) are lost because:\n *\n * 1. The old entry is deleted\n * 2. A new entry is created with relations resolved via documentId → entity ID mapping\n * 3. At mapping time, the old entity is already deleted and the new one doesn't exist yet\n * 4. The relation is silently dropped\n *\n * This utility:\n * 1. Captures self-referential join table rows before deletion\n * 2. Remaps old entity IDs to new entity IDs after creation\n * 3. Inserts the remapped relations\n */\n\n/**\n * Loads self-referential relations from source entries before they are deleted/recreated.\n */\nconst load = async (\n uid: UID.ContentType,\n sourceEntries: VersionEntry[]\n): Promise<RelationData[]> => {\n const updates: RelationData[] = [];\n const dbModel = strapi.db.metadata.get(uid);\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const attribute of Object.values(dbModel.attributes) as any) {\n if (attribute.type !== 'relation' || attribute.target !== uid) {\n continue;\n }\n\n // Bidirectional inverse side (e.g. `children` mappedBy `parent`) shares the same physical\n // join table as the owning attribute; processing both would duplicate rows and inserts.\n if (attribute.mappedBy) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n const sourceIds = sourceEntries.map((entry) => String(entry.id));\n\n // Load relations where both source and target are among the entries being processed.\n // These are the self-referential relations that would be lost during the\n // delete-and-recreate cycle.\n const selfRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(sourceColumnName, sourceIds)\n .whereIn(targetColumnName, sourceIds)\n .transacting(trx);\n\n if (selfRelations.length > 0) {\n updates.push({ joinTable, relations: selfRelations });\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Syncs self-referential relations by remapping old entry IDs to new entry IDs\n * and inserting the remapped relations into the join table.\n */\nconst sync = async (\n sourceEntries: VersionEntry[],\n targetEntries: VersionEntry[],\n relationData: RelationData[]\n) => {\n if (relationData.length === 0) return;\n\n const targetEntriesByLocale = keyBy('locale', targetEntries);\n\n // Keys stringified for object lookup; values keep the original DB type so PostgreSQL integer columns receive integers, not strings\n const idMapping = sourceEntries.reduce(\n (acc, sourceEntry) => {\n const targetEntry = targetEntriesByLocale[sourceEntry.locale];\n if (!targetEntry) return acc;\n acc[String(sourceEntry.id)] = targetEntry.id;\n return acc;\n },\n {} as Record<string, Data.ID>\n );\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of relationData) {\n const sourceColumn = joinTable.joinColumn.name;\n const targetColumn = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations\n .map((relation) => {\n const oldSourceId = String(relation[sourceColumn]);\n const oldTargetId = String(relation[targetColumn]);\n const newSourceId = idMapping[oldSourceId];\n const newTargetId = idMapping[oldTargetId];\n\n // Both sides must map to new entries\n if (!newSourceId || !newTargetId) return null;\n\n return {\n ...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),\n [sourceColumn]: newSourceId,\n [targetColumn]: newTargetId,\n };\n })\n .filter(Boolean) as Record<string, unknown>[];\n\n const pairKey = (r: Record<string, unknown>) =>\n `${String(r[sourceColumn])}:${String(r[targetColumn])}`;\n const seenPairs = new Set<string>();\n const deduped = newRelations.filter((r) => {\n const key = pairKey(r);\n if (seenPairs.has(key)) return false;\n seenPairs.add(key);\n return true;\n });\n\n if (deduped.length === 0) continue;\n\n const newSourceIds = [...new Set(deduped.map((r) => String(r[sourceColumn])))];\n const existingRows = await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .select(sourceColumn, targetColumn);\n\n const existingSet = new Set(existingRows.map((r: Record<string, unknown>) => pairKey(r)));\n const toInsert = deduped.filter((r) => !existingSet.has(pairKey(r)));\n\n if (toInsert.length > 0) {\n await trx.batchInsert(joinTable.name, toInsert as any[], batchSize);\n }\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","sourceEntries","updates","dbModel","strapi","db","metadata","get","transaction","trx","attribute","Object","values","attributes","type","target","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","sourceIds","map","entry","String","id","selfRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","targetEntries","relationData","targetEntriesByLocale","keyBy","idMapping","reduce","acc","sourceEntry","targetEntry","locale","batchSize","dialect","getBatchInsertSize","sourceColumn","targetColumn","newRelations","relation","oldSourceId","oldTargetId","newSourceId","newTargetId","omit","identifiers","ID_COLUMN","filter","Boolean","pairKey","r","seenPairs","Set","deduped","key","has","add","newSourceIds","existingRows","existingSet","toInsert","batchInsert"],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { keyBy, omit } from 'lodash/fp';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Preserves self-referential relations during publish/discard operations.
|
|
5
|
+
*
|
|
6
|
+
* When publishing or discarding a draft, self-referential relations (where both sides
|
|
7
|
+
* of the relation belong to the same content type) are lost because:
|
|
8
|
+
*
|
|
9
|
+
* 1. The old entry is deleted
|
|
10
|
+
* 2. A new entry is created with relations resolved via documentId → entity ID mapping
|
|
11
|
+
* 3. At mapping time, the old entity is already deleted and the new one doesn't exist yet
|
|
12
|
+
* 4. The relation is silently dropped
|
|
13
|
+
*
|
|
14
|
+
* This utility:
|
|
15
|
+
* 1. Captures self-referential join table rows before deletion
|
|
16
|
+
* 2. Remaps old entity IDs to new entity IDs after creation
|
|
17
|
+
* 3. Inserts the remapped relations
|
|
18
|
+
*/ /**
|
|
19
|
+
* Loads self-referential relations from source entries before they are deleted/recreated.
|
|
20
|
+
*/ const load = async (uid, sourceEntries)=>{
|
|
21
|
+
const updates = [];
|
|
22
|
+
const dbModel = strapi.db.metadata.get(uid);
|
|
23
|
+
await strapi.db.transaction(async ({ trx })=>{
|
|
24
|
+
for (const attribute of Object.values(dbModel.attributes)){
|
|
25
|
+
if (attribute.type !== 'relation' || attribute.target !== uid) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Bidirectional inverse side (e.g. `children` mappedBy `parent`) shares the same physical
|
|
29
|
+
// join table as the owning attribute; processing both would duplicate rows and inserts.
|
|
30
|
+
if (attribute.mappedBy) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const joinTable = attribute.joinTable;
|
|
34
|
+
if (!joinTable) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const { name: sourceColumnName } = joinTable.joinColumn;
|
|
38
|
+
const { name: targetColumnName } = joinTable.inverseJoinColumn;
|
|
39
|
+
const sourceIds = sourceEntries.map((entry)=>String(entry.id));
|
|
40
|
+
// Load relations where both source and target are among the entries being processed.
|
|
41
|
+
// These are the self-referential relations that would be lost during the
|
|
42
|
+
// delete-and-recreate cycle.
|
|
43
|
+
const selfRelations = await strapi.db.getConnection().select('*').from(joinTable.name).whereIn(sourceColumnName, sourceIds).whereIn(targetColumnName, sourceIds).transacting(trx);
|
|
44
|
+
if (selfRelations.length > 0) {
|
|
45
|
+
updates.push({
|
|
46
|
+
joinTable,
|
|
47
|
+
relations: selfRelations
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return updates;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Syncs self-referential relations by remapping old entry IDs to new entry IDs
|
|
56
|
+
* and inserting the remapped relations into the join table.
|
|
57
|
+
*/ const sync = async (sourceEntries, targetEntries, relationData)=>{
|
|
58
|
+
if (relationData.length === 0) return;
|
|
59
|
+
const targetEntriesByLocale = keyBy('locale', targetEntries);
|
|
60
|
+
// Keys stringified for object lookup; values keep the original DB type so PostgreSQL integer columns receive integers, not strings
|
|
61
|
+
const idMapping = sourceEntries.reduce((acc, sourceEntry)=>{
|
|
62
|
+
const targetEntry = targetEntriesByLocale[sourceEntry.locale];
|
|
63
|
+
if (!targetEntry) return acc;
|
|
64
|
+
acc[String(sourceEntry.id)] = targetEntry.id;
|
|
65
|
+
return acc;
|
|
66
|
+
}, {});
|
|
67
|
+
const batchSize = strapi.db.dialect.getBatchInsertSize();
|
|
68
|
+
await strapi.db.transaction(async ({ trx })=>{
|
|
69
|
+
for (const { joinTable, relations } of relationData){
|
|
70
|
+
const sourceColumn = joinTable.joinColumn.name;
|
|
71
|
+
const targetColumn = joinTable.inverseJoinColumn.name;
|
|
72
|
+
const newRelations = relations.map((relation)=>{
|
|
73
|
+
const oldSourceId = String(relation[sourceColumn]);
|
|
74
|
+
const oldTargetId = String(relation[targetColumn]);
|
|
75
|
+
const newSourceId = idMapping[oldSourceId];
|
|
76
|
+
const newTargetId = idMapping[oldTargetId];
|
|
77
|
+
// Both sides must map to new entries
|
|
78
|
+
if (!newSourceId || !newTargetId) return null;
|
|
79
|
+
return {
|
|
80
|
+
...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),
|
|
81
|
+
[sourceColumn]: newSourceId,
|
|
82
|
+
[targetColumn]: newTargetId
|
|
83
|
+
};
|
|
84
|
+
}).filter(Boolean);
|
|
85
|
+
const pairKey = (r)=>`${String(r[sourceColumn])}:${String(r[targetColumn])}`;
|
|
86
|
+
const seenPairs = new Set();
|
|
87
|
+
const deduped = newRelations.filter((r)=>{
|
|
88
|
+
const key = pairKey(r);
|
|
89
|
+
if (seenPairs.has(key)) return false;
|
|
90
|
+
seenPairs.add(key);
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
if (deduped.length === 0) continue;
|
|
94
|
+
const newSourceIds = [
|
|
95
|
+
...new Set(deduped.map((r)=>String(r[sourceColumn])))
|
|
96
|
+
];
|
|
97
|
+
const existingRows = await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).select(sourceColumn, targetColumn);
|
|
98
|
+
const existingSet = new Set(existingRows.map((r)=>pairKey(r)));
|
|
99
|
+
const toInsert = deduped.filter((r)=>!existingSet.has(pairKey(r)));
|
|
100
|
+
if (toInsert.length > 0) {
|
|
101
|
+
await trx.batchInsert(joinTable.name, toInsert, batchSize);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export { load, sync };
|
|
108
|
+
//# sourceMappingURL=self-referential-relations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-referential-relations.mjs","sources":["../../../../src/services/document-service/utils/self-referential-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\nimport type { Data, UID } from '@strapi/types';\nimport type { JoinTable } from '@strapi/database';\n\ninterface VersionEntry {\n id: Data.ID;\n locale: string;\n}\n\ninterface RelationData {\n joinTable: JoinTable;\n relations: Record<string, unknown>[];\n}\n\n/**\n * Preserves self-referential relations during publish/discard operations.\n *\n * When publishing or discarding a draft, self-referential relations (where both sides\n * of the relation belong to the same content type) are lost because:\n *\n * 1. The old entry is deleted\n * 2. A new entry is created with relations resolved via documentId → entity ID mapping\n * 3. At mapping time, the old entity is already deleted and the new one doesn't exist yet\n * 4. The relation is silently dropped\n *\n * This utility:\n * 1. Captures self-referential join table rows before deletion\n * 2. Remaps old entity IDs to new entity IDs after creation\n * 3. Inserts the remapped relations\n */\n\n/**\n * Loads self-referential relations from source entries before they are deleted/recreated.\n */\nconst load = async (\n uid: UID.ContentType,\n sourceEntries: VersionEntry[]\n): Promise<RelationData[]> => {\n const updates: RelationData[] = [];\n const dbModel = strapi.db.metadata.get(uid);\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const attribute of Object.values(dbModel.attributes) as any) {\n if (attribute.type !== 'relation' || attribute.target !== uid) {\n continue;\n }\n\n // Bidirectional inverse side (e.g. `children` mappedBy `parent`) shares the same physical\n // join table as the owning attribute; processing both would duplicate rows and inserts.\n if (attribute.mappedBy) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n const sourceIds = sourceEntries.map((entry) => String(entry.id));\n\n // Load relations where both source and target are among the entries being processed.\n // These are the self-referential relations that would be lost during the\n // delete-and-recreate cycle.\n const selfRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(sourceColumnName, sourceIds)\n .whereIn(targetColumnName, sourceIds)\n .transacting(trx);\n\n if (selfRelations.length > 0) {\n updates.push({ joinTable, relations: selfRelations });\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Syncs self-referential relations by remapping old entry IDs to new entry IDs\n * and inserting the remapped relations into the join table.\n */\nconst sync = async (\n sourceEntries: VersionEntry[],\n targetEntries: VersionEntry[],\n relationData: RelationData[]\n) => {\n if (relationData.length === 0) return;\n\n const targetEntriesByLocale = keyBy('locale', targetEntries);\n\n // Keys stringified for object lookup; values keep the original DB type so PostgreSQL integer columns receive integers, not strings\n const idMapping = sourceEntries.reduce(\n (acc, sourceEntry) => {\n const targetEntry = targetEntriesByLocale[sourceEntry.locale];\n if (!targetEntry) return acc;\n acc[String(sourceEntry.id)] = targetEntry.id;\n return acc;\n },\n {} as Record<string, Data.ID>\n );\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of relationData) {\n const sourceColumn = joinTable.joinColumn.name;\n const targetColumn = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations\n .map((relation) => {\n const oldSourceId = String(relation[sourceColumn]);\n const oldTargetId = String(relation[targetColumn]);\n const newSourceId = idMapping[oldSourceId];\n const newTargetId = idMapping[oldTargetId];\n\n // Both sides must map to new entries\n if (!newSourceId || !newTargetId) return null;\n\n return {\n ...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),\n [sourceColumn]: newSourceId,\n [targetColumn]: newTargetId,\n };\n })\n .filter(Boolean) as Record<string, unknown>[];\n\n const pairKey = (r: Record<string, unknown>) =>\n `${String(r[sourceColumn])}:${String(r[targetColumn])}`;\n const seenPairs = new Set<string>();\n const deduped = newRelations.filter((r) => {\n const key = pairKey(r);\n if (seenPairs.has(key)) return false;\n seenPairs.add(key);\n return true;\n });\n\n if (deduped.length === 0) continue;\n\n const newSourceIds = [...new Set(deduped.map((r) => String(r[sourceColumn])))];\n const existingRows = await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .select(sourceColumn, targetColumn);\n\n const existingSet = new Set(existingRows.map((r: Record<string, unknown>) => pairKey(r)));\n const toInsert = deduped.filter((r) => !existingSet.has(pairKey(r)));\n\n if (toInsert.length > 0) {\n await trx.batchInsert(joinTable.name, toInsert as any[], batchSize);\n }\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","sourceEntries","updates","dbModel","strapi","db","metadata","get","transaction","trx","attribute","Object","values","attributes","type","target","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","sourceIds","map","entry","String","id","selfRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","targetEntries","relationData","targetEntriesByLocale","keyBy","idMapping","reduce","acc","sourceEntry","targetEntry","locale","batchSize","dialect","getBatchInsertSize","sourceColumn","targetColumn","newRelations","relation","oldSourceId","oldTargetId","newSourceId","newTargetId","omit","identifiers","ID_COLUMN","filter","Boolean","pairKey","r","seenPairs","Set","deduped","key","has","add","newSourceIds","existingRows","existingSet","toInsert","batchInsert"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { UID, Schema } from '@strapi/types';
|
|
1
|
+
import type { Data, UID, Schema } from '@strapi/types';
|
|
2
2
|
import type { JoinTable } from '@strapi/database';
|
|
3
3
|
interface LoadContext {
|
|
4
4
|
oldVersions: {
|
|
5
|
-
id:
|
|
5
|
+
id: Data.ID;
|
|
6
6
|
locale: string;
|
|
7
7
|
}[];
|
|
8
8
|
newVersions: {
|
|
9
|
-
id:
|
|
9
|
+
id: Data.ID;
|
|
10
10
|
locale: string;
|
|
11
11
|
}[];
|
|
12
12
|
}
|
|
@@ -39,10 +39,10 @@ declare const load: (uid: UID.ContentType, { oldVersions, newVersions }: LoadCon
|
|
|
39
39
|
* @param oldRelations The relations that were previously loaded with `load` @see load
|
|
40
40
|
*/
|
|
41
41
|
declare const sync: (oldEntries: {
|
|
42
|
-
id:
|
|
42
|
+
id: Data.ID;
|
|
43
43
|
locale: string;
|
|
44
44
|
}[], newEntries: {
|
|
45
|
-
id:
|
|
45
|
+
id: Data.ID;
|
|
46
46
|
locale: string;
|
|
47
47
|
}[], oldRelations: {
|
|
48
48
|
joinTable: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unidirectional-relations.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"unidirectional-relations.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,UAAU,WAAW;IACnB,WAAW,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC/C,WAAW,EAAE;QAAE,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAChD;AAED,UAAU,cAAc;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CAClC;AAED,UAAU,qBAAqB;IAC7B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,CACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC7B,KAAK,EAAE,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,WAAW,EAC5C,GAAG,EAAE,GAAG,KACL,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB;AAED;;;;GAIG;AACH,QAAA,MAAM,IAAI,QACH,IAAI,WAAW,gCACU,WAAW,YAChC,qBAAqB,KAC7B,QAAQ,cAAc,EAAE,CA4G1B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,QAAA,MAAM,IAAI,eACI;IAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,cACjC;IAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,gBAC/B;IAAE,SAAS,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,GAAG,EAAE,CAAA;CAAE,EAAE,kBAiCrD,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACtB,YAAY,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -19,8 +19,10 @@ var fp = require('lodash/fp');
|
|
|
19
19
|
const dbModel = strapi.db.metadata.get(model.uid);
|
|
20
20
|
for (const attribute of Object.values(dbModel.attributes)){
|
|
21
21
|
/**
|
|
22
|
-
* Only consider unidirectional relations
|
|
23
|
-
|
|
22
|
+
* Only consider unidirectional relations.
|
|
23
|
+
* Self-referential relations (model.uid === uid) are handled by selfReferentialRelations;
|
|
24
|
+
* processing them here would re-insert stale source FK values pointing to deleted entries.
|
|
25
|
+
*/ if (attribute.type !== 'relation' || attribute.target !== uid || attribute.inversedBy || attribute.mappedBy || model.uid === uid) {
|
|
24
26
|
continue;
|
|
25
27
|
}
|
|
26
28
|
// TODO: joinColumn relations
|
|
@@ -105,7 +107,7 @@ var fp = require('lodash/fp');
|
|
|
105
107
|
const oldEntriesMap = oldEntries.reduce((acc, entry)=>{
|
|
106
108
|
const newEntry = newEntryByLocale[entry.locale];
|
|
107
109
|
if (!newEntry) return acc;
|
|
108
|
-
acc[entry.id] = newEntry.id;
|
|
110
|
+
acc[String(entry.id)] = newEntry.id;
|
|
109
111
|
return acc;
|
|
110
112
|
}, {});
|
|
111
113
|
await strapi.db.transaction(async ({ trx })=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unidirectional-relations.js","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record<string, any>[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record<string, any>,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise<boolean>;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise<RelationUpdate[]> => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[entry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n await trx.batchInsert(joinTable.name, newRelations, batchSize);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","column","newRelations","newId","batchSize","dialect","getBatchInsertSize","batchInsert"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"unidirectional-relations.js","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { Data, UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: Data.ID; locale: string }[];\n newVersions: { id: Data.ID; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record<string, any>[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record<string, any>,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise<boolean>;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise<RelationUpdate[]> => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations.\n * Self-referential relations (model.uid === uid) are handled by selfReferentialRelations;\n * processing them here would re-insert stale source FK values pointing to deleted entries.\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy ||\n model.uid === uid\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: Data.ID; locale: string }[],\n newEntries: { id: Data.ID; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[String(entry.id)] = newEntry.id;\n return acc;\n },\n {} as Record<string, Data.ID>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n await trx.batchInsert(joinTable.name, newRelations, batchSize);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","String","column","newRelations","newId","batchSize","dialect","getBatchInsertSize","batchInsert"],"mappings":"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"}
|
|
@@ -17,8 +17,10 @@ import { omit, keyBy } from 'lodash/fp';
|
|
|
17
17
|
const dbModel = strapi.db.metadata.get(model.uid);
|
|
18
18
|
for (const attribute of Object.values(dbModel.attributes)){
|
|
19
19
|
/**
|
|
20
|
-
* Only consider unidirectional relations
|
|
21
|
-
|
|
20
|
+
* Only consider unidirectional relations.
|
|
21
|
+
* Self-referential relations (model.uid === uid) are handled by selfReferentialRelations;
|
|
22
|
+
* processing them here would re-insert stale source FK values pointing to deleted entries.
|
|
23
|
+
*/ if (attribute.type !== 'relation' || attribute.target !== uid || attribute.inversedBy || attribute.mappedBy || model.uid === uid) {
|
|
22
24
|
continue;
|
|
23
25
|
}
|
|
24
26
|
// TODO: joinColumn relations
|
|
@@ -103,7 +105,7 @@ import { omit, keyBy } from 'lodash/fp';
|
|
|
103
105
|
const oldEntriesMap = oldEntries.reduce((acc, entry)=>{
|
|
104
106
|
const newEntry = newEntryByLocale[entry.locale];
|
|
105
107
|
if (!newEntry) return acc;
|
|
106
|
-
acc[entry.id] = newEntry.id;
|
|
108
|
+
acc[String(entry.id)] = newEntry.id;
|
|
107
109
|
return acc;
|
|
108
110
|
}, {});
|
|
109
111
|
await strapi.db.transaction(async ({ trx })=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record<string, any>[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record<string, any>,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise<boolean>;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise<RelationUpdate[]> => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[entry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n await trx.batchInsert(joinTable.name, newRelations, batchSize);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","column","newRelations","newId","batchSize","dialect","getBatchInsertSize","batchInsert"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"unidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { Data, UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: Data.ID; locale: string }[];\n newVersions: { id: Data.ID; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record<string, any>[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record<string, any>,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise<boolean>;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise<RelationUpdate[]> => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations.\n * Self-referential relations (model.uid === uid) are handled by selfReferentialRelations;\n * processing them here would re-insert stale source FK values pointing to deleted entries.\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy ||\n model.uid === uid\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: Data.ID; locale: string }[],\n newEntries: { id: Data.ID; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[String(entry.id)] = newEntry.id;\n return acc;\n },\n {} as Record<string, Data.ID>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n await trx.batchInsert(joinTable.name, newRelations, batchSize);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","String","column","newRelations","newId","batchSize","dialect","getBatchInsertSize","batchInsert"],"mappings":";;AA6BA;;;;AAIC,IACD,MAAMA,IAAAA,GAAO,OACXC,GAAAA,EACA,EAAEC,WAAW,EAAEC,WAAW,EAAe,EACzCC,OAAAA,GAAiC,EAAE,GAAA;AAEnC,IAAA,MAAMC,UAA4B,EAAE;;IAGpC,MAAMC,MAAAA,CAAOC,EAAE,CAACC,WAAW,CAAC,OAAO,EAAEC,GAAG,EAAE,GAAA;AACxC,QAAA,MAAMC,YAAAA,GAAeC,MAAAA,CAAOC,MAAM,CAACN,OAAOI,YAAY,CAAA;AACtD,QAAA,MAAMG,UAAAA,GAAaF,MAAAA,CAAOC,MAAM,CAACN,OAAOO,UAAU,CAAA;AAElD,QAAA,KAAK,MAAMC,KAAAA,IAAS;AAAIJ,YAAAA,GAAAA,YAAAA;AAAiBG,YAAAA,GAAAA;SAAW,CAAE;YACpD,MAAME,OAAAA,GAAUT,OAAOC,EAAE,CAACS,QAAQ,CAACC,GAAG,CAACH,KAAAA,CAAMb,GAAG,CAAA;AAEhD,YAAA,KAAK,MAAMiB,SAAAA,IAAaP,MAAAA,CAAOC,MAAM,CAACG,OAAAA,CAAQI,UAAU,CAAA,CAAU;AAChE;;;;AAIC,YACD,IACED,SAAAA,CAAUE,IAAI,KAAK,UAAA,IACnBF,SAAAA,CAAUG,MAAM,KAAKpB,GAAAA,IACrBiB,SAAAA,CAAUI,UAAU,IACpBJ,SAAAA,CAAUK,QAAQ,IAClBT,KAAAA,CAAMb,GAAG,KAAKA,GAAAA,EACd;AACA,oBAAA;AACF,gBAAA;;gBAGA,MAAMuB,SAAAA,GAAYN,UAAUM,SAAS;AACrC,gBAAA,IAAI,CAACA,SAAAA,EAAW;AACd,oBAAA;AACF,gBAAA;AAEA,gBAAA,MAAM,EAAEC,IAAAA,EAAMC,gBAAgB,EAAE,GAAGF,UAAUG,UAAU;AACvD,gBAAA,MAAM,EAAEF,IAAAA,EAAMG,gBAAgB,EAAE,GAAGJ,UAAUK,iBAAiB;AAE9D;;AAEC;AAED,gBAAA,MAAMC,MAAM5B,WAAAA,CAAY6B,GAAG,CAAC,CAACC,KAAAA,GAAUA,MAAMC,EAAE,CAAA;gBAE/C,MAAMC,oBAAAA,GAAuB,MAAM5B,MAAAA,CAAOC,EAAE,CACzC4B,aAAa,EAAA,CACbC,MAAM,CAAC,GAAA,CAAA,CACPC,IAAI,CAACb,SAAAA,CAAUC,IAAI,CAAA,CACnBa,OAAO,CAACV,gBAAAA,EAAkBE,GAAAA,CAAAA,CAC1BS,WAAW,CAAC9B,GAAAA,CAAAA;gBAEf,IAAIyB,oBAAAA,CAAqBM,MAAM,GAAG,CAAA,EAAG;AACnCnC,oBAAAA,OAAAA,CAAQoC,IAAI,CAAC;AAAEjB,wBAAAA,SAAAA;wBAAWkB,SAAAA,EAAWR;AAAqB,qBAAA,CAAA;AAC5D,gBAAA;AAEA;;;;;;;;;;AAUC,YACD,IAAI,CAACpB,KAAAA,CAAMV,OAAO,EAAEuC,eAAAA,EAAiB;AACnC,oBAAA,MAAMb,MAAM3B,WAAAA,CAAY4B,GAAG,CAAC,CAACC,KAAAA,GAAUA,MAAMC,EAAE,CAAA;;oBAG/C,MAAMW,oBAAAA,GAAuB,MAAMtC,MAAAA,CAAOC,EAAE,CACzC4B,aAAa,EAAA,CACbC,MAAM,CAAC,GAAA,CAAA,CACPC,IAAI,CAACb,SAAAA,CAAUC,IAAI,CAAA,CACnBa,OAAO,CAACV,gBAAAA,EAAkBE,GAAAA,CAAAA,CAC1BS,WAAW,CAAC9B,GAAAA,CAAAA;AAEf,oBAAA,IAAIoC,gBAAAA,GAAmBD,oBAAAA;oBACvB,IAAIxC,OAAAA,CAAQ0C,uBAAuB,EAAE;AACnC,wBAAA,MAAMC,uBAAuB,EAAE;wBAC/B,KAAK,MAAMC,YAAYJ,oBAAAA,CAAsB;AAC3C,4BAAA,IAAI,MAAMxC,OAAAA,CAAQ0C,uBAAuB,CAACE,QAAAA,EAAUlC,OAAOL,GAAAA,CAAAA,EAAM;AAC/DsC,gCAAAA,oBAAAA,CAAqBN,IAAI,CAACO,QAAAA,CAAAA;AAC5B,4BAAA;AACF,wBAAA;wBACAH,gBAAAA,GAAmBE,oBAAAA;AACrB,oBAAA;oBAEA,IAAIF,gBAAAA,CAAiBL,MAAM,GAAG,CAAA,EAAG;;;;AAI/B,wBAAA,MAAMS,YAAAA,GAAeJ,gBAAAA,CAClBK,MAAM,CAAC,CAACF,QAAAA,GAAAA;AACP,4BAAA,MAAMG,kBAAAA,GAAqBjB,oBAAAA,CAAqBkB,IAAI,CAAC,CAACC,WAAAA,GAAAA;AACpD,gCAAA,OAAOA,WAAW,CAAC3B,gBAAAA,CAAiB,KAAKsB,QAAQ,CAACtB,gBAAAA,CAAiB;AACrE,4BAAA,CAAA,CAAA;AAEA,4BAAA,OAAO,CAACyB,kBAAAA;wBACV,CAAA,CAAA,CACCpB,GAAG,CAACuB,IAAAA,CAAKhD,MAAAA,CAAOC,EAAE,CAACS,QAAQ,CAACuC,WAAW,CAACC,SAAS,CAAA,CAAA;AAEpDnD,wBAAAA,OAAAA,CAAQoC,IAAI,CAAC;AAAEjB,4BAAAA,SAAAA;4BAAWkB,SAAAA,EAAWO;AAAa,yBAAA,CAAA;AACpD,oBAAA;AACF,gBAAA;AACF,YAAA;AACF,QAAA;AACF,IAAA,CAAA,CAAA;IAEA,OAAO5C,OAAAA;AACT;AAEA;;;;;;;;;;AAUC,IACD,MAAMoD,IAAAA,GAAO,OACXC,UAAAA,EACAC,UAAAA,EACAC,YAAAA,GAAAA;AAEA;;;;MAKA,MAAMC,gBAAAA,GAAmBC,KAAAA,CAAM,QAAA,EAAUH,UAAAA,CAAAA;AACzC,IAAA,MAAMI,aAAAA,GAAgBL,UAAAA,CAAWM,MAAM,CACrC,CAACC,GAAAA,EAAKjC,KAAAA,GAAAA;AACJ,QAAA,MAAMkC,QAAAA,GAAWL,gBAAgB,CAAC7B,KAAAA,CAAMmC,MAAM,CAAC;QAC/C,IAAI,CAACD,UAAU,OAAOD,GAAAA;AACtBA,QAAAA,GAAG,CAACG,MAAAA,CAAOpC,KAAAA,CAAMC,EAAE,CAAA,CAAE,GAAGiC,SAASjC,EAAE;QACnC,OAAOgC,GAAAA;AACT,IAAA,CAAA,EACA,EAAC,CAAA;IAGH,MAAM3D,MAAAA,CAAOC,EAAE,CAACC,WAAW,CAAC,OAAO,EAAEC,GAAG,EAAE,GAAA;;AAExC,QAAA,KAAK,MAAM,EAAEe,SAAS,EAAEkB,SAAS,EAAE,IAAIkB,YAAAA,CAAc;;AAEnD,YAAA,MAAMS,MAAAA,GAAS7C,SAAAA,CAAUK,iBAAiB,CAACJ,IAAI;AAE/C,YAAA,MAAM6C,YAAAA,GAAe5B,SAAAA,CAAUX,GAAG,CAAC,CAACiB,QAAAA,GAAAA;AAClC,gBAAA,MAAMuB,QAAQR,aAAa,CAACf,QAAQ,CAACqB,OAAO,CAAC;gBAC7C,OAAO;AAAE,oBAAA,GAAGrB,QAAQ;AAAE,oBAAA,CAACqB,SAASE;AAAM,iBAAA;AACxC,YAAA,CAAA,CAAA;AAEA,YAAA,MAAMC,YAAYlE,MAAAA,CAAOC,EAAE,CAACkE,OAAO,CAACC,kBAAkB,EAAA;AACtD,YAAA,MAAMjE,IAAIkE,WAAW,CAACnD,SAAAA,CAAUC,IAAI,EAAE6C,YAAAA,EAAcE,SAAAA,CAAAA;AACtD,QAAA;AACF,IAAA,CAAA,CAAA;AACF;;;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.45.0",
|
|
4
4
|
"description": "Core of Strapi",
|
|
5
5
|
"homepage": "https://strapi.io",
|
|
6
6
|
"bugs": {
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"@koa/cors": "5.0.0",
|
|
60
60
|
"@koa/router": "12.0.2",
|
|
61
61
|
"@paralleldrive/cuid2": "2.2.2",
|
|
62
|
-
"@strapi/admin": "5.
|
|
63
|
-
"@strapi/database": "5.
|
|
64
|
-
"@strapi/generators": "5.
|
|
65
|
-
"@strapi/logger": "5.
|
|
66
|
-
"@strapi/permissions": "5.
|
|
67
|
-
"@strapi/types": "5.
|
|
68
|
-
"@strapi/typescript-utils": "5.
|
|
69
|
-
"@strapi/utils": "5.
|
|
62
|
+
"@strapi/admin": "5.45.0",
|
|
63
|
+
"@strapi/database": "5.45.0",
|
|
64
|
+
"@strapi/generators": "5.45.0",
|
|
65
|
+
"@strapi/logger": "5.45.0",
|
|
66
|
+
"@strapi/permissions": "5.45.0",
|
|
67
|
+
"@strapi/types": "5.45.0",
|
|
68
|
+
"@strapi/typescript-utils": "5.45.0",
|
|
69
|
+
"@strapi/utils": "5.45.0",
|
|
70
70
|
"@vercel/stega": "0.1.2",
|
|
71
71
|
"bcryptjs": "2.4.3",
|
|
72
72
|
"boxen": "5.1.2",
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"configstore": "5.0.1",
|
|
78
78
|
"debug": "4.3.4",
|
|
79
79
|
"delegates": "1.0.0",
|
|
80
|
-
"dotenv": "16.
|
|
80
|
+
"dotenv": "16.6.1",
|
|
81
81
|
"execa": "5.1.1",
|
|
82
|
-
"fs-extra": "11.
|
|
82
|
+
"fs-extra": "11.3.4",
|
|
83
83
|
"glob": "13.0.6",
|
|
84
84
|
"global-agent": "4.1.3",
|
|
85
85
|
"http-errors": "2.0.0",
|
|
@@ -105,9 +105,9 @@
|
|
|
105
105
|
"pkg-up": "3.1.0",
|
|
106
106
|
"qs": "6.15.0",
|
|
107
107
|
"resolve.exports": "2.0.2",
|
|
108
|
-
"semver": "7.
|
|
108
|
+
"semver": "7.7.4",
|
|
109
109
|
"statuses": "2.0.1",
|
|
110
|
-
"typescript": "5.4.
|
|
110
|
+
"typescript": "5.4.5",
|
|
111
111
|
"undici": "6.25.0",
|
|
112
112
|
"yup": "0.32.9",
|
|
113
113
|
"zod": "3.25.67"
|
|
@@ -133,11 +133,11 @@
|
|
|
133
133
|
"@types/node": "24.10.0",
|
|
134
134
|
"@types/node-schedule": "2.1.7",
|
|
135
135
|
"@types/statuses": "2.0.1",
|
|
136
|
-
"eslint-config-custom": "5.
|
|
136
|
+
"eslint-config-custom": "5.45.0",
|
|
137
137
|
"supertest": "7.2.2",
|
|
138
|
-
"tsconfig": "5.
|
|
138
|
+
"tsconfig": "5.45.0",
|
|
139
139
|
"vitest": "catalog:",
|
|
140
|
-
"vitest-config": "5.
|
|
140
|
+
"vitest-config": "5.45.0"
|
|
141
141
|
},
|
|
142
142
|
"engines": {
|
|
143
143
|
"node": ">=20.0.0 <=24.x.x",
|