@strapi/database 4.14.4 → 4.14.6

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.
Files changed (242) hide show
  1. package/README.md +3 -0
  2. package/dist/connection.d.ts +3 -0
  3. package/dist/connection.d.ts.map +1 -0
  4. package/dist/dialects/dialect.d.ts +27 -0
  5. package/dist/dialects/dialect.d.ts.map +1 -0
  6. package/dist/dialects/index.d.ts +5 -0
  7. package/dist/dialects/index.d.ts.map +1 -0
  8. package/dist/dialects/mysql/constants.d.ts +3 -0
  9. package/dist/dialects/mysql/constants.d.ts.map +1 -0
  10. package/dist/dialects/mysql/database-inspector.d.ts +12 -0
  11. package/dist/dialects/mysql/database-inspector.d.ts.map +1 -0
  12. package/dist/dialects/mysql/index.d.ts +20 -0
  13. package/dist/dialects/mysql/index.d.ts.map +1 -0
  14. package/dist/dialects/mysql/schema-inspector.d.ts +13 -0
  15. package/dist/dialects/mysql/schema-inspector.d.ts.map +1 -0
  16. package/dist/dialects/postgresql/index.d.ts +14 -0
  17. package/dist/dialects/postgresql/index.d.ts.map +1 -0
  18. package/dist/dialects/postgresql/schema-inspector.d.ts +14 -0
  19. package/dist/dialects/postgresql/schema-inspector.d.ts.map +1 -0
  20. package/dist/dialects/sqlite/index.d.ts +19 -0
  21. package/dist/dialects/sqlite/index.d.ts.map +1 -0
  22. package/dist/dialects/sqlite/schema-inspector.d.ts +13 -0
  23. package/dist/dialects/sqlite/schema-inspector.d.ts.map +1 -0
  24. package/dist/entity-manager/entity-repository.d.ts +4 -0
  25. package/dist/entity-manager/entity-repository.d.ts.map +1 -0
  26. package/dist/entity-manager/index.d.ts +5 -0
  27. package/dist/entity-manager/index.d.ts.map +1 -0
  28. package/dist/entity-manager/morph-relations.d.ts +13 -0
  29. package/dist/entity-manager/morph-relations.d.ts.map +1 -0
  30. package/dist/entity-manager/regular-relations.d.ts +83 -0
  31. package/dist/entity-manager/regular-relations.d.ts.map +1 -0
  32. package/dist/entity-manager/relations/cloning/regular-relations.d.ts +17 -0
  33. package/dist/entity-manager/relations/cloning/regular-relations.d.ts.map +1 -0
  34. package/dist/entity-manager/relations-orderer.d.ts +73 -0
  35. package/dist/entity-manager/relations-orderer.d.ts.map +1 -0
  36. package/dist/entity-manager/types.d.ts +97 -0
  37. package/dist/entity-manager/types.d.ts.map +1 -0
  38. package/dist/errors/database.d.ts +5 -0
  39. package/dist/errors/database.d.ts.map +1 -0
  40. package/dist/errors/index.d.ts +8 -0
  41. package/dist/errors/index.d.ts.map +1 -0
  42. package/dist/errors/invalid-date.d.ts +5 -0
  43. package/dist/errors/invalid-date.d.ts.map +1 -0
  44. package/dist/errors/invalid-datetime.d.ts +5 -0
  45. package/dist/errors/invalid-datetime.d.ts.map +1 -0
  46. package/dist/errors/invalid-relation.d.ts +5 -0
  47. package/dist/errors/invalid-relation.d.ts.map +1 -0
  48. package/dist/errors/invalid-time.d.ts +5 -0
  49. package/dist/errors/invalid-time.d.ts.map +1 -0
  50. package/dist/errors/not-null.d.ts +7 -0
  51. package/dist/errors/not-null.d.ts.map +1 -0
  52. package/dist/fields/biginteger.d.ts +4 -0
  53. package/dist/fields/biginteger.d.ts.map +1 -0
  54. package/dist/fields/boolean.d.ts +6 -0
  55. package/dist/fields/boolean.d.ts.map +1 -0
  56. package/dist/fields/date.d.ts +6 -0
  57. package/dist/fields/date.d.ts.map +1 -0
  58. package/dist/fields/datetime.d.ts +6 -0
  59. package/dist/fields/datetime.d.ts.map +1 -0
  60. package/dist/fields/field.d.ts +7 -0
  61. package/dist/fields/field.d.ts.map +1 -0
  62. package/dist/fields/index.d.ts +4 -0
  63. package/dist/fields/index.d.ts.map +1 -0
  64. package/dist/fields/json.d.ts +6 -0
  65. package/dist/fields/json.d.ts.map +1 -0
  66. package/dist/fields/number.d.ts +6 -0
  67. package/dist/fields/number.d.ts.map +1 -0
  68. package/dist/fields/shared/parsers.d.ts +4 -0
  69. package/dist/fields/shared/parsers.d.ts.map +1 -0
  70. package/dist/fields/string.d.ts +6 -0
  71. package/dist/fields/string.d.ts.map +1 -0
  72. package/dist/fields/time.d.ts +6 -0
  73. package/dist/fields/time.d.ts.map +1 -0
  74. package/dist/fields/timestamp.d.ts +6 -0
  75. package/dist/fields/timestamp.d.ts.map +1 -0
  76. package/dist/index.d.ts +46 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +6211 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/index.mjs +6179 -0
  81. package/dist/index.mjs.map +1 -0
  82. package/dist/lifecycles/index.d.ts +17 -0
  83. package/dist/lifecycles/index.d.ts.map +1 -0
  84. package/dist/lifecycles/subscribers/index.d.ts +5 -0
  85. package/dist/lifecycles/subscribers/index.d.ts.map +1 -0
  86. package/dist/lifecycles/subscribers/models-lifecycles.d.ts +6 -0
  87. package/dist/lifecycles/subscribers/models-lifecycles.d.ts.map +1 -0
  88. package/dist/lifecycles/subscribers/timestamps.d.ts +3 -0
  89. package/dist/lifecycles/subscribers/timestamps.d.ts.map +1 -0
  90. package/dist/lifecycles/types.d.ts +25 -0
  91. package/dist/lifecycles/types.d.ts.map +1 -0
  92. package/dist/metadata/index.d.ts +10 -0
  93. package/dist/metadata/index.d.ts.map +1 -0
  94. package/dist/metadata/metadata.d.ts +22 -0
  95. package/dist/metadata/metadata.d.ts.map +1 -0
  96. package/dist/metadata/relations.d.ts +16 -0
  97. package/dist/metadata/relations.d.ts.map +1 -0
  98. package/dist/migrations/index.d.ts +12 -0
  99. package/dist/migrations/index.d.ts.map +1 -0
  100. package/dist/migrations/storage.d.ts +15 -0
  101. package/dist/migrations/storage.d.ts.map +1 -0
  102. package/dist/query/helpers/index.d.ts +8 -0
  103. package/dist/query/helpers/index.d.ts.map +1 -0
  104. package/dist/query/helpers/join.d.ts +30 -0
  105. package/dist/query/helpers/join.d.ts.map +1 -0
  106. package/dist/query/helpers/order-by.d.ts +14 -0
  107. package/dist/query/helpers/order-by.d.ts.map +1 -0
  108. package/dist/query/helpers/populate/apply.d.ts +11 -0
  109. package/dist/query/helpers/populate/apply.d.ts.map +1 -0
  110. package/dist/query/helpers/populate/index.d.ts +4 -0
  111. package/dist/query/helpers/populate/index.d.ts.map +1 -0
  112. package/dist/query/helpers/populate/process.d.ts +24 -0
  113. package/dist/query/helpers/populate/process.d.ts.map +1 -0
  114. package/dist/query/helpers/search.d.ts +4 -0
  115. package/dist/query/helpers/search.d.ts.map +1 -0
  116. package/dist/query/helpers/streams/index.d.ts +2 -0
  117. package/dist/query/helpers/streams/index.d.ts.map +1 -0
  118. package/dist/query/helpers/streams/readable.d.ts +39 -0
  119. package/dist/query/helpers/streams/readable.d.ts.map +1 -0
  120. package/dist/query/helpers/transform.d.ts +8 -0
  121. package/dist/query/helpers/transform.d.ts.map +1 -0
  122. package/dist/query/helpers/where.d.ts +19 -0
  123. package/dist/query/helpers/where.d.ts.map +1 -0
  124. package/dist/query/index.d.ts +3 -0
  125. package/dist/query/index.d.ts.map +1 -0
  126. package/dist/query/query-builder.d.ts +79 -0
  127. package/dist/query/query-builder.d.ts.map +1 -0
  128. package/dist/query/types.d.ts +8 -0
  129. package/dist/query/types.d.ts.map +1 -0
  130. package/dist/schema/builder.d.ts +33 -0
  131. package/dist/schema/builder.d.ts.map +1 -0
  132. package/dist/schema/diff.d.ts +7 -0
  133. package/dist/schema/diff.d.ts.map +1 -0
  134. package/dist/schema/index.d.ts +20 -0
  135. package/dist/schema/index.d.ts.map +1 -0
  136. package/dist/schema/schema.d.ts +4 -0
  137. package/dist/schema/schema.d.ts.map +1 -0
  138. package/dist/schema/storage.d.ts +10 -0
  139. package/dist/schema/storage.d.ts.map +1 -0
  140. package/dist/schema/types.d.ts +103 -0
  141. package/dist/schema/types.d.ts.map +1 -0
  142. package/dist/transaction-context.d.ts +22 -0
  143. package/dist/transaction-context.d.ts.map +1 -0
  144. package/dist/types/index.d.ts +169 -0
  145. package/dist/types/index.d.ts.map +1 -0
  146. package/dist/utils/content-types.d.ts +13 -0
  147. package/dist/utils/content-types.d.ts.map +1 -0
  148. package/dist/utils/knex.d.ts +12 -0
  149. package/dist/utils/knex.d.ts.map +1 -0
  150. package/dist/utils/types.d.ts +10 -0
  151. package/dist/utils/types.d.ts.map +1 -0
  152. package/dist/validations/index.d.ts +6 -0
  153. package/dist/validations/index.d.ts.map +1 -0
  154. package/dist/validations/relations/bidirectional.d.ts +12 -0
  155. package/dist/validations/relations/bidirectional.d.ts.map +1 -0
  156. package/dist/validations/relations/index.d.ts +7 -0
  157. package/dist/validations/relations/index.d.ts.map +1 -0
  158. package/package.json +20 -8
  159. package/.eslintignore +0 -2
  160. package/.eslintrc.js +0 -4
  161. package/jest.config.js +0 -6
  162. package/lib/__tests__/index.test.js +0 -93
  163. package/lib/__tests__/lifecycles.test.js +0 -55
  164. package/lib/connection.js +0 -64
  165. package/lib/dialects/dialect.js +0 -63
  166. package/lib/dialects/index.js +0 -53
  167. package/lib/dialects/mysql/constants.js +0 -6
  168. package/lib/dialects/mysql/database-inspector.js +0 -37
  169. package/lib/dialects/mysql/index.js +0 -92
  170. package/lib/dialects/mysql/schema-inspector.js +0 -234
  171. package/lib/dialects/postgresql/index.js +0 -65
  172. package/lib/dialects/postgresql/schema-inspector.js +0 -283
  173. package/lib/dialects/sqlite/index.js +0 -87
  174. package/lib/dialects/sqlite/schema-inspector.js +0 -151
  175. package/lib/entity-manager/__tests__/relations-orderer.test.js +0 -186
  176. package/lib/entity-manager/__tests__/sort-connect-array.test.js +0 -79
  177. package/lib/entity-manager/entity-repository.js +0 -164
  178. package/lib/entity-manager/index.js +0 -1385
  179. package/lib/entity-manager/morph-relations.js +0 -63
  180. package/lib/entity-manager/regular-relations.js +0 -506
  181. package/lib/entity-manager/relations/cloning/regular-relations.js +0 -76
  182. package/lib/entity-manager/relations-orderer.js +0 -225
  183. package/lib/errors/database.js +0 -12
  184. package/lib/errors/index.js +0 -17
  185. package/lib/errors/invalid-date.js +0 -14
  186. package/lib/errors/invalid-datetime.js +0 -14
  187. package/lib/errors/invalid-relation.js +0 -14
  188. package/lib/errors/invalid-time.js +0 -14
  189. package/lib/errors/not-null.js +0 -15
  190. package/lib/fields/biginteger.js +0 -17
  191. package/lib/fields/boolean.js +0 -39
  192. package/lib/fields/date.js +0 -16
  193. package/lib/fields/datetime.js +0 -19
  194. package/lib/fields/field.js +0 -17
  195. package/lib/fields/index.d.ts +0 -9
  196. package/lib/fields/index.js +0 -50
  197. package/lib/fields/json.js +0 -21
  198. package/lib/fields/number.js +0 -23
  199. package/lib/fields/shared/parsers.js +0 -71
  200. package/lib/fields/string.js +0 -17
  201. package/lib/fields/time.js +0 -17
  202. package/lib/fields/timestamp.js +0 -19
  203. package/lib/index.d.ts +0 -198
  204. package/lib/index.js +0 -129
  205. package/lib/lifecycles/index.d.ts +0 -51
  206. package/lib/lifecycles/index.js +0 -90
  207. package/lib/lifecycles/subscribers/index.d.ts +0 -11
  208. package/lib/lifecycles/subscribers/models-lifecycles.js +0 -19
  209. package/lib/lifecycles/subscribers/timestamps.js +0 -65
  210. package/lib/metadata/index.js +0 -244
  211. package/lib/metadata/relations.js +0 -578
  212. package/lib/migrations/index.d.ts +0 -9
  213. package/lib/migrations/index.js +0 -75
  214. package/lib/migrations/storage.js +0 -44
  215. package/lib/query/helpers/index.js +0 -11
  216. package/lib/query/helpers/join.js +0 -96
  217. package/lib/query/helpers/order-by.js +0 -70
  218. package/lib/query/helpers/populate/apply.js +0 -664
  219. package/lib/query/helpers/populate/index.js +0 -9
  220. package/lib/query/helpers/populate/process.js +0 -102
  221. package/lib/query/helpers/search.js +0 -84
  222. package/lib/query/helpers/streams/index.js +0 -5
  223. package/lib/query/helpers/streams/readable.js +0 -174
  224. package/lib/query/helpers/transform.js +0 -84
  225. package/lib/query/helpers/where.js +0 -365
  226. package/lib/query/index.js +0 -7
  227. package/lib/query/query-builder.js +0 -514
  228. package/lib/schema/__tests__/schema-diff.test.js +0 -231
  229. package/lib/schema/builder.js +0 -386
  230. package/lib/schema/diff.js +0 -399
  231. package/lib/schema/index.d.ts +0 -49
  232. package/lib/schema/index.js +0 -94
  233. package/lib/schema/schema.js +0 -202
  234. package/lib/schema/storage.js +0 -76
  235. package/lib/transaction-context.js +0 -68
  236. package/lib/types/index.d.ts +0 -6
  237. package/lib/types/index.js +0 -35
  238. package/lib/utils/content-types.js +0 -40
  239. package/lib/utils/knex.js +0 -22
  240. package/lib/validations/index.js +0 -20
  241. package/lib/validations/relations/bidirectional.js +0 -89
  242. package/lib/validations/relations/index.js +0 -14
