@strapi/database 5.50.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.
@@ -1,4 +1,4 @@
1
- import { castArray, pick, has, isNull, isEmpty, compact, differenceWith, isEqual, uniqBy, difference, map, isNil, isPlainObject, isArray, isString, isNumber, isUndefined, isObject, uniqWith, isInteger } from 'lodash/fp';
1
+ import { castArray, pick, has, isNull, isEmpty, compact, differenceWith, uniqBy, difference, map, isNil, isPlainObject, isArray, isString, isNumber, isUndefined, isObject, uniqWith, isEqual, isInteger } from 'lodash/fp';
2
2
  import { isScalarAttribute, isRelationalAttribute } from '../utils/types.mjs';
3
3
  import { createField } from '../fields/index.mjs';
4
4
  import createQueryBuilder from '../query/query-builder.mjs';
@@ -900,7 +900,126 @@ const createEntityManager = (db)=>{
900
900
  if (isPartialUpdate) {
901
901
  if (isAnyToOne(attribute)) ;
902
902
  relIdsToaddOrMove = toIds(cleanRelationData.connect);
903
- const relIdsToDelete = toIds(differenceWith(isEqual, cleanRelationData.disconnect, cleanRelationData.connect ?? []));
903
+ // Use id-only comparison so a disconnect item whose id also appears in
904
+ // the connect array is correctly excluded from deletion (deep-equality
905
+ // fails because connect items carry extra fields like `position`).
906
+ const relIdsToDelete = toIds(differenceWith((a, b)=>a.id === b.id, cleanRelationData.disconnect, cleanRelationData.connect ?? []));
907
+ // When a connect item's position.before/after references an id that is
908
+ // about to be deleted (relIdsToDelete), the referenced row won't exist by
909
+ // the time the adjacentRelations query runs below, causing sortConnectArray
910
+ // to throw. Rewrite such a position to point at the nearest surviving
911
+ // neighbor in the relation's current order, so the item lands where the
912
+ // deleted relation used to be instead of always falling back to the end.
913
+ const idKey = (value)=>String(value);
914
+ const deletedIds = new Set(relIdsToDelete.map(idKey));
915
+ let resolvedConnect = cleanRelationData.connect ?? [];
916
+ if (hasOrderColumn(attribute) && !isEmpty(relIdsToDelete) && resolvedConnect.some((item)=>{
917
+ const adjacentId = item.position?.before ?? item.position?.after;
918
+ return adjacentId != null && deletedIds.has(idKey(adjacentId));
919
+ })) {
920
+ const currentOrder = await this.createQueryBuilder(joinTable.name).select([
921
+ inverseJoinColumn.name,
922
+ orderColumnName
923
+ ]).where({
924
+ [joinColumn.name]: id
925
+ }).where(joinTable.on || {}).orderBy(orderColumnName).transacting(trx).execute();
926
+ const orderedIds = currentOrder.map((rel)=>rel[inverseJoinColumn.name]);
927
+ const orderedIdKeys = orderedIds.map(idKey);
928
+ const connectIds = new Set(resolvedConnect.map((item)=>idKey(item.id)));
929
+ const deletedIdsInCurrentOrder = new Set(orderedIds.map(idKey).filter((orderedId)=>deletedIds.has(orderedId)));
930
+ // A neighbor is only a valid fallback target if it survives the delete
931
+ // and isn't being moved by this connect payload.
932
+ const findSurvivingNeighbor = (targetId, direction)=>{
933
+ let index = orderedIdKeys.indexOf(idKey(targetId));
934
+ while(index !== -1){
935
+ index += direction;
936
+ const candidate = orderedIds[index];
937
+ if (candidate === undefined) {
938
+ return undefined;
939
+ }
940
+ const candidateKey = idKey(candidate);
941
+ if (!deletedIds.has(candidateKey) && !connectIds.has(candidateKey)) {
942
+ return candidate;
943
+ }
944
+ }
945
+ return undefined;
946
+ };
947
+ const positionByConnectId = new Map();
948
+ const connectGroups = new Map();
949
+ const getConnectGroup = (targetId)=>{
950
+ const targetKey = idKey(targetId);
951
+ let group = connectGroups.get(targetKey);
952
+ if (!group) {
953
+ group = {
954
+ targetId,
955
+ before: [],
956
+ after: []
957
+ };
958
+ connectGroups.set(targetKey, group);
959
+ }
960
+ return group;
961
+ };
962
+ resolvedConnect.forEach((item)=>{
963
+ const { before, after } = item.position ?? {};
964
+ const adjacentId = before ?? after;
965
+ if (adjacentId == null || !deletedIds.has(idKey(adjacentId)) || !deletedIdsInCurrentOrder.has(idKey(adjacentId))) {
966
+ return;
967
+ }
968
+ const group = getConnectGroup(adjacentId);
969
+ if (before) {
970
+ group.before.push(item);
971
+ } else {
972
+ group.after.push(item);
973
+ }
974
+ });
975
+ connectGroups.forEach(({ targetId, before, after })=>{
976
+ const previousNeighbor = findSurvivingNeighbor(targetId, -1);
977
+ const nextNeighbor = findSurvivingNeighbor(targetId, 1);
978
+ let previousPositionId = previousNeighbor;
979
+ before.forEach((item)=>{
980
+ if (previousPositionId !== undefined) {
981
+ positionByConnectId.set(item.id, {
982
+ after: previousPositionId
983
+ });
984
+ } else if (nextNeighbor !== undefined) {
985
+ positionByConnectId.set(item.id, {
986
+ before: nextNeighbor
987
+ });
988
+ } else {
989
+ positionByConnectId.set(item.id, {
990
+ start: true
991
+ });
992
+ }
993
+ previousPositionId = item.id;
994
+ });
995
+ after.forEach((item)=>{
996
+ if (previousPositionId !== undefined) {
997
+ positionByConnectId.set(item.id, {
998
+ after: previousPositionId
999
+ });
1000
+ } else if (nextNeighbor !== undefined) {
1001
+ positionByConnectId.set(item.id, {
1002
+ before: nextNeighbor
1003
+ });
1004
+ } else {
1005
+ positionByConnectId.set(item.id, {
1006
+ start: true
1007
+ });
1008
+ }
1009
+ previousPositionId = item.id;
1010
+ });
1011
+ });
1012
+ resolvedConnect = resolvedConnect.map((item)=>{
1013
+ const position = positionByConnectId.get(item.id);
1014
+ if (position) {
1015
+ return {
1016
+ ...item,
1017
+ position
1018
+ };
1019
+ }
1020
+ return item;
1021
+ });
1022
+ }
904
1023
  if (!isEmpty(relIdsToDelete)) {
905
1024
  await deleteRelations({
906
1025
  id,
@@ -937,7 +1056,7 @@ const createEntityManager = (db)=>{
937
1056
  {
938
1057
  [joinColumn.name]: id,
939
1058
  [inverseJoinColumn.name]: {
940
- $in: compact(cleanRelationData.connect?.map((r)=>r.position?.after || r.position?.before))
1059
+ $in: compact(resolvedConnect.map((r)=>r.position?.after || r.position?.before))
941
1060
  }
942
1061
  },
943
1062
  {
@@ -948,7 +1067,7 @@ const createEntityManager = (db)=>{
948
1067
  }
949
1068
  ]
950
1069
  }).where(joinTable.on || {}).transacting(trx).execute();
951
- const orderMap = relationsOrderer(adjacentRelations, inverseJoinColumn.name, joinTable.orderColumnName, cleanRelationData.options?.strict).connect(cleanRelationData.connect ?? []).getOrderMap();
1070
+ const orderMap = relationsOrderer(adjacentRelations, inverseJoinColumn.name, joinTable.orderColumnName, cleanRelationData.options?.strict).connect(resolvedConnect).getOrderMap();
952
1071
  insert.forEach((row)=>{
953
1072
  row[orderColumnName] = orderMap[row[inverseJoinColumn.name]];
954
1073
  });