@strapi/core 5.49.0 → 5.50.1

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.
Files changed (272) hide show
  1. package/dist/Strapi.d.ts.map +1 -1
  2. package/dist/Strapi.js +2 -0
  3. package/dist/Strapi.js.map +1 -1
  4. package/dist/Strapi.mjs +2 -0
  5. package/dist/Strapi.mjs.map +1 -1
  6. package/dist/configuration/config-loader.d.ts.map +1 -1
  7. package/dist/configuration/get-dirs.d.ts.map +1 -1
  8. package/dist/configuration/index.d.ts +0 -3
  9. package/dist/configuration/index.d.ts.map +1 -1
  10. package/dist/configuration/index.js +0 -3
  11. package/dist/configuration/index.js.map +1 -1
  12. package/dist/configuration/index.mjs +0 -3
  13. package/dist/configuration/index.mjs.map +1 -1
  14. package/dist/configuration/server-config.d.ts +11 -0
  15. package/dist/configuration/server-config.d.ts.map +1 -0
  16. package/dist/configuration/server-config.js +16 -0
  17. package/dist/configuration/server-config.js.map +1 -0
  18. package/dist/configuration/server-config.mjs +14 -0
  19. package/dist/configuration/server-config.mjs.map +1 -0
  20. package/dist/configuration/urls.d.ts.map +1 -1
  21. package/dist/core-api/controller/collection-type.d.ts.map +1 -1
  22. package/dist/core-api/controller/single-type.d.ts.map +1 -1
  23. package/dist/core-api/controller/transform.d.ts.map +1 -1
  24. package/dist/core-api/routes/index.d.ts.map +1 -1
  25. package/dist/core-api/routes/validation/attributes.d.ts.map +1 -1
  26. package/dist/core-api/routes/validation/component.js +3 -3
  27. package/dist/core-api/routes/validation/component.js.map +1 -1
  28. package/dist/core-api/routes/validation/component.mjs +3 -3
  29. package/dist/core-api/routes/validation/component.mjs.map +1 -1
  30. package/dist/core-api/routes/validation/mappers.d.ts.map +1 -1
  31. package/dist/core-api/routes/validation/utils.d.ts.map +1 -1
  32. package/dist/core-api/service/collection-type.d.ts.map +1 -1
  33. package/dist/core-api/service/pagination.d.ts.map +1 -1
  34. package/dist/core-api/service/single-type.d.ts.map +1 -1
  35. package/dist/domain/content-type/index.d.ts.map +1 -1
  36. package/dist/domain/content-type/validator.d.ts.map +1 -1
  37. package/dist/domain/module/index.d.ts.map +1 -1
  38. package/dist/domain/module/validation.d.ts.map +1 -1
  39. package/dist/ee/index.d.ts +2 -1
  40. package/dist/ee/index.d.ts.map +1 -1
  41. package/dist/ee/license.d.ts.map +1 -1
  42. package/dist/factories.d.ts.map +1 -1
  43. package/dist/index.d.ts +4 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js.map +1 -1
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/loaders/plugins/get-enabled-plugins.d.ts.map +1 -1
  48. package/dist/loaders/sanitizers.d.ts.map +1 -1
  49. package/dist/loaders/src-index.d.ts.map +1 -1
  50. package/dist/loaders/validators.d.ts.map +1 -1
  51. package/dist/middlewares/body.d.ts.map +1 -1
  52. package/dist/middlewares/body.js +1 -1
  53. package/dist/middlewares/body.js.map +1 -1
  54. package/dist/middlewares/body.mjs +1 -1
  55. package/dist/middlewares/body.mjs.map +1 -1
  56. package/dist/middlewares/cors.d.ts.map +1 -1
  57. package/dist/middlewares/errors.d.ts +5 -0
  58. package/dist/middlewares/errors.d.ts.map +1 -1
  59. package/dist/middlewares/errors.js.map +1 -1
  60. package/dist/middlewares/errors.mjs.map +1 -1
  61. package/dist/middlewares/favicon.d.ts +0 -1
  62. package/dist/middlewares/favicon.d.ts.map +1 -1
  63. package/dist/migrations/draft-publish.d.ts.map +1 -1
  64. package/dist/migrations/first-published-at.d.ts.map +1 -1
  65. package/dist/migrations/i18n.d.ts.map +1 -1
  66. package/dist/migrations/index.d.ts.map +1 -1
  67. package/dist/package.json.js +17 -20
  68. package/dist/package.json.js.map +1 -1
  69. package/dist/package.json.mjs +17 -20
  70. package/dist/package.json.mjs.map +1 -1
  71. package/dist/providers/provider.d.ts.map +1 -1
  72. package/dist/registries/apis.d.ts.map +1 -1
  73. package/dist/registries/components.d.ts +1 -1
  74. package/dist/registries/components.d.ts.map +1 -1
  75. package/dist/registries/content-types.d.ts +2 -3
  76. package/dist/registries/content-types.d.ts.map +1 -1
  77. package/dist/registries/controllers.d.ts +3 -3
  78. package/dist/registries/controllers.d.ts.map +1 -1
  79. package/dist/registries/custom-fields.d.ts.map +1 -1
  80. package/dist/registries/hooks.d.ts +3 -4
  81. package/dist/registries/hooks.d.ts.map +1 -1
  82. package/dist/registries/middlewares.d.ts +2 -3
  83. package/dist/registries/middlewares.d.ts.map +1 -1
  84. package/dist/registries/models.d.ts +1 -1
  85. package/dist/registries/modules.d.ts.map +1 -1
  86. package/dist/registries/namespace.d.ts.map +1 -1
  87. package/dist/registries/plugins.d.ts.map +1 -1
  88. package/dist/registries/policies.d.ts +1 -2
  89. package/dist/registries/policies.d.ts.map +1 -1
  90. package/dist/registries/sanitizers.d.ts +2 -2
  91. package/dist/registries/services.d.ts +3 -3
  92. package/dist/registries/services.d.ts.map +1 -1
  93. package/dist/registries/validators.d.ts +2 -2
  94. package/dist/services/ai.d.ts.map +1 -1
  95. package/dist/services/config.d.ts.map +1 -1
  96. package/dist/services/content-api/index.d.ts +4 -4
  97. package/dist/services/content-api/index.d.ts.map +1 -1
  98. package/dist/services/content-api/permissions/engine.d.ts.map +1 -1
  99. package/dist/services/content-api/permissions/index.d.ts +2 -2
  100. package/dist/services/content-api/permissions/index.d.ts.map +1 -1
  101. package/dist/services/content-api/permissions/providers/action.d.ts +1 -1
  102. package/dist/services/content-api/permissions/providers/action.d.ts.map +1 -1
  103. package/dist/services/content-api/permissions/providers/condition.d.ts +1 -1
  104. package/dist/services/content-api/permissions/providers/condition.d.ts.map +1 -1
  105. package/dist/services/content-source-maps.d.ts.map +1 -1
  106. package/dist/services/core-store.d.ts.map +1 -1
  107. package/dist/services/core-store.js +1 -1
  108. package/dist/services/core-store.js.map +1 -1
  109. package/dist/services/core-store.mjs +1 -1
  110. package/dist/services/core-store.mjs.map +1 -1
  111. package/dist/services/cron.d.ts +5 -5
  112. package/dist/services/custom-fields.d.ts.map +1 -1
  113. package/dist/services/document-service/attributes/index.d.ts +0 -1
  114. package/dist/services/document-service/attributes/index.d.ts.map +1 -1
  115. package/dist/services/document-service/common.d.ts.map +1 -1
  116. package/dist/services/document-service/components.d.ts +2 -2
  117. package/dist/services/document-service/components.d.ts.map +1 -1
  118. package/dist/services/document-service/components.js +7 -7
  119. package/dist/services/document-service/components.js.map +1 -1
  120. package/dist/services/document-service/components.mjs +7 -7
  121. package/dist/services/document-service/components.mjs.map +1 -1
  122. package/dist/services/document-service/draft-and-publish.d.ts +0 -1
  123. package/dist/services/document-service/draft-and-publish.d.ts.map +1 -1
  124. package/dist/services/document-service/entries.d.ts.map +1 -1
  125. package/dist/services/document-service/events.d.ts.map +1 -1
  126. package/dist/services/document-service/first-published-at.d.ts.map +1 -1
  127. package/dist/services/document-service/index.d.ts.map +1 -1
  128. package/dist/services/document-service/internationalization.d.ts +0 -1
  129. package/dist/services/document-service/internationalization.d.ts.map +1 -1
  130. package/dist/services/document-service/middlewares/middleware-manager.d.ts.map +1 -1
  131. package/dist/services/document-service/params.d.ts +1 -1
  132. package/dist/services/document-service/params.d.ts.map +1 -1
  133. package/dist/services/document-service/publication-filter.d.ts +1 -1
  134. package/dist/services/document-service/publication-filter.d.ts.map +1 -1
  135. package/dist/services/document-service/repository.d.ts.map +1 -1
  136. package/dist/services/document-service/repository.js +12 -8
  137. package/dist/services/document-service/repository.js.map +1 -1
  138. package/dist/services/document-service/repository.mjs +12 -8
  139. package/dist/services/document-service/repository.mjs.map +1 -1
  140. package/dist/services/document-service/transform/data.d.ts.map +1 -1
  141. package/dist/services/document-service/transform/fields.d.ts.map +1 -1
  142. package/dist/services/document-service/transform/id-map.d.ts.map +1 -1
  143. package/dist/services/document-service/transform/id-map.js +7 -4
  144. package/dist/services/document-service/transform/id-map.js.map +1 -1
  145. package/dist/services/document-service/transform/id-map.mjs +7 -4
  146. package/dist/services/document-service/transform/id-map.mjs.map +1 -1
  147. package/dist/services/document-service/transform/id-transform.d.ts +0 -1
  148. package/dist/services/document-service/transform/id-transform.d.ts.map +1 -1
  149. package/dist/services/document-service/transform/populate.d.ts.map +1 -1
  150. package/dist/services/document-service/transform/query.d.ts +0 -1
  151. package/dist/services/document-service/transform/query.d.ts.map +1 -1
  152. package/dist/services/document-service/transform/relations/extract/data-ids.d.ts.map +1 -1
  153. package/dist/services/document-service/transform/relations/transform/data-ids.d.ts.map +1 -1
  154. package/dist/services/document-service/transform/relations/transform/default-locale.d.ts.map +1 -1
  155. package/dist/services/document-service/transform/relations/utils/data.d.ts.map +1 -1
  156. package/dist/services/document-service/transform/relations/utils/dp.d.ts +1 -1
  157. package/dist/services/document-service/transform/relations/utils/dp.d.ts.map +1 -1
  158. package/dist/services/document-service/transform/relations/utils/i18n.d.ts.map +1 -1
  159. package/dist/services/document-service/transform/relations/utils/map-relation.d.ts +0 -1
  160. package/dist/services/document-service/transform/relations/utils/map-relation.d.ts.map +1 -1
  161. package/dist/services/document-service/transform/relations/utils/xto-one.d.ts.map +1 -1
  162. package/dist/services/document-service/utils/bidirectional-relations.d.ts.map +1 -1
  163. package/dist/services/document-service/utils/bidirectional-relations.js +7 -2
  164. package/dist/services/document-service/utils/bidirectional-relations.js.map +1 -1
  165. package/dist/services/document-service/utils/bidirectional-relations.mjs +7 -2
  166. package/dist/services/document-service/utils/bidirectional-relations.mjs.map +1 -1
  167. package/dist/services/document-service/utils/clean-component-join-table.d.ts.map +1 -1
  168. package/dist/services/document-service/utils/ordered-parallel.d.ts.map +1 -1
  169. package/dist/services/document-service/utils/populate.d.ts.map +1 -1
  170. package/dist/services/document-service/utils/self-referential-relations.d.ts +6 -1
  171. package/dist/services/document-service/utils/self-referential-relations.d.ts.map +1 -1
  172. package/dist/services/document-service/utils/self-referential-relations.js +120 -7
  173. package/dist/services/document-service/utils/self-referential-relations.js.map +1 -1
  174. package/dist/services/document-service/utils/self-referential-relations.mjs +120 -7
  175. package/dist/services/document-service/utils/self-referential-relations.mjs.map +1 -1
  176. package/dist/services/document-service/utils/unidirectional-relations.d.ts.map +1 -1
  177. package/dist/services/entity-service/index.d.ts.map +1 -1
  178. package/dist/services/entity-service/index.js +1 -1
  179. package/dist/services/entity-service/index.js.map +1 -1
  180. package/dist/services/entity-service/index.mjs +1 -1
  181. package/dist/services/entity-service/index.mjs.map +1 -1
  182. package/dist/services/entity-validator/validators.d.ts.map +1 -1
  183. package/dist/services/entity-validator/validators.js +4 -2
  184. package/dist/services/entity-validator/validators.js.map +1 -1
  185. package/dist/services/entity-validator/validators.mjs +4 -2
  186. package/dist/services/entity-validator/validators.mjs.map +1 -1
  187. package/dist/services/errors.d.ts.map +1 -1
  188. package/dist/services/features.d.ts.map +1 -1
  189. package/dist/services/fs.d.ts.map +1 -1
  190. package/dist/services/mcp/authentication.d.ts.map +1 -1
  191. package/dist/services/mcp/handlers/handlePost.d.ts.map +1 -1
  192. package/dist/services/mcp/index.d.ts.map +1 -1
  193. package/dist/services/mcp/internal/McpCapabilityRegistry.d.ts +2 -2
  194. package/dist/services/mcp/internal/McpServerFactory.d.ts.map +1 -1
  195. package/dist/services/mcp/internal/syncMcpSessionCapabilities.d.ts.map +1 -1
  196. package/dist/services/mcp/metrics/metrics.d.ts.map +1 -1
  197. package/dist/services/mcp/metrics/normalizeMcpCapability.d.ts.map +1 -1
  198. package/dist/services/mcp/metrics/wrapCapabilityHandlerForMetrics.d.ts.map +1 -1
  199. package/dist/services/mcp/middleware/oauthDiscoveryFallback.d.ts.map +1 -1
  200. package/dist/services/mcp/prompt-registry.d.ts.map +1 -1
  201. package/dist/services/mcp/resource-registry.d.ts.map +1 -1
  202. package/dist/services/mcp/routes.d.ts +1 -1
  203. package/dist/services/mcp/routes.d.ts.map +1 -1
  204. package/dist/services/mcp/tool-registry.d.ts.map +1 -1
  205. package/dist/services/mcp/utils/createSafeCapabilityRegistration.d.ts.map +1 -1
  206. package/dist/services/mcp/utils/safeHandlerWrapper.d.ts.map +1 -1
  207. package/dist/services/mcp/utils/sendJsonRpcError.d.ts +0 -1
  208. package/dist/services/mcp/utils/sendJsonRpcError.d.ts.map +1 -1
  209. package/dist/services/mcp/utils/withTimeout.d.ts.map +1 -1
  210. package/dist/services/metrics/admin-user-hash.d.ts.map +1 -1
  211. package/dist/services/metrics/index.d.ts.map +1 -1
  212. package/dist/services/metrics/is-truthy.d.ts.map +1 -1
  213. package/dist/services/metrics/middleware.d.ts.map +1 -1
  214. package/dist/services/metrics/rate-limiter.d.ts +2 -1
  215. package/dist/services/metrics/rate-limiter.d.ts.map +1 -1
  216. package/dist/services/metrics/sender.d.ts.map +1 -1
  217. package/dist/services/query-params.d.ts.map +1 -1
  218. package/dist/services/reloader.d.ts.map +1 -1
  219. package/dist/services/request-context.d.ts.map +1 -1
  220. package/dist/services/server/admin-api.d.ts +3 -5
  221. package/dist/services/server/admin-api.d.ts.map +1 -1
  222. package/dist/services/server/api.d.ts +3 -4
  223. package/dist/services/server/api.d.ts.map +1 -1
  224. package/dist/services/server/compose-endpoint.d.ts.map +1 -1
  225. package/dist/services/server/content-api.d.ts +3 -5
  226. package/dist/services/server/content-api.d.ts.map +1 -1
  227. package/dist/services/server/http-server.d.ts +0 -1
  228. package/dist/services/server/http-server.d.ts.map +1 -1
  229. package/dist/services/server/index.d.ts.map +1 -1
  230. package/dist/services/server/koa-methods.d.ts +107 -0
  231. package/dist/services/server/koa-methods.d.ts.map +1 -0
  232. package/dist/services/server/koa-methods.js +24 -0
  233. package/dist/services/server/koa-methods.js.map +1 -0
  234. package/dist/services/server/koa-methods.mjs +22 -0
  235. package/dist/services/server/koa-methods.mjs.map +1 -0
  236. package/dist/services/server/koa.d.ts +0 -9
  237. package/dist/services/server/koa.d.ts.map +1 -1
  238. package/dist/services/server/koa.js +6 -9
  239. package/dist/services/server/koa.js.map +1 -1
  240. package/dist/services/server/koa.mjs +7 -9
  241. package/dist/services/server/koa.mjs.map +1 -1
  242. package/dist/services/server/middleware.d.ts.map +1 -1
  243. package/dist/services/server/openapi.d.ts.map +1 -1
  244. package/dist/services/server/policy.d.ts.map +1 -1
  245. package/dist/services/server/register-middlewares.d.ts.map +1 -1
  246. package/dist/services/server/register-routes.d.ts.map +1 -1
  247. package/dist/services/server/routing.d.ts +0 -2
  248. package/dist/services/server/routing.d.ts.map +1 -1
  249. package/dist/services/session-manager.d.ts +20 -0
  250. package/dist/services/session-manager.d.ts.map +1 -1
  251. package/dist/services/session-manager.js +57 -0
  252. package/dist/services/session-manager.js.map +1 -1
  253. package/dist/services/session-manager.mjs +57 -0
  254. package/dist/services/session-manager.mjs.map +1 -1
  255. package/dist/services/webhook-store.d.ts.map +1 -1
  256. package/dist/utils/convert-custom-field-type.d.ts.map +1 -1
  257. package/dist/utils/cron.d.ts.map +1 -1
  258. package/dist/utils/fetch.d.ts.map +1 -1
  259. package/dist/utils/filepath-to-prop-path.d.ts.map +1 -1
  260. package/dist/utils/is-initialized.d.ts.map +1 -1
  261. package/dist/utils/load-config-file.d.ts.map +1 -1
  262. package/dist/utils/load-files.d.ts +1 -1
  263. package/dist/utils/load-files.d.ts.map +1 -1
  264. package/dist/utils/open-browser.d.ts +0 -1
  265. package/dist/utils/open-browser.d.ts.map +1 -1
  266. package/dist/utils/resolve-working-dirs.d.ts.map +1 -1
  267. package/dist/utils/signals.d.ts.map +1 -1
  268. package/dist/utils/startup-logger.d.ts.map +1 -1
  269. package/dist/utils/transform-content-types-to-models.d.ts +3 -3
  270. package/dist/utils/transform-content-types-to-models.d.ts.map +1 -1
  271. package/dist/utils/update-notifier/index.d.ts.map +1 -1
  272. package/package.json +17 -20
