@nocobase/database 0.7.0-alpha.82 → 0.7.1-alpha.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.
Files changed (54) hide show
  1. package/lib/collection-importer.js +25 -42
  2. package/lib/collection.d.ts +6 -2
  3. package/lib/collection.js +37 -5
  4. package/lib/database.d.ts +21 -5
  5. package/lib/database.js +161 -49
  6. package/lib/fields/field.d.ts +4 -1
  7. package/lib/fields/field.js +117 -0
  8. package/lib/fields/formula-field.d.ts +19 -0
  9. package/lib/fields/formula-field.js +184 -0
  10. package/lib/fields/index.d.ts +3 -1
  11. package/lib/fields/index.js +13 -0
  12. package/lib/index.d.ts +1 -0
  13. package/lib/index.js +14 -0
  14. package/lib/migration.d.ts +35 -0
  15. package/lib/migration.js +90 -0
  16. package/lib/mock-database.d.ts +1 -0
  17. package/lib/mock-database.js +2 -1
  18. package/lib/model-hook.d.ts +5 -5
  19. package/lib/model-hook.js +26 -18
  20. package/lib/options-parser.js +65 -43
  21. package/lib/relation-repository/relation-repository.js +11 -1
  22. package/lib/relation-repository/single-relation-repository.js +8 -1
  23. package/lib/repository.js +12 -4
  24. package/lib/update-associations.js +1 -1
  25. package/package.json +9 -4
  26. package/src/__tests__/collection.test.ts +27 -0
  27. package/src/__tests__/database.test.ts +47 -0
  28. package/src/__tests__/fields/formula-field.test.ts +69 -0
  29. package/src/__tests__/fixtures/migrations/m1.ts +7 -0
  30. package/src/__tests__/fixtures/migrations/m2.ts +7 -0
  31. package/src/__tests__/hooks/afterCreateWithAssociations.test.ts +33 -0
  32. package/src/__tests__/migrator.test.ts +70 -0
  33. package/src/__tests__/model-hook.test.ts +54 -0
  34. package/src/__tests__/option-parser.test.ts +10 -6
  35. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +1 -1
  36. package/src/__tests__/sequelize-hooks.test.ts +69 -0
  37. package/src/__tests__/sort.test.ts +51 -0
  38. package/src/__tests__/update-associations.test.ts +3 -3
  39. package/src/collection-importer.ts +12 -20
  40. package/src/collection.ts +26 -2
  41. package/src/database.ts +112 -29
  42. package/src/fields/field.ts +88 -1
  43. package/src/fields/formula-field.ts +106 -0
  44. package/src/fields/index.ts +3 -0
  45. package/src/index.ts +1 -0
  46. package/src/migration.ts +76 -0
  47. package/src/mock-database.ts +1 -0
  48. package/src/model-hook.ts +25 -21
  49. package/src/options-parser.ts +13 -9
  50. package/src/relation-repository/multiple-relation-repository.ts +8 -2
  51. package/src/relation-repository/relation-repository.ts +1 -0
  52. package/src/relation-repository/single-relation-repository.ts +5 -1
  53. package/src/repository.ts +16 -4
  54. package/src/update-associations.ts +1 -1
@@ -19,18 +19,18 @@ var _filterParser = _interopRequireDefault(require("./filter-parser"));
19
19
 
20
20
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
21
 
22
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
23
-
24
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
25
-
26
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
27
-
28
22
  function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
29
23
 
30
24
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
31
25
 
32
26
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
33
27
 
28
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
29
+
30
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
31
+
32
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
33
+
34
34
  const debug = require('debug')('noco-database');
35
35
 
