@strapi/database 4.10.0-beta.0 → 4.10.0-beta.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.
package/.eslintignore ADDED
@@ -0,0 +1,2 @@
1
+ node_modules/
2
+ .eslintrc.js
package/.eslintrc.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ root: true,
3
+ extends: ['custom/back'],
4
+ };
package/jest.config.js CHANGED
@@ -1,10 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const baseConfig = require('../../../jest.base-config');
4
- const pkg = require('./package.json');
5
-
6
3
  module.exports = {
7
- ...baseConfig,
8
- displayName: (pkg.strapi && pkg.strapi.name) || pkg.name,
9
- roots: [__dirname],
4
+ preset: '../../../jest-preset.unit.js',
10
5
  };
@@ -1,5 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ * Require our dialect-specific code
5
+ */
3
6
  const getDialectClass = (client) => {
4
7
  switch (client) {
5
8
  case 'postgres':
@@ -13,12 +16,34 @@ const getDialectClass = (client) => {
13
16
  }
14
17
  };
15
18
 
19
+ /**
20
+ * Get the dialect of a database client
21
+ *
22
+ * @param {string} The client value from a project database configuration
23
+ * @returns {string} The dialect of that client
24
+ */
25
+ const getDialectName = (client) => {
26
+ switch (client) {
27
+ case 'postgres':
28
+ return 'postgres';
29
+ case 'mysql':
30
+ case 'mysql2':
31
+ return 'mysql';
32
+ case 'sqlite':
33
+ case 'sqlite-legacy':
34
+ return 'sqlite';
35
+ default:
36
+ throw new Error(`Unknown dialect ${client}`);
37
+ }
38
+ };
39
+
16
40
  const getDialect = (db) => {
17
41
  const { client } = db.config.connection;
42
+ const dialectName = getDialectName(client);
18
43
 
19
- const constructor = getDialectClass(client);
44
+ const constructor = getDialectClass(dialectName);
20
45
  const dialect = new constructor(db);
21
- dialect.client = client;
46
+ dialect.client = dialectName;
22
47
 
23
48
  return dialect;
24
49
  };
@@ -22,12 +22,6 @@ class PostgresDialect extends Dialect {
22
22
  'text',
23
23
  (v) => v
24
24
  );
25
- // Don't parse JSONB automatically
26
- this.db.connection.client.driver.types.setTypeParser(
27
- this.db.connection.client.driver.types.builtins.JSONB,
28
- 'text',
29
- (v) => v
30
- );
31
25
  this.db.connection.client.driver.types.setTypeParser(
32
26
  this.db.connection.client.driver.types.builtins.NUMERIC,
33
27
  'text',
@@ -121,6 +121,28 @@ describe('Given I have some relations in the database', () => {
121
121
  ]);
122
122
  });
123
123
  });
124
+
125
+ describe('When you connect a relation before one with null order', () => {
126
+ test('Then it replaces null order values to 1 and properly reorders relations', () => {
127
+ const orderer = relationsOrderer(
128
+ [
129
+ { id: 2, order: null },
130
+ { id: 3, order: null },
131
+ ],
132
+ 'id',
133
+ 'order'
134
+ );
135
+
136
+ orderer.connect([{ id: 4, position: { before: 3 } }, { id: 5 }]);
137
+
138
+ expect(orderer.get()).toMatchObject([
139
+ { id: 2, order: 1 },
140
+ { id: 4, order: 0.5 },
141
+ { id: 3, order: 1 },
142
+ { id: 5, order: 1.5 },
143
+ ]);
144
+ });
145
+ });
124
146
  });
125
147
 
