@strapi/database 4.4.0-beta.2 → 4.4.0-beta.4

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,19 +1,32 @@
1
1
  'use strict';
2
2
 
3
- const _ = require('lodash/fp');
4
- const types = require('./types');
5
- const { createField } = require('./fields');
6
- const { createQueryBuilder } = require('./query');
3
+ const {
4
+ isUndefined,
5
+ castArray,
6
+ isNil,
7
+ has,
8
+ isString,
9
+ isInteger,
10
+ pick,
11
+ isPlainObject,
12
+ isEmpty,
13
+ isArray,
14
+ isNull,
15
+ } = require('lodash/fp');
16
+ const types = require('../types');
17
+ const { createField } = require('../fields');
18
+ const { createQueryBuilder } = require('../query');
7
19
  const { createRepository } = require('./entity-repository');
8
- const { isBidirectional, isOneToAny } = require('./metadata/relations');
20
+ const { isBidirectional, isOneToAny } = require('../metadata/relations');
21
+ const { deleteRelatedMorphOneRelationsAfterMorphToManyUpdate } = require('./morph-relations');
9
22
 
10
23
  const toId = (value) => value.id || value;
11
- const toIds = (value) => _.castArray(value || []).map(toId);
24
+ const toIds = (value) => castArray(value || []).map(toId);
12
25
 
