@nocobase/database 0.8.0-alpha.9 → 0.8.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.
- package/lib/collection.d.ts +7 -3
- package/lib/collection.js +126 -22
- package/lib/database.d.ts +13 -7
- package/lib/database.js +39 -8
- package/lib/decorators/must-have-filter-decorator.js +4 -0
- package/lib/decorators/transaction-decorator.js +1 -0
- package/lib/features/ReferencesMap.js +14 -9
- package/lib/field-repository/array-field-repository.d.ts +28 -0
- package/lib/field-repository/array-field-repository.js +208 -0
- package/lib/fields/array-field.d.ts +1 -1
- package/lib/fields/array-field.js +15 -11
- package/lib/fields/belongs-to-field.d.ts +9 -1
- package/lib/fields/belongs-to-field.js +21 -7
- package/lib/fields/belongs-to-many-field.d.ts +4 -0
- package/lib/fields/belongs-to-many-field.js +24 -0
- package/lib/fields/field.d.ts +3 -2
- package/lib/fields/field.js +19 -13
- package/lib/fields/has-many-field.d.ts +2 -1
- package/lib/fields/has-many-field.js +18 -10
- package/lib/fields/has-one-field.d.ts +1 -0
- package/lib/fields/has-one-field.js +22 -10
- package/lib/fields/index.d.ts +3 -3
- package/lib/fields/index.js +14 -34
- package/lib/fields/relation-field.d.ts +2 -1
- package/lib/fields/relation-field.js +16 -0
- package/lib/fields/set-field.d.ts +10 -0
- package/lib/fields/set-field.js +35 -0
- package/lib/fields/sort-field.d.ts +1 -1
- package/lib/fields/sort-field.js +1 -1
- package/lib/filter-match.d.ts +1 -0
- package/lib/filter-match.js +84 -0
- package/lib/filter-parser.d.ts +2 -2
- package/lib/index.d.ts +5 -1
- package/lib/index.js +56 -0
- package/lib/inherited-collection.d.ts +13 -0
- package/lib/inherited-collection.js +210 -0
- package/lib/inherited-map.d.ts +21 -0
- package/lib/inherited-map.js +203 -0
- package/lib/model-hook.d.ts +1 -1
- package/lib/model.d.ts +1 -0
- package/lib/model.js +47 -0
- package/lib/operators/array.d.ts +1 -25
- package/lib/operators/association.d.ts +1 -9
- package/lib/operators/boolean.d.ts +1 -12
- package/lib/operators/date.d.ts +1 -33
- package/lib/operators/empty.d.ts +2 -25
- package/lib/operators/ne.d.ts +1 -13
- package/lib/operators/notIn.d.ts +1 -9
- package/lib/options-parser.d.ts +3 -2
- package/lib/options-parser.js +16 -1
- package/lib/relation-repository/relation-repository.d.ts +2 -2
- package/lib/relation-repository/single-relation-repository.js +2 -2
- package/lib/repository.d.ts +18 -10
- package/lib/repository.js +172 -38
- package/lib/sync-runner.d.ts +4 -0
- package/lib/sync-runner.js +181 -0
- package/lib/types.d.ts +2 -2
- package/lib/update-associations.d.ts +1 -0
- package/lib/update-associations.js +21 -2
- package/lib/update-guard.d.ts +3 -3
- package/lib/utils.js +5 -0
- package/package.json +4 -4
- package/src/__tests__/bigint.test.ts +48 -0
- package/src/__tests__/collection.test.ts +48 -13
- package/src/__tests__/database.test.ts +10 -0
- package/src/__tests__/field-repository/array-field-repository.test.ts +94 -0
- package/src/__tests__/fields/set.test.ts +37 -0
- package/src/__tests__/filter-match.test.ts +52 -0
- package/src/__tests__/inhertits/collection-inherits-sync.test.ts +38 -0
- package/src/__tests__/inhertits/collection-inherits.test.ts +994 -0
- package/src/__tests__/inhertits/helper.ts +3 -0
- package/src/__tests__/inhertits/inherited-map.test.ts +27 -0
- package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +2 -0
- package/src/__tests__/relation-repository/has-many-repository.test.ts +1 -1
- package/src/__tests__/repository/destroy.test.ts +122 -1
- package/src/__tests__/repository/update-many.test.ts +57 -0
- package/src/__tests__/update-association-values.test.ts +232 -0
- package/src/__tests__/update-associations.test.ts +6 -1
- package/src/collection.ts +90 -8
- package/src/database.ts +53 -14
- package/src/decorators/must-have-filter-decorator.ts +4 -0
- package/src/decorators/transaction-decorator.ts +1 -0
- package/src/features/ReferencesMap.ts +20 -9
- package/src/features/referential-integrity-check.ts +1 -0
- package/src/field-repository/array-field-repository.ts +155 -0
- package/src/fields/array-field.ts +4 -4
- package/src/fields/belongs-to-field.ts +26 -10
- package/src/fields/belongs-to-many-field.ts +34 -0
- package/src/fields/field.ts +26 -13
- package/src/fields/has-many-field.ts +17 -9
- package/src/fields/has-one-field.ts +23 -9
- package/src/fields/index.ts +5 -5
- package/src/fields/relation-field.ts +16 -0
- package/src/fields/set-field.ts +25 -0
- package/src/fields/sort-field.ts +5 -4
- package/src/filter-match.ts +49 -0
- package/src/filter-parser.ts +2 -2
- package/src/index.ts +5 -2
- package/src/inherited-collection.ts +112 -0
- package/src/inherited-map.ts +97 -0
- package/src/model-hook.ts +2 -3
- package/src/model.ts +43 -3
- package/src/operators/array.ts +1 -1
- package/src/operators/association.ts +1 -1
- package/src/operators/boolean.ts +1 -1
- package/src/operators/date.ts +1 -1
- package/src/operators/empty.ts +1 -1
- package/src/operators/ne.ts +1 -1
- package/src/operators/notIn.ts +2 -1
- package/src/options-parser.ts +20 -4
- package/src/relation-repository/relation-repository.ts +2 -2
- package/src/relation-repository/single-relation-repository.ts +2 -2
- package/src/repository.ts +144 -30
- package/src/sync-runner.ts +162 -0
- package/src/types.ts +2 -2
- package/src/update-associations.ts +23 -7
- package/src/update-guard.ts +3 -3
- package/src/utils.ts +5 -1
- package/lib/fields/sequence-field.d.ts +0 -32
- package/lib/fields/sequence-field.js +0 -300
- package/src/__tests__/fields/sequence-field.test.ts +0 -480
- package/src/fields/sequence-field.ts +0 -202
|
@@ -8,9 +8,9 @@ import {
|
|
|
8
8
|
Utils,
|
|
9
9
|
} from 'sequelize';
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
|
+
import { Reference } from '../features/ReferencesMap';
|
|
11
12
|
import { checkIdentifier } from '../utils';
|
|
12
13
|
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
|
13
|
-
import { Reference } from '../features/ReferencesMap';
|
|
14
14
|
|
|
15
15
|
export interface HasManyFieldOptions extends HasManyOptions {
|
|
16
16
|
/**
|
|
@@ -74,6 +74,10 @@ export interface HasManyFieldOptions extends HasManyOptions {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export class HasManyField extends RelationField {
|
|
77
|
+
get dataType(): any {
|
|
78
|
+
return 'HasMany';
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
get foreignKey() {
|
|
78
82
|
if (this.options.foreignKey) {
|
|
79
83
|
return this.options.foreignKey;
|
|
@@ -155,15 +159,18 @@ export class HasManyField extends RelationField {
|
|
|
155
159
|
database.removePendingField(this);
|
|
156
160
|
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
|
157
161
|
const tcoll = database.getCollection(this.target);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
if (tcoll) {
|
|
164
|
+
const foreignKey = this.options.foreignKey;
|
|
165
|
+
const field = tcoll.findField((field) => {
|
|
166
|
+
if (field.name === foreignKey) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return field.type === 'belongsTo' && field.foreignKey === foreignKey;
|
|
170
|
+
});
|
|
171
|
+
if (!field) {
|
|
172
|
+
tcoll.model.removeAttribute(foreignKey);
|
|
162
173
|
}
|
|
163
|
-
return field.type === 'belongsTo' && field.foreignKey === foreignKey;
|
|
164
|
-
});
|
|
165
|
-
if (!field) {
|
|
166
|
-
tcoll.model.removeAttribute(foreignKey);
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
const association = collection.model.associations[this.name];
|
|
@@ -172,6 +179,7 @@ export class HasManyField extends RelationField {
|
|
|
172
179
|
this.database.referenceMap.removeReference(this.reference(association));
|
|
173
180
|
}
|
|
174
181
|
|
|
182
|
+
this.clearAccessors();
|
|
175
183
|
// 删掉 model 的关联字段
|
|
176
184
|
delete collection.model.associations[this.name];
|
|
177
185
|
// @ts-ignore
|
|
@@ -74,6 +74,10 @@ export interface HasOneFieldOptions extends HasOneOptions {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export class HasOneField extends RelationField {
|
|
77
|
+
get dataType(): any {
|
|
78
|
+
return 'HasOne';
|
|
79
|
+
}
|
|
80
|
+
|
|
77
81
|
get target() {
|
|
78
82
|
const { target, name } = this.options;
|
|
79
83
|
return target || Utils.pluralize(name);
|
|
@@ -154,20 +158,30 @@ export class HasOneField extends RelationField {
|
|
|
154
158
|
database.removePendingField(this);
|
|
155
159
|
// 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉
|
|
156
160
|
const tcoll = database.collections.get(this.target);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
|
|
162
|
+
if (tcoll && !this.options.inherit) {
|
|
163
|
+
const foreignKey = this.options.foreignKey;
|
|
164
|
+
|
|
165
|
+
const field = tcoll.findField((field) => {
|
|
166
|
+
if (field.name === foreignKey) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return field.type === 'belongsTo' && field.foreignKey === foreignKey;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!field) {
|
|
174
|
+
tcoll.model.removeAttribute(foreignKey);
|
|
161
175
|
}
|
|
162
|
-
return field.type === 'belongsTo' && field.foreignKey === foreignKey;
|
|
163
|
-
});
|
|
164
|
-
if (!field) {
|
|
165
|
-
tcoll.model.removeAttribute(foreignKey);
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
const association = collection.model.associations[this.name];
|
|
169
|
-
this.database.referenceMap.removeReference(this.reference(association));
|
|
170
179
|
|
|
180
|
+
if (association) {
|
|
181
|
+
this.database.referenceMap.removeReference(this.reference(association));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.clearAccessors();
|
|
171
185
|
// 删掉 model 的关联字段
|
|
172
186
|
delete collection.model.associations[this.name];
|
|
173
187
|
// @ts-ignore
|
package/src/fields/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
DoubleFieldOptions,
|
|
14
14
|
FloatFieldOptions,
|
|
15
15
|
IntegerFieldOptions,
|
|
16
|
-
RealFieldOptions
|
|
16
|
+
RealFieldOptions,
|
|
17
17
|
} from './number-field';
|
|
18
18
|
import { PasswordFieldOptions } from './password-field';
|
|
19
19
|
import { RadioFieldOptions } from './radio-field';
|
|
@@ -25,9 +25,10 @@ import { UidFieldOptions } from './uid-field';
|
|
|
25
25
|
import { UUIDFieldOptions } from './uuid-field';
|
|
26
26
|
import { VirtualFieldOptions } from './virtual-field';
|
|
27
27
|
import { FormulaFieldOptions } from './formula-field';
|
|
28
|
-
import {
|
|
28
|
+
import { SetFieldOptions } from './set-field';
|
|
29
29
|
|
|
30
30
|
export * from './array-field';
|
|
31
|
+
export * from './set-field';
|
|
31
32
|
export * from './belongs-to-field';
|
|
32
33
|
export * from './belongs-to-many-field';
|
|
33
34
|
export * from './boolean-field';
|
|
@@ -49,7 +50,6 @@ export * from './uid-field';
|
|
|
49
50
|
export * from './uuid-field';
|
|
50
51
|
export * from './virtual-field';
|
|
51
52
|
export * from './formula-field';
|
|
52
|
-
export { SequenceField } from './sequence-field';
|
|
53
53
|
|
|
54
54
|
export type FieldOptions =
|
|
55
55
|
| BaseFieldOptions
|
|
@@ -68,6 +68,7 @@ export type FieldOptions =
|
|
|
68
68
|
| VirtualFieldOptions
|
|
69
69
|
| FormulaFieldOptions
|
|
70
70
|
| ArrayFieldOptions
|
|
71
|
+
| SetFieldOptions
|
|
71
72
|
| TimeFieldOptions
|
|
72
73
|
| DateFieldOptions
|
|
73
74
|
| UidFieldOptions
|
|
@@ -77,5 +78,4 @@ export type FieldOptions =
|
|
|
77
78
|
| BelongsToFieldOptions
|
|
78
79
|
| HasOneFieldOptions
|
|
79
80
|
| HasManyFieldOptions
|
|
80
|
-
| BelongsToManyFieldOptions
|
|
81
|
-
| SequenceFieldOptions;
|
|
81
|
+
| BelongsToManyFieldOptions;
|
|
@@ -34,4 +34,20 @@ export abstract class RelationField extends Field {
|
|
|
34
34
|
get TargetModel() {
|
|
35
35
|
return this.context.database.sequelize.models[this.target];
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
protected clearAccessors() {
|
|
39
|
+
const { collection } = this.context;
|
|
40
|
+
const association = collection.model.associations[this.name];
|
|
41
|
+
if (!association) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
const accessors = Object.values(association.accessors);
|
|
47
|
+
|
|
48
|
+
accessors.forEach((accessor) => {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
delete collection.model.prototype[accessor];
|
|
51
|
+
});
|
|
52
|
+
}
|
|
37
53
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ArrayField } from './array-field';
|
|
2
|
+
import { BaseColumnFieldOptions } from './field';
|
|
3
|
+
|
|
4
|
+
export interface SetFieldOptions extends BaseColumnFieldOptions {
|
|
5
|
+
type: 'set';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class SetField extends ArrayField {
|
|
9
|
+
beforeSave = (model) => {
|
|
10
|
+
const oldValue = model.get(this.options.name);
|
|
11
|
+
if (oldValue) {
|
|
12
|
+
model.set(this.options.name, [...new Set(oldValue)]);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
bind() {
|
|
17
|
+
super.bind();
|
|
18
|
+
this.on('beforeSave', this.beforeSave);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
unbind() {
|
|
22
|
+
super.unbind();
|
|
23
|
+
this.off('beforeSave', this.beforeSave);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/fields/sort-field.ts
CHANGED
|
@@ -7,7 +7,7 @@ const sortFieldMutex = new Mutex();
|
|
|
7
7
|
|
|
8
8
|
export class SortField extends Field {
|
|
9
9
|
get dataType() {
|
|
10
|
-
return DataTypes.
|
|
10
|
+
return DataTypes.BIGINT;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
setSortValue = async (instance, options) => {
|
|
@@ -32,19 +32,20 @@ export class SortField extends Field {
|
|
|
32
32
|
const newValue = (max || 0) + 1;
|
|
33
33
|
instance.set(name, newValue);
|
|
34
34
|
});
|
|
35
|
-
}
|
|
35
|
+
};
|
|
36
36
|
|
|
37
37
|
onScopeChange = async (instance, options) => {
|
|
38
38
|
const { scopeKey } = this.options;
|
|
39
39
|
if (scopeKey && !instance.isNewRecord && instance._previousDataValues[scopeKey] != instance[scopeKey]) {
|
|
40
40
|
await this.setSortValue(instance, options);
|
|
41
41
|
}
|
|
42
|
-
}
|
|
42
|
+
};
|
|
43
43
|
|
|
44
44
|
initRecordsSortValue = async ({ transaction }) => {
|
|
45
45
|
const totalCount = await this.collection.repository.count({
|
|
46
46
|
transaction,
|
|
47
47
|
});
|
|
48
|
+
|
|
48
49
|
const emptyCount = await this.collection.repository.count({
|
|
49
50
|
filter: {
|
|
50
51
|
[this.name]: null,
|
|
@@ -73,7 +74,7 @@ export class SortField extends Field {
|
|
|
73
74
|
start += 1;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
}
|
|
77
|
+
};
|
|
77
78
|
|
|
78
79
|
bind() {
|
|
79
80
|
super.bind();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { filter } from 'mathjs';
|
|
2
|
+
|
|
3
|
+
export function filterMatch(model, where) {
|
|
4
|
+
if (where.filter !== undefined) {
|
|
5
|
+
where = filter;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// Create an object that maps operator names to functions
|
|
9
|
+
const operatorFunctions = {
|
|
10
|
+
$eq: (value, condition) => value === condition,
|
|
11
|
+
$not: (value, condition) => !filterMatch(model, condition),
|
|
12
|
+
$gt: (value, condition) => value > condition,
|
|
13
|
+
$gte: (value, condition) => value >= condition,
|
|
14
|
+
$lt: (value, condition) => value < condition,
|
|
15
|
+
$lte: (value, condition) => value <= condition,
|
|
16
|
+
$ne: (value, condition) => value !== condition,
|
|
17
|
+
$in: (value, condition) => condition.includes(value),
|
|
18
|
+
$or: (model, conditions) => Object.values(conditions).some((condition) => filterMatch(model, condition)),
|
|
19
|
+
$and: (model, conditions) => Object.values(conditions).every((condition) => filterMatch(model, condition)),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
for (const [key, value] of Object.entries(where)) {
|
|
23
|
+
// Check if the property value contains a logical operator
|
|
24
|
+
if (operatorFunctions[key] !== undefined) {
|
|
25
|
+
// Check if the conditions specified in the property value are satisfied
|
|
26
|
+
if (!operatorFunctions[key](model, value)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
// Check if the property value is an object (which would contain operators)
|
|
31
|
+
if (typeof value === 'object') {
|
|
32
|
+
// Loop through each operator in the property value
|
|
33
|
+
for (const [operator, condition] of Object.entries(value)) {
|
|
34
|
+
// Check if the property value satisfies the condition
|
|
35
|
+
if (!operatorFunctions[operator](model[key], condition)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
// Assume the default operator is "eq"
|
|
41
|
+
if (!operatorFunctions['$eq'](model[key], value)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return true;
|
|
49
|
+
}
|
package/src/filter-parser.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { flatten, unflatten } from 'flat';
|
|
2
2
|
import { default as lodash, default as _ } from 'lodash';
|
|
3
|
-
import {
|
|
3
|
+
import { ModelStatic } from 'sequelize';
|
|
4
4
|
import { Collection } from './collection';
|
|
5
5
|
import { Database } from './database';
|
|
6
6
|
import { Model } from './model';
|
|
@@ -17,7 +17,7 @@ interface FilterParserContext {
|
|
|
17
17
|
export default class FilterParser {
|
|
18
18
|
collection: Collection;
|
|
19
19
|
database: Database;
|
|
20
|
-
model:
|
|
20
|
+
model: ModelStatic<Model>;
|
|
21
21
|
filter: FilterType;
|
|
22
22
|
context: FilterParserContext;
|
|
23
23
|
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { DataTypes,
|
|
1
|
+
export { DataTypes, ModelStatic, Op, SyncOptions } from 'sequelize';
|
|
2
2
|
export * from './collection';
|
|
3
|
+
export * from './inherited-collection';
|
|
3
4
|
export * from './database';
|
|
4
5
|
export { Database as default } from './database';
|
|
5
6
|
export * from './fields';
|
|
@@ -14,4 +15,6 @@ export * from './relation-repository/multiple-relation-repository';
|
|
|
14
15
|
export * from './relation-repository/single-relation-repository';
|
|
15
16
|
export * from './repository';
|
|
16
17
|
export * from './update-associations';
|
|
17
|
-
|
|
18
|
+
export * from './collection-importer';
|
|
19
|
+
export * from './filter-match';
|
|
20
|
+
export * from './field-repository/array-field-repository';
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Collection, CollectionContext, CollectionOptions } from './collection';
|
|
2
|
+
import { default as lodash } from 'lodash';
|
|
3
|
+
import { Field } from '.';
|
|
4
|
+
|
|
5
|
+
export class InheritedCollection extends Collection {
|
|
6
|
+
parents?: Collection[];
|
|
7
|
+
constructor(options: CollectionOptions, context: CollectionContext) {
|
|
8
|
+
if (!options.inherits) {
|
|
9
|
+
throw new Error('InheritedCollection must have inherits option');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
options.inherits = lodash.castArray(options.inherits);
|
|
13
|
+
|
|
14
|
+
super(options, context);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
this.bindParents();
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (err instanceof ParentCollectionNotFound) {
|
|
20
|
+
const listener = (collection) => {
|
|
21
|
+
if (
|
|
22
|
+
options.inherits.includes(collection.name) &&
|
|
23
|
+
(options.inherits as Array<string>).every((name) => this.db.collections.has(name))
|
|
24
|
+
) {
|
|
25
|
+
this.bindParents();
|
|
26
|
+
this.db.removeListener('afterDefineCollection', listener);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this.db.addListener('afterDefineCollection', listener);
|
|
31
|
+
} else {
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected bindParents() {
|
|
38
|
+
this.setParents(this.options.inherits);
|
|
39
|
+
this.setParentFields();
|
|
40
|
+
this.setFields(this.options.fields, false);
|
|
41
|
+
this.db.inheritanceMap.setInheritance(this.name, this.options.inherits);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected setParents(inherits: string | string[]) {
|
|
45
|
+
this.parents = lodash.castArray(inherits).map((name) => {
|
|
46
|
+
const existCollection = this.db.collections.get(name);
|
|
47
|
+
if (!existCollection) {
|
|
48
|
+
throw new ParentCollectionNotFound(name);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return existCollection;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected setParentFields() {
|
|
56
|
+
for (const [name, fieldOptions] of this.parentFields()) {
|
|
57
|
+
this.setField(name, {
|
|
58
|
+
...fieldOptions,
|
|
59
|
+
inherit: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getParents() {
|
|
65
|
+
return this.parents;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
parentFields() {
|
|
69
|
+
const fields = new Map<string, Field>();
|
|
70
|
+
|
|
71
|
+
for (const parent of this.parents) {
|
|
72
|
+
if (parent.isInherited()) {
|
|
73
|
+
for (const [name, field] of (<InheritedCollection>parent).parentFields()) {
|
|
74
|
+
fields.set(name, field.options);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const parentFields = parent.fields;
|
|
79
|
+
for (const [name, field] of parentFields) {
|
|
80
|
+
fields.set(name, field.options);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return fields;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
parentAttributes() {
|
|
88
|
+
const attributes = {};
|
|
89
|
+
|
|
90
|
+
for (const parent of this.parents) {
|
|
91
|
+
if (parent.isInherited()) {
|
|
92
|
+
Object.assign(attributes, (<InheritedCollection>parent).parentAttributes());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const parentAttributes = (<any>parent.model).tableAttributes;
|
|
96
|
+
|
|
97
|
+
Object.assign(attributes, parentAttributes);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return attributes;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
isInherited() {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class ParentCollectionNotFound extends Error {
|
|
109
|
+
constructor(name: string) {
|
|
110
|
+
super(`parent collection ${name} not found`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import lodash from 'lodash';
|
|
2
|
+
import Database from './database';
|
|
3
|
+
|
|
4
|
+
class TableNode {
|
|
5
|
+
name: string;
|
|
6
|
+
parents: Set<TableNode>;
|
|
7
|
+
children: Set<TableNode>;
|
|
8
|
+
constructor(name: string) {
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.parents = new Set();
|
|
11
|
+
this.children = new Set();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default class InheritanceMap {
|
|
16
|
+
nodes: Map<string, TableNode> = new Map<string, TableNode>();
|
|
17
|
+
|
|
18
|
+
removeNode(name: string) {
|
|
19
|
+
const node = this.nodes.get(name);
|
|
20
|
+
if (!node) return;
|
|
21
|
+
|
|
22
|
+
for (const parent of node.parents) {
|
|
23
|
+
parent.children.delete(node);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const child of node.children) {
|
|
27
|
+
child.parents.delete(node);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.nodes.delete(name);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getOrCreateNode(name: string) {
|
|
34
|
+
if (!this.nodes.has(name)) {
|
|
35
|
+
this.nodes.set(name, new TableNode(name));
|
|
36
|
+
}
|
|
37
|
+
return this.getNode(name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getNode(name: string) {
|
|
41
|
+
return this.nodes.get(name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
setInheritance(name: string, inherits: string | string[]) {
|
|
45
|
+
const node = this.getOrCreateNode(name);
|
|
46
|
+
const parents = lodash.castArray(inherits).map((name) => this.getOrCreateNode(name));
|
|
47
|
+
|
|
48
|
+
node.parents = new Set(parents);
|
|
49
|
+
|
|
50
|
+
for (const parent of parents) {
|
|
51
|
+
parent.children.add(node);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isParentNode(name: string) {
|
|
56
|
+
const node = this.getNode(name);
|
|
57
|
+
return node && node.children.size > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getChildren(name: string, options: { deep: boolean } = { deep: true }): Set<string> {
|
|
61
|
+
const results = new Set<string>();
|
|
62
|
+
const node = this.getNode(name);
|
|
63
|
+
if (!node) return results;
|
|
64
|
+
|
|
65
|
+
for (const child of node.children) {
|
|
66
|
+
results.add(child.name);
|
|
67
|
+
if (!options.deep) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const grandchild of this.getChildren(child.name)) {
|
|
72
|
+
results.add(grandchild);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getParents(name: string, options: { deep: boolean } = { deep: true }): Set<string> {
|
|
80
|
+
const results = new Set<string>();
|
|
81
|
+
const node = this.getNode(name);
|
|
82
|
+
if (!node) return results;
|
|
83
|
+
|
|
84
|
+
for (const parent of node.parents) {
|
|
85
|
+
results.add(parent.name);
|
|
86
|
+
if (!options.deep) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const grandparent of this.getParents(parent.name)) {
|
|
91
|
+
results.add(grandparent);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/model-hook.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import lodash from 'lodash';
|
|
2
|
-
import
|
|
2
|
+
import { SequelizeHooks } from 'sequelize/types/hooks';
|
|
3
|
+
|
|
3
4
|
import Database from './database';
|
|
4
5
|
import { Model } from './model';
|
|
5
6
|
|
|
6
7
|
const { hooks } = require('sequelize/lib/hooks');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
9
|
export class ModelHook {
|
|
11
10
|
database: Database;
|
|
12
11
|
|
package/src/model.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import lodash from 'lodash';
|
|
2
|
-
import { Model as SequelizeModel,
|
|
2
|
+
import { DataTypes, Model as SequelizeModel, ModelStatic } from 'sequelize';
|
|
3
3
|
import { Collection } from './collection';
|
|
4
4
|
import { Database } from './database';
|
|
5
5
|
import { Field } from './fields';
|
|
6
|
+
import type { InheritedCollection } from './inherited-collection';
|
|
7
|
+
import { SyncRunner } from './sync-runner';
|
|
8
|
+
|
|
9
|
+
const _ = lodash;
|
|
6
10
|
|
|
7
11
|
interface IModel {
|
|
8
12
|
[key: string]: any;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
interface JSONTransformerOptions {
|
|
12
|
-
model:
|
|
16
|
+
model: ModelStatic<any>;
|
|
13
17
|
collection: Collection;
|
|
14
18
|
db: Database;
|
|
15
19
|
key?: string;
|
|
@@ -78,7 +82,7 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|
|
78
82
|
};
|
|
79
83
|
|
|
80
84
|
const opts = {
|
|
81
|
-
model: this.constructor as
|
|
85
|
+
model: this.constructor as ModelStatic<any>,
|
|
82
86
|
collection: (this.constructor as any).collection,
|
|
83
87
|
db: (this.constructor as any).database as Database,
|
|
84
88
|
};
|
|
@@ -145,4 +149,40 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|
|
145
149
|
|
|
146
150
|
return lodash.orderBy(data, orderItems, orderDirections);
|
|
147
151
|
}
|
|
152
|
+
|
|
153
|
+
static async sync(options) {
|
|
154
|
+
const model = this as any;
|
|
155
|
+
|
|
156
|
+
// fix sequelize sync with model that not have any column
|
|
157
|
+
if (Object.keys(model.tableAttributes).length === 0) {
|
|
158
|
+
if (this.database.inDialect('sqlite', 'mysql')) {
|
|
159
|
+
throw new Error(`Zero-column tables aren't supported in ${this.database.sequelize.getDialect()}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
const queryInterface = this.sequelize.queryInterface;
|
|
164
|
+
|
|
165
|
+
if (!queryInterface.patched) {
|
|
166
|
+
const oldDescribeTable = queryInterface.describeTable;
|
|
167
|
+
queryInterface.describeTable = async function (...args) {
|
|
168
|
+
try {
|
|
169
|
+
return await oldDescribeTable.call(this, ...args);
|
|
170
|
+
} catch (err) {
|
|
171
|
+
if (err.message.includes('No description found for')) {
|
|
172
|
+
return [];
|
|
173
|
+
} else {
|
|
174
|
+
throw err;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
queryInterface.patched = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.collection.isInherited()) {
|
|
183
|
+
return SyncRunner.syncInheritModel(model, options);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return SequelizeModel.sync.call(this, options);
|
|
187
|
+
}
|
|
148
188
|
}
|
package/src/operators/array.ts
CHANGED
package/src/operators/boolean.ts
CHANGED
package/src/operators/date.ts
CHANGED
package/src/operators/empty.ts
CHANGED
package/src/operators/ne.ts
CHANGED
package/src/operators/notIn.ts
CHANGED