@nocobase/database 0.7.4-alpha.7 → 0.7.6-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 (101) hide show
  1. package/lib/collection.d.ts +7 -2
  2. package/lib/collection.js +20 -8
  3. package/lib/database.d.ts +2 -2
  4. package/lib/database.js +30 -6
  5. package/lib/decorators/must-have-filter-decorator.d.ts +2 -0
  6. package/lib/decorators/must-have-filter-decorator.js +25 -0
  7. package/lib/{transaction-decorator.d.ts → decorators/transaction-decorator.d.ts} +0 -0
  8. package/lib/{transaction-decorator.js → decorators/transaction-decorator.js} +1 -4
  9. package/lib/errors/identifier-error.d.ts +3 -0
  10. package/lib/errors/identifier-error.js +16 -0
  11. package/lib/fields/belongs-to-field.js +9 -0
  12. package/lib/fields/belongs-to-many-field.js +10 -0
  13. package/lib/fields/date-field.d.ts +1 -1
  14. package/lib/fields/date-field.js +1 -1
  15. package/lib/fields/field.d.ts +1 -1
  16. package/lib/fields/field.js +2 -3
  17. package/lib/fields/formula-field.d.ts +5 -5
  18. package/lib/fields/formula-field.js +123 -110
  19. package/lib/fields/has-many-field.js +9 -0
  20. package/lib/fields/has-one-field.js +9 -0
  21. package/lib/fields/index.d.ts +3 -1
  22. package/lib/fields/index.js +34 -1
  23. package/lib/fields/number-field.d.ts +6 -0
  24. package/lib/fields/number-field.js +10 -1
  25. package/lib/fields/radio-field.d.ts +3 -1
  26. package/lib/fields/radio-field.js +14 -11
  27. package/lib/fields/sequence-field.d.ts +31 -0
  28. package/lib/fields/sequence-field.js +299 -0
  29. package/lib/fields/sort-field.d.ts +4 -4
  30. package/lib/fields/sort-field.js +93 -80
  31. package/lib/filter-parser.js +10 -2
  32. package/lib/index.d.ts +1 -1
  33. package/lib/index.js +7 -0
  34. package/lib/magic-attribute-model.d.ts +1 -0
  35. package/lib/magic-attribute-model.js +207 -6
  36. package/lib/mock-database.js +1 -1
  37. package/lib/model.d.ts +5 -0
  38. package/lib/model.js +48 -6
  39. package/lib/operators/array.js +17 -9
  40. package/lib/operators/ne.d.ts +4 -0
  41. package/lib/operators/ne.js +3 -1
  42. package/lib/relation-repository/belongs-to-many-repository.js +6 -0
  43. package/lib/relation-repository/multiple-relation-repository.js +32 -9
  44. package/lib/relation-repository/relation-repository.js +7 -1
  45. package/lib/relation-repository/single-relation-repository.js +28 -0
  46. package/lib/repository.d.ts +6 -3
  47. package/lib/repository.js +42 -9
  48. package/lib/update-associations.js +48 -71
  49. package/lib/utils.d.ts +9 -0
  50. package/lib/utils.js +126 -0
  51. package/package.json +5 -3
  52. package/src/__tests__/collection.test.ts +47 -0
  53. package/src/__tests__/database.test.ts +2 -0
  54. package/src/__tests__/field-options/inddex.test.ts +43 -0
  55. package/src/__tests__/fields/array.test.ts +66 -0
  56. package/src/__tests__/fields/belongs-to-field.test.ts +35 -0
  57. package/src/__tests__/fields/belongs-to-many-field.test.ts +45 -0
  58. package/src/__tests__/fields/has-many-field.test.ts +22 -0
  59. package/src/__tests__/fields/has-one-field.test.ts +21 -0
  60. package/src/__tests__/fields/sequence-field.test.ts +455 -0
  61. package/src/__tests__/magic-attribute-model.test.ts +24 -0
  62. package/src/__tests__/model.changedWithAssociations.test.ts +46 -0
  63. package/src/__tests__/operator/ne.test.ts +12 -0
  64. package/src/__tests__/relation-repository/appends.test.ts +64 -0
  65. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +23 -0
  66. package/src/__tests__/relation-repository/has-many-repository.test.ts +20 -0
  67. package/src/__tests__/relation-repository/hasone-repository.test.ts +68 -1
  68. package/src/__tests__/repository/find.test.ts +35 -0
  69. package/src/__tests__/repository/update.test.ts +35 -0
  70. package/src/__tests__/repository.test.ts +15 -0
  71. package/src/collection.ts +28 -13
  72. package/src/database.ts +18 -6
  73. package/src/decorators/must-have-filter-decorator.ts +17 -0
  74. package/src/{transaction-decorator.ts → decorators/transaction-decorator.ts} +1 -2
  75. package/src/errors/identifier-error.ts +6 -0
  76. package/src/fields/belongs-to-field.ts +9 -0
  77. package/src/fields/belongs-to-many-field.ts +15 -0
  78. package/src/fields/date-field.ts +1 -1
  79. package/src/fields/field.ts +3 -3
  80. package/src/fields/formula-field.ts +13 -13
  81. package/src/fields/has-many-field.ts +10 -0
  82. package/src/fields/has-one-field.ts +13 -0
  83. package/src/fields/index.ts +5 -2
  84. package/src/fields/number-field.ts +10 -0
  85. package/src/fields/radio-field.ts +22 -24
  86. package/src/fields/sequence-field.ts +200 -0
  87. package/src/fields/sort-field.ts +9 -9
  88. package/src/filter-parser.ts +6 -5
  89. package/src/index.ts +1 -1
  90. package/src/magic-attribute-model.ts +188 -6
  91. package/src/mock-database.ts +1 -1
  92. package/src/model.ts +34 -1
  93. package/src/operators/array.ts +17 -10
  94. package/src/operators/ne.ts +10 -6
  95. package/src/relation-repository/belongs-to-many-repository.ts +4 -0
  96. package/src/relation-repository/multiple-relation-repository.ts +26 -11
  97. package/src/relation-repository/relation-repository.ts +5 -1
  98. package/src/relation-repository/single-relation-repository.ts +27 -1
  99. package/src/repository.ts +40 -10
  100. package/src/update-associations.ts +41 -53
  101. package/src/utils.ts +71 -0
