@strapi/core 5.41.0 → 5.42.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.
@@ -1,5 +1,85 @@
1
1
  import { keyBy, omit } from 'lodash/fp';
2
2
 
3
+ /** Draft id → published id for rows that already have a published counterpart (same document_id + locale). */ const draftToPublishedMap = async (trx, tableName, rowIds)=>{
4
+ const uniqueIds = [
5
+ ...new Set(rowIds)
6
+ ];
7
+ if (uniqueIds.length === 0) {
8
+ return new Map();
9
+ }
10
+ const draftEntries = await strapi.db.getConnection().select('id', 'document_id', 'locale').from(tableName).whereIn('id', uniqueIds).transacting(trx);
11
+ if (draftEntries.length === 0) {
12
+ return new Map();
13
+ }
14
+ const pubEntries = await strapi.db.getConnection().select('id', 'document_id', 'locale').from(tableName).whereNotNull('published_at').whereIn('document_id', draftEntries.map((e)=>e.document_id)).transacting(trx);
15
+ const pubByDocLocale = new Map(pubEntries.map((e)=>[
16
+ `${e.document_id}_${e.locale}`,
17
+ e.id
18
+ ]));
19
+ const map = new Map();
20
+ for (const d of draftEntries){
21
+ const pubId = pubByDocLocale.get(`${d.document_id}_${d.locale}`);
22
+ if (pubId) {
23
+ map.set(String(d.id), String(pubId));
24
+ }
25
+ }
26
+ return map;
27
+ };
28
+ const remapRelatedIds = (rows, relatedCol, idMap)=>rows.map((row)=>{
29
+ const next = idMap.get(String(row[relatedCol]));
30
+ return next ? {
31
+ ...row,
32
+ [relatedCol]: next
33
+ } : row;
34
+ });
35
+ /**
36
+ * Reads join rows tied to the entry being published (`publishedCol` IN draft/old ids) and returns
37
+ * batches for `sync()`. When the FK in `relatedCol` points at a D&P type, maps known draft ids to
38
+ * published ids; otherwise keeps draft ids so sync can still run after both sides exist.
39
+ */ const captureJoinBatches = async (trx, opts)=>{
40
+ const { joinTable, publishedCol, relatedCol, relatedUid, relatedHasDraftAndPublish, schemaUid, oldVersions, newVersions } = opts;
41
+ const batches = [];
42
+ const { name: table } = joinTable;
43
+ const oldIds = oldVersions.map((e)=>e.id);
44
+ if (oldIds.length > 0) {
45
+ const existing = await strapi.db.getConnection().select('*').from(table).whereIn(publishedCol, oldIds).transacting(trx);
46
+ if (existing.length > 0) {
47
+ batches.push({
48
+ joinTable,
49
+ relations: existing,
50
+ entityColumn: publishedCol,
51
+ relatedColumn: relatedCol
52
+ });
53
+ }
54
+ }
55
+ if (!strapi.contentTypes[schemaUid]) {
56
+ return batches;
57
+ }
58
+ const oldLocales = new Set(oldVersions.map((e)=>e.locale));
59
+ const draftsOnly = newVersions.filter((v)=>!oldLocales.has(v.locale));
60
+ if (draftsOnly.length === 0) {
61
+ return batches;
62
+ }
63
+ const draftIds = draftsOnly.map((e)=>e.id);
64
+ const draftRows = await strapi.db.getConnection().select('*').from(table).whereIn(publishedCol, draftIds).transacting(trx);
65
+ if (draftRows.length === 0) {
66
+ return batches;
67
+ }
68
+ let relations = draftRows;
69
+ if (relatedHasDraftAndPublish) {
70
+ const meta = strapi.db.metadata.get(relatedUid);
71
+ const relatedIds = draftRows.map((r)=>r[relatedCol]);
72
+ const map = await draftToPublishedMap(trx, meta.tableName, relatedIds);
73
+ relations = remapRelatedIds(draftRows, relatedCol, map);
74
+ }
75
+ batches.push({
76
+ joinTable,
77
+ relations,
78
+ entityColumn: publishedCol,
79
+ relatedColumn: relatedCol
80
+ });
81
+ return batches;
82
+ };
3
83
  /**
4
84
  * Loads all bidirectional relations that need to be synchronized when content entries change state
5
85
  * (e.g., during publish/unpublish operations).
@@ -44,40 +124,45 @@ import { keyBy, omit } from 'lodash/fp';
44
124
  * @param uid - The unique identifier of the content type being processed
45
125
  * @param context - Object containing arrays of old and new entry versions
46
126
  * @returns Array of objects containing join table metadata and relations to be updated
47
- */ const load = async (uid, { oldVersions })=>{
127
+ */ const load = async (uid, { oldVersions, newVersions })=>{
48
128
  const relationsToUpdate = [];
49
129
  await strapi.db.transaction(async ({ trx })=>{
50
- const contentTypes = Object.values(strapi.contentTypes);
51
- const components = Object.values(strapi.components);
52
- for (const model of [
53
- ...contentTypes,
54
- ...components
55
- ]){
130
+ const models = [
131
+ ...Object.values(strapi.contentTypes),
132
+ ...Object.values(strapi.components)
133
+ ];
134
+ for (const model of models){
56
135
  const dbModel = strapi.db.metadata.get(model.uid);
57
136
  for (const attribute of Object.values(dbModel.attributes)){
58
- // Skip if not a bidirectional relation targeting our content type
59
- if (attribute.type !== 'relation' || attribute.target !== uid || !(attribute.inversedBy || attribute.mappedBy)) {
137
+ const joinTable = attribute.joinTable;
138
+ if (attribute.type !== 'relation' || !joinTable) {
60
139
  continue;
61
140
  }
62
- // If it's a self referencing relation, there is no need to sync any relation
63
- // The order will already be handled as both sides are inside the same content type
64
- if (model.uid === uid) {
141
+ if (!(attribute.inversedBy || attribute.mappedBy)) {
65
142
  continue;
66
143
  }
67
- const joinTable = attribute.joinTable;
68
- if (!joinTable) {
144
+ // Owning side: e.g. Author.articles when publishing an Author.
145
+ const isOwningSide = !!attribute.inversedBy && model.uid === uid && attribute.relation === 'manyToMany' && model.uid !== attribute.target;
146
+ // Inverse side: e.g. Article.authors when publishing an Article.
147
+ const isInverseSide = attribute.target === uid && model.uid !== uid;
148
+ if (!isOwningSide && !isInverseSide) {
69
149
  continue;
70
150
  }
71
- const { name: targetColumnName } = joinTable.inverseJoinColumn;
72
- // Load all relations that need their order preserved
73
- const oldEntryIds = oldVersions.map((entry)=>entry.id);
74
- const existingRelations = await strapi.db.getConnection().select('*').from(joinTable.name).whereIn(targetColumnName, oldEntryIds).transacting(trx);
75
- if (existingRelations.length > 0) {
76
- relationsToUpdate.push({
77
- joinTable,
78
- relations: existingRelations
79
- });
80
- }
151
+ // Direction determines which join column belongs to the entity being published
152
+ const publishedCol = isOwningSide ? joinTable.joinColumn.name : joinTable.inverseJoinColumn.name;
153
+ const relatedCol = isOwningSide ? joinTable.inverseJoinColumn.name : joinTable.joinColumn.name;
154
+ const relatedUid = isOwningSide ? attribute.target : model.uid;
155
+ const batches = await captureJoinBatches(trx, {
156
+ joinTable,
157
+ publishedCol,
158
+ relatedCol,
159
+ relatedUid,
160
+ relatedHasDraftAndPublish: isOwningSide ? !!strapi.contentTypes[relatedUid]?.options?.draftAndPublish : !!model.options?.draftAndPublish,
161
+ schemaUid: model.uid,
162
+ oldVersions,
163
+ newVersions
164
+ });
165
+ relationsToUpdate.push(...batches);
81
166
  }
82
167
  }
83
168
  });
@@ -109,21 +194,19 @@ import { keyBy, omit } from 'lodash/fp';
109
194
  * @param newEntries - Array of new entry versions with their IDs and locales
110
195
  * @param existingRelations - Array of join table data containing the relations to be updated
111
196
  */ const sync = async (oldEntries, newEntries, existingRelations)=>{
112
- // Group new entries by locale for easier lookup
113
197
  const newEntriesByLocale = keyBy('locale', newEntries);
114
- // Create a mapping of old entry IDs to new entry IDs based on locale
115
198
  const entryIdMapping = oldEntries.reduce((acc, oldEntry)=>{
116
199
  const newEntry = newEntriesByLocale[oldEntry.locale];
117
- if (!newEntry) return acc;
200
+ if (!newEntry) {
201
+ return acc;
202
+ }
118
203
  acc[oldEntry.id] = newEntry.id;
119
204
  return acc;
120
205
  }, {});
121
206
  const republishedEntryIds = new Set(newEntries.map((e)=>String(e.id)));
122
207
  const isRepublishedEntry = (id)=>republishedEntryIds.has(String(id));
123
208
  await strapi.db.transaction(async ({ trx })=>{
124
- for (const { joinTable, relations } of existingRelations){
125
- const sourceColumn = joinTable.inverseJoinColumn.name;
126
- const targetColumn = joinTable.joinColumn.name;
209
+ for (const { joinTable, relations, entityColumn: sourceColumn, relatedColumn: targetColumn } of existingRelations){
127
210
  const orderColumn = joinTable.orderColumnName;
128
211
  // Failsafe in case those don't exist
129
212
  if (!sourceColumn || !targetColumn || !orderColumn) {
@@ -163,7 +246,8 @@ import { keyBy, omit } from 'lodash/fp';
163
246
  [orderColumn]: originalOrder
164
247
  }));
165
248
  if (toInsert.length) {
166
- await trx.batchInsert(joinTable.name, toInsert, 1000);
249
+ const batchSize = strapi.db.dialect.getBatchInsertSize();
250
+ await trx.batchInsert(joinTable.name, toInsert, batchSize);
167
251
  }
168
252
  }
169
253
  });
@@ -1 +1 @@
1
- {"version":3,"file":"bidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\nimport type { UID, Schema } from '@strapi/types';\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationEntry {\n joinTable: JoinTable;\n relations: Record<string, unknown>[];\n}\n\n/**\n * Loads all bidirectional relations that need to be synchronized when content entries change state\n * (e.g., during publish/unpublish operations).\n *\n * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.\n * When an entry is published, the following occurs:\n *\n * 1. The old published entry is deleted\n * 2. A new entry is created with all its relations\n *\n * This process affects relation ordering in the following way:\n *\n * Initial state (Entry A related to X, Y, Z):\n * ```\n * Entry A (draft) Entry A (published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A\n * ```\n *\n * After publishing Entry A (without relation order sync):\n * ```\n * Entry A (draft) Entry A (new published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A\n * (all relations appear last in order)\n * ```\n *\n * This module preserves the original ordering from both perspectives by:\n * 1. Capturing the relation order before the entry state changes\n * 2. Restoring this order after the new relations are created\n *\n * @param uid - The unique identifier of the content type being processed\n * @param context - Object containing arrays of old and new entry versions\n * @returns Array of objects containing join table metadata and relations to be updated\n */\nconst load = async (uid: UID.ContentType, { oldVersions }: LoadContext) => {\n const relationsToUpdate: RelationEntry[] = [];\n\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 Record<string, any>[]) {\n // Skip if not a bidirectional relation targeting our content type\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n !(attribute.inversedBy || attribute.mappedBy)\n ) {\n continue;\n }\n\n // If it's a self referencing relation, there is no need to sync any relation\n // The order will already be handled as both sides are inside the same content type\n if (model.uid === uid) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n // Load all relations that need their order preserved\n const oldEntryIds = oldVersions.map((entry) => entry.id);\n\n const existingRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, oldEntryIds)\n .transacting(trx);\n\n if (existingRelations.length > 0) {\n relationsToUpdate.push({ joinTable, relations: existingRelations });\n }\n }\n }\n });\n\n return relationsToUpdate;\n};\n\n/**\n * Synchronizes the order of bidirectional relations after content entries have changed state.\n *\n * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.\n * While the order of relations from the entry's perspective is maintained (as they're created in order),\n * the inverse relations (from related entries' perspective) would all appear last in order since they're new.\n *\n * Example:\n * ```\n * Before publish:\n * Article(id:1) →(order:1)→ Category(id:5)\n * Category(id:5) →(order:3)→ Article(id:1)\n *\n * After publish (without sync):\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]\n *\n * After sync:\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:3)→ Article(id:2) [order restored]\n * ```\n *\n * @param oldEntries - Array of previous entry versions with their IDs and locales\n * @param newEntries - Array of new entry versions with their IDs and locales\n * @param existingRelations - Array of join table data containing the relations to be updated\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n existingRelations: RelationEntry[]\n) => {\n // Group new entries by locale for easier lookup\n const newEntriesByLocale = keyBy('locale', newEntries);\n\n // Create a mapping of old entry IDs to new entry IDs based on locale\n const entryIdMapping = oldEntries.reduce(\n (acc, oldEntry) => {\n const newEntry = newEntriesByLocale[oldEntry.locale];\n if (!newEntry) return acc;\n acc[oldEntry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n const republishedEntryIds = new Set(newEntries.map((e) => String(e.id)));\n const isRepublishedEntry = (id: string | number) => republishedEntryIds.has(String(id));\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of existingRelations) {\n const sourceColumn = joinTable.inverseJoinColumn.name;\n const targetColumn = joinTable.joinColumn.name;\n const orderColumn = joinTable.orderColumnName;\n\n // Failsafe in case those don't exist\n if (!sourceColumn || !targetColumn || !orderColumn) {\n continue;\n }\n\n const mappedRelations = relations\n .map((relation) => ({\n relation,\n oldSourceId: relation[sourceColumn] as string,\n targetId: relation[targetColumn] as string,\n originalOrder: relation[orderColumn],\n newSourceId: entryIdMapping[relation[sourceColumn] as string],\n }))\n .filter((r): r is typeof r & { newSourceId: string } => Boolean(r.newSourceId));\n\n if (!mappedRelations.length) continue;\n\n const newSourceIds = mappedRelations.map((r) => r.newSourceId);\n\n // Batch UPDATE: set each row's order in a single statement using CASE\n const caseFragments = mappedRelations.map(() => `WHEN ?? = ? AND ?? = ? THEN ?`);\n const caseBindings = mappedRelations.flatMap(({ newSourceId, targetId, originalOrder }) => [\n sourceColumn,\n newSourceId,\n targetColumn,\n targetId,\n originalOrder,\n ]);\n\n await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .update({\n [orderColumn]: trx.raw(`CASE ${caseFragments.join(' ')} ELSE ?? END`, [\n ...caseBindings,\n orderColumn,\n ]),\n });\n\n // Batch SELECT: find which rows exist so we know what to insert\n const existingRows = await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .select(sourceColumn, targetColumn);\n\n const existingSet = new Set(\n existingRows.map((r: Record<string, unknown>) => `${r[sourceColumn]}:${r[targetColumn]}`)\n );\n\n // Batch INSERT: insert cascade-deleted rows that aren't from republished sources\n const toInsert = mappedRelations\n .filter(\n ({ newSourceId, targetId }) =>\n !existingSet.has(`${newSourceId}:${targetId}`) && !isRepublishedEntry(newSourceId)\n )\n .map(({ relation, newSourceId, originalOrder }) => ({\n ...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),\n [sourceColumn]: newSourceId,\n [orderColumn]: originalOrder,\n }));\n\n if (toInsert.length) {\n await trx.batchInsert(joinTable.name, toInsert, 1000);\n }\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","oldVersions","relationsToUpdate","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","targetColumnName","inverseJoinColumn","oldEntryIds","map","entry","id","existingRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","oldEntries","newEntries","newEntriesByLocale","keyBy","entryIdMapping","reduce","acc","oldEntry","newEntry","locale","republishedEntryIds","Set","e","String","isRepublishedEntry","has","sourceColumn","targetColumn","joinColumn","orderColumn","orderColumnName","mappedRelations","relation","oldSourceId","targetId","originalOrder","newSourceId","filter","r","Boolean","newSourceIds","caseFragments","caseBindings","flatMap","update","raw","join","existingRows","existingSet","toInsert","omit","identifiers","ID_COLUMN","batchInsert"],"mappings":"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"}
1
+ {"version":3,"file":"bidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"sourcesContent":["import { keyBy, omit } from 'lodash/fp';\nimport type { UID, Schema } from '@strapi/types';\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationEntry {\n joinTable: JoinTable;\n relations: Record<string, unknown>[];\n /** FK column for the entity being published */\n entityColumn: string;\n /** FK column for the related entity */\n relatedColumn: string;\n}\n\n/** Draft id → published id for rows that already have a published counterpart (same document_id + locale). */\nconst draftToPublishedMap = async (trx: any, tableName: string, rowIds: unknown[]) => {\n const uniqueIds = [...new Set(rowIds)];\n if (uniqueIds.length === 0) {\n return new Map<string, string>();\n }\n\n const draftEntries = await strapi.db\n .getConnection()\n .select('id', 'document_id', 'locale')\n .from(tableName)\n .whereIn('id', uniqueIds as any)\n .transacting(trx);\n\n if (draftEntries.length === 0) {\n return new Map<string, string>();\n }\n\n const pubEntries = await strapi.db\n .getConnection()\n .select('id', 'document_id', 'locale')\n .from(tableName)\n .whereNotNull('published_at')\n .whereIn(\n 'document_id',\n draftEntries.map((e: any) => e.document_id)\n )\n .transacting(trx);\n\n const pubByDocLocale = new Map(\n pubEntries.map((e: any) => [`${e.document_id}_${e.locale}`, e.id])\n );\n\n const map = new Map<string, string>();\n for (const d of draftEntries) {\n const pubId = pubByDocLocale.get(`${d.document_id}_${d.locale}`);\n if (pubId) {\n map.set(String(d.id), String(pubId));\n }\n }\n return map;\n};\n\nconst remapRelatedIds = (rows: any[], relatedCol: string, idMap: Map<string, string>) =>\n rows.map((row) => {\n const next = idMap.get(String(row[relatedCol]));\n return next ? { ...row, [relatedCol]: next } : row;\n });\n\n/**\n * Reads join rows tied to the entry being published (`publishedCol` IN draft/old ids) and returns\n * batches for `sync()`. When the FK in `relatedCol` points at a D&P type, maps known draft ids to\n * published ids; otherwise keeps draft ids so sync can still run after both sides exist.\n */\nconst captureJoinBatches = async (\n trx: any,\n opts: {\n joinTable: any;\n /** Column holding ids of the document being published (the `uid` passed to load). */\n publishedCol: string;\n /** The other FK; may be remapped via draftToPublishedMap. */\n relatedCol: string;\n /** Content type behind `relatedCol` (used for D&P + table name). */\n relatedUid: UID.ContentType;\n relatedHasDraftAndPublish: boolean;\n /** Model that owns this attribute; draft capture is skipped for components. */\n schemaUid: UID.ContentType;\n oldVersions: LoadContext['oldVersions'];\n newVersions: LoadContext['newVersions'];\n }\n): Promise<RelationEntry[]> => {\n const {\n joinTable,\n publishedCol,\n relatedCol,\n relatedUid,\n relatedHasDraftAndPublish,\n schemaUid,\n oldVersions,\n newVersions,\n } = opts;\n\n const batches: RelationEntry[] = [];\n const { name: table } = joinTable;\n\n const oldIds = oldVersions.map((e) => e.id);\n if (oldIds.length > 0) {\n const existing = await strapi.db\n .getConnection()\n .select('*')\n .from(table)\n .whereIn(publishedCol, oldIds)\n .transacting(trx);\n if (existing.length > 0) {\n batches.push({\n joinTable,\n relations: existing,\n entityColumn: publishedCol,\n relatedColumn: relatedCol,\n });\n }\n }\n\n if (!strapi.contentTypes[schemaUid]) {\n return batches;\n }\n\n const oldLocales = new Set(oldVersions.map((e) => e.locale));\n const draftsOnly = newVersions.filter((v) => !oldLocales.has(v.locale));\n if (draftsOnly.length === 0) {\n return batches;\n }\n\n const draftIds = draftsOnly.map((e) => e.id);\n const draftRows = await strapi.db\n .getConnection()\n .select('*')\n .from(table)\n .whereIn(publishedCol, draftIds)\n .transacting(trx);\n\n if (draftRows.length === 0) {\n return batches;\n }\n\n let relations = draftRows;\n if (relatedHasDraftAndPublish) {\n const meta = strapi.db.metadata.get(relatedUid);\n const relatedIds = draftRows.map((r: any) => r[relatedCol]);\n const map = await draftToPublishedMap(trx, meta.tableName, relatedIds);\n relations = remapRelatedIds(draftRows, relatedCol, map);\n }\n\n batches.push({ joinTable, relations, entityColumn: publishedCol, relatedColumn: relatedCol });\n return batches;\n};\n\n/**\n * Loads all bidirectional relations that need to be synchronized when content entries change state\n * (e.g., during publish/unpublish operations).\n *\n * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.\n * When an entry is published, the following occurs:\n *\n * 1. The old published entry is deleted\n * 2. A new entry is created with all its relations\n *\n * This process affects relation ordering in the following way:\n *\n * Initial state (Entry A related to X, Y, Z):\n * ```\n * Entry A (draft) Entry A (published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A\n * ```\n *\n * After publishing Entry A (without relation order sync):\n * ```\n * Entry A (draft) Entry A (new published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A\n * (all relations appear last in order)\n * ```\n *\n * This module preserves the original ordering from both perspectives by:\n * 1. Capturing the relation order before the entry state changes\n * 2. Restoring this order after the new relations are created\n *\n * @param uid - The unique identifier of the content type being processed\n * @param context - Object containing arrays of old and new entry versions\n * @returns Array of objects containing join table metadata and relations to be updated\n */\nconst load = async (uid: UID.ContentType, { oldVersions, newVersions }: LoadContext) => {\n const relationsToUpdate: RelationEntry[] = [];\n\n await strapi.db.transaction(async ({ trx }) => {\n const models = [\n ...(Object.values(strapi.contentTypes) as Schema.ContentType[]),\n ...Object.values(strapi.components),\n ];\n\n for (const model of models) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as Record<string, any>[]) {\n const joinTable = attribute.joinTable;\n\n if (attribute.type !== 'relation' || !joinTable) {\n continue;\n }\n\n if (!(attribute.inversedBy || attribute.mappedBy)) {\n continue;\n }\n\n // Owning side: e.g. Author.articles when publishing an Author.\n const isOwningSide =\n !!attribute.inversedBy &&\n model.uid === uid &&\n attribute.relation === 'manyToMany' &&\n model.uid !== attribute.target;\n\n // Inverse side: e.g. Article.authors when publishing an Article.\n const isInverseSide = attribute.target === uid && model.uid !== uid;\n\n if (!isOwningSide && !isInverseSide) {\n continue;\n }\n\n // Direction determines which join column belongs to the entity being published\n const publishedCol = isOwningSide\n ? joinTable.joinColumn.name\n : joinTable.inverseJoinColumn.name;\n const relatedCol = isOwningSide\n ? joinTable.inverseJoinColumn.name\n : joinTable.joinColumn.name;\n\n const relatedUid = (isOwningSide ? attribute.target : model.uid) as UID.ContentType;\n\n const batches = await captureJoinBatches(trx, {\n joinTable,\n publishedCol,\n relatedCol,\n relatedUid,\n relatedHasDraftAndPublish: isOwningSide\n ? !!strapi.contentTypes[relatedUid]?.options?.draftAndPublish\n : !!model.options?.draftAndPublish,\n schemaUid: model.uid as UID.ContentType,\n oldVersions,\n newVersions,\n });\n relationsToUpdate.push(...batches);\n }\n }\n });\n\n return relationsToUpdate;\n};\n\n/**\n * Synchronizes the order of bidirectional relations after content entries have changed state.\n *\n * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.\n * While the order of relations from the entry's perspective is maintained (as they're created in order),\n * the inverse relations (from related entries' perspective) would all appear last in order since they're new.\n *\n * Example:\n * ```\n * Before publish:\n * Article(id:1) →(order:1)→ Category(id:5)\n * Category(id:5) →(order:3)→ Article(id:1)\n *\n * After publish (without sync):\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]\n *\n * After sync:\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:3)→ Article(id:2) [order restored]\n * ```\n *\n * @param oldEntries - Array of previous entry versions with their IDs and locales\n * @param newEntries - Array of new entry versions with their IDs and locales\n * @param existingRelations - Array of join table data containing the relations to be updated\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n existingRelations: RelationEntry[]\n) => {\n const newEntriesByLocale = keyBy('locale', newEntries);\n\n const entryIdMapping = oldEntries.reduce(\n (acc, oldEntry) => {\n const newEntry = newEntriesByLocale[oldEntry.locale];\n if (!newEntry) {\n return acc;\n }\n acc[oldEntry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n const republishedEntryIds = new Set(newEntries.map((e) => String(e.id)));\n const isRepublishedEntry = (id: string | number) => republishedEntryIds.has(String(id));\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const {\n joinTable,\n relations,\n entityColumn: sourceColumn,\n relatedColumn: targetColumn,\n } of existingRelations) {\n const orderColumn = joinTable.orderColumnName;\n\n // Failsafe in case those don't exist\n if (!sourceColumn || !targetColumn || !orderColumn) {\n continue;\n }\n\n const mappedRelations = relations\n .map((relation) => ({\n relation,\n oldSourceId: relation[sourceColumn] as string,\n targetId: relation[targetColumn] as string,\n originalOrder: relation[orderColumn],\n newSourceId: entryIdMapping[relation[sourceColumn] as string],\n }))\n .filter((r): r is typeof r & { newSourceId: string } => Boolean(r.newSourceId));\n\n if (!mappedRelations.length) continue;\n\n const newSourceIds = mappedRelations.map((r) => r.newSourceId);\n\n // Batch UPDATE: set each row's order in a single statement using CASE\n const caseFragments = mappedRelations.map(() => `WHEN ?? = ? AND ?? = ? THEN ?`);\n const caseBindings = mappedRelations.flatMap(({ newSourceId, targetId, originalOrder }) => [\n sourceColumn,\n newSourceId,\n targetColumn,\n targetId,\n originalOrder,\n ]);\n\n await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .update({\n [orderColumn]: trx.raw(`CASE ${caseFragments.join(' ')} ELSE ?? END`, [\n ...caseBindings,\n orderColumn,\n ]),\n });\n\n // Batch SELECT: find which rows exist so we know what to insert\n const existingRows = await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .select(sourceColumn, targetColumn);\n\n const existingSet = new Set(\n existingRows.map((r: Record<string, unknown>) => `${r[sourceColumn]}:${r[targetColumn]}`)\n );\n\n // Batch INSERT: insert cascade-deleted rows that aren't from republished sources\n const toInsert = mappedRelations\n .filter(\n ({ newSourceId, targetId }) =>\n !existingSet.has(`${newSourceId}:${targetId}`) && !isRepublishedEntry(newSourceId)\n )\n .map(({ relation, newSourceId, originalOrder }) => ({\n ...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),\n [sourceColumn]: newSourceId,\n [orderColumn]: originalOrder,\n }));\n\n if (toInsert.length) {\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n await trx.batchInsert(joinTable.name, toInsert, batchSize);\n }\n }\n });\n};\n\nexport { load, sync };\n"],"names":["draftToPublishedMap","trx","tableName","rowIds","uniqueIds","Set","length","Map","draftEntries","strapi","db","getConnection","select","from","whereIn","transacting","pubEntries","whereNotNull","map","e","document_id","pubByDocLocale","locale","id","d","pubId","get","set","String","remapRelatedIds","rows","relatedCol","idMap","row","next","captureJoinBatches","opts","joinTable","publishedCol","relatedUid","relatedHasDraftAndPublish","schemaUid","oldVersions","newVersions","batches","name","table","oldIds","existing","push","relations","entityColumn","relatedColumn","contentTypes","oldLocales","draftsOnly","filter","v","has","draftIds","draftRows","meta","metadata","relatedIds","r","load","uid","relationsToUpdate","transaction","models","Object","values","components","model","dbModel","attribute","attributes","type","inversedBy","mappedBy","isOwningSide","relation","target","isInverseSide","joinColumn","inverseJoinColumn","options","draftAndPublish","sync","oldEntries","newEntries","existingRelations","newEntriesByLocale","keyBy","entryIdMapping","reduce","acc","oldEntry","newEntry","republishedEntryIds","isRepublishedEntry","sourceColumn","targetColumn","orderColumn","orderColumnName","mappedRelations","oldSourceId","targetId","originalOrder","newSourceId","Boolean","newSourceIds","caseFragments","caseBindings","flatMap","update","raw","join","existingRows","existingSet","toInsert","omit","identifiers","ID_COLUMN","batchSize","dialect","getBatchInsertSize","batchInsert"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/core",
3
- "version": "5.41.0",
3
+ "version": "5.42.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.41.0",
63
- "@strapi/database": "5.41.0",
64
- "@strapi/generators": "5.41.0",
65
- "@strapi/logger": "5.41.0",
66
- "@strapi/permissions": "5.41.0",
67
- "@strapi/types": "5.41.0",
68
- "@strapi/typescript-utils": "5.41.0",
69
- "@strapi/utils": "5.41.0",
62
+ "@strapi/admin": "5.42.0",
63
+ "@strapi/database": "5.42.0",
64
+ "@strapi/generators": "5.42.0",
65
+ "@strapi/logger": "5.42.0",
66
+ "@strapi/permissions": "5.42.0",
67
+ "@strapi/types": "5.42.0",
68
+ "@strapi/typescript-utils": "5.42.0",
69
+ "@strapi/utils": "5.42.0",
70
70
  "@vercel/stega": "0.1.2",
71
71
  "bcryptjs": "2.4.3",
72
72
  "boxen": "5.1.2",
@@ -108,7 +108,7 @@
108
108
  "semver": "7.5.4",
109
109
  "statuses": "2.0.1",
110
110
  "typescript": "5.4.4",
111
- "undici": "6.24.0",
111
+ "undici": "6.24.1",
112
112
  "yup": "0.32.9",
113
113
  "zod": "3.25.67"
114
114
  },
@@ -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.41.0",
136
+ "eslint-config-custom": "5.42.0",
137
137
  "supertest": "7.2.2",
138
- "tsconfig": "5.41.0",
138
+ "tsconfig": "5.42.0",
139
139
  "vitest": "catalog:",
140
- "vitest-config": "5.41.0"
140
+ "vitest-config": "5.42.0"
141
141
  },
142
142
  "engines": {
143
143
  "node": ">=20.0.0 <=24.x.x",