126
148
  describe('Given there are no relations in the database', () => {
@@ -268,9 +268,12 @@ const createEntityManager = (db) => {
268
268
  throw new Error('Nothing to insert');
269
269
  }
270
270
 
271
- await this.createQueryBuilder(uid).insert(dataToInsert).execute();
271
+ const createdEntries = await this.createQueryBuilder(uid).insert(dataToInsert).execute();
272
272
 
273
- const result = { count: data.length };
273
+ const result = {
274
+ count: data.length,
275
+ ids: createdEntries.map((entry) => (typeof entry === 'object' ? entry?.id : entry)),
276
+ };
274
277
 
275
278
  await db.lifecycles.run('afterCreateMany', uid, { params, result }, states);
276
279
 
@@ -135,7 +135,7 @@ const relationsOrderer = (initArr, idColumn, orderColumn, strict) => {
135
135
  const computedRelations = _.castArray(initArr || []).map((r) => ({
136
136
  init: true,
137
137
  id: r[idColumn],
138
- order: r[orderColumn],
138
+ order: r[orderColumn] || 1,
139
139
  }));
140
140
 
141
141
  const maxOrder = _.maxBy('order', computedRelations)?.order || 0;
@@ -8,7 +8,12 @@ class JSONField extends Field {
8
8
  }
9
9
 
10
10
  fromDB(value) {
11
- if (typeof value === 'string') return JSON.parse(value);
11
+ try {
12
+ if (typeof value === 'string') return JSON.parse(value);
13
+ } catch (error) {
14
+ // Just return the value if it's not a valid JSON string
15
+ return value;
16
+ }
12
17
  return value;
13
18
  }
14
19
  }
package/lib/index.js CHANGED
@@ -57,15 +57,18 @@ class Database {
57
57
 
58
58
  async function commit() {
59
59
  if (notNestedTransaction) {
60
+ transactionCtx.clear();
60
61
  await trx.commit();
61
62
  }
62
63
  }
63
64
 
64
65
  async function rollback() {
65
66
  if (notNestedTransaction) {
67
+ transactionCtx.clear();
66
68
  await trx.rollback();
67
69
  }
68
70
  }
71
+
69
72
  if (!cb) {
70
73
  return {
71
74
  commit,
@@ -344,6 +344,13 @@ module.exports = (db) => {
344
344
  }
345
345
  }
346
346
 
347
+ const parsePersistedTable = (persistedTable) => {
348
+ if (typeof persistedTable === 'string') {
349
+ return persistedTable;
350
+ }
351
+ return persistedTable.name;
352
+ };
353
+
347
354
  const persistedTables = helpers.hasTable(srcSchema, 'strapi_core_store_settings')
348
355
  ? (await strapi.store.get({
349
356
  type: 'core',
@@ -351,12 +358,19 @@ module.exports = (db) => {
351
358
  })) ?? []
352
359
  : [];
353
360
 
361
+ const reservedTables = [...RESERVED_TABLE_NAMES, ...persistedTables.map(parsePersistedTable)];
362
+
354
363
  for (const srcTable of srcSchema.tables) {
355
- if (
356
- !helpers.hasTable(destSchema, srcTable.name) &&
357
- ![...RESERVED_TABLE_NAMES, ...persistedTables].includes(srcTable.name)
358
- ) {
359
- removedTables.push(srcTable);
364
+ if (!helpers.hasTable(destSchema, srcTable.name) && !reservedTables.includes(srcTable.name)) {
365
+ const dependencies = persistedTables
366
+ .filter((table) => {
367
+ return table?.dependsOn?.some((table) => table.name === srcTable.name);
368
+ })
369
+ .map((dependsOnTable) => {
370
+ return srcSchema.tables.find((srcTable) => srcTable.name === dependsOnTable.name);
371
+ });
372
+
373
+ removedTables.push(srcTable, ...dependencies);
360
374
  }
361
375
  }
362
376
 
@@ -6,11 +6,19 @@ const storage = new AsyncLocalStorage();
6
6
 
7
7
  const transactionCtx = {
8
8
  async run(store, cb) {
9
- return storage.run(store, cb);
9
+ return storage.run({ trx: store }, cb);
10
10
  },
11
11
 
12
12
  get() {
13
- return storage.getStore();
13
+ const store = storage.getStore();
14
+ return store?.trx;
15
+ },
16
+
17
+ clear() {
18
+ const store = storage.getStore();
19
+ if (store?.trx) {
20
+ store.trx = null;
21
+ }
14
22
  },
15
23
  };
16
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strapi/database",
3
- "version": "4.10.0-beta.0",
3
+ "version": "4.10.0-beta.1",
4
4
  "description": "Strapi's database layer",
5
5
  "homepage": "https://strapi.io",
6
6
  "bugs": {
@@ -28,7 +28,9 @@
28
28
  "lib": "./lib"
29
29
  },
30
30
  "scripts": {
31
- "test:unit": "jest --verbose"
31
+ "test:unit": "run -T jest",
32
+ "test:unit:watch": "run -T jest --watch",
33
+ "lint": "run -T eslint ."
32
34
  },
33
35
  "dependencies": {
34
36
  "date-fns": "2.29.3",
@@ -43,5 +45,5 @@
43
45
  "node": ">=14.19.1 <=18.x.x",
44
46
  "npm": ">=6.0.0"
45
47
  },
46
- "gitHead": "1519ef0e56d27b738f24fc88223797651ad47aaf"
48
+ "gitHead": "95d581b31bee464af42e5d8db408fa578d8532c7"
47
49
  }
@@ -1,33 +0,0 @@
1
- 'use strict';
2
-
3
- const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
4
- const { isKnexQuery } = require('../utils/knex');
5
-
6
- let strapi;
7
-
8
- describe('knex', () => {
9
- beforeAll(async () => {
10
- strapi = await createStrapiInstance();
11
- });
12
-
13
- afterAll(async () => {
14
- await strapi.destroy();
15
- });
16
-
17
- describe('isKnexQuery', () => {
18
- test('knex query: true', () => {
19
- const res = isKnexQuery(strapi.db.connection('strapi_core_store_settings'));
20
- expect(res).toBe(true);
21
- });
22
-
23
- test('knex raw: true', () => {
24
- const res = isKnexQuery(strapi.db.connection.raw('SELECT * FROM strapi_core_store_settings'));
25
- expect(res).toBe(true);
26
- });
27
-
28
- test.each([[''], [{}], [[]], [2], [new Date()]])('%s: false', (value) => {
29
- const res = isKnexQuery(value);
30
- expect(res).toBe(false);
31
- });
32
- });
33
- });
@@ -1,308 +0,0 @@
1
- 'use strict';
2
-
3
- const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
4
-
5
- let strapi;
6
-
7
- describe('transactions', () => {
8
- let original;
9
- beforeAll(async () => {
10
- strapi = await createStrapiInstance();
11
- original = await strapi.db
12
- .queryBuilder('strapi::core-store')
13
- .select(['*'])
14
- .where({ id: 1 })
15
- .execute();
16
- });
17
-
18
- afterAll(async () => {
19
- await strapi.destroy();
20
- });
21
-
22
- afterEach(async () => {
23
- await strapi.db
24
- .queryBuilder('strapi::core-store')
25
- .update({
26
- key: original[0].key,
27
- })
28
- .where({ id: 1 })
29
- .execute();
30
- });
31
-
32
- describe('using a transaction method', () => {
33
- test('commits successfully', async () => {
34
- await strapi.db.transaction(async () => {
35
- await strapi.db
36
- .queryBuilder('strapi::core-store')
37
- .update({
38
- key: 'wrong key',
39
- })
40
- .where({ id: 1 })
41
- .execute();
42
-
43
- await strapi.db
44
- .queryBuilder('strapi::core-store')
45
- .update({
46
- key: 'new key',
47
- })
48
- .where({ id: 1 })
49
- .execute();
50
- });
51
-
52
- const end = await strapi.db
53
- .queryBuilder('strapi::core-store')
54
- .select(['*'])
55
- .where({ id: 1 })
56
- .execute();
57
-
58
- expect(end[0].key).toEqual('new key');
59
- });
60
-
61
- test('rollback successfully', async () => {
62
- try {
63
- await strapi.db.transaction(async () => {
64
- // this is valid
65
- await strapi.db
66
- .queryBuilder('strapi::core-store')
67
- .update({
68
- key: 'wrong key',
69
- })
70
- .where({ id: 1 })
71
- .execute();
72
-
73
- // this throws
74
- await strapi.db
75
- .queryBuilder('invalid_uid')
76
- .update({
77
- key: 'bad key',
78
- invalid_key: 'error',
79
- })
80
- .where({ id: 1 })
81
- .execute();
82
- });
83
-
84
- expect('this should not be reached').toBe(false);
85
- } catch (e) {
86
- // do nothing
87
- }
88
-
89
- const end = await strapi.db
90
- .queryBuilder('strapi::core-store')
91
- .select(['*'])
92
- .where({ id: 1 })
93
- .execute();
94
-
95
- expect(end[0].key).toEqual(original[0].key);
96
- });
97
-
98
- test('nested rollback -> rollback works', async () => {
99
- try {
100
- await strapi.db.transaction(async () => {
101
- // this is valid
102
- await strapi.db
103
- .queryBuilder('strapi::core-store')
104
- .update({
105
- key: 'changed key',
106
- })
107
- .where({ id: 1 })
108
- .execute();
109
-
110
- // here we'll make a nested transaction that throws and then confirm we still have "changed key" from above
111
- try {
112
- await strapi.db.transaction(async () => {
113
- await strapi.db
114
- .queryBuilder('strapi::core-store')
115
- .update({
116
- key: 'changed key - nested',
117
- })
118
- .where({ id: 1 })
119
- .execute();
120
-
121
- // this should throw and roll back
122
- await strapi.db
123
- .queryBuilder('invalid_uid')
124
- .update({
125
- invalid_key: 'error',
126
- })
127
- .where({ id: 1 })
128
- .execute();
129
- });
130
- } catch (e) {
131
- // do nothing
132
- }
133
-
134
- // should equal the result from above
135
- const result = await strapi.db
136
- .queryBuilder('strapi::core-store')
137
- .select(['*'])
138
- .where({ id: 1 })
139
- .execute();
140
-
141
- expect(result[0].key).toEqual('changed key');
142
-
143
- // this throws
144
- await strapi.db
145
- .queryBuilder('invalid_uid')
146
- .update({
147
- key: original[0].key,
148
- invalid_key: 'error',
149
- })
150
- .where({ id: 1 })
151
- .execute();
152
- });
153
-
154
- expect('this should not be reached').toBe(false);
155
- } catch (e) {
156
- // do nothing
157
- }
158
-
159
- const end = await strapi.db
160
- .queryBuilder('strapi::core-store')
161
- .select(['*'])
162
- .where({ id: 1 })
163
- .execute();
164
-
165
- expect(end[0].key).toEqual(original[0].key);
166
- });
167
-
168
- test('nested commit -> rollback works', async () => {
169
- try {
170
- await strapi.db.transaction(async () => {
171
- // this is valid
172
- await strapi.db
173
- .queryBuilder('strapi::core-store')
174
- .update({
175
- key: 'changed key',
176
- })
177
- .where({ id: 1 })
178
- .execute();
179
-
180
- // here we'll make a nested transaction that works, and then later we'll rollback the outer transaction
181
- try {
182
- await strapi.db.transaction(async () => {
183
- await strapi.db
184
- .queryBuilder('strapi::core-store')
185
- .update({
186
- key: 'changed key - nested',
187
- })
188
- .where({ id: 1 })
189
- .execute();
190
- });
191
- } catch (e) {
192
- // do nothing
193
- }
194
-
195
- // should equal the result from above
196
- const result = await strapi.db
197
- .queryBuilder('strapi::core-store')
198
- .select(['*'])
199
- .where({ id: 1 })
200
- .execute();
201
-
202
- expect(result[0].key).toEqual('changed key - nested');
203
-
204
- // this throws
205
- await strapi.db
206
- .queryBuilder('invalid_uid')
207
- .update({
208
- key: original[0].key,
209
- invalid_key: 'error',
210
- })
211
- .where({ id: 1 })
212
- .execute();
213
- });
214
-
215
- expect('this should not be reached').toBe(false);
216
- } catch (e) {
217
- // do nothing
218
- }
219
-
220
- const end = await strapi.db
221
- .queryBuilder('strapi::core-store')
222
- .select(['*'])
223
- .where({ id: 1 })
224
- .execute();
225
-
226
- expect(end[0].key).toEqual(original[0].key);
227
- });
228
- });
229
-
230
- describe('using a transaction object', () => {
231
- test('commits successfully', async () => {
232
- const trx = await strapi.db.transaction();
233
-
234
- try {
235
- await strapi.db
236
- .queryBuilder('strapi::core-store')
237
- .update({
238
- key: 'wrong key',
239
- })
240
- .where({ id: 1 })
241
- .transacting(trx.get())
242
- .execute();
243
-
244
- await strapi.db
245
- .queryBuilder('strapi::core-store')
246
- .update({
247
- key: original[0].key,
248
- })
249
- .where({ id: 1 })
250
- .transacting(trx.get())
251
- .execute();
252
-
253
- await trx.commit();
254
- } catch (e) {
255
- await trx.rollback();
256
- console.log(e.message);
257
- expect('this should not be reached').toBe(false);
258
- }
259
-
260
- const end = await strapi.db
261
- .queryBuilder('strapi::core-store')
262
- .select(['*'])
263
- .where({ id: 1 })
264
- .execute();
265
-
266
- expect(end[0].key).toEqual('strapi_content_types_schema');
267
- });
268
-
269
- test('rollback successfully', async () => {
270
- const trx = await strapi.db.transaction();
271
-
272
- try {
273
- await strapi.db
274
- .queryBuilder('strapi::core-store')
275
- .update({
276
- key: 'wrong key',
277
- })
278
- .where({ id: 1 })
279
- .transacting(trx.get())
280
- .execute();
281
-
282
- // this query should throw because it has errors
283
- await strapi.db
284
- .queryBuilder('invalid_uid')
285
- .update({
286
- key: 123,
287
- key_not_here: 'this should error',
288
- })
289
- .where({ id: 'this should error' })
290
- .transacting(trx.get())
291
- .execute();
292
-
293
- await trx.commit();
294
- expect('this should not be reached').toBe(false);
295
- } catch (e) {
296
- await trx.rollback();
297
- }
298
-
299
- const end = await strapi.db
300
- .queryBuilder('strapi::core-store')
301
- .select(['*'])
302
- .where({ id: 1 })
303
- .execute();
304
-
305
- expect(end[0].key).toEqual('strapi_content_types_schema');
306
- });
307
- });
308
- });