@nocobase/database 0.9.2-alpha.4 → 0.9.4-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.
Files changed (70) hide show
  1. package/lib/collection.d.ts +9 -9
  2. package/lib/collection.js +104 -96
  3. package/lib/database.d.ts +4 -4
  4. package/lib/database.js +25 -53
  5. package/lib/eager-loading/eager-loading-tree.d.ts +23 -0
  6. package/lib/eager-loading/eager-loading-tree.js +338 -0
  7. package/lib/fields/field.js +1 -0
  8. package/lib/filter-parser.d.ts +1 -7
  9. package/lib/filter-parser.js +27 -7
  10. package/lib/listeners/adjacency-list.d.ts +1 -2
  11. package/lib/listeners/adjacency-list.js +2 -71
  12. package/lib/listeners/append-child-collection-name-after-repository-find.d.ts +5 -0
  13. package/lib/listeners/append-child-collection-name-after-repository-find.js +40 -0
  14. package/lib/listeners/index.js +2 -1
  15. package/lib/mock-database.js +3 -1
  16. package/lib/operators/array.js +7 -4
  17. package/lib/operators/string.js +1 -1
  18. package/lib/options-parser.js +14 -0
  19. package/lib/query-interface/postgres-query-interface.js +2 -2
  20. package/lib/relation-repository/belongs-to-many-repository.d.ts +2 -1
  21. package/lib/relation-repository/belongs-to-many-repository.js +58 -37
  22. package/lib/relation-repository/hasmany-repository.d.ts +2 -1
  23. package/lib/relation-repository/hasmany-repository.js +31 -16
  24. package/lib/relation-repository/multiple-relation-repository.js +8 -26
  25. package/lib/relation-repository/relation-repository.d.ts +1 -7
  26. package/lib/relation-repository/single-relation-repository.d.ts +1 -1
  27. package/lib/relation-repository/single-relation-repository.js +10 -16
  28. package/lib/repository.d.ts +11 -8
  29. package/lib/repository.js +106 -90
  30. package/lib/sql-parser/postgres.js +41 -0
  31. package/lib/tree-repository/adjacency-list-repository.d.ts +9 -0
  32. package/lib/tree-repository/adjacency-list-repository.js +165 -0
  33. package/lib/update-guard.d.ts +1 -1
  34. package/lib/update-guard.js +16 -13
  35. package/lib/utils.d.ts +0 -7
  36. package/lib/utils.js +0 -76
  37. package/package.json +4 -4
  38. package/src/__tests__/collection.test.ts +19 -0
  39. package/src/__tests__/eager-loading/eager-loading-tree.test.ts +393 -0
  40. package/src/__tests__/migrator.test.ts +4 -0
  41. package/src/__tests__/relation-repository/hasone-repository.test.ts +1 -0
  42. package/src/__tests__/repository/aggregation.test.ts +297 -0
  43. package/src/__tests__/repository/count.test.ts +1 -1
  44. package/src/__tests__/repository/find.test.ts +267 -1
  45. package/src/__tests__/repository.test.ts +30 -0
  46. package/src/__tests__/tree.test.ts +266 -3
  47. package/src/__tests__/update-guard.test.ts +13 -0
  48. package/src/collection.ts +79 -65
  49. package/src/database.ts +26 -42
  50. package/src/eager-loading/eager-loading-tree.ts +304 -0
  51. package/src/fields/field.ts +4 -0
  52. package/src/filter-parser.ts +16 -2
  53. package/src/listeners/adjacency-list.ts +1 -44
  54. package/src/listeners/append-child-collection-name-after-repository-find.ts +31 -0
  55. package/src/listeners/index.ts +3 -2
  56. package/src/mock-database.ts +3 -1
  57. package/src/operators/array.ts +8 -4
  58. package/src/operators/notIn.ts +1 -0
  59. package/src/operators/string.ts +1 -1
  60. package/src/options-parser.ts +17 -0
  61. package/src/query-interface/postgres-query-interface.ts +1 -1
  62. package/src/relation-repository/belongs-to-many-repository.ts +33 -1
  63. package/src/relation-repository/hasmany-repository.ts +17 -0
  64. package/src/relation-repository/multiple-relation-repository.ts +14 -19
  65. package/src/relation-repository/single-relation-repository.ts +13 -15
  66. package/src/repository.ts +83 -38
  67. package/src/sql-parser/postgres.js +25505 -0
  68. package/src/tree-repository/adjacency-list-repository.ts +159 -0
  69. package/src/update-guard.ts +21 -16
  70. package/src/utils.ts +0 -61