@@ -39,10 +39,14 @@ const remapRelatedIds = (rows, relatedCol, idMap)=>rows.map((row)=>{
39
39
  * batches for `sync()`. When the FK in `relatedCol` points at a D&P type, maps known draft ids to
40
40
  * published ids; otherwise keeps draft ids so sync can still run after both sides exist.
41
41
  */ const captureJoinBatches = async (trx, opts)=>{
42
- const { joinTable, publishedCol, relatedCol, relatedUid, relatedHasDraftAndPublish, schemaUid, oldVersions, newVersions } = opts;
42
+ const { joinTable, publishedCol, relatedCol, relatedUid, relatedHasDraftAndPublish, schemaUid, isOwningSide, oldVersions, newVersions } = opts;
43
43
  const batches = [];
44
44
  const { name: table } = joinTable;
45
- const oldIds = oldVersions.map((e)=>e.id);
45
+ // Skip the old-published capture for the owning side: those rows are recreated in draft order
46
+ // by `entries.publish`, so restoring their order from the old published version is at best a
47
+ // no-op and at worst reverts a draft reorder. The draftsOnly capture below is still needed
48
+ // (e.g. when a related entry is published after this one) and is not affected by this guard.
49
+ const oldIds = isOwningSide ? [] : oldVersions.map((e)=>e.id);
46
50
  if (oldIds.length > 0) {
47
51
  const existing = await strapi.db.getConnection().select('*').from(table).whereIn(publishedCol, oldIds).transacting(trx);
48
52
  if (existing.length > 0) {
@@ -161,6 +165,7 @@ const remapRelatedIds = (rows, relatedCol, idMap)=>rows.map((row)=>{
161
165
  relatedUid,
162
166
  relatedHasDraftAndPublish: isOwningSide ? !!strapi.contentTypes[relatedUid]?.options?.draftAndPublish : !!model.options?.draftAndPublish,
163
167
  schemaUid: model.uid,
168
+ isOwningSide,
164
169
  oldVersions,
165
170
  newVersions
166
171
  });
@@ -1 +1 @@
1
- {"version":3,"file":"bidirectional-relations.js","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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"}
1
+ {"version":3,"file":"bidirectional-relations.js","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 /**\n * True when the attribute being captured is the owning side of the relation and the entity\n * being published owns it (e.g. publishing an Article for `Article.categories`). In that case\n * the owning-side order is rebuilt from the draft by `entries.publish`, so capturing the old\n * published rows would only let `sync()` overwrite the freshly published order with a stale\n * one — silently reverting a reorder made in the draft. The inverse-side order still needs the\n * old-published capture to be restored, so this only suppresses the owning-side capture.\n */\n isOwningSide: boolean;\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 isOwningSide,\n oldVersions,\n newVersions,\n } = opts;\n\n const batches: RelationEntry[] = [];\n const { name: table } = joinTable;\n\n // Skip the old-published capture for the owning side: those rows are recreated in draft order\n // by `entries.publish`, so restoring their order from the old published version is at best a\n // no-op and at worst reverts a draft reorder. The draftsOnly capture below is still needed\n // (e.g. when a related entry is published after this one) and is not affected by this guard.\n const oldIds = isOwningSide ? [] : 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 isOwningSide,\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","isOwningSide","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","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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"}
@@ -37,10 +37,14 @@ const remapRelatedIds = (rows, relatedCol, idMap)=>rows.map((row)=>{
37
37
  * batches for `sync()`. When the FK in `relatedCol` points at a D&P type, maps known draft ids to
38
38
  * published ids; otherwise keeps draft ids so sync can still run after both sides exist.
39
39
  */ const captureJoinBatches = async (trx, opts)=>{
40
- const { joinTable, publishedCol, relatedCol, relatedUid, relatedHasDraftAndPublish, schemaUid, oldVersions, newVersions } = opts;
40
+ const { joinTable, publishedCol, relatedCol, relatedUid, relatedHasDraftAndPublish, schemaUid, isOwningSide, oldVersions, newVersions } = opts;
41
41
  const batches = [];
42
42
  const { name: table } = joinTable;
43
- const oldIds = oldVersions.map((e)=>e.id);
43
+ // Skip the old-published capture for the owning side: those rows are recreated in draft order
44
+ // by `entries.publish`, so restoring their order from the old published version is at best a
45
+ // no-op and at worst reverts a draft reorder. The draftsOnly capture below is still needed
46
+ // (e.g. when a related entry is published after this one) and is not affected by this guard.
47
+ const oldIds = isOwningSide ? [] : oldVersions.map((e)=>e.id);
44
48
  if (oldIds.length > 0) {
45
49
  const existing = await strapi.db.getConnection().select('*').from(table).whereIn(publishedCol, oldIds).transacting(trx);
46
50
  if (existing.length > 0) {
@@ -159,6 +163,7 @@ const remapRelatedIds = (rows, relatedCol, idMap)=>rows.map((row)=>{
159
163
  relatedUid,
160
164
  relatedHasDraftAndPublish: isOwningSide ? !!strapi.contentTypes[relatedUid]?.options?.draftAndPublish : !!model.options?.draftAndPublish,
161
165
  schemaUid: model.uid,
166
+ isOwningSide,
162
167
  oldVersions,
163
168
  newVersions
164
169
  });
@@ -1 +1 @@
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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"}
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 /**\n * True when the attribute being captured is the owning side of the relation and the entity\n * being published owns it (e.g. publishing an Article for `Article.categories`). In that case\n * the owning-side order is rebuilt from the draft by `entries.publish`, so capturing the old\n * published rows would only let `sync()` overwrite the freshly published order with a stale\n * one — silently reverting a reorder made in the draft. The inverse-side order still needs the\n * old-published capture to be restored, so this only suppresses the owning-side capture.\n */\n isOwningSide: boolean;\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 isOwningSide,\n oldVersions,\n newVersions,\n } = opts;\n\n const batches: RelationEntry[] = [];\n const { name: table } = joinTable;\n\n // Skip the old-published capture for the owning side: those rows are recreated in draft order\n // by `entries.publish`, so restoring their order from the old published version is at best a\n // no-op and at worst reverts a draft reorder. The draftsOnly capture below is still needed\n // (e.g. when a related entry is published after this one) and is not affected by this guard.\n const oldIds = isOwningSide ? [] : 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 isOwningSide,\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","isOwningSide","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","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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"clean-component-join-table.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/clean-component-join-table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIjD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,OAC9B,QAAQ,iBACG,MAAM,YACX,GAAG,eACA,GAAG,KACf,QAAQ,MAAM,CAoDhB,CAAC"}
1
+ {"version":3,"file":"clean-component-join-table.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/clean-component-join-table.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAIjD;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAClC,IAAI,QAAQ,EACZ,eAAe,MAAM,EACrB,UAAU,GAAG,EACb,aAAa,GAAG,KACf,OAAO,CAAC,MAAM,CAoDhB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ordered-parallel.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/ordered-parallel.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,4BAA4B,gBAAuB,QAAQ,CAAC,CAAC,EAAE,KAAG,QAAQ,CAAC,EAAE,CAYzF,CAAC"}
1
+ {"version":3,"file":"ordered-parallel.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/ordered-parallel.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,4BAA4B,GAAU,CAAC,EAAE,UAAU,OAAO,CAAC,CAAC,CAAC,EAAE,KAAG,OAAO,CAAC,CAAC,EAAE,CAYzF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"populate.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/populate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEzC,UAAU,OAAO;IACf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAKD,eAAO,MAAM,eAAe,QAAS,IAAI,MAAM,SAAQ,OAAO,KAAQ,OAAO,MAAM,EAAE,OAAO,CA2D3F,CAAC"}
1
+ {"version":3,"file":"populate.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/populate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAEzC,UAAU,OAAO;IACf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAKD,eAAO,MAAM,eAAe,GAAI,KAAK,GAAG,CAAC,MAAM,EAAE,OAAM,OAAY,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CA2D3F,CAAC"}
@@ -7,7 +7,12 @@ interface VersionEntry {
7
7
  interface RelationData {
8
8
  joinTable: JoinTable;
9
9
  relations: Record<string, unknown>[];
10
+ remap?: {
11
+ source: boolean;
12
+ target: boolean;
13
+ };
10
14
  }
15
+ type TargetStatus = 'draft' | 'published';
11
16
  /**
12
17
  * Preserves self-referential relations during publish/discard operations.
13
18
  *
@@ -27,7 +32,7 @@ interface RelationData {
27
32
  /**
28
33
  * Loads self-referential relations from source entries before they are deleted/recreated.
29
34
  */
30
- declare const load: (uid: UID.ContentType, sourceEntries: VersionEntry[]) => Promise<RelationData[]>;
35
+ declare const load: (uid: UID.ContentType, sourceEntries: VersionEntry[], targetStatus: TargetStatus) => Promise<RelationData[]>;
31
36
  /**
32
37
  * Syncs self-referential relations by remapping old entry IDs to new entry IDs
33
38
  * and inserting the remapped relations into the join table.
@@ -1 +1 @@
1
- {"version":3,"file":"self-referential-relations.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/self-referential-relations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,UAAU,YAAY;IACpB,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,YAAY;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACtC;AAED;;;;;;;;;;;;;;;GAeG;AAEH;;GAEG;AACH,QAAA,MAAM,IAAI,QACH,IAAI,WAAW,iBACL,YAAY,EAAE,KAC5B,QAAQ,YAAY,EAAE,CA4CxB,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,IAAI,kBACO,YAAY,EAAE,iBACd,YAAY,EAAE,gBACf,YAAY,EAAE,kBAmE7B,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"self-referential-relations.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/self-referential-relations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE5D,UAAU,YAAY;IACpB,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,YAAY;IACpB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,EAAE,OAAO,CAAC;KACjB,CAAC;CACH;AAED,KAAK,YAAY,GAAG,OAAO,GAAG,WAAW,CAAC;AAiE1C;;;;;;;;;;;;;;;GAeG;AAEH;;GAEG;AACH,QAAA,MAAM,IAAI,GACR,KAAK,GAAG,CAAC,WAAW,EACpB,eAAe,YAAY,EAAE,EAC7B,cAAc,YAAY,KACzB,OAAO,CAAC,YAAY,EAAE,CAoIxB,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,IAAI,GACR,eAAe,YAAY,EAAE,EAC7B,eAAe,YAAY,EAAE,EAC7B,cAAc,YAAY,EAAE,kBA2F7B,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC"}
@@ -2,6 +2,32 @@
2
2
 
3
3
  var fp = require('lodash/fp');
4
4
 
5
+ const entryKey = (entry)=>`${String(entry.document_id)}:${String(entry.locale)}`;
6
+ const getCounterparts = async (trx, tableName, idsToResolve, status)=>{
7
+ const ids = [
8
+ ...new Set(idsToResolve.map((id)=>String(id)))
9
+ ];
10
+ if (ids.length === 0) {
11
+ return {};
12
+ }
13
+ const drafts = await strapi.db.getConnection().select('id', 'document_id', 'locale').from(tableName).whereIn('id', ids).transacting(trx);
14
+ if (drafts.length === 0) {
15
+ return {};
16
+ }
17
+ const documentIds = [
18
+ ...new Set(drafts.map((entry)=>entry.document_id))
19
+ ];
20
+ const counterpartsQuery = strapi.db.getConnection().select('id', 'document_id', 'locale').from(tableName).whereIn('document_id', documentIds).transacting(trx);
21
+ const counterparts = status === 'published' ? await counterpartsQuery.whereNotNull('published_at') : await counterpartsQuery.whereNull('published_at');
22
+ const counterpartByDocument = fp.keyBy(entryKey, counterparts);
23
+ return drafts.reduce((acc, draft)=>{
24
+ const counterpart = counterpartByDocument[entryKey(draft)];
25
+ if (counterpart) {
26
+ acc[String(draft.id)] = counterpart.id;
27
+ }
28
+ return acc;
29
+ }, {});
30
+ };
5
31
  /**
6
32
  * Preserves self-referential relations during publish/discard operations.
7
33
  *
@@ -19,7 +45,7 @@ var fp = require('lodash/fp');
19
45
  * 3. Inserts the remapped relations
20
46
  */ /**
21
47
  * Loads self-referential relations from source entries before they are deleted/recreated.
22
- */ const load = async (uid, sourceEntries)=>{
48
+ */ const load = async (uid, sourceEntries, targetStatus)=>{
23
49
  const updates = [];
24
50
  const dbModel = strapi.db.metadata.get(uid);
25
51
  await strapi.db.transaction(async ({ trx })=>{
@@ -46,9 +72,72 @@ var fp = require('lodash/fp');
46
72
  if (selfRelations.length > 0) {
47
73
  updates.push({
48
74
  joinTable,
49
- relations: selfRelations
75
+ relations: selfRelations,
76
+ remap: {
77
+ source: true,
78
+ target: true
79
+ }
50
80
  });
51
81
  }
82
+ // Use rows from the state being copied as the source of truth for one-sided
83
+ // self-relations. Publishing maps untouched targets to published counterparts and drops
84
+ // draft-only targets; discarding maps untouched targets to draft counterparts. This avoids
85
+ // restoring stale old rows that were removed from the copied state.
86
+ const [sourceDraftRelations, targetDraftRelations] = await Promise.all([
87
+ strapi.db.getConnection().select('*').from(joinTable.name).whereIn(sourceColumnName, sourceIds).whereNotIn(targetColumnName, sourceIds).transacting(trx),
88
+ strapi.db.getConnection().select('*').from(joinTable.name).whereIn(targetColumnName, sourceIds).whereNotIn(sourceColumnName, sourceIds).transacting(trx)
89
+ ]);
90
+ const [targetCounterparts, sourceCounterparts] = await Promise.all([
91
+ getCounterparts(trx, dbModel.tableName, sourceDraftRelations.map((relation)=>relation[targetColumnName]), targetStatus),
92
+ getCounterparts(trx, dbModel.tableName, targetDraftRelations.map((relation)=>relation[sourceColumnName]), targetStatus)
93
+ ]);
94
+ if (sourceDraftRelations.length > 0) {
95
+ const relations = sourceDraftRelations.map((relation)=>{
96
+ const targetCounterpart = targetCounterparts[String(relation[targetColumnName])];
97
+ if (targetCounterpart == null) {
98
+ return null;
99
+ }
100
+ return {
101
+ ...relation,
102
+ [targetColumnName]: targetCounterpart
103
+ };
104
+ }).filter(Boolean);
105
+ if (relations.length > 0) {
106
+ updates.push({
107
+ joinTable,
108
+ relations,
109
+ remap: {
110
+ source: true,
111
+ target: false
112
+ }
113
+ });
114
+ }
115
+ }
116
+ if (targetDraftRelations.length > 0) {
117
+ const relations = targetDraftRelations.map((relation)=>{
118
+ const sourceCounterpart = sourceCounterparts[String(relation[sourceColumnName])];
119
+ if (sourceCounterpart == null) {
120
+ return null;
121
+ }
122
+ return {
123
+ ...relation,
124
+ [sourceColumnName]: sourceCounterpart
125
+ };
126
+ }).filter(Boolean);
127
+ if (relations.length > 0) {
128
+ updates.push({
129
+ joinTable,
130
+ relations,
131
+ remap: {
132
+ source: false,
133
+ target: true
134
+ }
135
+ });
136
+ }
137
+ }
138
+ // One-sided rows from the replaced state are intentionally not restored here. The rows
139
+ // captured above represent the current logical relation set; restoring stale old rows can
140
+ // resurrect relations that were removed before publishing or discarding.
52
141
  }
53
142
  });
54
143
  return updates;
@@ -68,16 +157,19 @@ var fp = require('lodash/fp');
68
157
  }, {});
69
158
  const batchSize = strapi.db.dialect.getBatchInsertSize();
70
159
  await strapi.db.transaction(async ({ trx })=>{
71
- for (const { joinTable, relations } of relationData){
160
+ for (const { joinTable, relations, remap = {
161
+ source: true,
162
+ target: true
163
+ } } of relationData){
72
164
  const sourceColumn = joinTable.joinColumn.name;
73
165
  const targetColumn = joinTable.inverseJoinColumn.name;
74
166
  const newRelations = relations.map((relation)=>{
75
167
  const oldSourceId = String(relation[sourceColumn]);
76
168
  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;
169
+ const newSourceId = remap.source ? idMapping[oldSourceId] : relation[sourceColumn];
170
+ const newTargetId = remap.target ? idMapping[oldTargetId] : relation[targetColumn];
171
+ // Any side being remapped must resolve to a new entry.
172
+ if (newSourceId == null || newTargetId == null) return null;
81
173
  return {
82
174
  ...fp.omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),
83
175
  [sourceColumn]: newSourceId,
@@ -93,12 +185,33 @@ var fp = require('lodash/fp');
93
185
  return true;
94
186
  });
95
187
  if (deduped.length === 0) continue;
188
+ // Relation writes may have already recreated the remapped pair. In that case restore
189
+ // the saved join-row metadata (notably order columns) instead of inserting a duplicate.
96
190
  const newSourceIds = [
97
191
  ...new Set(deduped.map((r)=>String(r[sourceColumn])))
98
192
  ];
99
193
  const existingRows = await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).select(sourceColumn, targetColumn);
100
194
  const existingSet = new Set(existingRows.map((r)=>pairKey(r)));
195
+ const toUpdate = deduped.filter((r)=>existingSet.has(pairKey(r)));
101
196
  const toInsert = deduped.filter((r)=>!existingSet.has(pairKey(r)));
197
+ await Promise.all(toUpdate.map((relation)=>{
198
+ const sourceId = relation[sourceColumn];
199
+ const targetId = relation[targetColumn];
200
+ const dataToRestore = Object.fromEntries([
201
+ joinTable.orderColumnName,
202
+ joinTable.inverseOrderColumnName
203
+ ].filter((columnName)=>columnName != null).filter((columnName)=>columnName in relation).map((columnName)=>[
204
+ columnName,
205
+ relation[columnName]
206
+ ]));
207
+ if (Object.keys(dataToRestore).length === 0) {
208
+ return Promise.resolve();
209
+ }
210
+ return trx(joinTable.name).where({
211
+ [sourceColumn]: sourceId,
212
+ [targetColumn]: targetId
213
+ }).update(dataToRestore);
214
+ }));
102
215
  if (toInsert.length > 0) {
103
216
  await trx.batchInsert(joinTable.name, toInsert, batchSize);
104
217
  }
@@ -1 +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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"}
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 { Database, 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 remap?: {\n source: boolean;\n target: boolean;\n };\n}\n\ntype TargetStatus = 'draft' | 'published';\ntype DatabaseQueryBuilder = ReturnType<Database['getConnection']>;\ntype DatabaseTransaction = Parameters<DatabaseQueryBuilder['transacting']>[0];\n\nconst entryKey = (entry: { document_id: unknown; locale: unknown }) =>\n `${String(entry.document_id)}:${String(entry.locale)}`;\n\nconst getCounterparts = async (\n trx: DatabaseTransaction,\n tableName: string,\n idsToResolve: unknown[],\n status: TargetStatus\n): Promise<Record<string, Data.ID>> => {\n const ids = [...new Set(idsToResolve.map((id) => String(id)))];\n\n if (ids.length === 0) {\n return {};\n }\n\n const drafts = await strapi.db\n .getConnection()\n .select('id', 'document_id', 'locale')\n .from(tableName)\n .whereIn('id', ids)\n .transacting(trx);\n\n if (drafts.length === 0) {\n return {};\n }\n\n const documentIds = [\n ...new Set(drafts.map((entry: { document_id: Data.ID }) => entry.document_id)),\n ];\n\n const counterpartsQuery = strapi.db\n .getConnection()\n .select('id', 'document_id', 'locale')\n .from(tableName)\n .whereIn('document_id', documentIds)\n .transacting(trx);\n\n const counterparts =\n status === 'published'\n ? await counterpartsQuery.whereNotNull('published_at')\n : await counterpartsQuery.whereNull('published_at');\n\n const counterpartByDocument = keyBy(entryKey, counterparts);\n\n return drafts.reduce(\n (\n acc: Record<string, Data.ID>,\n draft: { id: Data.ID; document_id: unknown; locale: unknown }\n ) => {\n const counterpart = counterpartByDocument[entryKey(draft)];\n\n if (counterpart) {\n acc[String(draft.id)] = counterpart.id;\n }\n\n return acc;\n },\n {}\n );\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 targetStatus: TargetStatus\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({\n joinTable,\n relations: selfRelations,\n remap: { source: true, target: true },\n });\n }\n\n // Use rows from the state being copied as the source of truth for one-sided\n // self-relations. Publishing maps untouched targets to published counterparts and drops\n // draft-only targets; discarding maps untouched targets to draft counterparts. This avoids\n // restoring stale old rows that were removed from the copied state.\n const [sourceDraftRelations, targetDraftRelations] = await Promise.all([\n strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(sourceColumnName, sourceIds)\n .whereNotIn(targetColumnName, sourceIds)\n .transacting(trx),\n strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, sourceIds)\n .whereNotIn(sourceColumnName, sourceIds)\n .transacting(trx),\n ]);\n\n const [targetCounterparts, sourceCounterparts] = await Promise.all([\n getCounterparts(\n trx,\n dbModel.tableName,\n sourceDraftRelations.map((relation) => relation[targetColumnName]),\n targetStatus\n ),\n getCounterparts(\n trx,\n dbModel.tableName,\n targetDraftRelations.map((relation) => relation[sourceColumnName]),\n targetStatus\n ),\n ]);\n\n if (sourceDraftRelations.length > 0) {\n const relations = sourceDraftRelations\n .map((relation) => {\n const targetCounterpart = targetCounterparts[String(relation[targetColumnName])];\n\n if (targetCounterpart == null) {\n return null;\n }\n\n return { ...relation, [targetColumnName]: targetCounterpart };\n })\n .filter(Boolean) as Record<string, unknown>[];\n\n if (relations.length > 0) {\n updates.push({\n joinTable,\n relations,\n remap: { source: true, target: false },\n });\n }\n }\n\n if (targetDraftRelations.length > 0) {\n const relations = targetDraftRelations\n .map((relation) => {\n const sourceCounterpart = sourceCounterparts[String(relation[sourceColumnName])];\n\n if (sourceCounterpart == null) {\n return null;\n }\n\n return { ...relation, [sourceColumnName]: sourceCounterpart };\n })\n .filter(Boolean) as Record<string, unknown>[];\n\n if (relations.length > 0) {\n updates.push({\n joinTable,\n relations,\n remap: { source: false, target: true },\n });\n }\n }\n\n // One-sided rows from the replaced state are intentionally not restored here. The rows\n // captured above represent the current logical relation set; restoring stale old rows can\n // resurrect relations that were removed before publishing or discarding.\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, remap = { source: true, target: true } } 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 = remap.source ? idMapping[oldSourceId] : relation[sourceColumn];\n const newTargetId = remap.target ? idMapping[oldTargetId] : relation[targetColumn];\n\n // Any side being remapped must resolve to a new entry.\n if (newSourceId == null || newTargetId == null) 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 // Relation writes may have already recreated the remapped pair. In that case restore\n // the saved join-row metadata (notably order columns) instead of inserting a duplicate.\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 toUpdate = deduped.filter((r) => existingSet.has(pairKey(r)));\n const toInsert = deduped.filter((r) => !existingSet.has(pairKey(r)));\n\n await Promise.all(\n toUpdate.map((relation) => {\n const sourceId = relation[sourceColumn];\n const targetId = relation[targetColumn];\n const dataToRestore = Object.fromEntries(\n [joinTable.orderColumnName, joinTable.inverseOrderColumnName]\n .filter((columnName): columnName is string => columnName != null)\n .filter((columnName) => columnName in relation)\n .map((columnName) => [columnName, relation[columnName]])\n );\n\n if (Object.keys(dataToRestore).length === 0) {\n return Promise.resolve();\n }\n\n return trx(joinTable.name)\n .where({ [sourceColumn]: sourceId, [targetColumn]: targetId })\n .update(dataToRestore);\n })\n );\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":["entryKey","entry","String","document_id","locale","getCounterparts","trx","tableName","idsToResolve","status","ids","Set","map","id","length","drafts","strapi","db","getConnection","select","from","whereIn","transacting","documentIds","counterpartsQuery","counterparts","whereNotNull","whereNull","counterpartByDocument","keyBy","reduce","acc","draft","counterpart","load","uid","sourceEntries","targetStatus","updates","dbModel","metadata","get","transaction","attribute","Object","values","attributes","type","target","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","sourceIds","selfRelations","push","relations","remap","source","sourceDraftRelations","targetDraftRelations","Promise","all","whereNotIn","targetCounterparts","sourceCounterparts","relation","targetCounterpart","filter","Boolean","sourceCounterpart","sync","targetEntries","relationData","targetEntriesByLocale","idMapping","sourceEntry","targetEntry","batchSize","dialect","getBatchInsertSize","sourceColumn","targetColumn","newRelations","oldSourceId","oldTargetId","newSourceId","newTargetId","omit","identifiers","ID_COLUMN","pairKey","r","seenPairs","deduped","key","has","add","newSourceIds","existingRows","existingSet","toUpdate","toInsert","sourceId","targetId","dataToRestore","fromEntries","orderColumnName","inverseOrderColumnName","columnName","keys","resolve","where","update","batchInsert"],"mappings":"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"}