@@ -9,6 +9,9 @@ describe('has one repository', () => {
9
9
  let User: Collection;
10
10
  let Profile: Collection;
11
11
 
12
+ let A1: Collection;
13
+ let A2: Collection;
14
+
12
15
  afterEach(async () => {
13
16
  await db.close();
14
17
  });
@@ -25,12 +28,75 @@ describe('has one repository', () => {
25
28
 
26
29
  Profile = db.collection({
27
30
  name: 'profiles',
28
- fields: [{ type: 'string', name: 'avatar' }],
31
+ fields: [
32
+ { type: 'string', name: 'avatar' },
33
+ {
34
+ type: 'hasMany',
35
+ name: 'a1',
36
+ },
37
+ {
38
+ type: 'hasMany',
39
+ name: 'a2',
40
+ },
41
+ ],
42
+ });
43
+
44
+ A1 = db.collection({
45
+ name: 'a1',
46
+ fields: [{ type: 'string', name: 'name' }],
47
+ });
48
+
49
+ A2 = db.collection({
50
+ name: 'a2',
51
+ fields: [{ type: 'string', name: 'name' }],
29
52
  });
30
53
 
31
54
  await db.sync();
32
55
  });
33
56
 
57
+ test('find with appends', async () => {
58
+ const user = await User.repository.create({
59
+ values: {
60
+ name: 'u1',
61
+ profile: {
62
+ avatar: 'avatar',
63
+ a1: [
64
+ {
65
+ name: 'a11',
66
+ },
67
+ {
68
+ name: 'a12',
69
+ },
70
+ {
71
+ name: 'a13',
72
+ },
73
+ ],
74
+ a2: [
75
+ {
76
+ name: 'a21',
77
+ },
78
+ {
79
+ name: 'a22',
80
+ },
81
+ {
82
+ name: 'a23',
83
+ },
84
+ ],
85
+ },
86
+ },
87
+ });
88
+
89
+ const UserProfileRepository = new HasOneRepository(User, 'profile', user['id']);
90
+
91
+ const profile = await UserProfileRepository.find({
92
+ appends: ['a1', 'a2'],
93
+ });
94
+
95
+ const data = profile.toJSON();
96
+ expect(data['a1']).toBeDefined();
97
+ expect(data['a2']).toBeDefined();
98
+ });
99
+
34
100
  test('find', async () => {
35
101
  const user = await User.repository.create({
36
102
  values: { name: 'u1' },
@@ -70,6 +136,7 @@ describe('has one repository', () => {
70
136
  avatar: 'new_updated_avatar',
71
137
  },
72
138
  });
139
+
73
140
  expect((await UserProfileRepository.find())['avatar']).toEqual('new_updated_avatar');
74
141
  await UserProfileRepository.destroy();
75
142
  expect(await UserProfileRepository.find()).toBeNull();
@@ -10,6 +10,9 @@ describe('repository find', () => {
10
10
  let Comment: Collection;
11
11
  let Tag: Collection;
12
12
 
13
+ let A1: Collection;
14
+ let A2: Collection;
15
+
13
16
  afterEach(async () => {
14
17
  await db.close();
15
18
  });
@@ -23,9 +26,21 @@ describe('repository find', () => {
23
26
  { type: 'string', name: 'name' },
24
27
  { type: 'integer', name: 'age' },
25
28
  { type: 'hasMany', name: 'posts' },
29
+ { type: 'belongsToMany', name: 'a1' },
30
+ { type: 'belongsToMany', name: 'a2' },
26
31
  ],
27
32
  });
28
33
 
34
+ A1 = db.collection({
35
+ name: 'a1',
36
+ fields: [{ type: 'string', name: 'name' }],
37
+ });
38
+
39
+ A2 = db.collection({
40
+ name: 'a2',
41
+ fields: [{ type: 'string', name: 'name' }],
42
+ });
43
+
29
44
  Post = db.collection({
30
45
  name: 'posts',
31
46
  fields: [
@@ -73,11 +88,15 @@ describe('repository find', () => {
73
88
  name: 'u1',
74
89
  age: 10,
75
90
  posts: [{ title: 'u1t1', comments: [{ content: 'u1t1c1' }], abc1: [{ name: 't1' }] }],
91
+ a1: [{ name: 'u1a11' }, { name: 'u1a12' }],
92
+ a2: [{ name: 'u1a21' }, { name: 'u1a22' }],
76
93
  },
77
94
  {
78
95
  name: 'u2',
79
96
  age: 20,
80
97
  posts: [{ title: 'u2t1', comments: [{ content: 'u2t1c1' }] }],
98
+ a1: [{ name: 'u2a11' }, { name: 'u2a12' }],
99
+ a2: [{ name: 'u2a21' }, { name: 'u2a22' }],
81
100
  },
82
101
  {
83
102
  name: 'u3',
@@ -161,6 +180,22 @@ describe('repository find', () => {
161
180
  });
162
181
 
163
182
  describe('find with appends', () => {
183
+ test('toJSON', async () => {
184
+ const user = await User.repository.findOne({
185
+ filter: {
186
+ name: 'u1',
187
+ },
188
+ appends: ['a1', 'a2'],
189
+ });
190
+
191
+ const data = user.toJSON();
192
+
193
+ expect(user['a1']).toBeDefined();
194
+ expect(user['a2']).toBeDefined();
195
+ expect(data['a1'][0].createdAt).toBeDefined();
196
+ expect(data['a2'][0].createdAt).toBeDefined();
197
+ });
198
+
164
199
  test('filter attribute', async () => {
165
200
  const user = await User.repository.findOne({
166
201
  filter: {
@@ -57,4 +57,39 @@ describe('update', () => {
57
57
 
58
58
  expect(p1.toJSON()['tags']).toEqual([]);
59
59
  });
60
+
61
+ it('should not update items without filter or filterByPk', async () => {
62
+ await db.getRepository('posts').create({
63
+ values: {
64
+ title: 'p1',
65
+ },
66
+ });
67
+
68
+ let error;
69
+
70
+ try {
71
+ await db.getRepository('posts').update({
72
+ values: {
73
+ title: 'p3',
74
+ },
75
+ });
76
+ } catch (e) {
77
+ error = e;
78
+ }
79
+
80
+ expect(error).not.toBeUndefined();
81
+
82
+ const p1 = await db.getRepository('posts').findOne();
83
+ expect(p1.toJSON()['title']).toEqual('p1');
84
+
85
+ await db.getRepository('posts').update({
86
+ values: {
87
+ title: 'p3',
88
+ },
89
+ filterByTk: p1.get('id') as number,
90
+ });
91
+
92
+ await p1.reload();
93
+ expect(p1.toJSON()['title']).toEqual('p3');
94
+ });
60
95
  });
@@ -234,6 +234,21 @@ describe('repository.create', () => {
234
234
  const comments = await Comment.model.findAll();
235
235
  expect(comments.map((m) => m.get('postId'))).toEqual([post.get('id'), post.get('id'), post.get('id')]);
236
236
  });
237
+
238
+ it('can create with array of values', async () => {
239
+ const users = await User.repository.create({
240
+ values: [
241
+ {
242
+ name: 'u1',
243
+ },
244
+ {
245
+ name: 'u2',
246
+ },
247
+ ],
248
+ });
249
+
250
+ expect(users.length).toEqual(2);
251
+ });
237
252
  });
238
253
 
239
254
  describe('repository.update', () => {
package/src/collection.ts CHANGED
@@ -7,12 +7,13 @@ import {
7
7
  QueryInterfaceDropTableOptions,
8
8
  SyncOptions,
9
9
  Transactionable,
10
- Utils
10
+ Utils,
11
11
  } from 'sequelize';
12
12
  import { Database } from './database';
13
13
  import { Field, FieldOptions } from './fields';
14
14
  import { Model } from './model';
15
15
  import { Repository } from './repository';
16
+ import { checkIdentifier, md5 } from './utils';
16
17
 
17
18
  export type RepositoryType = typeof Repository;
18
19
 
@@ -66,8 +67,11 @@ export class Collection<
66
67
 
67
68
  constructor(options: CollectionOptions, context?: CollectionContext) {
68
69
  super();
70
+ this.checkOptions(options);
71
+
69
72
  this.context = context;
70
73
  this.options = options;
74
+
71
75
  this.bindFieldEventListener();
72
76
  this.modelInit();
73
77
  this.setFields(options.fields);
@@ -75,6 +79,10 @@ export class Collection<
75
79
  this.setSortable(options.sortable);
76
80
  }
77
81
 
82
+ private checkOptions(options: CollectionOptions) {
83
+ checkIdentifier(options.name);
84
+ }
85
+
78
86
  private sequelizeModelOptions() {
79
87
  const { name, tableName } = this.options;
80
88
  return {
@@ -134,10 +142,8 @@ export class Collection<
134
142
  }
135
143
 
136
144
  private bindFieldEventListener() {
137
- this.on('field.afterAdd', (field: Field) => {
138
- field.bind();
139
- });
140
- this.on('field.afterRemove', (field) => field.unbind());
145
+ this.on('field.afterAdd', (field: Field) => field.bind());
146
+ this.on('field.afterRemove', (field: Field) => field.unbind());
141
147
  }
142
148
 
143
149
  forEachField(callback: (field: Field) => void) {
@@ -161,6 +167,8 @@ export class Collection<
161
167
  }
162
168
 
163
169
  setField(name: string, options: FieldOptions): Field {
170
+ checkIdentifier(name);
171
+
164
172
  const { database } = this.context;
165
173
 
166
174
  const field = database.buildField(
@@ -170,6 +178,7 @@ export class Collection<
170
178
  collection: this,
171
179
  },
172
180
  );
181
+
173
182
  this.removeField(name);
174
183
  this.fields.set(name, field);
175
184
  this.emit('field.afterAdd', field);
@@ -217,7 +226,7 @@ export class Collection<
217
226
  return this.db.collectionExistsInDb(this.name, options);
218
227
  }
219
228
 
220
- removeField(name) {
229
+ removeField(name: string): void | Field {
221
230
  if (!this.fields.has(name)) {
222
231
  return;
223
232
  }
@@ -285,7 +294,7 @@ export class Collection<
285
294
  this.setField(options.name || name, options);
286
295
  }
287
296
 
288
- addIndex(index: any) {
297
+ addIndex(index: string | string[] | { fields: string[]; unique?: boolean; [key: string]: any }) {
289
298
  if (!index) {
290
299
  return;
291
300
  }
@@ -331,7 +340,13 @@ export class Collection<
331
340
  // @ts-ignore
332
341
  this.model._indexes = this.model.options.indexes
333
342
  // @ts-ignore
334
- .map((index) => Utils.nameIndex(this.model._conformIndex(index), tableName));
343
+ .map((index) => Utils.nameIndex(this.model._conformIndex(index), tableName))
344
+ .map((item) => {
345
+ if (item.name && item.name.length > 63) {
346
+ item.name = 'i_' + md5(item.name);
347
+ }
348
+ return item;
349
+ });
335
350
  }
336
351
 
337
352
  removeIndex(fields: any) {
@@ -347,22 +362,22 @@ export class Collection<
347
362
  }
348
363
 
349
364
  async sync(syncOptions?: SyncOptions) {
350
- const modelNames = [this.model.name];
365
+ const modelNames = new Set([this.model.name]);
351
366
 
352
- const associations = this.model.associations;
367
+ const { associations } = this.model;
353
368
 
354
369
  for (const associationKey in associations) {
355
370
  const association = associations[associationKey];
356
- modelNames.push(association.target.name);
371
+ modelNames.add(association.target.name);
357
372
  if ((<any>association).through) {
358
- modelNames.push((<any>association).through.model.name);
373
+ modelNames.add((<any>association).through.model.name);
359
374
  }
360
375
  }
361
376
 
362
377
  const models: ModelCtor<Model>[] = [];
363
378
  // @ts-ignore
364
379
  this.context.database.sequelize.modelManager.forEachModel((model) => {
365
- if (modelNames.includes(model.name)) {
380
+ if (modelNames.has(model.name)) {
366
381
  models.push(model);
367
382
  }
368
383
  });
package/src/database.ts CHANGED
@@ -21,7 +21,7 @@ import { Collection, CollectionOptions, RepositoryType } from './collection';
21
21
  import { ImporterReader, ImportFileExtension } from './collection-importer';
22
22
  import * as FieldTypes from './fields';
23
23
  import { Field, FieldContext, RelationField } from './fields';
24
- import { Migrations } from './migration';
24
+ import { MigrationItem, Migrations } from './migration';
25
25
  import { Model } from './model';
26
26
  import { ModelHook } from './model-hook';
27
27
  import extendOperators from './operators';
@@ -81,14 +81,16 @@ class DatabaseVersion {
81
81
  },
82
82
  mysql: {
83
83
  sql: 'select version() as version',
84
- get: (v) => v,
84
+ get: (v) => {
85
+ const m = /([\d+\.]+)/.exec(v);
86
+ return m[0];
87
+ },
85
88
  },
86
89
  postgres: {
87
90
  sql: 'select version() as version',
88
91
  get: (v) => {
89
- const keys = v.split(/\s|,/);
90
- keys.shift();
91
- return semver.minVersion(keys.shift()).version;
92
+ const m = /([\d+\.]+)/.exec(v);
93
+ return semver.minVersion(m[0]).version;
92
94
  },
93
95
  },
94
96
  };
@@ -203,9 +205,17 @@ export class Database extends EventEmitter implements AsyncEmitter {
203
205
  opts.tableName = `${this.options.tablePrefix}${opts.tableName || opts.modelName || opts.name.plural}`;
204
206
  }
205
207
  });
208
+
209
+ this.on('afterCreate', async (instance) => {
210
+ instance?.toChangedWithAssociations?.();
211
+ });
212
+
213
+ this.on('afterUpdate', async (instance) => {
214
+ instance?.toChangedWithAssociations?.();
215
+ });
206
216
  }
207
217
 
208
- addMigration(item) {
218
+ addMigration(item: MigrationItem) {
209
219
  return this.migrations.add(item);
210
220
  }
211
221
 
@@ -369,9 +379,11 @@ export class Database extends EventEmitter implements AsyncEmitter {
369
379
  buildField(options, context: FieldContext) {
370
380
  const { type } = options;
371
381
  const Field = this.fieldTypes.get(type);
382
+
372
383
  if (!Field) {
373
384
  throw Error(`unsupported field type ${type}`);
374
385
  }
386
+
375
387
  return new Field(options, context);
376
388
  }
377
389
 
@@ -0,0 +1,17 @@
1
+ const mustHaveFilter = () => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
2
+ const oldValue = descriptor.value;
3
+
4
+ descriptor.value = function () {
5
+ const options = arguments[0];
6
+
7
+ if (!options?.filter && !options?.filterByTk && !options?.forceUpdate) {
8
+ throw new Error(`must provide filter or filterByTk for ${propertyKey} call, or set forceUpdate to true`);
9
+ }
10
+
11
+ return oldValue.apply(this, arguments);
12
+ };
13
+
14
+ return descriptor;
15
+ };
16
+
17
+ export default mustHaveFilter;
@@ -37,13 +37,12 @@ export function transactionWrapperBuilder(transactionGenerator) {
37
37
  throw new Error(`please provide transactionInjector for ${name} call`);
38
38
  }
39
39
 
40
- const results = await oldValue.apply(this, [callArguments]);
40
+ const results = await oldValue.call(this, callArguments);
41
41
 
42
42
  await transaction.commit();
43
43
 
44
44
  return results;
45
45
  } catch (err) {
46
- console.error({ err });
47
46
  await transaction.rollback();
48
47
  throw err;
49
48
  }
@@ -0,0 +1,6 @@
1
+ export class IdentifierError extends Error {
2
+ constructor(message: string) {
3
+ super(message);
4
+ this.name = 'IdentifierError';
5
+ }
6
+ }
@@ -1,5 +1,6 @@
1
1
  import { omit } from 'lodash';
2
2
  import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
3
+ import { checkIdentifier } from '../utils';
3
4
  import { BaseRelationFieldOptions, RelationField } from './relation-field';
4
5
 
5
6
  export class BelongsToField extends RelationField {
@@ -42,10 +43,18 @@ export class BelongsToField extends RelationField {
42
43
  this.options.foreignKey = association.foreignKey;
43
44
  }
44
45
 
46
+ try {
47
+ checkIdentifier(this.options.foreignKey);
48
+ } catch (error) {
49
+ this.unbind();
50
+ throw error;
51
+ }
52
+
45
53
  if (!this.options.sourceKey) {
46
54
  // @ts-ignore
47
55
  this.options.sourceKey = association.sourceKey;
48
56
  }
57
+
49
58
  this.collection.addIndex([this.options.foreignKey]);
50
59
  return true;
51
60
  }
@@ -1,6 +1,7 @@
1
1
  import { omit } from 'lodash';
2
2
  import { BelongsToManyOptions as SequelizeBelongsToManyOptions, Utils } from 'sequelize';
3
3
  import { Collection } from '../collection';
4
+ import { checkIdentifier } from '../utils';
4
5
  import { MultipleRelationFieldOptions, RelationField } from './relation-field';
5
6
 
6
7
  export class BelongsToManyField extends RelationField {
@@ -23,6 +24,7 @@ export class BelongsToManyField extends RelationField {
23
24
  database.addPendingField(this);
24
25
  return false;
25
26
  }
27
+
26
28
  const through = this.through;
27
29
 
28
30
  let Through: Collection;
@@ -33,6 +35,7 @@ export class BelongsToManyField extends RelationField {
33
35
  Through = database.collection({
34
36
  name: through,
35
37
  });
38
+
36
39
  Object.defineProperty(Through.model, 'isThrough', { value: true });
37
40
  }
38
41
 
@@ -49,15 +52,27 @@ export class BelongsToManyField extends RelationField {
49
52
  if (!this.options.foreignKey) {
50
53
  this.options.foreignKey = association.foreignKey;
51
54
  }
55
+
52
56
  if (!this.options.sourceKey) {
53
57
  this.options.sourceKey = association.sourceKey;
54
58
  }
59
+
55
60
  if (!this.options.otherKey) {
56
61
  this.options.otherKey = association.otherKey;
57
62
  }
63
+
64
+ try {
65
+ checkIdentifier(this.options.foreignKey);
66
+ checkIdentifier(this.options.otherKey);
67
+ } catch (error) {
68
+ this.unbind();
69
+ throw error;
70
+ }
71
+
58
72
  if (!this.options.through) {
59
73
  this.options.through = this.through;
60
74
  }
75
+
61
76
  Through.addIndex([this.options.foreignKey]);
62
77
  Through.addIndex([this.options.otherKey]);
63
78
  return true;
@@ -3,7 +3,7 @@ import { BaseColumnFieldOptions, Field } from './field';
3
3
 
4
4
  export class DateField extends Field {
5
5
  get dataType() {
6
- return DataTypes.DATE;
6
+ return DataTypes.DATE(3);
7
7
  }
8
8
  }
9
9
 
@@ -5,10 +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 { checkIdentifier } from '../utils';
12
13
 
13
14
  export interface FieldContext {
14
15
  database: Database;
@@ -53,7 +54,6 @@ export abstract class Field {
53
54
  this.init();
54
55
  }
55
56
 
56
- // TODO
57
57
  async sync(syncOptions: SyncOptions) {
58
58
  await this.collection.sync({
59
59
  ...syncOptions,
@@ -179,7 +179,7 @@ export abstract class Field {
179
179
  unbind() {
180
180
  const { model } = this.context.collection;
181
181
  model.removeAttribute(this.name);
182
- if (this.options.index) {
182
+ if (this.options.index || this.options.unique) {
183
183
  this.context.collection.removeIndex([this.name]);
184
184
  }
185
185
  }
@@ -16,7 +16,7 @@ export class FormulaField extends Field {
16
16
  return result;
17
17
  }
18
18
 
19
- async initFieldData({ transaction }) {
19
+ initFieldData = async ({ transaction }) => {
20
20
  const { expression, name } = this.options;
21
21
 
22
22
  const records = await this.collection.repository.find({
@@ -42,7 +42,7 @@ export class FormulaField extends Field {
42
42
  }
43
43
  }
44
44
 
45
- async caculateField(instance) {
45
+ caculateField = async (instance) => {
46
46
  const { expression, name } = this.options;
47
47
  const scope = instance.toJSON();
48
48
  let result;
@@ -55,7 +55,7 @@ export class FormulaField extends Field {
55
55
  }
56
56
  }
57
57
 
58
- async updateFieldData(instance, { transaction }) {
58
+ updateFieldData = async (instance, { transaction }) => {
59
59
  if (this.collection.name === instance.collectionName && instance.name === this.options.name) {
60
60
  this.options = Object.assign(this.options, instance.options);
61
61
  const { name, expression } = this.options;
@@ -64,7 +64,7 @@ export class FormulaField extends Field {
64
64
  order: [this.collection.model.primaryKeyAttribute],
65
65
  transaction,
66
66
  });
67
-
67
+
68
68
  for (const record of records) {
69
69
  const scope = record.toJSON();
70
70
  const result = this.caculate(expression, scope);
@@ -84,18 +84,18 @@ export class FormulaField extends Field {
84
84
 
85
85
  bind() {
86
86
  super.bind();
87
- this.on('afterSync', this.initFieldData.bind(this));
88
- this.database.on('fields.afterUpdate', this.updateFieldData.bind(this));
89
- this.on('beforeCreate', this.caculateField.bind(this));
90
- this.on('beforeUpdate', this.caculateField.bind(this));
87
+ this.on('afterSync', this.initFieldData);
88
+ // TODO: should not depends on fields table (which is defined by other plugin)
89
+ this.database.on('fields.afterUpdate', this.updateFieldData);
90
+ this.on('beforeSave', this.caculateField);
91
91
  }
92
92
 
93
93
  unbind() {
94
94
  super.unbind();
95
- this.off('beforeCreate', this.caculateField.bind(this));
96
- this.off('beforeUpdate', this.caculateField.bind(this));
97
- this.database.off('fields.afterUpdate', this.updateFieldData.bind(this));
98
- this.off('afterSync', this.initFieldData.bind(this));
95
+ this.off('beforeSave', this.caculateField);
96
+ // TODO: should not depends on fields table
97
+ this.database.off('fields.afterUpdate', this.updateFieldData);
98
+ this.off('afterSync', this.initFieldData);
99
99
  }
100
100
  }
101
101
 
@@ -103,4 +103,4 @@ export interface FormulaFieldOptions extends BaseColumnFieldOptions {
103
103
  type: 'formula';
104
104
 
105
105
  expression: string;
106
- }
106
+ }
@@ -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 { MultipleRelationFieldOptions, RelationField } from './relation-field';
12
13
 
13
14
  export interface HasManyFieldOptions extends HasManyOptions {
@@ -98,6 +99,7 @@ export class HasManyField extends RelationField {
98
99
  as: this.name,
99
100
  foreignKey: this.foreignKey,
100
101
  });
102
+
101
103
  // inverse relation
102
104
  // this.TargetModel.belongsTo(collection.model);
103
105
 
@@ -107,6 +109,14 @@ export class HasManyField extends RelationField {
107
109
  if (!this.options.foreignKey) {
108
110
  this.options.foreignKey = association.foreignKey;
109
111
  }
112
+
113
+ try {
114
+ checkIdentifier(this.options.foreignKey);
115
+ } catch (error) {
116
+ this.unbind();
117
+ throw error;
118
+ }
119
+
110
120
  if (!this.options.sourceKey) {
111
121
  // @ts-ignore
112
122
  this.options.sourceKey = association.sourceKey;