13
- const isValidId = (value) => _.isString(value) || _.isInteger(value);
26
+ const isValidId = (value) => isString(value) || isInteger(value);
14
27
  const toAssocs = (data) => {
15
- return _.castArray(data)
16
- .filter((datum) => !_.isNil(datum))
28
+ return castArray(data)
29
+ .filter((datum) => !isNil(datum))
17
30
  .map((datum) => {
18
31
  // if it is a string or an integer return an obj with id = to datum
19
32
  if (isValidId(datum)) {
@@ -21,7 +34,7 @@ const toAssocs = (data) => {
21
34
  }
22
35
 
23
36
  // if it is an object check it has at least a valid id
24
- if (!_.has('id', datum) || !isValidId(datum.id)) {
37
+ if (!has('id', datum) || !isValidId(datum.id)) {
25
38
  throw new Error(`Invalid id, expected a string or integer, got ${datum}`);
26
39
  }
27
40
 
@@ -40,8 +53,8 @@ const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
40
53
  if (types.isScalar(attribute.type)) {
41
54
  const field = createField(attribute);
42
55
 
43
- if (_.isUndefined(data[attributeName])) {
44
- if (!_.isUndefined(attribute.default) && withDefaults) {
56
+ if (isUndefined(data[attributeName])) {
57
+ if (!isUndefined(attribute.default) && withDefaults) {
45
58
  if (typeof attribute.default === 'function') {
46
59
  obj[attributeName] = attribute.default();
47
60
  } else {
@@ -66,11 +79,11 @@ const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
66
79
  const joinColumnName = attribute.joinColumn.name;
67
80
 
68
81
  // allow setting to null
69
- const attrValue = !_.isUndefined(data[attributeName])
82
+ const attrValue = !isUndefined(data[attributeName])
70
83
  ? data[attributeName]
71
84
  : data[joinColumnName];
72
85
 
73
- if (!_.isUndefined(attrValue)) {
86
+ if (!isUndefined(attrValue)) {
74
87
  obj[joinColumnName] = attrValue;
75
88
  }
76
89
 
@@ -91,8 +104,8 @@ const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
91
104
  continue;
92
105
  }
93
106
 
94
- if (!_.isUndefined(value)) {
95
- if (!_.has('id', value) || !_.has(typeField, value)) {
107
+ if (!isUndefined(value)) {
108
+ if (!has('id', value) || !has(typeField, value)) {
96
109
  throw new Error(`Expects properties ${typeField} an id to make a morph association`);
97
110
  }
98
111
 
@@ -137,7 +150,7 @@ const createEntityManager = (db) => {
137
150
  const states = await db.lifecycles.run('beforeCount', uid, { params });
138
151
 
139
152
  const res = await this.createQueryBuilder(uid)
140
- .init(_.pick(['_q', 'where', 'filters'], params))
153
+ .init(pick(['_q', 'where', 'filters'], params))
141
154
  .count()
142
155
  .first()
143
156
  .execute();
@@ -155,7 +168,7 @@ const createEntityManager = (db) => {
155
168
  const metadata = db.metadata.get(uid);
156
169
  const { data } = params;
157
170
 
158
- if (!_.isPlainObject(data)) {
171
+ if (!isPlainObject(data)) {
159
172
  throw new Error('Create expects a data object');
160
173
  }
161
174
 
@@ -187,7 +200,7 @@ const createEntityManager = (db) => {
187
200
  const metadata = db.metadata.get(uid);
188
201
  const { data } = params;
189
202
 
190
- if (!_.isArray(data)) {
203
+ if (!isArray(data)) {
191
204
  throw new Error('CreateMany expects data to be an array');
192
205
  }
193
206
 
@@ -195,7 +208,7 @@ const createEntityManager = (db) => {
195
208
  processData(metadata, datum, { withDefaults: true })
196
209
  );
197
210
 
198
- if (_.isEmpty(dataToInsert)) {
211
+ if (isEmpty(dataToInsert)) {
199
212
  throw new Error('Nothing to insert');
200
213
  }
201
214
 
@@ -214,11 +227,11 @@ const createEntityManager = (db) => {
214
227
  const metadata = db.metadata.get(uid);
215
228
  const { where, data } = params;
216
229
 
217
- if (!_.isPlainObject(data)) {
230
+ if (!isPlainObject(data)) {
218
231
  throw new Error('Update requires a data object');
219
232
  }
220
233
 
221
- if (_.isEmpty(where)) {
234
+ if (isEmpty(where)) {
222
235
  throw new Error('Update requires a where parameter');
223
236
  }
224
237
 
@@ -232,7 +245,7 @@ const createEntityManager = (db) => {
232
245
 
233
246
  const dataToUpdate = processData(metadata, data);
234
247
 
235
- if (!_.isEmpty(dataToUpdate)) {
248
+ if (!isEmpty(dataToUpdate)) {
236
249
  await this.createQueryBuilder(uid).where({ id }).update(dataToUpdate).execute();
237
250
  }
238
251
 
@@ -259,7 +272,7 @@ const createEntityManager = (db) => {
259
272
 
260
273
  const dataToUpdate = processData(metadata, data);
261
274
 
262
- if (_.isEmpty(dataToUpdate)) {
275
+ if (isEmpty(dataToUpdate)) {
263
276
  throw new Error('Update requires data');
264
277
  }
265
278
 
@@ -280,7 +293,7 @@ const createEntityManager = (db) => {
280
293
 
281
294
  const { where, select, populate } = params;
282
295
 
283
- if (_.isEmpty(where)) {
296
+ if (isEmpty(where)) {
284
297
  throw new Error('Delete requires a where parameter');
285
298
  }
286
299
 
@@ -336,7 +349,7 @@ const createEntityManager = (db) => {
336
349
  for (const attributeName of Object.keys(attributes)) {
337
350
  const attribute = attributes[attributeName];
338
351
 
339
- const isValidLink = _.has(attributeName, data) && !_.isNil(data[attributeName]);
352
+ const isValidLink = has(attributeName, data) && !isNil(data[attributeName]);
340
353
 
341
354
  if (attribute.type !== 'relation' || !isValidLink) {
342
355
  continue;
@@ -373,7 +386,7 @@ const createEntityManager = (db) => {
373
386
  };
374
387
  });
375
388
 
376
- if (_.isEmpty(rows)) {
389
+ if (isEmpty(rows)) {
377
390
  continue;
378
391
  }
379
392
 
@@ -398,10 +411,18 @@ const createEntityManager = (db) => {
398
411
  ...(data.__pivot || {}),
399
412
  }));
400
413
 
401
- if (_.isEmpty(rows)) {
414
+ if (isEmpty(rows)) {
402
415
  continue;
403
416
  }
404
417
 
418
+ // delete previous relations
419
+ await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
420
+ uid,
421
+ attributeName,
422
+ joinTable,
423
+ db,
424
+ });
425
+
405
426
  await this.createQueryBuilder(joinTable.name).insert(rows).execute();
406
427
 
407
428
  continue;
@@ -450,7 +471,7 @@ const createEntityManager = (db) => {
450
471
  if (isOneToAny(attribute) && isBidirectional(attribute)) {
451
472
  await this.createQueryBuilder(joinTable.name)
452
473
  .delete()
453
- .where({ [inverseJoinColumn.name]: _.castArray(data[attributeName]) })
474
+ .where({ [inverseJoinColumn.name]: castArray(data[attributeName]) })
454
475
  .where(joinTable.on || {})
455
476
  .execute();
456
477
  }
@@ -490,7 +511,7 @@ const createEntityManager = (db) => {
490
511
  for (const attributeName of Object.keys(attributes)) {
491
512
  const attribute = attributes[attributeName];
492
513
 
493
- if (attribute.type !== 'relation' || !_.has(attributeName, data)) {
514
+ if (attribute.type !== 'relation' || !has(attributeName, data)) {
494
515
  continue;
495
516
  }
496
517
 
@@ -503,12 +524,14 @@ const createEntityManager = (db) => {
503
524
  // set columns
504
525
  const { idColumn, typeColumn } = targetAttribute.morphColumn;
505
526
 
527
+ // update instead of deleting because the relation is directly on the entity table
528
+ // and not in a join table
506
529
  await this.createQueryBuilder(target)
507
530
  .update({ [idColumn.name]: null, [typeColumn.name]: null })
508
531
  .where({ [idColumn.name]: id, [typeColumn.name]: uid })
509
532
  .execute();
510
533
 
511
- if (!_.isNull(data[attributeName])) {
534
+ if (!isNull(data[attributeName])) {
512
535
  await this.createQueryBuilder(target)
513
536
  .update({ [idColumn.name]: id, [typeColumn.name]: uid })
514
537
  .where({ id: toId(data[attributeName]) })
@@ -540,7 +563,7 @@ const createEntityManager = (db) => {
540
563
  field: attributeName,
541
564
  }));
542
565
 
543
- if (_.isEmpty(rows)) {
566
+ if (isEmpty(rows)) {
544
567
  continue;
545
568
  }
546
569
 
@@ -577,10 +600,18 @@ const createEntityManager = (db) => {
577
600
  ...(data.__pivot || {}),
578
601
  }));
579
602
 
580
- if (_.isEmpty(rows)) {
603
+ if (isEmpty(rows)) {
581
604
  continue;
582
605
  }
583
606
 
607
+ // delete previous relations
608
+ await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
609
+ uid,
610
+ attributeName,
611
+ joinTable,
612
+ db,
613
+ });
614
+
584
615
  await this.createQueryBuilder(joinTable.name).insert(rows).execute();
585
616
 
586
617
  continue;
@@ -602,7 +633,7 @@ const createEntityManager = (db) => {
602
633
  .update({ [attribute.joinColumn.referencedColumn]: null })
603
634
  .execute();
604
635
 
605
- if (!_.isNull(data[attributeName])) {
636
+ if (!isNull(data[attributeName])) {
606
637
  await this.createQueryBuilder(target)
607
638
  // NOTE: works if it is an array or a single id
608
639
  .where({ id: data[attributeName] })
@@ -633,7 +664,7 @@ const createEntityManager = (db) => {
633
664
  .execute();
634
665
  }
635
666
 
636
- if (!_.isNull(data[attributeName])) {
667
+ if (!isNull(data[attributeName])) {
637
668
  const insert = toAssocs(data[attributeName]).map((data) => {
638
669
  return {
639
670
  [joinColumn.name]: id,
@@ -791,7 +822,7 @@ const createEntityManager = (db) => {
791
822
  async load(uid, entity, fields, params) {
792
823
  const { attributes } = db.metadata.get(uid);
793
824
 
794
- const fieldsArr = _.castArray(fields);
825
+ const fieldsArr = castArray(fields);
795
826
  fieldsArr.forEach((field) => {
796
827
  const attribute = attributes[field];
797
828
 
@@ -814,7 +845,7 @@ const createEntityManager = (db) => {
814
845
  }
815
846
 
816
847
  if (Array.isArray(fields)) {
817
- return _.pick(fields, entry);
848
+ return pick(fields, entry);
818
849
  }
819
850
 
820
851
  return entry[fields];
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const { groupBy, pipe, mapValues, map, isEmpty } = require('lodash/fp');
4
+ const { createQueryBuilder } = require('../query');
5
+
6
+ const getMorphToManyRowsLinkedToMorphOne = (rows, { uid, attributeName, typeColumn, db }) =>
7
+ rows.filter((row) => {
8
+ const relatedType = row[typeColumn.name];
9
+ const field = row.field;
10
+
11
+ const targetAttribute = db.metadata.get(relatedType).attributes[field];
12
+
13
+ // ensure targeted field is the right one + check if it is a morphOne
14
+ return (
15
+ targetAttribute?.target === uid &&
16
+ targetAttribute?.morphBy === attributeName &&
17
+ targetAttribute?.relation === 'morphOne'
18
+ );
19
+ });
20
+
21
+ const deleteRelatedMorphOneRelationsAfterMorphToManyUpdate = async (
22
+ rows,
23
+ { uid, attributeName, joinTable, db }
24
+ ) => {
25
+ const { morphColumn } = joinTable;
26
+ const { idColumn, typeColumn } = morphColumn;
27
+
28
+ const morphOneRows = getMorphToManyRowsLinkedToMorphOne(rows, {
29
+ uid,
30
+ attributeName,
31
+ typeColumn,
32
+ db,
33
+ });
34
+
35
+ const groupByType = groupBy(typeColumn.name);
36
+ const groupByField = groupBy('field');
37
+
38
+ const typeAndFieldIdsGrouped = pipe(groupByType, mapValues(groupByField))(morphOneRows);
39
+
40
+ const orWhere = [];
41
+
42
+ for (const [type, v] of Object.entries(typeAndFieldIdsGrouped)) {
43
+ for (const [field, arr] of Object.entries(v)) {
44
+ orWhere.push({
45
+ [typeColumn.name]: type,
46
+ field,
47
+ [idColumn.name]: { $in: map(idColumn.name, arr) },
48
+ });
49
+ }
50
+ }
51
+
52
+ if (!isEmpty(orWhere)) {
53
+ await createQueryBuilder(joinTable.name, db).delete().where({ $or: orWhere }).execute();
54
+ }
55
+ };
56
+
57
+ module.exports = {
58
+ deleteRelatedMorphOneRelationsAfterMorphToManyUpdate,
59
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/database",
3
- "version": "4.4.0-beta.2",
3
+ "version": "4.4.0-beta.4",
4
4
  "description": "Strapi's database layer",
5
5
  "homepage": "https://strapi.io",
6
6
  "bugs": {
@@ -42,5 +42,5 @@
42
42
  "node": ">=14.19.1 <=18.x.x",
43
43
  "npm": ">=6.0.0"
44
44
  },
45
- "gitHead": "baad89e67844d972ef53a6f1b0839a361bf968c0"
45
+ "gitHead": "8ad31453be3eda4e01eae027995e7e584892e688"
46
46
  }