@nocobase/database 0.8.0-alpha.8 → 0.8.1-alpha.3

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 (122) hide show
  1. package/lib/collection.d.ts +7 -3
  2. package/lib/collection.js +126 -22
  3. package/lib/database.d.ts +13 -7
  4. package/lib/database.js +39 -8
  5. package/lib/decorators/must-have-filter-decorator.js +4 -0
  6. package/lib/decorators/transaction-decorator.js +1 -0
  7. package/lib/features/ReferencesMap.js +14 -9
  8. package/lib/field-repository/array-field-repository.d.ts +28 -0
  9. package/lib/field-repository/array-field-repository.js +208 -0
  10. package/lib/fields/array-field.d.ts +1 -1
  11. package/lib/fields/array-field.js +15 -11
  12. package/lib/fields/belongs-to-field.d.ts +9 -1
  13. package/lib/fields/belongs-to-field.js +21 -7
  14. package/lib/fields/belongs-to-many-field.d.ts +4 -0
  15. package/lib/fields/belongs-to-many-field.js +24 -0
  16. package/lib/fields/field.d.ts +3 -2
  17. package/lib/fields/field.js +19 -13
  18. package/lib/fields/has-many-field.d.ts +2 -1
  19. package/lib/fields/has-many-field.js +18 -10
  20. package/lib/fields/has-one-field.d.ts +1 -0
  21. package/lib/fields/has-one-field.js +22 -10
  22. package/lib/fields/index.d.ts +3 -3
  23. package/lib/fields/index.js +14 -34
  24. package/lib/fields/relation-field.d.ts +2 -1
  25. package/lib/fields/relation-field.js +16 -0
  26. package/lib/fields/set-field.d.ts +10 -0
  27. package/lib/fields/set-field.js +35 -0
  28. package/lib/fields/sort-field.d.ts +1 -1
  29. package/lib/fields/sort-field.js +1 -1
  30. package/lib/filter-match.d.ts +1 -0
  31. package/lib/filter-match.js +84 -0
  32. package/lib/filter-parser.d.ts +2 -2
  33. package/lib/index.d.ts +5 -1
  34. package/lib/index.js +56 -0
  35. package/lib/inherited-collection.d.ts +13 -0
  36. package/lib/inherited-collection.js +210 -0
  37. package/lib/inherited-map.d.ts +21 -0
  38. package/lib/inherited-map.js +203 -0
  39. package/lib/model-hook.d.ts +1 -1
  40. package/lib/model.d.ts +1 -0
  41. package/lib/model.js +47 -0
  42. package/lib/operators/array.d.ts +1 -25
  43. package/lib/operators/association.d.ts +1 -9
  44. package/lib/operators/boolean.d.ts +1 -12
  45. package/lib/operators/date.d.ts +1 -33
  46. package/lib/operators/empty.d.ts +2 -25
  47. package/lib/operators/ne.d.ts +1 -13
  48. package/lib/operators/notIn.d.ts +1 -9
  49. package/lib/options-parser.d.ts +3 -2
  50. package/lib/options-parser.js +16 -1
  51. package/lib/relation-repository/relation-repository.d.ts +2 -2
  52. package/lib/relation-repository/single-relation-repository.js +2 -2
  53. package/lib/repository.d.ts +18 -10
  54. package/lib/repository.js +172 -38
  55. package/lib/sync-runner.d.ts +4 -0
  56. package/lib/sync-runner.js +181 -0
  57. package/lib/types.d.ts +2 -2
  58. package/lib/update-associations.d.ts +1 -0
  59. package/lib/update-associations.js +21 -2
  60. package/lib/update-guard.d.ts +3 -3
  61. package/lib/utils.js +5 -0
  62. package/package.json +4 -4
  63. package/src/__tests__/bigint.test.ts +48 -0
  64. package/src/__tests__/collection.test.ts +48 -13
  65. package/src/__tests__/database.test.ts +10 -0
  66. package/src/__tests__/field-repository/array-field-repository.test.ts +94 -0
  67. package/src/__tests__/fields/set.test.ts +37 -0
  68. package/src/__tests__/filter-match.test.ts +52 -0
  69. package/src/__tests__/inhertits/collection-inherits-sync.test.ts +38 -0
  70. package/src/__tests__/inhertits/collection-inherits.test.ts +994 -0
  71. package/src/__tests__/inhertits/helper.ts +3 -0
  72. package/src/__tests__/inhertits/inherited-map.test.ts +27 -0
  73. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +2 -0
  74. package/src/__tests__/relation-repository/has-many-repository.test.ts +1 -1
  75. package/src/__tests__/repository/destroy.test.ts +122 -1
  76. package/src/__tests__/repository/update-many.test.ts +57 -0
  77. package/src/__tests__/update-association-values.test.ts +232 -0
  78. package/src/__tests__/update-associations.test.ts +6 -1
  79. package/src/collection.ts +90 -8
  80. package/src/database.ts +53 -14
  81. package/src/decorators/must-have-filter-decorator.ts +4 -0
  82. package/src/decorators/transaction-decorator.ts +1 -0
  83. package/src/features/ReferencesMap.ts +20 -9
  84. package/src/features/referential-integrity-check.ts +1 -0
  85. package/src/field-repository/array-field-repository.ts +155 -0
  86. package/src/fields/array-field.ts +4 -4
  87. package/src/fields/belongs-to-field.ts +26 -10
  88. package/src/fields/belongs-to-many-field.ts +34 -0
  89. package/src/fields/field.ts +26 -13
  90. package/src/fields/has-many-field.ts +17 -9
  91. package/src/fields/has-one-field.ts +23 -9
  92. package/src/fields/index.ts +5 -5
  93. package/src/fields/relation-field.ts +16 -0
  94. package/src/fields/set-field.ts +25 -0
  95. package/src/fields/sort-field.ts +5 -4
  96. package/src/filter-match.ts +49 -0
  97. package/src/filter-parser.ts +2 -2
  98. package/src/index.ts +5 -2
  99. package/src/inherited-collection.ts +112 -0
  100. package/src/inherited-map.ts +97 -0
  101. package/src/model-hook.ts +2 -3
  102. package/src/model.ts +43 -3
  103. package/src/operators/array.ts +1 -1
  104. package/src/operators/association.ts +1 -1
  105. package/src/operators/boolean.ts +1 -1
  106. package/src/operators/date.ts +1 -1
  107. package/src/operators/empty.ts +1 -1
  108. package/src/operators/ne.ts +1 -1
  109. package/src/operators/notIn.ts +2 -1
  110. package/src/options-parser.ts +20 -4
  111. package/src/relation-repository/relation-repository.ts +2 -2
  112. package/src/relation-repository/single-relation-repository.ts +2 -2
  113. package/src/repository.ts +144 -30
  114. package/src/sync-runner.ts +162 -0
  115. package/src/types.ts +2 -2
  116. package/src/update-associations.ts +23 -7
  117. package/src/update-guard.ts +3 -3
  118. package/src/utils.ts +5 -1
  119. package/lib/fields/sequence-field.d.ts +0 -31
  120. package/lib/fields/sequence-field.js +0 -299
  121. package/src/__tests__/fields/sequence-field.test.ts +0 -455
  122. package/src/fields/sequence-field.ts +0 -200
