@nocobase/database 0.7.4-alpha.7 → 0.7.5-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/collection.d.ts +7 -2
- package/lib/collection.js +20 -8
- package/lib/database.d.ts +2 -2
- package/lib/database.js +6 -4
- package/lib/decorators/must-have-filter-decorator.d.ts +2 -0
- package/lib/decorators/must-have-filter-decorator.js +25 -0
- package/lib/{transaction-decorator.d.ts → decorators/transaction-decorator.d.ts} +0 -0
- package/lib/{transaction-decorator.js → decorators/transaction-decorator.js} +1 -4
- package/lib/errors/identifier-error.d.ts +3 -0
- package/lib/errors/identifier-error.js +16 -0
- package/lib/fields/belongs-to-field.js +9 -0
- package/lib/fields/belongs-to-many-field.js +10 -0
- package/lib/fields/date-field.d.ts +1 -1
- package/lib/fields/date-field.js +1 -1
- package/lib/fields/field.d.ts +1 -1
- package/lib/fields/field.js +2 -3
- package/lib/fields/formula-field.d.ts +5 -5
- package/lib/fields/formula-field.js +123 -110
- package/lib/fields/has-many-field.js +9 -0
- package/lib/fields/has-one-field.js +9 -0
- package/lib/fields/index.d.ts +3 -1
- package/lib/fields/index.js +34 -1
- package/lib/fields/number-field.d.ts +6 -0
- package/lib/fields/number-field.js +10 -1
- package/lib/fields/radio-field.d.ts +3 -1
- package/lib/fields/radio-field.js +14 -11
- package/lib/fields/sequence-field.d.ts +31 -0
- package/lib/fields/sequence-field.js +299 -0
- package/lib/fields/sort-field.d.ts +4 -4
- package/lib/fields/sort-field.js +93 -80
- package/lib/filter-parser.js +10 -2
- package/lib/index.d.ts +1 -1
- package/lib/index.js +7 -0
- package/lib/magic-attribute-model.d.ts +1 -0
- package/lib/magic-attribute-model.js +207 -6
- package/lib/mock-database.js +1 -1
- package/lib/operators/array.js +17 -9
- package/lib/operators/ne.d.ts +4 -0
- package/lib/operators/ne.js +3 -1
- package/lib/relation-repository/belongs-to-many-repository.js +6 -0
- package/lib/relation-repository/multiple-relation-repository.js +32 -9
- package/lib/relation-repository/relation-repository.js +7 -1
- package/lib/relation-repository/single-relation-repository.js +28 -0
- package/lib/repository.d.ts +5 -3
- package/lib/repository.js +40 -9
- package/lib/update-associations.js +48 -71
- package/lib/utils.d.ts +9 -0
- package/lib/utils.js +126 -0
- package/package.json +5 -3
- package/src/__tests__/collection.test.ts +47 -0
- package/src/__tests__/database.test.ts +2 -0
- package/src/__tests__/field-options/inddex.test.ts +43 -0
- package/src/__tests__/fields/array.test.ts +66 -0
- package/src/__tests__/fields/belongs-to-field.test.ts +35 -0
- package/src/__tests__/fields/belongs-to-many-field.test.ts +45 -0
- package/src/__tests__/fields/has-many-field.test.ts +22 -0
- package/src/__tests__/fields/has-one-field.test.ts +21 -0
- package/src/__tests__/fields/sequence-field.test.ts +455 -0
- package/src/__tests__/magic-attribute-model.test.ts +24 -0
- package/src/__tests__/operator/ne.test.ts +12 -0
- package/src/__tests__/relation-repository/appends.test.ts +64 -0
- package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +23 -0
- package/src/__tests__/relation-repository/has-many-repository.test.ts +20 -0
- package/src/__tests__/relation-repository/hasone-repository.test.ts +68 -1
- package/src/__tests__/repository/find.test.ts +35 -0
- package/src/__tests__/repository/update.test.ts +35 -0
- package/src/__tests__/repository.test.ts +15 -0
- package/src/collection.ts +28 -13
- package/src/database.ts +11 -7
- package/src/decorators/must-have-filter-decorator.ts +17 -0
- package/src/{transaction-decorator.ts → decorators/transaction-decorator.ts} +1 -2
- package/src/errors/identifier-error.ts +6 -0
- package/src/fields/belongs-to-field.ts +9 -0
- package/src/fields/belongs-to-many-field.ts +15 -0
- package/src/fields/date-field.ts +1 -1
- package/src/fields/field.ts +3 -3
- package/src/fields/formula-field.ts +13 -13
- package/src/fields/has-many-field.ts +10 -0
- package/src/fields/has-one-field.ts +13 -0
- package/src/fields/index.ts +5 -2
- package/src/fields/number-field.ts +10 -0
- package/src/fields/radio-field.ts +22 -24
- package/src/fields/sequence-field.ts +200 -0
- package/src/fields/sort-field.ts +9 -9
- package/src/filter-parser.ts +6 -5
- package/src/index.ts +1 -1
- package/src/magic-attribute-model.ts +188 -6
- package/src/mock-database.ts +1 -1
- package/src/operators/array.ts +17 -10
- package/src/operators/ne.ts +10 -6
- package/src/relation-repository/belongs-to-many-repository.ts +4 -0
- package/src/relation-repository/multiple-relation-repository.ts +26 -11
- package/src/relation-repository/relation-repository.ts +5 -1
- package/src/relation-repository/single-relation-repository.ts +27 -1
- package/src/repository.ts +37 -10
- package/src/update-associations.ts +41 -53
- package/src/utils.ts +71 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Utils
|
|
9
9
|
} from 'sequelize';
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
|
+
import { checkIdentifier } from '../utils';
|
|
11
12
|
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
|
12
13
|
|
|
13
14
|
export interface HasOneFieldOptions extends HasOneOptions {
|
|
@@ -92,21 +93,33 @@ export class HasOneField extends RelationField {
|
|
|
92
93
|
database.addPendingField(this);
|
|
93
94
|
return false;
|
|
94
95
|
}
|
|
96
|
+
|
|
95
97
|
const association = collection.model.hasOne(Target, {
|
|
96
98
|
constraints: false,
|
|
97
99
|
...omit(this.options, ['name', 'type', 'target']),
|
|
98
100
|
as: this.name,
|
|
99
101
|
foreignKey: this.foreignKey,
|
|
100
102
|
});
|
|
103
|
+
|
|
101
104
|
// 建立关系之后从 pending 列表中删除
|
|
102
105
|
database.removePendingField(this);
|
|
106
|
+
|
|
103
107
|
if (!this.options.foreignKey) {
|
|
104
108
|
this.options.foreignKey = association.foreignKey;
|
|
105
109
|
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
checkIdentifier(this.options.foreignKey);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.unbind();
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
|
|
106
118
|
if (!this.options.sourceKey) {
|
|
107
119
|
// @ts-ignore
|
|
108
120
|
this.options.sourceKey = association.sourceKey;
|
|
109
121
|
}
|
|
122
|
+
|
|
110
123
|
let tcoll: Collection;
|
|
111
124
|
if (this.target === collection.name) {
|
|
112
125
|
tcoll = collection;
|
package/src/fields/index.ts
CHANGED
|
@@ -24,7 +24,8 @@ import { TimeFieldOptions } from './time-field';
|
|
|
24
24
|
import { UidFieldOptions } from './uid-field';
|
|
25
25
|
import { UUIDFieldOptions } from './uuid-field';
|
|
26
26
|
import { VirtualFieldOptions } from './virtual-field';
|
|
27
|
-
import { FormulaFieldOptions } from './formula-field'
|
|
27
|
+
import { FormulaFieldOptions } from './formula-field';
|
|
28
|
+
import { SequenceFieldOptions } from './sequence-field';
|
|
28
29
|
|
|
29
30
|
export * from './array-field';
|
|
30
31
|
export * from './belongs-to-field';
|
|
@@ -48,6 +49,7 @@ export * from './uid-field';
|
|
|
48
49
|
export * from './uuid-field';
|
|
49
50
|
export * from './virtual-field';
|
|
50
51
|
export * from './formula-field';
|
|
52
|
+
export { SequenceField } from './sequence-field';
|
|
51
53
|
|
|
52
54
|
export type FieldOptions =
|
|
53
55
|
| BaseFieldOptions
|
|
@@ -75,4 +77,5 @@ export type FieldOptions =
|
|
|
75
77
|
| BelongsToFieldOptions
|
|
76
78
|
| HasOneFieldOptions
|
|
77
79
|
| HasManyFieldOptions
|
|
78
|
-
| BelongsToManyFieldOptions
|
|
80
|
+
| BelongsToManyFieldOptions
|
|
81
|
+
| SequenceFieldOptions;
|
|
@@ -11,6 +11,16 @@ export interface IntegerFieldOptions extends BaseColumnFieldOptions {
|
|
|
11
11
|
type: 'integer';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export class BigIntField extends Field {
|
|
15
|
+
get dataType() {
|
|
16
|
+
return DataTypes.BIGINT;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BigIntFieldOptions extends BaseColumnFieldOptions {
|
|
21
|
+
type: 'bigInt';
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
export class FloatField extends Field {
|
|
15
25
|
get dataType() {
|
|
16
26
|
return DataTypes.FLOAT;
|
|
@@ -13,38 +13,36 @@ export class RadioField extends Field {
|
|
|
13
13
|
return DataTypes.BOOLEAN;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
listener = async (model, { transaction }) => {
|
|
17
17
|
const { name } = this.options;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
[name]: true,
|
|
30
|
-
},
|
|
31
|
-
transaction,
|
|
32
|
-
hooks: false,
|
|
18
|
+
if (!model.changed(name as any)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const value = model.get(name) as boolean;
|
|
22
|
+
if (value) {
|
|
23
|
+
const M = this.collection.model;
|
|
24
|
+
await M.update(
|
|
25
|
+
{ [name]: false },
|
|
26
|
+
{
|
|
27
|
+
where: {
|
|
28
|
+
[name]: true,
|
|
33
29
|
},
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
transaction,
|
|
31
|
+
hooks: false,
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
38
36
|
|
|
39
37
|
bind() {
|
|
40
38
|
super.bind();
|
|
41
|
-
this.on('beforeCreate', this.listener
|
|
42
|
-
this.on('beforeUpdate', this.listener
|
|
39
|
+
this.on('beforeCreate', this.listener);
|
|
40
|
+
this.on('beforeUpdate', this.listener);
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
unbind() {
|
|
46
44
|
super.unbind();
|
|
47
|
-
this.off('beforeCreate', this.listener
|
|
48
|
-
this.off('beforeUpdate', this.listener
|
|
45
|
+
this.off('beforeCreate', this.listener);
|
|
46
|
+
this.off('beforeUpdate', this.listener);
|
|
49
47
|
}
|
|
50
48
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { DataTypes, Transactionable } from 'sequelize';
|
|
2
|
+
import parser from 'cron-parser';
|
|
3
|
+
import moment from 'moment';
|
|
4
|
+
import { escapeRegExp } from 'lodash';
|
|
5
|
+
|
|
6
|
+
import { Registry } from '@nocobase/utils';
|
|
7
|
+
|
|
8
|
+
import { Model } from '..';
|
|
9
|
+
import { BaseColumnFieldOptions, Field, FieldContext } from './field';
|
|
10
|
+
|
|
11
|
+
interface Pattern {
|
|
12
|
+
validate?(options): string | null;
|
|
13
|
+
generate(this: SequenceField, instance: Model, index: number): string;
|
|
14
|
+
getLength(options): number;
|
|
15
|
+
getMatcher(options): string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const sequencePatterns = new Registry<Pattern>();
|
|
19
|
+
|
|
20
|
+
sequencePatterns.register('string', {
|
|
21
|
+
validate(options) {
|
|
22
|
+
if (!options?.value) {
|
|
23
|
+
return 'options.value should be configured as a non-empty string';
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
},
|
|
27
|
+
generate(instance, index) {
|
|
28
|
+
const { options } = this.options.patterns[index];
|
|
29
|
+
return options.value;
|
|
30
|
+
},
|
|
31
|
+
getLength(options) {
|
|
32
|
+
return options.value.length;
|
|
33
|
+
},
|
|
34
|
+
getMatcher(options) {
|
|
35
|
+
return escapeRegExp(options.value);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
sequencePatterns.register('integer', {
|
|
40
|
+
generate(instance: Model, index) {
|
|
41
|
+
const { options = {} } = this.options.patterns[index];
|
|
42
|
+
const { digits = 1, start = 0, base = 10, cycle } = options;
|
|
43
|
+
const max = Math.pow(base, digits) - 1;
|
|
44
|
+
const { lastRecord = null } = this.options;
|
|
45
|
+
|
|
46
|
+
if (typeof options.current === 'undefined') {
|
|
47
|
+
if (lastRecord) {
|
|
48
|
+
// if match current pattern
|
|
49
|
+
const matcher = this.match(lastRecord.get(this.options.name));
|
|
50
|
+
if (matcher) {
|
|
51
|
+
const lastNumber = Number.parseInt(matcher[index + 1], base);
|
|
52
|
+
options.current = Number.isNaN(lastNumber) ? start : lastNumber + 1;
|
|
53
|
+
} else {
|
|
54
|
+
options.current = start;
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
options.current = start;
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
options.current += 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// cycle as cron string
|
|
64
|
+
if (cycle && lastRecord) {
|
|
65
|
+
const interval = parser.parseExpression(cycle, { currentDate: <Date>lastRecord.get('createdAt') });
|
|
66
|
+
const next = interval.next();
|
|
67
|
+
if ((<Date>instance.get('createdAt')).getTime() >= next.getTime()) {
|
|
68
|
+
options.current = start;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (options.current > max) {
|
|
73
|
+
options.current = start;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// update options
|
|
77
|
+
Object.assign(this.options.patterns[index], { options });
|
|
78
|
+
|
|
79
|
+
return options.current.toString(base).padStart(digits, '0');
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
getLength({ digits = 1 } = {}) {
|
|
83
|
+
return digits;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
getMatcher(options = {}) {
|
|
87
|
+
const { digits = 1, start = 0, base = 10 } = options;
|
|
88
|
+
const startLen = start ? start.toString(base).length : 1;
|
|
89
|
+
const chars = '0123456789abcdefghijklmnopqrstuvwxyz'.slice(0, base);
|
|
90
|
+
return `[${chars}]{${digits}}`;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
sequencePatterns.register('date', {
|
|
95
|
+
generate(instance, index) {
|
|
96
|
+
const { options } = this.options.patterns[index];
|
|
97
|
+
return moment(instance.get(options?.field ?? 'createdAt')).format(options?.format ?? 'YYYYMMDD');
|
|
98
|
+
},
|
|
99
|
+
getLength(options) {
|
|
100
|
+
return options.format?.length ?? 8;
|
|
101
|
+
},
|
|
102
|
+
getMatcher(options = {}) {
|
|
103
|
+
return `.{${options?.format?.length ?? 8}}`;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
interface PatternConfig {
|
|
108
|
+
type: string;
|
|
109
|
+
title?: string;
|
|
110
|
+
options?: any;
|
|
111
|
+
}
|
|
112
|
+
export interface SequenceFieldOptions extends BaseColumnFieldOptions {
|
|
113
|
+
type: 'sequence';
|
|
114
|
+
patterns: PatternConfig[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class SequenceField extends Field {
|
|
118
|
+
get dataType() {
|
|
119
|
+
return DataTypes.STRING;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
constructor(options: SequenceFieldOptions, context: FieldContext) {
|
|
123
|
+
super(options, context);
|
|
124
|
+
if (!options.patterns || !options.patterns.length) {
|
|
125
|
+
throw new Error('at least one pattern should be defined for sequence type');
|
|
126
|
+
}
|
|
127
|
+
options.patterns.forEach(pattern => {
|
|
128
|
+
const P = sequencePatterns.get(pattern.type);
|
|
129
|
+
if (!P) {
|
|
130
|
+
throw new Error(`pattern type ${pattern.type} is not registered`);
|
|
131
|
+
}
|
|
132
|
+
if (P.validate) {
|
|
133
|
+
const error = P.validate(pattern.options);
|
|
134
|
+
if (error) {
|
|
135
|
+
throw new Error(error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const patterns = options.patterns
|
|
141
|
+
.map(({ type, options }) => sequencePatterns.get(type).getMatcher(options));
|
|
142
|
+
this.matcher = new RegExp(`^${patterns.map(p => `(${p})`).join('')}$`, 'i');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setValue = async (instance: Model, options) => {
|
|
146
|
+
const { name, patterns } = this.options;
|
|
147
|
+
// NOTE: only load when value is not set, if null stand for no last record
|
|
148
|
+
if (typeof this.options.lastRecord === 'undefined') {
|
|
149
|
+
const model = <typeof Model>instance.constructor;
|
|
150
|
+
this.options.lastRecord = await model.findOne({
|
|
151
|
+
attributes: [model.primaryKeyAttribute, this.options.name, 'createdAt'],
|
|
152
|
+
order: [
|
|
153
|
+
['createdAt', 'DESC'],
|
|
154
|
+
// TODO(bug): will cause problem if no auto-increment id
|
|
155
|
+
[model.primaryKeyAttribute, 'DESC']
|
|
156
|
+
],
|
|
157
|
+
transaction: options.transaction
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const results = patterns.reduce((result, p, i) => {
|
|
162
|
+
const item = sequencePatterns.get(p.type).generate.call(this, instance, i, options);
|
|
163
|
+
return result.concat(item);
|
|
164
|
+
}, []);
|
|
165
|
+
instance.set(name, results.join(''));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
setLast = (instance: Model, options) => {
|
|
169
|
+
this.options.lastRecord = instance;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
match(value) {
|
|
173
|
+
return value.match(this.matcher);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
parse(value: string, patternIndex: number): string {
|
|
177
|
+
for (let i = 0, index = 0; i < this.options.patterns.length; i += 1) {
|
|
178
|
+
const { type, options } = this.options.patterns[i];
|
|
179
|
+
const { getLength } = sequencePatterns.get(type);
|
|
180
|
+
const length = getLength(options);
|
|
181
|
+
if (i === patternIndex) {
|
|
182
|
+
return value.substring(index, index + length);
|
|
183
|
+
}
|
|
184
|
+
index += length;
|
|
185
|
+
}
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
bind() {
|
|
190
|
+
super.bind();
|
|
191
|
+
this.on('beforeCreate', this.setValue);
|
|
192
|
+
this.on('afterCreate', this.setLast);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
unbind() {
|
|
196
|
+
super.unbind();
|
|
197
|
+
this.off('beforeCreate', this.setValue);
|
|
198
|
+
this.off('afterCreate', this.setLast);
|
|
199
|
+
}
|
|
200
|
+
}
|
package/src/fields/sort-field.ts
CHANGED
|
@@ -10,7 +10,7 @@ export class SortField extends Field {
|
|
|
10
10
|
return DataTypes.INTEGER;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
async
|
|
13
|
+
setSortValue = async (instance, options) => {
|
|
14
14
|
const { name, scopeKey } = this.options;
|
|
15
15
|
const { model } = this.context.collection;
|
|
16
16
|
|
|
@@ -34,14 +34,14 @@ export class SortField extends Field {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
async
|
|
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
|
-
async
|
|
44
|
+
initRecordsSortValue = async ({ transaction }) => {
|
|
45
45
|
const totalCount = await this.collection.repository.count({
|
|
46
46
|
transaction,
|
|
47
47
|
});
|
|
@@ -77,16 +77,16 @@ export class SortField extends Field {
|
|
|
77
77
|
|
|
78
78
|
bind() {
|
|
79
79
|
super.bind();
|
|
80
|
-
this.on('afterSync', this.initRecordsSortValue
|
|
81
|
-
this.on('beforeUpdate', this.onScopeChange
|
|
82
|
-
this.on('beforeCreate', this.setSortValue
|
|
80
|
+
this.on('afterSync', this.initRecordsSortValue);
|
|
81
|
+
this.on('beforeUpdate', this.onScopeChange);
|
|
82
|
+
this.on('beforeCreate', this.setSortValue);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
unbind() {
|
|
86
86
|
super.unbind();
|
|
87
|
-
this.off('beforeUpdate', this.onScopeChange
|
|
88
|
-
this.off('beforeCreate', this.setSortValue
|
|
89
|
-
this.off('afterSync', this.initRecordsSortValue
|
|
87
|
+
this.off('beforeUpdate', this.onScopeChange);
|
|
88
|
+
this.off('beforeCreate', this.setSortValue);
|
|
89
|
+
this.off('afterSync', this.initRecordsSortValue);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
package/src/filter-parser.ts
CHANGED
|
@@ -124,12 +124,13 @@ export default class FilterParser {
|
|
|
124
124
|
skipPrefix = origins.join('.');
|
|
125
125
|
|
|
126
126
|
const queryValue = lodash.get(unflatten(originalFiler), skipPrefix);
|
|
127
|
-
|
|
127
|
+
const [fieldName, fullName] = this.getFieldNameFromQueryPath(skipPrefix);
|
|
128
128
|
value = opKey(queryValue, {
|
|
129
129
|
app: this.context.app,
|
|
130
130
|
db: this.database,
|
|
131
131
|
path: skipPrefix,
|
|
132
|
-
|
|
132
|
+
fullName,
|
|
133
|
+
fieldName,
|
|
133
134
|
model: this.model,
|
|
134
135
|
});
|
|
135
136
|
break;
|
|
@@ -230,14 +231,14 @@ export default class FilterParser {
|
|
|
230
231
|
private getFieldNameFromQueryPath(queryPath: string) {
|
|
231
232
|
const paths = queryPath.split('.');
|
|
232
233
|
let fieldName;
|
|
234
|
+
let fullPaths = [];
|
|
233
235
|
for (const path of paths) {
|
|
234
236
|
if (path.startsWith('$') || !lodash.isNaN(parseInt(path))) {
|
|
235
237
|
continue;
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
fullPaths.push(path);
|
|
238
240
|
fieldName = path;
|
|
239
241
|
}
|
|
240
|
-
|
|
241
|
-
return fieldName;
|
|
242
|
+
return [fieldName, fullPaths.join('.')];
|
|
242
243
|
}
|
|
243
244
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { merge } from '@nocobase/utils';
|
|
2
2
|
import _ from 'lodash';
|
|
3
|
+
import { Utils } from 'sequelize';
|
|
3
4
|
import Database from './database';
|
|
4
5
|
import { Model } from './model';
|
|
6
|
+
const Dottie = require('dottie');
|
|
5
7
|
|
|
6
8
|
export class MagicAttributeModel extends Model {
|
|
7
9
|
get magicAttribute() {
|
|
@@ -14,25 +16,205 @@ export class MagicAttributeModel extends Model {
|
|
|
14
16
|
if (typeof key === 'string') {
|
|
15
17
|
const [column] = key.split('.');
|
|
16
18
|
if ((this.constructor as any).hasAlias(column)) {
|
|
17
|
-
return
|
|
19
|
+
return this.setV1(key, value, options);
|
|
18
20
|
}
|
|
19
21
|
if ((this.constructor as any).rawAttributes[column]) {
|
|
20
|
-
return
|
|
22
|
+
return this.setV1(key, value, options);
|
|
21
23
|
}
|
|
22
24
|
if (_.isPlainObject(value)) {
|
|
23
25
|
const opts = super.get(this.magicAttribute) || {};
|
|
24
|
-
return
|
|
26
|
+
return this.setV1(`${this.magicAttribute}.${key}`, merge(opts?.[key], value), options);
|
|
25
27
|
}
|
|
26
|
-
return
|
|
28
|
+
return this.setV1(`${this.magicAttribute}.${key}`, value, options);
|
|
27
29
|
} else {
|
|
28
30
|
if (!key) {
|
|
29
31
|
return;
|
|
30
32
|
}
|
|
31
33
|
Object.keys(key).forEach((k) => {
|
|
32
|
-
this.
|
|
34
|
+
this.setV1(k, key[k], options);
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
|
-
return
|
|
37
|
+
return this.setV1(key, value, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setV1(key?: any, value?: any, options?: any) {
|
|
41
|
+
let values;
|
|
42
|
+
let originalValue;
|
|
43
|
+
|
|
44
|
+
if (typeof key === 'object' && key !== null) {
|
|
45
|
+
values = key;
|
|
46
|
+
options = value || {};
|
|
47
|
+
|
|
48
|
+
if (options.reset) {
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
this.dataValues = {};
|
|
51
|
+
for (const key in values) {
|
|
52
|
+
this.changed<any>(key, false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// If raw, and we're not dealing with includes or special attributes, just set it straight on the dataValues object
|
|
57
|
+
// @ts-ignore
|
|
58
|
+
if (
|
|
59
|
+
options.raw &&
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
!(this._options && this._options.include) &&
|
|
62
|
+
!(options && options.attributes) &&
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
!this.constructor._hasDateAttributes &&
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
!this.constructor._hasBooleanAttributes
|
|
67
|
+
) {
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
if (Object.keys(this.dataValues).length) {
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
Object.assign(this.dataValues, values);
|
|
72
|
+
} else {
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
this.dataValues = values;
|
|
75
|
+
}
|
|
76
|
+
// If raw, .changed() shouldn't be true
|
|
77
|
+
// @ts-ignore
|
|
78
|
+
this._previousDataValues = { ...this.dataValues };
|
|
79
|
+
} else {
|
|
80
|
+
// Loop and call set
|
|
81
|
+
if (options.attributes) {
|
|
82
|
+
const setKeys = (data) => {
|
|
83
|
+
for (const k of data) {
|
|
84
|
+
if (values[k] === undefined) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
this.set(k, values[k], options);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
setKeys(options.attributes);
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
if (this.constructor._hasVirtualAttributes) {
|
|
93
|
+
// @ts-ignore
|
|
94
|
+
setKeys(this.constructor._virtualAttributes);
|
|
95
|
+
}
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
if (this._options.includeNames) {
|
|
98
|
+
// @ts-ignore
|
|
99
|
+
setKeys(this._options.includeNames);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
for (const key in values) {
|
|
103
|
+
this.set(key, values[key], options);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (options.raw) {
|
|
108
|
+
// If raw, .changed() shouldn't be true
|
|
109
|
+
// @ts-ignore
|
|
110
|
+
this._previousDataValues = { ...this.dataValues };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
if (!options) options = {};
|
|
116
|
+
if (!options.raw) {
|
|
117
|
+
// @ts-ignore
|
|
118
|
+
originalValue = this.dataValues[key];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// If not raw, and there's a custom setter
|
|
122
|
+
// @ts-ignore
|
|
123
|
+
if (!options.raw && this._customSetters[key]) {
|
|
124
|
+
// @ts-ignore
|
|
125
|
+
this._customSetters[key].call(this, value, key);
|
|
126
|
+
// custom setter should have changed value, get that changed value
|
|
127
|
+
// TODO: v5 make setters return new value instead of changing internal store
|
|
128
|
+
// @ts-ignore
|
|
129
|
+
const newValue = this.dataValues[key];
|
|
130
|
+
if (!_.isEqual(newValue, originalValue)) {
|
|
131
|
+
// @ts-ignore
|
|
132
|
+
this._previousDataValues[key] = originalValue;
|
|
133
|
+
this.changed(key, true);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
// Check if we have included models, and if this key matches the include model names/aliases
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
if (this._options && this._options.include && this._options.includeNames.includes(key)) {
|
|
139
|
+
// Pass it on to the include handler
|
|
140
|
+
// @ts-ignore
|
|
141
|
+
this._setInclude(key, value, options);
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
// Bunch of stuff we won't do when it's raw
|
|
145
|
+
if (!options.raw) {
|
|
146
|
+
// If attribute is not in model definition, return
|
|
147
|
+
// @ts-ignore
|
|
148
|
+
if (!this._isAttribute(key)) {
|
|
149
|
+
// @ts-ignore
|
|
150
|
+
if (key.includes('.') && this.constructor._jsonAttributes.has(key.split('.')[0])) {
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
const previousNestedValue = Dottie.get(this.dataValues, key);
|
|
153
|
+
if (!_.isEqual(previousNestedValue, value)) {
|
|
154
|
+
// @ts-ignore
|
|
155
|
+
this._previousDataValues = _.cloneDeep(this._previousDataValues);
|
|
156
|
+
// @ts-ignore
|
|
157
|
+
Dottie.set(this.dataValues, key, value);
|
|
158
|
+
this.changed(key.split('.')[0], true);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// If attempting to set primary key and primary key is already defined, return
|
|
165
|
+
// @ts-ignore
|
|
166
|
+
if (this.constructor._hasPrimaryKeys && originalValue && this.constructor._isPrimaryKey(key)) {
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If attempting to set read only attributes, return
|
|
171
|
+
// @ts-ignore
|
|
172
|
+
if (
|
|
173
|
+
!this.isNewRecord &&
|
|
174
|
+
// @ts-ignore
|
|
175
|
+
this.constructor._hasReadOnlyAttributes &&
|
|
176
|
+
// @ts-ignore
|
|
177
|
+
this.constructor._readOnlyAttributes.has(key)
|
|
178
|
+
) {
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// If there's a data type sanitizer
|
|
184
|
+
if (
|
|
185
|
+
!(value instanceof Utils.SequelizeMethod) &&
|
|
186
|
+
// @ts-ignore
|
|
187
|
+
Object.prototype.hasOwnProperty.call(this.constructor._dataTypeSanitizers, key)
|
|
188
|
+
) {
|
|
189
|
+
// @ts-ignore
|
|
190
|
+
value = this.constructor._dataTypeSanitizers[key].call(this, value, options);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Set when the value has changed and not raw
|
|
194
|
+
if (
|
|
195
|
+
!options.raw &&
|
|
196
|
+
// True when sequelize method
|
|
197
|
+
(value instanceof Utils.SequelizeMethod ||
|
|
198
|
+
// Check for data type type comparators
|
|
199
|
+
// @ts-ignore
|
|
200
|
+
(!(value instanceof Utils.SequelizeMethod) &&
|
|
201
|
+
// @ts-ignore
|
|
202
|
+
this.constructor._dataTypeChanges[key] &&
|
|
203
|
+
// @ts-ignore
|
|
204
|
+
this.constructor._dataTypeChanges[key].call(this, value, originalValue, options)) || // Check default
|
|
205
|
+
// @ts-ignore
|
|
206
|
+
(!this.constructor._dataTypeChanges[key] && !_.isEqual(value, originalValue)))
|
|
207
|
+
) {
|
|
208
|
+
// @ts-ignore
|
|
209
|
+
this._previousDataValues[key] = originalValue;
|
|
210
|
+
this.changed(key, true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// set data value
|
|
214
|
+
// @ts-ignore
|
|
215
|
+
this.dataValues[key] = value;
|
|
216
|
+
}
|
|
217
|
+
return this;
|
|
36
218
|
}
|
|
37
219
|
|
|
38
220
|
get(key?: any, value?: any): any {
|
package/src/mock-database.ts
CHANGED
|
@@ -20,7 +20,7 @@ export function getConfigByEnv() {
|
|
|
20
20
|
database: process.env.DB_DATABASE,
|
|
21
21
|
host: process.env.DB_HOST,
|
|
22
22
|
port: process.env.DB_PORT,
|
|
23
|
-
dialect: process.env.DB_DIALECT,
|
|
23
|
+
dialect: process.env.DB_DIALECT || 'sqlite',
|
|
24
24
|
logging: process.env.DB_LOGGING === 'on' ? console.log : false,
|
|
25
25
|
storage:
|
|
26
26
|
process.env.DB_STORAGE && process.env.DB_STORAGE !== ':memory:'
|