36
36
  class OptionsParser {
@@ -94,23 +94,45 @@ class OptionsParser {
94
94
  sort = sort.split(',');
95
95
  }
96
96
 
97
- const orderParams = sort.map(sortKey => {
98
- const direction = sortKey.startsWith('-') ? 'DESC' : 'ASC';
99
- const sortField = sortKey.replace('-', '').split('.'); // handle sort by association
97
+ const orderParams = [];
98
+
99
+ var _iterator = _createForOfIteratorHelper(sort),
100
+ _step;
101
+
102
+ try {
103
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
104
+ const sortKey = _step.value;
105
+ let direction = sortKey.startsWith('-') ? 'DESC' : 'ASC';
106
+ let sortField = sortKey.replace('-', '').split('.');
107
+
108
+ if (this.database.inDialect('postgres', 'sqlite')) {
109
+ direction = `${direction} NULLS LAST`;
110
+ } // handle sort by association
111
+
100
112
 
101
- if (sortField.length > 1) {
102
- let associationModel = this.model;
113
+ if (sortField.length > 1) {
114
+ let associationModel = this.model;
103
115
 
104
- for (let i = 0; i < sortField.length - 1; i++) {
105
- const associationKey = sortField[i];
106
- sortField[i] = associationModel.associations[associationKey].target;
107
- associationModel = sortField[i];
116
+ for (let i = 0; i < sortField.length - 1; i++) {
117
+ const associationKey = sortField[i];
118
+ sortField[i] = associationModel.associations[associationKey].target;
119
+ associationModel = sortField[i];
120
+ }
108
121
  }
109
- }
110
122
 
111
- sortField.push(direction);
112
- return sortField;
113
- });
123
+ sortField.push(direction);
124
+
125
+ if (this.database.inDialect('mysql')) {
126
+ orderParams.push([_sequelize().Sequelize.fn('ISNULL', _sequelize().Sequelize.col(`${this.model.name}.${sortField[0]}`))]);
127
+ }
128
+
129
+ orderParams.push(sortField);
130
+ }
131
+ } catch (err) {
132
+ _iterator.e(err);
133
+ } finally {
134
+ _iterator.f();
135
+ }
114
136
 
115
137
  if (orderParams.length > 0) {
116
138
  return _objectSpread({
@@ -133,12 +155,12 @@ class OptionsParser {
133
155
 
134
156
  if ((_this$options4 = this.options) === null || _this$options4 === void 0 ? void 0 : _this$options4.fields) {
135
157
  // 将fields拆分为 attributes 和 appends
136
- var _iterator = _createForOfIteratorHelper(this.options.fields),
137
- _step;
158
+ var _iterator2 = _createForOfIteratorHelper(this.options.fields),
159
+ _step2;
138
160
 
139
161
  try {
140
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
141
- const field = _step.value;
162
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
163
+ const field = _step2.value;
142
164
 
143
165
  if (this.isAssociationPath(field)) {
144
166
  // field is association field
@@ -150,19 +172,19 @@ class OptionsParser {
150
172
  }
151
173
  }
152
174
  } catch (err) {
153
- _iterator.e(err);
175
+ _iterator2.e(err);
154
176
  } finally {
155
- _iterator.f();
177
+ _iterator2.f();
156
178
  }
157
179
  }
158
180
 
159
181
  if ((_this$options5 = this.options) === null || _this$options5 === void 0 ? void 0 : _this$options5.except) {
160
- var _iterator2 = _createForOfIteratorHelper(this.options.except),
161
- _step2;
182
+ var _iterator3 = _createForOfIteratorHelper(this.options.except),
183
+ _step3;
162
184
 
163
185
  try {
164
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
165
- const exceptKey = _step2.value;
186
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
187
+ const exceptKey = _step3.value;
166
188
 
167
189
  if (this.isAssociationPath(exceptKey)) {
168
190
  // except association field
@@ -174,9 +196,9 @@ class OptionsParser {
174
196
  }
175
197
  }
176
198
  } catch (err) {
177
- _iterator2.e(err);
199
+ _iterator3.e(err);
178
200
  } finally {
179
- _iterator2.f();
201
+ _iterator3.f();
180
202
  }
181
203
  }
182
204
 
@@ -218,18 +240,18 @@ class OptionsParser {
218
240
  }
219
241
  };
220
242
 
221
- var _iterator3 = _createForOfIteratorHelper(except),
222
- _step3;
243
+ var _iterator4 = _createForOfIteratorHelper(except),
244
+ _step4;
223
245
 
224
246
  try {
225
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
226
- const exceptKey = _step3.value;
247
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
248
+ const exceptKey = _step4.value;
227
249
  setExcept(filterParams, exceptKey);
228
250
  }
229
251
  } catch (err) {
230
- _iterator3.e(err);
252
+ _iterator4.e(err);
231
253
  } finally {
232
- _iterator3.f();
254
+ _iterator4.f();
233
255
  }
234
256
 
235
257
  return filterParams;
@@ -317,18 +339,18 @@ class OptionsParser {
317
339
  }; // handle every appends
318
340
 
319
341
 
320
- var _iterator4 = _createForOfIteratorHelper(appends),
321
- _step4;
342
+ var _iterator5 = _createForOfIteratorHelper(appends),
343
+ _step5;
322
344
 
323
345
  try {
324
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
325
- const append = _step4.value;
346
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
347
+ const append = _step5.value;
326
348
  setInclude(this.model, filterParams, append);
327
349
  }
328
350
  } catch (err) {
329
- _iterator4.e(err);
351
+ _iterator5.e(err);
330
352
  } finally {
331
- _iterator4.f();
353
+ _iterator5.f();
332
354
  }
333
355
 
334
356
  debug('filter params: %o', filterParams);
@@ -37,6 +37,14 @@ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try
37
37
 
38
38
  function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
39
39
 
40
+ var __decorate = void 0 && (void 0).__decorate || function (decorators, target, key, desc) {
41
+ var c = arguments.length,
42
+ r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
43
+ d;
44
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
45
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
46
+ };
47
+
40
48
  const transaction = (0, _transactionDecorator.transactionWrapperBuilder)(function () {
41
49
  return this.sourceCollection.model.sequelize.transaction();
42
50
  });
@@ -157,4 +165,6 @@ class RelationRepository {
157
165
 
158
166
  }
159
167
 
160
- exports.RelationRepository = RelationRepository;
168
+ exports.RelationRepository = RelationRepository;
169
+
170
+ __decorate([transaction()], RelationRepository.prototype, "create", null);
@@ -86,7 +86,9 @@ class SingleRelationRepository extends _relationRepository.RelationRepository {
86
86
  var _this4 = this;
87
87
 
88
88
  return _asyncToGenerator(function* () {
89
- return _this4.find(options);
89
+ return _this4.find(_objectSpread(_objectSpread({}, options), {}, {
90
+ filterByTk: null
91
+ }));
90
92
  })();
91
93
  }
92
94
 
@@ -113,6 +115,11 @@ class SingleRelationRepository extends _relationRepository.RelationRepository {
113
115
  const target = yield _this6.find({
114
116
  transaction
115
117
  });
118
+
119
+ if (!target) {
120
+ throw new Error('The record does not exist');
121
+ }
122
+
116
123
  yield (0, _updateAssociations.updateModelByValues)(target, options === null || options === void 0 ? void 0 : options.values, _objectSpread(_objectSpread({}, _lodash().default.omit(options, 'values')), {}, {
117
124
  transaction
118
125
  }));
package/lib/repository.js CHANGED
@@ -257,8 +257,12 @@ class Repository {
257
257
  }));
258
258
 
259
259
  if (options.hooks !== false) {
260
- yield _this5.database.emitAsync(`${_this5.collection.name}.afterCreateWithAssociations`, instance, options);
261
- yield _this5.database.emitAsync(`${_this5.collection.name}.afterSaveWithAssociations`, instance, options);
260
+ yield _this5.database.emitAsync(`${_this5.collection.name}.afterCreateWithAssociations`, instance, _objectSpread(_objectSpread({}, options), {}, {
261
+ transaction
262
+ }));
263
+ yield _this5.database.emitAsync(`${_this5.collection.name}.afterSaveWithAssociations`, instance, _objectSpread(_objectSpread({}, options), {}, {
264
+ transaction
265
+ }));
262
266
  }
263
267
 
264
268
  return instance;
@@ -349,8 +353,12 @@ class Repository {
349
353
  try {
350
354
  for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
351
355
  const instance = _step3.value;
352
- yield _this7.database.emitAsync(`${_this7.collection.name}.afterUpdateWithAssociations`, instance, options);
353
- yield _this7.database.emitAsync(`${_this7.collection.name}.afterSaveWithAssociations`, instance, options);
356
+ yield _this7.database.emitAsync(`${_this7.collection.name}.afterUpdateWithAssociations`, instance, _objectSpread(_objectSpread({}, options), {}, {
357
+ transaction
358
+ }));
359
+ yield _this7.database.emitAsync(`${_this7.collection.name}.afterSaveWithAssociations`, instance, _objectSpread(_objectSpread({}, options), {}, {
360
+ transaction
361
+ }));
354
362
  }
355
363
  } catch (err) {
356
364
  _iterator3.e(err);
@@ -364,7 +364,7 @@ function _updateSingleAssociation() {
364
364
 
365
365
  dataKey = association.targetKey;
366
366
  } else {
367
- M = association.source;
367
+ M = association.target;
368
368
  dataKey = M.primaryKeyAttribute;
369
369
  }
370
370
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/database",
3
- "version": "0.7.0-alpha.82",
3
+ "version": "0.7.1-alpha.4",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -12,17 +12,22 @@
12
12
  }
13
13
  ],