package/src/collection.ts CHANGED
@@ -14,6 +14,7 @@ import { BelongsToField, Field, FieldOptions, HasManyField } from './fields';
14
14
  import { Model } from './model';
15
15
  import { Repository } from './repository';
16
16
  import { checkIdentifier, md5, snakeCase } from './utils';
17
+ import { AdjacencyListRepository } from './tree-repository/adjacency-list-repository';
17
18
 
18
19
  export type RepositoryType = typeof Repository;
19
20
 
@@ -78,6 +79,31 @@ export class Collection<
78
79
  model: ModelStatic<Model>;
79
80
  repository: Repository<TModelAttributes, TCreationAttributes>;
80
81
 
82
+ constructor(options: CollectionOptions, context: CollectionContext) {
83
+ super();
84
+ this.context = context;
85
+ this.options = options;
86
+
87
+ this.checkOptions(options);
88
+
89
+ this.bindFieldEventListener();
90
+ this.modelInit();
91
+
92
+ this.db.modelCollection.set(this.model, this);
93
+
94
+ // set tableName to collection map
95
+ // the form of key is `${schema}.${tableName}` if schema exists
96
+ // otherwise is `${tableName}`
97
+ this.db.tableNameCollectionMap.set(this.getTableNameWithSchemaAsString(), this);
98
+
99
+ if (!options.inherits) {
100
+ this.setFields(options.fields);
101
+ }
102
+
103
+ this.setRepository(options.repository);
104
+ this.setSortable(options.sortable);
105
+ }
106
+
81
107
  get filterTargetKey() {
82
108
  const targetKey = lodash.get(this.options, 'filterTargetKey', this.model.primaryKeyAttribute);
83
109
  if (!targetKey && this.model.rawAttributes['id']) {
@@ -115,65 +141,12 @@ export class Collection<
115
141
  }
116
142
  }
117
143
 
118
- constructor(options: CollectionOptions, context: CollectionContext) {
119
- super();
120
- this.context = context;
121
- this.options = options;
122
-
123
- this.checkOptions(options);
124
-
125
- this.bindFieldEventListener();
126
- this.modelInit();
127
-
128
- this.db.modelCollection.set(this.model, this);
129
-
130
- // set tableName to collection map
131
- // the form of key is `${schema}.${tableName}` if schema exists
132
- // otherwise is `${tableName}`
133
- this.db.tableNameCollectionMap.set(this.getTableNameWithSchemaAsString(), this);
134
-
135
- if (!options.inherits) {
136
- this.setFields(options.fields);
137
- }
138
-
139
- this.setRepository(options.repository);
140
- this.setSortable(options.sortable);
141
- }
142
-
143
- private checkOptions(options: CollectionOptions) {
144
- checkIdentifier(options.name);
145
- this.checkTableName();
146
- }
147
-
148
- private checkTableName() {
149
- const tableName = this.tableName();
150
- for (const [k, collection] of this.db.collections) {
151
- if (
152
- collection.name != this.options.name &&
153
- tableName === collection.tableName() &&
154
- collection.collectionSchema() === this.collectionSchema()
155
- ) {
156
- throw new Error(`collection ${collection.name} and ${this.name} have same tableName "${tableName}"`);
157
- }
158
- }
159
- }
160
-
161
144
  tableName() {
162
145
  const { name, tableName } = this.options;
163
146
  const tName = tableName || name;
164
147
  return this.options.underscored ? snakeCase(tName) : tName;
165
148
  }
166
149
 
167
- protected sequelizeModelOptions() {
168
- const { name } = this.options;
169
- return {
170
- ..._.omit(this.options, ['name', 'fields', 'model', 'targetKey']),
171
- modelName: name,
172
- sequelize: this.context.database.sequelize,
173
- tableName: this.tableName(),
174
- };
175
- }
176
-
177
150
  /**
178
151
  * TODO
179
152
  */
@@ -223,18 +196,12 @@ export class Collection<
223
196
  if (typeof repository === 'string') {
224
197
  repo = this.context.database.repositories.get(repository) || Repository;
225
198
  }