@@ -0,0 +1,181 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.SyncRunner = void 0;
7
+
8
+ function _lodash() {
9
+ const data = _interopRequireDefault(require("lodash"));
10
+
11
+ _lodash = function _lodash() {
12
+ return data;
13
+ };
14
+
15
+ return data;
16
+ }
17
+
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+
20
+ 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; }
21
+
22
+ 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; }
23
+
24
+ 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; }
25
+
26
+ 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; } } }; }
27
+
28
+ 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); }
29
+
30
+ 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; }
31
+
32
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
33
+
34
+ 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); }); }; }
35
+
36
+ class SyncRunner {
37
+ static syncInheritModel(model, options) {
38
+ var _this = this;
39
+
40
+ return _asyncToGenerator(function* () {
41
+ const transaction = options.transaction;
42
+ const inheritedCollection = model.collection;
43
+ const db = inheritedCollection.context.database;
44
+ const dialect = db.sequelize.getDialect();
45
+ const queryInterface = db.sequelize.getQueryInterface();
46
+
47
+ if (dialect != 'postgres') {
48
+ throw new Error('Inherit model is only supported on postgres');
49
+ }
50
+
51
+ const parents = inheritedCollection.parents;
52
+
53
+ if (!parents) {
54
+ throw new Error(`Inherit model ${inheritedCollection.name} can't be created without parents, parents option is ${_lodash().default.castArray(inheritedCollection.options.inherits).join(', ')}`);
55
+ }
56
+
57
+ const parentTables = parents.map(parent => parent.model.tableName);
58
+ const tableName = model.getTableName();
59
+ const attributes = model.tableAttributes;
60
+
61
+ const childAttributes = _lodash().default.pickBy(attributes, value => {
62
+ return !value.inherit;
63
+ });
64
+
65
+ let maxSequenceVal = 0;
66
+ let maxSequenceName;
67
+
68
+ if (childAttributes.id && childAttributes.id.autoIncrement) {
69
+ var _iterator = _createForOfIteratorHelper(parentTables),
70
+ _step;
71
+
72
+ try {
73
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
74
+ const parent = _step.value;
75
+ const sequenceNameResult = yield queryInterface.sequelize.query(`SELECT column_default FROM information_schema.columns WHERE
76
+ table_name='${parent}' and "column_name" = 'id';`, {
77
+ transaction
78
+ });
79
+
80
+ if (!sequenceNameResult[0].length) {
81
+ continue;
82
+ }
83
+
84
+ const columnDefault = sequenceNameResult[0][0]['column_default'];
85
+
86
+ if (!columnDefault) {
87
+ throw new Error(`Can't find sequence name of ${parent}`);
88
+ }
89
+
90
+ const regex = new RegExp(/nextval\('("?\w+"?)\'.*\)/);
91
+ const match = regex.exec(columnDefault);
92
+ const sequenceName = match[1];
93
+ const sequenceCurrentValResult = yield queryInterface.sequelize.query(`select last_value from ${sequenceName}`, {
94
+ transaction
95
+ });
96
+ const sequenceCurrentVal = parseInt(sequenceCurrentValResult[0][0]['last_value']);
97
+
98
+ if (sequenceCurrentVal > maxSequenceVal) {
99
+ maxSequenceName = sequenceName;
100
+ maxSequenceVal = sequenceCurrentVal;
101
+ }
102
+ }
103
+ } catch (err) {
104
+ _iterator.e(err);
105
+ } finally {
106
+ _iterator.f();
107
+ }
108
+ }
109
+
110
+ yield _this.createTable(tableName, childAttributes, options, model, parentTables);
111
+
112
+ if (maxSequenceName) {
113
+ const parentsDeep = Array.from(db.inheritanceMap.getParents(inheritedCollection.name)).map(parent => db.getCollection(parent).model.tableName);
114
+ const sequenceTables = [...parentsDeep, tableName];
115
+
116
+ for (var _i = 0, _sequenceTables = sequenceTables; _i < _sequenceTables.length; _i++) {
117
+ const sequenceTable = _sequenceTables[_i];
118
+ const queryName = Boolean(sequenceTable.match(/[A-Z]/)) ? `"${sequenceTable}"` : sequenceTable;
119
+ const idColumnQuery = yield queryInterface.sequelize.query(`
120
+ SELECT true
121
+ FROM pg_attribute
122
+ WHERE attrelid = '${queryName}'::regclass -- cast to a registered class (table)
123
+ AND attname = 'id'
124
+ AND NOT attisdropped
125
+ `, {
126
+ transaction
127
+ });
128
+
129
+ if (idColumnQuery[0].length == 0) {
130
+ continue;
131
+ }
132
+
133
+ yield queryInterface.sequelize.query(`alter table "${sequenceTable}" alter column id set default nextval('${maxSequenceName}')`, {
134
+ transaction
135
+ });
136
+ }
137
+ }
138
+
139
+ if (options.alter) {
140
+ const columns = yield queryInterface.describeTable(tableName, options);
141
+
142
+ for (const columnName in childAttributes) {
143
+ if (!columns[columnName]) {
144
+ yield queryInterface.addColumn(tableName, columnName, childAttributes[columnName], options);
145
+ }
146
+ }
147
+ }
148
+ })();
149
+ }
150
+
151
+ static createTable(tableName, attributes, options, model, parentTables) {
152
+ return _asyncToGenerator(function* () {
153
+ let sql = '';
154
+ options = _objectSpread({}, options);
155
+
156
+ if (options && options.uniqueKeys) {
157
+ _lodash().default.forOwn(options.uniqueKeys, uniqueKey => {
158
+ if (uniqueKey.customIndex === undefined) {
159
+ uniqueKey.customIndex = true;
160
+ }
161
+ });
162
+ }
163
+
164
+ if (model) {
165
+ options.uniqueKeys = options.uniqueKeys || model.uniqueKeys;
166
+ }
167
+
168
+ const queryGenerator = model.queryGenerator;
169
+ attributes = _lodash().default.mapValues(attributes, attribute => model.sequelize.normalizeAttribute(attribute));
170
+ attributes = queryGenerator.attributesToSQL(attributes, {
171
+ table: tableName,
172
+ context: 'createTable'
173
+ });
174
+ sql = `${queryGenerator.createTableQuery(tableName, attributes, options)}`.replace(';', ` INHERITS (${parentTables.map(t => `"${t}"`).join(', ')});`);
175
+ return yield model.sequelize.query(sql, options);
176
+ })();
177
+ }
178
+
179
+ }
180
+
181
+ exports.SyncRunner = SyncRunner;
package/lib/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Model } from './model';
2
- import type { ValidationOptions } from 'sequelize/types/lib/instance-validator';
3
- import type { HookReturn } from 'sequelize/types/lib/hooks';
4
2
  import type { CreateOptions, DestroyOptions, SaveOptions, SyncOptions, UpdateOptions } from 'sequelize/types';
