@nocobase/database 0.7.7-alpha.1 → 0.8.0-alpha.5
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 +1 -1
- package/lib/collection.js +1 -0
- package/lib/database.d.ts +17 -2
- package/lib/database.js +53 -41
- package/lib/features/ReferencesMap.d.ts +15 -0
- package/lib/features/ReferencesMap.js +60 -0
- package/lib/features/referential-integrity-check.d.ts +8 -0
- package/lib/features/referential-integrity-check.js +89 -0
- package/lib/fields/belongs-to-field.d.ts +2 -0
- package/lib/fields/belongs-to-field.js +16 -2
- package/lib/fields/belongs-to-many-field.d.ts +1 -0
- package/lib/fields/belongs-to-many-field.js +8 -2
- package/lib/fields/context-field.d.ts +2 -1
- package/lib/fields/context-field.js +12 -8
- package/lib/fields/field.d.ts +2 -1
- package/lib/fields/field.js +1 -0
- package/lib/fields/has-many-field.d.ts +2 -0
- package/lib/fields/has-many-field.js +19 -1
- package/lib/fields/has-one-field.d.ts +2 -0
- package/lib/fields/has-one-field.js +16 -2
- package/lib/filter-parser.js +5 -3
- package/lib/model.d.ts +1 -0
- package/lib/options-parser.js +3 -2
- package/lib/relation-repository/belongs-to-many-repository.d.ts +3 -3
- package/lib/relation-repository/hasmany-repository.d.ts +2 -2
- package/lib/relation-repository/hasone-repository.d.ts +2 -4
- package/lib/relation-repository/multiple-relation-repository.d.ts +2 -5
- package/lib/relation-repository/relation-repository.js +2 -2
- package/lib/relation-repository/single-relation-repository.d.ts +1 -1
- package/lib/repository.d.ts +25 -15
- package/lib/repository.js +3 -1
- package/lib/types.d.ts +43 -0
- package/lib/types.js +5 -0
- package/package.json +3 -3
- package/src/__tests__/fields/belongs-to-field.test.ts +112 -4
- package/src/__tests__/fields/has-many-field.test.ts +83 -0
- package/src/__tests__/relation-repository/hasone-repository.test.ts +3 -3
- package/src/__tests__/repository/find.test.ts +10 -0
- package/src/collection.ts +3 -1
- package/src/database.ts +64 -15
- package/src/features/ReferencesMap.ts +64 -0
- package/src/features/referential-integrity-check.ts +61 -0
- package/src/fields/belongs-to-field.ts +21 -1
- package/src/fields/belongs-to-many-field.ts +6 -0
- package/src/fields/context-field.ts +5 -7
- package/src/fields/field.ts +4 -3
- package/src/fields/has-many-field.ts +25 -2
- package/src/fields/has-one-field.ts +22 -2
- package/src/filter-parser.ts +5 -1
- package/src/model.ts +1 -0
- package/src/options-parser.ts +4 -2
- package/src/relation-repository/belongs-to-many-repository.ts +3 -3
- package/src/relation-repository/hasmany-repository.ts +8 -5
- package/src/relation-repository/hasone-repository.ts +2 -4
- package/src/relation-repository/multiple-relation-repository.ts +3 -4
- package/src/relation-repository/relation-repository.ts +1 -1
- package/src/relation-repository/single-relation-repository.ts +1 -1
- package/src/repository.ts +41 -19
- package/src/types.ts +64 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { mockDatabase } from '../index';
|
|
2
|
-
import { HasOneRepository } from '../../relation-repository/hasone-repository';
|
|
3
|
-
import Database from '../../database';
|
|
4
1
|
import { Collection } from '../../collection';
|
|
2
|
+
import Database from '../../database';
|
|
3
|
+
import { HasOneRepository } from '../../relation-repository/hasone-repository';
|
|
4
|
+
import { mockDatabase } from '../index';
|
|
5
5
|
|
|
6
6
|
describe('has one repository', () => {
|
|
7
7
|
let db: Database;
|
|
@@ -163,6 +163,16 @@ describe('repository find', () => {
|
|
|
163
163
|
expect(users[1]).toEqual(2);
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
+
test('find with empty or', async () => {
|
|
167
|
+
const usersCount = await User.repository.count({
|
|
168
|
+
filter: {
|
|
169
|
+
$or: [],
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(usersCount).toEqual(await User.repository.count());
|
|
174
|
+
});
|
|
175
|
+
|
|
166
176
|
test('find with fields', async () => {
|
|
167
177
|
let user = await User.repository.findOne({
|
|
168
178
|
fields: ['name'],
|
package/src/collection.ts
CHANGED
|
@@ -65,7 +65,7 @@ export class Collection<
|
|
|
65
65
|
return this.context.database;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
constructor(options: CollectionOptions, context
|
|
68
|
+
constructor(options: CollectionOptions, context: CollectionContext) {
|
|
69
69
|
super();
|
|
70
70
|
this.checkOptions(options);
|
|
71
71
|
|
|
@@ -74,6 +74,8 @@ export class Collection<
|
|
|
74
74
|
|
|
75
75
|
this.bindFieldEventListener();
|
|
76
76
|
this.modelInit();
|
|
77
|
+
this.db.modelCollection.set(this.model, this);
|
|
78
|
+
|
|
77
79
|
this.setFields(options.fields);
|
|
78
80
|
this.setRepository(options.repository);
|
|
79
81
|
this.setSortable(options.sortable);
|
package/src/database.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
|
|
1
|
+
import { applyMixins, AsyncEmitter, requireModule } from '@nocobase/utils';
|
|
2
2
|
import merge from 'deepmerge';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
4
|
import glob from 'glob';
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
Sequelize,
|
|
15
15
|
SyncOptions,
|
|
16
16
|
Transactionable,
|
|
17
|
-
Utils
|
|
17
|
+
Utils,
|
|
18
18
|
} from 'sequelize';
|
|
19
19
|
import { SequelizeStorage, Umzug } from 'umzug';
|
|
20
20
|
import { Collection, CollectionOptions, RepositoryType } from './collection';
|
|
@@ -27,6 +27,35 @@ import { ModelHook } from './model-hook';
|
|
|
27
27
|
import extendOperators from './operators';
|
|
28
28
|
import { RelationRepository } from './relation-repository/relation-repository';
|
|
29
29
|
import { Repository } from './repository';
|
|
30
|
+
import {
|
|
31
|
+
AfterDefineCollectionListener,
|
|
32
|
+
BeforeDefineCollectionListener,
|
|
33
|
+
CreateListener,
|
|
34
|
+
CreateWithAssociationsListener,
|
|
35
|
+
DatabaseAfterDefineCollectionEventType,
|
|
36
|
+
DatabaseAfterRemoveCollectionEventType,
|
|
37
|
+
DatabaseBeforeDefineCollectionEventType,
|
|
38
|
+
DatabaseBeforeRemoveCollectionEventType,
|
|
39
|
+
DestroyListener,
|
|
40
|
+
EventType,
|
|
41
|
+
ModelCreateEventTypes,
|
|
42
|
+
ModelCreateWithAssociationsEventTypes,
|
|
43
|
+
ModelDestroyEventTypes,
|
|
44
|
+
ModelSaveEventTypes,
|
|
45
|
+
ModelSaveWithAssociationsEventTypes,
|
|
46
|
+
ModelUpdateEventTypes,
|
|
47
|
+
ModelUpdateWithAssociationsEventTypes,
|
|
48
|
+
ModelValidateEventTypes,
|
|
49
|
+
RemoveCollectionListener,
|
|
50
|
+
SaveListener,
|
|
51
|
+
SaveWithAssociationsListener,
|
|
52
|
+
SyncListener,
|
|
53
|
+
UpdateListener,
|
|
54
|
+
UpdateWithAssociationsListener,
|
|
55
|
+
ValidateListener,
|
|
56
|
+
} from './types';
|
|
57
|
+
import { referentialIntegrityCheck } from './features/referential-integrity-check';
|
|
58
|
+
import ReferencesMap from './features/ReferencesMap';
|
|
30
59
|
|
|
31
60
|
export interface MergeOptions extends merge.Options {}
|
|
32
61
|
|
|
@@ -119,6 +148,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
119
148
|
collections = new Map<string, Collection>();
|
|
120
149
|
pendingFields = new Map<string, RelationField[]>();
|
|
121
150
|
modelCollection = new Map<ModelCtor<any>, Collection>();
|
|
151
|
+
referenceMap = new ReferencesMap();
|
|
122
152
|
|
|
123
153
|
modelHook: ModelHook;
|
|
124
154
|
version: DatabaseVersion;
|
|
@@ -128,6 +158,8 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
128
158
|
constructor(options: DatabaseOptions) {
|
|
129
159
|
super();
|
|
130
160
|
|
|
161
|
+
// this.setMaxListeners(100);
|
|
162
|
+
|
|
131
163
|
this.version = new DatabaseVersion(this);
|
|
132
164
|
|
|
133
165
|
const opts = {
|
|
@@ -206,6 +238,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
206
238
|
}
|
|
207
239
|
});
|
|
208
240
|
|
|
241
|
+
this.initListener();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
initListener() {
|
|
209
245
|
this.on('afterCreate', async (instance) => {
|
|
210
246
|
instance?.toChangedWithAssociations?.();
|
|
211
247
|
});
|
|
@@ -213,6 +249,14 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
213
249
|
this.on('afterUpdate', async (instance) => {
|
|
214
250
|
instance?.toChangedWithAssociations?.();
|
|
215
251
|
});
|
|
252
|
+
|
|
253
|
+
this.on('beforeDestroy', async (instance, options) => {
|
|
254
|
+
await referentialIntegrityCheck({
|
|
255
|
+
db: this,
|
|
256
|
+
referencedInstance: instance,
|
|
257
|
+
transaction: options.transaction,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
216
260
|
}
|
|
217
261
|
|
|
218
262
|
addMigration(item: MigrationItem) {
|
|
@@ -230,7 +274,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
230
274
|
filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
|
|
231
275
|
this.migrations.add({
|
|
232
276
|
name: namespace ? `${namespace}/${filename}` : filename,
|
|
233
|
-
migration:
|
|
277
|
+
migration: requireModule(file),
|
|
234
278
|
context,
|
|
235
279
|
});
|
|
236
280
|
}
|
|
@@ -240,16 +284,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
240
284
|
return dialect.includes(this.sequelize.getDialect());
|
|
241
285
|
}
|
|
242
286
|
|
|
243
|
-
private requireModule(module: any) {
|
|
244
|
-
if (typeof module === 'string') {
|
|
245
|
-
module = require(module);
|
|
246
|
-
}
|
|
247
|
-
if (typeof module !== 'object') {
|
|
248
|
-
return module;
|
|
249
|
-
}
|
|
250
|
-
return module.__esModule ? module.default : module;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
287
|
/**
|
|
254
288
|
* Add collection to database
|
|
255
289
|
* @param options
|
|
@@ -264,7 +298,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
264
298
|
});
|
|
265
299
|
|
|
266
300
|
this.collections.set(collection.name, collection);
|
|
267
|
-
this.modelCollection.set(collection.model, collection);
|
|
268
301
|
|
|
269
302
|
this.emit('afterDefineCollection', collection);
|
|
270
303
|
|
|
@@ -466,7 +499,23 @@ export class Database extends EventEmitter implements AsyncEmitter {
|
|
|
466
499
|
return this.sequelize.close();
|
|
467
500
|
}
|
|
468
501
|
|
|
469
|
-
on(event:
|
|
502
|
+
on(event: EventType, listener: any): this;
|
|
503
|
+
on(event: ModelValidateEventTypes, listener: SyncListener): this;
|
|
504
|
+
on(event: ModelValidateEventTypes, listener: ValidateListener): this;
|
|
505
|
+
on(event: ModelCreateEventTypes, listener: CreateListener): this;
|
|
506
|
+
on(event: ModelUpdateEventTypes, listener: UpdateListener): this;
|
|
507
|
+
on(event: ModelSaveEventTypes, listener: SaveListener): this;
|
|
508
|
+
on(event: ModelDestroyEventTypes, listener: DestroyListener): this;
|
|
509
|
+
on(event: ModelCreateWithAssociationsEventTypes, listener: CreateWithAssociationsListener): this;
|
|
510
|
+
on(event: ModelUpdateWithAssociationsEventTypes, listener: UpdateWithAssociationsListener): this;
|
|
511
|
+
on(event: ModelSaveWithAssociationsEventTypes, listener: SaveWithAssociationsListener): this;
|
|
512
|
+
on(event: DatabaseBeforeDefineCollectionEventType, listener: BeforeDefineCollectionListener): this;
|
|
513
|
+
on(event: DatabaseAfterDefineCollectionEventType, listener: AfterDefineCollectionListener): this;
|
|
514
|
+
on(
|
|
515
|
+
event: DatabaseBeforeRemoveCollectionEventType | DatabaseAfterRemoveCollectionEventType,
|
|
516
|
+
listener: RemoveCollectionListener,
|
|
517
|
+
): this;
|
|
518
|
+
on(event: EventType, listener: any): this {
|
|
470
519
|
// NOTE: to match if event is a sequelize or model type
|
|
471
520
|
const type = this.modelHook.match(event);
|
|
472
521
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface Reference {
|
|
2
|
+
sourceCollectionName: string;
|
|
3
|
+
sourceField: string;
|
|
4
|
+
targetField: string;
|
|
5
|
+
targetCollectionName: string;
|
|
6
|
+
onDelete: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
class ReferencesMap {
|
|
10
|
+
protected map: Map<string, Reference[]> = new Map();
|
|
11
|
+
|
|
12
|
+
addReference(reference: Reference) {
|
|
13
|
+
const existReference = this.existReference(reference);
|
|
14
|
+
|
|
15
|
+
if (existReference) {
|
|
16
|
+
if (reference.onDelete && existReference.onDelete !== reference.onDelete) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`On Delete Conflict, exist reference ${JSON.stringify(existReference)}, new reference ${JSON.stringify(
|
|
19
|
+
reference,
|
|
20
|
+
)}`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!reference.onDelete) {
|
|
28
|
+
reference.onDelete = 'SET NULL';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.map.set(reference.targetCollectionName, [...(this.map.get(reference.targetCollectionName) || []), reference]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getReferences(collectionName) {
|
|
35
|
+
return this.map.get(collectionName);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
existReference(reference: Reference) {
|
|
39
|
+
const references = this.map.get(reference.targetCollectionName);
|
|
40
|
+
|
|
41
|
+
if (!references) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const keys = Object.keys(reference).filter((k) => k !== 'onDelete');
|
|
46
|
+
|
|
47
|
+
return references.find((ref) => keys.every((key) => ref[key] === reference[key]));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
removeReference(reference: Reference) {
|
|
51
|
+
const references = this.map.get(reference.targetCollectionName);
|
|
52
|
+
if (!references) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const keys = Object.keys(reference);
|
|
56
|
+
|
|
57
|
+
this.map.set(
|
|
58
|
+
reference.targetCollectionName,
|
|
59
|
+
references.filter((ref) => !keys.every((key) => ref[key] === reference[key])),
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default ReferencesMap;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Database from '../database';
|
|
2
|
+
import { Model, Transactionable } from 'sequelize';
|
|
3
|
+
|
|
4
|
+
interface ReferentialIntegrityCheckOptions extends Transactionable {
|
|
5
|
+
db: Database;
|
|
6
|
+
referencedInstance: Model;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function referentialIntegrityCheck(options: ReferentialIntegrityCheckOptions) {
|
|
10
|
+
const { referencedInstance, db, transaction } = options;
|
|
11
|
+
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
const collection = db.modelCollection.get(referencedInstance.constructor);
|
|
14
|
+
|
|
15
|
+
const collectionName = collection.name;
|
|
16
|
+
const references = db.referenceMap.getReferences(collectionName);
|
|
17
|
+
|
|
18
|
+
if (!references) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const reference of references) {
|
|
23
|
+
const { sourceCollectionName, sourceField, targetField, onDelete } = reference;
|
|
24
|
+
const sourceCollection = db.collections.get(sourceCollectionName);
|
|
25
|
+
const sourceRepository = sourceCollection.repository;
|
|
26
|
+
|
|
27
|
+
const filter = {
|
|
28
|
+
[sourceField]: referencedInstance[targetField],
|
|
29
|
+
};
|
|
30
|
+
const referencingExists = await sourceRepository.count({
|
|
31
|
+
filter,
|
|
32
|
+
transaction,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!referencingExists) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (onDelete === 'RESTRICT') {
|
|
40
|
+
throw new Error('RESTRICT');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (onDelete === 'CASCADE') {
|
|
44
|
+
await sourceRepository.destroy({
|
|
45
|
+
filter: filter,
|
|
46
|
+
transaction,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (onDelete === 'SET NULL') {
|
|
51
|
+
await sourceRepository.update({
|
|
52
|
+
filter,
|
|
53
|
+
values: {
|
|
54
|
+
[sourceField]: null,
|
|
55
|
+
},
|
|
56
|
+
hooks: false,
|
|
57
|
+
transaction,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -2,6 +2,7 @@ import { omit } from 'lodash';
|
|
|
2
2
|
import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
|
|
3
3
|
import { checkIdentifier } from '../utils';
|
|
4
4
|
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
|
5
|
+
import { Reference } from '../features/ReferencesMap';
|
|
5
6
|
|
|
6
7
|
export class BelongsToField extends RelationField {
|
|
7
8
|
static type = 'belongsTo';
|
|
@@ -11,6 +12,18 @@ export class BelongsToField extends RelationField {
|
|
|
11
12
|
return target || Utils.pluralize(name);
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
reference(association): Reference {
|
|
16
|
+
const targetKey = association.targetKey;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
sourceCollectionName: this.database.modelCollection.get(association.source).name,
|
|
20
|
+
sourceField: association.foreignKey,
|
|
21
|
+
targetField: targetKey,
|
|
22
|
+
targetCollectionName: this.database.modelCollection.get(association.target).name,
|
|
23
|
+
onDelete: this.options.onDelete,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
bind() {
|
|
15
28
|
const { database, collection } = this.context;
|
|
16
29
|
const Target = this.TargetModel;
|
|
@@ -30,7 +43,7 @@ export class BelongsToField extends RelationField {
|
|
|
30
43
|
const association = collection.model.belongsTo(Target, {
|
|
31
44
|
as: this.name,
|
|
32
45
|
constraints: false,
|
|
33
|
-
...omit(this.options, ['name', 'type', 'target']),
|
|
46
|
+
...omit(this.options, ['name', 'type', 'target', 'onDelete']),
|
|
34
47
|
});
|
|
35
48
|
|
|
36
49
|
// inverse relation
|
|
@@ -56,6 +69,9 @@ export class BelongsToField extends RelationField {
|
|
|
56
69
|
}
|
|
57
70
|
|
|
58
71
|
this.collection.addIndex([this.options.foreignKey]);
|
|
72
|
+
|
|
73
|
+
this.database.referenceMap.addReference(this.reference(association));
|
|
74
|
+
|
|
59
75
|
return true;
|
|
60
76
|
}
|
|
61
77
|
|
|
@@ -73,6 +89,10 @@ export class BelongsToField extends RelationField {
|
|
|
73
89
|
if (!field1 && !field2) {
|
|
74
90
|
collection.model.removeAttribute(foreignKey);
|
|
75
91
|
}
|
|
92
|
+
|
|
93
|
+
const association = collection.model.associations[this.name];
|
|
94
|
+
this.database.referenceMap.removeReference(this.reference(association));
|
|
95
|
+
|
|
76
96
|
// 删掉 model 的关联字段
|
|
77
97
|
delete collection.model.associations[this.name];
|
|
78
98
|
// @ts-ignore
|
|
@@ -17,6 +17,10 @@ export class BelongsToManyField extends RelationField {
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
get otherKey() {
|
|
21
|
+
return this.options.otherKey;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
bind() {
|
|
21
25
|
const { database, collection } = this.context;
|
|
22
26
|
const Target = this.TargetModel;
|
|
@@ -34,6 +38,7 @@ export class BelongsToManyField extends RelationField {
|
|
|
34
38
|
} else {
|
|
35
39
|
Through = database.collection({
|
|
36
40
|
name: through,
|
|
41
|
+
// timestamps: false,
|
|
37
42
|
});
|
|
38
43
|
|
|
39
44
|
Object.defineProperty(Through.model, 'isThrough', { value: true });
|
|
@@ -80,6 +85,7 @@ export class BelongsToManyField extends RelationField {
|
|
|
80
85
|
|
|
81
86
|
unbind() {
|
|
82
87
|
const { database, collection } = this.context;
|
|
88
|
+
const Through = database.getCollection(this.through);
|
|
83
89
|
// 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段
|
|
84
90
|
database.removePendingField(this);
|
|
85
91
|
// 删掉 model 的关联字段
|
|
@@ -9,14 +9,12 @@ export class ContextField extends Field {
|
|
|
9
9
|
return DataTypes[type.toUpperCase()] || DataTypes.STRING;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
listener = async (model: Model, options) => {
|
|
13
13
|
const { name, dataIndex } = this.options;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
}
|
|
14
|
+
const { context } = options;
|
|
15
|
+
model.set(name, lodash.get(context, dataIndex));
|
|
16
|
+
model.changed(name, true);
|
|
17
|
+
};
|
|
20
18
|
|
|
21
19
|
bind() {
|
|
22
20
|
super.bind();
|
package/src/fields/field.ts
CHANGED
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
ModelIndexesOptions,
|
|
6
6
|
QueryInterfaceOptions,
|
|
7
7
|
SyncOptions,
|
|
8
|
-
Transactionable
|
|
8
|
+
Transactionable
|
|
9
9
|
} from 'sequelize';
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
11
|
import { Database } from '../database';
|
|
12
|
-
import {
|
|
12
|
+
import { ModelEventTypes } from '../types';
|
|
13
13
|
|
|
14
14
|
export interface FieldContext {
|
|
15
15
|
database: Database;
|
|
@@ -68,7 +68,7 @@ export abstract class Field {
|
|
|
68
68
|
// code
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
on(eventName:
|
|
71
|
+
on(eventName: ModelEventTypes, listener: (...args: any[]) => void) {
|
|
72
72
|
this.database.on(`${this.collection.name}.${eventName}`, listener);
|
|
73
73
|
return this;
|
|
74
74
|
}
|
|
@@ -83,6 +83,7 @@ export abstract class Field {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
remove() {
|
|
86
|
+
this.collection.removeIndex([this.name]);
|
|
86
87
|
return this.collection.removeField(this.name);
|
|
87
88
|
}
|
|
88
89
|
|
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
ForeignKeyOptions,
|
|
6
6
|
HasManyOptions,
|
|
7
7
|
HasManyOptions as SequelizeHasManyOptions,
|
|
8
|
-
Utils
|
|
8
|
+
Utils,
|
|
9
9
|
} from 'sequelize';
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
11
|
import { checkIdentifier } from '../utils';
|
|
12
12
|
import { MultipleRelationFieldOptions, RelationField } from './relation-field';
|
|
13
|
+
import { Reference } from '../features/ReferencesMap';
|
|
13
14
|
|
|
14
15
|
export interface HasManyFieldOptions extends HasManyOptions {
|
|
15
16
|
/**
|
|
@@ -81,6 +82,18 @@ export class HasManyField extends RelationField {
|
|
|
81
82
|
return Utils.camelize([model.options.name.singular, this.sourceKey || model.primaryKeyAttribute].join('_'));
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
reference(association): Reference {
|
|
86
|
+
const sourceKey = association.sourceKey;
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
sourceCollectionName: this.database.modelCollection.get(association.target).name,
|
|
90
|
+
sourceField: association.foreignKey,
|
|
91
|
+
targetField: sourceKey,
|
|
92
|
+
targetCollectionName: this.database.modelCollection.get(association.source).name,
|
|
93
|
+
onDelete: this.options.onDelete,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
bind() {
|
|
85
98
|
const { database, collection } = this.context;
|
|
86
99
|
const Target = this.TargetModel;
|
|
@@ -95,7 +108,7 @@ export class HasManyField extends RelationField {
|
|
|
95
108
|
|
|
96
109
|
const association = collection.model.hasMany(Target, {
|
|
97
110
|
constraints: false,
|
|
98
|
-
...omit(this.options, ['name', 'type', 'target']),
|
|
111
|
+
...omit(this.options, ['name', 'type', 'target', 'onDelete']),
|
|
99
112
|
as: this.name,
|
|
100
113
|
foreignKey: this.foreignKey,
|
|
101
114
|
});
|
|
@@ -130,6 +143,9 @@ export class HasManyField extends RelationField {
|
|
|
130
143
|
if (tcoll) {
|
|
131
144
|
tcoll.addIndex([this.options.foreignKey]);
|
|
132
145
|
}
|
|
146
|
+
|
|
147
|
+
this.database.referenceMap.addReference(this.reference(association));
|
|
148
|
+
|
|
133
149
|
return true;
|
|
134
150
|
}
|
|
135
151
|
|
|
@@ -149,6 +165,13 @@ export class HasManyField extends RelationField {
|
|
|
149
165
|
if (!field) {
|
|
150
166
|
tcoll.model.removeAttribute(foreignKey);
|
|
151
167
|
}
|
|
168
|
+
|
|
169
|
+
const association = collection.model.associations[this.name];
|
|
170
|
+
|
|
171
|
+
if (association) {
|
|
172
|
+
this.database.referenceMap.removeReference(this.reference(association));
|
|
173
|
+
}
|
|
174
|
+
|
|
152
175
|
// 删掉 model 的关联字段
|
|
153
176
|
delete collection.model.associations[this.name];
|
|
154
177
|
// @ts-ignore
|
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
ForeignKeyOptions,
|
|
6
6
|
HasOneOptions,
|
|
7
7
|
HasOneOptions as SequelizeHasOneOptions,
|
|
8
|
-
Utils
|
|
8
|
+
Utils,
|
|
9
9
|
} from 'sequelize';
|
|
10
10
|
import { Collection } from '../collection';
|
|
11
11
|
import { checkIdentifier } from '../utils';
|
|
12
12
|
import { BaseRelationFieldOptions, RelationField } from './relation-field';
|
|
13
|
+
import { Reference } from '../features/ReferencesMap';
|
|
13
14
|
|
|
14
15
|
export interface HasOneFieldOptions extends HasOneOptions {
|
|
15
16
|
/**
|
|
@@ -86,9 +87,22 @@ export class HasOneField extends RelationField {
|
|
|
86
87
|
return Utils.camelize([model.options.name.singular, model.primaryKeyAttribute].join('_'));
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
reference(association): Reference {
|
|
91
|
+
const sourceKey = association.sourceKey;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
sourceCollectionName: this.database.modelCollection.get(association.target).name,
|
|
95
|
+
sourceField: association.foreignKey,
|
|
96
|
+
targetField: sourceKey,
|
|
97
|
+
targetCollectionName: this.database.modelCollection.get(association.source).name,
|
|
98
|
+
onDelete: this.options.onDelete,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
bind() {
|
|
90
103
|
const { database, collection } = this.context;
|
|
91
104
|
const Target = this.TargetModel;
|
|
105
|
+
|
|
92
106
|
if (!Target) {
|
|
93
107
|
database.addPendingField(this);
|
|
94
108
|
return false;
|
|
@@ -96,7 +110,7 @@ export class HasOneField extends RelationField {
|
|
|
96
110
|
|
|
97
111
|
const association = collection.model.hasOne(Target, {
|
|
98
112
|
constraints: false,
|
|
99
|
-
...omit(this.options, ['name', 'type', 'target']),
|
|
113
|
+
...omit(this.options, ['name', 'type', 'target', 'onDelete']),
|
|
100
114
|
as: this.name,
|
|
101
115
|
foreignKey: this.foreignKey,
|
|
102
116
|
});
|
|
@@ -129,6 +143,8 @@ export class HasOneField extends RelationField {
|
|
|
129
143
|
if (tcoll) {
|
|
130
144
|
tcoll.addIndex([this.options.foreignKey]);
|
|
131
145
|
}
|
|
146
|
+
|
|
147
|
+
this.database.referenceMap.addReference(this.reference(association));
|
|
132
148
|
return true;
|
|
133
149
|
}
|
|
134
150
|
|
|
@@ -148,6 +164,10 @@ export class HasOneField extends RelationField {
|
|
|
148
164
|
if (!field) {
|
|
149
165
|
tcoll.model.removeAttribute(foreignKey);
|
|
150
166
|
}
|
|
167
|
+
|
|
168
|
+
const association = collection.model.associations[this.name];
|
|
169
|
+
this.database.referenceMap.removeReference(this.reference(association));
|
|
170
|
+
|
|
151
171
|
// 删掉 model 的关联字段
|
|
152
172
|
delete collection.model.associations[this.name];
|
|
153
173
|
// @ts-ignore
|
package/src/filter-parser.ts
CHANGED
|
@@ -78,7 +78,6 @@ export default class FilterParser {
|
|
|
78
78
|
|
|
79
79
|
const include = {};
|
|
80
80
|
const where = {};
|
|
81
|
-
const filter2 = lodash.cloneDeep(flattenedFilter);
|
|
82
81
|
|
|
83
82
|
let skipPrefix = null;
|
|
84
83
|
const associations = model.associations;
|
|
@@ -91,6 +90,11 @@ export default class FilterParser {
|
|
|
91
90
|
continue;
|
|
92
91
|
}
|
|
93
92
|
|
|
93
|
+
// skip empty logic operator value
|
|
94
|
+
if ((key == '$or' || key == '$and') && Array.isArray(value) && value.length == 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
94
98
|
debug('handle filter key "%s: "%s"', key, value);
|
|
95
99
|
let keys = key.split('.');
|
|
96
100
|
|
package/src/model.ts
CHANGED
|
@@ -23,6 +23,7 @@ export class Model<TModelAttributes extends {} = any, TCreationAttributes extend
|
|
|
23
23
|
public static database: Database;
|
|
24
24
|
public static collection: Collection;
|
|
25
25
|
|
|
26
|
+
[key: string]: any;
|
|
26
27
|
protected _changedWithAssociations = new Set();
|
|
27
28
|
protected _previousDataValuesWithAssociations = {};
|
|
28
29
|
|
package/src/options-parser.ts
CHANGED
|
@@ -70,6 +70,7 @@ export class OptionsParser {
|
|
|
70
70
|
if (typeof sort === 'string') {
|
|
71
71
|
sort = sort.split(',');
|
|
72
72
|
}
|
|
73
|
+
|
|
73
74
|
const orderParams = [];
|
|
74
75
|
for (const sortKey of sort) {
|
|
75
76
|
let direction = sortKey.startsWith('-') ? 'DESC' : 'ASC';
|
|
@@ -191,8 +192,9 @@ export class OptionsParser {
|
|
|
191
192
|
|
|
192
193
|
/**
|
|
193
194
|
* set include params
|
|
194
|
-
* @param
|
|
195
|
-
* @param
|
|
195
|
+
* @param model
|
|
196
|
+
* @param queryParams
|
|
197
|
+
* @param append
|
|
196
198
|
*/
|
|
197
199
|
const setInclude = (model: ModelCtor<any>, queryParams: any, append: string) => {
|
|
198
200
|
const appendFields = append.split('.');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import lodash from 'lodash';
|
|
2
2
|
import { BelongsToMany, Op, Transaction } from 'sequelize';
|
|
3
3
|
import { Model } from '../model';
|
|
4
|
-
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
|
|
4
|
+
import { CreateOptions, DestroyOptions, FindOneOptions, FindOptions, TargetKey, UpdateOptions } from '../repository';
|
|
5
5
|
import { updateThroughTableValue } from '../update-associations';
|
|
6
|
-
import { FindAndCountOptions,
|
|
6
|
+
import { FindAndCountOptions, MultipleRelationRepository } from './multiple-relation-repository';
|
|
7
7
|
import { transaction } from './relation-repository';
|
|
8
8
|
import { AssociatedOptions, PrimaryKeyWithThroughValues } from './types';
|
|
9
9
|
|
|
@@ -14,7 +14,7 @@ interface IBelongsToManyRepository<M extends Model> {
|
|
|
14
14
|
findAndCount(options?: FindAndCountOptions): Promise<[M[], number]>;
|
|
15
15
|
findOne(options?: FindOneOptions): Promise<M>;
|
|
16
16
|
// 新增并关联,存在中间表数据
|
|
17
|
-
create(options?:
|
|
17
|
+
create(options?: CreateOptions): Promise<M>;
|
|
18
18
|
// 更新,存在中间表数据
|
|
19
19
|
update(options?: UpdateOptions): Promise<M>;
|
|
20
20
|
// 删除
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { omit } from 'lodash';
|
|
2
2
|
import { HasMany, Op } from 'sequelize';
|
|
3
3
|
import { Model } from '../model';
|
|
4
|
-
import { CreateOptions, DestroyOptions, FindOptions, TargetKey, TK, UpdateOptions } from '../repository';
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
CreateOptions,
|
|
6
|
+
DestroyOptions,
|
|
8
7
|
FindOneOptions,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
FindOptions,
|
|
9
|
+
TargetKey,
|
|
10
|
+
TK,
|
|
11
|
+
UpdateOptions,
|
|
12
|
+
} from '../repository';
|
|
13
|
+
import { AssociatedOptions, FindAndCountOptions, MultipleRelationRepository } from './multiple-relation-repository';
|
|
11
14
|
import { transaction } from './relation-repository';
|
|
12
15
|
|
|
13
16
|
interface IHasManyRepository<M extends Model> {
|