226
- this.repository = new repo(this);
227
- }
228
199
 
229
- private bindFieldEventListener() {
230
- this.on('field.afterAdd', (field: Field) => {
231
- field.bind();
232
- });
200
+ if (this.options.tree == 'adjacency-list' || this.options.tree == 'adjacencyList') {
201
+ repo = AdjacencyListRepository;
202
+ }
233
203
 
234
- this.on('field.afterRemove', (field: Field) => {
235
- field.unbind();
236
- this.db.emit('field.afterRemove', field);
237
- });
204
+ this.repository = new repo(this);
238
205
  }
239
206
 
240
207
  forEachField(callback: (field: Field) => void) {
@@ -293,12 +260,20 @@ export class Collection<
293
260
  const [sourceCollectionName, sourceFieldName] = options.source.split('.');
294
261
  const sourceCollection = this.db.collections.get(sourceCollectionName);
295
262
  if (!sourceCollection) {
296
- throw new Error(
263
+ this.db.logger.warn(
297
264
  `source collection "${sourceCollectionName}" not found for field "${name}" at collection "${this.name}"`,
298
265
  );
299
266
  }
267
+
300
268
  const sourceField = sourceCollection.fields.get(sourceFieldName);
301
- options = { ...sourceField.options, ...options };
269
+
270
+ if (!sourceField) {
271
+ this.db.logger.warn(
272
+ `source field "${sourceFieldName}" not found for field "${name}" at collection "${this.name}"`,
273
+ );
274
+ } else {
275
+ options = { ...sourceField.options, ...options };
276
+ }
302
277
  }
303
278
 
304
279
  this.emit('field.beforeAdd', name, options, { collection: this });
@@ -672,4 +647,43 @@ export class Collection<
672
647
  public isView() {
673
648
  return false;
674
649
  }
650
+
651
+ protected sequelizeModelOptions() {
652
+ const { name } = this.options;
653
+ return {
654
+ ..._.omit(this.options, ['name', 'fields', 'model', 'targetKey']),
655
+ modelName: name,
656
+ sequelize: this.context.database.sequelize,
657
+ tableName: this.tableName(),
658
+ };
659
+ }
660
+
661
+ private checkOptions(options: CollectionOptions) {
662
+ checkIdentifier(options.name);
663
+ this.checkTableName();
664
+ }
665
+
666
+ private checkTableName() {
667
+ const tableName = this.tableName();
668
+ for (const [k, collection] of this.db.collections) {
669
+ if (
670
+ collection.name != this.options.name &&
671
+ tableName === collection.tableName() &&
672
+ collection.collectionSchema() === this.collectionSchema()
673
+ ) {
674
+ throw new Error(`collection ${collection.name} and ${this.name} have same tableName "${tableName}"`);
675
+ }
676
+ }
677
+ }
678
+
679
+ private bindFieldEventListener() {
680
+ this.on('field.afterAdd', (field: Field) => {
681
+ field.bind();
682
+ });
683
+
684
+ this.on('field.afterRemove', (field: Field) => {
685
+ field.unbind();
686
+ this.db.emit('field.afterRemove', field);
687
+ });
688
+ }
675
689
  }
package/src/database.ts CHANGED
@@ -62,13 +62,13 @@ import {
62
62
  } from './types';
63
63
  import { patchSequelizeQueryInterface, snakeCase } from './utils';
64
64
 
65
+ import { Logger } from '@nocobase/logger';
66
+ import { CollectionGroupManager } from './collection-group-manager';
65
67
  import DatabaseUtils from './database-utils';
66
68
  import { registerBuiltInListeners } from './listeners';
67
- import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
68
- import buildQueryInterface from './query-interface/query-interface-builder';
69
69
  import QueryInterface from './query-interface/query-interface';
70
- import { Logger } from '@nocobase/logger';
71
- import { CollectionGroupManager } from './collection-group-manager';
70
+ import buildQueryInterface from './query-interface/query-interface-builder';
71
+ import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
72
72
  import { ViewCollection } from './view-collection';
73
73
 
74
74
  export type MergeOptions = merge.Options;
@@ -188,6 +188,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
188
188
  logger: Logger;
189
189
 
190
190
  collectionGroupManager = new CollectionGroupManager(this);