5
3
  import { Collection, CollectionOptions } from './collection';
4
+ import { HookReturn } from 'sequelize/types/hooks';
5
+ import { ValidationOptions } from 'sequelize/types/instance-validator';
6
6
  export declare type CollectionNameType = string;
7
7
  export declare type ModelSyncEventType = 'beforeSync' | 'afterSync';
8
8
  export declare type ModelValidateEventType = 'beforeValidate' | 'afterValidate';
@@ -22,6 +22,7 @@ interface UpdateAssociationOptions extends Transactionable, Hookable {
22
22
  sourceModel?: Model;
23
23
  context?: any;
24
24
  associationContext?: any;
25
+ recursive?: boolean;
25
26
  }
26
27
  export declare function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions): Promise<void>;
27
28
  export declare function updateThroughTableValue(instance: Model, throughName: string, throughValues: any, source: Model, transaction?: any): Promise<any>;
@@ -151,6 +151,10 @@ function _updateAssociations() {
151
151
  return;
152
152
  }
153
153
 
154
+ if (options === null || options === void 0 ? void 0 : options.updateAssociationValues) {
155
+ options.recursive = true;
156
+ }
157
+
154
158
  let newTransaction = false;
155
159
  let transaction = options.transaction;
156
160
 
@@ -306,7 +310,8 @@ function _updateSingleAssociation() {
306
310
  throw new Error(`The value of '${key}' cannot be in array format`);
307
311
  }