@@ -1,1385 +0,0 @@
1
- 'use strict';
2
-
3
- const {
4
- castArray,
5
- compact,
6
- difference,
7
- differenceWith,
8
- flow,
9
- has,
10
- isArray,
11
- isEmpty,
12
- isEqual,
13
- isInteger,
14
- isNil,
15
- isNull,
16
- isNumber,
17
- isPlainObject,
18
- isString,
19
- isUndefined,
20
- map,
21
- mergeWith,
22
- omit,
23
- pick,
24
- uniqBy,
25
- uniqWith,
26
- } = require('lodash/fp');
27
-
28
- const { mapAsync } = require('@strapi/utils');
29
- const types = require('../types');
30
- const { createField } = require('../fields');
31
- const { createQueryBuilder } = require('../query');
32
- const { createRepository } = require('./entity-repository');
33
- const { deleteRelatedMorphOneRelationsAfterMorphToManyUpdate } = require('./morph-relations');
34
- const {
35
- isPolymorphic,
36
- isBidirectional,
37
- isAnyToOne,
38
- isOneToAny,
39
- hasOrderColumn,
40
- hasInverseOrderColumn,
41
- } = require('../metadata/relations');
42
- const {
43
- deletePreviousOneToAnyRelations,
44
- deletePreviousAnyToOneRelations,
45
- deleteRelations,
46
- cleanOrderColumns,
47
- } = require('./regular-relations');
48
- const { relationsOrderer } = require('./relations-orderer');
49
- const {
50
- replaceRegularRelations,
51
- cloneRegularRelations,
52
- } = require('./relations/cloning/regular-relations');
53
- const { DatabaseError } = require('../errors');
54
-
55
- const toId = (value) => value.id || value;
56
- const toIds = (value) => castArray(value || []).map(toId);
57
-
58
- const isValidId = (value) => isString(value) || isInteger(value);
59
- const toIdArray = (data) => {
60
- const array = castArray(data)
61
- .filter((datum) => !isNil(datum))
62
- .map((datum) => {
63
- // if it is a string or an integer return an obj with id = to datum
64
- if (isValidId(datum)) {
65
- return { id: datum, __pivot: {} };
66
- }
67
-
68
- // if it is an object check it has at least a valid id
69
- if (!has('id', datum) || !isValidId(datum.id)) {
70
- throw new Error(`Invalid id, expected a string or integer, got ${datum}`);
71
- }
72
-
73
- return datum;
74
- });
75
- return uniqWith(isEqual, array);
76
- };
77
-
78
- const toAssocs = (data) => {
79
- if (isArray(data) || isString(data) || isNumber(data) || isNull(data) || data?.id) {
80
- return {
81
- set: isNull(data) ? data : toIdArray(data),
82
- };
83
- }
84
-
85
- if (data?.set) {
86
- return {
87
- set: isNull(data.set) ? data.set : toIdArray(data.set),
88
- };
89
- }
90
-
91
- return {
92
- options: {
93
- strict: data?.options?.strict,
94
- },
95
- connect: toIdArray(data?.connect).map((elm) => ({
96
- id: elm.id,
97
- position: elm.position ? elm.position : { end: true },
98
- })),
99
- disconnect: toIdArray(data?.disconnect),
100
- };
101
- };
102
-
103
- const processData = (metadata, data = {}, { withDefaults = false } = {}) => {
104
- const { attributes } = metadata;
105
-
106
- const obj = {};
107
-
108
- for (const attributeName of Object.keys(attributes)) {
109
- const attribute = attributes[attributeName];
110
-
111
- if (types.isScalar(attribute.type)) {
112
- const field = createField(attribute);
113
-
114
- if (isUndefined(data[attributeName])) {
115
- if (!isUndefined(attribute.default) && withDefaults) {
116
- if (typeof attribute.default === 'function') {
117
- obj[attributeName] = attribute.default();
118
- } else {
119
- obj[attributeName] = attribute.default;
120
- }
121
- }
122
- continue;
123
- }
124
-
125
- if (typeof field.validate === 'function' && data[attributeName] !== null) {
126
- field.validate(data[attributeName]);
127
- }
128
-
129
- const val = data[attributeName] === null ? null : field.toDB(data[attributeName]);
130
-
131
- obj[attributeName] = val;
132
- }
133
-
134
- if (types.isRelation(attribute.type)) {
135
- // oneToOne & manyToOne
136
- if (attribute.joinColumn && attribute.owner) {
137
- const joinColumnName = attribute.joinColumn.name;
138
-
139
- // allow setting to null
140
- const attrValue = !isUndefined(data[attributeName])
141
- ? data[attributeName]
142
- : data[joinColumnName];
143
-
144
- if (!isUndefined(attrValue)) {
145
- obj[joinColumnName] = attrValue;
146
- }
147
-
148
- continue;
149
- }
150
-
151
- if (attribute.morphColumn && attribute.owner) {
152
- const { idColumn, typeColumn, typeField = '__type' } = attribute.morphColumn;
153
-
154
- const value = data[attributeName];
155
-
156
- if (value === null) {
157
- Object.assign(obj, {
158
- [idColumn.name]: null,
159
- [typeColumn.name]: null,
160
- });
161
-
162
- continue;
163
- }
164
-
165
- if (!isUndefined(value)) {
166
- if (!has('id', value) || !has(typeField, value)) {
167
- throw new Error(`Expects properties ${typeField} an id to make a morph association`);
168
- }
169
-
170
- Object.assign(obj, {
171
- [idColumn.name]: value.id,
172
- [typeColumn.name]: value[typeField],
173
- });
174
- }
175
- }
176
- }
177
- }
178
-
179
- return obj;
180
- };
181
-
182
- const createEntityManager = (db) => {
183
- const repoMap = {};
184
-
185
- return {
186
- async findOne(uid, params) {
187
- const states = await db.lifecycles.run('beforeFindOne', uid, { params });
188
-
189
- const result = await this.createQueryBuilder(uid).init(params).first().execute();
190
-
191
- await db.lifecycles.run('afterFindOne', uid, { params, result }, states);
192
-
193
- return result;
194
- },
195
-
196
- // should we name it findOne because people are used to it ?
197
- async findMany(uid, params) {
198
- const states = await db.lifecycles.run('beforeFindMany', uid, { params });
199
-
200
- const result = await this.createQueryBuilder(uid).init(params).execute();
201
-
202
- await db.lifecycles.run('afterFindMany', uid, { params, result }, states);
203
-
204
- return result;
205
- },
206
-
207
- async count(uid, params) {
208
- const states = await db.lifecycles.run('beforeCount', uid, { params });
209
-
210
- const res = await this.createQueryBuilder(uid)
211
- .init(pick(['_q', 'where', 'filters'], params))
212
- .count()
213
- .first()
214
- .execute();
215
-
216
- const result = Number(res.count);
217
-
218
- await db.lifecycles.run('afterCount', uid, { params, result }, states);
219
-
220
- return result;
221
- },
222
-
223
- async create(uid, params = {}) {
224
- const states = await db.lifecycles.run('beforeCreate', uid, { params });
225
-
226
- const metadata = db.metadata.get(uid);
227
- const { data } = params;
228
-
229
- if (!isPlainObject(data)) {
230
- throw new Error('Create expects a data object');
231
- }
232
-
233
- const dataToInsert = processData(metadata, data, { withDefaults: true });
234
-
235
- const res = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
236
-
237
- const id = res[0].id || res[0];
238
-
239
- const trx = await strapi.db.transaction();
240
- try {
241
- await this.attachRelations(uid, id, data, { transaction: trx.get() });
242
-
243
- await trx.commit();
244
- } catch (e) {
245
- await trx.rollback();
246
- await this.createQueryBuilder(uid).where({ id }).delete().execute();
247
- throw e;
248
- }
249
-
250
- // TODO: in case there is no select or populate specified return the inserted data ?
251
- // TODO: do not trigger the findOne lifecycles ?
252
- const result = await this.findOne(uid, {
253
- where: { id },
254
- select: params.select,
255
- populate: params.populate,
256
- });
257
-
258
- await db.lifecycles.run('afterCreate', uid, { params, result }, states);
259
-
260
- return result;
261
- },
262
-
263
- // TODO: where do we handle relation processing for many queries ?
264
- async createMany(uid, params = {}) {
265
- const states = await db.lifecycles.run('beforeCreateMany', uid, { params });
266
-
267
- const metadata = db.metadata.get(uid);
268
- const { data } = params;
269
-
270
- if (!isArray(data)) {
271
- throw new Error('CreateMany expects data to be an array');
272
- }
273
-
274
- const dataToInsert = data.map((datum) =>
275
- processData(metadata, datum, { withDefaults: true })
276
- );
277
-
278
- if (isEmpty(dataToInsert)) {
279
- throw new Error('Nothing to insert');
280
- }
281
-
282
- const createdEntries = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
283
-
284
- const result = {
285
- count: data.length,
286
- ids: createdEntries.map((entry) => (typeof entry === 'object' ? entry?.id : entry)),
287
- };
288
-
289
- await db.lifecycles.run('afterCreateMany', uid, { params, result }, states);
290
-
291
- return result;
292
- },
293
-
294
- async update(uid, params = {}) {
295
- const states = await db.lifecycles.run('beforeUpdate', uid, { params });
296
-
297
- const metadata = db.metadata.get(uid);
298
- const { where, data } = params;
299
-
300
- if (!isPlainObject(data)) {
301
- throw new Error('Update requires a data object');
302
- }
303
-
304
- if (isEmpty(where)) {
305
- throw new Error('Update requires a where parameter');
306
- }
307
-
308
- const entity = await this.createQueryBuilder(uid)
309
- .select('*')
310
- .where(where)
311
- .first()
312
- .execute({ mapResults: false });
313
-
314
- if (!entity) {
315
- return null;
316
- }
317
-
318
- const { id } = entity;
319
-
320
- const dataToUpdate = processData(metadata, data);
321
-
322
- if (!isEmpty(dataToUpdate)) {
323
- await this.createQueryBuilder(uid).where({ id }).update(dataToUpdate).execute();
324
- }
325
-
326
- const trx = await strapi.db.transaction();
327
- try {
328
- await this.updateRelations(uid, id, data, { transaction: trx.get() });
329
- await trx.commit();
330
- } catch (e) {
331
- await trx.rollback();
332
- await this.createQueryBuilder(uid).where({ id }).update(entity).execute();
333
- throw e;
334
- }
335
-
336
- // TODO: do not trigger the findOne lifecycles ?
337
- const result = await this.findOne(uid, {
338
- where: { id },
339
- select: params.select,
340
- populate: params.populate,
341
- });
342
-
343
- await db.lifecycles.run('afterUpdate', uid, { params, result }, states);
344
-
345
- return result;
346
- },
347
-
348
- // TODO: where do we handle relation processing for many queries ?
349
- async updateMany(uid, params = {}) {
350
- const states = await db.lifecycles.run('beforeUpdateMany', uid, { params });
351
-
352
- const metadata = db.metadata.get(uid);
353
- const { where, data } = params;
354
-
355
- const dataToUpdate = processData(metadata, data);
356
-
357
- if (isEmpty(dataToUpdate)) {
358
- throw new Error('Update requires data');
359
- }
360
-
361
- const updatedRows = await this.createQueryBuilder(uid)
362
- .where(where)
363
- .update(dataToUpdate)
364
- .execute();
365
-
366
- const result = { count: updatedRows };
367
-
368
- await db.lifecycles.run('afterUpdateMany', uid, { params, result }, states);
369
-
370
- return result;
371
- },
372
-
373
- async clone(uid, cloneId, params = {}) {
374
- const states = await db.lifecycles.run('beforeCreate', uid, { params });
375
-
376
- const metadata = db.metadata.get(uid);
377
- const { data } = params;
378
-
379
- if (!isNil(data) && !isPlainObject(data)) {
380
- throw new Error('Create expects a data object');
381
- }
382
-
383
- // TODO: Handle join columns?
384
- const entity = await this.findOne(uid, { where: { id: cloneId } });
385
-
386
- const dataToInsert = flow(
387
- // Omit unwanted properties
388
- omit(['id', 'created_at', 'updated_at']),
389
- // Merge with provided data, set attribute to null if data attribute is null
390
- mergeWith(data || {}, (original, override) => (override === null ? override : original)),
391
- // Process data with metadata
392
- (entity) => processData(metadata, entity, { withDefaults: true })
393
- )(entity);
394
-
395
- const res = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
396
-
397
- const id = res[0].id || res[0];
398
-
399
- const trx = await strapi.db.transaction();
400
- try {
401
- const cloneAttrs = Object.entries(metadata.attributes).reduce((acc, [attrName, attr]) => {
402
- // TODO: handle components in the db layer
403
- if (attr.type === 'relation' && attr.joinTable && !attr.component) {
404
- acc.push(attrName);
405
- }
406
- return acc;
407
- }, []);
408
-
409
- await this.cloneRelations(uid, id, cloneId, data, { cloneAttrs, transaction: trx.get() });
410
- await trx.commit();
411
- } catch (e) {
412
- await trx.rollback();
413
- await this.createQueryBuilder(uid).where({ id }).delete().execute();
414
- throw e;
415
- }
416
-
417
- const result = await this.findOne(uid, {
418
- where: { id },
419
- select: params.select,
420
- populate: params.populate,
421
- });
422
-
423
- await db.lifecycles.run('afterCreate', uid, { params, result }, states);
424
-
425
- return result;
426
- },
427
-
428
- async delete(uid, params = {}) {
429
- const states = await db.lifecycles.run('beforeDelete', uid, { params });
430
-
431
- const { where, select, populate } = params;
432
-
433
- if (isEmpty(where)) {
434
- throw new Error('Delete requires a where parameter');
435
- }
436
-
437
- // TODO: do not trigger the findOne lifecycles ?
438
- const entity = await this.findOne(uid, {
439
- select: select && ['id'].concat(select),
440
- where,
441
- populate,
442
- });
443
-
444
- if (!entity) {
445
- return null;
446
- }
447
-
448
- const { id } = entity;
449
-
450
- await this.createQueryBuilder(uid).where({ id }).delete().execute();
451
-
452
- const trx = await strapi.db.transaction();
453
- try {
454
- await this.deleteRelations(uid, id, { transaction: trx.get() });
455
-
456
- await trx.commit();
457
- } catch (e) {
458
- await trx.rollback();
459
- throw e;
460
- }
461
-
462
- await db.lifecycles.run('afterDelete', uid, { params, result: entity }, states);
463
-
464
- return entity;
465
- },
466
-
467
- // TODO: where do we handle relation processing for many queries ?
468
- async deleteMany(uid, params = {}) {
469
- const states = await db.lifecycles.run('beforeDeleteMany', uid, { params });
470
-
471
- const { where } = params;
472
-
473
- const deletedRows = await this.createQueryBuilder(uid).where(where).delete().execute();
474
-
475
- const result = { count: deletedRows };
476
-
477
- await db.lifecycles.run('afterDeleteMany', uid, { params, result }, states);
478
-
479
- return result;
480
- },
481
-
482
- /**
483
- * Attach relations to a new entity
484
- *
485
- * @param {EntityManager} em - entity manager instance
486
- * @param {Metadata} metadata - model metadta
487
- * @param {ID} id - entity ID
488
- * @param {object} data - data received for creation
489
- */
490
- async attachRelations(uid, id, data, { transaction: trx }) {
491
- const { attributes } = db.metadata.get(uid);
492
-
493
- for (const attributeName of Object.keys(attributes)) {
494
- const attribute = attributes[attributeName];
495
-
496
- const isValidLink = has(attributeName, data) && !isNil(data[attributeName]);
497
-
498
- if (attribute.type !== 'relation' || !isValidLink) {
499
- continue;
500
- }
501
-
502
- const cleanRelationData = toAssocs(data[attributeName]);
503
-
504
- if (attribute.relation === 'morphOne' || attribute.relation === 'morphMany') {
505
- const { target, morphBy } = attribute;
506
-
507
- const targetAttribute = db.metadata.get(target).attributes[morphBy];
508
-
509
- if (targetAttribute.relation === 'morphToOne') {
510
- // set columns
511
- const { idColumn, typeColumn } = targetAttribute.morphColumn;
512
-
513
- const relId = toId(cleanRelationData.set[0]);
514
-
515
- await this.createQueryBuilder(target)
516
- .update({ [idColumn.name]: id, [typeColumn.name]: uid })
517
- .where({ id: relId })
518
- .transacting(trx)
519
- .execute();
520
- } else if (targetAttribute.relation === 'morphToMany') {
521
- const { joinTable } = targetAttribute;
522
- const { joinColumn, morphColumn } = joinTable;
523
-
524
- const { idColumn, typeColumn } = morphColumn;
525
-
526
- if (isEmpty(cleanRelationData.set)) {
527
- continue;
528
- }
529
-
530
- const rows = cleanRelationData.set.map((data, idx) => {
531
- return {
532
- [joinColumn.name]: data.id,
533
- [idColumn.name]: id,
534
- [typeColumn.name]: uid,
535
- ...(joinTable.on || {}),
536
- ...(data.__pivot || {}),
537
- order: idx + 1,
538
- field: attributeName,
539
- };
540
- });
541
-
542
- await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
543
- }
544
-
545
- continue;
546
- } else if (attribute.relation === 'morphToOne') {
547
- // handled on the entry itself
548
- continue;
549
- } else if (attribute.relation === 'morphToMany') {
550
- const { joinTable } = attribute;
551
- const { joinColumn, morphColumn } = joinTable;
552
-
553
- const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
554
-
555
- if (isEmpty(cleanRelationData.set)) {
556
- continue;
557
- }
558
-
559
- const rows = cleanRelationData.set.map((data, idx) => ({
560
- [joinColumn.name]: id,
561
- [idColumn.name]: data.id,
562
- [typeColumn.name]: data[typeField],
563
- ...(joinTable.on || {}),
564
- ...(data.__pivot || {}),
565
- order: idx + 1,
566
- }));
567
-
568
- // delete previous relations
569
- await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
570
- uid,
571
- attributeName,
572
- joinTable,
573
- db,
574
- transaction: trx,
575
- });
576
-
577
- await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
578
-
579
- continue;
580
- }
581
-
582
- if (attribute.joinColumn && attribute.owner) {
583
- const relIdsToAdd = toIds(cleanRelationData.set);
584
- if (
585
- attribute.relation === 'oneToOne' &&
586
- isBidirectional(attribute) &&
587
- relIdsToAdd.length
588
- ) {
589
- await this.createQueryBuilder(uid)
590
- .where({ [attribute.joinColumn.name]: relIdsToAdd, id: { $ne: id } })
591
- .update({ [attribute.joinColumn.name]: null })
592
- .transacting(trx)
593
- .execute();
594
- }
595
-
596
- continue;
597
- }
598
-
599
- // oneToOne oneToMany on the non owning side
600
- if (attribute.joinColumn && !attribute.owner) {
601
- // need to set the column on the target
602
- const { target } = attribute;
603
-
604
- // TODO: check it is an id & the entity exists (will throw due to FKs otherwise so not a big pbl in SQL)
605
- const relIdsToAdd = toIds(cleanRelationData.set);
606
-
607
- await this.createQueryBuilder(target)
608
- .where({ [attribute.joinColumn.referencedColumn]: id })
609
- .update({ [attribute.joinColumn.referencedColumn]: null })
610
- .transacting(trx)
611
- .execute();
612
-
613
- await this.createQueryBuilder(target)
614
- .update({ [attribute.joinColumn.referencedColumn]: id })
615
- // NOTE: works if it is an array or a single id
616
- .where({ id: relIdsToAdd })
617
- .transacting(trx)
618
- .execute();
619
- }
620
-
621
- if (attribute.joinTable) {
622
- // need to set the column on the target
623
-
624
- const { joinTable } = attribute;
625
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } =
626
- joinTable;
627
-
628
- const relsToAdd = cleanRelationData.set || cleanRelationData.connect;
629
- const relIdsToadd = toIds(relsToAdd);
630
-
631
- if (isBidirectional(attribute) && isOneToAny(attribute)) {
632
- await deletePreviousOneToAnyRelations({
633
- id,
634
- attribute,
635
- relIdsToadd,
636
- db,
637
- transaction: trx,
638
- });
639
- }
640
-
641
- // prepare new relations to insert
642
- const insert = uniqBy('id', relsToAdd).map((data) => {
643
- return {
644
- [joinColumn.name]: id,
645
- [inverseJoinColumn.name]: data.id,
646
- ...(joinTable.on || {}),
647
- ...(data.__pivot || {}),
648
- };
649
- });
650
-
651
- // add order value
652
- if (cleanRelationData.set && hasOrderColumn(attribute)) {
653
- insert.forEach((data, idx) => {
654
- data[orderColumnName] = idx + 1;
655
- });
656
- } else if (cleanRelationData.connect && hasOrderColumn(attribute)) {
657
- // use position attributes to calculate order
658
- const orderMap = relationsOrderer(
659
- [],
660
- inverseJoinColumn.name,
661
- joinTable.orderColumnName,
662
- true // Always make an strict connect when inserting
663
- )
664
- .connect(relsToAdd)
665
- .get()
666
- // set the order based on the order of the ids
667
- .reduce((acc, rel, idx) => ({ ...acc, [rel.id]: idx }), {});
668
-
669
- insert.forEach((row) => {
670
- row[orderColumnName] = orderMap[row[inverseJoinColumn.name]];
671
- });
672
- }
673
-
674
- // add inv_order value
675
- if (hasInverseOrderColumn(attribute)) {
676
- const maxResults = await db
677
- .getConnection()
678
- .select(inverseJoinColumn.name)
679
- .max(inverseOrderColumnName, { as: 'max' })
680
- .whereIn(inverseJoinColumn.name, relIdsToadd)
681
- .where(joinTable.on || {})
682
- .groupBy(inverseJoinColumn.name)
683
- .from(joinTable.name)
684
- .transacting(trx);
685
-
686
- const maxMap = maxResults.reduce(
687
- (acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
688
- {}
689
- );
690
-
691
- insert.forEach((rel) => {
692
- rel[inverseOrderColumnName] = (maxMap[rel[inverseJoinColumn.name]] || 0) + 1;
693
- });
694
- }
695
-
696
- if (insert.length === 0) {
697
- continue;
698
- }
699
-
700
- // insert new relations
701
- await this.createQueryBuilder(joinTable.name).insert(insert).transacting(trx).execute();
702
- }
703
- }
704
- },
705
-
706
- /**
707
- * Updates relations of an existing entity
708
- *
709
- * @param {EntityManager} em - entity manager instance
710
- * @param {Metadata} metadata - model metadta
711
- * @param {ID} id - entity ID
712
- * @param {object} data - data received for creation
713
- */
714
- // TODO: check relation exists (handled by FKs except for polymorphics)
715
- async updateRelations(uid, id, data, { transaction: trx }) {
716
- const { attributes } = db.metadata.get(uid);
717
-
718
- for (const attributeName of Object.keys(attributes)) {
719
- const attribute = attributes[attributeName];
720
-
721
- if (attribute.type !== 'relation' || !has(attributeName, data)) {
722
- continue;
723
- }
724
- const cleanRelationData = toAssocs(data[attributeName]);
725
-
726
- if (attribute.relation === 'morphOne' || attribute.relation === 'morphMany') {
727
- const { target, morphBy } = attribute;
728
-
729
- const targetAttribute = db.metadata.get(target).attributes[morphBy];
730
-
731
- if (targetAttribute.relation === 'morphToOne') {
732
- // set columns
733
- const { idColumn, typeColumn } = targetAttribute.morphColumn;
734
-
735
- // update instead of deleting because the relation is directly on the entity table
736
- // and not in a join table
737
- await this.createQueryBuilder(target)
738
- .update({ [idColumn.name]: null, [typeColumn.name]: null })
739
- .where({ [idColumn.name]: id, [typeColumn.name]: uid })
740
- .transacting(trx)
741
- .execute();
742
-
743
- if (!isNull(cleanRelationData.set)) {
744
- const relId = toIds(cleanRelationData.set[0]);
745
- await this.createQueryBuilder(target)
746
- .update({ [idColumn.name]: id, [typeColumn.name]: uid })
747
- .where({ id: relId })
748
- .transacting(trx)
749
- .execute();
750
- }
751
- } else if (targetAttribute.relation === 'morphToMany') {
752
- const { joinTable } = targetAttribute;
753
- const { joinColumn, morphColumn } = joinTable;
754
-
755
- const { idColumn, typeColumn } = morphColumn;
756
-
757
- await this.createQueryBuilder(joinTable.name)
758
- .delete()
759
- .where({
760
- [idColumn.name]: id,
761
- [typeColumn.name]: uid,
762
- ...(joinTable.on || {}),
763
- field: attributeName,
764
- })
765
- .transacting(trx)
766
- .execute();
767
-
768
- if (isEmpty(cleanRelationData.set)) {
769
- continue;
770
- }
771
-
772
- const rows = cleanRelationData.set.map((data, idx) => ({
773
- [joinColumn.name]: data.id,
774
- [idColumn.name]: id,
775
- [typeColumn.name]: uid,
776
- ...(joinTable.on || {}),
777
- ...(data.__pivot || {}),
778
- order: idx + 1,
779
- field: attributeName,
780
- }));
781
-
782
- await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
783
- }
784
-
785
- continue;
786
- }
787
-
788
- if (attribute.relation === 'morphToOne') {
789
- // handled on the entry itself
790
- continue;
791
- }
792
-
793
- if (attribute.relation === 'morphToMany') {
794
- const { joinTable } = attribute;
795
- const { joinColumn, morphColumn } = joinTable;
796
-
797
- const { idColumn, typeColumn, typeField = '__type' } = morphColumn;
798
-
799
- await this.createQueryBuilder(joinTable.name)
800
- .delete()
801
- .where({
802
- [joinColumn.name]: id,
803
- ...(joinTable.on || {}),
804
- })
805
- .transacting(trx)
806
- .execute();
807
-
808
- if (isEmpty(cleanRelationData.set)) {
809
- continue;
810
- }
811
-
812
- const rows = cleanRelationData.set.map((data, idx) => ({
813
- [joinColumn.name]: id,
814
- [idColumn.name]: data.id,
815
- [typeColumn.name]: data[typeField],
816
- ...(joinTable.on || {}),
817
- ...(data.__pivot || {}),
818
- order: idx + 1,
819
- }));
820
-
821
- // delete previous relations
822
- await deleteRelatedMorphOneRelationsAfterMorphToManyUpdate(rows, {
823
- uid,
824
- attributeName,
825
- joinTable,
826
- db,
827
- transaction: trx,
828
- });
829
-
830
- await this.createQueryBuilder(joinTable.name).insert(rows).transacting(trx).execute();
831
-
832
- continue;
833
- }
834
-
835
- if (attribute.joinColumn && attribute.owner) {
836
- // handled in the row itself
837
- continue;
838
- }
839
-
840
- // oneToOne oneToMany on the non owning side.
841
- // Since it is a join column no need to remove previous relations
842
- if (attribute.joinColumn && !attribute.owner) {
843
- // need to set the column on the target
844
- const { target } = attribute;
845
-
846
- await this.createQueryBuilder(target)
847
- .where({ [attribute.joinColumn.referencedColumn]: id })
848
- .update({ [attribute.joinColumn.referencedColumn]: null })
849
- .transacting(trx)
850
- .execute();
851
-
852
- if (!isNull(cleanRelationData.set)) {
853
- const relIdsToAdd = toIds(cleanRelationData.set);
854
- await this.createQueryBuilder(target)
855
- .where({ id: relIdsToAdd })
856
- .update({ [attribute.joinColumn.referencedColumn]: id })
857
- .transacting(trx)
858
- .execute();
859
- }
860
- }
861
-
862
- if (attribute.joinTable) {
863
- const { joinTable } = attribute;
864
- const { joinColumn, inverseJoinColumn, orderColumnName, inverseOrderColumnName } =
865
- joinTable;
866
- const select = [joinColumn.name, inverseJoinColumn.name];
867
- if (hasOrderColumn(attribute)) {
868
- select.push(orderColumnName);
869
- }
870
- if (hasInverseOrderColumn(attribute)) {
871
- select.push(inverseOrderColumnName);
872
- }
873
-
874
- // only delete relations
875
- if (isNull(cleanRelationData.set)) {
876
- await deleteRelations({ id, attribute, db, relIdsToDelete: 'all', transaction: trx });
877
- } else {
878
- const isPartialUpdate = !has('set', cleanRelationData);
879
- let relIdsToaddOrMove;
880
-
881
- if (isPartialUpdate) {
882
- if (isAnyToOne(attribute)) {
883
- cleanRelationData.connect = cleanRelationData.connect.slice(-1);
884
- }
885
- relIdsToaddOrMove = toIds(cleanRelationData.connect);
886
- const relIdsToDelete = toIds(
887
- differenceWith(isEqual, cleanRelationData.disconnect, cleanRelationData.connect)
888
- );
889
-
890
- if (!isEmpty(relIdsToDelete)) {
891
- await deleteRelations({ id, attribute, db, relIdsToDelete, transaction: trx });
892
- }
893
-
894
- if (isEmpty(cleanRelationData.connect)) {
895
- continue;
896
- }
897
-
898
- // Fetch current relations to handle ordering
899
- let currentMovingRels;
900
- if (hasOrderColumn(attribute) || hasInverseOrderColumn(attribute)) {
901
- currentMovingRels = await this.createQueryBuilder(joinTable.name)
902
- .select(select)
903
- .where({
904
- [joinColumn.name]: id,
905
- [inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
906
- })
907
- .where(joinTable.on || {})
908
- .transacting(trx)
909
- .execute();
910
- }
911
-
912
- // prepare relations to insert
913
- const insert = uniqBy('id', cleanRelationData.connect).map((relToAdd) => ({
914
- [joinColumn.name]: id,
915
- [inverseJoinColumn.name]: relToAdd.id,
916
- ...(joinTable.on || {}),
917
- ...(relToAdd.__pivot || {}),
918
- }));
919
-
920
- if (hasOrderColumn(attribute)) {
921
- // Get all adjacent relations and the one with the highest order
922
- const adjacentRelations = await this.createQueryBuilder(joinTable.name)
923
- .where({
924
- $or: [
925
- {
926
- [joinColumn.name]: id,
927
- [inverseJoinColumn.name]: {
928
- $in: compact(
929
- cleanRelationData.connect.map(
930
- (r) => r.position?.after || r.position?.before
931
- )
932
- ),
933
- },
934
- },
935
- {
936
- [joinColumn.name]: id,
937
- [orderColumnName]: this.createQueryBuilder(joinTable.name)
938
- .max(orderColumnName)
939
- .where({ [joinColumn.name]: id })
940
- .where(joinTable.on || {})
941
- .transacting(trx)
942
- .getKnexQuery(),
943
- },
944
- ],
945
- })
946
- .where(joinTable.on || {})
947
- .transacting(trx)
948
- .execute();
949
-
950
- const orderMap = relationsOrderer(
951
- adjacentRelations,
952
- inverseJoinColumn.name,
953
- joinTable.orderColumnName,
954
- cleanRelationData.options.strict
955
- )
956
- .connect(cleanRelationData.connect)
957
- .getOrderMap();
958
-
959
- insert.forEach((row) => {
960
- row[orderColumnName] = orderMap[row[inverseJoinColumn.name]];
961
- });
962
- }
963
-
964
- // add inv order value
965
- if (hasInverseOrderColumn(attribute)) {
966
- const nonExistingRelsIds = difference(
967
- relIdsToaddOrMove,
968
- map(inverseJoinColumn.name, currentMovingRels)
969
- );
970
-
971
- const maxResults = await db
972
- .getConnection()
973
- .select(inverseJoinColumn.name)
974
- .max(inverseOrderColumnName, { as: 'max' })
975
- .whereIn(inverseJoinColumn.name, nonExistingRelsIds)
976
- .where(joinTable.on || {})
977
- .groupBy(inverseJoinColumn.name)
978
- .from(joinTable.name)
979
- .transacting(trx);
980
-
981
- const maxMap = maxResults.reduce(
982
- (acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
983
- {}
984
- );
985
-
986
- insert.forEach((row) => {
987
- row[inverseOrderColumnName] = (maxMap[row[inverseJoinColumn.name]] || 0) + 1;
988
- });
989
- }
990
-
991
- // insert rows
992
- const query = this.createQueryBuilder(joinTable.name)
993
- .insert(insert)
994
- .onConflict(joinTable.pivotColumns)
995
- .transacting(trx);
996
-
997
- if (hasOrderColumn(attribute)) {
998
- query.merge([orderColumnName]);
999
- } else {
1000
- query.ignore();
1001
- }
1002
-
1003
- await query.execute();
1004
-
1005
- // remove gap between orders
1006
- await cleanOrderColumns({ attribute, db, id, transaction: trx });
1007
- } else {
1008
- if (isAnyToOne(attribute)) {
1009
- cleanRelationData.set = cleanRelationData.set.slice(-1);
1010
- }
1011
- // overwrite all relations
1012
- relIdsToaddOrMove = toIds(cleanRelationData.set);
1013
- await deleteRelations({
1014
- id,
1015
- attribute,
1016
- db,
1017
- relIdsToDelete: 'all',
1018
- relIdsToNotDelete: relIdsToaddOrMove,
1019
- transaction: trx,
1020
- });
1021
-
1022
- if (isEmpty(cleanRelationData.set)) {
1023
- continue;
1024
- }
1025
-
1026
- const insert = uniqBy('id', cleanRelationData.set).map((relToAdd) => ({
1027
- [joinColumn.name]: id,
1028
- [inverseJoinColumn.name]: relToAdd.id,
1029
- ...(joinTable.on || {}),
1030
- ...(relToAdd.__pivot || {}),
1031
- }));
1032
-
1033
- // add order value
1034
- if (hasOrderColumn(attribute)) {
1035
- insert.forEach((row, idx) => {
1036
- row[orderColumnName] = idx + 1;
1037
- });
1038
- }
1039
-
1040
- // add inv order value
1041
- if (hasInverseOrderColumn(attribute)) {
1042
- const existingRels = await this.createQueryBuilder(joinTable.name)
1043
- .select(inverseJoinColumn.name)
1044
- .where({
1045
- [joinColumn.name]: id,
1046
- [inverseJoinColumn.name]: { $in: relIdsToaddOrMove },
1047
- })
1048
- .where(joinTable.on || {})
1049
- .transacting(trx)
1050
- .execute();
1051
-
1052
- const nonExistingRelsIds = difference(
1053
- relIdsToaddOrMove,
1054
- map(inverseJoinColumn.name, existingRels)
1055
- );
1056
-
1057
- const maxResults = await db
1058
- .getConnection()
1059
- .select(inverseJoinColumn.name)
1060
- .max(inverseOrderColumnName, { as: 'max' })
1061
- .whereIn(inverseJoinColumn.name, nonExistingRelsIds)
1062
- .where(joinTable.on || {})
1063
- .groupBy(inverseJoinColumn.name)
1064
- .from(joinTable.name)
1065
- .transacting(trx);
1066
-
1067
- const maxMap = maxResults.reduce(
1068
- (acc, res) => Object.assign(acc, { [res[inverseJoinColumn.name]]: res.max }),
1069
- {}
1070
- );
1071
-
1072
- insert.forEach((row) => {
1073
- row[inverseOrderColumnName] = (maxMap[row[inverseJoinColumn.name]] || 0) + 1;
1074
- });
1075
- }
1076
-
1077
- // insert rows
1078
- const query = this.createQueryBuilder(joinTable.name)
1079
- .insert(insert)
1080
- .onConflict(joinTable.pivotColumns)
1081
- .transacting(trx);
1082
-
1083
- if (hasOrderColumn(attribute)) {
1084
- query.merge([orderColumnName]);
1085
- } else {
1086
- query.ignore();
1087
- }
1088
-
1089
- await query.execute();
1090
- }
1091
-
1092
- // Delete the previous relations for oneToAny relations
1093
- if (isBidirectional(attribute) && isOneToAny(attribute)) {
1094
- await deletePreviousOneToAnyRelations({
1095
- id,
1096
- attribute,
1097
- relIdsToadd: relIdsToaddOrMove,
1098
- db,
1099
- transaction: trx,
1100
- });
1101
- }
1102
-
1103
- // Delete the previous relations for anyToOne relations
1104
- if (isAnyToOne(attribute)) {
1105
- await deletePreviousAnyToOneRelations({
1106
- id,
1107
- attribute,
1108
- relIdToadd: relIdsToaddOrMove[0],
1109
- db,
1110
- transaction: trx,
1111
- });
1112
- }
1113
- }
1114
- }
1115
- }
1116
- },
1117
-
1118
- /**
1119
- * Delete relational associations of an existing entity
1120
- * This removes associations but doesn't do cascade deletions for components for example. This will be handled on the entity service layer instead
1121
- * NOTE: Most of the deletion should be handled by ON DELETE CASCADE for dialects that have FKs
1122
- *
1123
- * @param {EntityManager} em - entity manager instance
1124
- * @param {Metadata} metadata - model metadta
1125
- * @param {ID} id - entity ID
1126
- */
1127
- async deleteRelations(uid, id, { transaction: trx }) {
1128
- const { attributes } = db.metadata.get(uid);
1129
-
1130
- for (const attributeName of Object.keys(attributes)) {
1131
- const attribute = attributes[attributeName];
1132
-
1133
- if (attribute.type !== 'relation') {
1134
- continue;
1135
- }
1136
-
1137
- /*
1138
- if morphOne | morphMany
1139
- if morphBy is morphToOne
1140
- set null
1141
- if morphBy is morphToOne
1142
- delete links
1143
- */
1144
- if (attribute.relation === 'morphOne' || attribute.relation === 'morphMany') {
1145
- const { target, morphBy } = attribute;
1146
-
1147
- const targetAttribute = db.metadata.get(target).attributes[morphBy];
1148
-
1149
- if (targetAttribute.relation === 'morphToOne') {
1150
- // set columns
1151
- const { idColumn, typeColumn } = targetAttribute.morphColumn;
1152
-
1153
- await this.createQueryBuilder(target)
1154
- .update({ [idColumn.name]: null, [typeColumn.name]: null })
1155
- .where({ [idColumn.name]: id, [typeColumn.name]: uid })
1156
- .transacting(trx)
1157
- .execute();
1158
- } else if (targetAttribute.relation === 'morphToMany') {
1159
- const { joinTable } = targetAttribute;
1160
- const { morphColumn } = joinTable;
1161
-
1162
- const { idColumn, typeColumn } = morphColumn;
1163
-
1164
- await this.createQueryBuilder(joinTable.name)
1165
- .delete()
1166
- .where({
1167
- [idColumn.name]: id,
1168
- [typeColumn.name]: uid,
1169
- ...(joinTable.on || {}),
1170
- field: attributeName,
1171
- })
1172
- .transacting(trx)
1173
- .execute();
1174
- }
1175
-
1176
- continue;
1177
- }
1178
-
1179
- /*
1180
- if morphToOne
1181
- nothing to do
1182
- */
1183
- if (attribute.relation === 'morphToOne') {
1184
- // do nothing
1185
- }
1186
-
1187
- /*
1188
- if morphToMany
1189
- delete links
1190
- */
1191
- if (attribute.relation === 'morphToMany') {
1192
- const { joinTable } = attribute;
1193
- const { joinColumn } = joinTable;
1194
-
1195
- await this.createQueryBuilder(joinTable.name)
1196
- .delete()
1197
- .where({
1198
- [joinColumn.name]: id,
1199
- ...(joinTable.on || {}),
1200
- })
1201
- .transacting(trx)
1202
- .execute();
1203
-
1204
- continue;
1205
- }
1206
-
1207
- // do not need to delete links when using foreign keys
1208
- if (db.dialect.usesForeignKeys()) {
1209
- return;
1210
- }
1211
-
1212
- // NOTE: we do not remove existing associations with the target as it should handled by unique FKs instead
1213
- if (attribute.joinColumn && attribute.owner) {
1214
- // nothing to do => relation already added on the table
1215
- continue;
1216
- }
1217
-
1218
- // oneToOne oneToMany on the non owning side.
1219
- if (attribute.joinColumn && !attribute.owner) {
1220
- // need to set the column on the target
1221
- const { target } = attribute;
1222
-
1223
- await this.createQueryBuilder(target)
1224
- .where({ [attribute.joinColumn.referencedColumn]: id })
1225
- .update({ [attribute.joinColumn.referencedColumn]: null })
1226
- .transacting(trx)
1227
- .execute();
1228
- }
1229
-
1230
- if (attribute.joinTable) {
1231
- await deleteRelations({ id, attribute, db, relIdsToDelete: 'all', transaction: trx });
1232
- }
1233
- }
1234
- },
1235
-
1236
- // TODO: Clone polymorphic relations
1237
- /**
1238
- *
1239
- * @param {string} uid - uid of the entity to clone
1240
- * @param {number} targetId - id of the entity to clone into
1241
- * @param {number} sourceId - id of the entity to clone from
1242
- * @param {object} opt
1243
- * @param {object} opt.cloneAttrs - key value pair of attributes to clone
1244
- * @param {object} opt.transaction - transaction to use
1245
- * @example cloneRelations('user', 3, 1, { cloneAttrs: ["comments"]})
1246
- * @example cloneRelations('post', 5, 2, { cloneAttrs: ["comments", "likes"] })
1247
- */
1248
- async cloneRelations(uid, targetId, sourceId, data, { cloneAttrs = [], transaction }) {
1249
- const { attributes } = db.metadata.get(uid);
1250
-
1251
- if (!attributes) {
1252
- return;
1253
- }
1254
-
1255
- await mapAsync(cloneAttrs, async (attrName) => {
1256
- const attribute = attributes[attrName];
1257
-
1258
- if (attribute.type !== 'relation') {
1259
- throw new DatabaseError(
1260
- `Attribute ${attrName} is not a relation attribute. Cloning relations is only supported for relation attributes.`
1261
- );
1262
- }
1263
-
1264
- if (isPolymorphic(attribute)) {
1265
- // TODO: add support for cloning polymorphic relations
1266
- return;
1267
- }
1268
-
1269
- if (attribute.joinColumn) {
1270
- // TODO: add support for cloning oneToMany relations on the owning side
1271
- return;
1272
- }
1273
-
1274
- if (!attribute.joinTable) {
1275
- return;
1276
- }
1277
-
1278
- let omitIds = [];
1279
- if (has(attrName, data)) {
1280
- const cleanRelationData = toAssocs(data[attrName]);
1281
-
1282
- // Don't clone if the relation attr is being set
1283
- if (cleanRelationData.set) {
1284
- return;
1285
- }
1286
-
1287
- // Disconnected relations don't need to be cloned
1288
- if (cleanRelationData.disconnect) {
1289
- omitIds = toIds(cleanRelationData.disconnect);
1290
- }
1291
- }
1292
-
1293
- if (isOneToAny(attribute) && isBidirectional(attribute)) {
1294
- await replaceRegularRelations({ targetId, sourceId, attribute, omitIds, transaction });
1295
- } else {
1296
- await cloneRegularRelations({ targetId, sourceId, attribute, transaction });
1297
- }
1298
- });
1299
-
1300
- await this.updateRelations(uid, targetId, data, { transaction });
1301
- },
1302
-
1303
- // TODO: add lifecycle events
1304
- async populate(uid, entity, populate) {
1305
- const entry = await this.findOne(uid, {
1306
- select: ['id'],
1307
- where: { id: entity.id },
1308
- populate,
1309
- });
1310
-
1311
- return { ...entity, ...entry };
1312
- },
1313
-
1314
- // TODO: add lifecycle events
1315
- async load(uid, entity, fields, params) {
1316
- const { attributes } = db.metadata.get(uid);
1317
-
1318
- const fieldsArr = castArray(fields);
1319
- fieldsArr.forEach((field) => {
1320
- const attribute = attributes[field];
1321
-
1322
- if (!attribute || attribute.type !== 'relation') {
1323
- throw new Error(`Invalid load. Expected ${field} to be a relational attribute`);
1324
- }
1325
- });
1326
-
1327
- const entry = await this.findOne(uid, {
1328
- select: ['id'],
1329
- where: { id: entity.id },
1330
- populate: fieldsArr.reduce((acc, field) => {
1331
- acc[field] = params || true;
1332
- return acc;
1333
- }, {}),
1334
- });
1335
-
1336
- if (!entry) {
1337
- return null;
1338
- }
1339
-
1340
- if (Array.isArray(fields)) {
1341
- return pick(fields, entry);
1342
- }
1343
-
1344
- return entry[fields];
1345
- },
1346
-
1347
- // cascading
1348
- // aggregations
1349
- // -> avg
1350
- // -> min
1351
- // -> max
1352
- // -> grouping
1353
-
1354
- // formulas
1355
- // custom queries
1356
-
1357
- // utilities
1358
- // -> map result
1359
- // -> map input
1360
-
1361
- // extra features
1362
- // -> virtuals
1363
- // -> private
1364
-
1365
- createQueryBuilder(uid) {
1366
- return createQueryBuilder(uid, db);
1367
- },
1368
-
1369
- getRepository(uid) {
1370
- if (!repoMap[uid]) {
1371
- repoMap[uid] = createRepository(uid, db);
1372
- }
1373
-
1374
- return repoMap[uid];
1375
- },
1376
-
1377
- clearRepositories() {
1378
- repoMap.clear();
1379
- },
1380
- };
1381
- };
1382
-
1383
- module.exports = {
1384
- createEntityManager,
1385
- };