191
+ declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
191
192
 
192
193
  constructor(options: DatabaseOptions) {
193
194
  super();
@@ -275,6 +276,12 @@ export class Database extends EventEmitter implements AsyncEmitter {
275
276
  }),
276
277
  });
277
278
 
279
+ this.sequelize.beforeDefine((model, opts) => {
280
+ if (this.options.tablePrefix) {
281
+ opts.tableName = `${this.options.tablePrefix}${opts.tableName || opts.modelName || opts.name.plural}`;
282
+ }
283
+ });
284
+
278
285
  this.collection({
279
286
  name: 'migrations',
280
287
  autoGenId: false,
@@ -284,12 +291,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
284
291
  fields: [{ type: 'string', name: 'name' }],
285
292
  });
286
293
 
287
- this.sequelize.beforeDefine((model, opts) => {
288
- if (this.options.tablePrefix) {
289
- opts.tableName = `${this.options.tablePrefix}${opts.tableName || opts.modelName || opts.name.plural}`;
290
- }
291
- });
292
-
293
294
  this.initListener();
294
295
  patchSequelizeQueryInterface(this);
295
296
  }
@@ -387,36 +388,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
387
388
  }
388
389
  });
389
390
 
390
- this.on('afterRepositoryFind', ({ findOptions, dataCollection, data }) => {
391
- if (dataCollection.isParent()) {
392
- for (const row of data) {
393
- const rowCollection = this.tableNameCollectionMap.get(
394
- findOptions.raw
395
- ? `${row['__schemaName']}.${row['__tableName']}`
396
- : `${row.get('__schemaName')}.${row.get('__tableName')}`,
397
- );
398
-
399
- if (!rowCollection) {
400
- this.logger.warn(
401
- `Can not find collection by table name ${JSON.stringify(row)}, current collections: ${Array.from(
402
- this.tableNameCollectionMap.keys(),
403
- ).join(', ')}`,
404
- );
405
-
406
- return;
407
- }
408
-
409
- const rowCollectionName = rowCollection.name;
410
-
411
- findOptions.raw
412
- ? (row['__collection'] = rowCollectionName)
413
- : row.set('__collection', rowCollectionName, {
414
- raw: true,
415
- });
416
- }
417
- }
418
- });
419
-
420
391
  registerBuiltInListeners(this);
421
392
  }
422
393
 
@@ -565,7 +536,9 @@ export class Database extends EventEmitter implements AsyncEmitter {
565
536
  }
566
537
 
567
538
  getRepository<R extends Repository>(name: string): R;
539
+
568
540
  getRepository<R extends RelationRepository>(name: string, relationId: string | number): R;
541
+
569
542
  getRepository<R extends ArrayFieldRepository>(name: string, relationId: string | number): R;
570
543
 
