@strapi/core 0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393 → 0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834

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/index.mjs CHANGED
@@ -290,7 +290,7 @@ const getDirs = ({ appDir, distDir }, config)=>({
290
290
  });
291
291
 
292
292
  var name = "@strapi/core";
293
- var version = "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393";
293
+ var version = "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834";
294
294
  var description = "Core of Strapi";
295
295
  var homepage = "https://strapi.io";
296
296
  var bugs = {
@@ -346,14 +346,14 @@ var dependencies = {
346
346
  "@koa/cors": "5.0.0",
347
347
  "@koa/router": "12.0.2",
348
348
  "@paralleldrive/cuid2": "2.2.2",
349
- "@strapi/admin": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
350
- "@strapi/database": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
351
- "@strapi/generators": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
352
- "@strapi/logger": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
353
- "@strapi/permissions": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
354
- "@strapi/types": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
355
- "@strapi/typescript-utils": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
356
- "@strapi/utils": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
349
+ "@strapi/admin": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
350
+ "@strapi/database": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
351
+ "@strapi/generators": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
352
+ "@strapi/logger": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
353
+ "@strapi/permissions": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
354
+ "@strapi/types": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
355
+ "@strapi/typescript-utils": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
356
+ "@strapi/utils": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
357
357
  bcryptjs: "2.4.3",
358
358
  boxen: "5.1.2",
359
359
  chalk: "4.1.2",
@@ -416,9 +416,9 @@ var devDependencies = {
416
416
  "@types/node": "18.19.24",
417
417
  "@types/node-schedule": "2.1.7",
418
418
  "@types/statuses": "2.0.1",
419
- "eslint-config-custom": "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393",
419
+ "eslint-config-custom": "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834",
420
420
  supertest: "6.3.3",
421
- tsconfig: "0.0.0-next.e98ae27d8c69c68823eb7c46cf329385637ba393"
421
+ tsconfig: "0.0.0-next.eb48c73c86cbc452c1ba8d727106f9ed9da0c834"
422
422
  };
423
423
  var engines = {
424
424
  node: ">=18.0.0 <=22.x.x",
@@ -1179,6 +1179,9 @@ const DEFAULT_FEATURES = {
1179
1179
  options: {
1180
1180
  retentionDays: 99999
1181
1181
  }
1182
+ },
1183
+ {
1184
+ name: 'cms-advanced-preview'
1182
1185
  }
1183
1186
  ]
1184
1187
  };
@@ -1493,6 +1496,9 @@ var index = Object.freeze({
1493
1496
  get seats () {
1494
1497
  return ee.licenseInfo.seats;
1495
1498
  },
1499
+ get type () {
1500
+ return ee.licenseInfo.type;
1501
+ },
1496
1502
  features: Object.freeze({
1497
1503
  list,
1498
1504
  get,
@@ -6748,7 +6754,7 @@ const EVENTS = {
6748
6754
  * Loads lingering relations that need to be updated when overriding a published or draft entry.
6749
6755
  * This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.
6750
6756
  * This is not the case for bi-directional relations, where the target entry is also linked to the source entry.
6751
- */ const load = async (uid, { oldVersions, newVersions })=>{
6757
+ */ const load$1 = async (uid, { oldVersions, newVersions })=>{
6752
6758
  const updates = [];
6753
6759
  // Iterate all components and content types to find relations that need to be updated
6754
6760
  await strapi.db.transaction(async ({ trx })=>{
@@ -6823,7 +6829,7 @@ const EVENTS = {
6823
6829
  * @param oldEntries The old entries that are being overridden
6824
6830
  * @param newEntries The new entries that are overriding the old ones
6825
6831
  * @param oldRelations The relations that were previously loaded with `load` @see load
6826
- */ const sync = async (oldEntries, newEntries, oldRelations)=>{
6832
+ */ const sync$1 = async (oldEntries, newEntries, oldRelations)=>{
6827
6833
  /**
6828
6834
  * Create a map of old entry ids to new entry ids
6829
6835
  *
@@ -6853,6 +6859,146 @@ const EVENTS = {
6853
6859
  });
6854
6860
  };
6855
6861
 
6862
+ /**
6863
+ * Loads all bidirectional relations that need to be synchronized when content entries change state
6864
+ * (e.g., during publish/unpublish operations).
6865
+ *
6866
+ * In Strapi, bidirectional relations allow maintaining order from both sides of the relation.
6867
+ * When an entry is published, the following occurs:
6868
+ *
6869
+ * 1. The old published entry is deleted
6870
+ * 2. A new entry is created with all its relations
6871
+ *
6872
+ * This process affects relation ordering in the following way:
6873
+ *
6874
+ * Initial state (Entry A related to X, Y, Z):
6875
+ * ```
6876
+ * Entry A (draft) Entry A (published)
6877
+ * │ │
6878
+ * ├──(1)→ X ├──(1)→ X
6879
+ * ├──(2)→ Y ├──(2)→ Y
6880
+ * └──(3)→ Z └──(3)→ Z
6881
+ *
6882
+ * X's perspective: Y's perspective: Z's perspective:
6883
+ * └──(2)→ Entry A └──(1)→ Entry A └──(3)→ Entry A
6884
+ * ```
6885
+ *
6886
+ * After publishing Entry A (without relation order sync):
6887
+ * ```
6888
+ * Entry A (draft) Entry A (new published)
6889
+ * │ │
6890
+ * ├──(1)→ X ├──(1)→ X
6891
+ * ├──(2)→ Y ├──(2)→ Y
6892
+ * └──(3)→ Z └──(3)→ Z
6893
+ *
6894
+ * X's perspective: Y's perspective: Z's perspective:
6895
+ * └──(3)→ Entry A └──(3)→ Entry A └──(3)→ Entry A
6896
+ * (all relations appear last in order)
6897
+ * ```
6898
+ *
6899
+ * This module preserves the original ordering from both perspectives by:
6900
+ * 1. Capturing the relation order before the entry state changes
6901
+ * 2. Restoring this order after the new relations are created
6902
+ *
6903
+ * @param uid - The unique identifier of the content type being processed
6904
+ * @param context - Object containing arrays of old and new entry versions
6905
+ * @returns Array of objects containing join table metadata and relations to be updated
6906
+ */ const load = async (uid, { oldVersions })=>{
6907
+ const relationsToUpdate = [];
6908
+ await strapi.db.transaction(async ({ trx })=>{
6909
+ const contentTypes = Object.values(strapi.contentTypes);
6910
+ const components = Object.values(strapi.components);
6911
+ for (const model of [
6912
+ ...contentTypes,
6913
+ ...components
6914
+ ]){
6915
+ const dbModel = strapi.db.metadata.get(model.uid);
6916
+ for (const attribute of Object.values(dbModel.attributes)){
6917
+ // Skip if not a bidirectional relation targeting our content type
6918
+ if (attribute.type !== 'relation' || attribute.target !== uid || !(attribute.inversedBy || attribute.mappedBy)) {
6919
+ continue;
6920
+ }
6921
+ // If it's a self referencing relation, there is no need to sync any relation
6922
+ // The order will already be handled as both sides are inside the same content type
6923
+ if (model.uid === uid) {
6924
+ continue;
6925
+ }
6926
+ const joinTable = attribute.joinTable;
6927
+ if (!joinTable) {
6928
+ continue;
6929
+ }
6930
+ const { name: targetColumnName } = joinTable.inverseJoinColumn;
6931
+ // Load all relations that need their order preserved
6932
+ const oldEntryIds = oldVersions.map((entry)=>entry.id);
6933
+ const existingRelations = await strapi.db.getConnection().select('*').from(joinTable.name).whereIn(targetColumnName, oldEntryIds).transacting(trx);
6934
+ if (existingRelations.length > 0) {
6935
+ relationsToUpdate.push({
6936
+ joinTable,
6937
+ relations: existingRelations
6938
+ });
6939
+ }
6940
+ }
6941
+ }
6942
+ });
6943
+ return relationsToUpdate;
6944
+ };
6945
+ /**
6946
+ * Synchronizes the order of bidirectional relations after content entries have changed state.
6947
+ *
6948
+ * When entries change state (e.g., draft → published), their IDs change and all relations are recreated.
6949
+ * While the order of relations from the entry's perspective is maintained (as they're created in order),
6950
+ * the inverse relations (from related entries' perspective) would all appear last in order since they're new.
6951
+ *
6952
+ * Example:
6953
+ * ```
6954
+ * Before publish:
6955
+ * Article(id:1) →(order:1)→ Category(id:5)
6956
+ * Category(id:5) →(order:3)→ Article(id:1)
6957
+ *
6958
+ * After publish (without sync):
6959
+ * Article(id:2) →(order:1)→ Category(id:5) [order preserved]
6960
+ * Category(id:5) →(order:99)→ Article(id:2) [order lost - appears last]
6961
+ *
6962
+ * After sync:
6963
+ * Article(id:2) →(order:1)→ Category(id:5) [order preserved]
6964
+ * Category(id:5) →(order:3)→ Article(id:2) [order restored]
6965
+ * ```
6966
+ *
6967
+ * @param oldEntries - Array of previous entry versions with their IDs and locales
6968
+ * @param newEntries - Array of new entry versions with their IDs and locales
6969
+ * @param existingRelations - Array of join table data containing the relations to be updated
6970
+ */ const sync = async (oldEntries, newEntries, existingRelations)=>{
6971
+ // Group new entries by locale for easier lookup
6972
+ const newEntriesByLocale = keyBy('locale', newEntries);
6973
+ // Create a mapping of old entry IDs to new entry IDs based on locale
6974
+ const entryIdMapping = oldEntries.reduce((acc, oldEntry)=>{
6975
+ const newEntry = newEntriesByLocale[oldEntry.locale];
6976
+ if (!newEntry) return acc;
6977
+ acc[oldEntry.id] = newEntry.id;
6978
+ return acc;
6979
+ }, {});
6980
+ await strapi.db.transaction(async ({ trx })=>{
6981
+ for (const { joinTable, relations } of existingRelations){
6982
+ const sourceColumn = joinTable.inverseJoinColumn.name;
6983
+ const targetColumn = joinTable.joinColumn.name;
6984
+ const orderColumn = joinTable.orderColumnName;
6985
+ // Failsafe in case those don't exist
6986
+ if (!sourceColumn || !targetColumn || !orderColumn) {
6987
+ continue;
6988
+ }
6989
+ // Update order values for each relation
6990
+ // TODO: Find a way to batch it more efficiently
6991
+ await async.map(relations, (relation)=>{
6992
+ const { [sourceColumn]: oldSourceId, [targetColumn]: targetId, [orderColumn]: originalOrder } = relation;
6993
+ // Update the order column for the new relation entry
6994
+ return trx.from(joinTable.name).where(sourceColumn, entryIdMapping[oldSourceId]).where(targetColumn, targetId).update({
6995
+ [orderColumn]: originalOrder
6996
+ });
6997
+ });
6998
+ }
6999
+ });
7000
+ };
7001
+
6856
7002
  const textNodeValidator = yup$1.object().shape({
6857
7003
  type: yup$1.string().equals([
6858
7004
  'text'
@@ -7881,7 +8027,11 @@ const createContentTypeRepository = (uid, validator = entityValidator)=>{
7881
8027
  })
7882
8028
  ]);
7883
8029
  // Load any unidirectional relation targetting the old published entries
7884
- const relationsToSync = await load(uid, {
8030
+ const relationsToSync = await load$1(uid, {
8031
+ newVersions: draftsToPublish,
8032
+ oldVersions: oldPublishedVersions
8033
+ });
8034
+ const bidirectionalRelationsToSync = await load(uid, {
7885
8035
  newVersions: draftsToPublish,
7886
8036
  oldVersions: oldPublishedVersions
7887
8037
  });
@@ -7890,10 +8040,14 @@ const createContentTypeRepository = (uid, validator = entityValidator)=>{
7890
8040
  // Transform draft entry data and create published versions
7891
8041
  const publishedEntries = await async.map(draftsToPublish, (draft)=>entries.publish(draft, queryParams));
7892
8042
  // Sync unidirectional relations with the new published entries
7893
- await sync([
8043
+ await sync$1([
7894
8044
  ...oldPublishedVersions,
7895
8045
  ...draftsToPublish
7896
8046
  ], publishedEntries, relationsToSync);
8047
+ await sync([
8048
+ ...oldPublishedVersions,
8049
+ ...draftsToPublish
8050
+ ], publishedEntries, bidirectionalRelationsToSync);
7897
8051
  publishedEntries.forEach(emitEvent('entry.publish'));
7898
8052
  return {
7899
8053
  documentId,
@@ -7951,7 +8105,11 @@ const createContentTypeRepository = (uid, validator = entityValidator)=>{
7951
8105
  })
7952
8106
  ]);
7953
8107
  // Load any unidirectional relation targeting the old drafts
7954
- const relationsToSync = await load(uid, {
8108
+ const relationsToSync = await load$1(uid, {
8109
+ newVersions: versionsToDraft,
8110
+ oldVersions: oldDrafts
8111
+ });
8112
+ const bidirectionalRelationsToSync = await load(uid, {
7955
8113
  newVersions: versionsToDraft,
7956
8114
  oldVersions: oldDrafts
7957
8115
  });
@@ -7960,10 +8118,14 @@ const createContentTypeRepository = (uid, validator = entityValidator)=>{
7960
8118
  // Transform published entry data and create draft versions
7961
8119
  const draftEntries = await async.map(versionsToDraft, (entry)=>entries.discardDraft(entry, queryParams));
7962
8120
  // Sync unidirectional relations with the new draft entries
7963
- await sync([
8121
+ await sync$1([
7964
8122
  ...oldDrafts,
7965
8123
  ...versionsToDraft
7966
8124
  ], draftEntries, relationsToSync);
8125
+ await sync([
8126
+ ...oldDrafts,
8127
+ ...versionsToDraft
8128
+ ], draftEntries, bidirectionalRelationsToSync);
7967
8129
  draftEntries.forEach(emitEvent('entry.draft-discard'));
7968
8130
  return {
7969
8131
  documentId,