@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.
- package/dist/Strapi.d.ts.map +1 -1
- package/dist/Strapi.js +2 -0
- package/dist/Strapi.js.map +1 -1
- package/dist/Strapi.mjs +2 -0
- package/dist/Strapi.mjs.map +1 -1
- package/dist/configuration/config-loader.d.ts.map +1 -1
- package/dist/configuration/get-dirs.d.ts.map +1 -1
- package/dist/configuration/index.d.ts +0 -3
- package/dist/configuration/index.d.ts.map +1 -1
- package/dist/configuration/index.js +0 -3
- package/dist/configuration/index.js.map +1 -1
- package/dist/configuration/index.mjs +0 -3
- package/dist/configuration/index.mjs.map +1 -1
- package/dist/configuration/server-config.d.ts +11 -0
- package/dist/configuration/server-config.d.ts.map +1 -0
- package/dist/configuration/server-config.js +16 -0
- package/dist/configuration/server-config.js.map +1 -0
- package/dist/configuration/server-config.mjs +14 -0
- package/dist/configuration/server-config.mjs.map +1 -0
- package/dist/configuration/urls.d.ts.map +1 -1
- package/dist/core-api/controller/collection-type.d.ts.map +1 -1
- package/dist/core-api/controller/single-type.d.ts.map +1 -1
- package/dist/core-api/controller/transform.d.ts.map +1 -1
- package/dist/core-api/routes/index.d.ts.map +1 -1
- package/dist/core-api/routes/validation/attributes.d.ts.map +1 -1
- package/dist/core-api/routes/validation/component.js +3 -3
- package/dist/core-api/routes/validation/component.js.map +1 -1
- package/dist/core-api/routes/validation/component.mjs +3 -3
- package/dist/core-api/routes/validation/component.mjs.map +1 -1
- package/dist/core-api/routes/validation/mappers.d.ts.map +1 -1
- package/dist/core-api/routes/validation/utils.d.ts.map +1 -1
- package/dist/core-api/service/collection-type.d.ts.map +1 -1
- package/dist/core-api/service/pagination.d.ts.map +1 -1
- package/dist/core-api/service/single-type.d.ts.map +1 -1
- package/dist/domain/content-type/index.d.ts.map +1 -1
- package/dist/domain/content-type/validator.d.ts.map +1 -1
- package/dist/domain/module/index.d.ts.map +1 -1
- package/dist/domain/module/validation.d.ts.map +1 -1
- package/dist/ee/index.d.ts +2 -1
- package/dist/ee/index.d.ts.map +1 -1
- package/dist/ee/license.d.ts.map +1 -1
- package/dist/factories.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/loaders/plugins/get-enabled-plugins.d.ts.map +1 -1
- package/dist/loaders/sanitizers.d.ts.map +1 -1
- package/dist/loaders/src-index.d.ts.map +1 -1
- package/dist/loaders/validators.d.ts.map +1 -1
- package/dist/middlewares/body.d.ts.map +1 -1
- package/dist/middlewares/body.js +1 -1
- package/dist/middlewares/body.js.map +1 -1
- package/dist/middlewares/body.mjs +1 -1
- package/dist/middlewares/body.mjs.map +1 -1
- package/dist/middlewares/cors.d.ts.map +1 -1
- package/dist/middlewares/errors.d.ts +5 -0
- package/dist/middlewares/errors.d.ts.map +1 -1
- package/dist/middlewares/errors.js.map +1 -1
- package/dist/middlewares/errors.mjs.map +1 -1
- package/dist/middlewares/favicon.d.ts +0 -1
- package/dist/middlewares/favicon.d.ts.map +1 -1
- package/dist/migrations/draft-publish.d.ts.map +1 -1
- package/dist/migrations/first-published-at.d.ts.map +1 -1
- package/dist/migrations/i18n.d.ts.map +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/package.json.js +17 -20
- package/dist/package.json.js.map +1 -1
- package/dist/package.json.mjs +17 -20
- package/dist/package.json.mjs.map +1 -1
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/registries/apis.d.ts.map +1 -1
- package/dist/registries/components.d.ts +1 -1
- package/dist/registries/components.d.ts.map +1 -1
- package/dist/registries/content-types.d.ts +2 -3
- package/dist/registries/content-types.d.ts.map +1 -1
- package/dist/registries/controllers.d.ts +3 -3
- package/dist/registries/controllers.d.ts.map +1 -1
- package/dist/registries/custom-fields.d.ts.map +1 -1
- package/dist/registries/hooks.d.ts +3 -4
- package/dist/registries/hooks.d.ts.map +1 -1
- package/dist/registries/middlewares.d.ts +2 -3
- package/dist/registries/middlewares.d.ts.map +1 -1
- package/dist/registries/models.d.ts +1 -1
- package/dist/registries/modules.d.ts.map +1 -1
- package/dist/registries/namespace.d.ts.map +1 -1
- package/dist/registries/plugins.d.ts.map +1 -1
- package/dist/registries/policies.d.ts +1 -2
- package/dist/registries/policies.d.ts.map +1 -1
- package/dist/registries/sanitizers.d.ts +2 -2
- package/dist/registries/services.d.ts +3 -3
- package/dist/registries/services.d.ts.map +1 -1
- package/dist/registries/validators.d.ts +2 -2
- package/dist/services/ai.d.ts.map +1 -1
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/content-api/index.d.ts +4 -4
- package/dist/services/content-api/index.d.ts.map +1 -1
- package/dist/services/content-api/permissions/engine.d.ts.map +1 -1
- package/dist/services/content-api/permissions/index.d.ts +2 -2
- package/dist/services/content-api/permissions/index.d.ts.map +1 -1
- package/dist/services/content-api/permissions/providers/action.d.ts +1 -1
- package/dist/services/content-api/permissions/providers/action.d.ts.map +1 -1
- package/dist/services/content-api/permissions/providers/condition.d.ts +1 -1
- package/dist/services/content-api/permissions/providers/condition.d.ts.map +1 -1
- package/dist/services/content-source-maps.d.ts.map +1 -1
- package/dist/services/core-store.d.ts.map +1 -1
- package/dist/services/core-store.js +1 -1
- package/dist/services/core-store.js.map +1 -1
- package/dist/services/core-store.mjs +1 -1
- package/dist/services/core-store.mjs.map +1 -1
- package/dist/services/cron.d.ts +5 -5
- package/dist/services/custom-fields.d.ts.map +1 -1
- package/dist/services/document-service/attributes/index.d.ts +0 -1
- package/dist/services/document-service/attributes/index.d.ts.map +1 -1
- package/dist/services/document-service/common.d.ts.map +1 -1
- package/dist/services/document-service/components.d.ts +2 -2
- package/dist/services/document-service/components.d.ts.map +1 -1
- package/dist/services/document-service/components.js +7 -7
- package/dist/services/document-service/components.js.map +1 -1
- package/dist/services/document-service/components.mjs +7 -7
- package/dist/services/document-service/components.mjs.map +1 -1
- package/dist/services/document-service/draft-and-publish.d.ts +0 -1
- package/dist/services/document-service/draft-and-publish.d.ts.map +1 -1
- package/dist/services/document-service/entries.d.ts.map +1 -1
- package/dist/services/document-service/events.d.ts.map +1 -1
- package/dist/services/document-service/first-published-at.d.ts.map +1 -1
- package/dist/services/document-service/index.d.ts.map +1 -1
- package/dist/services/document-service/internationalization.d.ts +0 -1
- package/dist/services/document-service/internationalization.d.ts.map +1 -1
- package/dist/services/document-service/middlewares/middleware-manager.d.ts.map +1 -1
- package/dist/services/document-service/params.d.ts +1 -1
- package/dist/services/document-service/params.d.ts.map +1 -1
- package/dist/services/document-service/publication-filter.d.ts +1 -1
- package/dist/services/document-service/publication-filter.d.ts.map +1 -1
- package/dist/services/document-service/repository.d.ts.map +1 -1
- package/dist/services/document-service/repository.js +12 -8
- package/dist/services/document-service/repository.js.map +1 -1
- package/dist/services/document-service/repository.mjs +12 -8
- package/dist/services/document-service/repository.mjs.map +1 -1
- package/dist/services/document-service/transform/data.d.ts.map +1 -1
- package/dist/services/document-service/transform/fields.d.ts.map +1 -1
- package/dist/services/document-service/transform/id-map.d.ts.map +1 -1
- package/dist/services/document-service/transform/id-map.js +7 -4
- package/dist/services/document-service/transform/id-map.js.map +1 -1
- package/dist/services/document-service/transform/id-map.mjs +7 -4
- package/dist/services/document-service/transform/id-map.mjs.map +1 -1
- package/dist/services/document-service/transform/id-transform.d.ts +0 -1
- package/dist/services/document-service/transform/id-transform.d.ts.map +1 -1
- package/dist/services/document-service/transform/populate.d.ts.map +1 -1
- package/dist/services/document-service/transform/query.d.ts +0 -1
- package/dist/services/document-service/transform/query.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/extract/data-ids.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/transform/data-ids.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/transform/default-locale.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/utils/data.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/utils/dp.d.ts +1 -1
- package/dist/services/document-service/transform/relations/utils/dp.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/utils/i18n.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/utils/map-relation.d.ts +0 -1
- package/dist/services/document-service/transform/relations/utils/map-relation.d.ts.map +1 -1
- package/dist/services/document-service/transform/relations/utils/xto-one.d.ts.map +1 -1
- package/dist/services/document-service/utils/bidirectional-relations.d.ts.map +1 -1
- package/dist/services/document-service/utils/bidirectional-relations.js +7 -2
- package/dist/services/document-service/utils/bidirectional-relations.js.map +1 -1
- package/dist/services/document-service/utils/bidirectional-relations.mjs +7 -2
- package/dist/services/document-service/utils/bidirectional-relations.mjs.map +1 -1
- package/dist/services/document-service/utils/clean-component-join-table.d.ts.map +1 -1
- package/dist/services/document-service/utils/ordered-parallel.d.ts.map +1 -1
- package/dist/services/document-service/utils/populate.d.ts.map +1 -1
- package/dist/services/document-service/utils/self-referential-relations.d.ts +6 -1
- package/dist/services/document-service/utils/self-referential-relations.d.ts.map +1 -1
- package/dist/services/document-service/utils/self-referential-relations.js +120 -7
- package/dist/services/document-service/utils/self-referential-relations.js.map +1 -1
- package/dist/services/document-service/utils/self-referential-relations.mjs +120 -7
- package/dist/services/document-service/utils/self-referential-relations.mjs.map +1 -1
- package/dist/services/document-service/utils/unidirectional-relations.d.ts.map +1 -1
- package/dist/services/entity-service/index.d.ts.map +1 -1
- package/dist/services/entity-service/index.js +1 -1
- package/dist/services/entity-service/index.js.map +1 -1
- package/dist/services/entity-service/index.mjs +1 -1
- package/dist/services/entity-service/index.mjs.map +1 -1
- package/dist/services/entity-validator/validators.d.ts.map +1 -1
- package/dist/services/entity-validator/validators.js +4 -2
- package/dist/services/entity-validator/validators.js.map +1 -1
- package/dist/services/entity-validator/validators.mjs +4 -2
- package/dist/services/entity-validator/validators.mjs.map +1 -1
- package/dist/services/errors.d.ts.map +1 -1
- package/dist/services/features.d.ts.map +1 -1
- package/dist/services/fs.d.ts.map +1 -1
- package/dist/services/mcp/authentication.d.ts.map +1 -1
- package/dist/services/mcp/handlers/handlePost.d.ts.map +1 -1
- package/dist/services/mcp/index.d.ts.map +1 -1
- package/dist/services/mcp/internal/McpCapabilityRegistry.d.ts +2 -2
- package/dist/services/mcp/internal/McpServerFactory.d.ts.map +1 -1
- package/dist/services/mcp/internal/syncMcpSessionCapabilities.d.ts.map +1 -1
- package/dist/services/mcp/metrics/metrics.d.ts.map +1 -1
- package/dist/services/mcp/metrics/normalizeMcpCapability.d.ts.map +1 -1
- package/dist/services/mcp/metrics/wrapCapabilityHandlerForMetrics.d.ts.map +1 -1
- package/dist/services/mcp/middleware/oauthDiscoveryFallback.d.ts.map +1 -1
- package/dist/services/mcp/prompt-registry.d.ts.map +1 -1
- package/dist/services/mcp/resource-registry.d.ts.map +1 -1
- package/dist/services/mcp/routes.d.ts +1 -1
- package/dist/services/mcp/routes.d.ts.map +1 -1
- package/dist/services/mcp/tool-registry.d.ts.map +1 -1
- package/dist/services/mcp/utils/createSafeCapabilityRegistration.d.ts.map +1 -1
- package/dist/services/mcp/utils/safeHandlerWrapper.d.ts.map +1 -1
- package/dist/services/mcp/utils/sendJsonRpcError.d.ts +0 -1
- package/dist/services/mcp/utils/sendJsonRpcError.d.ts.map +1 -1
- package/dist/services/mcp/utils/withTimeout.d.ts.map +1 -1
- package/dist/services/metrics/admin-user-hash.d.ts.map +1 -1
- package/dist/services/metrics/index.d.ts.map +1 -1
- package/dist/services/metrics/is-truthy.d.ts.map +1 -1
- package/dist/services/metrics/middleware.d.ts.map +1 -1
- package/dist/services/metrics/rate-limiter.d.ts +2 -1
- package/dist/services/metrics/rate-limiter.d.ts.map +1 -1
- package/dist/services/metrics/sender.d.ts.map +1 -1
- package/dist/services/query-params.d.ts.map +1 -1
- package/dist/services/reloader.d.ts.map +1 -1
- package/dist/services/request-context.d.ts.map +1 -1
- package/dist/services/server/admin-api.d.ts +3 -5
- package/dist/services/server/admin-api.d.ts.map +1 -1
- package/dist/services/server/api.d.ts +3 -4
- package/dist/services/server/api.d.ts.map +1 -1
- package/dist/services/server/compose-endpoint.d.ts.map +1 -1
- package/dist/services/server/content-api.d.ts +3 -5
- package/dist/services/server/content-api.d.ts.map +1 -1
- package/dist/services/server/http-server.d.ts +0 -1
- package/dist/services/server/http-server.d.ts.map +1 -1
- package/dist/services/server/index.d.ts.map +1 -1
- package/dist/services/server/koa-methods.d.ts +107 -0
- package/dist/services/server/koa-methods.d.ts.map +1 -0
- package/dist/services/server/koa-methods.js +24 -0
- package/dist/services/server/koa-methods.js.map +1 -0
- package/dist/services/server/koa-methods.mjs +22 -0
- package/dist/services/server/koa-methods.mjs.map +1 -0
- package/dist/services/server/koa.d.ts +0 -9
- package/dist/services/server/koa.d.ts.map +1 -1
- package/dist/services/server/koa.js +6 -9
- package/dist/services/server/koa.js.map +1 -1
- package/dist/services/server/koa.mjs +7 -9
- package/dist/services/server/koa.mjs.map +1 -1
- package/dist/services/server/middleware.d.ts.map +1 -1
- package/dist/services/server/openapi.d.ts.map +1 -1
- package/dist/services/server/policy.d.ts.map +1 -1
- package/dist/services/server/register-middlewares.d.ts.map +1 -1
- package/dist/services/server/register-routes.d.ts.map +1 -1
- package/dist/services/server/routing.d.ts +0 -2
- package/dist/services/server/routing.d.ts.map +1 -1
- package/dist/services/session-manager.d.ts +20 -0
- package/dist/services/session-manager.d.ts.map +1 -1
- package/dist/services/session-manager.js +57 -0
- package/dist/services/session-manager.js.map +1 -1
- package/dist/services/session-manager.mjs +57 -0
- package/dist/services/session-manager.mjs.map +1 -1
- package/dist/services/webhook-store.d.ts.map +1 -1
- package/dist/utils/convert-custom-field-type.d.ts.map +1 -1
- package/dist/utils/cron.d.ts.map +1 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/filepath-to-prop-path.d.ts.map +1 -1
- package/dist/utils/is-initialized.d.ts.map +1 -1
- package/dist/utils/load-config-file.d.ts.map +1 -1
- package/dist/utils/load-files.d.ts +1 -1
- package/dist/utils/load-files.d.ts.map +1 -1
- package/dist/utils/open-browser.d.ts +0 -1
- package/dist/utils/open-browser.d.ts.map +1 -1
- package/dist/utils/resolve-working-dirs.d.ts.map +1 -1
- package/dist/utils/signals.d.ts.map +1 -1
- package/dist/utils/startup-logger.d.ts.map +1 -1
- package/dist/utils/transform-content-types-to-models.d.ts +3 -3
- package/dist/utils/transform-content-types-to-models.d.ts.map +1 -1
- package/dist/utils/update-notifier/index.d.ts.map +1 -1
- 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
|
-
|
|
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
|
-
|
|
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":";;AAkBA,+GACA,MAAMA,mBAAAA,GAAsB,OAAOC,KAAUC,SAAAA,EAAmBC,MAAAA,GAAAA;AAC9D,IAAA,MAAMC,SAAAA,GAAY;AAAI,QAAA,GAAA,IAAIC,GAAAA,CAAIF,MAAAA;AAAQ,KAAA;IACtC,IAAIC,SAAAA,CAAUE,MAAM,KAAK,CAAA,EAAG;AAC1B,QAAA,OAAO,IAAIC,GAAAA,EAAAA;AACb,IAAA;IAEA,MAAMC,YAAAA,GAAe,MAAMC,MAAAA,CAAOC,EAAE,CACjCC,aAAa,EAAA,CACbC,MAAM,CAAC,IAAA,EAAM,eAAe,QAAA,CAAA,CAC5BC,IAAI,CAACX,SAAAA,CAAAA,CACLY,OAAO,CAAC,IAAA,EAAMV,SAAAA,CAAAA,CACdW,WAAW,CAACd,GAAAA,CAAAA;IAEf,IAAIO,YAAAA,CAAaF,MAAM,KAAK,CAAA,EAAG;AAC7B,QAAA,OAAO,IAAIC,GAAAA,EAAAA;AACb,IAAA;AAEA,IAAA,MAAMS,UAAAA,GAAa,MAAMP,MAAAA,CAAOC,EAAE,CAC/BC,aAAa,EAAA,CACbC,MAAM,CAAC,IAAA,EAAM,aAAA,EAAe,QAAA,CAAA,CAC5BC,IAAI,CAACX,SAAAA,CAAAA,CACLe,YAAY,CAAC,cAAA,CAAA,CACbH,OAAO,CACN,aAAA,EACAN,YAAAA,CAAaU,GAAG,CAAC,CAACC,CAAAA,GAAWA,CAAAA,CAAEC,WAAW,CAAA,CAAA,CAE3CL,WAAW,CAACd,GAAAA,CAAAA;AAEf,IAAA,MAAMoB,iBAAiB,IAAId,GAAAA,CACzBS,WAAWE,GAAG,CAAC,CAACC,CAAAA,GAAW;AAAC,YAAA,CAAA,EAAGA,EAAEC,WAAW,CAAC,CAAC,EAAED,CAAAA,CAAEG,MAAM,CAAA,CAAE;AAAEH,YAAAA,CAAAA,CAAEI;AAAG,SAAA,CAAA,CAAA;AAGnE,IAAA,MAAML,MAAM,IAAIX,GAAAA,EAAAA;IAChB,KAAK,MAAMiB,KAAKhB,YAAAA,CAAc;AAC5B,QAAA,MAAMiB,KAAAA,GAAQJ,cAAAA,CAAeK,GAAG,CAAC,CAAA,EAAGF,CAAAA,CAAEJ,WAAW,CAAC,CAAC,EAAEI,CAAAA,CAAEF,MAAM,CAAA,CAAE,CAAA;AAC/D,QAAA,IAAIG,KAAAA,EAAO;AACTP,YAAAA,GAAAA,CAAIS,GAAG,CAACC,MAAAA,CAAOJ,CAAAA,CAAED,EAAE,GAAGK,MAAAA,CAAOH,KAAAA,CAAAA,CAAAA;AAC/B,QAAA;AACF,IAAA;IACA,OAAOP,GAAAA;AACT,CAAA;AAEA,MAAMW,eAAAA,GAAkB,CAACC,IAAAA,EAAaC,UAAAA,EAAoBC,QACxDF,IAAAA,CAAKZ,GAAG,CAAC,CAACe,GAAAA,GAAAA;AACR,QAAA,MAAMC,OAAOF,KAAAA,CAAMN,GAAG,CAACE,MAAAA,CAAOK,GAAG,CAACF,UAAAA,CAAW,CAAA,CAAA;AAC7C,QAAA,OAAOG,IAAAA,GAAO;AAAE,YAAA,GAAGD,GAAG;AAAE,YAAA,CAACF,aAAaG;SAAK,GAAID,GAAAA;AACjD,IAAA,CAAA,CAAA;AAEF;;;;IAKA,MAAME,kBAAAA,GAAqB,OACzBlC,GAAAA,EACAmC,IAAAA,GAAAA;AAwBA,IAAA,MAAM,EACJC,SAAS,EACTC,YAAY,EACZP,UAAU,EACVQ,UAAU,EACVC,yBAAyB,EACzBC,SAAS,EACTC,YAAY,EACZC,WAAW,EACXC,WAAW,EACZ,GAAGR,IAAAA;AAEJ,IAAA,MAAMS,UAA2B,EAAE;AACnC,IAAA,MAAM,EAAEC,IAAAA,EAAMC,KAAK,EAAE,GAAGV,SAAAA;;;;;IAMxB,MAAMW,MAAAA,GAASN,YAAAA,GAAe,EAAE,GAAGC,WAAAA,CAAYzB,GAAG,CAAC,CAACC,CAAAA,GAAMA,CAAAA,CAAEI,EAAE,CAAA;IAC9D,IAAIyB,MAAAA,CAAO1C,MAAM,GAAG,CAAA,EAAG;AACrB,QAAA,MAAM2C,WAAW,MAAMxC,MAAAA,CAAOC,EAAE,CAC7BC,aAAa,GACbC,MAAM,CAAC,GAAA,CAAA,CACPC,IAAI,CAACkC,KAAAA,CAAAA,CACLjC,OAAO,CAACwB,YAAAA,EAAcU,MAAAA,CAAAA,CACtBjC,WAAW,CAACd,GAAAA,CAAAA;QACf,IAAIgD,QAAAA,CAAS3C,MAAM,GAAG,CAAA,EAAG;AACvBuC,YAAAA,OAAAA,CAAQK,IAAI,CAAC;AACXb,gBAAAA,SAAAA;gBACAc,SAAAA,EAAWF,QAAAA;gBACXG,YAAAA,EAAcd,YAAAA;gBACde,aAAAA,EAAetB;AACjB,aAAA,CAAA;AACF,QAAA;AACF,IAAA;AAEA,IAAA,IAAI,CAACtB,MAAAA,CAAO6C,YAAY,CAACb,UAAU,EAAE;QACnC,OAAOI,OAAAA;AACT,IAAA;IAEA,MAAMU,UAAAA,GAAa,IAAIlD,GAAAA,CAAIsC,WAAAA,CAAYzB,GAAG,CAAC,CAACC,CAAAA,GAAMA,CAAAA,CAAEG,MAAM,CAAA,CAAA;IAC1D,MAAMkC,UAAAA,GAAaZ,WAAAA,CAAYa,MAAM,CAAC,CAACC,CAAAA,GAAM,CAACH,UAAAA,CAAWI,GAAG,CAACD,CAAAA,CAAEpC,MAAM,CAAA,CAAA;IACrE,IAAIkC,UAAAA,CAAWlD,MAAM,KAAK,CAAA,EAAG;QAC3B,OAAOuC,OAAAA;AACT,IAAA;AAEA,IAAA,MAAMe,WAAWJ,UAAAA,CAAWtC,GAAG,CAAC,CAACC,CAAAA,GAAMA,EAAEI,EAAE,CAAA;AAC3C,IAAA,MAAMsC,YAAY,MAAMpD,MAAAA,CAAOC,EAAE,CAC9BC,aAAa,GACbC,MAAM,CAAC,GAAA,CAAA,CACPC,IAAI,CAACkC,KAAAA,CAAAA,CACLjC,OAAO,CAACwB,YAAAA,EAAcsB,QAAAA,CAAAA,CACtB7C,WAAW,CAACd,GAAAA,CAAAA;IAEf,IAAI4D,SAAAA,CAAUvD,MAAM,KAAK,CAAA,EAAG;QAC1B,OAAOuC,OAAAA;AACT,IAAA;AAEA,IAAA,IAAIM,SAAAA,GAAYU,SAAAA;AAChB,IAAA,IAAIrB,yBAAAA,EAA2B;AAC7B,QAAA,MAAMsB,OAAOrD,MAAAA,CAAOC,EAAE,CAACqD,QAAQ,CAACrC,GAAG,CAACa,UAAAA,CAAAA;QACpC,MAAMyB,UAAAA,GAAaH,UAAU3C,GAAG,CAAC,CAAC+C,CAAAA,GAAWA,CAAC,CAAClC,UAAAA,CAAW,CAAA;AAC1D,QAAA,MAAMb,MAAM,MAAMlB,mBAAAA,CAAoBC,GAAAA,EAAK6D,IAAAA,CAAK5D,SAAS,EAAE8D,UAAAA,CAAAA;QAC3Db,SAAAA,GAAYtB,eAAAA,CAAgBgC,WAAW9B,UAAAA,EAAYb,GAAAA,CAAAA;AACrD,IAAA;AAEA2B,IAAAA,OAAAA,CAAQK,IAAI,CAAC;AAAEb,QAAAA,SAAAA;AAAWc,QAAAA,SAAAA;QAAWC,YAAAA,EAAcd,YAAAA;QAAce,aAAAA,EAAetB;AAAW,KAAA,CAAA;IAC3F,OAAOc,OAAAA;AACT,CAAA;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA6CA,MAAMqB,OAAO,OAAOC,GAAAA,EAAsB,EAAExB,WAAW,EAAEC,WAAW,EAAe,GAAA;AACjF,IAAA,MAAMwB,oBAAqC,EAAE;IAE7C,MAAM3D,MAAAA,CAAOC,EAAE,CAAC2D,WAAW,CAAC,OAAO,EAAEpE,GAAG,EAAE,GAAA;AACxC,QAAA,MAAMqE,MAAAA,GAAS;eACTC,MAAAA,CAAOC,MAAM,CAAC/D,MAAAA,CAAO6C,YAAY,CAAA;eAClCiB,MAAAA,CAAOC,MAAM,CAAC/D,MAAAA,CAAOgE,UAAU;AACnC,SAAA;QAED,KAAK,MAAMC,SAASJ,MAAAA,CAAQ;YAC1B,MAAMK,OAAAA,GAAUlE,OAAOC,EAAE,CAACqD,QAAQ,CAACrC,GAAG,CAACgD,KAAAA,CAAMP,GAAG,CAAA;AAEhD,YAAA,KAAK,MAAMS,SAAAA,IAAaL,MAAAA,CAAOC,MAAM,CAACG,OAAAA,CAAQE,UAAU,CAAA,CAA4B;gBAClF,MAAMxC,SAAAA,GAAYuC,UAAUvC,SAAS;AAErC,gBAAA,IAAIuC,SAAAA,CAAUE,IAAI,KAAK,UAAA,IAAc,CAACzC,SAAAA,EAAW;AAC/C,oBAAA;AACF,gBAAA;gBAEA,IAAI,EAAEuC,SAAAA,CAAUG,UAAU,IAAIH,SAAAA,CAAUI,QAAQ,CAAD,EAAI;AACjD,oBAAA;AACF,gBAAA;;AAGA,gBAAA,MAAMtC,eACJ,CAAC,CAACkC,UAAUG,UAAU,IACtBL,MAAMP,GAAG,KAAKA,GAAAA,IACdS,SAAAA,CAAUK,QAAQ,KAAK,YAAA,IACvBP,MAAMP,GAAG,KAAKS,UAAUM,MAAM;;AAGhC,gBAAA,MAAMC,gBAAgBP,SAAAA,CAAUM,MAAM,KAAKf,GAAAA,IAAOO,KAAAA,CAAMP,GAAG,KAAKA,GAAAA;gBAEhE,IAAI,CAACzB,YAAAA,IAAgB,CAACyC,aAAAA,EAAe;AACnC,oBAAA;AACF,gBAAA;;gBAGA,MAAM7C,YAAAA,GAAeI,YAAAA,GACjBL,SAAAA,CAAU+C,UAAU,CAACtC,IAAI,GACzBT,SAAAA,CAAUgD,iBAAiB,CAACvC,IAAI;gBACpC,MAAMf,UAAAA,GAAaW,YAAAA,GACfL,SAAAA,CAAUgD,iBAAiB,CAACvC,IAAI,GAChCT,SAAAA,CAAU+C,UAAU,CAACtC,IAAI;AAE7B,gBAAA,MAAMP,aAAcG,YAAAA,GAAekC,SAAAA,CAAUM,MAAM,GAAGR,MAAMP,GAAG;gBAE/D,MAAMtB,OAAAA,GAAU,MAAMV,kBAAAA,CAAmBlC,GAAAA,EAAK;AAC5CoC,oBAAAA,SAAAA;AACAC,oBAAAA,YAAAA;AACAP,oBAAAA,UAAAA;AACAQ,oBAAAA,UAAAA;AACAC,oBAAAA,yBAAAA,EAA2BE,YAAAA,GACvB,CAAC,CAACjC,MAAAA,CAAO6C,YAAY,CAACf,UAAAA,CAAW,EAAE+C,OAAAA,EAASC,eAAAA,GAC5C,CAAC,CAACb,KAAAA,CAAMY,OAAO,EAAEC,eAAAA;AACrB9C,oBAAAA,SAAAA,EAAWiC,MAAMP,GAAG;AACpBzB,oBAAAA,YAAAA;AACAC,oBAAAA,WAAAA;AACAC,oBAAAA;AACF,iBAAA,CAAA;AACAwB,gBAAAA,iBAAAA,CAAkBlB,IAAI,CAAA,GAAIL,OAAAA,CAAAA;AAC5B,YAAA;AACF,QAAA;AACF,IAAA,CAAA,CAAA;IAEA,OAAOuB,iBAAAA;AACT;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;AAyBC,IACD,MAAMoB,IAAAA,GAAO,OACXC,UAAAA,EACAC,UAAAA,EACAC,iBAAAA,GAAAA;IAEA,MAAMC,kBAAAA,GAAqBC,MAAM,QAAA,EAAUH,UAAAA,CAAAA;AAE3C,IAAA,MAAMI,cAAAA,GAAiBL,UAAAA,CAAWM,MAAM,CACtC,CAACC,GAAAA,EAAKC,QAAAA,GAAAA;AACJ,QAAA,MAAMC,QAAAA,GAAWN,kBAAkB,CAACK,QAAAA,CAAS3E,MAAM,CAAC;AACpD,QAAA,IAAI,CAAC4E,QAAAA,EAAU;YACb,OAAOF,GAAAA;AACT,QAAA;AACAA,QAAAA,GAAG,CAACC,QAAAA,CAAS1E,EAAE,CAAC,GAAG2E,SAAS3E,EAAE;QAC9B,OAAOyE,GAAAA;AACT,IAAA,CAAA,EACA,EAAC,CAAA;IAGH,MAAMG,mBAAAA,GAAsB,IAAI9F,GAAAA,CAAIqF,UAAAA,CAAWxE,GAAG,CAAC,CAACC,CAAAA,GAAMS,MAAAA,CAAOT,CAAAA,CAAEI,EAAE,CAAA,CAAA,CAAA;AACrE,IAAA,MAAM6E,qBAAqB,CAAC7E,EAAAA,GAAwB4E,mBAAAA,CAAoBxC,GAAG,CAAC/B,MAAAA,CAAOL,EAAAA,CAAAA,CAAAA;IAEnF,MAAMd,MAAAA,CAAOC,EAAE,CAAC2D,WAAW,CAAC,OAAO,EAAEpE,GAAG,EAAE,GAAA;AACxC,QAAA,KAAK,MAAM,EACToC,SAAS,EACTc,SAAS,EACTC,YAAAA,EAAciD,YAAY,EAC1BhD,aAAAA,EAAeiD,YAAY,EAC5B,IAAIX,iBAAAA,CAAmB;YACtB,MAAMY,WAAAA,GAAclE,UAAUmE,eAAe;;AAG7C,YAAA,IAAI,CAACH,YAAAA,IAAgB,CAACC,YAAAA,IAAgB,CAACC,WAAAA,EAAa;AAClD,gBAAA;AACF,YAAA;AAEA,YAAA,MAAME,kBAAkBtD,SAAAA,CACrBjC,GAAG,CAAC,CAAC+D,YAAc;AAClBA,oBAAAA,QAAAA;oBACAyB,WAAAA,EAAazB,QAAQ,CAACoB,YAAAA,CAAa;oBACnCM,QAAAA,EAAU1B,QAAQ,CAACqB,YAAAA,CAAa;oBAChCM,aAAAA,EAAe3B,QAAQ,CAACsB,WAAAA,CAAY;AACpCM,oBAAAA,WAAAA,EAAaf,cAAc,CAACb,QAAQ,CAACoB,aAAa;AACpD,iBAAA,GACC5C,MAAM,CAAC,CAACQ,CAAAA,GAA+C6C,OAAAA,CAAQ7C,EAAE4C,WAAW,CAAA,CAAA;YAE/E,IAAI,CAACJ,eAAAA,CAAgBnG,MAAM,EAAE;AAE7B,YAAA,MAAMyG,eAAeN,eAAAA,CAAgBvF,GAAG,CAAC,CAAC+C,CAAAA,GAAMA,EAAE4C,WAAW,CAAA;;AAG7D,YAAA,MAAMG,gBAAgBP,eAAAA,CAAgBvF,GAAG,CAAC,IAAM,CAAC,6BAA6B,CAAC,CAAA;AAC/E,YAAA,MAAM+F,YAAAA,GAAeR,eAAAA,CAAgBS,OAAO,CAAC,CAAC,EAAEL,WAAW,EAAEF,QAAQ,EAAEC,aAAa,EAAE,GAAK;AACzFP,oBAAAA,YAAAA;AACAQ,oBAAAA,WAAAA;AACAP,oBAAAA,YAAAA;AACAK,oBAAAA,QAAAA;AACAC,oBAAAA;AACD,iBAAA,CAAA;YAED,MAAM3G,GAAAA,CAAIoC,UAAUS,IAAI,CAAA,CACrBhC,OAAO,CAACuF,YAAAA,EAAcU,YAAAA,CAAAA,CACtBI,MAAM,CAAC;AACN,gBAAA,CAACZ,WAAAA,GAActG,GAAAA,CAAImH,GAAG,CAAC,CAAC,KAAK,EAAEJ,aAAAA,CAAcK,IAAI,CAAC,GAAA,CAAA,CAAK,YAAY,CAAC,EAAE;AACjEJ,oBAAAA,GAAAA,YAAAA;AACHV,oBAAAA;AACD,iBAAA;AACH,aAAA,CAAA;;AAGF,YAAA,MAAMe,YAAAA,GAAe,MAAMrH,GAAAA,CAAIoC,SAAAA,CAAUS,IAAI,CAAA,CAC1ChC,OAAO,CAACuF,YAAAA,EAAcU,YAAAA,CAAAA,CACtBnG,MAAM,CAACyF,YAAAA,EAAcC,YAAAA,CAAAA;AAExB,YAAA,MAAMiB,cAAc,IAAIlH,GAAAA,CACtBiH,aAAapG,GAAG,CAAC,CAAC+C,CAAAA,GAA+B,CAAA,EAAGA,CAAC,CAACoC,aAAa,CAAC,CAAC,EAAEpC,CAAC,CAACqC,aAAa,CAAA,CAAE,CAAA,CAAA;;AAI1F,YAAA,MAAMkB,QAAAA,GAAWf,eAAAA,CACdhD,MAAM,CACL,CAAC,EAAEoD,WAAW,EAAEF,QAAQ,EAAE,GACxB,CAACY,WAAAA,CAAY5D,GAAG,CAAC,CAAA,EAAGkD,WAAAA,CAAY,CAAC,EAAEF,UAAU,CAAA,IAAK,CAACP,kBAAAA,CAAmBS,WAAAA,CAAAA,CAAAA,CAEzE3F,GAAG,CAAC,CAAC,EAAE+D,QAAQ,EAAE4B,WAAW,EAAED,aAAa,EAAE,IAAM;oBAClD,GAAGa,IAAAA,CAAKhH,MAAAA,CAAOC,EAAE,CAACqD,QAAQ,CAAC2D,WAAW,CAACC,SAAS,EAAE1C,QAAAA,CAAS;AAC3D,oBAAA,CAACoB,eAAeQ,WAAAA;AAChB,oBAAA,CAACN,cAAcK;iBACjB,CAAA,CAAA;YAEF,IAAIY,QAAAA,CAASlH,MAAM,EAAE;AACnB,gBAAA,MAAMsH,YAAYnH,MAAAA,CAAOC,EAAE,CAACmH,OAAO,CAACC,kBAAkB,EAAA;AACtD,gBAAA,MAAM7H,IAAI8H,WAAW,CAAC1F,SAAAA,CAAUS,IAAI,EAAE0E,QAAAA,EAAUI,SAAAA,CAAAA;AAClD,YAAA;AACF,QAAA;AACF,IAAA,CAAA,CAAA;AACF;;;;"}
|
|
@@ -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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
//
|
|
80
|
-
if (
|
|
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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"}
|