571
544
  getRepository<R extends RelationRepository>(name: string, relationId?: string | number): Repository | R {
@@ -779,21 +752,34 @@ export class Database extends EventEmitter implements AsyncEmitter {
779
752
  }
780
753
 
781
754
  on(event: EventType, listener: any): this;
755
+
782
756
  on(event: ModelValidateEventTypes, listener: SyncListener): this;
757
+
783
758
  on(event: ModelValidateEventTypes, listener: ValidateListener): this;
759
+
784
760
  on(event: ModelCreateEventTypes, listener: CreateListener): this;
761
+
785
762
  on(event: ModelUpdateEventTypes, listener: UpdateListener): this;
763
+
786
764
  on(event: ModelSaveEventTypes, listener: SaveListener): this;
765
+
787
766
  on(event: ModelDestroyEventTypes, listener: DestroyListener): this;
767
+
788
768
  on(event: ModelCreateWithAssociationsEventTypes, listener: CreateWithAssociationsListener): this;
769
+
789
770
  on(event: ModelUpdateWithAssociationsEventTypes, listener: UpdateWithAssociationsListener): this;
771
+
790
772
  on(event: ModelSaveWithAssociationsEventTypes, listener: SaveWithAssociationsListener): this;
773
+
791
774
  on(event: DatabaseBeforeDefineCollectionEventType, listener: BeforeDefineCollectionListener): this;
775
+
792
776
  on(event: DatabaseAfterDefineCollectionEventType, listener: AfterDefineCollectionListener): this;
777
+
793
778
  on(
794
779
  event: DatabaseBeforeRemoveCollectionEventType | DatabaseAfterRemoveCollectionEventType,
795
780
  listener: RemoveCollectionListener,
796
781
  ): this;
782
+
797
783
  on(event: EventType, listener: any): this {
798
784
  // NOTE: to match if event is a sequelize or model type
799
785
  const type = this.modelHook.match(event);
@@ -844,8 +830,6 @@ export class Database extends EventEmitter implements AsyncEmitter {
844
830
 
845
831
  return result;
846
832
  }
847
-
848
- declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
849
833
  }
850
834
 
851
835
  export function extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions) {
@@ -0,0 +1,304 @@
1
+ import { Association, Includeable, Model, ModelStatic, Transaction } from 'sequelize';
2
+ import lodash from 'lodash';
3
+
4
+ interface EagerLoadingNode {
5
+ model: ModelStatic<any>;
6
+ association: Association;
7
+ attributes: Array<string>;
8
+ rawAttributes: Array<string>;
9
+ children: Array<EagerLoadingNode>;
10
+ parent?: EagerLoadingNode;
11
+ instances?: Array<Model>;
12
+ order?: any;
13
+ }
14
+
15
+ export class EagerLoadingTree {
16
+ public root: EagerLoadingNode;
17
+
18
+ constructor(root: EagerLoadingNode) {
19
+ this.root = root;
20
+ }
21
+
22
+ static buildFromSequelizeOptions(options: {
23
+ model: ModelStatic<any>;
24
+ rootAttributes: Array<string>;
25
+ rootOrder?: any;
26
+ includeOption: Includeable | Includeable[];
27
+ }): EagerLoadingTree {
28
+ const { model, rootAttributes, includeOption } = options;
29
+
30
+ const root = {
31
+ model,
32
+ association: null,
33
+ rawAttributes: lodash.cloneDeep(rootAttributes),
34
+ attributes: lodash.cloneDeep(rootAttributes),
35
+ order: options.rootOrder,
36
+ children: [],
37
+ };
38
+
39
+ const pushAttribute = (node, attribute) => {
40
+ if (lodash.isArray(node.attributes) && !node.attributes.includes(attribute)) {
41
+ node.attributes.push(attribute);
42
+ }
43
+ };
44
+
45
+ const traverseIncludeOption = (includeOption, eagerLoadingTreeParent) => {
46
+ const includeOptions = lodash.castArray(includeOption);
47
+
48
+ if (includeOption.length > 0) {
49
+ const modelPrimaryKey = eagerLoadingTreeParent.model.primaryKeyAttribute;
50
+ pushAttribute(eagerLoadingTreeParent, modelPrimaryKey);
51
+ }
52
+
53
+ for (const include of includeOptions) {
54
+ // skip fromFilter include option
55
+ if (include.fromFilter) {
56
+ continue;
57
+ }
58
+
59
+ const association = eagerLoadingTreeParent.model.associations[include.association];
60
+ const associationType = association.associationType;
61
+
62
+ const child = {
63
+ model: association.target,
64
+ association,
65
+ rawAttributes: lodash.cloneDeep(include.attributes),
66
+ attributes: lodash.cloneDeep(include.attributes),
67
+ parent: eagerLoadingTreeParent,
68
+ children: [],
69
+ };
70
+
71
+ if (associationType == 'HasOne' || associationType == 'HasMany') {
72
+ const { sourceKey, foreignKey } = association;
73
+
74
+ pushAttribute(eagerLoadingTreeParent, sourceKey);
75
+ pushAttribute(child, foreignKey);
76
+ }
77
+
78
+ if (associationType == 'BelongsTo') {
79
+ const { targetKey, foreignKey } = association;
80
+
81
+ pushAttribute(eagerLoadingTreeParent, foreignKey);
82
+ pushAttribute(child, targetKey);
83
+ }
84
+
85
+ if (associationType == 'BelongsToMany') {
86
+ const { sourceKey } = association;
87
+ pushAttribute(eagerLoadingTreeParent, sourceKey);
88
+ }
89
+
90
+ eagerLoadingTreeParent.children.push(child);
91
+
92
+ if (include.include) {
93
+ traverseIncludeOption(include.include, child);
94
+ }
95
+ }
96
+ };
97
+
98
+ traverseIncludeOption(includeOption, root);
99
+
100
+ return new EagerLoadingTree(root);
101
+ }
102
+
103
+ async load(pks: Array<string | number>, transaction?: Transaction) {
104
+ const result = {};
105
+
106
+ const loadRecursive = async (node, ids) => {
107
+ const modelPrimaryKey = node.model.primaryKeyAttribute;
108
+
109
+ let instances = [];
110
+
111
+ // load instances from database
112
+ if (!node.parent) {
113
+ const findOptions = {
114
+ where: { [modelPrimaryKey]: ids },
115
+ attributes: node.attributes,
116
+ };
117
+
118
+ if (node.order) {
119
+ findOptions['order'] = node.order;
120
+ }
121
+
122
+ instances = await node.model.findAll({
123
+ ...findOptions,
124
+ transaction,
125
+ });
126
+ } else if (ids.length > 0) {
127
+ const association = node.association;
128
+ const associationType = association.associationType;
129
+
130
+ if (associationType == 'HasOne' || associationType == 'HasMany') {
131
+ const foreignKey = association.foreignKey;
132
+ const foreignKeyValues = node.parent.instances.map((instance) => instance.get(association.sourceKey));
133
+
134
+ const findOptions = {
135
+ where: { [foreignKey]: foreignKeyValues },
136
+ attributes: node.attributes,
137
+ transaction,
138
+ };
139
+
140
+ instances = await node.model.findAll(findOptions);
141
+ }
142
+
143
+ if (associationType == 'BelongsTo') {
144
+ const foreignKey = association.foreignKey;
145
+
146
+ const parentInstancesForeignKeyValues = node.parent.instances.map((instance) => instance.get(foreignKey));
147
+
148
+ instances = await node.model.findAll({
149
+ transaction,
150
+ where: {
151
+ [association.targetKey]: parentInstancesForeignKeyValues,
152
+ },
153
+ attributes: node.attributes,
154
+ });
155
+ }
156
+
157
+ if (associationType == 'BelongsToMany') {
158
+ const foreignKeyValues = node.parent.instances.map((instance) => instance.get(association.sourceKey));
159
+
160
+ instances = await node.model.findAll({
161
+ transaction,
162
+ attributes: node.attributes,
163
+ include: [
164
+ {
165
+ association: association.oneFromTarget,
166
+ where: {
167
+ [association.foreignKey]: foreignKeyValues,
168
+ },
169
+ },
170
+ ],
171
+ });
172
+ }
173
+ }
174
+
175
+ node.instances = instances;
176
+
177
+ for (const child of node.children) {
178
+ const nodeIds = instances.map((instance) => instance.get(modelPrimaryKey));
179
+
180
+ await loadRecursive(child, nodeIds);
181
+ }
182
+
183
+ // merge instances to parent
184
+ if (!node.parent) {
185
+ return;
186
+ } else {
187
+ const association = node.association;
188
+ const associationType = association.associationType;
189
+
190
+ const setParentAccessor = (parentInstance) => {
191
+ const key = association.as;
192
+
193
+ const children = parentInstance.getDataValue(association.as);
194
+
195
+ if (association.isSingleAssociation) {
196
+ const isEmpty = !children;
197
+ parentInstance[key] = parentInstance.dataValues[key] = isEmpty ? null : children;
198
+ } else {
199
+ const isEmpty = !children || children.length == 0;
200
+ parentInstance[key] = parentInstance.dataValues[key] = isEmpty ? [] : children;
201
+ }
202
+ };
203
+
204
+ if (associationType == 'HasMany' || associationType == 'HasOne') {
205
+ const foreignKey = association.foreignKey;
206
+ const sourceKey = association.sourceKey;
207
+
208
+ for (const instance of node.instances) {
209
+ const parentInstance = node.parent.instances.find(
210
+ (parentInstance) => parentInstance.get(sourceKey) == instance.get(foreignKey),
211
+ );
212
+
213
+ if (parentInstance) {
214
+ if (associationType == 'HasMany') {
215
+ const children = parentInstance.getDataValue(association.as);
216
+ if (!children) {
217
+ parentInstance.setDataValue(association.as, [instance]);
218
+ } else {
219
+ children.push(instance);
220
+ }
221
+ }
222
+
223
+ if (associationType == 'HasOne') {
224
+ parentInstance.setDataValue(association.as, instance);
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ if (associationType == 'BelongsTo') {
231
+ const foreignKey = association.foreignKey;
232
+ const targetKey = association.targetKey;
233
+
234
+ for (const instance of node.instances) {
235
+ const parentInstances = node.parent.instances.filter(
236
+ (parentInstance) => parentInstance.get(foreignKey) == instance.get(targetKey),
237
+ );
238
+
239
+ for (const parentInstance of parentInstances) {
240
+ parentInstance.setDataValue(association.as, instance);
241
+ }
242
+ }
243
+ }
244
+
245
+ if (associationType == 'BelongsToMany') {
246
+ const sourceKey = association.sourceKey;
247
+ const foreignKey = association.foreignKey;
248
+
249
+ const oneFromTarget = association.oneFromTarget;
250
+
251
+ for (const instance of node.instances) {
252
+ const parentInstance = node.parent.instances.find(
253
+ (parentInstance) => parentInstance.get(sourceKey) == instance[oneFromTarget.as].get(foreignKey),
254
+ );
255
+
256
+ if (parentInstance) {
257
+ const children = parentInstance.getDataValue(association.as);
258
+
259
+ if (!children) {
260
+ parentInstance.setDataValue(association.as, [instance]);
261
+ } else {
262
+ children.push(instance);
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ for (const parent of node.parent.instances) {
269
+ setParentAccessor(parent);
270
+ }
271
+ }
272
+ };
273
+
274
+ await loadRecursive(this.root, pks);
275
+
276
+ const setInstanceAttributes = (node) => {
277
+ const nodeRawAttributes = node.rawAttributes;
278
+
279
+ if (!lodash.isArray(nodeRawAttributes)) {
280
+ return;
281
+ }
282
+
283
+ const nodeChildrenAs = node.children.map((child) => child.association.as);
284
+ const includeAttributes = [...nodeRawAttributes, ...nodeChildrenAs];
285
+
286
+ for (const instance of node.instances) {
287
+ const attributes = lodash.pick(instance.dataValues, includeAttributes);
288
+ instance.dataValues = attributes;
289
+ }
290
+ };
291
+
292
+ // traverse tree and set instance attributes
293
+ const traverse = (node) => {
294
+ setInstanceAttributes(node);
295
+
296
+ for (const child of node.children) {
297
+ traverse(child);
298
+ }
299
+ };
300
+
301
+ traverse(this.root);
302
+ return result;
303
+ }
304
+ }
@@ -217,6 +217,7 @@ export abstract class Field {
217
217
  bind() {
218
218
  const { model } = this.context.collection;
219
219
  model.rawAttributes[this.name] = this.toSequelize();
220
+
220
221
  // @ts-ignore
221
222
  model.refreshAttributes();
222
223
  if (this.options.index) {
@@ -226,6 +227,9 @@ export abstract class Field {
226
227
 
227
228
  unbind() {
228
229
  const { model } = this.context.collection;
230
+
231
+ delete model.prototype[this.name];
232
+
229
233
  model.removeAttribute(this.name);
230
234
  if (this.options.index || this.options.unique) {
231
235
  this.context.collection.removeIndex([this.name]);
@@ -56,7 +56,7 @@ export default class FilterParser {
56
56
  return filter;
57
57
  }
58
58
 
59
- toSequelizeParams() {
59
+ toSequelizeParams(): any {
60
60
  debug('filter %o', this.filter);
61
61
 
62
62
  if (!this.filter) {
@@ -230,7 +230,21 @@ export default class FilterParser {
230
230
  });
231
231
  };
232
232
  debug('where %o, include %o', where, include);
233
- return { where, include: toInclude(include) };
233
+ const results = { where, include: toInclude(include) };
234
+
235
+ //traverse filter include, set fromFiler to true
236
+ const traverseInclude = (include) => {
237
+ for (const item of include) {
238
+ if (item.include) {
239
+ traverseInclude(item.include);
240
+ }
241
+ item.fromFilter = true;
242
+ }
243
+ };
244
+
245
+ traverseInclude(results.include);
246
+
247
+ return results;
234
248
  }
235
249
 
236
250
  private getFieldNameFromQueryPath(queryPath: string) {