@strapi/core 5.40.0 → 5.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Strapi.js +1 -20
- package/dist/Strapi.js.map +1 -1
- package/dist/Strapi.mjs +2 -2
- package/dist/Strapi.mjs.map +1 -1
- package/dist/migrations/database/5.0.0-discard-drafts.d.ts.map +1 -1
- package/dist/migrations/database/5.0.0-discard-drafts.js +1 -11
- package/dist/migrations/database/5.0.0-discard-drafts.js.map +1 -1
- package/dist/migrations/database/5.0.0-discard-drafts.mjs +1 -11
- package/dist/migrations/database/5.0.0-discard-drafts.mjs.map +1 -1
- package/dist/package.json.js +18 -19
- package/dist/package.json.js.map +1 -1
- package/dist/package.json.mjs +18 -19
- package/dist/package.json.mjs.map +1 -1
- package/dist/services/document-service/utils/bidirectional-relations.d.ts +7 -5
- package/dist/services/document-service/utils/bidirectional-relations.d.ts.map +1 -1
- package/dist/services/document-service/utils/bidirectional-relations.js +37 -9
- package/dist/services/document-service/utils/bidirectional-relations.js.map +1 -1
- package/dist/services/document-service/utils/bidirectional-relations.mjs +38 -10
- package/dist/services/document-service/utils/bidirectional-relations.mjs.map +1 -1
- package/dist/services/document-service/utils/unidirectional-relations.js +2 -2
- package/dist/services/document-service/utils/unidirectional-relations.js.map +1 -1
- package/dist/services/document-service/utils/unidirectional-relations.mjs +2 -2
- package/dist/services/document-service/utils/unidirectional-relations.mjs.map +1 -1
- package/package.json +18 -19
package/dist/package.json.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var name = "@strapi/core";
|
|
6
|
-
var version = "5.
|
|
6
|
+
var version = "5.41.0";
|
|
7
7
|
var description = "Core of Strapi";
|
|
8
8
|
var homepage = "https://strapi.io";
|
|
9
9
|
var bugs = {
|
|
@@ -62,14 +62,14 @@ var dependencies = {
|
|
|
62
62
|
"@koa/cors": "5.0.0",
|
|
63
63
|
"@koa/router": "12.0.2",
|
|
64
64
|
"@paralleldrive/cuid2": "2.2.2",
|
|
65
|
-
"@strapi/admin": "5.
|
|
66
|
-
"@strapi/database": "5.
|
|
67
|
-
"@strapi/generators": "5.
|
|
68
|
-
"@strapi/logger": "5.
|
|
69
|
-
"@strapi/permissions": "5.
|
|
70
|
-
"@strapi/types": "5.
|
|
71
|
-
"@strapi/typescript-utils": "5.
|
|
72
|
-
"@strapi/utils": "5.
|
|
65
|
+
"@strapi/admin": "5.41.0",
|
|
66
|
+
"@strapi/database": "5.41.0",
|
|
67
|
+
"@strapi/generators": "5.41.0",
|
|
68
|
+
"@strapi/logger": "5.41.0",
|
|
69
|
+
"@strapi/permissions": "5.41.0",
|
|
70
|
+
"@strapi/types": "5.41.0",
|
|
71
|
+
"@strapi/typescript-utils": "5.41.0",
|
|
72
|
+
"@strapi/utils": "5.41.0",
|
|
73
73
|
"@vercel/stega": "0.1.2",
|
|
74
74
|
bcryptjs: "2.4.3",
|
|
75
75
|
boxen: "5.1.2",
|
|
@@ -78,16 +78,15 @@ var dependencies = {
|
|
|
78
78
|
"cli-table3": "0.6.5",
|
|
79
79
|
commander: "8.3.0",
|
|
80
80
|
configstore: "5.0.1",
|
|
81
|
-
copyfiles: "2.4.1",
|
|
82
81
|
debug: "4.3.4",
|
|
83
82
|
delegates: "1.0.0",
|
|
84
83
|
dotenv: "16.4.5",
|
|
85
84
|
execa: "5.1.1",
|
|
86
85
|
"fs-extra": "11.2.0",
|
|
87
|
-
glob: "13.0.
|
|
88
|
-
"global-agent": "
|
|
86
|
+
glob: "13.0.6",
|
|
87
|
+
"global-agent": "4.1.3",
|
|
89
88
|
"http-errors": "2.0.0",
|
|
90
|
-
inquirer: "
|
|
89
|
+
inquirer: "9.3.8",
|
|
91
90
|
"is-docker": "2.2.1",
|
|
92
91
|
"json-logic-js": "2.0.5",
|
|
93
92
|
jsonwebtoken: "9.0.0",
|
|
@@ -112,7 +111,7 @@ var dependencies = {
|
|
|
112
111
|
semver: "7.5.4",
|
|
113
112
|
statuses: "2.0.1",
|
|
114
113
|
typescript: "5.4.4",
|
|
115
|
-
undici: "6.
|
|
114
|
+
undici: "6.24.0",
|
|
116
115
|
yup: "0.32.9",
|
|
117
116
|
zod: "3.25.67"
|
|
118
117
|
};
|
|
@@ -123,7 +122,7 @@ var devDependencies = {
|
|
|
123
122
|
"@types/configstore": "5.0.1",
|
|
124
123
|
"@types/delegates": "1.0.0",
|
|
125
124
|
"@types/fs-extra": "11.0.4",
|
|
126
|
-
"@types/global-agent": "
|
|
125
|
+
"@types/global-agent": "3.0.0",
|
|
127
126
|
"@types/http-errors": "2.0.4",
|
|
128
127
|
"@types/jest": "29.5.2",
|
|
129
128
|
"@types/json-logic-js": "2.0.8",
|
|
@@ -137,11 +136,11 @@ var devDependencies = {
|
|
|
137
136
|
"@types/node": "24.10.0",
|
|
138
137
|
"@types/node-schedule": "2.1.7",
|
|
139
138
|
"@types/statuses": "2.0.1",
|
|
140
|
-
"eslint-config-custom": "5.
|
|
141
|
-
supertest: "
|
|
142
|
-
tsconfig: "5.
|
|
139
|
+
"eslint-config-custom": "5.41.0",
|
|
140
|
+
supertest: "7.2.2",
|
|
141
|
+
tsconfig: "5.41.0",
|
|
143
142
|
vitest: "catalog:",
|
|
144
|
-
"vitest-config": "5.
|
|
143
|
+
"vitest-config": "5.41.0"
|
|
145
144
|
};
|
|
146
145
|
var engines = {
|
|
147
146
|
node: ">=20.0.0 <=24.x.x",
|
package/dist/package.json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"package.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/package.json.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "@strapi/core";
|
|
2
|
-
var version = "5.
|
|
2
|
+
var version = "5.41.0";
|
|
3
3
|
var description = "Core of Strapi";
|
|
4
4
|
var homepage = "https://strapi.io";
|
|
5
5
|
var bugs = {
|
|
@@ -58,14 +58,14 @@ var dependencies = {
|
|
|
58
58
|
"@koa/cors": "5.0.0",
|
|
59
59
|
"@koa/router": "12.0.2",
|
|
60
60
|
"@paralleldrive/cuid2": "2.2.2",
|
|
61
|
-
"@strapi/admin": "5.
|
|
62
|
-
"@strapi/database": "5.
|
|
63
|
-
"@strapi/generators": "5.
|
|
64
|
-
"@strapi/logger": "5.
|
|
65
|
-
"@strapi/permissions": "5.
|
|
66
|
-
"@strapi/types": "5.
|
|
67
|
-
"@strapi/typescript-utils": "5.
|
|
68
|
-
"@strapi/utils": "5.
|
|
61
|
+
"@strapi/admin": "5.41.0",
|
|
62
|
+
"@strapi/database": "5.41.0",
|
|
63
|
+
"@strapi/generators": "5.41.0",
|
|
64
|
+
"@strapi/logger": "5.41.0",
|
|
65
|
+
"@strapi/permissions": "5.41.0",
|
|
66
|
+
"@strapi/types": "5.41.0",
|
|
67
|
+
"@strapi/typescript-utils": "5.41.0",
|
|
68
|
+
"@strapi/utils": "5.41.0",
|
|
69
69
|
"@vercel/stega": "0.1.2",
|
|
70
70
|
bcryptjs: "2.4.3",
|
|
71
71
|
boxen: "5.1.2",
|
|
@@ -74,16 +74,15 @@ var dependencies = {
|
|
|
74
74
|
"cli-table3": "0.6.5",
|
|
75
75
|
commander: "8.3.0",
|
|
76
76
|
configstore: "5.0.1",
|
|
77
|
-
copyfiles: "2.4.1",
|
|
78
77
|
debug: "4.3.4",
|
|
79
78
|
delegates: "1.0.0",
|
|
80
79
|
dotenv: "16.4.5",
|
|
81
80
|
execa: "5.1.1",
|
|
82
81
|
"fs-extra": "11.2.0",
|
|
83
|
-
glob: "13.0.
|
|
84
|
-
"global-agent": "
|
|
82
|
+
glob: "13.0.6",
|
|
83
|
+
"global-agent": "4.1.3",
|
|
85
84
|
"http-errors": "2.0.0",
|
|
86
|
-
inquirer: "
|
|
85
|
+
inquirer: "9.3.8",
|
|
87
86
|
"is-docker": "2.2.1",
|
|
88
87
|
"json-logic-js": "2.0.5",
|
|
89
88
|
jsonwebtoken: "9.0.0",
|
|
@@ -108,7 +107,7 @@ var dependencies = {
|
|
|
108
107
|
semver: "7.5.4",
|
|
109
108
|
statuses: "2.0.1",
|
|
110
109
|
typescript: "5.4.4",
|
|
111
|
-
undici: "6.
|
|
110
|
+
undici: "6.24.0",
|
|
112
111
|
yup: "0.32.9",
|
|
113
112
|
zod: "3.25.67"
|
|
114
113
|
};
|
|
@@ -119,7 +118,7 @@ var devDependencies = {
|
|
|
119
118
|
"@types/configstore": "5.0.1",
|
|
120
119
|
"@types/delegates": "1.0.0",
|
|
121
120
|
"@types/fs-extra": "11.0.4",
|
|
122
|
-
"@types/global-agent": "
|
|
121
|
+
"@types/global-agent": "3.0.0",
|
|
123
122
|
"@types/http-errors": "2.0.4",
|
|
124
123
|
"@types/jest": "29.5.2",
|
|
125
124
|
"@types/json-logic-js": "2.0.8",
|
|
@@ -133,11 +132,11 @@ var devDependencies = {
|
|
|
133
132
|
"@types/node": "24.10.0",
|
|
134
133
|
"@types/node-schedule": "2.1.7",
|
|
135
134
|
"@types/statuses": "2.0.1",
|
|
136
|
-
"eslint-config-custom": "5.
|
|
137
|
-
supertest: "
|
|
138
|
-
tsconfig: "5.
|
|
135
|
+
"eslint-config-custom": "5.41.0",
|
|
136
|
+
supertest: "7.2.2",
|
|
137
|
+
tsconfig: "5.41.0",
|
|
139
138
|
vitest: "catalog:",
|
|
140
|
-
"vitest-config": "5.
|
|
139
|
+
"vitest-config": "5.41.0"
|
|
141
140
|
};
|
|
142
141
|
var engines = {
|
|
143
142
|
node: ">=20.0.0 <=24.x.x",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"package.json.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UID } from '@strapi/types';
|
|
2
|
+
import type { JoinTable } from '@strapi/database';
|
|
2
3
|
interface LoadContext {
|
|
3
4
|
oldVersions: {
|
|
4
5
|
id: string;
|
|
@@ -9,6 +10,10 @@ interface LoadContext {
|
|
|
9
10
|
locale: string;
|
|
10
11
|
}[];
|
|
11
12
|
}
|
|
13
|
+
interface RelationEntry {
|
|
14
|
+
joinTable: JoinTable;
|
|
15
|
+
relations: Record<string, unknown>[];
|
|
16
|
+
}
|
|
12
17
|
/**
|
|
13
18
|
* Loads all bidirectional relations that need to be synchronized when content entries change state
|
|
14
19
|
* (e.g., during publish/unpublish operations).
|
|
@@ -54,7 +59,7 @@ interface LoadContext {
|
|
|
54
59
|
* @param context - Object containing arrays of old and new entry versions
|
|
55
60
|
* @returns Array of objects containing join table metadata and relations to be updated
|
|
56
61
|
*/
|
|
57
|
-
declare const load: (uid: UID.ContentType, { oldVersions }: LoadContext) => Promise<
|
|
62
|
+
declare const load: (uid: UID.ContentType, { oldVersions }: LoadContext) => Promise<RelationEntry[]>;
|
|
58
63
|
/**
|
|
59
64
|
* Synchronizes the order of bidirectional relations after content entries have changed state.
|
|
60
65
|
*
|
|
@@ -87,9 +92,6 @@ declare const sync: (oldEntries: {
|
|
|
87
92
|
}[], newEntries: {
|
|
88
93
|
id: string;
|
|
89
94
|
locale: string;
|
|
90
|
-
}[], existingRelations:
|
|
91
|
-
joinTable: any;
|
|
92
|
-
relations: any[];
|
|
93
|
-
}[]) => Promise<void>;
|
|
95
|
+
}[], existingRelations: RelationEntry[]) => Promise<void>;
|
|
94
96
|
export { load, sync };
|
|
95
97
|
//# sourceMappingURL=bidirectional-relations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bidirectional-relations.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bidirectional-relations.d.ts","sourceRoot":"","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAU,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,UAAU,WAAW;IACnB,WAAW,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,WAAW,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/C;AAED,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,QAAA,MAAM,IAAI,QAAe,IAAI,WAAW,mBAAmB,WAAW,6BAmDrE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,QAAA,MAAM,IAAI,eACI;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,cAChC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,qBACzB,aAAa,EAAE,kBAyFnC,CAAC;AAEF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var fp = require('lodash/fp');
|
|
4
|
-
var strapiUtils = require('@strapi/utils');
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Loads all bidirectional relations that need to be synchronized when content entries change state
|
|
@@ -121,6 +120,8 @@ var strapiUtils = require('@strapi/utils');
|
|
|
121
120
|
acc[oldEntry.id] = newEntry.id;
|
|
122
121
|
return acc;
|
|
123
122
|
}, {});
|
|
123
|
+
const republishedEntryIds = new Set(newEntries.map((e)=>String(e.id)));
|
|
124
|
+
const isRepublishedEntry = (id)=>republishedEntryIds.has(String(id));
|
|
124
125
|
await strapi.db.transaction(async ({ trx })=>{
|
|
125
126
|
for (const { joinTable, relations } of existingRelations){
|
|
126
127
|
const sourceColumn = joinTable.inverseJoinColumn.name;
|
|
@@ -130,15 +131,42 @@ var strapiUtils = require('@strapi/utils');
|
|
|
130
131
|
if (!sourceColumn || !targetColumn || !orderColumn) {
|
|
131
132
|
continue;
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
const mappedRelations = relations.map((relation)=>({
|
|
135
|
+
relation,
|
|
136
|
+
oldSourceId: relation[sourceColumn],
|
|
137
|
+
targetId: relation[targetColumn],
|
|
138
|
+
originalOrder: relation[orderColumn],
|
|
139
|
+
newSourceId: entryIdMapping[relation[sourceColumn]]
|
|
140
|
+
})).filter((r)=>Boolean(r.newSourceId));
|
|
141
|
+
if (!mappedRelations.length) continue;
|
|
142
|
+
const newSourceIds = mappedRelations.map((r)=>r.newSourceId);
|
|
143
|
+
// Batch UPDATE: set each row's order in a single statement using CASE
|
|
144
|
+
const caseFragments = mappedRelations.map(()=>`WHEN ?? = ? AND ?? = ? THEN ?`);
|
|
145
|
+
const caseBindings = mappedRelations.flatMap(({ newSourceId, targetId, originalOrder })=>[
|
|
146
|
+
sourceColumn,
|
|
147
|
+
newSourceId,
|
|
148
|
+
targetColumn,
|
|
149
|
+
targetId,
|
|
150
|
+
originalOrder
|
|
151
|
+
]);
|
|
152
|
+
await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).update({
|
|
153
|
+
[orderColumn]: trx.raw(`CASE ${caseFragments.join(' ')} ELSE ?? END`, [
|
|
154
|
+
...caseBindings,
|
|
155
|
+
orderColumn
|
|
156
|
+
])
|
|
141
157
|
});
|
|
158
|
+
// Batch SELECT: find which rows exist so we know what to insert
|
|
159
|
+
const existingRows = await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).select(sourceColumn, targetColumn);
|
|
160
|
+
const existingSet = new Set(existingRows.map((r)=>`${r[sourceColumn]}:${r[targetColumn]}`));
|
|
161
|
+
// Batch INSERT: insert cascade-deleted rows that aren't from republished sources
|
|
162
|
+
const toInsert = mappedRelations.filter(({ newSourceId, targetId })=>!existingSet.has(`${newSourceId}:${targetId}`) && !isRepublishedEntry(newSourceId)).map(({ relation, newSourceId, originalOrder })=>({
|
|
163
|
+
...fp.omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),
|
|
164
|
+
[sourceColumn]: newSourceId,
|
|
165
|
+
[orderColumn]: originalOrder
|
|
166
|
+
}));
|
|
167
|
+
if (toInsert.length) {
|
|
168
|
+
await trx.batchInsert(joinTable.name, toInsert, 1000);
|
|
169
|
+
}
|
|
142
170
|
}
|
|
143
171
|
});
|
|
144
172
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bidirectional-relations.js","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy } from 'lodash/fp';\nimport { async } from '@strapi/utils';\nimport type { UID, Schema } from '@strapi/types';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\n/**\n * Loads all bidirectional relations that need to be synchronized when content entries change state\n * (e.g., during publish/unpublish operations).\n *\n * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.\n * When an entry is published, the following occurs:\n *\n * 1. The old published entry is deleted\n * 2. A new entry is created with all its relations\n *\n * This process affects relation ordering in the following way:\n *\n * Initial state (Entry A related to X, Y, Z):\n * ```\n * Entry A (draft) Entry A (published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A\n * ```\n *\n * After publishing Entry A (without relation order sync):\n * ```\n * Entry A (draft) Entry A (new published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A\n * (all relations appear last in order)\n * ```\n *\n * This module preserves the original ordering from both perspectives by:\n * 1. Capturing the relation order before the entry state changes\n * 2. Restoring this order after the new relations are created\n *\n * @param uid - The unique identifier of the content type being processed\n * @param context - Object containing arrays of old and new entry versions\n * @returns Array of objects containing join table metadata and relations to be updated\n */\nconst load = async (uid: UID.ContentType, { oldVersions }: LoadContext) => {\n const relationsToUpdate = [] as any;\n\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n // Skip if not a bidirectional relation targeting our content type\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n !(attribute.inversedBy || attribute.mappedBy)\n ) {\n continue;\n }\n\n // If it's a self referencing relation, there is no need to sync any relation\n // The order will already be handled as both sides are inside the same content type\n if (model.uid === uid) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n // Load all relations that need their order preserved\n const oldEntryIds = oldVersions.map((entry) => entry.id);\n\n const existingRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, oldEntryIds)\n .transacting(trx);\n\n if (existingRelations.length > 0) {\n relationsToUpdate.push({ joinTable, relations: existingRelations });\n }\n }\n }\n });\n\n return relationsToUpdate;\n};\n\n/**\n * Synchronizes the order of bidirectional relations after content entries have changed state.\n *\n * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.\n * While the order of relations from the entry's perspective is maintained (as they're created in order),\n * the inverse relations (from related entries' perspective) would all appear last in order since they're new.\n *\n * Example:\n * ```\n * Before publish:\n * Article(id:1) →(order:1)→ Category(id:5)\n * Category(id:5) →(order:3)→ Article(id:1)\n *\n * After publish (without sync):\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]\n *\n * After sync:\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:3)→ Article(id:2) [order restored]\n * ```\n *\n * @param oldEntries - Array of previous entry versions with their IDs and locales\n * @param newEntries - Array of new entry versions with their IDs and locales\n * @param existingRelations - Array of join table data containing the relations to be updated\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n existingRelations: { joinTable: any; relations: any[] }[]\n) => {\n // Group new entries by locale for easier lookup\n const newEntriesByLocale = keyBy('locale', newEntries);\n\n // Create a mapping of old entry IDs to new entry IDs based on locale\n const entryIdMapping = oldEntries.reduce(\n (acc, oldEntry) => {\n const newEntry = newEntriesByLocale[oldEntry.locale];\n if (!newEntry) return acc;\n acc[oldEntry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of existingRelations) {\n const sourceColumn = joinTable.inverseJoinColumn.name;\n const targetColumn = joinTable.joinColumn.name;\n const orderColumn = joinTable.orderColumnName;\n\n // Failsafe in case those don't exist\n if (!sourceColumn || !targetColumn || !orderColumn) {\n continue;\n }\n\n // Update order values for each relation\n // TODO: Find a way to batch it more efficiently\n await async.map(relations, (relation: any) => {\n const {\n [sourceColumn]: oldSourceId,\n [targetColumn]: targetId,\n [orderColumn]: originalOrder,\n } = relation;\n\n // Update the order column for the new relation entry\n return trx\n .from(joinTable.name)\n .where(sourceColumn, entryIdMapping[oldSourceId])\n .where(targetColumn, targetId)\n .update({ [orderColumn]: originalOrder });\n });\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","oldVersions","relationsToUpdate","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","targetColumnName","inverseJoinColumn","oldEntryIds","map","entry","id","existingRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","oldEntries","newEntries","newEntriesByLocale","keyBy","entryIdMapping","reduce","acc","oldEntry","newEntry","locale","sourceColumn","targetColumn","joinColumn","orderColumn","orderColumnName","async","relation","oldSourceId","targetId","originalOrder","where","update"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bidirectional-relations.js","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\nimport type { UID, Schema } from '@strapi/types';\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationEntry {\n joinTable: JoinTable;\n relations: Record<string, unknown>[];\n}\n\n/**\n * Loads all bidirectional relations that need to be synchronized when content entries change state\n * (e.g., during publish/unpublish operations).\n *\n * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.\n * When an entry is published, the following occurs:\n *\n * 1. The old published entry is deleted\n * 2. A new entry is created with all its relations\n *\n * This process affects relation ordering in the following way:\n *\n * Initial state (Entry A related to X, Y, Z):\n * ```\n * Entry A (draft) Entry A (published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A\n * ```\n *\n * After publishing Entry A (without relation order sync):\n * ```\n * Entry A (draft) Entry A (new published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A\n * (all relations appear last in order)\n * ```\n *\n * This module preserves the original ordering from both perspectives by:\n * 1. Capturing the relation order before the entry state changes\n * 2. Restoring this order after the new relations are created\n *\n * @param uid - The unique identifier of the content type being processed\n * @param context - Object containing arrays of old and new entry versions\n * @returns Array of objects containing join table metadata and relations to be updated\n */\nconst load = async (uid: UID.ContentType, { oldVersions }: LoadContext) => {\n const relationsToUpdate: RelationEntry[] = [];\n\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as Record<string, any>[]) {\n // Skip if not a bidirectional relation targeting our content type\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n !(attribute.inversedBy || attribute.mappedBy)\n ) {\n continue;\n }\n\n // If it's a self referencing relation, there is no need to sync any relation\n // The order will already be handled as both sides are inside the same content type\n if (model.uid === uid) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n // Load all relations that need their order preserved\n const oldEntryIds = oldVersions.map((entry) => entry.id);\n\n const existingRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, oldEntryIds)\n .transacting(trx);\n\n if (existingRelations.length > 0) {\n relationsToUpdate.push({ joinTable, relations: existingRelations });\n }\n }\n }\n });\n\n return relationsToUpdate;\n};\n\n/**\n * Synchronizes the order of bidirectional relations after content entries have changed state.\n *\n * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.\n * While the order of relations from the entry's perspective is maintained (as they're created in order),\n * the inverse relations (from related entries' perspective) would all appear last in order since they're new.\n *\n * Example:\n * ```\n * Before publish:\n * Article(id:1) →(order:1)→ Category(id:5)\n * Category(id:5) →(order:3)→ Article(id:1)\n *\n * After publish (without sync):\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]\n *\n * After sync:\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:3)→ Article(id:2) [order restored]\n * ```\n *\n * @param oldEntries - Array of previous entry versions with their IDs and locales\n * @param newEntries - Array of new entry versions with their IDs and locales\n * @param existingRelations - Array of join table data containing the relations to be updated\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n existingRelations: RelationEntry[]\n) => {\n // Group new entries by locale for easier lookup\n const newEntriesByLocale = keyBy('locale', newEntries);\n\n // Create a mapping of old entry IDs to new entry IDs based on locale\n const entryIdMapping = oldEntries.reduce(\n (acc, oldEntry) => {\n const newEntry = newEntriesByLocale[oldEntry.locale];\n if (!newEntry) return acc;\n acc[oldEntry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n const republishedEntryIds = new Set(newEntries.map((e) => String(e.id)));\n const isRepublishedEntry = (id: string | number) => republishedEntryIds.has(String(id));\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of existingRelations) {\n const sourceColumn = joinTable.inverseJoinColumn.name;\n const targetColumn = joinTable.joinColumn.name;\n const orderColumn = joinTable.orderColumnName;\n\n // Failsafe in case those don't exist\n if (!sourceColumn || !targetColumn || !orderColumn) {\n continue;\n }\n\n const mappedRelations = relations\n .map((relation) => ({\n relation,\n oldSourceId: relation[sourceColumn] as string,\n targetId: relation[targetColumn] as string,\n originalOrder: relation[orderColumn],\n newSourceId: entryIdMapping[relation[sourceColumn] as string],\n }))\n .filter((r): r is typeof r & { newSourceId: string } => Boolean(r.newSourceId));\n\n if (!mappedRelations.length) continue;\n\n const newSourceIds = mappedRelations.map((r) => r.newSourceId);\n\n // Batch UPDATE: set each row's order in a single statement using CASE\n const caseFragments = mappedRelations.map(() => `WHEN ?? = ? AND ?? = ? THEN ?`);\n const caseBindings = mappedRelations.flatMap(({ newSourceId, targetId, originalOrder }) => [\n sourceColumn,\n newSourceId,\n targetColumn,\n targetId,\n originalOrder,\n ]);\n\n await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .update({\n [orderColumn]: trx.raw(`CASE ${caseFragments.join(' ')} ELSE ?? END`, [\n ...caseBindings,\n orderColumn,\n ]),\n });\n\n // Batch SELECT: find which rows exist so we know what to insert\n const existingRows = await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .select(sourceColumn, targetColumn);\n\n const existingSet = new Set(\n existingRows.map((r: Record<string, unknown>) => `${r[sourceColumn]}:${r[targetColumn]}`)\n );\n\n // Batch INSERT: insert cascade-deleted rows that aren't from republished sources\n const toInsert = mappedRelations\n .filter(\n ({ newSourceId, targetId }) =>\n !existingSet.has(`${newSourceId}:${targetId}`) && !isRepublishedEntry(newSourceId)\n )\n .map(({ relation, newSourceId, originalOrder }) => ({\n ...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),\n [sourceColumn]: newSourceId,\n [orderColumn]: originalOrder,\n }));\n\n if (toInsert.length) {\n await trx.batchInsert(joinTable.name, toInsert, 1000);\n }\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","oldVersions","relationsToUpdate","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","targetColumnName","inverseJoinColumn","oldEntryIds","map","entry","id","existingRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","oldEntries","newEntries","newEntriesByLocale","keyBy","entryIdMapping","reduce","acc","oldEntry","newEntry","locale","republishedEntryIds","Set","e","String","isRepublishedEntry","has","sourceColumn","targetColumn","joinColumn","orderColumn","orderColumnName","mappedRelations","relation","oldSourceId","targetId","originalOrder","newSourceId","filter","r","Boolean","newSourceIds","caseFragments","caseBindings","flatMap","update","raw","join","existingRows","existingSet","toInsert","omit","identifiers","ID_COLUMN","batchInsert"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { keyBy } from 'lodash/fp';
|
|
2
|
-
import { async } from '@strapi/utils';
|
|
1
|
+
import { keyBy, omit } from 'lodash/fp';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Loads all bidirectional relations that need to be synchronized when content entries change state
|
|
@@ -119,6 +118,8 @@ import { async } from '@strapi/utils';
|
|
|
119
118
|
acc[oldEntry.id] = newEntry.id;
|
|
120
119
|
return acc;
|
|
121
120
|
}, {});
|
|
121
|
+
const republishedEntryIds = new Set(newEntries.map((e)=>String(e.id)));
|
|
122
|
+
const isRepublishedEntry = (id)=>republishedEntryIds.has(String(id));
|
|
122
123
|
await strapi.db.transaction(async ({ trx })=>{
|
|
123
124
|
for (const { joinTable, relations } of existingRelations){
|
|
124
125
|
const sourceColumn = joinTable.inverseJoinColumn.name;
|
|
@@ -128,15 +129,42 @@ import { async } from '@strapi/utils';
|
|
|
128
129
|
if (!sourceColumn || !targetColumn || !orderColumn) {
|
|
129
130
|
continue;
|
|
130
131
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
const mappedRelations = relations.map((relation)=>({
|
|
133
|
+
relation,
|
|
134
|
+
oldSourceId: relation[sourceColumn],
|
|
135
|
+
targetId: relation[targetColumn],
|
|
136
|
+
originalOrder: relation[orderColumn],
|
|
137
|
+
newSourceId: entryIdMapping[relation[sourceColumn]]
|
|
138
|
+
})).filter((r)=>Boolean(r.newSourceId));
|
|
139
|
+
if (!mappedRelations.length) continue;
|
|
140
|
+
const newSourceIds = mappedRelations.map((r)=>r.newSourceId);
|
|
141
|
+
// Batch UPDATE: set each row's order in a single statement using CASE
|
|
142
|
+
const caseFragments = mappedRelations.map(()=>`WHEN ?? = ? AND ?? = ? THEN ?`);
|
|
143
|
+
const caseBindings = mappedRelations.flatMap(({ newSourceId, targetId, originalOrder })=>[
|
|
144
|
+
sourceColumn,
|
|
145
|
+
newSourceId,
|
|
146
|
+
targetColumn,
|
|
147
|
+
targetId,
|
|
148
|
+
originalOrder
|
|
149
|
+
]);
|
|
150
|
+
await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).update({
|
|
151
|
+
[orderColumn]: trx.raw(`CASE ${caseFragments.join(' ')} ELSE ?? END`, [
|
|
152
|
+
...caseBindings,
|
|
153
|
+
orderColumn
|
|
154
|
+
])
|
|
139
155
|
});
|
|
156
|
+
// Batch SELECT: find which rows exist so we know what to insert
|
|
157
|
+
const existingRows = await trx(joinTable.name).whereIn(sourceColumn, newSourceIds).select(sourceColumn, targetColumn);
|
|
158
|
+
const existingSet = new Set(existingRows.map((r)=>`${r[sourceColumn]}:${r[targetColumn]}`));
|
|
159
|
+
// Batch INSERT: insert cascade-deleted rows that aren't from republished sources
|
|
160
|
+
const toInsert = mappedRelations.filter(({ newSourceId, targetId })=>!existingSet.has(`${newSourceId}:${targetId}`) && !isRepublishedEntry(newSourceId)).map(({ relation, newSourceId, originalOrder })=>({
|
|
161
|
+
...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),
|
|
162
|
+
[sourceColumn]: newSourceId,
|
|
163
|
+
[orderColumn]: originalOrder
|
|
164
|
+
}));
|
|
165
|
+
if (toInsert.length) {
|
|
166
|
+
await trx.batchInsert(joinTable.name, toInsert, 1000);
|
|
167
|
+
}
|
|
140
168
|
}
|
|
141
169
|
});
|
|
142
170
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy } from 'lodash/fp';\nimport { async } from '@strapi/utils';\nimport type { UID, Schema } from '@strapi/types';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\n/**\n * Loads all bidirectional relations that need to be synchronized when content entries change state\n * (e.g., during publish/unpublish operations).\n *\n * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.\n * When an entry is published, the following occurs:\n *\n * 1. The old published entry is deleted\n * 2. A new entry is created with all its relations\n *\n * This process affects relation ordering in the following way:\n *\n * Initial state (Entry A related to X, Y, Z):\n * ```\n * Entry A (draft) Entry A (published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A\n * ```\n *\n * After publishing Entry A (without relation order sync):\n * ```\n * Entry A (draft) Entry A (new published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A\n * (all relations appear last in order)\n * ```\n *\n * This module preserves the original ordering from both perspectives by:\n * 1. Capturing the relation order before the entry state changes\n * 2. Restoring this order after the new relations are created\n *\n * @param uid - The unique identifier of the content type being processed\n * @param context - Object containing arrays of old and new entry versions\n * @returns Array of objects containing join table metadata and relations to be updated\n */\nconst load = async (uid: UID.ContentType, { oldVersions }: LoadContext) => {\n const relationsToUpdate = [] as any;\n\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n // Skip if not a bidirectional relation targeting our content type\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n !(attribute.inversedBy || attribute.mappedBy)\n ) {\n continue;\n }\n\n // If it's a self referencing relation, there is no need to sync any relation\n // The order will already be handled as both sides are inside the same content type\n if (model.uid === uid) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n // Load all relations that need their order preserved\n const oldEntryIds = oldVersions.map((entry) => entry.id);\n\n const existingRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, oldEntryIds)\n .transacting(trx);\n\n if (existingRelations.length > 0) {\n relationsToUpdate.push({ joinTable, relations: existingRelations });\n }\n }\n }\n });\n\n return relationsToUpdate;\n};\n\n/**\n * Synchronizes the order of bidirectional relations after content entries have changed state.\n *\n * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.\n * While the order of relations from the entry's perspective is maintained (as they're created in order),\n * the inverse relations (from related entries' perspective) would all appear last in order since they're new.\n *\n * Example:\n * ```\n * Before publish:\n * Article(id:1) →(order:1)→ Category(id:5)\n * Category(id:5) →(order:3)→ Article(id:1)\n *\n * After publish (without sync):\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]\n *\n * After sync:\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:3)→ Article(id:2) [order restored]\n * ```\n *\n * @param oldEntries - Array of previous entry versions with their IDs and locales\n * @param newEntries - Array of new entry versions with their IDs and locales\n * @param existingRelations - Array of join table data containing the relations to be updated\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n existingRelations: { joinTable: any; relations: any[] }[]\n) => {\n // Group new entries by locale for easier lookup\n const newEntriesByLocale = keyBy('locale', newEntries);\n\n // Create a mapping of old entry IDs to new entry IDs based on locale\n const entryIdMapping = oldEntries.reduce(\n (acc, oldEntry) => {\n const newEntry = newEntriesByLocale[oldEntry.locale];\n if (!newEntry) return acc;\n acc[oldEntry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of existingRelations) {\n const sourceColumn = joinTable.inverseJoinColumn.name;\n const targetColumn = joinTable.joinColumn.name;\n const orderColumn = joinTable.orderColumnName;\n\n // Failsafe in case those don't exist\n if (!sourceColumn || !targetColumn || !orderColumn) {\n continue;\n }\n\n // Update order values for each relation\n // TODO: Find a way to batch it more efficiently\n await async.map(relations, (relation: any) => {\n const {\n [sourceColumn]: oldSourceId,\n [targetColumn]: targetId,\n [orderColumn]: originalOrder,\n } = relation;\n\n // Update the order column for the new relation entry\n return trx\n .from(joinTable.name)\n .where(sourceColumn, entryIdMapping[oldSourceId])\n .where(targetColumn, targetId)\n .update({ [orderColumn]: originalOrder });\n });\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","oldVersions","relationsToUpdate","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","targetColumnName","inverseJoinColumn","oldEntryIds","map","entry","id","existingRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","oldEntries","newEntries","newEntriesByLocale","keyBy","entryIdMapping","reduce","acc","oldEntry","newEntry","locale","sourceColumn","targetColumn","joinColumn","orderColumn","orderColumnName","async","relation","oldSourceId","targetId","originalOrder","where","update"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bidirectional-relations.mjs","sources":["../../../../src/services/document-service/utils/bidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\nimport type { UID, Schema } from '@strapi/types';\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationEntry {\n joinTable: JoinTable;\n relations: Record<string, unknown>[];\n}\n\n/**\n * Loads all bidirectional relations that need to be synchronized when content entries change state\n * (e.g., during publish/unpublish operations).\n *\n * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.\n * When an entry is published, the following occurs:\n *\n * 1. The old published entry is deleted\n * 2. A new entry is created with all its relations\n *\n * This process affects relation ordering in the following way:\n *\n * Initial state (Entry A related to X, Y, Z):\n * ```\n * Entry A (draft) Entry A (published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A\n * ```\n *\n * After publishing Entry A (without relation order sync):\n * ```\n * Entry A (draft) Entry A (new published)\n * │ │\n * ├──(1)→ X ├──(1)→ X\n * ├──(2)→ Y ├──(2)→ Y\n * └──(3)→ Z └──(3)→ Z\n *\n * X's perspective: Y's perspective: Z's perspective:\n * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A\n * (all relations appear last in order)\n * ```\n *\n * This module preserves the original ordering from both perspectives by:\n * 1. Capturing the relation order before the entry state changes\n * 2. Restoring this order after the new relations are created\n *\n * @param uid - The unique identifier of the content type being processed\n * @param context - Object containing arrays of old and new entry versions\n * @returns Array of objects containing join table metadata and relations to be updated\n */\nconst load = async (uid: UID.ContentType, { oldVersions }: LoadContext) => {\n const relationsToUpdate: RelationEntry[] = [];\n\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as Record<string, any>[]) {\n // Skip if not a bidirectional relation targeting our content type\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n !(attribute.inversedBy || attribute.mappedBy)\n ) {\n continue;\n }\n\n // If it's a self referencing relation, there is no need to sync any relation\n // The order will already be handled as both sides are inside the same content type\n if (model.uid === uid) {\n continue;\n }\n\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n // Load all relations that need their order preserved\n const oldEntryIds = oldVersions.map((entry) => entry.id);\n\n const existingRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, oldEntryIds)\n .transacting(trx);\n\n if (existingRelations.length > 0) {\n relationsToUpdate.push({ joinTable, relations: existingRelations });\n }\n }\n }\n });\n\n return relationsToUpdate;\n};\n\n/**\n * Synchronizes the order of bidirectional relations after content entries have changed state.\n *\n * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.\n * While the order of relations from the entry's perspective is maintained (as they're created in order),\n * the inverse relations (from related entries' perspective) would all appear last in order since they're new.\n *\n * Example:\n * ```\n * Before publish:\n * Article(id:1) →(order:1)→ Category(id:5)\n * Category(id:5) →(order:3)→ Article(id:1)\n *\n * After publish (without sync):\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]\n *\n * After sync:\n * Article(id:2) →(order:1)→ Category(id:5) [order preserved]\n * Category(id:5) →(order:3)→ Article(id:2) [order restored]\n * ```\n *\n * @param oldEntries - Array of previous entry versions with their IDs and locales\n * @param newEntries - Array of new entry versions with their IDs and locales\n * @param existingRelations - Array of join table data containing the relations to be updated\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n existingRelations: RelationEntry[]\n) => {\n // Group new entries by locale for easier lookup\n const newEntriesByLocale = keyBy('locale', newEntries);\n\n // Create a mapping of old entry IDs to new entry IDs based on locale\n const entryIdMapping = oldEntries.reduce(\n (acc, oldEntry) => {\n const newEntry = newEntriesByLocale[oldEntry.locale];\n if (!newEntry) return acc;\n acc[oldEntry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n const republishedEntryIds = new Set(newEntries.map((e) => String(e.id)));\n const isRepublishedEntry = (id: string | number) => republishedEntryIds.has(String(id));\n\n await strapi.db.transaction(async ({ trx }) => {\n for (const { joinTable, relations } of existingRelations) {\n const sourceColumn = joinTable.inverseJoinColumn.name;\n const targetColumn = joinTable.joinColumn.name;\n const orderColumn = joinTable.orderColumnName;\n\n // Failsafe in case those don't exist\n if (!sourceColumn || !targetColumn || !orderColumn) {\n continue;\n }\n\n const mappedRelations = relations\n .map((relation) => ({\n relation,\n oldSourceId: relation[sourceColumn] as string,\n targetId: relation[targetColumn] as string,\n originalOrder: relation[orderColumn],\n newSourceId: entryIdMapping[relation[sourceColumn] as string],\n }))\n .filter((r): r is typeof r & { newSourceId: string } => Boolean(r.newSourceId));\n\n if (!mappedRelations.length) continue;\n\n const newSourceIds = mappedRelations.map((r) => r.newSourceId);\n\n // Batch UPDATE: set each row's order in a single statement using CASE\n const caseFragments = mappedRelations.map(() => `WHEN ?? = ? AND ?? = ? THEN ?`);\n const caseBindings = mappedRelations.flatMap(({ newSourceId, targetId, originalOrder }) => [\n sourceColumn,\n newSourceId,\n targetColumn,\n targetId,\n originalOrder,\n ]);\n\n await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .update({\n [orderColumn]: trx.raw(`CASE ${caseFragments.join(' ')} ELSE ?? END`, [\n ...caseBindings,\n orderColumn,\n ]),\n });\n\n // Batch SELECT: find which rows exist so we know what to insert\n const existingRows = await trx(joinTable.name)\n .whereIn(sourceColumn, newSourceIds)\n .select(sourceColumn, targetColumn);\n\n const existingSet = new Set(\n existingRows.map((r: Record<string, unknown>) => `${r[sourceColumn]}:${r[targetColumn]}`)\n );\n\n // Batch INSERT: insert cascade-deleted rows that aren't from republished sources\n const toInsert = mappedRelations\n .filter(\n ({ newSourceId, targetId }) =>\n !existingSet.has(`${newSourceId}:${targetId}`) && !isRepublishedEntry(newSourceId)\n )\n .map(({ relation, newSourceId, originalOrder }) => ({\n ...omit(strapi.db.metadata.identifiers.ID_COLUMN, relation),\n [sourceColumn]: newSourceId,\n [orderColumn]: originalOrder,\n }));\n\n if (toInsert.length) {\n await trx.batchInsert(joinTable.name, toInsert, 1000);\n }\n }\n });\n};\n\nexport { load, sync };\n"],"names":["load","uid","oldVersions","relationsToUpdate","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","targetColumnName","inverseJoinColumn","oldEntryIds","map","entry","id","existingRelations","getConnection","select","from","whereIn","transacting","length","push","relations","sync","oldEntries","newEntries","newEntriesByLocale","keyBy","entryIdMapping","reduce","acc","oldEntry","newEntry","locale","republishedEntryIds","Set","e","String","isRepublishedEntry","has","sourceColumn","targetColumn","joinColumn","orderColumn","orderColumnName","mappedRelations","relation","oldSourceId","targetId","originalOrder","newSourceId","filter","r","Boolean","newSourceIds","caseFragments","caseBindings","flatMap","update","raw","join","existingRows","existingSet","toInsert","omit","identifiers","ID_COLUMN","batchInsert"],"mappings":"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"}
|
|
@@ -120,8 +120,8 @@ var fp = require('lodash/fp');
|
|
|
120
120
|
[column]: newId
|
|
121
121
|
};
|
|
122
122
|
});
|
|
123
|
-
|
|
124
|
-
await trx.batchInsert(joinTable.name, newRelations,
|
|
123
|
+
const batchSize = strapi.db.dialect.getBatchInsertSize();
|
|
124
|
+
await trx.batchInsert(joinTable.name, newRelations, batchSize);
|
|
125
125
|
}
|
|
126
126
|
});
|
|
127
127
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unidirectional-relations.js","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record<string, any>[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record<string, any>,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise<boolean>;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise<RelationUpdate[]> => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[entry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n // Insert those relations into the join table\n await trx.batchInsert(joinTable.name, newRelations, 1000);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","column","newRelations","newId","batchInsert"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"unidirectional-relations.js","sources":["../../../../src/services/document-service/utils/unidirectional-relations.ts"],"sourcesContent":["/* eslint-disable no-continue */\nimport { keyBy, omit } from 'lodash/fp';\n\nimport type { UID, Schema } from '@strapi/types';\n\nimport type { JoinTable } from '@strapi/database';\n\ninterface LoadContext {\n oldVersions: { id: string; locale: string }[];\n newVersions: { id: string; locale: string }[];\n}\n\ninterface RelationUpdate {\n joinTable: JoinTable;\n relations: Record<string, any>[];\n}\n\ninterface RelationFilterOptions {\n /**\n * Function to determine if a relation should be propagated to new document versions\n * This replaces the hardcoded component-specific logic\n */\n shouldPropagateRelation?: (\n relation: Record<string, any>,\n model: Schema.Component | Schema.ContentType,\n trx: any\n ) => Promise<boolean>;\n}\n\n/**\n * Loads lingering relations that need to be updated when overriding a published or draft entry.\n * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.\n * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.\n */\nconst load = async (\n uid: UID.ContentType,\n { oldVersions, newVersions }: LoadContext,\n options: RelationFilterOptions = {}\n): Promise<RelationUpdate[]> => {\n const updates: RelationUpdate[] = [];\n\n // Iterate all components and content types to find relations that need to be updated\n await strapi.db.transaction(async ({ trx }) => {\n const contentTypes = Object.values(strapi.contentTypes) as Schema.ContentType[];\n const components = Object.values(strapi.components) as Schema.Component[];\n\n for (const model of [...contentTypes, ...components]) {\n const dbModel = strapi.db.metadata.get(model.uid);\n\n for (const attribute of Object.values(dbModel.attributes) as any) {\n /**\n * Only consider unidirectional relations\n */\n if (\n attribute.type !== 'relation' ||\n attribute.target !== uid ||\n attribute.inversedBy ||\n attribute.mappedBy\n ) {\n continue;\n }\n\n // TODO: joinColumn relations\n const joinTable = attribute.joinTable;\n if (!joinTable) {\n continue;\n }\n\n const { name: sourceColumnName } = joinTable.joinColumn;\n const { name: targetColumnName } = joinTable.inverseJoinColumn;\n\n /**\n * Load all relations that need to be updated\n */\n // NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published\n const ids = oldVersions.map((entry) => entry.id);\n\n const oldVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n if (oldVersionsRelations.length > 0) {\n updates.push({ joinTable, relations: oldVersionsRelations });\n }\n\n /**\n * if publishing\n * if published version exists\n * updated published versions links\n * else\n * create link to newly published version\n *\n * if discarding\n * if published version link exists & not draft version link\n * create link to new draft version\n */\n if (!model.options?.draftAndPublish) {\n const ids = newVersions.map((entry) => entry.id);\n\n // This is the step were we query the join table based on the id of the document\n const newVersionsRelations = await strapi.db\n .getConnection()\n .select('*')\n .from(joinTable.name)\n .whereIn(targetColumnName, ids)\n .transacting(trx);\n\n let versionRelations = newVersionsRelations;\n if (options.shouldPropagateRelation) {\n const relationsToPropagate = [];\n for (const relation of newVersionsRelations) {\n if (await options.shouldPropagateRelation(relation, model, trx)) {\n relationsToPropagate.push(relation);\n }\n }\n versionRelations = relationsToPropagate;\n }\n\n if (versionRelations.length > 0) {\n // when publishing a draft that doesn't have a published version yet,\n // copy the links to the draft over to the published version\n // when discarding a published version, if no drafts exists\n const discardToAdd = versionRelations\n .filter((relation) => {\n const matchingOldVersion = oldVersionsRelations.find((oldRelation) => {\n return oldRelation[sourceColumnName] === relation[sourceColumnName];\n });\n\n return !matchingOldVersion;\n })\n .map(omit(strapi.db.metadata.identifiers.ID_COLUMN));\n\n updates.push({ joinTable, relations: discardToAdd });\n }\n }\n }\n }\n });\n\n return updates;\n};\n\n/**\n * Updates uni directional relations to target the right entries when overriding published or draft entries.\n *\n * This function:\n * 1. Creates new relations pointing to the new entry versions\n * 2. Precisely deletes only the old relations being replaced to prevent orphaned links\n *\n * @param oldEntries The old entries that are being overridden\n * @param newEntries The new entries that are overriding the old ones\n * @param oldRelations The relations that were previously loaded with `load` @see load\n */\nconst sync = async (\n oldEntries: { id: string; locale: string }[],\n newEntries: { id: string; locale: string }[],\n oldRelations: { joinTable: any; relations: any[] }[]\n) => {\n /**\n * Create a map of old entry ids to new entry ids\n *\n * Will be used to update the relation target ids\n */\n const newEntryByLocale = keyBy('locale', newEntries);\n const oldEntriesMap = oldEntries.reduce(\n (acc, entry) => {\n const newEntry = newEntryByLocale[entry.locale];\n if (!newEntry) return acc;\n acc[entry.id] = newEntry.id;\n return acc;\n },\n {} as Record<string, string>\n );\n\n await strapi.db.transaction(async ({ trx }) => {\n // Iterate old relations that are deleted and insert the new ones\n for (const { joinTable, relations } of oldRelations) {\n // Update old ids with the new ones\n const column = joinTable.inverseJoinColumn.name;\n\n const newRelations = relations.map((relation) => {\n const newId = oldEntriesMap[relation[column]];\n return { ...relation, [column]: newId };\n });\n\n const batchSize = strapi.db.dialect.getBatchInsertSize();\n await trx.batchInsert(joinTable.name, newRelations, batchSize);\n }\n });\n};\n\nexport { load, sync };\nexport type { RelationFilterOptions };\n"],"names":["load","uid","oldVersions","newVersions","options","updates","strapi","db","transaction","trx","contentTypes","Object","values","components","model","dbModel","metadata","get","attribute","attributes","type","target","inversedBy","mappedBy","joinTable","name","sourceColumnName","joinColumn","targetColumnName","inverseJoinColumn","ids","map","entry","id","oldVersionsRelations","getConnection","select","from","whereIn","transacting","length","push","relations","draftAndPublish","newVersionsRelations","versionRelations","shouldPropagateRelation","relationsToPropagate","relation","discardToAdd","filter","matchingOldVersion","find","oldRelation","omit","identifiers","ID_COLUMN","sync","oldEntries","newEntries","oldRelations","newEntryByLocale","keyBy","oldEntriesMap","reduce","acc","newEntry","locale","column","newRelations","newId","batchSize","dialect","getBatchInsertSize","batchInsert"],"mappings":"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"}
|
|
@@ -118,8 +118,8 @@ import { omit, keyBy } from 'lodash/fp';
|
|
|
118
118
|
[column]: newId
|
|
119
119
|
};
|
|
120
120
|
});
|
|
121
|
-
|
|
122
|
-
await trx.batchInsert(joinTable.name, newRelations,
|
|
121
|
+
const batchSize = strapi.db.dialect.getBatchInsertSize();
|
|
122
|
+
await trx.batchInsert(joinTable.name, newRelations, batchSize);
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
};
|