@strapi/database 5.2.0 → 5.4.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/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from "path";
2
2
  import fse from "fs-extra";
3
3
  import createDebug from "debug";
4
- import _, { isNil, castArray, prop, omit, isInteger, snakeCase, partition, sumBy, cloneDeep, toString, toNumber, isString as isString$1, padCharsEnd, isArray, isPlainObject, isFinite, groupBy, pipe, mapValues, map, isEmpty, maxBy, pick, has, uniqBy, isNull, differenceWith, isEqual, compact, difference, isObject, isNumber as isNumber$1, isUndefined, uniqWith } from "lodash/fp";
4
+ import _, { isNil, castArray, prop, omit, isInteger, snakeCase, partition, sumBy, cloneDeep, toString, toNumber, isString as isString$1, padCharsEnd, isArray, isPlainObject, isFinite, curry, groupBy, pipe, mapValues, map, isEmpty, maxBy, pick, has, uniqBy, isNull, compact, differenceWith, isEqual, difference, isObject, isNumber as isNumber$1, isUndefined, uniqWith } from "lodash/fp";
5
5
  import crypto from "crypto";
6
6
  import crypto$1 from "node:crypto";
7
7
  import * as dateFns from "date-fns";
@@ -4878,6 +4878,25 @@ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (rows, {
4878
4878
  await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).transacting(trx).execute();
4879
4879
  }
4880
4880
  };
