@nocobase/database 0.7.0-alpha.9 → 0.7.1-alpha.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.
- package/lib/collection-importer.js +85 -68
- package/lib/collection.d.ts +6 -2
- package/lib/collection.js +371 -210
- package/lib/database.d.ts +23 -4
- package/lib/database.js +599 -273
- package/lib/fields/array-field.js +45 -25
- package/lib/fields/belongs-to-field.js +101 -54
- package/lib/fields/belongs-to-many-field.js +98 -53
- package/lib/fields/boolean-field.js +24 -9
- package/lib/fields/context-field.js +77 -45
- package/lib/fields/date-field.js +24 -9
- package/lib/fields/field.d.ts +4 -1
- package/lib/fields/field.js +231 -75
- package/lib/fields/formula-field.d.ts +19 -0
- package/lib/fields/formula-field.js +184 -0
- package/lib/fields/has-inverse-field.js +4 -2
- package/lib/fields/has-many-field.js +105 -56
- package/lib/fields/has-one-field.js +105 -54
- package/lib/fields/index.d.ts +5 -1
- package/lib/fields/index.js +290 -32
- package/lib/fields/json-field.js +36 -16
- package/lib/fields/number-field.js +53 -26
- package/lib/fields/password-field.js +120 -73
- package/lib/fields/radio-field.js +75 -47
- package/lib/fields/relation-field.js +41 -28
- package/lib/fields/sort-field.js +165 -89
- package/lib/fields/string-field.js +24 -9
- package/lib/fields/text-field.js +24 -9
- package/lib/fields/time-field.js +24 -9
- package/lib/fields/uid-field.js +57 -28
- package/lib/fields/uuid-field.d.ts +9 -0
- package/lib/fields/uuid-field.js +39 -0
- package/lib/fields/virtual-field.js +24 -9
- package/lib/filter-parser.js +288 -179
- package/lib/index.d.ts +1 -0
- package/lib/index.js +224 -29
- package/lib/magic-attribute-model.js +123 -71
- package/lib/migration.d.ts +35 -0
- package/lib/migration.js +90 -0
- package/lib/mock-database.d.ts +1 -0
- package/lib/mock-database.js +69 -34
- package/lib/model-hook.d.ts +5 -5
- package/lib/model-hook.js +109 -60
- package/lib/model.js +116 -81
- package/lib/operators/array.js +136 -96
- package/lib/operators/association.js +30 -14
- package/lib/operators/boolean.d.ts +13 -0
- package/lib/operators/boolean.js +35 -0
- package/lib/operators/date.js +78 -34
- package/lib/operators/empty.js +113 -75
- package/lib/operators/index.js +15 -3
- package/lib/operators/ne.js +27 -12
- package/{esm/operators/ne.d.ts → lib/operators/notIn.d.ts} +2 -2
- package/lib/operators/notIn.js +29 -0
- package/lib/operators/string.js +56 -35
- package/lib/operators/utils.js +18 -10
- package/lib/options-parser.js +345 -215
- package/lib/playground.js +66 -53
- package/lib/relation-repository/belongs-to-many-repository.js +281 -198
- package/lib/relation-repository/belongs-to-repository.js +10 -6
- package/lib/relation-repository/hasmany-repository.js +168 -121
- package/lib/relation-repository/hasone-repository.js +10 -6
- package/lib/relation-repository/multiple-relation-repository.d.ts +3 -3
- package/lib/relation-repository/multiple-relation-repository.js +263 -148
- package/lib/relation-repository/relation-repository.d.ts +1 -1
- package/lib/relation-repository/relation-repository.js +163 -93
- package/lib/relation-repository/single-relation-repository.d.ts +6 -6
- package/lib/relation-repository/single-relation-repository.js +145 -99
- package/lib/relation-repository/types.js +4 -2
- package/lib/repository.d.ts +4 -7
- package/lib/repository.js +473 -291
- package/lib/transaction-decorator.js +80 -67
- package/lib/update-associations.d.ts +1 -2
- package/lib/update-associations.js +525 -321
- package/lib/update-guard.js +160 -117
- package/package.json +9 -9
- package/src/__tests__/collection.test.ts +27 -0
- package/src/__tests__/database.test.ts +47 -0
- package/src/__tests__/fields/formula-field.test.ts +69 -0
- package/src/__tests__/fields/uuid-field.test.ts +30 -0
- package/src/__tests__/fixtures/migrations/m1.ts +7 -0
- package/src/__tests__/fixtures/migrations/m2.ts +7 -0
- package/src/__tests__/hooks/afterCreateWithAssociations.test.ts +33 -0
- package/src/__tests__/migrator.test.ts +70 -0
- package/src/__tests__/model-hook.test.ts +54 -0
- package/src/__tests__/operator/notIn.test.ts +33 -0
- package/src/__tests__/option-parser.test.ts +30 -6
- package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +1 -1
- package/src/__tests__/sequelize-hooks.test.ts +69 -0
- package/src/__tests__/sort.test.ts +51 -0
- package/src/__tests__/update-associations.test.ts +3 -3
- package/src/collection-importer.ts +12 -20
- package/src/collection.ts +26 -2
- package/src/database.ts +144 -14
- package/src/fields/field.ts +88 -1
- package/src/fields/formula-field.ts +106 -0
- package/src/fields/index.ts +6 -0
- package/src/fields/password-field.ts +2 -0
- package/src/fields/uuid-field.ts +21 -0
- package/src/index.ts +1 -0
- package/src/migration.ts +76 -0
- package/src/mock-database.ts +2 -1
- package/src/model-hook.ts +26 -22
- package/src/operators/boolean.ts +18 -0
- package/src/operators/index.ts +2 -0
- package/src/operators/notIn.ts +12 -0
- package/src/options-parser.ts +14 -10
- package/src/relation-repository/multiple-relation-repository.ts +14 -6
- package/src/relation-repository/relation-repository.ts +12 -6
- package/src/relation-repository/single-relation-repository.ts +11 -7
- package/src/repository.ts +20 -10
- package/src/update-associations.ts +2 -3
- package/esm/collection-importer.d.ts +0 -7
- package/esm/collection-importer.js +0 -49
- package/esm/collection-importer.js.map +0 -1
- package/esm/collection.d.ts +0 -73
- package/esm/collection.js +0 -224
- package/esm/collection.js.map +0 -1
- package/esm/database.d.ts +0 -101
- package/esm/database.js +0 -275
- package/esm/database.js.map +0 -1
- package/esm/fields/array-field.d.ts +0 -11
- package/esm/fields/array-field.js +0 -26
- package/esm/fields/array-field.js.map +0 -1
- package/esm/fields/belongs-to-field.d.ts +0 -12
- package/esm/fields/belongs-to-field.js +0 -57
- package/esm/fields/belongs-to-field.js.map +0 -1
- package/esm/fields/belongs-to-many-field.d.ts +0 -11
- package/esm/fields/belongs-to-many-field.js +0 -55
- package/esm/fields/belongs-to-many-field.js.map +0 -1
- package/esm/fields/boolean-field.d.ts +0 -8
- package/esm/fields/boolean-field.js +0 -8
- package/esm/fields/boolean-field.js.map +0 -1
- package/esm/fields/context-field.d.ts +0 -13
- package/esm/fields/context-field.js +0 -43
- package/esm/fields/context-field.js.map +0 -1
- package/esm/fields/date-field.d.ts +0 -8
- package/esm/fields/date-field.js +0 -8
- package/esm/fields/date-field.js.map +0 -1
- package/esm/fields/field.d.ts +0 -37
- package/esm/fields/field.js +0 -74
- package/esm/fields/field.js.map +0 -1
- package/esm/fields/has-inverse-field.d.ts +0 -4
- package/esm/fields/has-inverse-field.js +0 -2
- package/esm/fields/has-inverse-field.js.map +0 -1
- package/esm/fields/has-many-field.d.ts +0 -64
- package/esm/fields/has-many-field.js +0 -58
- package/esm/fields/has-many-field.js.map +0 -1
- package/esm/fields/has-one-field.d.ts +0 -64
- package/esm/fields/has-one-field.js +0 -57
- package/esm/fields/has-one-field.js.map +0 -1
- package/esm/fields/index.d.ts +0 -40
- package/esm/fields/index.js +0 -21
- package/esm/fields/index.js.map +0 -1
- package/esm/fields/json-field.d.ts +0 -14
- package/esm/fields/json-field.js +0 -17
- package/esm/fields/json-field.js.map +0 -1
- package/esm/fields/number-field.d.ts +0 -32
- package/esm/fields/number-field.js +0 -28
- package/esm/fields/number-field.js.map +0 -1
- package/esm/fields/password-field.d.ts +0 -21
- package/esm/fields/password-field.js +0 -71
- package/esm/fields/password-field.js.map +0 -1
- package/esm/fields/radio-field.d.ts +0 -14
- package/esm/fields/radio-field.js +0 -49
- package/esm/fields/radio-field.js.map +0 -1
- package/esm/fields/relation-field.d.ts +0 -20
- package/esm/fields/relation-field.js +0 -27
- package/esm/fields/relation-field.js.map +0 -1
- package/esm/fields/sort-field.d.ts +0 -16
- package/esm/fields/sort-field.js +0 -90
- package/esm/fields/sort-field.js.map +0 -1
- package/esm/fields/string-field.d.ts +0 -8
- package/esm/fields/string-field.js +0 -8
- package/esm/fields/string-field.js.map +0 -1
- package/esm/fields/text-field.d.ts +0 -8
- package/esm/fields/text-field.js +0 -8
- package/esm/fields/text-field.js.map +0 -1
- package/esm/fields/time-field.d.ts +0 -8
- package/esm/fields/time-field.js +0 -8
- package/esm/fields/time-field.js.map +0 -1
- package/esm/fields/uid-field.d.ts +0 -10
- package/esm/fields/uid-field.js +0 -27
- package/esm/fields/uid-field.js.map +0 -1
- package/esm/fields/virtual-field.d.ts +0 -8
- package/esm/fields/virtual-field.js +0 -8
- package/esm/fields/virtual-field.js.map +0 -1
- package/esm/filter-parser.d.ts +0 -27
- package/esm/filter-parser.js +0 -185
- package/esm/filter-parser.js.map +0 -1
- package/esm/index.d.ts +0 -15
- package/esm/index.js +0 -16
- package/esm/index.js.map +0 -1
- package/esm/magic-attribute-model.d.ts +0 -7
- package/esm/magic-attribute-model.js +0 -70
- package/esm/magic-attribute-model.js.map +0 -1
- package/esm/mock-database.d.ts +0 -22
- package/esm/mock-database.js +0 -34
- package/esm/mock-database.js.map +0 -1
- package/esm/model-hook.d.ts +0 -12
- package/esm/model-hook.js +0 -61
- package/esm/model-hook.js.map +0 -1
- package/esm/model.d.ts +0 -15
- package/esm/model.js +0 -80
- package/esm/model.js.map +0 -1
- package/esm/operators/array.d.ts +0 -26
- package/esm/operators/array.js +0 -105
- package/esm/operators/array.js.map +0 -1
- package/esm/operators/association.d.ts +0 -10
- package/esm/operators/association.js +0 -14
- package/esm/operators/association.js.map +0 -1
- package/esm/operators/date.d.ts +0 -34
- package/esm/operators/date.js +0 -35
- package/esm/operators/date.js.map +0 -1
- package/esm/operators/empty.d.ts +0 -28
- package/esm/operators/empty.js +0 -58
- package/esm/operators/empty.js.map +0 -1
- package/esm/operators/index.d.ts +0 -2
- package/esm/operators/index.js +0 -2
- package/esm/operators/index.js.map +0 -1
- package/esm/operators/ne.js +0 -12
- package/esm/operators/ne.js.map +0 -1
- package/esm/operators/string.d.ts +0 -21
- package/esm/operators/string.js +0 -35
- package/esm/operators/string.js.map +0 -1
- package/esm/operators/utils.d.ts +0 -4
- package/esm/operators/utils.js +0 -11
- package/esm/operators/utils.js.map +0 -1
- package/esm/options-parser.d.ts +0 -31
- package/esm/options-parser.js +0 -225
- package/esm/options-parser.js.map +0 -1
- package/esm/playground.d.ts +0 -1
- package/esm/playground.js +0 -53
- package/esm/playground.js.map +0 -1
- package/esm/relation-repository/belongs-to-many-repository.d.ts +0 -36
- package/esm/relation-repository/belongs-to-many-repository.js +0 -199
- package/esm/relation-repository/belongs-to-many-repository.js.map +0 -1
- package/esm/relation-repository/belongs-to-repository.d.ts +0 -17
- package/esm/relation-repository/belongs-to-repository.js +0 -4
- package/esm/relation-repository/belongs-to-repository.js.map +0 -1
- package/esm/relation-repository/hasmany-repository.d.ts +0 -23
- package/esm/relation-repository/hasmany-repository.js +0 -125
- package/esm/relation-repository/hasmany-repository.js.map +0 -1
- package/esm/relation-repository/hasone-repository.d.ts +0 -17
- package/esm/relation-repository/hasone-repository.js +0 -4
- package/esm/relation-repository/hasone-repository.js.map +0 -1
- package/esm/relation-repository/multiple-relation-repository.d.ts +0 -23
- package/esm/relation-repository/multiple-relation-repository.js +0 -149
- package/esm/relation-repository/multiple-relation-repository.js.map +0 -1
- package/esm/relation-repository/relation-repository.d.ts +0 -32
- package/esm/relation-repository/relation-repository.js +0 -93
- package/esm/relation-repository/relation-repository.js.map +0 -1
- package/esm/relation-repository/single-relation-repository.d.ts +0 -23
- package/esm/relation-repository/single-relation-repository.js +0 -96
- package/esm/relation-repository/single-relation-repository.js.map +0 -1
- package/esm/relation-repository/types.d.ts +0 -7
- package/esm/relation-repository/types.js +0 -2
- package/esm/relation-repository/types.js.map +0 -1
- package/esm/repository.d.ts +0 -165
- package/esm/repository.js +0 -276
- package/esm/repository.js.map +0 -1
- package/esm/transaction-decorator.d.ts +0 -1
- package/esm/transaction-decorator.js +0 -63
- package/esm/transaction-decorator.js.map +0 -1
- package/esm/update-associations.d.ts +0 -60
- package/esm/update-associations.js +0 -362
- package/esm/update-associations.js.map +0 -1
- package/esm/update-guard.d.ts +0 -26
- package/esm/update-guard.js +0 -122
- package/esm/update-guard.js.map +0 -1
- package/lib/collection-importer.js.map +0 -1
- package/lib/collection.js.map +0 -1
- package/lib/database.js.map +0 -1
- package/lib/fields/array-field.js.map +0 -1
- package/lib/fields/belongs-to-field.js.map +0 -1
- package/lib/fields/belongs-to-many-field.js.map +0 -1
- package/lib/fields/boolean-field.js.map +0 -1
- package/lib/fields/context-field.js.map +0 -1
- package/lib/fields/date-field.js.map +0 -1
- package/lib/fields/field.js.map +0 -1
- package/lib/fields/has-inverse-field.js.map +0 -1
- package/lib/fields/has-many-field.js.map +0 -1
- package/lib/fields/has-one-field.js.map +0 -1
- package/lib/fields/index.js.map +0 -1
- package/lib/fields/json-field.js.map +0 -1
- package/lib/fields/number-field.js.map +0 -1
- package/lib/fields/password-field.js.map +0 -1
- package/lib/fields/radio-field.js.map +0 -1
- package/lib/fields/relation-field.js.map +0 -1
- package/lib/fields/sort-field.js.map +0 -1
- package/lib/fields/string-field.js.map +0 -1
- package/lib/fields/text-field.js.map +0 -1
- package/lib/fields/time-field.js.map +0 -1
- package/lib/fields/uid-field.js.map +0 -1
- package/lib/fields/virtual-field.js.map +0 -1
- package/lib/filter-parser.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/magic-attribute-model.js.map +0 -1
- package/lib/mock-database.js.map +0 -1
- package/lib/model-hook.js.map +0 -1
- package/lib/model.js.map +0 -1
- package/lib/operators/array.js.map +0 -1
- package/lib/operators/association.js.map +0 -1
- package/lib/operators/date.js.map +0 -1
- package/lib/operators/empty.js.map +0 -1
- package/lib/operators/index.js.map +0 -1
- package/lib/operators/ne.js.map +0 -1
- package/lib/operators/string.js.map +0 -1
- package/lib/operators/utils.js.map +0 -1
- package/lib/options-parser.js.map +0 -1
- package/lib/playground.js.map +0 -1
- package/lib/relation-repository/belongs-to-many-repository.js.map +0 -1
- package/lib/relation-repository/belongs-to-repository.js.map +0 -1
- package/lib/relation-repository/hasmany-repository.js.map +0 -1
- package/lib/relation-repository/hasone-repository.js.map +0 -1
- package/lib/relation-repository/multiple-relation-repository.js.map +0 -1
- package/lib/relation-repository/relation-repository.js.map +0 -1
- package/lib/relation-repository/single-relation-repository.js.map +0 -1
- package/lib/relation-repository/types.js.map +0 -1
- package/lib/repository.js.map +0 -1
- package/lib/transaction-decorator.js.map +0 -1
- package/lib/update-associations.js.map +0 -1
- package/lib/update-guard.js.map +0 -1
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mockDatabase } from '../index';
|
|
2
|
+
import Database from '../../database';
|
|
3
|
+
|
|
4
|
+
describe('ne operator', () => {
|
|
5
|
+
let db: Database;
|
|
6
|
+
let Test;
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
db = mockDatabase({});
|
|
9
|
+
|
|
10
|
+
Test = db.collection({
|
|
11
|
+
name: 'tests',
|
|
12
|
+
fields: [{ type: 'string', name: 'name' }],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await db.sync();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
await db.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should notIn with null', async () => {
|
|
23
|
+
await db.getRepository('tests').create({});
|
|
24
|
+
|
|
25
|
+
const results = await db.getRepository('tests').count({
|
|
26
|
+
filter: {
|
|
27
|
+
'name.$notIn': ['123'],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(results).toEqual(1);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Collection } from '../collection';
|
|
2
|
-
import { mockDatabase } from './index';
|
|
3
|
-
import { OptionsParser } from '../options-parser';
|
|
4
2
|
import { Database } from '../database';
|
|
3
|
+
import { OptionsParser } from '../options-parser';
|
|
4
|
+
import { mockDatabase } from './index';
|
|
5
5
|
|
|
6
6
|
describe('option parser', () => {
|
|
7
7
|
let db: Database;
|
|
@@ -81,7 +81,12 @@ describe('option parser', () => {
|
|
|
81
81
|
],
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
|
+
|
|
84
85
|
test('with sort option', () => {
|
|
86
|
+
if (db.inDialect('mysql')) {
|
|
87
|
+
expect(1).toBe(1);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
85
90
|
let options: any = {
|
|
86
91
|
sort: ['id'],
|
|
87
92
|
};
|
|
@@ -90,7 +95,7 @@ describe('option parser', () => {
|
|
|
90
95
|
collection: User,
|
|
91
96
|
});
|
|
92
97
|
let params = parser.toSequelizeParams();
|
|
93
|
-
expect(params['order']).toEqual([['id', 'ASC']]);
|
|
98
|
+
expect(params['order']).toEqual([['id', 'ASC NULLS LAST']]);
|
|
94
99
|
|
|
95
100
|
options = {
|
|
96
101
|
sort: ['id', '-posts.title', 'posts.comments.createdAt'],
|
|
@@ -101,9 +106,9 @@ describe('option parser', () => {
|
|
|
101
106
|
});
|
|
102
107
|
params = parser.toSequelizeParams();
|
|
103
108
|
expect(params['order']).toEqual([
|
|
104
|
-
['id', 'ASC'],
|
|
105
|
-
[Post.model, 'title', 'DESC'],
|
|
106
|
-
[Post.model, Comment.model, 'createdAt', 'ASC'],
|
|
109
|
+
['id', 'ASC NULLS LAST'],
|
|
110
|
+
[Post.model, 'title', 'DESC NULLS LAST'],
|
|
111
|
+
[Post.model, Comment.model, 'createdAt', 'ASC NULLS LAST'],
|
|
107
112
|
]);
|
|
108
113
|
});
|
|
109
114
|
|
|
@@ -182,4 +187,23 @@ describe('option parser', () => {
|
|
|
182
187
|
|
|
183
188
|
expect(params['include'][0]['attributes']['exclude']).toContain('id');
|
|
184
189
|
});
|
|
190
|
+
|
|
191
|
+
test('option parser with multiple association', () => {
|
|
192
|
+
// fields with association field
|
|
193
|
+
const options = {
|
|
194
|
+
appends: ['user', 'comments.id', 'tags.id'],
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const parser = new OptionsParser(options, {
|
|
198
|
+
collection: Post,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const params = parser.toSequelizeParams();
|
|
202
|
+
expect(params.include.length).toBe(3);
|
|
203
|
+
expect(params.include[0].association).toBe('user');
|
|
204
|
+
expect(params.include[1].association).toBe('comments');
|
|
205
|
+
expect(params.include[1].attributes).toEqual(['id']);
|
|
206
|
+
expect(params.include[2].association).toBe('tags');
|
|
207
|
+
expect(params.include[2].attributes).toEqual(['id']);
|
|
208
|
+
});
|
|
185
209
|
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Database } from '../database';
|
|
2
|
+
import { mockDatabase } from './index';
|
|
3
|
+
|
|
4
|
+
// TODO
|
|
5
|
+
describe('sequelize-hooks', () => {
|
|
6
|
+
let db: Database;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
db = mockDatabase();
|
|
10
|
+
await db.sync();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await db.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('exec order', async () => {
|
|
18
|
+
const collection = db.collection({
|
|
19
|
+
name: 't_test',
|
|
20
|
+
});
|
|
21
|
+
const orders = [];
|
|
22
|
+
db.on('beforeCreate', () => {
|
|
23
|
+
orders.push('beforeCreate');
|
|
24
|
+
});
|
|
25
|
+
db.on('t_test.beforeCreate', () => {
|
|
26
|
+
orders.push('model.beforeCreate');
|
|
27
|
+
});
|
|
28
|
+
db.on('afterCreate', () => {
|
|
29
|
+
orders.push('afterCreate');
|
|
30
|
+
});
|
|
31
|
+
db.on('t_test.afterCreate', () => {
|
|
32
|
+
orders.push('model.afterCreate');
|
|
33
|
+
});
|
|
34
|
+
await collection.sync();
|
|
35
|
+
await collection.model.create();
|
|
36
|
+
expect(orders).toEqual([
|
|
37
|
+
'model.beforeCreate',
|
|
38
|
+
'beforeCreate',
|
|
39
|
+
'model.afterCreate',
|
|
40
|
+
'afterCreate'
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('afterSync', () => {
|
|
45
|
+
test('singular name', async () => {
|
|
46
|
+
const collection = db.collection({
|
|
47
|
+
name: 't_test',
|
|
48
|
+
});
|
|
49
|
+
const spy = jest.fn();
|
|
50
|
+
db.on('t_test.afterSync', () => {
|
|
51
|
+
spy('afterSync');
|
|
52
|
+
});
|
|
53
|
+
await collection.sync();
|
|
54
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('plural name', async () => {
|
|
58
|
+
const collection = db.collection({
|
|
59
|
+
name: 't_tests',
|
|
60
|
+
});
|
|
61
|
+
const spy = jest.fn();
|
|
62
|
+
db.on('t_tests.afterSync', () => {
|
|
63
|
+
spy('afterSync');
|
|
64
|
+
});
|
|
65
|
+
await collection.sync();
|
|
66
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Database } from '../database';
|
|
2
|
+
import { mockDatabase } from './';
|
|
3
|
+
|
|
4
|
+
describe('sort', function () {
|
|
5
|
+
let db: Database;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
db = mockDatabase();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
await db.close();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('order nulls last', async () => {
|
|
16
|
+
const Test = db.collection({
|
|
17
|
+
name: 'test',
|
|
18
|
+
fields: [
|
|
19
|
+
{
|
|
20
|
+
type: 'integer',
|
|
21
|
+
name: 'num',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
});
|
|
25
|
+
await Test.sync();
|
|
26
|
+
await Test.model.bulkCreate([
|
|
27
|
+
{
|
|
28
|
+
num: 3,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
num: 2,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
num: null,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
num: 1,
|
|
38
|
+
},
|
|
39
|
+
]);
|
|
40
|
+
const items = await Test.repository.find({
|
|
41
|
+
sort: '-num',
|
|
42
|
+
});
|
|
43
|
+
const nums = items.map((item) => item.get('num'));
|
|
44
|
+
expect(nums).toEqual([3,2,1,null]);
|
|
45
|
+
const items2 = await Test.repository.find({
|
|
46
|
+
sort: 'num',
|
|
47
|
+
});
|
|
48
|
+
const nums2 = items2.map((item) => item.get('num'));
|
|
49
|
+
expect(nums2).toEqual([1,2,3,null]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -173,7 +173,7 @@ describe('update associations', () => {
|
|
|
173
173
|
await updateAssociations(user1, {
|
|
174
174
|
posts: post1,
|
|
175
175
|
});
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
expect(user1.toJSON()).toMatchObject({
|
|
178
178
|
name: 'user1',
|
|
179
179
|
});
|
|
@@ -191,7 +191,7 @@ describe('update associations', () => {
|
|
|
191
191
|
name: 'post111',
|
|
192
192
|
},
|
|
193
193
|
});
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
expect(user1.toJSON()).toMatchObject({
|
|
196
196
|
name: 'user1',
|
|
197
197
|
});
|
|
@@ -216,7 +216,7 @@ describe('update associations', () => {
|
|
|
216
216
|
post3,
|
|
217
217
|
],
|
|
218
218
|
});
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
expect(user1.toJSON()).toMatchObject({
|
|
221
221
|
name: 'user1',
|
|
222
222
|
});
|
|
@@ -1,20 +1,10 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import lodash from 'lodash';
|
|
3
1
|
import path from 'path';
|
|
2
|
+
import { readdir } from 'fs/promises';
|
|
3
|
+
import { isPlainObject } from 'lodash';
|
|
4
|
+
import { requireModule } from '@nocobase/utils';
|
|
4
5
|
|
|
5
6
|
export type ImportFileExtension = 'js' | 'ts' | 'json';
|
|
6
7
|
|
|
7
|
-
async function requireModule(module: any) {
|
|
8
|
-
if (typeof module === 'string') {
|
|
9
|
-
module = require(module);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (typeof module !== 'object') {
|
|
13
|
-
return module;
|
|
14
|
-
}
|
|
15
|
-
return module.__esModule ? module.default : module;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
8
|
export class ImporterReader {
|
|
19
9
|
directory: string;
|
|
20
10
|
extensions: Set<string>;
|
|
@@ -30,11 +20,10 @@ export class ImporterReader {
|
|
|
30
20
|
}
|
|
31
21
|
|
|
32
22
|
async read() {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
23
|
+
const files = await readdir(this.directory, {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
});
|
|
26
|
+
const modules = files
|
|
38
27
|
.filter((fileName) => {
|
|
39
28
|
if (fileName.endsWith('.d.ts')) {
|
|
40
29
|
return false;
|
|
@@ -42,8 +31,11 @@ export class ImporterReader {
|
|
|
42
31
|
const ext = path.parse(fileName).ext.replace('.', '');
|
|
43
32
|
return this.extensions.has(ext);
|
|
44
33
|
})
|
|
45
|
-
.map(
|
|
34
|
+
.map((fileName) => {
|
|
35
|
+
const mod = requireModule(path.join(this.directory, fileName));
|
|
36
|
+
return typeof mod === 'function' ? mod() : mod;
|
|
37
|
+
});
|
|
46
38
|
|
|
47
|
-
return (await Promise.all(modules)).filter((module) =>
|
|
39
|
+
return (await Promise.all(modules)).filter((module) => isPlainObject(module));
|
|
48
40
|
}
|
|
49
41
|
}
|
package/src/collection.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import merge from 'deepmerge';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import { default as lodash, default as _ } from 'lodash';
|
|
4
|
-
import {
|
|
4
|
+
import { ModelCtor, ModelOptions, QueryInterfaceDropTableOptions, SyncOptions, Transactionable } from 'sequelize';
|
|
5
5
|
import { Database } from './database';
|
|
6
6
|
import { Field, FieldOptions } from './fields';
|
|
7
7
|
import { Model } from './model';
|
|
@@ -53,6 +53,10 @@ export class Collection<
|
|
|
53
53
|
return this.options.name;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
get db() {
|
|
57
|
+
return this.context.database;
|
|
58
|
+
}
|
|
59
|
+
|
|
56
60
|
constructor(options: CollectionOptions, context?: CollectionContext) {
|
|
57
61
|
super();
|
|
58
62
|
this.context = context;
|
|
@@ -186,6 +190,26 @@ export class Collection<
|
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
192
|
|
|
193
|
+
remove() {
|
|
194
|
+
this.context.database.removeCollection(this.name);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async removeFromDb(options?: QueryInterfaceDropTableOptions) {
|
|
198
|
+
if (
|
|
199
|
+
await this.existsInDb({
|
|
200
|
+
transaction: options?.transaction,
|
|
201
|
+
})
|
|
202
|
+
) {
|
|
203
|
+
const queryInterface = this.db.sequelize.getQueryInterface();
|
|
204
|
+
await queryInterface.dropTable(this.model.tableName, options);
|
|
205
|
+
}
|
|
206
|
+
this.remove();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async existsInDb(options?: Transactionable) {
|
|
210
|
+
return this.db.collectionExistsInDb(this.name, options);
|
|
211
|
+
}
|
|
212
|
+
|
|
189
213
|
removeField(name) {
|
|
190
214
|
if (!this.fields.has(name)) {
|
|
191
215
|
return;
|
|
@@ -195,7 +219,7 @@ export class Collection<
|
|
|
195
219
|
if (bool) {
|
|
196
220
|
this.emit('field.afterRemove', field);
|
|
197
221
|
}
|
|
198
|
-
return
|
|
222
|
+
return field as Field;
|
|
199
223
|
}
|
|
200
224
|
|
|
201
225
|
/**
|
package/src/database.ts
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
|
2
2
|
import merge from 'deepmerge';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
|
+
import glob from 'glob';
|
|
4
5
|
import lodash from 'lodash';
|
|
5
|
-
import {
|
|
6
|
+
import { basename, isAbsolute, resolve } from 'path';
|
|
7
|
+
import {
|
|
8
|
+
ModelCtor,
|
|
9
|
+
Op,
|
|
10
|
+
Options,
|
|
11
|
+
QueryInterfaceDropAllTablesOptions,
|
|
12
|
+
QueryOptions,
|
|
13
|
+
Sequelize,
|
|
14
|
+
SyncOptions,
|
|
15
|
+
Transactionable,
|
|
16
|
+
Utils
|
|
17
|
+
} from 'sequelize';
|
|
18
|
+
import { SequelizeStorage, Umzug } from 'umzug';
|
|
6
19
|
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
|
7
20
|
import { ImporterReader, ImportFileExtension } from './collection-importer';
|
|
8
21
|
import * as FieldTypes from './fields';
|
|
9
22
|
import { Field, FieldContext, RelationField } from './fields';
|
|
23
|
+
import { Migrations } from './migration';
|
|
10
24
|
import { Model } from './model';
|
|
11
25
|
import { ModelHook } from './model-hook';
|
|
12
26
|
import extendOperators from './operators';
|
|
@@ -26,9 +40,10 @@ interface MapOf<T> {
|
|
|
26
40
|
|
|
27
41
|
export interface IDatabaseOptions extends Options {
|
|
28
42
|
tablePrefix?: string;
|
|
43
|
+
migrator?: any;
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
export type DatabaseOptions = IDatabaseOptions
|
|
46
|
+
export type DatabaseOptions = IDatabaseOptions;
|
|
32
47
|
|
|
33
48
|
interface RegisterOperatorsContext {
|
|
34
49
|
db?: Database;
|
|
@@ -41,10 +56,19 @@ export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {
|
|
|
41
56
|
drop?: boolean;
|
|
42
57
|
}
|
|
43
58
|
|
|
59
|
+
export type AddMigrationsOptions = {
|
|
60
|
+
context?: any;
|
|
61
|
+
namespace?: string;
|
|
62
|
+
extensions?: string[];
|
|
63
|
+
directory: string;
|
|
64
|
+
};
|
|
65
|
+
|
|
44
66
|
type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
|
|
45
67
|
|
|
46
68
|
export class Database extends EventEmitter implements AsyncEmitter {
|
|
47
69
|
sequelize: Sequelize;
|
|
70
|
+
migrator: Umzug;
|
|
71
|
+
migrations: Migrations;
|
|
48
72
|
fieldTypes = new Map();
|
|
49
73
|
options: IDatabaseOptions;
|
|
50
74
|
models = new Map<string, ModelCtor<Model>>();
|
|
@@ -61,13 +85,30 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
61
85
|
constructor(options: DatabaseOptions) {
|
|
62
86
|
super();
|
|
63
87
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
const opts = {
|
|
89
|
+
sync: {
|
|
90
|
+
alter: {
|
|
91
|
+
drop: false,
|
|
92
|
+
},
|
|
93
|
+
force: false,
|
|
94
|
+
},
|
|
95
|
+
...options,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (options.storage && options.storage !== ':memory:') {
|
|
99
|
+
if (!isAbsolute(options.storage)) {
|
|
100
|
+
opts.storage = resolve(process.cwd(), options.storage);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (options.dialect === 'sqlite') {
|
|
105
|
+
delete opts.timezone;
|
|
106
|
+
} else if (!opts.timezone) {
|
|
107
|
+
opts.timezone = '+00:00';
|
|
69
108
|
}
|
|
70
109
|
|
|
110
|
+
this.sequelize = new Sequelize(opts);
|
|
111
|
+
this.options = opts;
|
|
71
112
|
this.collections = new Map();
|
|
72
113
|
this.modelHook = new ModelHook(this);
|
|
73
114
|
|
|
@@ -92,6 +133,62 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
92
133
|
}
|
|
93
134
|
|
|
94
135
|
this.initOperators();
|
|
136
|
+
|
|
137
|
+
const migratorOptions: any = this.options.migrator || {};
|
|
138
|
+
|
|
139
|
+
const context = {
|
|
140
|
+
db: this,
|
|
141
|
+
sequelize: this.sequelize,
|
|
142
|
+
queryInterface: this.sequelize.getQueryInterface(),
|
|
143
|
+
...migratorOptions.context,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
this.migrations = new Migrations(context);
|
|
147
|
+
this.migrator = new Umzug({
|
|
148
|
+
logger: migratorOptions.logger || console,
|
|
149
|
+
migrations: this.migrations.callback(),
|
|
150
|
+
context,
|
|
151
|
+
storage: new SequelizeStorage({
|
|
152
|
+
modelName: `${this.options.tablePrefix || ''}migrations`,
|
|
153
|
+
...migratorOptions.storage,
|
|
154
|
+
sequelize: this.sequelize,
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
addMigration(item) {
|
|
160
|
+
return this.migrations.add(item);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
addMigrations(options: AddMigrationsOptions) {
|
|
164
|
+
const { namespace, context, extensions = ['js', 'ts'], directory } = options;
|
|
165
|
+
const patten = `${directory}/*.{${extensions.join(',')}}`;
|
|
166
|
+
const files = glob.sync(patten, {
|
|
167
|
+
ignore: ['**/*.d.ts'],
|
|
168
|
+
});
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
let filename = basename(file);
|
|
171
|
+
filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
|
|
172
|
+
this.migrations.add({
|
|
173
|
+
name: namespace ? `${namespace}/${filename}` : filename,
|
|
174
|
+
migration: this.requireModule(file),
|
|
175
|
+
context,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
inDialect(...dialect: string[]) {
|
|
181
|
+
return dialect.includes(this.sequelize.getDialect());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private requireModule(module: any) {
|
|
185
|
+
if (typeof module === 'string') {
|
|
186
|
+
module = require(module);
|
|
187
|
+
}
|
|
188
|
+
if (typeof module !== 'object') {
|
|
189
|
+
return module;
|
|
190
|
+
}
|
|
191
|
+
return module.__esModule ? module.default : module;
|
|
95
192
|
}
|
|
96
193
|
|
|
97
194
|
/**
|
|
@@ -137,9 +234,13 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
137
234
|
|
|
138
235
|
const result = this.collections.delete(name);
|
|
139
236
|
|
|
237
|
+
this.sequelize.modelManager.removeModel(collection.model);
|
|
238
|
+
|
|
140
239
|
if (result) {
|
|
141
240
|
this.emit('afterRemoveCollection', collection);
|
|
142
241
|
}
|
|
242
|
+
|
|
243
|
+
return collection;
|
|
143
244
|
}
|
|
144
245
|
|
|
145
246
|
getModel<M extends Model>(name: string) {
|
|
@@ -244,10 +345,39 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
244
345
|
}
|
|
245
346
|
}
|
|
246
347
|
|
|
348
|
+
async collectionExistsInDb(name, options?: Transactionable) {
|
|
349
|
+
const tables = await this.sequelize.getQueryInterface().showAllTables({
|
|
350
|
+
transaction: options?.transaction,
|
|
351
|
+
});
|
|
352
|
+
return !!tables.find((table) => table === `${this.getTablePrefix()}${name}`);
|
|
353
|
+
}
|
|
354
|
+
|
|
247
355
|
public isSqliteMemory() {
|
|
248
356
|
return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:';
|
|
249
357
|
}
|
|
250
358
|
|
|
359
|
+
async auth(options: QueryOptions & { retry?: number } = {}) {
|
|
360
|
+
const { retry = 10, ...others } = options;
|
|
361
|
+
const delay = (ms) => new Promise((yea) => setTimeout(yea, ms));
|
|
362
|
+
let count = 1;
|
|
363
|
+
const authenticate = async () => {
|
|
364
|
+
try {
|
|
365
|
+
await this.sequelize.authenticate(others);
|
|
366
|
+
console.log('Connection has been established successfully.');
|
|
367
|
+
return true;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
if (count >= retry) {
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
console.log('reconnecting...', count);
|
|
373
|
+
++count;
|
|
374
|
+
await delay(500);
|
|
375
|
+
return await authenticate();
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
return await authenticate();
|
|
379
|
+
}
|
|
380
|
+
|
|
251
381
|
async reconnect() {
|
|
252
382
|
if (this.isSqliteMemory()) {
|
|
253
383
|
return;
|
|
@@ -275,13 +405,13 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
275
405
|
return this.sequelize.close();
|
|
276
406
|
}
|
|
277
407
|
|
|
278
|
-
on(event: string | symbol, listener
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (modelEventName && !this.modelHook.hasBindEvent(modelEventName)) {
|
|
282
|
-
this.sequelize.addHook(modelEventName, this.modelHook.sequelizeHookBuilder(modelEventName));
|
|
408
|
+
on(event: string | symbol, listener): this {
|
|
409
|
+
// NOTE: to match if event is a sequelize or model type
|
|
410
|
+
const type = this.modelHook.match(event);
|
|
283
411
|
|
|
284
|
-
|
|
412
|
+
if (type && !this.modelHook.hasBoundEvent(type)) {
|
|
413
|
+
this.sequelize.addHook(type, this.modelHook.buildSequelizeHook(type));
|
|
414
|
+
this.modelHook.bindEvent(type);
|
|
285
415
|
}
|
|
286
416
|
|
|
287
417
|
return super.on(event, listener);
|
|
@@ -312,7 +442,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
312
442
|
return result;
|
|
313
443
|
}
|
|
314
444
|
|
|
315
|
-
emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
|
445
|
+
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
|
|
316
446
|
}
|
|
317
447
|
|
|
318
448
|
export function extend(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions) {
|