308
312
 
309
- const context = options.context,
313
+ const recursive = options.recursive,
314
+ context = options.context,
310
315
  _options$updateAssoci = options.updateAssociationValues,
311
316
  updateAssociationValues = _options$updateAssoci === void 0 ? [] : _options$updateAssoci,
312
317
  transaction = options.transaction;
@@ -378,6 +383,10 @@ function _updateSingleAssociation() {
378
383
  transaction
379
384
  });
380
385
 
386
+ if (!recursive) {
387
+ return;
388
+ }
389
+
381
390
  if (updateAssociationValues.includes(key)) {
382
391
  yield instance.update(value, _objectSpread(_objectSpread({}, options), {}, {
383
392
  transaction
@@ -431,7 +440,8 @@ function _updateMultipleAssociation() {
431
440
  return false;
432
441
  }
433
442
 
434
- const context = options.context,
443
+ const recursive = options.recursive,
444
+ context = options.context,
435
445
  _options$updateAssoci2 = options.updateAssociationValues,
436
446
  updateAssociationValues = _options$updateAssoci2 === void 0 ? [] : _options$updateAssoci2,
437
447
  transaction = options.transaction;
@@ -528,9 +538,18 @@ function _updateMultipleAssociation() {
528
538
  const instance = yield association.target.findByPk(item[pk], {
529
539
  transaction
530
540
  });
541
+
542
+ if (!instance) {
543
+ continue;
544
+ }
545
+
531
546
  const addAccessor = association.accessors.add;
532
547
  yield model[addAccessor](item[pk], accessorOptions);
533
548
 
549
+ if (!recursive) {
550
+ continue;
551
+ }
552
+
534
553
  if (updateAssociationValues.includes(key)) {
535
554
  yield instance.update(item, _objectSpread(_objectSpread({}, options), {}, {
536
555
  transaction
@@ -1,4 +1,4 @@
1
- import { ModelCtor } from 'sequelize';
1
+ import { ModelStatic } from 'sequelize';
2
2
  import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository';
3
3
  declare type UpdateValueItem = string | number | UpdateValues;
4
4
  declare type UpdateValues = {
@@ -6,13 +6,13 @@ declare type UpdateValues = {
6
6
  };
7
7
  declare type UpdateAction = 'create' | 'update';
8
8
  export declare class UpdateGuard {
9
- model: ModelCtor<any>;
9
+ model: ModelStatic<any>;
10
10
  action: UpdateAction;
11
11
  private associationKeysToBeUpdate;
12
12
  private blackList;
13
13
  private whiteList;
14
14
  setAction(action: UpdateAction): void;
15
- setModel(model: ModelCtor<any>): void;
15
+ setModel(model: ModelStatic<any>): void;
16
16
  setAssociationKeysToBeUpdate(associationKeysToBeUpdate: AssociationKeysToBeUpdate): void;
17
17
  setWhiteList(whiteList: WhiteList): void;
18
18
  setBlackList(blackList: BlackList): void;
package/lib/utils.js CHANGED
@@ -45,6 +45,11 @@ function _handleAppendsQuery() {
45
45
  _handleAppendsQuery = _asyncToGenerator(function* (options) {
46
46
  const templateModel = options.templateModel,
47
47
  queryPromises = options.queryPromises;
48
+
49
+ if (!templateModel) {
50
+ return [];
51
+ }
52
+
48
53
  const primaryKey = templateModel.constructor.primaryKeyAttribute;
49
54
  const results = yield Promise.all(queryPromises);
50
55
  let rows;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/database",
3
- "version": "0.8.0-alpha.8",
3
+ "version": "0.8.1-alpha.3",
4
4
  "description": "",
5
5
  "main": "./lib/index.js",
6
6
  "types": "./lib/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  }
13
13
  ],
14
14
  "dependencies": {
15
- "@nocobase/utils": "0.8.0-alpha.8",
15
+ "@nocobase/utils": "0.8.1-alpha.3",
16
16
  "async-mutex": "^0.3.2",
17
17
  "cron-parser": "4.4.0",
18
18
  "deepmerge": "^4.2.2",
@@ -21,7 +21,7 @@
21
21
  "mathjs": "^10.6.1",
22
22
  "moment": "2.x",
23
23
  "semver": "^7.3.7",
24
- "sequelize": "^6.9.0",
24
+ "sequelize": "^6.26.0",
25
25
  "umzug": "^3.1.1"
26
26
  },
27
27
  "devDependencies": {
@@ -32,5 +32,5 @@
32
32
  "url": "git+https://github.com/nocobase/nocobase.git",
33
33
  "directory": "packages/database"
34
34
  },
35
- "gitHead": "d53b20e08591651692c37b8bfdf1bfe59332bbdb"
35
+ "gitHead": "e03df3df5962b99d9fbf5b6e33fbe2b23f14f3d3"
36
36
  }
@@ -0,0 +1,48 @@
1
+ import { Database } from '../database';
2
+ import { mockDatabase } from './index';
3
+
4
+ const excludeSqlite = () => (process.env.DB_DIALECT != 'sqlite' ? describe : describe.skip);
5
+
6
+ excludeSqlite()('collection', () => {
7
+ let db: Database;
8
+
9
+ beforeEach(async () => {
10
+ db = mockDatabase({
11
+ logging: console.log,
12
+ });
13
+
14
+ await db.clean({ drop: true });
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await db.close();
19
+ });
20
+
21
+ it('should using bigint for id field', async () => {
22
+ const collection = db.collection({
23
+ name: 'users',
24
+ fields: [{ type: 'hasOne', name: 'profile' }],
25
+ });
26
+
27
+ await db.sync();
28
+ const tableInfo = await db.sequelize.getQueryInterface().describeTable(collection.model.tableName);
29
+
30
+ expect(tableInfo['id'].type).toBe('BIGINT');
31
+
32
+ const profile = db.collection({
33
+ name: 'profiles',
34
+ fields: [
35
+ {
36
+ type: 'belongsTo',
37
+ name: 'user',
38
+ },
39
+ ],
40
+ });
41
+
42
+ await db.sync();
43
+
44
+ const profileTableInfo = await db.sequelize.getQueryInterface().describeTable(profile.model.tableName);
45
+
46
+ expect(profileTableInfo['userId'].type).toBe('BIGINT');
47
+ });
48
+ });
@@ -3,31 +3,66 @@ import { Database } from '../database';
3
3
  import { mockDatabase } from './index';
4
4
  import { IdentifierError } from '../errors/identifier-error';
5
5
 
6
+ const pgOnly = () => (process.env.DB_DIALECT == 'postgres' ? it : it.skip);
6
7
  describe('collection', () => {
7
8
  let db: Database;
8
9
 
9
10
  beforeEach(async () => {
10
- db = mockDatabase();
11
+ db = mockDatabase({
12
+ logging: console.log,
13
+ });
14
+
15
+ await db.clean({ drop: true });
11
16
  });
12
17
 
13
18
  afterEach(async () => {
14
19
  await db.close();
15
20
  });
16
21
 
17
- test.skip('indexes', async () => {
18
- await db.clean({ drop: true });
19
- const collection = db.collection({
20
- name: 'test',
21
- fields: [
22
- {
23
- type: 'string',
24
- name: 'name',
25
- index: true,
22
+ it('should throw error when create empty collection in sqlite and mysql', async () => {
23
+ if (!db.inDialect('sqlite', 'mysql')) {
24
+ return;
25
+ }
26
+
27
+ db.collection({
28
+ name: 'empty',
29
+ timestamps: false,
30
+ autoGenId: false,
31
+ fields: [],
32
+ });
33
+
34
+ let error;
35
+
36
+ try {
37
+ await db.sync({
38
+ force: false,
39
+ alter: {
40
+ drop: false,
26
41
  },
27
- ],
42
+ });
43
+ } catch (e) {
44
+ error = e;
45
+ }
46
+
47
+ expect(error.message.includes("Zero-column tables aren't supported in")).toBeTruthy();
48
+ });
49
+
50
+ pgOnly()('can create empty collection', async () => {
51
+ db.collection({
52
+ name: 'empty',
53
+ timestamps: false,
54
+ autoGenId: false,
55
+ fields: [],
28
56
  });
29
- collection.removeField('name');
30
- await db.sync();
57
+
58
+ await db.sync({
59
+ force: false,
60
+ alter: {
61
+ drop: false,
62
+ },
63
+ });
64
+
65
+ expect(db.getCollection('empty')).toBeInstanceOf(Collection);
31
66
  });
32
67
 
33
68
  test('removeFromDb', async () => {
@@ -1,5 +1,6 @@
1
1
  import path from 'path';
2
2
  import { Database, Model } from '..';
3
+ import { ArrayFieldRepository } from '../field-repository/array-field-repository';
3
4
  import { mockDatabase } from './index';
4
5
 
5
6
  describe('database', () => {
@@ -47,6 +48,15 @@ describe('database', () => {
47
48
  );
48
49
  });
49
50
 
51
+ it('should get array field repository', async () => {
52
+ db.collection({
53
+ name: 'tests',
54
+ fields: [{ type: 'set', name: 'set-field' }],
55
+ });
56
+
57
+ expect(db.getRepository('tests.set-field', '1')).toBeInstanceOf(ArrayFieldRepository);
58
+ });
59
+
50
60
  test('import', async () => {
51
61
  await db.import({
52
62
  directory: path.resolve(__dirname, './fixtures/collections'),
@@ -0,0 +1,94 @@
1
+ import { mockDatabase } from '../index';
2
+ import Database from '../../database';
3
+ import { ArrayFieldRepository } from '../../field-repository/array-field-repository';
4
+
5
+ describe('Array field repository', () => {
6
+ let db: Database;
7
+
8
+ let TestCollection;
9
+
10
+ beforeEach(async () => {
11
+ db = mockDatabase();
12
+ TestCollection = db.collection({
13
+ name: 'test',
14
+ fields: [
15
+ {
16
+ type: 'set',
17
+ name: 'set-field',
18
+ },
19
+ ],
20
+ });
21
+
22
+ await db.sync();
23
+ });
24
+
25
+ afterEach(async () => {
26
+ await db.close();
27
+ });
28
+
29
+ it('should add item into fields', async () => {
30
+ const a1 = await TestCollection.repository.create({});
31
+
32
+ const fieldRepository = new ArrayFieldRepository(TestCollection, 'set-field', a1.get('id'));
33
+ await fieldRepository.add({
34
+ values: 'a',
35
+ });
36
+
37
+ expect(await fieldRepository.get()).toEqual(['a']);
38
+ });
39
+
40
+ it('should remove item', async () => {
41
+ const a1 = await TestCollection.repository.create({});
42
+
43
+ const fieldRepository = new ArrayFieldRepository(TestCollection, 'set-field', a1.get('id'));
44
+ await fieldRepository.add({
45
+ values: ['a', 'b', 'c'],
46
+ });
47
+
48
+ expect(await fieldRepository.get()).toEqual(['a', 'b', 'c']);
49
+ await fieldRepository.remove({
50
+ values: ['c'],
51
+ });
52
+
53
+ expect(await fieldRepository.get()).toEqual(['a', 'b']);
54
+ });
55
+
56
+ it('should set items', async () => {
57
+ const a1 = await TestCollection.repository.create({});
58
+
59
+ const fieldRepository = new ArrayFieldRepository(TestCollection, 'set-field', a1.get('id'));
60
+ await fieldRepository.add({
61
+ values: ['a', 'b', 'c'],
62
+ });
63
+
64
+ expect(await fieldRepository.get()).toEqual(['a', 'b', 'c']);
65
+ await fieldRepository.set({
66
+ values: ['d', 'e'],
67
+ });
68
+
69
+ expect(await fieldRepository.get()).toEqual(['d', 'e']);
70
+ });
71
+
72
+ it('should toggle item', async () => {
73
+ const a1 = await TestCollection.repository.create({});
74
+
75
+ const fieldRepository = new ArrayFieldRepository(TestCollection, 'set-field', a1.get('id'));
76
+ await fieldRepository.add({
77
+ values: ['a', 'b', 'c'],
78
+ });
79
+
80
+ expect(await fieldRepository.get()).toEqual(['a', 'b', 'c']);
81
+
82
+ await fieldRepository.toggle({
83
+ value: 'c',
84
+ });
85
+
86
+ expect(await fieldRepository.get()).toEqual(['a', 'b']);
87
+
88
+ await fieldRepository.toggle({
89
+ value: 'c',
90
+ });
91
+
92
+ expect(await fieldRepository.get()).toEqual(['a', 'b', 'c']);
93
+ });
94
+ });
@@ -0,0 +1,37 @@
1
+ import { mockDatabase } from '../';
2
+ import { Database } from '../../database';
3
+
4
+ describe('set field', () => {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ it('should set Set field', async () => {
16
+ const A = db.collection({
17
+ name: 'a',
18
+ fields: [
19
+ {
20
+ type: 'set',
21
+ name: 'set',
22
+ },
23
+ ],
24
+ });
25
+
26
+ await db.sync();
27
+
28
+ const a = await A.repository.create({});
29
+
30
+ a.set('set', ['a', 'b', 'c', 'a']);
31
+
32
+ await a.save();
33
+
34
+ const setValue = a.get('set');
35
+ expect(setValue).toEqual(['a', 'b', 'c']);
36
+ });
37
+ });
@@ -0,0 +1,52 @@
1
+ import { Database, Model } from '..';
2
+ import { filterMatch } from '../filter-match';
3
+ import { mockDatabase } from './index';
4
+
5
+ describe('filterMatch', () => {
6
+ let db: Database;
7
+
8
+ beforeEach(async () => {
9
+ db = mockDatabase();
10
+ });
11
+
12
+ afterEach(async () => {
13
+ await db.close();
14
+ });
15
+
16
+ test('filter match', async () => {
17
+ const Post = db.collection({
18
+ name: 'posts',
19
+ fields: [{ type: 'string', name: 'title' }],
20
+ });
21
+
22
+ await db.sync();
23
+
24
+ const post = await Post.repository.create({
25
+ values: { title: 't1' },
26
+ });
27
+
28
+ expect(
29
+ filterMatch(post, {
30
+ title: 't1',
31
+ }),
32
+ ).toBeTruthy();
33
+
34
+ expect(
35
+ filterMatch(post, {
36
+ $or: [{ title: 't1' }, { title: 't2' }],
37
+ }),
38
+ ).toBeTruthy();
39
+
40
+ expect(
41
+ filterMatch(post, {
42
+ $and: [{ title: 't1' }, { title: 't2' }],
43
+ }),
44
+ ).toBeFalsy();
45
+
46
+ expect(
47
+ filterMatch(post, {
48
+ title: 't2',
49
+ }),
50
+ ).toBeFalsy();
51
+ });
52
+ });