4881
+ const encodePolymorphicId = (id, __type) => {
4882
+ return `${id}:::${__type}`;
4883
+ };
4884
+ const encodePolymorphicRelation = curry(({ idColumn, typeColumn }, relation) => {
4885
+ const newRelation = {
4886
+ ...relation,
4887
+ [idColumn]: encodePolymorphicId(relation[idColumn], relation[typeColumn])
4888
+ };
4889
+ if (relation.position) {
4890
+ const { before, after } = relation.position;
4891
+ const __type = relation.position.__type || relation.__type;
4892
+ newRelation.position = { ...relation.position };
4893
+ if (before)
4894
+ newRelation.position.before = encodePolymorphicId(before, __type);
4895
+ if (after)
4896
+ newRelation.position.after = encodePolymorphicId(after, __type);
4897
+ }
4898
+ return newRelation;
4899
+ });
4881
4900
  const getDocumentSiblingIdsQuery = (tableName, id) => {
4882
4901
  const models = Array.from(strapi.db.metadata.values());
4883
4902
  const isContentType = models.find((model) => {
@@ -5064,7 +5083,10 @@ const sortConnectArray = (connectArr, initialArr = [], strictSort = true) => {
5064
5083
  if (!adjacentRelId || !relationInInitialArray[adjacentRelId] && !mapper[adjacentRelId]) {
5065
5084
  needsSorting = true;
5066
5085
  }
5067
- if (mapper[relation.id]) {
5086
+ const existingRelation = mapper[relation.id];
5087
+ const hasNoComponent = existingRelation && !("__component" in existingRelation);
5088
+ const hasSameComponent = existingRelation && existingRelation.__component === relation.__component;
5089
+ if (existingRelation && (hasNoComponent || hasSameComponent)) {
5068
5090
  throw new InvalidRelationError(
5069
5091
  `The relation with id ${relation.id} is already connected. You cannot connect the same relation twice.`
5070
5092
  );
@@ -5205,7 +5227,7 @@ const toId = (value) => {
5205
5227
  if (isValidId(value)) {
5206
5228
  return value;
5207
5229
  }
5208
- throw new Error(`Invalid id, expected a string or integer, got ${value}`);
5230
+ throw new Error(`Invalid id, expected a string or integer, got ${JSON.stringify(value)}`);
5209
5231
  };
5210
5232
  const toIds = (value) => castArray(value || []).map(toId);
5211
5233
  const isValidId = (value) => isString$1(value) || isInteger(value);
@@ -5240,7 +5262,8 @@ const toAssocs = (data) => {
5240
5262
  connect: toIdArray(data?.connect).map((elm) => ({
5241
5263
  id: elm.id,
5242
5264
  position: elm.position ? elm.position : { end: true },
5243
- __pivot: elm.__pivot ?? {}
5265
+ __pivot: elm.__pivot ?? {},
5266
+ __type: elm.__type
5244
5267
  })),
5245
5268
  disconnect: toIdArray(data?.disconnect)
5246
5269
  };
@@ -5515,23 +5538,33 @@ const createEntityManager = (db) => {
5515
5538
  const { joinTable } = attribute;
5516
5539
  const { joinColumn, morphColumn } = joinTable;
5517
5540
  const { idColumn, typeColumn, typeField = "__type" } = morphColumn;
5518
- if (isEmpty(cleanRelationData.set)) {
5541
+ if (isEmpty(cleanRelationData.set) && isEmpty(cleanRelationData.connect)) {
5519
5542
  continue;
5520
5543
  }
5521
- const rows = cleanRelationData.set?.map((data2, idx) => ({
5544
+ const dataset = cleanRelationData.set || cleanRelationData.connect || [];
5545
+ const rows = dataset.map((data2, idx) => ({
5522
5546
  [joinColumn.name]: id,
5523
5547
  [idColumn.name]: data2.id,
5524
5548
  [typeColumn.name]: data2[typeField],
5525
5549
  ..."on" in joinTable && joinTable.on || {},
5526
5550
  ...data2.__pivot || {},
5527
5551
  order: idx + 1
5528
- })) ?? [];
5529
- await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
5530
- uid,
5531
- attributeName,
5532
- joinTable,
5533
- db,
5534
- transaction: trx
5552
+ }));
5553
+ const orderMap = relationsOrderer(
5554
+ [],
5555
+ morphColumn.idColumn.name,
5556
+ "order",
5557
+ true
5558
+ // Always make a strict connect when inserting
5559
+ ).connect(
5560
+ // Merge id & __type to get a single id key
5561
+ dataset.map(encodePolymorphicRelation({ idColumn: "id", typeColumn: "__type" }))
5562
+ ).get().reduce((acc, rel, idx) => ({ ...acc, [rel.id]: idx }), {});
5563
+ rows.forEach((row) => {
5564
+ const rowId = row[morphColumn.idColumn.name];
5565
+ const rowType = row[morphColumn.typeColumn.name];
5566
+ const encodedId = encodePolymorphicId(rowId, rowType);
5567
+ row.order = orderMap[encodedId];
5535
5568
  });
5536
5569
  await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5537
5570
  continue;
@@ -5631,25 +5664,67 @@ const createEntityManager = (db) => {
5631
5664
  const { joinTable } = targetAttribute;
5632
5665
  const { joinColumn, morphColumn } = joinTable;
5633
5666
  const { idColumn, typeColumn } = morphColumn;
5667
+ const hasSet = !isEmpty(cleanRelationData.set);
5668
+ const hasConnect = !isEmpty(cleanRelationData.connect);
5669
+ const hasDisconnect = !isEmpty(cleanRelationData.disconnect);
5670
+ if (!hasSet && (hasConnect || hasDisconnect)) {
5671
+ const idsToDelete = [
5672
+ ...cleanRelationData.disconnect || [],
5673
+ ...cleanRelationData.connect || []
5674
+ ];
5675
+ if (!isEmpty(idsToDelete)) {
5676
+ const where = {
5677
+ $or: idsToDelete.map((item) => {
5678
+ return {
5679
+ [idColumn.name]: id,
5680
+ [typeColumn.name]: uid,
5681
+ [joinColumn.name]: item.id,
5682
+ ...joinTable.on || {},
5683
+ field: attributeName
5684
+ };
5685
+ })
5686
+ };
5687
+ await this.createQueryBuilder(joinTable.name).delete().where(where).transacting(trx).execute();
5688
+ }
5689
+ if (hasConnect) {
5690
+ const start = await this.createQueryBuilder(joinTable.name).where({
5691
+ [idColumn.name]: id,
5692
+ [typeColumn.name]: uid,
5693
+ ...joinTable.on || {},
5694
+ ...data.__pivot || {}
5695
+ }).max("order").first().transacting(trx).execute();
5696
+ const startOrder = start?.max || 0;
5697
+ const rows = (cleanRelationData.connect ?? []).map((data2, idx) => ({
5698
+ [joinColumn.name]: data2.id,
5699
+ [idColumn.name]: id,
5700
+ [typeColumn.name]: uid,
5701
+ ...joinTable.on || {},
5702
+ ...data2.__pivot || {},
5703
+ order: startOrder + idx + 1,
5704
+ field: attributeName
5705
+ }));
5706
+ await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5707
+ }
5708
+ continue;
5709
+ }
5634
5710
  await this.createQueryBuilder(joinTable.name).delete().where({
5635
5711
  [idColumn.name]: id,
5636
5712
  [typeColumn.name]: uid,
5637
5713
  ...joinTable.on || {},
5638
5714
  field: attributeName
5639
5715
  }).transacting(trx).execute();
5640
- if (isEmpty(cleanRelationData.set)) {
5641
- continue;
5716
+ if (hasSet) {
5717
+ const rows = (cleanRelationData.set ?? []).map((data2, idx) => ({
5718
+ [joinColumn.name]: data2.id,
5719
+ [idColumn.name]: id,
5720
+ [typeColumn.name]: uid,
5721
+ ...joinTable.on || {},
5722
+ ...data2.__pivot || {},
5723
+ order: idx + 1,
5724
+ field: attributeName
5725
+ }));
5726
+ await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5642
5727
  }
5643
- const rows = cleanRelationData.set?.map((data2, idx) => ({
5644
- [joinColumn.name]: data2.id,
5645
- [idColumn.name]: id,
5646
- [typeColumn.name]: uid,
5647
- ...joinTable.on || {},
5648
- ...data2.__pivot || {},
5649
- order: idx + 1,
5650
- field: attributeName
5651
- }));
5652
- await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5653
5728
  }
5654
5729
  continue;
5655
5730
  }
@@ -5660,29 +5735,129 @@ const createEntityManager = (db) => {
5660
5735
  const { joinTable } = attribute;
5661
5736
  const { joinColumn, morphColumn } = joinTable;
5662
5737
  const { idColumn, typeColumn, typeField = "__type" } = morphColumn;
5663
- await this.createQueryBuilder(joinTable.name).delete().where({
5664
- [joinColumn.name]: id,
5665
- ...joinTable.on || {}
5666
- }).transacting(trx).execute();
5667
- if (isEmpty(cleanRelationData.set)) {
5738
+ const hasSet = !isEmpty(cleanRelationData.set);
5739
+ const hasConnect = !isEmpty(cleanRelationData.connect);
5740
+ const hasDisconnect = !isEmpty(cleanRelationData.disconnect);
5741
+ if (!hasSet && (hasConnect || hasDisconnect)) {
5742
+ const idsToDelete = [
5743
+ ...cleanRelationData.disconnect || [],
5744
+ ...cleanRelationData.connect || []
5745
+ ];
5746
+ const rowsToDelete = [
5747
+ ...(cleanRelationData.disconnect ?? []).map((data2, idx) => ({
5748
+ [joinColumn.name]: id,
5749
+ [idColumn.name]: data2.id,
5750
+ [typeColumn.name]: data2[typeField],
5751
+ ..."on" in joinTable && joinTable.on || {},
5752
+ ...data2.__pivot || {},
5753
+ order: idx + 1
5754
+ })),
5755
+ ...(cleanRelationData.connect ?? []).map((data2, idx) => ({
5756
+ [joinColumn.name]: id,
5757
+ [idColumn.name]: data2.id,
5758
+ // @ts-expect-error TODO
5759
+ [typeColumn.name]: data2[typeField],
5760
+ ..."on" in joinTable && joinTable.on || {},
5761
+ ...data2.__pivot || {},
5762
+ order: idx + 1
5763
+ }))
5764
+ ];
5765
+ const adjacentRelations = await this.createQueryBuilder(joinTable.name).where({
5766
+ $or: [
5767
+ {
5768
+ [joinColumn.name]: id,
5769
+ [idColumn.name]: {
5770
+ $in: compact(
5771
+ cleanRelationData.connect?.map(
5772
+ (r) => r.position?.after || r.position?.before
5773
+ )
5774
+ )
5775
+ }
5776
+ },
5777
+ {
5778
+ [joinColumn.name]: id,
5779
+ order: this.createQueryBuilder(joinTable.name).max("order").where({ [joinColumn.name]: id }).where(joinTable.on || {}).transacting(trx).getKnexQuery()
5780
+ }
5781
+ ]
5782
+ }).where(joinTable.on || {}).transacting(trx).execute();
5783
+ if (!isEmpty(idsToDelete)) {
5784
+ const where = {
5785
+ $or: idsToDelete.map((item) => {
5786
+ return {
5787
+ [idColumn.name]: item.id,
5788
+ [typeColumn.name]: item[typeField],
5789
+ [joinColumn.name]: id,
5790
+ ...joinTable.on || {}
5791
+ };
5792
+ })
5793
+ };
5794
+ await this.createQueryBuilder(joinTable.name).delete().where(where).transacting(trx).execute();
5795
+ await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rowsToDelete, {
5796
+ uid,
5797
+ attributeName,
5798
+ joinTable,
5799
+ db,
5800
+ transaction: trx
5801
+ });
5802
+ }
5803
+ if (hasConnect) {
5804
+ const dataset = cleanRelationData.connect || [];
5805
+ const rows = dataset.map((data2) => ({
5806
+ [joinColumn.name]: id,
5807
+ [idColumn.name]: data2.id,
5808
+ [typeColumn.name]: data2[typeField],
5809
+ ...joinTable.on || {},
5810
+ ...data2.__pivot || {},
5811
+ field: attributeName
5812
+ }));
5813
+ const orderMap = relationsOrderer(
5814
+ // Merge id & __type to get a single id key
5815
+ adjacentRelations.map(
5816
+ encodePolymorphicRelation({
5817
+ idColumn: idColumn.name,
5818
+ typeColumn: typeColumn.name
5819
+ })
5820
+ ),
5821
+ idColumn.name,
5822
+ "order",
5823
+ cleanRelationData.options?.strict
5824
+ ).connect(
5825
+ // Merge id & __type to get a single id key
5826
+ dataset.map(encodePolymorphicRelation({ idColumn: "id", typeColumn: "__type" }))
5827
+ ).getOrderMap();
5828
+ rows.forEach((row) => {
5829
+ const rowId = row[idColumn.name];
5830
+ const rowType = row[typeColumn.name];
5831
+ const encodedId = encodePolymorphicId(rowId, rowType);
5832
+ row.order = orderMap[encodedId];
5833
+ });
5834
+ await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5835
+ }
5668
5836
  continue;
5669
5837
  }
5670
- const rows = (cleanRelationData.set ?? []).map((data2, idx) => ({
5671
- [joinColumn.name]: id,
5672
- [idColumn.name]: data2.id,
5673
- [typeColumn.name]: data2[typeField],
5674
- ...joinTable.on || {},
5675
- ...data2.__pivot || {},
5676
- order: idx + 1
5677
- }));
5678
- await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
5679
- uid,
5680
- attributeName,
5681
- joinTable,
5682
- db,
5683
- transaction: trx
5684
- });
5685
- await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5838
+ if (hasSet) {
5839
+ await this.createQueryBuilder(joinTable.name).delete().where({
5840
+ [joinColumn.name]: id,
5841
+ ...joinTable.on || {}
5842
+ }).transacting(trx).execute();
5843
+ const rows = (cleanRelationData.set ?? []).map((data2, idx) => ({
5844
+ [joinColumn.name]: id,
5845
+ [idColumn.name]: data2.id,
5846
+ [typeColumn.name]: data2[typeField],
5847
+ field: attributeName,
5848
+ ...joinTable.on || {},
5849
+ ...data2.__pivot || {},
5850
+ order: idx + 1
5851
+ }));
5852
+ await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
5853
+ uid,
5854
+ attributeName,
5855
+ joinTable,
5856
+ db,
5857
+ transaction: trx
5858
+ });
5859
+ await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
5860
+ }
5686
5861
  continue;
5687
5862
  }
5688
5863
  if ("joinColumn" in attribute && attribute.joinColumn && attribute.owner) {
@@ -6107,7 +6282,7 @@ const QUERIES = {
6107
6282
  FROM :tableName:
6108
6283
  LEFT JOIN :joinTableName: ON :tableName:.id = :joinTableName:.:joinColumn:
6109
6284
  WHERE document_id IS NULL
6110
- GROUP BY :tableName:.id, :joinColumn:
6285
+ GROUP BY :tableName:.id, :joinTableName:.:joinColumn:
6111
6286
  LIMIT 1;
6112
6287
  `,
6113
6288
  params
@@ -6121,7 +6296,7 @@ const QUERIES = {
6121
6296
  FROM :tableName:
6122
6297
  LEFT JOIN :joinTableName: ON :tableName:.id = :joinTableName:.:joinColumn:
6123
6298
  WHERE document_id IS NULL
6124
- GROUP BY :tableName:.id, :joinColumn:
6299
+ GROUP BY :tableName:.id, :joinTableName:.:joinColumn:
6125
6300
  LIMIT 1;
6126
6301
  `,
6127
6302
  params
@@ -6135,7 +6310,7 @@ const QUERIES = {
6135
6310
  FROM :tableName:
6136
6311
  LEFT JOIN :joinTableName: ON :tableName:.id = :joinTableName:.:joinColumn:
6137
6312
  WHERE document_id IS NULL
6138
- GROUP BY :joinColumn:
6313
+ GROUP BY :joinTableName:.:joinColumn:
6139
6314
  LIMIT 1;
6140
6315
  `,
6141
6316
  params