14
14
  "dependencies": {
15
- "@nocobase/utils": "0.7.0-alpha.82",
15
+ "@nocobase/utils": "0.7.1-alpha.4",
16
16
  "async-mutex": "^0.3.2",
17
17
  "deepmerge": "^4.2.2",
18
18
  "flat": "^5.0.2",
19
19
  "glob": "^7.1.6",
20
- "sequelize": "^6.9.0"
20
+ "mathjs": "^10.6.1",
21
+ "sequelize": "^6.9.0",
22
+ "umzug": "^3.1.1"
23
+ },
24
+ "devDependencies": {
25
+ "@types/glob": "^7.2.0"
21
26
  },
22
27
  "repository": {
23
28
  "type": "git",
24
29
  "url": "git+https://github.com/nocobase/nocobase.git",
25
30
  "directory": "packages/database"
26
31
  },
27
- "gitHead": "4820fd09375c7200d1ea0bb0aab1bd4783b80d3d"
32
+ "gitHead": "570d039f1904a041729c1967c0637b1109c278a8"
28
33
  }
@@ -13,6 +13,33 @@ describe('collection', () => {
13
13
  await db.close();
14
14
  });
15
15
 
16
+ test('removeFromDb', async () => {
17
+ await db.clean({ drop: true });
18
+ const collection = db.collection({
19
+ name: 'test',
20
+ fields: [
21
+ {
22
+ type: 'string',
23
+ name: 'name',
24
+ },
25
+ ],
26
+ });
27
+ await db.sync();
28
+
29
+ const field = collection.getField('name');
30
+ const r1 = await field.existsInDb();
31
+ expect(r1).toBe(true);
32
+ await field.removeFromDb();
33
+ const r2 = await field.existsInDb();
34
+ expect(r2).toBe(false);
35
+
36
+ const r3 = await collection.existsInDb();
37
+ expect(r3).toBe(true);
38
+ await collection.removeFromDb();
39
+ const r4 = await collection.existsInDb();
40
+ expect(r4).toBe(false);
41
+ });
42
+
16
43
  test('collection disable authGenId', async () => {
17
44
  const Test = db.collection({
18
45
  name: 'test',
@@ -204,6 +204,53 @@ describe('database', () => {
204
204
  expect(listener).toHaveBeenCalled();
205
205
  });
206
206
 
207
+ test('off collection event', async () => {
208
+ const Post = db.collection({
209
+ name: 'posts',
210
+ fields: [{ type: 'string', name: 'title' }],
211
+ });
212
+
213
+ const postAfterCreateListener = jest.fn();
214
+
215
+ db.on('posts.afterCreate', postAfterCreateListener);
216
+
217
+ await db.sync();
218
+
219
+ db.off('posts.afterCreate', postAfterCreateListener);
220
+
221
+ await Post.repository.create({
222
+ values: {
223
+ title: 'test',
224
+ },
225
+ });
226
+
227
+ expect(postAfterCreateListener).toHaveBeenCalledTimes(0);
228
+ });
229
+
230
+ test('off global event', async () => {
231
+ const Post = db.collection({
232
+ name: 'posts',
233
+ fields: [{ type: 'string', name: 'title' }],
234
+ });
235
+
236
+ const postAfterCreateListener = jest.fn();
237
+
238
+ db.on('posts.afterCreate', postAfterCreateListener);
239
+ db.on('afterCreate', postAfterCreateListener);
240
+
241
+ await db.sync();
242
+
243
+ db.off('afterCreate', postAfterCreateListener);
244
+
245
+ await Post.repository.create({
246
+ values: {
247
+ title: 'test',
248
+ },
249
+ });
250
+
251
+ expect(postAfterCreateListener).toHaveBeenCalledTimes(1);
252
+ });
253
+
207
254
  test('custom model', async () => {
208
255
  class CustomModel extends Model {
209
256
  customMethod() {
@@ -0,0 +1,69 @@
1
+ import { mockDatabase } from '..';
2
+ import { Database } from '../../database';
3
+ import { FormulaField } from '../../fields';
4
+
5
+ describe('formula field', () => {
6
+ let db: Database;
7
+
8
+ beforeEach(async () => {
9
+ db = mockDatabase();
10
+ });
11
+
12
+ afterEach(async () => {
13
+ await db.close();
14
+ });
15
+
16
+ it('add formula field with old table, already has data.', async () => {
17
+ const Test = db.collection({
18
+ name: 'tests',
19
+ fields: [
20
+ { type: 'float', name: 'price' },
21
+ { type: 'float', name: 'count' },
22
+ ],
23
+ });
24
+
25
+ await db.sync();
26
+
27
+ const test = await Test.model.create<any>({
28
+ price: '1.2',
29
+ count: '2',
30
+ });
31
+
32
+ const expression = 'price*count';
33
+ const field = Test.addField('sum', { type: 'formula', expression });
34
+
35
+ await field.sync({});
36
+
37
+ const updatedTest = await Test.model.findByPk(test.id);
38
+ const sum = updatedTest.get('sum');
39
+
40
+ const sumField = Test.getField<FormulaField>('sum');
41
+ expect(sum).toEqual(sumField.caculate(expression, updatedTest.toJSON()));
42
+ });
43
+
44
+ it('auto set formula field with create or update data', async () => {
45
+ const expression = 'price*count';
46
+ const Test = db.collection({
47
+ name: 'tests',
48
+ fields: [
49
+ { type: 'float', name: 'price' },
50
+ { type: 'float', name: 'count' },
51
+ { name: 'sum', type: 'formula', expression },
52
+ ],
53
+ });
54
+
55
+ await db.sync();
56
+
57
+ const test = await Test.model.create<any>({
58
+ price: '1.2',
59
+ count: '2',
60
+ });
61
+
62
+ const sumField = Test.getField<FormulaField>('sum');
63
+ expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON()));
64
+
65
+ test.set('count', '6');
66
+ await test.save();
67
+ expect(test.get('sum')).toEqual(sumField.caculate(expression, test.toJSON()));
68
+ });
69
+ });
@@ -0,0 +1,7 @@
1
+ import { Migration } from '@nocobase/database';
2
+
3
+ export default class extends Migration {
4
+ async up() {}
5
+
6
+ async down() {}
7
+ }
@@ -0,0 +1,7 @@
1
+ import { Migration } from '@nocobase/database';
2
+
3
+ export default class extends Migration {
4
+ async up() {}
5
+
6
+ async down() {}
7
+ }
@@ -0,0 +1,33 @@
1
+ import { mockDatabase } from '../';
2
+ import { Database } from '../../database';
3
+
4
+ describe('afterCreateWithAssociations', () => {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ test('case 1', async () => {
16
+ db.collection({
17
+ name: 'test',
18
+ });
19
+ await db.sync();
20
+ const repo = db.getRepository('test');
21
+ db.on('test.afterCreateWithAssociations', async (model, { transaction }) => {
22
+ throw new Error('test error');
23
+ });
24
+ try {
25
+ await repo.create({
26
+ values: {},
27
+ });
28
+ } catch (error) {
29
+ }
30
+ const count = await repo.count();
31
+ expect(count).toBe(0);
32
+ });
33
+ });
@@ -0,0 +1,70 @@
1
+ import { Database, Migration, mockDatabase } from '@nocobase/database';
2
+ import { resolve } from 'path';
3
+
4
+ const names = (migrations: Array<{ name: string }>) => migrations.map(m => m.name);
5
+
6
+ describe('migrator', () => {
7
+ let db: Database;
8
+
9
+ beforeEach(async () => {
10
+
11
+ db = mockDatabase({
12
+ tablePrefix: 'test_',
13
+ });
14
+
15
+ await db.clean({ drop: true });
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await db.close();
20
+ });
21
+
22
+ test('addMigrations', async () => {
23
+ db.addMigrations({
24
+ directory: resolve(__dirname, './fixtures/migrations'),
25
+ });
26
+ await db.migrator.up();
27
+ expect(names(await db.migrator.executed())).toEqual(['m1', 'm2']);
28
+ });
29
+
30
+ test('addMigrations', async () => {
31
+ db.addMigrations({
32
+ namespace: 'test',
33
+ directory: resolve(__dirname, './fixtures/migrations'),
34
+ });
35
+ await db.migrator.up();
36
+ expect(names(await db.migrator.executed())).toEqual(['test/m1', 'test/m2']);
37
+ });
38
+
39
+ test('up and down', async () => {
40
+ const spy = jest.fn();
41
+ db.addMigration({
42
+ name: 'migration1',
43
+ migration: class extends Migration {
44
+ async up() {
45
+ spy('migration1-up');
46
+ }
47
+ async down() {
48
+ spy('migration1-down');
49
+ }
50
+ },
51
+ });
52
+ db.addMigration({
53
+ name: 'migration2',
54
+ migration: class extends Migration {
55
+ async up() {
56
+ spy('migration2-up');
57
+ }
58
+ async down() {
59
+ spy('migration2-down');
60
+ }
61
+ },
62
+ });
63
+ await db.migrator.up();
64
+ expect(names(await db.migrator.executed())).toEqual(['migration1', 'migration2']);
65
+ await db.migrator.down();
66
+ expect(names(await db.migrator.executed())).toEqual(['migration1']);
67
+ expect(spy).toHaveBeenCalledTimes(3);
68
+ expect(spy).toHaveBeenNthCalledWith(1, 'migration1-up');
69
+ });
70
+ });
@@ -0,0 +1,54 @@
1
+ import { Database } from '..';
2
+ import { mockDatabase } from '.';
3
+
4
+ describe('model hook', () => {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ describe('match', () => {
16
+ test('sequelize db hooks', async () => {
17
+ const matcher = db.modelHook.match('beforeDefine');
18
+ expect(matcher).toEqual('beforeDefine');
19
+ });
20
+
21
+ test('sequelize global model hooks', async () => {
22
+ const matcher = db.modelHook.match('beforeCreate');
23
+ expect(matcher).toEqual('beforeCreate');
24
+ });
25
+
26
+ test('sequelize model hooks without existing collection', async () => {
27
+ const matcher = db.modelHook.match('posts.beforeCreate');
28
+ expect(matcher).toEqual('beforeCreate');
29
+ });
30
+
31
+ test('sequelize model hooks with existing collection', async () => {
32
+ db.collection({
33
+ name: 'posts',
34
+ fields: []
35
+ });
36
+ const matcher = db.modelHook.match('posts.beforeCreate');
37
+ expect(matcher).toEqual('beforeCreate');
38
+ });
39
+
40
+ test('customized global hooks', async () => {
41
+ const matcher = db.modelHook.match('beforeDefineCollection');
42
+ expect(matcher).toBeNull();
43
+ });
44
+
45
+ test('customized model hooks', async () => {
46
+ db.collection({
47
+ name: 'posts',
48
+ fields: []
49
+ });
50
+ const matcher = db.modelHook.match('posts.beforeCreateWithAssociations');
51
+ expect(matcher).toBeNull();
52
+ });
53
+ });
54
+ });